aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/CMakeLists.txt2
-rw-r--r--libsolidity/analysis/ControlFlowAnalyzer.cpp156
-rw-r--r--libsolidity/analysis/ControlFlowAnalyzer.h52
-rw-r--r--libsolidity/analysis/ControlFlowBuilder.cpp370
-rw-r--r--libsolidity/analysis/ControlFlowBuilder.h143
-rw-r--r--libsolidity/analysis/ControlFlowGraph.cpp136
-rw-r--r--libsolidity/analysis/ControlFlowGraph.h148
-rw-r--r--libsolidity/analysis/TypeChecker.cpp3
-rw-r--r--libsolidity/ast/Types.cpp6
-rw-r--r--libsolidity/ast/Types.h4
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp1
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp4
-rw-r--r--libsolidity/formal/SMTChecker.cpp44
-rw-r--r--libsolidity/formal/SMTChecker.h4
-rw-r--r--libsolidity/formal/VariableUsage.cpp1
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp10
-rw-r--r--libsolidity/interface/CompilerStack.cpp18
-rw-r--r--libsolidity/parsing/Parser.cpp158
-rw-r--r--libsolidity/parsing/Parser.h22
-rw-r--r--libsolidity/parsing/ParserBase.cpp4
20 files changed, 1236 insertions, 50 deletions
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt
index 97b01c83..0bdec4b4 100644
--- a/libsolidity/CMakeLists.txt
+++ b/libsolidity/CMakeLists.txt
@@ -28,7 +28,7 @@ else()
endif()
add_library(solidity ${sources} ${headers})
-target_link_libraries(solidity PUBLIC evmasm devcore)
+target_link_libraries(solidity PUBLIC evmasm devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})
if (${Z3_FOUND})
target_link_libraries(solidity PUBLIC ${Z3_LIBRARY})
diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp
new file mode 100644
index 00000000..6edf7986
--- /dev/null
+++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp
@@ -0,0 +1,156 @@
+/*
+ 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/>.
+*/
+
+#include <libsolidity/analysis/ControlFlowAnalyzer.h>
+
+using namespace std;
+using namespace dev::solidity;
+
+bool ControlFlowAnalyzer::analyze(ASTNode const& _astRoot)
+{
+ _astRoot.accept(*this);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
+}
+
+bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
+{
+ auto const& functionFlow = m_cfg.functionFlow(_function);
+ checkUnassignedStorageReturnValues(_function, functionFlow.entry, functionFlow.exit);
+ return false;
+}
+
+set<VariableDeclaration const*> ControlFlowAnalyzer::variablesAssignedInNode(CFGNode const *node)
+{
+ set<VariableDeclaration const*> result;
+ for (auto expression: node->block.expressions)
+ {
+ if (auto const* assignment = dynamic_cast<Assignment const*>(expression))
+ {
+ stack<Expression const*> expressions;
+ expressions.push(&assignment->leftHandSide());
+ while (!expressions.empty())
+ {
+ Expression const* expression = expressions.top();
+ expressions.pop();
+
+ if (auto const *tuple = dynamic_cast<TupleExpression const*>(expression))
+ for (auto const& component: tuple->components())
+ expressions.push(component.get());
+ else if (auto const* identifier = dynamic_cast<Identifier const*>(expression))
+ if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(
+ identifier->annotation().referencedDeclaration
+ ))
+ result.insert(variableDeclaration);
+ }
+ }
+ }
+ return result;
+}
+
+void ControlFlowAnalyzer::checkUnassignedStorageReturnValues(
+ FunctionDefinition const& _function,
+ CFGNode const* _functionEntry,
+ CFGNode const* _functionExit
+) const
+{
+ if (_function.returnParameterList()->parameters().empty())
+ return;
+
+ map<CFGNode const*, set<VariableDeclaration const*>> unassigned;
+
+ {
+ auto& unassignedAtFunctionEntry = unassigned[_functionEntry];
+ for (auto const& returnParameter: _function.returnParameterList()->parameters())
+ if (returnParameter->type()->dataStoredIn(DataLocation::Storage))
+ unassignedAtFunctionEntry.insert(returnParameter.get());
+ }
+
+ stack<CFGNode const*> nodesToTraverse;
+ nodesToTraverse.push(_functionEntry);
+
+ // walk all paths from entry with maximal set of unassigned return values
+ while (!nodesToTraverse.empty())
+ {
+ auto node = nodesToTraverse.top();
+ nodesToTraverse.pop();
+
+ auto& unassignedAtNode = unassigned[node];
+
+ if (node->block.returnStatement != nullptr)
+ if (node->block.returnStatement->expression())
+ unassignedAtNode.clear();
+ if (!unassignedAtNode.empty())
+ {
+ // kill all return values to which a value is assigned
+ for (auto const* variableDeclaration: variablesAssignedInNode(node))
+ unassignedAtNode.erase(variableDeclaration);
+
+ // kill all return values referenced in inline assembly
+ // a reference is enough, checking whether there actually was an assignment might be overkill
+ for (auto assembly: node->block.inlineAssemblyStatements)
+ for (auto const& ref: assembly->annotation().externalReferences)
+ if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
+ unassignedAtNode.erase(variableDeclaration);
+ }
+
+ for (auto const& exit: node->exits)
+ {
+ auto& unassignedAtExit = unassigned[exit];
+ auto oldSize = unassignedAtExit.size();
+ unassignedAtExit.insert(unassignedAtNode.begin(), unassignedAtNode.end());
+ // (re)traverse an exit, if we are on a path with new unassigned return values to consider
+ // this will terminate, since there is only a finite number of unassigned return values
+ if (unassignedAtExit.size() > oldSize)
+ nodesToTraverse.push(exit);
+ }
+ }
+
+ if (!unassigned[_functionExit].empty())
+ {
+ vector<VariableDeclaration const*> unassignedOrdered(
+ unassigned[_functionExit].begin(),
+ unassigned[_functionExit].end()
+ );
+ sort(
+ unassignedOrdered.begin(),
+ unassignedOrdered.end(),
+ [](VariableDeclaration const* lhs, VariableDeclaration const* rhs) -> bool {
+ return lhs->id() < rhs->id();
+ }
+ );
+ for (auto const* returnVal: unassignedOrdered)
+ {
+ SecondarySourceLocation ssl;
+ for (CFGNode* lastNodeBeforeExit: _functionExit->entries)
+ if (unassigned[lastNodeBeforeExit].count(returnVal))
+ {
+ if (!!lastNodeBeforeExit->block.returnStatement)
+ ssl.append("Problematic return:", lastNodeBeforeExit->block.returnStatement->location());
+ else
+ ssl.append("Problematic end of function:", _function.location());
+ }
+
+ m_errorReporter.warning(
+ returnVal->location(),
+ "This variable is of storage pointer type and might be returned without assignment. "
+ "This can cause storage corruption. Assign the variable (potentially from itself) "
+ "to remove this warning.",
+ ssl
+ );
+ }
+ }
+}
diff --git a/libsolidity/analysis/ControlFlowAnalyzer.h b/libsolidity/analysis/ControlFlowAnalyzer.h
new file mode 100644
index 00000000..43e13fb6
--- /dev/null
+++ b/libsolidity/analysis/ControlFlowAnalyzer.h
@@ -0,0 +1,52 @@
+/*
+ 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 <libsolidity/analysis/ControlFlowGraph.h>
+
+#include <set>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ControlFlowAnalyzer: private ASTConstVisitor
+{
+public:
+ explicit ControlFlowAnalyzer(CFG const& _cfg, ErrorReporter& _errorReporter):
+ m_cfg(_cfg), m_errorReporter(_errorReporter) {}
+
+ bool analyze(ASTNode const& _astRoot);
+
+ virtual bool visit(FunctionDefinition const& _function) override;
+
+private:
+ static std::set<VariableDeclaration const*> variablesAssignedInNode(CFGNode const *node);
+ void checkUnassignedStorageReturnValues(
+ FunctionDefinition const& _function,
+ CFGNode const* _functionEntry,
+ CFGNode const* _functionExit
+ ) const;
+
+ CFG const& m_cfg;
+ ErrorReporter& m_errorReporter;
+};
+
+}
+}
diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp
new file mode 100644
index 00000000..35d7687c
--- /dev/null
+++ b/libsolidity/analysis/ControlFlowBuilder.cpp
@@ -0,0 +1,370 @@
+/*
+ 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/>.
+*/
+
+#include <libsolidity/analysis/ControlFlowBuilder.h>
+
+using namespace dev;
+using namespace solidity;
+using namespace std;
+
+ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow):
+ m_nodeContainer(_nodeContainer), m_currentFunctionFlow(_functionFlow), m_currentNode(_functionFlow.entry)
+{
+}
+
+unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
+ CFG::NodeContainer& _nodeContainer,
+ FunctionDefinition const& _function
+)
+{
+ auto functionFlow = unique_ptr<FunctionFlow>(new FunctionFlow());
+ functionFlow->entry = _nodeContainer.newNode();
+ functionFlow->exit = _nodeContainer.newNode();
+ functionFlow->revert = _nodeContainer.newNode();
+ ControlFlowBuilder builder(_nodeContainer, *functionFlow);
+ builder.appendControlFlow(_function);
+ connect(builder.m_currentNode, functionFlow->exit);
+ return functionFlow;
+}
+
+
+unique_ptr<ModifierFlow> ControlFlowBuilder::createModifierFlow(
+ CFG::NodeContainer& _nodeContainer,
+ ModifierDefinition const& _modifier
+)
+{
+ auto modifierFlow = unique_ptr<ModifierFlow>(new ModifierFlow());
+ modifierFlow->entry = _nodeContainer.newNode();
+ modifierFlow->exit = _nodeContainer.newNode();
+ modifierFlow->revert = _nodeContainer.newNode();
+ modifierFlow->placeholderEntry = _nodeContainer.newNode();
+ modifierFlow->placeholderExit = _nodeContainer.newNode();
+ ControlFlowBuilder builder(_nodeContainer, *modifierFlow);
+ builder.appendControlFlow(_modifier);
+ connect(builder.m_currentNode, modifierFlow->exit);
+ return modifierFlow;
+}
+
+bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
+{
+ solAssert(!!m_currentNode, "");
+
+ switch(_operation.getOperator())
+ {
+ case Token::Or:
+ case Token::And:
+ {
+ appendControlFlow(_operation.leftExpression());
+
+ auto nodes = splitFlow<2>();
+ nodes[0] = createFlow(nodes[0], _operation.rightExpression());
+ mergeFlow(nodes, nodes[1]);
+
+ return false;
+ }
+ default:
+ break;
+ }
+ return ASTConstVisitor::visit(_operation);
+}
+
+bool ControlFlowBuilder::visit(Conditional const& _conditional)
+{
+ solAssert(!!m_currentNode, "");
+
+ _conditional.condition().accept(*this);
+
+ auto nodes = splitFlow<2>();
+
+ nodes[0] = createFlow(nodes[0], _conditional.trueExpression());
+ nodes[1] = createFlow(nodes[1], _conditional.falseExpression());
+
+ mergeFlow(nodes);
+
+ return false;
+}
+
+bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
+{
+ solAssert(!!m_currentNode, "");
+
+ _ifStatement.condition().accept(*this);
+
+ auto nodes = splitFlow<2>();
+ nodes[0] = createFlow(nodes[0], _ifStatement.trueStatement());
+
+ if (_ifStatement.falseStatement())
+ {
+ nodes[1] = createFlow(nodes[1], *_ifStatement.falseStatement());
+ mergeFlow(nodes);
+ }
+ else
+ mergeFlow(nodes, nodes[1]);
+
+ return false;
+}
+
+bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
+{
+ solAssert(!!m_currentNode, "");
+
+ if (_forStatement.initializationExpression())
+ _forStatement.initializationExpression()->accept(*this);
+
+ auto condition = createLabelHere();
+
+ if (_forStatement.condition())
+ appendControlFlow(*_forStatement.condition());
+
+ auto loopExpression = newLabel();
+ auto nodes = splitFlow<2>();
+ auto afterFor = nodes[1];
+ m_currentNode = nodes[0];
+
+ {
+ BreakContinueScope scope(*this, afterFor, loopExpression);
+ appendControlFlow(_forStatement.body());
+ }
+
+ placeAndConnectLabel(loopExpression);
+
+ if (auto expression = _forStatement.loopExpression())
+ appendControlFlow(*expression);
+
+ connect(m_currentNode, condition);
+ m_currentNode = afterFor;
+
+ return false;
+}
+
+bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement)
+{
+ solAssert(!!m_currentNode, "");
+
+ if (_whileStatement.isDoWhile())
+ {
+ auto afterWhile = newLabel();
+ auto whileBody = createLabelHere();
+
+ {
+ // Note that "continue" in this case currently indeed jumps to whileBody
+ // and not to the condition. This is inconsistent with JavaScript and C and
+ // therefore a bug. This will be fixed in the future (planned for 0.5.0)
+ // and the Control Flow Graph will have to be adjusted accordingly.
+ BreakContinueScope scope(*this, afterWhile, whileBody);
+ appendControlFlow(_whileStatement.body());
+ }
+ appendControlFlow(_whileStatement.condition());
+
+ connect(m_currentNode, whileBody);
+ placeAndConnectLabel(afterWhile);
+ }
+ else
+ {
+ auto whileCondition = createLabelHere();
+
+ appendControlFlow(_whileStatement.condition());
+
+ auto nodes = splitFlow<2>();
+
+ auto whileBody = nodes[0];
+ auto afterWhile = nodes[1];
+
+ m_currentNode = whileBody;
+ {
+ BreakContinueScope scope(*this, afterWhile, whileCondition);
+ appendControlFlow(_whileStatement.body());
+ }
+
+ connect(m_currentNode, whileCondition);
+
+ m_currentNode = afterWhile;
+ }
+
+
+ return false;
+}
+
+bool ControlFlowBuilder::visit(Break const&)
+{
+ solAssert(!!m_currentNode, "");
+ solAssert(!!m_breakJump, "");
+ connect(m_currentNode, m_breakJump);
+ m_currentNode = newLabel();
+ return false;
+}
+
+bool ControlFlowBuilder::visit(Continue const&)
+{
+ solAssert(!!m_currentNode, "");
+ solAssert(!!m_continueJump, "");
+ connect(m_currentNode, m_continueJump);
+ m_currentNode = newLabel();
+ return false;
+}
+
+bool ControlFlowBuilder::visit(Throw const&)
+{
+ solAssert(!!m_currentNode, "");
+ solAssert(!!m_currentFunctionFlow.revert, "");
+ connect(m_currentNode, m_currentFunctionFlow.revert);
+ m_currentNode = newLabel();
+ return false;
+}
+
+bool ControlFlowBuilder::visit(Block const&)
+{
+ solAssert(!!m_currentNode, "");
+ createLabelHere();
+ return true;
+}
+
+void ControlFlowBuilder::endVisit(Block const&)
+{
+ solAssert(!!m_currentNode, "");
+ createLabelHere();
+}
+
+bool ControlFlowBuilder::visit(Return const& _return)
+{
+ solAssert(!!m_currentNode, "");
+ solAssert(!!m_currentFunctionFlow.exit, "");
+ solAssert(!m_currentNode->block.returnStatement, "");
+ m_currentNode->block.returnStatement = &_return;
+ connect(m_currentNode, m_currentFunctionFlow.exit);
+ m_currentNode = newLabel();
+ return true;
+}
+
+
+bool ControlFlowBuilder::visit(PlaceholderStatement const&)
+{
+ solAssert(!!m_currentNode, "");
+ auto modifierFlow = dynamic_cast<ModifierFlow const*>(&m_currentFunctionFlow);
+ solAssert(!!modifierFlow, "");
+
+ connect(m_currentNode, modifierFlow->placeholderEntry);
+
+ m_currentNode = newLabel();
+
+ connect(modifierFlow->placeholderExit, m_currentNode);
+ return false;
+}
+
+bool ControlFlowBuilder::visitNode(ASTNode const& node)
+{
+ solAssert(!!m_currentNode, "");
+ if (auto const* expression = dynamic_cast<Expression const*>(&node))
+ m_currentNode->block.expressions.emplace_back(expression);
+ else if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(&node))
+ m_currentNode->block.variableDeclarations.emplace_back(variableDeclaration);
+ else if (auto const* assembly = dynamic_cast<InlineAssembly const*>(&node))
+ m_currentNode->block.inlineAssemblyStatements.emplace_back(assembly);
+
+ return true;
+}
+
+bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
+{
+ solAssert(!!m_currentNode, "");
+ solAssert(!!_functionCall.expression().annotation().type, "");
+
+ if (auto functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type))
+ switch (functionType->kind())
+ {
+ case FunctionType::Kind::Revert:
+ solAssert(!!m_currentFunctionFlow.revert, "");
+ _functionCall.expression().accept(*this);
+ ASTNode::listAccept(_functionCall.arguments(), *this);
+ connect(m_currentNode, m_currentFunctionFlow.revert);
+ m_currentNode = newLabel();
+ return false;
+ case FunctionType::Kind::Require:
+ case FunctionType::Kind::Assert:
+ {
+ solAssert(!!m_currentFunctionFlow.revert, "");
+ _functionCall.expression().accept(*this);
+ ASTNode::listAccept(_functionCall.arguments(), *this);
+ connect(m_currentNode, m_currentFunctionFlow.revert);
+ auto nextNode = newLabel();
+ connect(m_currentNode, nextNode);
+ m_currentNode = nextNode;
+ return false;
+ }
+ default:
+ break;
+ }
+ return ASTConstVisitor::visit(_functionCall);
+}
+
+void ControlFlowBuilder::appendControlFlow(ASTNode const& _node)
+{
+ _node.accept(*this);
+}
+
+CFGNode* ControlFlowBuilder::createFlow(CFGNode* _entry, ASTNode const& _node)
+{
+ auto oldCurrentNode = m_currentNode;
+ m_currentNode = _entry;
+ appendControlFlow(_node);
+ auto endNode = m_currentNode;
+ m_currentNode = oldCurrentNode;
+ return endNode;
+}
+
+void ControlFlowBuilder::connect(CFGNode* _from, CFGNode* _to)
+{
+ solAssert(_from, "");
+ solAssert(_to, "");
+ _from->exits.push_back(_to);
+ _to->entries.push_back(_from);
+}
+
+CFGNode* ControlFlowBuilder::newLabel()
+{
+ return m_nodeContainer.newNode();
+}
+
+CFGNode* ControlFlowBuilder::createLabelHere()
+{
+ auto label = m_nodeContainer.newNode();
+ connect(m_currentNode, label);
+ m_currentNode = label;
+ return label;
+}
+
+void ControlFlowBuilder::placeAndConnectLabel(CFGNode* _node)
+{
+ connect(m_currentNode, _node);
+ m_currentNode = _node;
+}
+
+ControlFlowBuilder::BreakContinueScope::BreakContinueScope(
+ ControlFlowBuilder& _parser,
+ CFGNode* _breakJump,
+ CFGNode* _continueJump
+): m_parser(_parser), m_origBreakJump(_parser.m_breakJump), m_origContinueJump(_parser.m_continueJump)
+{
+ m_parser.m_breakJump = _breakJump;
+ m_parser.m_continueJump = _continueJump;
+}
+
+ControlFlowBuilder::BreakContinueScope::~BreakContinueScope()
+{
+ m_parser.m_breakJump = m_origBreakJump;
+ m_parser.m_continueJump = m_origContinueJump;
+}
diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h
new file mode 100644
index 00000000..e9d96e5f
--- /dev/null
+++ b/libsolidity/analysis/ControlFlowBuilder.h
@@ -0,0 +1,143 @@
+/*
+ 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 <libsolidity/analysis/ControlFlowGraph.h>
+#include <libsolidity/ast/AST.h>
+#include <libsolidity/ast/ASTVisitor.h>
+
+#include <array>
+#include <memory>
+
+namespace dev {
+namespace solidity {
+
+/** Helper class that builds the control flow of a function or modifier.
+ * Modifiers are not yet applied to the functions. This is done in a second
+ * step in the CFG class.
+ */
+class ControlFlowBuilder: private ASTConstVisitor
+{
+public:
+ static std::unique_ptr<FunctionFlow> createFunctionFlow(
+ CFG::NodeContainer& _nodeContainer,
+ FunctionDefinition const& _function
+ );
+ static std::unique_ptr<ModifierFlow> createModifierFlow(
+ CFG::NodeContainer& _nodeContainer,
+ ModifierDefinition const& _modifier
+ );
+
+private:
+ explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
+
+ virtual bool visit(BinaryOperation const& _operation) override;
+ virtual bool visit(Conditional const& _conditional) override;
+ virtual bool visit(IfStatement const& _ifStatement) override;
+ virtual bool visit(ForStatement const& _forStatement) override;
+ virtual bool visit(WhileStatement const& _whileStatement) override;
+ virtual bool visit(Break const&) override;
+ virtual bool visit(Continue const&) override;
+ virtual bool visit(Throw const&) override;
+ virtual bool visit(Block const&) override;
+ virtual void endVisit(Block const&) override;
+ virtual bool visit(Return const& _return) override;
+ virtual bool visit(PlaceholderStatement const&) override;
+ virtual bool visit(FunctionCall const& _functionCall) override;
+
+
+ /// Appends the control flow of @a _node to the current control flow.
+ void appendControlFlow(ASTNode const& _node);
+
+ /// Starts at @a _entry and parses the control flow of @a _node.
+ /// @returns The node at which the parsed control flow ends.
+ /// m_currentNode is not affected (it is saved and restored).
+ CFGNode* createFlow(CFGNode* _entry, ASTNode const& _node);
+
+ /// Creates an arc from @a _from to @a _to.
+ static void connect(CFGNode* _from, CFGNode* _to);
+
+
+protected:
+ virtual bool visitNode(ASTNode const& node) override;
+
+private:
+
+ /// Splits the control flow starting at the current node into n paths.
+ /// m_currentNode is set to nullptr and has to be set manually or
+ /// using mergeFlow later.
+ template<size_t n>
+ std::array<CFGNode*, n> splitFlow()
+ {
+ std::array<CFGNode*, n> result;
+ for (auto& node: result)
+ {
+ node = m_nodeContainer.newNode();
+ connect(m_currentNode, node);
+ }
+ m_currentNode = nullptr;
+ return result;
+ }
+
+ /// Merges the control flow of @a _nodes to @a _endNode.
+ /// If @a _endNode is nullptr, a new node is creates and used as end node.
+ /// Sets the merge destination as current node.
+ /// Note: @a _endNode may be one of the nodes in @a _nodes.
+ template<size_t n>
+ void mergeFlow(std::array<CFGNode*, n> const& _nodes, CFGNode* _endNode = nullptr)
+ {
+ CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode;
+ for (auto& node: _nodes)
+ if (node != mergeDestination)
+ connect(node, mergeDestination);
+ m_currentNode = mergeDestination;
+ }
+
+ CFGNode* newLabel();
+ CFGNode* createLabelHere();
+ void placeAndConnectLabel(CFGNode *_node);
+
+ CFG::NodeContainer& m_nodeContainer;
+
+ /// The control flow of the function that is currently parsed.
+ /// Note: this can also be a ModifierFlow
+ FunctionFlow const& m_currentFunctionFlow;
+
+ CFGNode* m_currentNode = nullptr;
+
+ /// The current jump destination of break Statements.
+ CFGNode* m_breakJump = nullptr;
+ /// The current jump destination of continue Statements.
+ CFGNode* m_continueJump = nullptr;
+
+ /// Helper class that replaces the break and continue jump destinations for the
+ /// current scope and restores the originals at the end of the scope.
+ class BreakContinueScope
+ {
+ public:
+ BreakContinueScope(ControlFlowBuilder& _parser, CFGNode* _breakJump, CFGNode* _continueJump);
+ ~BreakContinueScope();
+ private:
+ ControlFlowBuilder& m_parser;
+ CFGNode* m_origBreakJump;
+ CFGNode* m_origContinueJump;
+ };
+};
+
+}
+}
diff --git a/libsolidity/analysis/ControlFlowGraph.cpp b/libsolidity/analysis/ControlFlowGraph.cpp
new file mode 100644
index 00000000..9b3da0eb
--- /dev/null
+++ b/libsolidity/analysis/ControlFlowGraph.cpp
@@ -0,0 +1,136 @@
+/*
+ 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/>.
+*/
+
+#include <libsolidity/analysis/ControlFlowGraph.h>
+#include <libsolidity/analysis/ControlFlowBuilder.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+
+#include <algorithm>
+
+using namespace std;
+using namespace dev::solidity;
+
+bool CFG::constructFlow(ASTNode const& _astRoot)
+{
+ _astRoot.accept(*this);
+ applyModifiers();
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
+}
+
+
+bool CFG::visit(ModifierDefinition const& _modifier)
+{
+ m_modifierControlFlow[&_modifier] = ControlFlowBuilder::createModifierFlow(m_nodeContainer, _modifier);
+ return false;
+}
+
+bool CFG::visit(FunctionDefinition const& _function)
+{
+ m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function);
+ return false;
+}
+
+FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const
+{
+ solAssert(m_functionControlFlow.count(&_function), "");
+ return *m_functionControlFlow.find(&_function)->second;
+}
+
+CFGNode* CFG::NodeContainer::newNode()
+{
+ m_nodes.emplace_back(new CFGNode());
+ return m_nodes.back().get();
+}
+
+void CFG::applyModifiers()
+{
+ for (auto const& function: m_functionControlFlow)
+ {
+ for (auto const& modifierInvocation: boost::adaptors::reverse(function.first->modifiers()))
+ {
+ if (auto modifierDefinition = dynamic_cast<ModifierDefinition const*>(
+ modifierInvocation->name()->annotation().referencedDeclaration
+ ))
+ {
+ solAssert(m_modifierControlFlow.count(modifierDefinition), "");
+ applyModifierFlowToFunctionFlow(*m_modifierControlFlow[modifierDefinition], function.second.get());
+ }
+ }
+ }
+}
+
+void CFG::applyModifierFlowToFunctionFlow(
+ ModifierFlow const& _modifierFlow,
+ FunctionFlow* _functionFlow
+)
+{
+ solAssert(!!_functionFlow, "");
+
+ map<CFGNode*, CFGNode*> copySrcToCopyDst;
+
+ // inherit the revert node of the function
+ copySrcToCopyDst[_modifierFlow.revert] = _functionFlow->revert;
+
+ // replace the placeholder nodes by the function entry and exit
+ copySrcToCopyDst[_modifierFlow.placeholderEntry] = _functionFlow->entry;
+ copySrcToCopyDst[_modifierFlow.placeholderExit] = _functionFlow->exit;
+
+ stack<CFGNode*> nodesToCopy;
+ nodesToCopy.push(_modifierFlow.entry);
+
+ // map the modifier entry to a new node that will become the new function entry
+ copySrcToCopyDst[_modifierFlow.entry] = m_nodeContainer.newNode();
+
+ while (!nodesToCopy.empty())
+ {
+ CFGNode* copySrcNode = nodesToCopy.top();
+ nodesToCopy.pop();
+
+ solAssert(copySrcToCopyDst.count(copySrcNode), "");
+
+ CFGNode* copyDstNode = copySrcToCopyDst[copySrcNode];
+
+ copyDstNode->block = copySrcNode->block;
+ for (auto const& entry: copySrcNode->entries)
+ {
+ if (!copySrcToCopyDst.count(entry))
+ {
+ copySrcToCopyDst[entry] = m_nodeContainer.newNode();
+ nodesToCopy.push(entry);
+ }
+ copyDstNode->entries.emplace_back(copySrcToCopyDst[entry]);
+ }
+ for (auto const& exit: copySrcNode->exits)
+ {
+ if (!copySrcToCopyDst.count(exit))
+ {
+ copySrcToCopyDst[exit] = m_nodeContainer.newNode();
+ nodesToCopy.push(exit);
+ }
+ copyDstNode->exits.emplace_back(copySrcToCopyDst[exit]);
+ }
+ }
+
+ // if the modifier control flow never reached its exit node,
+ // we need to create a new (disconnected) exit node now
+ if (!copySrcToCopyDst.count(_modifierFlow.exit))
+ copySrcToCopyDst[_modifierFlow.exit] = m_nodeContainer.newNode();
+
+ _functionFlow->entry = copySrcToCopyDst[_modifierFlow.entry];
+ _functionFlow->exit = copySrcToCopyDst[_modifierFlow.exit];
+} \ No newline at end of file
diff --git a/libsolidity/analysis/ControlFlowGraph.h b/libsolidity/analysis/ControlFlowGraph.h
new file mode 100644
index 00000000..c646e4f1
--- /dev/null
+++ b/libsolidity/analysis/ControlFlowGraph.h
@@ -0,0 +1,148 @@
+/*
+ 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 <libsolidity/ast/AST.h>
+#include <libsolidity/ast/ASTVisitor.h>
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <map>
+#include <memory>
+#include <stack>
+#include <vector>
+
+namespace dev
+{
+namespace solidity
+{
+
+/** Basic Control Flow Block.
+ * Basic block of control flow. Consists of a set of (unordered) AST nodes
+ * for which control flow is always linear. A basic control flow block
+ * encompasses at most one scope. Reverts are considered to break the control
+ * flow.
+ * @todo Handle function calls correctly. So far function calls are not considered
+ * to change the control flow.
+ */
+struct ControlFlowBlock
+{
+ /// All variable declarations inside this control flow block.
+ std::vector<VariableDeclaration const*> variableDeclarations;
+ /// All expressions inside this control flow block (this includes all subexpressions!).
+ std::vector<Expression const*> expressions;
+ /// All inline assembly statements inside in this control flow block.
+ std::vector<InlineAssembly const*> inlineAssemblyStatements;
+ /// If control flow returns in this node, the return statement is stored in returnStatement,
+ /// otherwise returnStatement is nullptr.
+ Return const* returnStatement = nullptr;
+};
+
+/** Node of the Control Flow Graph.
+ * The control flow is a directed graph connecting control flow blocks.
+ * An arc between two nodes indicates that the control flow can possibly
+ * move from its start node to its end node during execution.
+ */
+struct CFGNode
+{
+ /// Entry nodes. All CFG nodes from which control flow may move into this node.
+ std::vector<CFGNode*> entries;
+ /// Exit nodes. All CFG nodes to which control flow may continue after this node.
+ std::vector<CFGNode*> exits;
+
+ /// Control flow in the node.
+ ControlFlowBlock block;
+};
+
+/** Describes the control flow of a function. */
+struct FunctionFlow
+{
+ virtual ~FunctionFlow() {}
+ /// Entry node. Control flow of the function starts here.
+ /// This node is empty and does not have any entries.
+ CFGNode* entry = nullptr;
+ /// Exit node. All non-reverting control flow of the function ends here.
+ /// This node is empty and does not have any exits, but may have multiple entries
+ /// (e.g. all return statements of the function).
+ CFGNode* exit = nullptr;
+ /// Revert node. Control flow of the function in case of revert.
+ /// This node is empty does not have any exits, but may have multiple entries
+ /// (e.g. all assert, require, revert and throw statements).
+ CFGNode* revert = nullptr;
+};
+
+/** Describes the control flow of a modifier.
+ * Every placeholder breaks the control flow. The node preceding the
+ * placeholder is assigned placeholderEntry as exit and the node
+ * following the placeholder is assigned placeholderExit as entry.
+ */
+struct ModifierFlow: FunctionFlow
+{
+ /// Control flow leading towards a placeholder exit in placeholderEntry.
+ CFGNode* placeholderEntry = nullptr;
+ /// Control flow coming from a placeholder enter from placeholderExit.
+ CFGNode* placeholderExit = nullptr;
+};
+
+class CFG: private ASTConstVisitor
+{
+public:
+ explicit CFG(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
+
+ bool constructFlow(ASTNode const& _astRoot);
+
+ virtual bool visit(ModifierDefinition const& _modifier) override;
+ virtual bool visit(FunctionDefinition const& _function) override;
+
+ FunctionFlow const& functionFlow(FunctionDefinition const& _function) const;
+
+ class NodeContainer
+ {
+ public:
+ CFGNode* newNode();
+ private:
+ std::vector<std::unique_ptr<CFGNode>> m_nodes;
+ };
+private:
+ /// Initially the control flow for all functions *ignoring* modifiers and for
+ /// all modifiers is constructed. Afterwards the control flow of functions
+ /// is adjusted by applying all modifiers.
+ void applyModifiers();
+
+ /// Creates a copy of the modifier flow @a _modifierFlow, while replacing the
+ /// placeholder entry and exit with the function entry and exit, as well as
+ /// replacing the modifier revert node with the function's revert node.
+ /// The resulting control flow is the new function flow with the modifier applied.
+ /// @a _functionFlow is updated in-place.
+ void applyModifierFlowToFunctionFlow(
+ ModifierFlow const& _modifierFlow,
+ FunctionFlow* _functionFlow
+ );
+
+ ErrorReporter& m_errorReporter;
+
+ /// Node container.
+ /// All nodes allocated during the construction of the control flow graph
+ /// are owned by the CFG class and stored in this container.
+ NodeContainer m_nodeContainer;
+
+ std::map<FunctionDefinition const*, std::unique_ptr<FunctionFlow>> m_functionControlFlow;
+ std::map<ModifierDefinition const*, std::unique_ptr<ModifierFlow>> m_modifierControlFlow;
+};
+
+}
+}
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index a222bdf0..e8694e88 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -2093,6 +2093,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (auto tt = dynamic_cast<TypeType const*>(exprType.get()))
if (tt->actualType()->category() == Type::Category::Enum)
annotation.isPure = true;
+ if (auto magicType = dynamic_cast<MagicType const*>(exprType.get()))
+ if (magicType->kind() == MagicType::Kind::ABI)
+ annotation.isPure = true;
return false;
}
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index dc548538..60e3183c 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -2865,7 +2865,11 @@ bool FunctionType::isPure() const
m_kind == Kind::RIPEMD160 ||
m_kind == Kind::AddMod ||
m_kind == Kind::MulMod ||
- m_kind == Kind::ObjectCreation;
+ m_kind == Kind::ObjectCreation ||
+ m_kind == Kind::ABIEncode ||
+ m_kind == Kind::ABIEncodePacked ||
+ m_kind == Kind::ABIEncodeWithSelector ||
+ m_kind == Kind::ABIEncodeWithSignature;
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 6defacfc..4884696d 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -1046,8 +1046,8 @@ public:
return *m_declaration;
}
bool hasDeclaration() const { return !!m_declaration; }
- /// @returns true if the result of this function only depends on its arguments
- /// and it does not modify the state.
+ /// @returns true if the result of this function only depends on its arguments,
+ /// does not modify the state and is a compile-time constant.
/// Currently, this will only return true for internal functions like keccak and ecrecover.
bool isPure() const;
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index a39e799c..d9f17263 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -89,7 +89,6 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
abiEncode({_argumentType.shared_from_this()}, {make_shared<ArrayType>(DataLocation::Memory, true)});
toSizeAfterFreeMemoryPointer();
m_context << Instruction::REVERT;
- m_context.adjustStackOffset(_argumentType.sizeOnStack());
}
unsigned CompilerUtils::loadFromMemory(
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index a8222e21..4bcc1fa9 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -933,7 +933,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// condition was not met, flag an error
m_context.appendInvalid();
else if (arguments.size() > 1)
+ {
utils().revertWithStringData(*arguments.at(1)->annotation().type);
+ // Here, the argument is consumed, but in the other branch, it is still there.
+ m_context.adjustStackOffset(arguments.at(1)->annotation().type->sizeOnStack());
+ }
else
m_context.appendRevert();
// the success branch
diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp
index c4dee22d..425c5c1e 100644
--- a/libsolidity/formal/SMTChecker.cpp
+++ b/libsolidity/formal/SMTChecker.cpp
@@ -58,6 +58,19 @@ void SMTChecker::analyze(SourceUnit const& _source)
_source.accept(*this);
}
+bool SMTChecker::visit(ContractDefinition const& _contract)
+{
+ for (auto _var : _contract.stateVariables())
+ if (_var->type()->isValueType())
+ createVariable(*_var);
+ return true;
+}
+
+void SMTChecker::endVisit(ContractDefinition const&)
+{
+ m_stateVariables.clear();
+}
+
void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
{
if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value())
@@ -72,13 +85,13 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
"Assertion checker does not yet support constructors and functions with modifiers."
);
m_currentFunction = &_function;
- // We only handle local variables, so we clear at the beginning of the function.
- // If we add storage variables, those should be cleared differently.
m_interface->reset();
m_variables.clear();
+ m_variables.insert(m_stateVariables.begin(), m_stateVariables.end());
m_pathConditions.clear();
m_loopExecutionHappened = false;
initializeLocalVariables(_function);
+ resetStateVariables();
return true;
}
@@ -586,6 +599,12 @@ void SMTChecker::checkCondition(
expressionsToEvaluate.emplace_back(currentValue(*var));
expressionNames.push_back(var->name());
}
+ for (auto const& var: m_stateVariables)
+ if (knownVariable(*var.first))
+ {
+ expressionsToEvaluate.emplace_back(currentValue(*var.first));
+ expressionNames.push_back(var.first->name());
+ }
}
smt::CheckResult result;
vector<string> values;
@@ -607,7 +626,8 @@ void SMTChecker::checkCondition(
message << " for:\n";
solAssert(values.size() == expressionNames.size(), "");
for (size_t i = 0; i < values.size(); ++i)
- message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
+ if (expressionsToEvaluate.at(i).name != values.at(i))
+ message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
}
else
message << ".";
@@ -722,6 +742,15 @@ void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function)
setZeroValue(*retParam);
}
+void SMTChecker::resetStateVariables()
+{
+ for (auto const& variable: m_stateVariables)
+ {
+ newValue(*variable.first);
+ setUnknownValue(*variable.first);
+ }
+}
+
void SMTChecker::resetVariables(vector<Declaration const*> _variables)
{
for (auto const* decl: _variables)
@@ -752,7 +781,14 @@ bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
if (SSAVariable::isSupportedType(_varDecl.type()->category()))
{
solAssert(m_variables.count(&_varDecl) == 0, "");
- m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
+ solAssert(m_stateVariables.count(&_varDecl) == 0, "");
+ if (_varDecl.isLocalVariable())
+ m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
+ else
+ {
+ solAssert(_varDecl.isStateVariable(), "");
+ m_stateVariables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
+ }
return true;
}
else
diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h
index fd54fb5c..50d40ab9 100644
--- a/libsolidity/formal/SMTChecker.h
+++ b/libsolidity/formal/SMTChecker.h
@@ -50,6 +50,8 @@ private:
// because the order of expression evaluation is undefined
// TODO: or just force a certain order, but people might have a different idea about that.
+ virtual bool visit(ContractDefinition const& _node) override;
+ virtual void endVisit(ContractDefinition const& _node) override;
virtual void endVisit(VariableDeclaration const& _node) override;
virtual bool visit(FunctionDefinition const& _node) override;
virtual void endVisit(FunctionDefinition const& _node) override;
@@ -111,6 +113,7 @@ private:
smt::CheckResult checkSatisfiable();
void initializeLocalVariables(FunctionDefinition const& _function);
+ void resetStateVariables();
void resetVariables(std::vector<Declaration const*> _variables);
/// Given two different branches and the touched variables,
/// merge the touched variables into after-branch ite variables
@@ -163,6 +166,7 @@ private:
bool m_loopExecutionHappened = false;
std::map<Expression const*, smt::Expression> m_expressions;
std::map<Declaration const*, SSAVariable> m_variables;
+ std::map<Declaration const*, SSAVariable> m_stateVariables;
std::vector<smt::Expression> m_pathConditions;
ErrorReporter& m_errorReporter;
diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp
index 4e96059d..c2dea844 100644
--- a/libsolidity/formal/VariableUsage.cpp
+++ b/libsolidity/formal/VariableUsage.cpp
@@ -33,7 +33,6 @@ VariableUsage::VariableUsage(ASTNode const& _node)
solAssert(declaration, "");
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
if (
- varDecl->isLocalVariable() &&
identifier->annotation().lValueRequested &&
varDecl->annotation().type->isValueType()
)
diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp
index abf7ddf2..9f505889 100644
--- a/libsolidity/inlineasm/AsmAnalysis.cpp
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -54,6 +54,7 @@ bool AsmAnalyzer::analyze(Block const& _block)
bool AsmAnalyzer::operator()(Label const& _label)
{
+ solAssert(!_label.name.empty(), "");
checkLooseFeature(
_label.location,
"The use of labels is deprecated. Please use \"if\", \"switch\", \"for\" or function calls instead."
@@ -107,6 +108,7 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
+ solAssert(!_identifier.name.empty(), "");
size_t numErrorsBefore = m_errorReporter.errors().size();
bool success = true;
if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
@@ -208,6 +210,7 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{
+ solAssert(_assignment.value, "");
int const expectedItems = _assignment.variableNames.size();
solAssert(expectedItems >= 1, "");
int const stackHeight = m_stackHeight;
@@ -259,6 +262,7 @@ bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
{
+ solAssert(!_funDef.name.empty(), "");
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
solAssert(virtualBlock, "");
Scope& varScope = scope(virtualBlock);
@@ -280,6 +284,7 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
{
+ solAssert(!_funCall.functionName.name.empty(), "");
bool success = true;
size_t arguments = 0;
size_t returns = 0;
@@ -349,6 +354,8 @@ bool AsmAnalyzer::operator()(If const& _if)
bool AsmAnalyzer::operator()(Switch const& _switch)
{
+ solAssert(_switch.expression, "");
+
bool success = true;
if (!expectExpression(*_switch.expression))
@@ -391,6 +398,8 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
bool AsmAnalyzer::operator()(assembly::ForLoop const& _for)
{
+ solAssert(_for.condition, "");
+
Scope* originalScope = m_currentScope;
bool success = true;
@@ -478,6 +487,7 @@ bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation con
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
{
+ solAssert(!_variable.name.empty(), "");
bool success = true;
size_t numErrorsBefore = m_errorReporter.errors().size();
size_t variableSize(-1);
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index 4ff14aa2..47dc30cf 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -29,6 +29,8 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/parsing/Parser.h>
+#include <libsolidity/analysis/ControlFlowAnalyzer.h>
+#include <libsolidity/analysis/ControlFlowGraph.h>
#include <libsolidity/analysis/GlobalContext.h>
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/analysis/TypeChecker.h>
@@ -224,6 +226,22 @@ bool CompilerStack::analyze()
if (noErrors)
{
+ CFG cfg(m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!cfg.constructFlow(*source->ast))
+ noErrors = false;
+
+ if (noErrors)
+ {
+ ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!controlFlowAnalyzer.analyze(*source->ast))
+ noErrors = false;
+ }
+ }
+
+ if (noErrors)
+ {
StaticAnalyzer staticAnalyzer(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!staticAnalyzer.analyze(*source->ast))
diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp
index 49745e29..e2e1eebc 100644
--- a/libsolidity/parsing/Parser.cpp
+++ b/libsolidity/parsing/Parser.cpp
@@ -54,6 +54,7 @@ public:
template <class NodeType, typename... Args>
ASTPointer<NodeType> createNode(Args&& ... _args)
{
+ solAssert(m_location.sourceName, "");
if (m_location.end < 0)
markEndPosition();
return make_shared<NodeType>(m_location, forward<Args>(_args)...);
@@ -1083,18 +1084,110 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const
ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const& _docString)
{
RecursionGuard recursionGuard(*this);
+ LookAheadInfo statementType;
+ IndexAccessedPath iap;
+
+ if (m_scanner->currentToken() == Token::LParen)
+ {
+ ASTNodeFactory nodeFactory(*this);
+ size_t emptyComponents = 0;
+ // First consume all empty components.
+ expectToken(Token::LParen);
+ while (m_scanner->currentToken() == Token::Comma)
+ {
+ m_scanner->next();
+ emptyComponents++;
+ }
+
+ // Now see whether we have a variable declaration or an expression.
+ tie(statementType, iap) = tryParseIndexAccessedPath();
+ switch (statementType)
+ {
+ case LookAheadInfo::VariableDeclaration:
+ {
+ vector<ASTPointer<VariableDeclaration>> variables;
+ ASTPointer<Expression> value;
+ // We have already parsed something like `(,,,,a.b.c[2][3]`
+ VarDeclParserOptions options;
+ options.allowLocationSpecifier = true;
+ variables = vector<ASTPointer<VariableDeclaration>>(emptyComponents, nullptr);
+ variables.push_back(parseVariableDeclaration(options, typeNameFromIndexAccessStructure(iap)));
+
+ while (m_scanner->currentToken() != Token::RParen)
+ {
+ expectToken(Token::Comma);
+ if (m_scanner->currentToken() == Token::Comma || m_scanner->currentToken() == Token::RParen)
+ variables.push_back(nullptr);
+ else
+ variables.push_back(parseVariableDeclaration(options));
+ }
+ expectToken(Token::RParen);
+ expectToken(Token::Assign);
+ value = parseExpression();
+ nodeFactory.setEndPositionFromNode(value);
+ return nodeFactory.createNode<VariableDeclarationStatement>(_docString, variables, value);
+ }
+ case LookAheadInfo::Expression:
+ {
+ // Complete parsing the expression in the current component.
+ vector<ASTPointer<Expression>> components(emptyComponents, nullptr);
+ components.push_back(parseExpression(expressionFromIndexAccessStructure(iap)));
+ while (m_scanner->currentToken() != Token::RParen)
+ {
+ expectToken(Token::Comma);
+ if (m_scanner->currentToken() == Token::Comma || m_scanner->currentToken() == Token::RParen)
+ components.push_back(ASTPointer<Expression>());
+ else
+ components.push_back(parseExpression());
+ }
+ nodeFactory.markEndPosition();
+ expectToken(Token::RParen);
+ return parseExpressionStatement(_docString, nodeFactory.createNode<TupleExpression>(components, false));
+ }
+ default:
+ solAssert(false, "");
+ }
+ }
+ else
+ {
+ tie(statementType, iap) = tryParseIndexAccessedPath();
+ switch (statementType)
+ {
+ case LookAheadInfo::VariableDeclaration:
+ return parseVariableDeclarationStatement(_docString, typeNameFromIndexAccessStructure(iap));
+ case LookAheadInfo::Expression:
+ return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(iap));
+ default:
+ solAssert(false, "");
+ }
+ }
+}
+
+bool Parser::IndexAccessedPath::empty() const
+{
+ if (!indices.empty())
+ {
+ solAssert(!path.empty(), "");
+ }
+ return path.empty() && indices.empty();
+}
+
+
+pair<Parser::LookAheadInfo, Parser::IndexAccessedPath> Parser::tryParseIndexAccessedPath()
+{
// These two cases are very hard to distinguish:
- // x[7 * 20 + 3] a; - x[7 * 20 + 3] = 9;
+ // x[7 * 20 + 3] a; and x[7 * 20 + 3] = 9;
// In the first case, x is a type name, in the second it is the name of a variable.
// As an extension, we can even have:
// `x.y.z[1][2] a;` and `x.y.z[1][2] = 10;`
// Where in the first, x.y.z leads to a type name where in the second, it accesses structs.
- switch (peekStatementType())
+
+ auto statementType = peekStatementType();
+ switch (statementType)
{
- case LookAheadInfo::VariableDeclarationStatement:
- return parseVariableDeclarationStatement(_docString);
- case LookAheadInfo::ExpressionStatement:
- return parseExpressionStatement(_docString);
+ case LookAheadInfo::VariableDeclaration:
+ case LookAheadInfo::Expression:
+ return make_pair(statementType, IndexAccessedPath());
default:
break;
}
@@ -1106,9 +1199,9 @@ ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const&
IndexAccessedPath iap = parseIndexAccessedPath();
if (m_scanner->currentToken() == Token::Identifier || Token::isLocationSpecifier(m_scanner->currentToken()))
- return parseVariableDeclarationStatement(_docString, typeNameFromIndexAccessStructure(iap));
+ return make_pair(LookAheadInfo::VariableDeclaration, move(iap));
else
- return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(iap));
+ return make_pair(LookAheadInfo::Expression, move(iap));
}
ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStatement(
@@ -1116,6 +1209,9 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
ASTPointer<TypeName> const& _lookAheadArrayType
)
{
+ // This does not parse multi variable declaration statements starting directly with
+ // `(`, they are parsed in parseSimpleStatement, because they are hard to distinguish
+ // from tuple expressions.
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
if (_lookAheadArrayType)
@@ -1178,20 +1274,20 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
ASTPointer<ExpressionStatement> Parser::parseExpressionStatement(
ASTPointer<ASTString> const& _docString,
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure
+ ASTPointer<Expression> const& _partialParserResult
)
{
RecursionGuard recursionGuard(*this);
- ASTPointer<Expression> expression = parseExpression(_lookAheadIndexAccessStructure);
+ ASTPointer<Expression> expression = parseExpression(_partialParserResult);
return ASTNodeFactory(*this, expression).createNode<ExpressionStatement>(_docString, expression);
}
ASTPointer<Expression> Parser::parseExpression(
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure
+ ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
- ASTPointer<Expression> expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure);
+ ASTPointer<Expression> expression = parseBinaryExpression(4, _partiallyParsedExpression);
if (Token::isAssignmentOp(m_scanner->currentToken()))
{
Token::Value assignmentOperator = m_scanner->currentToken();
@@ -1217,11 +1313,11 @@ ASTPointer<Expression> Parser::parseExpression(
ASTPointer<Expression> Parser::parseBinaryExpression(
int _minPrecedence,
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure
+ ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
- ASTPointer<Expression> expression = parseUnaryExpression(_lookAheadIndexAccessStructure);
+ ASTPointer<Expression> expression = parseUnaryExpression(_partiallyParsedExpression);
ASTNodeFactory nodeFactory(*this, expression);
int precedence = Token::precedence(m_scanner->currentToken());
for (; precedence >= _minPrecedence; --precedence)
@@ -1237,14 +1333,14 @@ ASTPointer<Expression> Parser::parseBinaryExpression(
}
ASTPointer<Expression> Parser::parseUnaryExpression(
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure
+ ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
- ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
- ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
+ ASTNodeFactory nodeFactory = _partiallyParsedExpression ?
+ ASTNodeFactory(*this, _partiallyParsedExpression) : ASTNodeFactory(*this);
Token::Value token = m_scanner->currentToken();
- if (!_lookAheadIndexAccessStructure && (Token::isUnaryOp(token) || Token::isCountOp(token)))
+ if (!_partiallyParsedExpression && (Token::isUnaryOp(token) || Token::isCountOp(token)))
{
// prefix expression
m_scanner->next();
@@ -1255,7 +1351,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression(
else
{
// potential postfix expression
- ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_lookAheadIndexAccessStructure);
+ ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_partiallyParsedExpression);
token = m_scanner->currentToken();
if (!Token::isCountOp(token))
return subExpression;
@@ -1266,16 +1362,16 @@ ASTPointer<Expression> Parser::parseUnaryExpression(
}
ASTPointer<Expression> Parser::parseLeftHandSideExpression(
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure
+ ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
- ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
- ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
+ ASTNodeFactory nodeFactory = _partiallyParsedExpression ?
+ ASTNodeFactory(*this, _partiallyParsedExpression) : ASTNodeFactory(*this);
ASTPointer<Expression> expression;
- if (_lookAheadIndexAccessStructure)
- expression = _lookAheadIndexAccessStructure;
+ if (_partiallyParsedExpression)
+ expression = _partiallyParsedExpression;
else if (m_scanner->currentToken() == Token::New)
{
expectToken(Token::New);
@@ -1489,16 +1585,16 @@ Parser::LookAheadInfo Parser::peekStatementType() const
bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier);
if (token == Token::Mapping || token == Token::Function || token == Token::Var)
- return LookAheadInfo::VariableDeclarationStatement;
+ return LookAheadInfo::VariableDeclaration;
if (mightBeTypeName)
{
Token::Value next = m_scanner->peekNextToken();
if (next == Token::Identifier || Token::isLocationSpecifier(next))
- return LookAheadInfo::VariableDeclarationStatement;
+ return LookAheadInfo::VariableDeclaration;
if (next == Token::LBrack || next == Token::Period)
return LookAheadInfo::IndexAccessStructure;
}
- return LookAheadInfo::ExpressionStatement;
+ return LookAheadInfo::Expression;
}
Parser::IndexAccessedPath Parser::parseIndexAccessedPath()
@@ -1539,7 +1635,9 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath()
ASTPointer<TypeName> Parser::typeNameFromIndexAccessStructure(Parser::IndexAccessedPath const& _iap)
{
- solAssert(!_iap.path.empty(), "");
+ if (_iap.empty())
+ return {};
+
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
SourceLocation location = _iap.path.front()->location();
@@ -1571,7 +1669,9 @@ ASTPointer<Expression> Parser::expressionFromIndexAccessStructure(
Parser::IndexAccessedPath const& _iap
)
{
- solAssert(!_iap.path.empty(), "");
+ if (_iap.empty())
+ return {};
+
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this, _iap.path.front());
ASTPointer<Expression> expression(_iap.path.front());
diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h
index 7f02d895..08653364 100644
--- a/libsolidity/parsing/Parser.h
+++ b/libsolidity/parsing/Parser.h
@@ -118,19 +118,19 @@ private:
);
ASTPointer<ExpressionStatement> parseExpressionStatement(
ASTPointer<ASTString> const& _docString,
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
+ ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseExpression(
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
+ ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseBinaryExpression(int _minPrecedence = 4,
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
+ ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseUnaryExpression(
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
+ ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseLeftHandSideExpression(
- ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
+ ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parsePrimaryExpression();
std::vector<ASTPointer<Expression>> parseFunctionCallListArguments();
@@ -143,16 +143,18 @@ private:
/// Used as return value of @see peekStatementType.
enum class LookAheadInfo
{
- IndexAccessStructure, VariableDeclarationStatement, ExpressionStatement
+ IndexAccessStructure, VariableDeclaration, Expression
};
/// Structure that represents a.b.c[x][y][z]. Can be converted either to an expression
- /// or to a type name. Path cannot be empty, but indices can be empty.
+ /// or to a type name. For this to be valid, path cannot be empty, but indices can be empty.
struct IndexAccessedPath
{
std::vector<ASTPointer<PrimaryExpression>> path;
std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> indices;
+ bool empty() const;
};
+ std::pair<LookAheadInfo, IndexAccessedPath> tryParseIndexAccessedPath();
/// Performs limited look-ahead to distinguish between variable declaration and expression statement.
/// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to
/// decide with constant look-ahead.
@@ -160,9 +162,11 @@ private:
/// @returns an IndexAccessedPath as a prestage to parsing a variable declaration (type name)
/// or an expression;
IndexAccessedPath parseIndexAccessedPath();
- /// @returns a typename parsed in look-ahead fashion from something like "a.b[8][2**70]".
+ /// @returns a typename parsed in look-ahead fashion from something like "a.b[8][2**70]",
+ /// or an empty pointer if an empty @a _pathAndIncides has been supplied.
ASTPointer<TypeName> typeNameFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices);
- /// @returns an expression parsed in look-ahead fashion from something like "a.b[8][2**70]".
+ /// @returns an expression parsed in look-ahead fashion from something like "a.b[8][2**70]",
+ /// or an empty pointer if an empty @a _pathAndIncides has been supplied.
ASTPointer<Expression> expressionFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices);
ASTPointer<ASTString> expectIdentifierToken();
diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp
index d0c7a551..71133746 100644
--- a/libsolidity/parsing/ParserBase.cpp
+++ b/libsolidity/parsing/ParserBase.cpp
@@ -106,10 +106,10 @@ void ParserBase::decreaseRecursionDepth()
void ParserBase::parserError(string const& _description)
{
- m_errorReporter.parserError(SourceLocation(position(), position(), sourceName()), _description);
+ m_errorReporter.parserError(SourceLocation(position(), endPosition(), sourceName()), _description);
}
void ParserBase::fatalParserError(string const& _description)
{
- m_errorReporter.fatalParserError(SourceLocation(position(), position(), sourceName()), _description);
+ m_errorReporter.fatalParserError(SourceLocation(position(), endPosition(), sourceName()), _description);
}