mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Control flow analysis for inline assembly.
This commit is contained in:
parent
b7c001eb7f
commit
809e3503ba
@ -3,6 +3,7 @@
|
|||||||
Language Features:
|
Language Features:
|
||||||
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
|
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
|
||||||
* General: Deprecated `value(...)` and `gas(...)` in favor of `{value: ...}` and `{gas: ...}`
|
* General: Deprecated `value(...)` and `gas(...)` in favor of `{value: ...}` and `{gas: ...}`
|
||||||
|
* Inline Assembly: Perform control flow analysis on inline assembly. Allows storage returns to be set in assembly only.
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
|
@ -155,6 +155,26 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::reverts(AssemblyItem const& _item)
|
||||||
|
{
|
||||||
|
if (_item.type() != Operation)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return reverts(_item.instruction());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::reverts(Instruction _instruction)
|
||||||
|
{
|
||||||
|
switch (_instruction)
|
||||||
|
{
|
||||||
|
case Instruction::INVALID:
|
||||||
|
case Instruction::REVERT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
||||||
{
|
{
|
||||||
if (_item.type() != Operation)
|
if (_item.type() != Operation)
|
||||||
|
@ -47,6 +47,8 @@ struct SemanticInformation
|
|||||||
static bool altersControlFlow(AssemblyItem const& _item);
|
static bool altersControlFlow(AssemblyItem const& _item);
|
||||||
static bool terminatesControlFlow(AssemblyItem const& _item);
|
static bool terminatesControlFlow(AssemblyItem const& _item);
|
||||||
static bool terminatesControlFlow(Instruction _instruction);
|
static bool terminatesControlFlow(Instruction _instruction);
|
||||||
|
static bool reverts(AssemblyItem const& _item);
|
||||||
|
static bool reverts(Instruction _instruction);
|
||||||
/// @returns false if the value put on the stack by _item depends on anything else than
|
/// @returns false if the value put on the stack by _item depends on anything else than
|
||||||
/// the information in the current block header, memory, storage or stack.
|
/// the information in the current block header, memory, storage or stack.
|
||||||
static bool isDeterministic(AssemblyItem const& _item);
|
static bool isDeterministic(AssemblyItem const& _item);
|
||||||
|
@ -37,7 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
|
|||||||
{
|
{
|
||||||
auto const& functionFlow = m_cfg.functionFlow(_function);
|
auto const& functionFlow = m_cfg.functionFlow(_function);
|
||||||
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
|
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
|
||||||
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert);
|
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
|||||||
|
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
variableOccurrence->occurrence() ?
|
variableOccurrence->occurrence() ?
|
||||||
variableOccurrence->occurrence()->location() :
|
*variableOccurrence->occurrence() :
|
||||||
variableOccurrence->declaration().location(),
|
variableOccurrence->declaration().location(),
|
||||||
ssl,
|
ssl,
|
||||||
string("This variable is of storage pointer type and can be ") +
|
string("This variable is of storage pointer type and can be ") +
|
||||||
@ -148,7 +148,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const
|
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const
|
||||||
{
|
{
|
||||||
// collect all nodes reachable from the entry point
|
// collect all nodes reachable from the entry point
|
||||||
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
||||||
@ -158,10 +158,10 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
|
|||||||
}
|
}
|
||||||
).visited;
|
).visited;
|
||||||
|
|
||||||
// traverse all paths backwards from exit and revert
|
// traverse all paths backwards from exit, revert and transaction return
|
||||||
// and extract (valid) source locations of unreachable nodes into sorted set
|
// and extract (valid) source locations of unreachable nodes into sorted set
|
||||||
std::set<SourceLocation> unreachable;
|
std::set<SourceLocation> unreachable;
|
||||||
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
|
||||||
[&](CFGNode const* _node, auto&& _addChild) {
|
[&](CFGNode const* _node, auto&& _addChild) {
|
||||||
if (!reachable.count(_node) && _node->location.isValid())
|
if (!reachable.count(_node) && _node->location.isValid())
|
||||||
unreachable.insert(_node->location);
|
unreachable.insert(_node->location);
|
||||||
|
@ -36,9 +36,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
||||||
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
|
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
|
||||||
/// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert
|
/// Checks for unreachable code, i.e. code ending in @param _exit, @param _revert or @param _transactionReturn
|
||||||
/// that can not be reached from @param _entry.
|
/// that can not be reached from @param _entry.
|
||||||
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const;
|
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const;
|
||||||
|
|
||||||
CFG const& m_cfg;
|
CFG const& m_cfg;
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
||||||
|
#include <libyul/AsmData.h>
|
||||||
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
|
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
@ -26,10 +28,12 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct
|
|||||||
m_nodeContainer(_nodeContainer),
|
m_nodeContainer(_nodeContainer),
|
||||||
m_currentNode(_functionFlow.entry),
|
m_currentNode(_functionFlow.entry),
|
||||||
m_returnNode(_functionFlow.exit),
|
m_returnNode(_functionFlow.exit),
|
||||||
m_revertNode(_functionFlow.revert)
|
m_revertNode(_functionFlow.revert),
|
||||||
|
m_transactionReturnNode(_functionFlow.transactionReturn)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
||||||
CFG::NodeContainer& _nodeContainer,
|
CFG::NodeContainer& _nodeContainer,
|
||||||
FunctionDefinition const& _function
|
FunctionDefinition const& _function
|
||||||
@ -39,6 +43,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
|||||||
functionFlow->entry = _nodeContainer.newNode();
|
functionFlow->entry = _nodeContainer.newNode();
|
||||||
functionFlow->exit = _nodeContainer.newNode();
|
functionFlow->exit = _nodeContainer.newNode();
|
||||||
functionFlow->revert = _nodeContainer.newNode();
|
functionFlow->revert = _nodeContainer.newNode();
|
||||||
|
functionFlow->transactionReturn = _nodeContainer.newNode();
|
||||||
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
|
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
|
||||||
builder.appendControlFlow(_function);
|
builder.appendControlFlow(_function);
|
||||||
|
|
||||||
@ -131,17 +136,17 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
|
|||||||
if (_forStatement.condition())
|
if (_forStatement.condition())
|
||||||
appendControlFlow(*_forStatement.condition());
|
appendControlFlow(*_forStatement.condition());
|
||||||
|
|
||||||
auto loopExpression = newLabel();
|
auto postPart = newLabel();
|
||||||
auto nodes = splitFlow<2>();
|
auto nodes = splitFlow<2>();
|
||||||
auto afterFor = nodes[1];
|
auto afterFor = nodes[1];
|
||||||
m_currentNode = nodes[0];
|
m_currentNode = nodes[0];
|
||||||
|
|
||||||
{
|
{
|
||||||
BreakContinueScope scope(*this, afterFor, loopExpression);
|
BreakContinueScope scope(*this, afterFor, postPart);
|
||||||
appendControlFlow(_forStatement.body());
|
appendControlFlow(_forStatement.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
placeAndConnectLabel(loopExpression);
|
placeAndConnectLabel(postPart);
|
||||||
|
|
||||||
if (auto expression = _forStatement.loopExpression())
|
if (auto expression = _forStatement.loopExpression())
|
||||||
appendControlFlow(*expression);
|
appendControlFlow(*expression);
|
||||||
@ -315,8 +320,7 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
|
|||||||
appendControlFlow(*returnParameter);
|
appendControlFlow(*returnParameter);
|
||||||
m_returnNode->variableOccurrences.emplace_back(
|
m_returnNode->variableOccurrences.emplace_back(
|
||||||
*returnParameter,
|
*returnParameter,
|
||||||
VariableOccurrence::Kind::Return,
|
VariableOccurrence::Kind::Return
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -345,7 +349,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*returnParameter,
|
*returnParameter,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
&_return
|
_return.location()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
connect(m_currentNode, m_returnNode);
|
connect(m_currentNode, m_returnNode);
|
||||||
@ -363,18 +367,158 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
|
|||||||
|
|
||||||
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
||||||
{
|
{
|
||||||
solAssert(!!m_currentNode, "");
|
solAssert(!!m_currentNode && !m_inlineAssembly, "");
|
||||||
visitNode(_inlineAssembly);
|
|
||||||
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
|
m_inlineAssembly = &_inlineAssembly;
|
||||||
|
(*this)(_inlineAssembly.operations());
|
||||||
|
m_inlineAssembly = nullptr;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::visit(yul::Statement const& _statement)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, locationOf(_statement));
|
||||||
|
ASTWalker::visit(_statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::If const& _if)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_if.condition);
|
||||||
|
|
||||||
|
auto nodes = splitFlow<2>();
|
||||||
|
m_currentNode = nodes[0];
|
||||||
|
(*this)(_if.body);
|
||||||
|
nodes[0] = m_currentNode;
|
||||||
|
mergeFlow(nodes, nodes[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Switch const& _switch)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_switch.expression);
|
||||||
|
|
||||||
|
auto beforeSwitch = m_currentNode;
|
||||||
|
|
||||||
|
auto nodes = splitFlow(_switch.cases.size());
|
||||||
|
for (size_t i = 0u; i < _switch.cases.size(); ++i)
|
||||||
{
|
{
|
||||||
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
|
m_currentNode = nodes[i];
|
||||||
|
(*this)(_switch.cases[i].body);
|
||||||
|
nodes[i] = m_currentNode;
|
||||||
|
}
|
||||||
|
mergeFlow(nodes);
|
||||||
|
|
||||||
|
bool hasDefault = util::contains_if(_switch.cases, [](yul::Case const& _case) { return !_case.value; });
|
||||||
|
if (!hasDefault)
|
||||||
|
connect(beforeSwitch, m_currentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::ForLoop const& _forLoop)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
|
||||||
|
(*this)(_forLoop.pre);
|
||||||
|
|
||||||
|
auto condition = createLabelHere();
|
||||||
|
|
||||||
|
if (_forLoop.condition)
|
||||||
|
visit(*_forLoop.condition);
|
||||||
|
|
||||||
|
auto loopExpression = newLabel();
|
||||||
|
auto nodes = splitFlow<2>();
|
||||||
|
auto afterFor = nodes[1];
|
||||||
|
m_currentNode = nodes[0];
|
||||||
|
|
||||||
|
{
|
||||||
|
BreakContinueScope scope(*this, afterFor, loopExpression);
|
||||||
|
(*this)(_forLoop.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
placeAndConnectLabel(loopExpression);
|
||||||
|
|
||||||
|
(*this)(_forLoop.post);
|
||||||
|
|
||||||
|
connect(m_currentNode, condition);
|
||||||
|
m_currentNode = afterFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Break const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
solAssert(m_breakJump, "");
|
||||||
|
connect(m_currentNode, m_breakJump);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Continue const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
solAssert(m_continueJump, "");
|
||||||
|
connect(m_currentNode, m_continueJump);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
|
||||||
|
if (externalReferences.count(&_identifier))
|
||||||
|
{
|
||||||
|
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*variableDeclaration,
|
*declaration,
|
||||||
VariableOccurrence::Kind::InlineAssembly,
|
VariableOccurrence::Kind::Access,
|
||||||
&_inlineAssembly
|
_identifier.location
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_assignment.value);
|
||||||
|
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
|
||||||
|
for (auto const& variable: _assignment.variableNames)
|
||||||
|
if (externalReferences.count(&variable))
|
||||||
|
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
|
||||||
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
|
*declaration,
|
||||||
|
VariableOccurrence::Kind::Assignment,
|
||||||
|
variable.location
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
|
||||||
|
{
|
||||||
|
using namespace yul;
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
yul::ASTWalker::operator()(_functionCall);
|
||||||
|
|
||||||
|
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
|
||||||
|
if (builtinFunction->controlFlowSideEffects.terminates)
|
||||||
|
{
|
||||||
|
if (builtinFunction->controlFlowSideEffects.reverts)
|
||||||
|
connect(m_currentNode, m_revertNode);
|
||||||
|
else
|
||||||
|
connect(m_currentNode, m_transactionReturnNode);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
// External references cannot be accessed from within functions, so we can ignore their control flow.
|
||||||
|
// TODO: we might still want to track if they always revert or return, though.
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Leave const&)
|
||||||
|
{
|
||||||
|
// This has to be implemented, if we ever decide to visit functions.
|
||||||
|
solUnimplementedAssert(false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
||||||
@ -384,8 +528,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
|||||||
|
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Declaration,
|
VariableOccurrence::Kind::Declaration
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle declaration with immediate assignment.
|
// Handle declaration with immediate assignment.
|
||||||
@ -393,14 +536,13 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
_variableDeclaration.value().get()
|
_variableDeclaration.value()->location()
|
||||||
);
|
);
|
||||||
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
|
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
|
||||||
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
|
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -434,7 +576,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*var,
|
*var,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
expression
|
expression ? std::make_optional(expression->location()) : std::optional<langutil::SourceLocation>{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,7 +594,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
|
|||||||
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
||||||
VariableOccurrence::Kind::Assignment :
|
VariableOccurrence::Kind::Assignment :
|
||||||
VariableOccurrence::Kind::Access,
|
VariableOccurrence::Kind::Access,
|
||||||
&_identifier
|
_identifier.location()
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
|
#include <libyul/optimiser/ASTWalker.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -30,7 +31,7 @@ namespace solidity::frontend {
|
|||||||
* Modifiers are not yet applied to the functions. This is done in a second
|
* Modifiers are not yet applied to the functions. This is done in a second
|
||||||
* step in the CFG class.
|
* step in the CFG class.
|
||||||
*/
|
*/
|
||||||
class ControlFlowBuilder: private ASTConstVisitor
|
class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static std::unique_ptr<FunctionFlow> createFunctionFlow(
|
static std::unique_ptr<FunctionFlow> createFunctionFlow(
|
||||||
@ -39,7 +40,10 @@ public:
|
|||||||
);
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
|
explicit ControlFlowBuilder(
|
||||||
|
CFG::NodeContainer& _nodeContainer,
|
||||||
|
FunctionFlow const& _functionFlow
|
||||||
|
);
|
||||||
|
|
||||||
// Visits for constructing the control flow.
|
// Visits for constructing the control flow.
|
||||||
bool visit(BinaryOperation const& _operation) override;
|
bool visit(BinaryOperation const& _operation) override;
|
||||||
@ -62,6 +66,17 @@ private:
|
|||||||
// Visits for filling variable occurrences.
|
// Visits for filling variable occurrences.
|
||||||
bool visit(FunctionTypeName const& _functionTypeName) override;
|
bool visit(FunctionTypeName const& _functionTypeName) override;
|
||||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||||
|
void visit(yul::Statement const& _statement) override;
|
||||||
|
void operator()(yul::If const& _if) override;
|
||||||
|
void operator()(yul::Switch const& _switch) override;
|
||||||
|
void operator()(yul::ForLoop const& _for) override;
|
||||||
|
void operator()(yul::Break const&) override;
|
||||||
|
void operator()(yul::Continue const&) override;
|
||||||
|
void operator()(yul::Identifier const& _identifier) override;
|
||||||
|
void operator()(yul::Assignment const& _assignment) override;
|
||||||
|
void operator()(yul::FunctionCall const& _functionCall) override;
|
||||||
|
void operator()(yul::FunctionDefinition const& _functionDefinition) override;
|
||||||
|
void operator()(yul::Leave const& _leave) override;
|
||||||
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
||||||
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
||||||
bool visit(Identifier const& _identifier) override;
|
bool visit(Identifier const& _identifier) override;
|
||||||
@ -70,6 +85,9 @@ protected:
|
|||||||
bool visitNode(ASTNode const&) override;
|
bool visitNode(ASTNode const&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using ASTConstVisitor::visit;
|
||||||
|
using yul::ASTWalker::visit;
|
||||||
|
using yul::ASTWalker::operator();
|
||||||
|
|
||||||
/// Appends the control flow of @a _node to the current control flow.
|
/// Appends the control flow of @a _node to the current control flow.
|
||||||
void appendControlFlow(ASTNode const& _node);
|
void appendControlFlow(ASTNode const& _node);
|
||||||
@ -136,6 +154,7 @@ private:
|
|||||||
CFGNode* m_currentNode = nullptr;
|
CFGNode* m_currentNode = nullptr;
|
||||||
CFGNode* m_returnNode = nullptr;
|
CFGNode* m_returnNode = nullptr;
|
||||||
CFGNode* m_revertNode = nullptr;
|
CFGNode* m_revertNode = nullptr;
|
||||||
|
CFGNode* m_transactionReturnNode = nullptr;
|
||||||
|
|
||||||
/// The current jump destination of break Statements.
|
/// The current jump destination of break Statements.
|
||||||
CFGNode* m_breakJump = nullptr;
|
CFGNode* m_breakJump = nullptr;
|
||||||
@ -145,6 +164,8 @@ private:
|
|||||||
CFGNode* m_placeholderEntry = nullptr;
|
CFGNode* m_placeholderEntry = nullptr;
|
||||||
CFGNode* m_placeholderExit = nullptr;
|
CFGNode* m_placeholderExit = nullptr;
|
||||||
|
|
||||||
|
InlineAssembly const* m_inlineAssembly = nullptr;
|
||||||
|
|
||||||
/// Helper class that replaces the break and continue jump destinations for the
|
/// Helper class that replaces the break and continue jump destinations for the
|
||||||
/// current scope and restores the originals at the end of the scope.
|
/// current scope and restores the originals at the end of the scope.
|
||||||
class BreakContinueScope
|
class BreakContinueScope
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
#include <liblangutil/ErrorReporter.h>
|
#include <liblangutil/ErrorReporter.h>
|
||||||
|
#include <liblangutil/EVMVersion.h>
|
||||||
#include <liblangutil/SourceLocation.h>
|
#include <liblangutil/SourceLocation.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -33,8 +34,8 @@ namespace solidity::frontend
|
|||||||
/**
|
/**
|
||||||
* Occurrence of a variable in a block of control flow.
|
* Occurrence of a variable in a block of control flow.
|
||||||
* Stores the declaration of the referenced variable, the
|
* Stores the declaration of the referenced variable, the
|
||||||
* kind of the occurrence and possibly the node at which
|
* kind of the occurrence and possibly the source location
|
||||||
* it occurred.
|
* at which it occurred.
|
||||||
*/
|
*/
|
||||||
class VariableOccurrence
|
class VariableOccurrence
|
||||||
{
|
{
|
||||||
@ -47,7 +48,7 @@ public:
|
|||||||
Assignment,
|
Assignment,
|
||||||
InlineAssembly
|
InlineAssembly
|
||||||
};
|
};
|
||||||
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence):
|
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
|
||||||
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -57,8 +58,8 @@ public:
|
|||||||
{
|
{
|
||||||
if (m_occurrence && _rhs.m_occurrence)
|
if (m_occurrence && _rhs.m_occurrence)
|
||||||
{
|
{
|
||||||
if (m_occurrence->id() < _rhs.m_occurrence->id()) return true;
|
if (*m_occurrence < *_rhs.m_occurrence) return true;
|
||||||
if (_rhs.m_occurrence->id() < m_occurrence->id()) return false;
|
if (*_rhs.m_occurrence < *m_occurrence) return false;
|
||||||
}
|
}
|
||||||
else if (_rhs.m_occurrence)
|
else if (_rhs.m_occurrence)
|
||||||
return true;
|
return true;
|
||||||
@ -74,14 +75,14 @@ public:
|
|||||||
|
|
||||||
VariableDeclaration const& declaration() const { return m_declaration; }
|
VariableDeclaration const& declaration() const { return m_declaration; }
|
||||||
Kind kind() const { return m_occurrenceKind; };
|
Kind kind() const { return m_occurrenceKind; };
|
||||||
ASTNode const* occurrence() const { return m_occurrence; }
|
std::optional<langutil::SourceLocation> const& occurrence() const { return m_occurrence; }
|
||||||
private:
|
private:
|
||||||
/// Declaration of the occurring variable.
|
/// Declaration of the occurring variable.
|
||||||
VariableDeclaration const& m_declaration;
|
VariableDeclaration const& m_declaration;
|
||||||
/// Kind of occurrence.
|
/// Kind of occurrence.
|
||||||
Kind m_occurrenceKind = Kind::Access;
|
Kind m_occurrenceKind = Kind::Access;
|
||||||
/// AST node at which the variable occurred, if available (may be nullptr).
|
/// Source location at which the variable occurred, if available (may be nullptr).
|
||||||
ASTNode const* m_occurrence = nullptr;
|
std::optional<langutil::SourceLocation> m_occurrence;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,6 +120,10 @@ struct FunctionFlow
|
|||||||
/// This node is empty does not have any exits, but may have multiple entries
|
/// This node is empty does not have any exits, but may have multiple entries
|
||||||
/// (e.g. all assert, require, revert and throw statements).
|
/// (e.g. all assert, require, revert and throw statements).
|
||||||
CFGNode* revert = nullptr;
|
CFGNode* revert = nullptr;
|
||||||
|
/// Transaction return node. Destination node for inline assembly "return" calls.
|
||||||
|
/// This node is empty and does not have any exits, but may have multiple entries
|
||||||
|
/// (e.g. all inline assembly return calls).
|
||||||
|
CFGNode* transactionReturn = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CFG: private ASTConstVisitor
|
class CFG: private ASTConstVisitor
|
||||||
@ -140,7 +145,6 @@ public:
|
|||||||
std::vector<std::unique_ptr<CFGNode>> m_nodes;
|
std::vector<std::unique_ptr<CFGNode>> m_nodes;
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
|
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
|
||||||
/// Node container.
|
/// Node container.
|
||||||
|
38
libyul/ControlFlowSideEffects.h
Normal file
38
libyul/ControlFlowSideEffects.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace solidity::yul
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effects of code related to control flow.
|
||||||
|
*/
|
||||||
|
struct ControlFlowSideEffects
|
||||||
|
{
|
||||||
|
/// If true, this code terminates the control flow.
|
||||||
|
/// State may or may not be reverted as indicated by the ``reverts`` flag.
|
||||||
|
bool terminates = false;
|
||||||
|
/// If true, this code reverts all state changes in the transaction.
|
||||||
|
/// Whenever this is true, ``terminates`` has to be true as well.
|
||||||
|
bool reverts = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <libyul/YulString.h>
|
#include <libyul/YulString.h>
|
||||||
#include <libyul/SideEffects.h>
|
#include <libyul/SideEffects.h>
|
||||||
|
#include <libyul/ControlFlowSideEffects.h>
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ struct BuiltinFunction
|
|||||||
std::vector<Type> parameters;
|
std::vector<Type> parameters;
|
||||||
std::vector<Type> returns;
|
std::vector<Type> returns;
|
||||||
SideEffects sideEffects;
|
SideEffects sideEffects;
|
||||||
|
ControlFlowSideEffects controlFlowSideEffects;
|
||||||
/// If true, this is the msize instruction.
|
/// If true, this is the msize instruction.
|
||||||
bool isMSize = false;
|
bool isMSize = false;
|
||||||
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
||||||
|
@ -52,6 +52,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
|||||||
f.parameters.resize(info.args);
|
f.parameters.resize(info.args);
|
||||||
f.returns.resize(info.ret);
|
f.returns.resize(info.ret);
|
||||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||||
|
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
||||||
|
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
|
||||||
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
||||||
f.literalArguments = false;
|
f.literalArguments = false;
|
||||||
f.instruction = _instruction;
|
f.instruction = _instruction;
|
||||||
|
@ -99,6 +99,8 @@ WasmDialect::WasmDialect()
|
|||||||
addFunction("unreachable", {}, {}, false);
|
addFunction("unreachable", {}, {}, false);
|
||||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
|
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
|
||||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
|
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
|
||||||
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
|
||||||
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
|
||||||
|
|
||||||
addFunction("datasize", {i64}, {i64}, true, true);
|
addFunction("datasize", {i64}, {i64}, true, true);
|
||||||
addFunction("dataoffset", {i64}, {i64}, true, true);
|
addFunction("dataoffset", {i64}, {i64}, true, true);
|
||||||
@ -147,7 +149,12 @@ void WasmDialect::addEthereumExternals()
|
|||||||
static string const i64{"i64"};
|
static string const i64{"i64"};
|
||||||
static string const i32{"i32"};
|
static string const i32{"i32"};
|
||||||
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
|
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
|
||||||
struct External { string name; vector<string> parameters; vector<string> returns; };
|
struct External {
|
||||||
|
string name;
|
||||||
|
vector<string> parameters;
|
||||||
|
vector<string> returns;
|
||||||
|
ControlFlowSideEffects controlFlowSideEffects = {};
|
||||||
|
};
|
||||||
static vector<External> externals{
|
static vector<External> externals{
|
||||||
{"getAddress", {i32ptr}, {}},
|
{"getAddress", {i32ptr}, {}},
|
||||||
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
||||||
@ -175,11 +182,11 @@ void WasmDialect::addEthereumExternals()
|
|||||||
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
||||||
{"getBlockNumber", {}, {i64}},
|
{"getBlockNumber", {}, {i64}},
|
||||||
{"getTxOrigin", {i32ptr}, {}},
|
{"getTxOrigin", {i32ptr}, {}},
|
||||||
{"finish", {i32ptr, i32}, {}},
|
{"finish", {i32ptr, i32}, {}, {true, false}},
|
||||||
{"revert", {i32ptr, i32}, {}},
|
{"revert", {i32ptr, i32}, {}, {true, true}},
|
||||||
{"getReturnDataSize", {}, {i32}},
|
{"getReturnDataSize", {}, {i32}},
|
||||||
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
||||||
{"selfDestruct", {i32ptr}, {}},
|
{"selfDestruct", {i32ptr}, {}, {true, false}},
|
||||||
{"getBlockTimestamp", {}, {i64}}
|
{"getBlockTimestamp", {}, {i64}}
|
||||||
};
|
};
|
||||||
for (External const& ext: externals)
|
for (External const& ext: externals)
|
||||||
@ -193,6 +200,7 @@ void WasmDialect::addEthereumExternals()
|
|||||||
f.returns.emplace_back(YulString(p));
|
f.returns.emplace_back(YulString(p));
|
||||||
// TODO some of them are side effect free.
|
// TODO some of them are side effect free.
|
||||||
f.sideEffects = SideEffects::worst();
|
f.sideEffects = SideEffects::worst();
|
||||||
|
f.controlFlowSideEffects = ext.controlFlowSideEffects;
|
||||||
f.isMSize = false;
|
f.isMSize = false;
|
||||||
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
||||||
f.literalArguments = false;
|
f.literalArguments = false;
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
contract C {
|
||||||
|
function f() public pure {
|
||||||
|
assembly {
|
||||||
|
function f() {
|
||||||
|
// Make sure this doesn't trigger the unimplemented assertion in the control flow builder.
|
||||||
|
leave
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -0,0 +1,10 @@
|
|||||||
|
contract C {
|
||||||
|
function f() public pure {
|
||||||
|
assembly {
|
||||||
|
// Make sure this doesn't trigger the unimplemented assertion in the control flow builder.
|
||||||
|
leave
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// SyntaxError: (178-183): Keyword "leave" can only be used inside a function.
|
@ -0,0 +1,29 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f() internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
for {} eq(0,0) { c_slot := s_slot } {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function g() internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
for {} eq(0,1) { c_slot := s_slot } {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function h() internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
for {} eq(0,0) {} { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function i() internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
for {} eq(0,1) {} { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
||||||
|
// TypeError: (228-239): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
||||||
|
// TypeError: (369-380): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
||||||
|
// TypeError: (510-521): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
@ -0,0 +1,15 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f() internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
for { c_slot := s_slot } iszero(0) {} {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function g() internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
for { c_slot := s_slot } iszero(1) {} {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -0,0 +1,11 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f(bool flag) internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
if flag { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
@ -0,0 +1,13 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f() internal pure returns (S storage c) {
|
||||||
|
// this should warn about unreachable code, but currently function flow is ignored
|
||||||
|
assembly {
|
||||||
|
function f() { return(0, 0) }
|
||||||
|
f()
|
||||||
|
c_slot := s_slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -0,0 +1,13 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f() internal pure returns (S storage c) {
|
||||||
|
// this could be allowed, but currently control flow for functions is not analysed
|
||||||
|
assembly {
|
||||||
|
function f() { revert(0, 0) }
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
@ -0,0 +1,10 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f() internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
c_slot := s_slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -0,0 +1,27 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f(uint256 a) internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
switch a
|
||||||
|
case 0 { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function g(bool flag) internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
switch flag
|
||||||
|
case 0 { c_slot := s_slot }
|
||||||
|
case 1 { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function h(uint256 a) internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
switch a
|
||||||
|
case 0 { c_slot := s_slot }
|
||||||
|
default { return(0,0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
||||||
|
// TypeError: (256-267): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour.
|
@ -0,0 +1,25 @@
|
|||||||
|
contract C {
|
||||||
|
struct S { bool f; }
|
||||||
|
S s;
|
||||||
|
function f(uint256 a) internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
switch a
|
||||||
|
default { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function g(bool flag) internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
switch flag
|
||||||
|
case 0 { c_slot := s_slot }
|
||||||
|
default { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function h(uint256 a) internal pure returns (S storage c) {
|
||||||
|
assembly {
|
||||||
|
switch a
|
||||||
|
case 0 { revert(0, 0) }
|
||||||
|
default { c_slot := s_slot }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -6,4 +6,4 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError: (92-116): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour.
|
// TypeError: (107-113): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour.
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
contract C {
|
||||||
|
function f() public pure {
|
||||||
|
assembly {
|
||||||
|
revert(0, 0)
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function g() public pure {
|
||||||
|
assembly {
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
revert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (100-112): Unreachable code.
|
||||||
|
// Warning: (222-230): Unreachable code.
|
@ -0,0 +1,13 @@
|
|||||||
|
contract C {
|
||||||
|
function f() public pure {
|
||||||
|
assembly {
|
||||||
|
for { let a := 0} lt(a,1) { a := add(a, 1) } {
|
||||||
|
break
|
||||||
|
let b := 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (103-117): Unreachable code.
|
||||||
|
// Warning: (160-171): Unreachable code.
|
@ -0,0 +1,12 @@
|
|||||||
|
contract C {
|
||||||
|
function f() public pure {
|
||||||
|
assembly {
|
||||||
|
for { let a := 0} lt(a,1) { a := add(a, 1) } {
|
||||||
|
continue
|
||||||
|
let b := 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (163-174): Unreachable code.
|
@ -0,0 +1,17 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint256 y) public pure returns (uint256 x) {
|
||||||
|
assembly {
|
||||||
|
return(0, 0)
|
||||||
|
x := y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function g(uint256 y) public pure returns (uint256 x) {
|
||||||
|
assembly {
|
||||||
|
return(0, 0)
|
||||||
|
}
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (129-135): Unreachable code.
|
||||||
|
// Warning: (274-279): Unreachable code.
|
@ -0,0 +1,17 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint256 y) public pure returns (uint256 x) {
|
||||||
|
assembly {
|
||||||
|
revert(0, 0)
|
||||||
|
x := y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function g(uint256 y) public pure returns (uint256 x) {
|
||||||
|
assembly {
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning: (129-135): Unreachable code.
|
||||||
|
// Warning: (274-279): Unreachable code.
|
@ -539,7 +539,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis)
|
|||||||
{
|
{
|
||||||
return _name == "builtin"_yulstring ? &f : nullptr;
|
return _name == "builtin"_yulstring ? &f : nullptr;
|
||||||
}
|
}
|
||||||
BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), {}};
|
BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), {}, {}};
|
||||||
};
|
};
|
||||||
|
|
||||||
SimpleDialect dialect;
|
SimpleDialect dialect;
|
||||||
|
Loading…
Reference in New Issue
Block a user