diff options
Diffstat (limited to 'libsolidity')
86 files changed, 2089 insertions, 1009 deletions
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index dc4c6d15..8c2ab347 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -1,68 +1,121 @@ # Until we have a clear separation, libyul has to be included here set(sources analysis/ConstantEvaluator.cpp + analysis/ConstantEvaluator.h analysis/ContractLevelChecker.cpp + analysis/ContractLevelChecker.h analysis/ControlFlowAnalyzer.cpp + analysis/ControlFlowAnalyzer.h analysis/ControlFlowBuilder.cpp + analysis/ControlFlowBuilder.h analysis/ControlFlowGraph.cpp + analysis/ControlFlowGraph.h analysis/DeclarationContainer.cpp + analysis/DeclarationContainer.h analysis/DocStringAnalyser.cpp + analysis/DocStringAnalyser.h analysis/GlobalContext.cpp + analysis/GlobalContext.h analysis/NameAndTypeResolver.cpp + analysis/NameAndTypeResolver.h analysis/PostTypeChecker.cpp + analysis/PostTypeChecker.h analysis/ReferencesResolver.cpp + analysis/ReferencesResolver.h analysis/SemVerHandler.cpp + analysis/SemVerHandler.h analysis/StaticAnalyzer.cpp + analysis/StaticAnalyzer.h analysis/SyntaxChecker.cpp + analysis/SyntaxChecker.h analysis/TypeChecker.cpp + analysis/TypeChecker.h analysis/ViewPureChecker.cpp + analysis/ViewPureChecker.h ast/AST.cpp + ast/AST.h + ast/AST_accept.h ast/ASTAnnotations.cpp + ast/ASTAnnotations.h + ast/ASTEnums.h + ast/ASTForward.h ast/ASTJsonConverter.cpp + ast/ASTJsonConverter.h ast/ASTPrinter.cpp + ast/ASTPrinter.h + ast/ASTVisitor.h + ast/ExperimentalFeatures.h ast/Types.cpp + ast/Types.h codegen/ABIFunctions.cpp + codegen/ABIFunctions.h codegen/ArrayUtils.cpp + codegen/ArrayUtils.h + codegen/AsmCodeGen.cpp + codegen/AsmCodeGen.h codegen/Compiler.cpp + codegen/Compiler.h codegen/CompilerContext.cpp + codegen/CompilerContext.h codegen/CompilerUtils.cpp + codegen/CompilerUtils.h codegen/ContractCompiler.cpp + codegen/ContractCompiler.h codegen/ExpressionCompiler.cpp + codegen/ExpressionCompiler.h codegen/LValue.cpp + codegen/LValue.h formal/SMTChecker.cpp + formal/SMTChecker.h formal/SMTLib2Interface.cpp + formal/SMTLib2Interface.h formal/SMTPortfolio.cpp + formal/SMTPortfolio.h + formal/SolverInterface.h formal/SSAVariable.cpp + formal/SSAVariable.h formal/SymbolicTypes.cpp + formal/SymbolicTypes.h formal/SymbolicVariables.cpp + formal/SymbolicVariables.h formal/VariableUsage.cpp + formal/VariableUsage.h interface/ABI.cpp + interface/ABI.h interface/AssemblyStack.cpp + interface/AssemblyStack.h interface/CompilerStack.cpp + interface/CompilerStack.h interface/GasEstimator.cpp + interface/GasEstimator.h interface/Natspec.cpp + interface/Natspec.h + interface/ReadFile.h interface/StandardCompiler.cpp + interface/StandardCompiler.h interface/Version.cpp + interface/Version.h parsing/DocStringParser.cpp + parsing/DocStringParser.h parsing/Parser.cpp + parsing/Parser.h + parsing/Token.h ) find_package(Z3 QUIET) if (${Z3_FOUND}) - include_directories(${Z3_INCLUDE_DIR}) add_definitions(-DHAVE_Z3) message("Z3 SMT solver found. This enables optional SMT checking with Z3.") - set(z3_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/formal/Z3Interface.cpp") + set(z3_SRCS formal/Z3Interface.cpp formal/Z3Interface.h) else() set(z3_SRCS) endif() find_package(CVC4 QUIET) if (${CVC4_FOUND}) - include_directories(${CVC4_INCLUDE_DIR}) add_definitions(-DHAVE_CVC4) message("CVC4 SMT solver found. This enables optional SMT checking with CVC4.") - set(cvc4_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/formal/CVC4Interface.cpp") + set(cvc4_SRCS formal/CVC4Interface.cpp formal/CVC4Interface.h) else() set(cvc4_SRCS) endif() @@ -76,9 +129,9 @@ add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS}) target_link_libraries(solidity PUBLIC yul evmasm langutil devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) if (${Z3_FOUND}) - target_link_libraries(solidity PUBLIC ${Z3_LIBRARY}) + target_link_libraries(solidity PUBLIC Z3::Z3) endif() if (${CVC4_FOUND}) - target_link_libraries(solidity PUBLIC ${CVC4_LIBRARIES}) + target_link_libraries(solidity PUBLIC CVC4::CVC4) endif() diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 9d041ce5..e637795a 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -21,6 +21,7 @@ */ #include <libsolidity/analysis/ConstantEvaluator.h> + #include <libsolidity/ast/AST.h> #include <liblangutil/ErrorReporter.h> @@ -41,7 +42,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) auto right = type(_operation.rightExpression()); if (left && right) { - auto commonType = left->binaryOperatorResult(_operation.getOperator(), right); + TypePointer commonType = left->binaryOperatorResult(_operation.getOperator(), right); if (!commonType) m_errorReporter.fatalTypeError( _operation.location(), diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index 6dc564de..96b9611e 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -20,10 +20,9 @@ */ #include <libsolidity/analysis/ContractLevelChecker.h> -#include <libsolidity/ast/AST.h> +#include <libsolidity/ast/AST.h> #include <liblangutil/ErrorReporter.h> - #include <boost/range/adaptor/reversed.hpp> @@ -227,7 +226,7 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con return _type->hasEqualParameterTypes(*_funAndFlag.first); }); if (it == overloads.end()) - overloads.push_back(make_pair(_type, _implemented)); + overloads.emplace_back(_type, _implemented); else if (it->second) { if (!_implemented) @@ -409,8 +408,8 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c auto functionType = make_shared<FunctionType>(*f); // under non error circumstances this should be true if (functionType->interfaceFunctionType()) - externalDeclarations[functionType->externalSignature()].push_back( - make_pair(f, functionType->asCallableFunction(false)) + externalDeclarations[functionType->externalSignature()].emplace_back( + f, functionType->asCallableFunction(false) ); } for (VariableDeclaration const* v: contract->stateVariables()) @@ -419,8 +418,8 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c auto functionType = make_shared<FunctionType>(*v); // under non error circumstances this should be true if (functionType->interfaceFunctionType()) - externalDeclarations[functionType->externalSignature()].push_back( - make_pair(v, functionType->asCallableFunction(false)) + externalDeclarations[functionType->externalSignature()].emplace_back( + v, functionType->asCallableFunction(false) ); } } diff --git a/libsolidity/analysis/ContractLevelChecker.h b/libsolidity/analysis/ContractLevelChecker.h index 15cbf45d..d754687a 100644 --- a/libsolidity/analysis/ContractLevelChecker.h +++ b/libsolidity/analysis/ContractLevelChecker.h @@ -22,7 +22,6 @@ #pragma once #include <libsolidity/ast/ASTForward.h> - #include <map> namespace langutil diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index fe58f0aa..3adf6318 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -16,7 +16,9 @@ */ #include <libsolidity/analysis/ControlFlowAnalyzer.h> + #include <liblangutil/SourceLocation.h> +#include <boost/range/algorithm/sort.hpp> using namespace std; using namespace langutil; @@ -33,131 +35,112 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function) if (_function.isImplemented()) { auto const& functionFlow = m_cfg.functionFlow(_function); - checkUnassignedStorageReturnValues(_function, functionFlow.entry, functionFlow.exit); + checkUninitializedAccess(functionFlow.entry, functionFlow.exit); } return false; } -set<VariableDeclaration const*> ControlFlowAnalyzer::variablesAssignedInNode(CFGNode const *node) +void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const { - set<VariableDeclaration const*> result; - for (auto expression: node->block.expressions) + struct NodeInfo { - if (auto const* assignment = dynamic_cast<Assignment const*>(expression)) + set<VariableDeclaration const*> unassignedVariablesAtEntry; + set<VariableDeclaration const*> unassignedVariablesAtExit; + set<VariableOccurrence const*> uninitializedVariableAccesses; + /// Propagate the information from another node to this node. + /// To be used to propagate information from a node to its exit nodes. + /// Returns true, if new variables were added and thus the current node has + /// to be traversed again. + bool propagateFrom(NodeInfo const& _entryNode) { - 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); - } + size_t previousUnassignedVariablesAtEntry = unassignedVariablesAtEntry.size(); + size_t previousUninitializedVariableAccessess = uninitializedVariableAccesses.size(); + unassignedVariablesAtEntry += _entryNode.unassignedVariablesAtExit; + uninitializedVariableAccesses += _entryNode.uninitializedVariableAccesses; + return + unassignedVariablesAtEntry.size() > previousUnassignedVariablesAtEntry || + uninitializedVariableAccesses.size() > previousUninitializedVariableAccessess + ; } - } - 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) || - returnParameter->type()->category() == Type::Category::Mapping - ) - unassignedAtFunctionEntry.insert(returnParameter.get()); - } - - stack<CFGNode const*> nodesToTraverse; - nodesToTraverse.push(_functionEntry); - - // walk all paths from entry with maximal set of unassigned return values + }; + map<CFGNode const*, NodeInfo> nodeInfos; + set<CFGNode const*> nodesToTraverse; + nodesToTraverse.insert(_entry); + + // Walk all paths starting from the nodes in ``nodesToTraverse`` until ``NodeInfo::propagateFrom`` + // returns false for all exits, i.e. until all paths have been walked with maximal sets of unassigned + // variables and accesses. while (!nodesToTraverse.empty()) { - auto node = nodesToTraverse.top(); - nodesToTraverse.pop(); - - auto& unassignedAtNode = unassigned[node]; + CFGNode const* currentNode = *nodesToTraverse.begin(); + nodesToTraverse.erase(nodesToTraverse.begin()); - if (node->block.returnStatement != nullptr) - if (node->block.returnStatement->expression()) - unassignedAtNode.clear(); - if (!unassignedAtNode.empty()) + auto& nodeInfo = nodeInfos[currentNode]; + auto unassignedVariables = nodeInfo.unassignedVariablesAtEntry; + for (auto const& variableOccurrence: currentNode->variableOccurrences) { - // 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); + switch (variableOccurrence.kind()) + { + case VariableOccurrence::Kind::Assignment: + unassignedVariables.erase(&variableOccurrence.declaration()); + break; + case VariableOccurrence::Kind::InlineAssembly: + // We consider all variables referenced in inline assembly as accessed. + // So far any reference is enough, but we might want to actually analyze + // the control flow in the assembly at some point. + case VariableOccurrence::Kind::Access: + case VariableOccurrence::Kind::Return: + if (unassignedVariables.count(&variableOccurrence.declaration())) + { + if (variableOccurrence.declaration().type()->dataStoredIn(DataLocation::Storage)) + // Merely store the unassigned access. We do not generate an error right away, since this + // path might still always revert. It is only an error if this is propagated to the exit + // node of the function (i.e. there is a path with an uninitialized access). + nodeInfo.uninitializedVariableAccesses.insert(&variableOccurrence); + } + break; + case VariableOccurrence::Kind::Declaration: + unassignedVariables.insert(&variableOccurrence.declaration()); + break; + } } + nodeInfo.unassignedVariablesAtExit = std::move(unassignedVariables); - 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); - } + // Propagate changes to all exits and queue them for traversal, if needed. + for (auto const& exit: currentNode->exits) + if (nodeInfos[exit].propagateFrom(nodeInfo)) + nodesToTraverse.insert(exit); } - if (!unassigned[_functionExit].empty()) + auto const& exitInfo = nodeInfos[_exit]; + if (!exitInfo.uninitializedVariableAccesses.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(); + vector<VariableOccurrence const*> uninitializedAccessesOrdered( + exitInfo.uninitializedVariableAccesses.begin(), + exitInfo.uninitializedVariableAccesses.end() + ); + boost::range::sort( + uninitializedAccessesOrdered, + [](VariableOccurrence const* lhs, VariableOccurrence const* rhs) -> bool + { + return *lhs < *rhs; } ); - for (auto const* returnVal: unassignedOrdered) + + for (auto const* variableOccurrence: uninitializedAccessesOrdered) { 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()); - } + if (variableOccurrence->occurrence()) + ssl.append("The variable was declared here.", variableOccurrence->declaration().location()); m_errorReporter.typeError( - returnVal->location(), + variableOccurrence->occurrence() ? + variableOccurrence->occurrence()->location() : + variableOccurrence->declaration().location(), ssl, - "This variable is of storage pointer type and might be returned without assignment and " - "could be used uninitialized. Assign the variable (potentially from itself) " - "to fix this error." + string("This variable is of storage pointer type and can be ") + + (variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") + + " without prior assignment." ); } } diff --git a/libsolidity/analysis/ControlFlowAnalyzer.h b/libsolidity/analysis/ControlFlowAnalyzer.h index 411d57ff..7761817a 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.h +++ b/libsolidity/analysis/ControlFlowAnalyzer.h @@ -18,7 +18,6 @@ #pragma once #include <libsolidity/analysis/ControlFlowGraph.h> - #include <set> namespace dev @@ -37,12 +36,8 @@ public: 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; + /// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit. + void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const; CFG const& m_cfg; langutil::ErrorReporter& m_errorReporter; diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 5bd39da3..3dab8b16 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -22,7 +22,10 @@ using namespace solidity; using namespace std; ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow): - m_nodeContainer(_nodeContainer), m_currentFunctionFlow(_functionFlow), m_currentNode(_functionFlow.entry) + m_nodeContainer(_nodeContainer), + m_currentNode(_functionFlow.entry), + m_returnNode(_functionFlow.exit), + m_revertNode(_functionFlow.revert) { } @@ -37,26 +40,8 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow( 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; + return functionFlow; } bool ControlFlowBuilder::visit(BinaryOperation const& _operation) @@ -219,64 +204,24 @@ bool ControlFlowBuilder::visit(Continue const&) bool ControlFlowBuilder::visit(Throw const&) { solAssert(!!m_currentNode, ""); - solAssert(!!m_currentFunctionFlow.revert, ""); - connect(m_currentNode, m_currentFunctionFlow.revert); + solAssert(!!m_revertNode, ""); + connect(m_currentNode, m_revertNode); 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); + solAssert(!!m_placeholderEntry, ""); + solAssert(!!m_placeholderExit, ""); + connect(m_currentNode, m_placeholderEntry); m_currentNode = newLabel(); - - connect(modifierFlow->placeholderExit, m_currentNode); + connect(m_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, ""); @@ -286,19 +231,19 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) switch (functionType->kind()) { case FunctionType::Kind::Revert: - solAssert(!!m_currentFunctionFlow.revert, ""); + solAssert(!!m_revertNode, ""); _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); - connect(m_currentNode, m_currentFunctionFlow.revert); + connect(m_currentNode, m_revertNode); m_currentNode = newLabel(); return false; case FunctionType::Kind::Require: case FunctionType::Kind::Assert: { - solAssert(!!m_currentFunctionFlow.revert, ""); + solAssert(!!m_revertNode, ""); _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); - connect(m_currentNode, m_currentFunctionFlow.revert); + connect(m_currentNode, m_revertNode); auto nextNode = newLabel(); connect(m_currentNode, nextNode); m_currentNode = nextNode; @@ -310,6 +255,183 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) return ASTConstVisitor::visit(_functionCall); } +bool ControlFlowBuilder::visit(ModifierInvocation const& _modifierInvocation) +{ + if (auto arguments = _modifierInvocation.arguments()) + for (auto& argument: *arguments) + appendControlFlow(*argument); + + auto modifierDefinition = dynamic_cast<ModifierDefinition const*>( + _modifierInvocation.name()->annotation().referencedDeclaration + ); + if (!modifierDefinition) return false; + solAssert(!!modifierDefinition, ""); + solAssert(!!m_returnNode, ""); + + m_placeholderEntry = newLabel(); + m_placeholderExit = newLabel(); + + appendControlFlow(*modifierDefinition); + connect(m_currentNode, m_returnNode); + + m_currentNode = m_placeholderEntry; + m_returnNode = m_placeholderExit; + + m_placeholderEntry = nullptr; + m_placeholderExit = nullptr; + + return false; +} + +bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition) +{ + for (auto const& parameter: _functionDefinition.parameters()) + appendControlFlow(*parameter); + + for (auto const& returnParameter: _functionDefinition.returnParameters()) + { + appendControlFlow(*returnParameter); + m_returnNode->variableOccurrences.emplace_back( + *returnParameter, + VariableOccurrence::Kind::Return, + nullptr + ); + + } + + for (auto const& modifier: _functionDefinition.modifiers()) + appendControlFlow(*modifier); + + appendControlFlow(_functionDefinition.body()); + + connect(m_currentNode, m_returnNode); + m_currentNode = nullptr; + + return false; +} + +bool ControlFlowBuilder::visit(Return const& _return) +{ + solAssert(!!m_currentNode, ""); + solAssert(!!m_returnNode, ""); + if (_return.expression()) + { + appendControlFlow(*_return.expression()); + // Returns with return expression are considered to be assignments to the return parameters. + for (auto returnParameter: _return.annotation().functionReturnParameters->parameters()) + m_currentNode->variableOccurrences.emplace_back( + *returnParameter, + VariableOccurrence::Kind::Assignment, + &_return + ); + } + connect(m_currentNode, m_returnNode); + m_currentNode = newLabel(); + return true; +} + +bool ControlFlowBuilder::visit(FunctionTypeName const&) +{ + // Do not visit the parameters and return values of a function type name. + // We do not want to consider them as variable declarations for the control flow graph. + return false; +} + +bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly) +{ + solAssert(!!m_currentNode, ""); + for (auto const& ref: _inlineAssembly.annotation().externalReferences) + { + if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration)) + m_currentNode->variableOccurrences.emplace_back( + *variableDeclaration, + VariableOccurrence::Kind::InlineAssembly, + &_inlineAssembly + ); + } + return true; +} + +bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) +{ + solAssert(!!m_currentNode, ""); + + m_currentNode->variableOccurrences.emplace_back( + _variableDeclaration, + VariableOccurrence::Kind::Declaration, + nullptr + ); + + // Handle declaration with immediate assignment. + if (_variableDeclaration.value()) + m_currentNode->variableOccurrences.emplace_back( + _variableDeclaration, + VariableOccurrence::Kind::Assignment, + _variableDeclaration.value().get() + ); + // Function arguments are considered to be immediately assigned as well (they are "externally assigned"). + else if (_variableDeclaration.isCallableParameter() && !_variableDeclaration.isReturnParameter()) + m_currentNode->variableOccurrences.emplace_back( + _variableDeclaration, + VariableOccurrence::Kind::Assignment, + nullptr + ); + return true; +} + +bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + solAssert(!!m_currentNode, ""); + + for (auto const& var: _variableDeclarationStatement.declarations()) + if (var) + var->accept(*this); + if (_variableDeclarationStatement.initialValue()) + { + _variableDeclarationStatement.initialValue()->accept(*this); + for (size_t i = 0; i < _variableDeclarationStatement.declarations().size(); i++) + if (auto const& var = _variableDeclarationStatement.declarations()[i]) + { + auto expression = _variableDeclarationStatement.initialValue(); + if (auto tupleExpression = dynamic_cast<TupleExpression const*>(expression)) + if (tupleExpression->components().size() > 1) + { + solAssert(tupleExpression->components().size() > i, ""); + expression = tupleExpression->components()[i].get(); + } + while (auto tupleExpression = dynamic_cast<TupleExpression const*>(expression)) + if (tupleExpression->components().size() == 1) + expression = tupleExpression->components().front().get(); + else + break; + m_currentNode->variableOccurrences.emplace_back( + *var, + VariableOccurrence::Kind::Assignment, + expression + ); + } + } + return false; +} + +bool ControlFlowBuilder::visit(Identifier const& _identifier) +{ + solAssert(!!m_currentNode, ""); + + if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) + m_currentNode->variableOccurrences.emplace_back( + *variableDeclaration, + static_cast<Expression const&>(_identifier).annotation().lValueRequested ? + VariableOccurrence::Kind::Assignment : + VariableOccurrence::Kind::Access, + &_identifier + ); + + return true; +} + + + void ControlFlowBuilder::appendControlFlow(ASTNode const& _node) { _node.accept(*this); diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h index 40605e00..f196e5fc 100644 --- a/libsolidity/analysis/ControlFlowBuilder.h +++ b/libsolidity/analysis/ControlFlowBuilder.h @@ -38,14 +38,11 @@ public: 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); + // Visits for constructing the control flow. bool visit(BinaryOperation const& _operation) override; bool visit(Conditional const& _conditional) override; bool visit(IfStatement const& _ifStatement) override; @@ -54,12 +51,20 @@ private: bool visit(Break const&) override; bool visit(Continue const&) override; bool visit(Throw const&) override; - bool visit(Block const&) override; - void endVisit(Block const&) override; - bool visit(Return const& _return) override; bool visit(PlaceholderStatement const&) override; bool visit(FunctionCall const& _functionCall) override; + bool visit(ModifierInvocation const& _modifierInvocation) override; + + // Visits for constructing the control flow as well as filling variable occurrences. + bool visit(FunctionDefinition const& _functionDefinition) override; + bool visit(Return const& _return) override; + // Visits for filling variable occurrences. + bool visit(FunctionTypeName const& _functionTypeName) override; + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(VariableDeclaration const& _variableDeclaration) override; + bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; + bool visit(Identifier const& _identifier) override; /// Appends the control flow of @a _node to the current control flow. void appendControlFlow(ASTNode const& _node); @@ -73,9 +78,6 @@ private: static void connect(CFGNode* _from, CFGNode* _to); -protected: - bool visitNode(ASTNode const& node) override; - private: /// Splits the control flow starting at the current node into n paths. @@ -114,17 +116,18 @@ private: 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; + CFGNode* m_returnNode = nullptr; + CFGNode* m_revertNode = nullptr; /// The current jump destination of break Statements. CFGNode* m_breakJump = nullptr; /// The current jump destination of continue Statements. CFGNode* m_continueJump = nullptr; + CFGNode* m_placeholderEntry = nullptr; + CFGNode* m_placeholderExit = 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 diff --git a/libsolidity/analysis/ControlFlowGraph.cpp b/libsolidity/analysis/ControlFlowGraph.cpp index b8860158..8960166a 100644 --- a/libsolidity/analysis/ControlFlowGraph.cpp +++ b/libsolidity/analysis/ControlFlowGraph.cpp @@ -16,10 +16,9 @@ */ #include <libsolidity/analysis/ControlFlowGraph.h> -#include <libsolidity/analysis/ControlFlowBuilder.h> +#include <libsolidity/analysis/ControlFlowBuilder.h> #include <boost/range/adaptor/reversed.hpp> - #include <algorithm> using namespace std; @@ -29,20 +28,14 @@ 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); + if (_function.isImplemented()) + m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function); return false; } @@ -57,81 +50,3 @@ 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]; -} diff --git a/libsolidity/analysis/ControlFlowGraph.h b/libsolidity/analysis/ControlFlowGraph.h index 8fe9fe8e..cc0113d8 100644 --- a/libsolidity/analysis/ControlFlowGraph.h +++ b/libsolidity/analysis/ControlFlowGraph.h @@ -31,25 +31,57 @@ 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 +/** Occurrence of a variable in a block of control flow. + * Stores the declaration of the referenced variable, the + * kind of the occurrence and possibly the node at which + * it occurred. + */ +class VariableOccurrence { - /// 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; +public: + enum class Kind + { + Declaration, + Access, + Return, + Assignment, + InlineAssembly + }; + VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence): + m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence) + { + } + + /// Defines a deterministic order on variable occurrences. + bool operator<(VariableOccurrence const& _rhs) const + { + if (m_occurrence && _rhs.m_occurrence) + { + if (m_occurrence->id() < _rhs.m_occurrence->id()) return true; + if (_rhs.m_occurrence->id() < m_occurrence->id()) return false; + } + else if (_rhs.m_occurrence) + return true; + else if (m_occurrence) + return false; + + using KindCompareType = std::underlying_type<VariableOccurrence::Kind>::type; + return + std::make_pair(m_declaration.id(), static_cast<KindCompareType>(m_occurrenceKind)) < + std::make_pair(_rhs.m_declaration.id(), static_cast<KindCompareType>(_rhs.m_occurrenceKind)) + ; + } + + VariableDeclaration const& declaration() const { return m_declaration; } + Kind kind() const { return m_occurrenceKind; }; + ASTNode const* occurrence() const { return m_occurrence; } +private: + /// Declaration of the occurring variable. + VariableDeclaration const& m_declaration; + /// Kind of occurrence. + Kind m_occurrenceKind = Kind::Access; + /// AST node at which the variable occurred, if available (may be nullptr). + ASTNode const* m_occurrence = nullptr; }; /** Node of the Control Flow Graph. @@ -64,14 +96,15 @@ struct CFGNode /// 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; + /// Variable occurrences in the node. + std::vector<VariableOccurrence> variableOccurrences; }; /** Describes the control flow of a function. */ struct FunctionFlow { - virtual ~FunctionFlow() {} + virtual ~FunctionFlow() = default; + /// Entry node. Control flow of the function starts here. /// This node is empty and does not have any entries. CFGNode* entry = nullptr; @@ -85,19 +118,6 @@ struct FunctionFlow 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: @@ -105,7 +125,6 @@ public: bool constructFlow(ASTNode const& _astRoot); - bool visit(ModifierDefinition const& _modifier) override; bool visit(FunctionDefinition const& _function) override; FunctionFlow const& functionFlow(FunctionDefinition const& _function) const; @@ -118,20 +137,6 @@ public: 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 - ); langutil::ErrorReporter& m_errorReporter; @@ -141,7 +146,6 @@ private: 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/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index cf12a49d..d0657898 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -21,6 +21,7 @@ */ #include <libsolidity/analysis/DeclarationContainer.h> + #include <libsolidity/ast/AST.h> #include <libsolidity/ast/Types.h> #include <libdevcore/StringUtils.h> diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h index 9d7a17a3..e26f5891 100644 --- a/libsolidity/analysis/DeclarationContainer.h +++ b/libsolidity/analysis/DeclarationContainer.h @@ -22,11 +22,10 @@ #pragma once +#include <libsolidity/ast/ASTForward.h> +#include <boost/noncopyable.hpp> #include <map> #include <set> -#include <boost/noncopyable.hpp> - -#include <libsolidity/ast/ASTForward.h> namespace dev { diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 69a7a43c..0878b550 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -22,9 +22,10 @@ */ #include <libsolidity/analysis/DocStringAnalyser.h> + #include <libsolidity/ast/AST.h> -#include <liblangutil/ErrorReporter.h> #include <libsolidity/parsing/DocStringParser.h> +#include <liblangutil/ErrorReporter.h> using namespace std; using namespace dev; diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index cba2655c..cd5fe07d 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -21,10 +21,11 @@ * Container of the (implicit and explicit) global objects. */ -#include <memory> #include <libsolidity/analysis/GlobalContext.h> + #include <libsolidity/ast/AST.h> #include <libsolidity/ast/Types.h> +#include <memory> using namespace std; diff --git a/libsolidity/analysis/GlobalContext.h b/libsolidity/analysis/GlobalContext.h index 4ed08711..09611c41 100644 --- a/libsolidity/analysis/GlobalContext.h +++ b/libsolidity/analysis/GlobalContext.h @@ -22,12 +22,12 @@ #pragma once -#include <string> -#include <vector> +#include <libsolidity/ast/ASTForward.h> +#include <boost/noncopyable.hpp> #include <map> #include <memory> -#include <boost/noncopyable.hpp> -#include <libsolidity/ast/ASTForward.h> +#include <string> +#include <vector> namespace dev { diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 0528a200..95bc69fe 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -22,11 +22,10 @@ #include <libsolidity/analysis/NameAndTypeResolver.h> -#include <libsolidity/ast/AST.h> #include <libsolidity/analysis/TypeChecker.h> +#include <libsolidity/ast/AST.h> #include <liblangutil/ErrorReporter.h> #include <libdevcore/StringUtils.h> - #include <boost/algorithm/string.hpp> using namespace std; diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 1b034ef4..89c53932 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -22,13 +22,15 @@ #pragma once -#include <map> -#include <list> -#include <boost/noncopyable.hpp> #include <libsolidity/analysis/DeclarationContainer.h> #include <libsolidity/analysis/ReferencesResolver.h> -#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTAnnotations.h> +#include <libsolidity/ast/ASTVisitor.h> + +#include <boost/noncopyable.hpp> + +#include <list> +#include <map> namespace langutil { diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index 27cbcd45..6a7e5c7e 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -16,15 +16,14 @@ */ #include <libsolidity/analysis/PostTypeChecker.h> -#include <libsolidity/ast/AST.h> + #include <libsolidity/analysis/SemVerHandler.h> -#include <liblangutil/ErrorReporter.h> +#include <libsolidity/ast/AST.h> #include <libsolidity/interface/Version.h> - +#include <liblangutil/ErrorReporter.h> #include <libdevcore/Algorithms.h> #include <boost/range/adaptor/map.hpp> - #include <memory> using namespace std; diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index c4931d98..4e6d7f59 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -21,12 +21,15 @@ */ #include <libsolidity/analysis/ReferencesResolver.h> -#include <libsolidity/ast/AST.h> #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/analysis/ConstantEvaluator.h> +#include <libsolidity/ast/AST.h> + #include <libyul/AsmAnalysis.h> #include <libyul/AsmAnalysisInfo.h> #include <libyul/AsmData.h> +#include <libyul/backends/evm/EVMDialect.h> + #include <liblangutil/ErrorReporter.h> #include <liblangutil/Exceptions.h> @@ -316,7 +319,14 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // We use the latest EVM version because we will re-run it anyway. yul::AsmAnalysisInfo analysisInfo; boost::optional<Error::Type> errorTypeForLoose = Error::Type::SyntaxError; - yul::AsmAnalyzer(analysisInfo, errorsIgnored, EVMVersion(), errorTypeForLoose, yul::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); + yul::AsmAnalyzer( + analysisInfo, + errorsIgnored, + EVMVersion(), + errorTypeForLoose, + yul::EVMDialect::looseAssemblyForEVM(), + resolver + ).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 32c0553f..b3de9458 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -22,12 +22,13 @@ #pragma once -#include <map> -#include <list> -#include <boost/noncopyable.hpp> #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTAnnotations.h> +#include <boost/noncopyable.hpp> +#include <list> +#include <map> + namespace langutil { class ErrorReporter; diff --git a/libsolidity/analysis/SemVerHandler.cpp b/libsolidity/analysis/SemVerHandler.cpp index 64fa17b3..7c6ba91f 100644 --- a/libsolidity/analysis/SemVerHandler.cpp +++ b/libsolidity/analysis/SemVerHandler.cpp @@ -21,6 +21,7 @@ */ #include <libsolidity/analysis/SemVerHandler.h> + #include <functional> using namespace std; diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 38391841..aaaa4f9f 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -21,6 +21,7 @@ */ #include <libsolidity/analysis/StaticAnalyzer.h> + #include <libsolidity/analysis/ConstantEvaluator.h> #include <libsolidity/ast/AST.h> #include <liblangutil/ErrorReporter.h> @@ -63,21 +64,21 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function) void StaticAnalyzer::endVisit(FunctionDefinition const&) { - m_currentFunction = nullptr; - m_constructor = false; - for (auto const& var: m_localVarUseCount) - if (var.second == 0) - { - if (var.first.second->isCallableParameter()) - m_errorReporter.warning( - var.first.second->location(), - "Unused function parameter. Remove or comment out the variable name to silence this warning." - ); - else - m_errorReporter.warning(var.first.second->location(), "Unused local variable."); - } - + if (m_currentFunction && !m_currentFunction->body().statements().empty()) + for (auto const& var: m_localVarUseCount) + if (var.second == 0) + { + if (var.first.second->isCallableParameter()) + m_errorReporter.warning( + var.first.second->location(), + "Unused function parameter. Remove or comment out the variable name to silence this warning." + ); + else + m_errorReporter.warning(var.first.second->location(), "Unused local variable."); + } m_localVarUseCount.clear(); + m_constructor = false; + m_currentFunction = nullptr; } bool StaticAnalyzer::visit(Identifier const& _identifier) diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index a73d7e5c..066b5004 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -16,15 +16,18 @@ */ #include <libsolidity/analysis/SyntaxChecker.h> -#include <memory> + +#include <libsolidity/analysis/SemVerHandler.h> #include <libsolidity/ast/AST.h> #include <libsolidity/ast/ExperimentalFeatures.h> -#include <libsolidity/analysis/SemVerHandler.h> -#include <liblangutil/ErrorReporter.h> #include <libsolidity/interface/Version.h> -#include <boost/algorithm/cxx11/all_of.hpp> +#include <liblangutil/ErrorReporter.h> + +#include <boost/algorithm/cxx11/all_of.hpp> #include <boost/algorithm/string.hpp> + +#include <memory> #include <string> using namespace std; @@ -111,7 +114,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); SemVerMatchExpressionParser parser(tokens, literals); auto matchExpression = parser.parse(); - SemVerVersion currentVersion{string(VersionString)}; + static SemVerVersion const currentVersion{string(VersionString)}; if (!matchExpression.matches(currentVersion)) m_errorReporter.syntaxError( _pragma.location(), diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 9350df05..507a2c94 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -26,6 +26,7 @@ #include <libyul/AsmAnalysis.h> #include <libyul/AsmAnalysisInfo.h> #include <libyul/AsmData.h> +#include <libyul/backends/evm/EVMDialect.h> #include <liblangutil/ErrorReporter.h> @@ -33,8 +34,8 @@ #include <libdevcore/StringUtils.h> #include <boost/algorithm/cxx11/all_of.hpp> -#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/join.hpp> +#include <boost/algorithm/string/predicate.hpp> #include <memory> #include <vector> @@ -658,7 +659,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) m_errorReporter, m_evmVersion, Error::Type::SyntaxError, - yul::AsmFlavour::Loose, + yul::EVMDialect::looseAssemblyForEVM(), identifierAccess ); if (!analyzer.analyze(_inlineAssembly.operations())) @@ -935,30 +936,32 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) var.accept(*this); if (!valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type)) { + auto errorMsg = "Type " + + valueComponentType->toString() + + " is not implicitly convertible to expected type " + + var.annotation().type->toString(); if ( valueComponentType->category() == Type::Category::RationalNumber && dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() && valueComponentType->mobileType() ) - m_errorReporter.typeError( - _statement.location(), - "Type " + - valueComponentType->toString() + - " is not implicitly convertible to expected type " + - var.annotation().type->toString() + - ". Try converting to type " + - valueComponentType->mobileType()->toString() + - " or use an explicit conversion." - ); + { + if (var.annotation().type->operator==(*valueComponentType->mobileType())) + m_errorReporter.typeError( + _statement.location(), + errorMsg + ", but it can be explicitly converted." + ); + else + m_errorReporter.typeError( + _statement.location(), + errorMsg + + ". Try converting to type " + + valueComponentType->mobileType()->toString() + + " or use an explicit conversion." + ); + } else - m_errorReporter.typeError( - _statement.location(), - "Type " + - valueComponentType->toString() + - " is not implicitly convertible to expected type " + - var.annotation().type->toString() + - "." - ); + m_errorReporter.typeError(_statement.location(), errorMsg + "."); } } } @@ -1252,7 +1255,8 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) { TypePointer const& leftType = type(_operation.leftExpression()); TypePointer const& rightType = type(_operation.rightExpression()); - TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + TypeResult result = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + TypePointer commonType = result.get(); if (!commonType) { m_errorReporter.typeError( @@ -1262,7 +1266,8 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) " not compatible with types " + leftType->toString() + " and " + - rightType->toString() + rightType->toString() + + (!result.message().empty() ? ". " + result.message() : "") ); commonType = leftType; } @@ -2329,30 +2334,32 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte _expression.accept(*this); if (!type(_expression)->isImplicitlyConvertibleTo(_expectedType)) { + auto errorMsg = "Type " + + type(_expression)->toString() + + " is not implicitly convertible to expected type " + + _expectedType.toString(); if ( type(_expression)->category() == Type::Category::RationalNumber && dynamic_pointer_cast<RationalNumberType const>(type(_expression))->isFractional() && type(_expression)->mobileType() ) - m_errorReporter.typeError( - _expression.location(), - "Type " + - type(_expression)->toString() + - " is not implicitly convertible to expected type " + - _expectedType.toString() + - ". Try converting to type " + - type(_expression)->mobileType()->toString() + - " or use an explicit conversion." - ); + { + if (_expectedType.operator==(*type(_expression)->mobileType())) + m_errorReporter.typeError( + _expression.location(), + errorMsg + ", but it can be explicitly converted." + ); + else + m_errorReporter.typeError( + _expression.location(), + errorMsg + + ". Try converting to type " + + type(_expression)->mobileType()->toString() + + " or use an explicit conversion." + ); + } else - m_errorReporter.typeError( - _expression.location(), - "Type " + - type(_expression)->toString() + - " is not implicitly convertible to expected type " + - _expectedType.toString() + - "." - ); + m_errorReporter.typeError(_expression.location(), errorMsg + "."); return false; } return true; diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index ebfcdadc..b60c571a 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -24,10 +24,10 @@ #include <liblangutil/EVMVersion.h> -#include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/ast/Types.h> namespace langutil { diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 1112d682..eb019481 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -16,14 +16,10 @@ */ #include <libsolidity/analysis/ViewPureChecker.h> - -#include <libevmasm/SemanticInformation.h> - #include <libsolidity/ast/ExperimentalFeatures.h> #include <libyul/AsmData.h> - #include <liblangutil/ErrorReporter.h> - +#include <libevmasm/SemanticInformation.h> #include <functional> using namespace std; @@ -156,6 +152,7 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef) m_bestMutabilityAndLocation.mutability < _funDef.stateMutability() && _funDef.stateMutability() != StateMutability::Payable && _funDef.isImplemented() && + !_funDef.body().statements().empty() && !_funDef.isConstructor() && !_funDef.isFallback() && !_funDef.annotation().superFunction diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 3ae6bd6d..f379d758 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -21,13 +21,12 @@ */ #include <libsolidity/ast/AST.h> + #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/AST_accept.h> - #include <libdevcore/Keccak256.h> #include <boost/algorithm/string.hpp> - #include <algorithm> #include <functional> @@ -198,7 +197,7 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::inter { signaturesSeen.insert(functionSignature); FixedHash<4> hash(dev::keccak256(functionSignature)); - m_interfaceFunctionList->push_back(make_pair(hash, fun)); + m_interfaceFunctionList->emplace_back(hash, fun); } } } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 2f418b09..9ac065ea 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -22,24 +22,22 @@ #pragma once - -#include <libsolidity/parsing/Token.h> #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTEnums.h> +#include <libsolidity/parsing/Token.h> #include <liblangutil/SourceLocation.h> #include <libevmasm/Instruction.h> - #include <libdevcore/FixedHash.h> -#include <json/json.h> #include <boost/noncopyable.hpp> +#include <json/json.h> +#include <memory> #include <string> #include <vector> -#include <memory> namespace yul { diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index e9cc905e..d1acf90b 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -27,8 +27,8 @@ #include <map> #include <memory> -#include <vector> #include <set> +#include <vector> namespace yul { @@ -46,7 +46,7 @@ using TypePointer = std::shared_ptr<Type const>; struct ASTAnnotation { - virtual ~ASTAnnotation() {} + virtual ~ASTAnnotation() = default; }; struct DocTag @@ -57,7 +57,7 @@ struct DocTag struct DocumentedAnnotation { - virtual ~DocumentedAnnotation() {} + virtual ~DocumentedAnnotation() = default; /// Mapping docstring tag name -> content. std::multimap<std::string, DocTag> docTags; }; diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index 992fe4cd..f61acad9 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -22,8 +22,8 @@ #pragma once -#include <string> #include <memory> +#include <string> #include <vector> // Forward-declare all AST node types diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index cfb13271..f8222598 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -20,11 +20,12 @@ */ #include <libsolidity/ast/ASTJsonConverter.h> -#include <boost/algorithm/string/join.hpp> -#include <libdevcore/UTF8.h> + #include <libsolidity/ast/AST.h> #include <libyul/AsmData.h> #include <libyul/AsmPrinter.h> +#include <libdevcore/UTF8.h> +#include <boost/algorithm/string/join.hpp> using namespace std; using namespace langutil; @@ -234,7 +235,7 @@ bool ASTJsonConverter::visit(ImportDirective const& _node) make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)), make_pair("scope", idOrNull(_node.scope())) }; - attributes.push_back(make_pair("unitAlias", _node.name())); + attributes.emplace_back("unitAlias", _node.name()); Json::Value symbolAliases(Json::arrayValue); for (auto const& symbolAlias: _node.symbolAliases()) { @@ -244,7 +245,7 @@ bool ASTJsonConverter::visit(ImportDirective const& _node) tuple["local"] = symbolAlias.second ? Json::Value(*symbolAlias.second) : Json::nullValue; symbolAliases.append(tuple); } - attributes.push_back(make_pair("symbolAliases", std::move(symbolAliases))); + attributes.emplace_back("symbolAliases", std::move(symbolAliases)); setJsonNode(_node, "ImportDirective", std::move(attributes)); return false; } @@ -357,7 +358,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }; if (m_inEvent) - attributes.push_back(make_pair("indexed", _node.isIndexed())); + attributes.emplace_back("indexed", _node.isIndexed()); setJsonNode(_node, "VariableDeclaration", std::move(attributes)); return false; } @@ -647,11 +648,11 @@ bool ASTJsonConverter::visit(FunctionCall const& _node) }; if (m_legacy) { - attributes.push_back(make_pair("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall)); - attributes.push_back(make_pair("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion)); + attributes.emplace_back("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall); + attributes.emplace_back("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion); } else - attributes.push_back(make_pair("kind", functionCallKind(_node.annotation().kind))); + attributes.emplace_back("kind", functionCallKind(_node.annotation().kind)); appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "FunctionCall", std::move(attributes)); return false; @@ -724,7 +725,7 @@ bool ASTJsonConverter::visit(Literal const& _node) std::vector<pair<string, Json::Value>> attributes = { make_pair(m_legacy ? "token" : "kind", literalTokenKind(_node.token())), make_pair("value", value), - make_pair(m_legacy ? "hexvalue" : "hexValue", toHex(_node.value())), + make_pair(m_legacy ? "hexvalue" : "hexValue", toHex(asBytes(_node.value()))), make_pair( "subdenomination", subdenomination == Token::Illegal ? diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index ef0a217a..770e3d9d 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -22,12 +22,13 @@ #pragma once -#include <ostream> -#include <stack> +#include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTVisitor.h> #include <liblangutil/Exceptions.h> -#include <libsolidity/ast/ASTAnnotations.h> + #include <json/json.h> +#include <ostream> +#include <stack> namespace langutil { diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index cdc6ae7d..d8bafa2c 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -21,11 +21,10 @@ */ #include <libsolidity/ast/ASTPrinter.h> -#include <libsolidity/ast/AST.h> - -#include <json/json.h> +#include <libsolidity/ast/AST.h> #include <boost/algorithm/string/join.hpp> +#include <json/json.h> using namespace std; using namespace langutil; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index de3bf8a2..d762af47 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -22,9 +22,9 @@ #pragma once -#include <ostream> #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/interface/GasEstimator.h> +#include <ostream> namespace dev { diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 1a761032..8cb94e05 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -22,10 +22,10 @@ #pragma once -#include <string> +#include <libsolidity/ast/AST.h> #include <functional> +#include <string> #include <vector> -#include <libsolidity/ast/AST.h> namespace dev { diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6cadb5f3..cc978b4a 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -24,22 +24,22 @@ #include <libsolidity/ast/AST.h> -#include <libdevcore/CommonIO.h> +#include <libdevcore/Algorithms.h> #include <libdevcore/CommonData.h> +#include <libdevcore/CommonIO.h> #include <libdevcore/Keccak256.h> #include <libdevcore/UTF8.h> -#include <libdevcore/Algorithms.h> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/join.hpp> -#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/predicate.hpp> -#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/split.hpp> #include <boost/range/adaptor/reversed.hpp> -#include <boost/range/algorithm/copy.hpp> #include <boost/range/adaptor/sliced.hpp> #include <boost/range/adaptor/transformed.hpp> -#include <boost/algorithm/string.hpp> +#include <boost/range/algorithm/copy.hpp> #include <limits> @@ -125,6 +125,22 @@ bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2) return fitsPrecisionBaseX(_mantissa, 1.0, _expBase2); } +/// Checks whether _value fits into IntegerType _type. +bool fitsIntegerType(bigint const& _value, IntegerType const& _type) +{ + return (_type.minValue() <= _value) && (_value <= _type.maxValue()); +} + +/// Checks whether _value fits into _bits bits when having 1 bit as the sign bit +/// if _signed is true. +bool fitsIntoBits(bigint const& _value, unsigned _bits, bool _signed) +{ + return fitsIntegerType(_value, IntegerType( + _bits, + _signed ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned + )); +} + } void StorageOffsets::computeOffsets(TypePointers const& _types) @@ -446,7 +462,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition continue; FunctionTypePointer fun = FunctionType(*function, false).asCallableFunction(true, true); if (_type.isImplicitlyConvertibleTo(*fun->selfType())) - members.push_back(MemberList::Member(function->name(), fun, function)); + members.emplace_back(function->name(), fun, function); } } return members; @@ -466,7 +482,7 @@ string AddressType::richIdentifier() const return "t_address"; } -bool AddressType::isImplicitlyConvertibleTo(Type const& _other) const +BoolResult AddressType::isImplicitlyConvertibleTo(Type const& _other) const { if (_other.category() != category()) return false; @@ -475,7 +491,7 @@ bool AddressType::isImplicitlyConvertibleTo(Type const& _other) const return other.m_stateMutability <= m_stateMutability; } -bool AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (auto const* contractType = dynamic_cast<ContractType const*>(&_convertTo)) return (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable(); @@ -504,17 +520,16 @@ u256 AddressType::literalValue(Literal const* _literal) const return u256(_literal->valueWithoutUnderscores()); } -TypePointer AddressType::unaryOperatorResult(Token _operator) const +TypeResult AddressType::unaryOperatorResult(Token _operator) const { return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); } -TypePointer AddressType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult AddressType::binaryOperatorResult(Token _operator, TypePointer const& _other) const { - // Addresses can only be compared. if (!TokenTraits::isCompareOp(_operator)) - return TypePointer(); + return TypeResult{"Arithmetic operations on addresses are not supported. Convert to integer first before using them."}; return Type::commonType(shared_from_this(), _other); } @@ -576,7 +591,7 @@ string IntegerType::richIdentifier() const return "t_" + string(isSigned() ? "" : "u") + "int" + to_string(numBits()); } -bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() == category()) { @@ -597,7 +612,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } -bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || _convertTo.category() == Category::Address || @@ -607,18 +622,17 @@ bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const _convertTo.category() == Category::FixedPoint; } -TypePointer IntegerType::unaryOperatorResult(Token _operator) const +TypeResult IntegerType::unaryOperatorResult(Token _operator) const { // "delete" is ok for all integer types if (_operator == Token::Delete) - return make_shared<TupleType>(); - // we allow +, -, ++ and -- - else if (_operator == Token::Add || _operator == Token::Sub || - _operator == Token::Inc || _operator == Token::Dec || - _operator == Token::BitNot) - return shared_from_this(); + return TypeResult{make_shared<TupleType>()}; + // we allow -, ++ and -- + else if (_operator == Token::Sub || _operator == Token::Inc || + _operator == Token::Dec || _operator == Token::BitNot) + return TypeResult{shared_from_this()}; else - return TypePointer(); + return TypeResult{""}; } bool IntegerType::operator==(Type const& _other) const @@ -651,7 +665,7 @@ bigint IntegerType::maxValue() const return (bigint(1) << m_bits) - 1; } -TypePointer IntegerType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult IntegerType::binaryOperatorResult(Token _operator, TypePointer const& _other) const { if ( _other->category() != Category::RationalNumber && @@ -679,9 +693,8 @@ TypePointer IntegerType::binaryOperatorResult(Token _operator, TypePointer const return TypePointer(); if (auto intType = dynamic_pointer_cast<IntegerType const>(commonType)) { - // Signed EXP is not allowed if (Token::Exp == _operator && intType->isSigned()) - return TypePointer(); + return TypeResult{"Exponentiation is not allowed for signed integer types."}; } else if (auto fixType = dynamic_pointer_cast<FixedPointType const>(commonType)) if (Token::Exp == _operator) @@ -704,7 +717,7 @@ string FixedPointType::richIdentifier() const return "t_" + string(isSigned() ? "" : "u") + "fixed" + to_string(m_totalBits) + "x" + to_string(m_fractionalDigits); } -bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() == category()) { @@ -717,18 +730,18 @@ bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } -bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || _convertTo.category() == Category::Integer; } -TypePointer FixedPointType::unaryOperatorResult(Token _operator) const +TypeResult FixedPointType::unaryOperatorResult(Token _operator) const { switch(_operator) { case Token::Delete: // "delete" is ok for all fixed types - return make_shared<TupleType>(); + return TypeResult(make_shared<TupleType>()); case Token::Add: case Token::Sub: case Token::Inc: @@ -771,7 +784,7 @@ bigint FixedPointType::minIntegerValue() const return bigint(0); } -TypePointer FixedPointType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FixedPointType::binaryOperatorResult(Token _operator, TypePointer const& _other) const { auto commonType = Type::commonType(shared_from_this(), _other); @@ -957,7 +970,7 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal return make_tuple(true, value); } -bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const { switch (_convertTo.category()) { @@ -966,27 +979,21 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const if (isFractional()) return false; IntegerType const& targetType = dynamic_cast<IntegerType const&>(_convertTo); - if (m_value == rational(0)) - return true; - unsigned forSignBit = (targetType.isSigned() ? 1 : 0); - if (m_value > rational(0)) - { - if (m_value.numerator() <= (u256(-1) >> (256 - targetType.numBits() + forSignBit))) - return true; - return false; - } - if (targetType.isSigned()) - { - if (-m_value.numerator() <= (u256(1) << (targetType.numBits() - forSignBit))) - return true; - } - return false; + return fitsIntegerType(m_value.numerator(), targetType); } case Category::FixedPoint: { - if (auto fixed = fixedPointType()) - return fixed->isImplicitlyConvertibleTo(_convertTo); - return false; + FixedPointType const& targetType = dynamic_cast<FixedPointType const&>(_convertTo); + // Store a negative number into an unsigned. + if (isNegative() && !targetType.isSigned()) + return false; + if (!isFractional()) + return (targetType.minIntegerValue() <= m_value) && (m_value <= targetType.maxIntegerValue()); + rational value = m_value * pow(bigint(10), targetType.fractionalDigits()); + // Need explicit conversion since truncation will occur. + if (value.denominator() != 1) + return false; + return fitsIntoBits(value.numerator(), targetType.numBits(), targetType.isSigned()); } case Category::FixedBytes: return (m_value == rational(0)) || (m_compatibleBytesType && *m_compatibleBytesType == _convertTo); @@ -995,7 +1002,7 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const } } -bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (isImplicitlyConvertibleTo(_convertTo)) return true; @@ -1008,7 +1015,7 @@ bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const return false; } -TypePointer RationalNumberType::unaryOperatorResult(Token _operator) const +TypeResult RationalNumberType::unaryOperatorResult(Token _operator) const { rational value; switch (_operator) @@ -1029,10 +1036,10 @@ TypePointer RationalNumberType::unaryOperatorResult(Token _operator) const default: return TypePointer(); } - return make_shared<RationalNumberType>(value); + return TypeResult(make_shared<RationalNumberType>(value)); } -TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult RationalNumberType::binaryOperatorResult(Token _operator, TypePointer const& _other) const { if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint) { @@ -1129,9 +1136,8 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe uint32_t absExp = bigint(abs(exp)).convert_to<uint32_t>(); - // Limit size to 4096 bits if (!fitsPrecisionExp(abs(m_value.numerator()), absExp) || !fitsPrecisionExp(abs(m_value.denominator()), absExp)) - return TypePointer(); + return TypeResult{"Precision of rational constants is limited to 4096 bits."}; static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint { if (_base == 1) @@ -1212,9 +1218,9 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe // verify that numerator and denominator fit into 4096 bit after every operation if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096) - return TypePointer(); + return TypeResult{"Precision of rational constants is limited to 4096 bits."}; - return make_shared<RationalNumberType>(value); + return TypeResult(make_shared<RationalNumberType>(value)); } } @@ -1354,7 +1360,7 @@ StringLiteralType::StringLiteralType(Literal const& _literal): { } -bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (auto fixedBytes = dynamic_cast<FixedBytesType const*>(&_convertTo)) return size_t(fixedBytes->numBytes()) >= m_value.size(); @@ -1409,7 +1415,7 @@ FixedBytesType::FixedBytesType(unsigned _bytes): m_bytes(_bytes) ); } -bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -1417,7 +1423,7 @@ bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const return convertTo.m_bytes >= m_bytes; } -bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return (_convertTo.category() == Category::Integer && numBytes() * 8 == dynamic_cast<IntegerType const&>(_convertTo).numBits()) || (_convertTo.category() == Category::Address && numBytes() == 20) || @@ -1425,18 +1431,18 @@ bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const _convertTo.category() == category(); } -TypePointer FixedBytesType::unaryOperatorResult(Token _operator) const +TypeResult FixedBytesType::unaryOperatorResult(Token _operator) const { // "delete" and "~" is okay for FixedBytesType if (_operator == Token::Delete) - return make_shared<TupleType>(); + return TypeResult(make_shared<TupleType>()); else if (_operator == Token::BitNot) return shared_from_this(); return TypePointer(); } -TypePointer FixedBytesType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FixedBytesType::binaryOperatorResult(Token _operator, TypePointer const& _other) const { if (TokenTraits::isShiftOp(_operator)) { @@ -1452,7 +1458,7 @@ TypePointer FixedBytesType::binaryOperatorResult(Token _operator, TypePointer co // FixedBytes can be compared and have bitwise operators applied to them if (TokenTraits::isCompareOp(_operator) || TokenTraits::isBitOp(_operator)) - return commonType; + return TypeResult(commonType); return TypePointer(); } @@ -1486,14 +1492,14 @@ u256 BoolType::literalValue(Literal const* _literal) const solAssert(false, "Bool type constructed from non-boolean literal."); } -TypePointer BoolType::unaryOperatorResult(Token _operator) const +TypeResult BoolType::unaryOperatorResult(Token _operator) const { if (_operator == Token::Delete) - return make_shared<TupleType>(); + return TypeResult(make_shared<TupleType>()); return (_operator == Token::Not) ? shared_from_this() : TypePointer(); } -TypePointer BoolType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult BoolType::binaryOperatorResult(Token _operator, TypePointer const& _other) const { if (category() != _other->category()) return TypePointer(); @@ -1503,7 +1509,7 @@ TypePointer BoolType::binaryOperatorResult(Token _operator, TypePointer const& _ return TypePointer(); } -bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (*this == _convertTo) return true; @@ -1520,7 +1526,7 @@ bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } -bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (auto const* addressType = dynamic_cast<AddressType const*>(&_convertTo)) return isPayable() || (addressType->stateMutability() < StateMutability::Payable); @@ -1533,14 +1539,14 @@ bool ContractType::isPayable() const return fallbackFunction && fallbackFunction->isPayable(); } -TypePointer ContractType::unaryOperatorResult(Token _operator) const +TypeResult ContractType::unaryOperatorResult(Token _operator) const { if (isSuper()) return TypePointer{}; return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); } -TypePointer ReferenceType::unaryOperatorResult(Token _operator) const +TypeResult ReferenceType::unaryOperatorResult(Token _operator) const { if (_operator != Token::Delete) return TypePointer(); @@ -1551,7 +1557,7 @@ TypePointer ReferenceType::unaryOperatorResult(Token _operator) const case DataLocation::CallData: return TypePointer(); case DataLocation::Memory: - return make_shared<TupleType>(); + return TypeResult(make_shared<TupleType>()); case DataLocation::Storage: return m_isPointer ? TypePointer() : make_shared<TupleType>(); } @@ -1605,7 +1611,7 @@ string ReferenceType::identifierLocationSuffix() const return id; } -bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const +BoolResult ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -1645,7 +1651,7 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const } } -bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const +BoolResult ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const { if (isImplicitlyConvertibleTo(_convertTo)) return true; @@ -1815,23 +1821,23 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const MemberList::MemberMap members; if (!isString()) { - members.push_back({"length", make_shared<IntegerType>(256)}); + members.emplace_back("length", make_shared<IntegerType>(256)); if (isDynamicallySized() && location() == DataLocation::Storage) { - members.push_back({"push", make_shared<FunctionType>( + members.emplace_back("push", make_shared<FunctionType>( TypePointers{baseType()}, TypePointers{make_shared<IntegerType>(256)}, strings{string()}, strings{string()}, isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush - )}); - members.push_back({"pop", make_shared<FunctionType>( + )); + members.emplace_back("pop", make_shared<FunctionType>( TypePointers{}, TypePointers{}, strings{string()}, strings{string()}, FunctionType::Kind::ArrayPop - )}); + )); } } return members; @@ -1960,21 +1966,17 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con break; } if (!functionWithEqualArgumentsFound) - members.push_back(MemberList::Member( - function->name(), - functionType, - function - )); + members.emplace_back(function->name(), functionType, function); } } else if (!m_contract.isLibrary()) { for (auto const& it: m_contract.interfaceFunctions()) - members.push_back(MemberList::Member( + members.emplace_back( it.second->declaration().name(), it.second->asCallableFunction(m_contract.isLibrary()), &it.second->declaration() - )); + ); } return members; } @@ -2002,11 +2004,11 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar vector<tuple<VariableDeclaration const*, u256, unsigned>> variablesAndOffsets; for (size_t index = 0; index < variables.size(); ++index) if (auto const* offset = offsets.offset(index)) - variablesAndOffsets.push_back(make_tuple(variables[index], offset->first, offset->second)); + variablesAndOffsets.emplace_back(variables[index], offset->first, offset->second); return variablesAndOffsets; } -bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const +BoolResult StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -2092,10 +2094,10 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const // Skip all mapping members if we are not in storage. if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) continue; - members.push_back(MemberList::Member( + members.emplace_back( variable->name(), copyForLocationIfReference(type), - variable.get()) + variable.get() ); } return members; @@ -2249,7 +2251,7 @@ bool StructType::recursive() const return *m_recursive; } -TypePointer EnumType::unaryOperatorResult(Token _operator) const +TypeResult EnumType::unaryOperatorResult(Token _operator) const { return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); } @@ -2291,7 +2293,7 @@ size_t EnumType::numberOfMembers() const return m_enum.members().size(); }; -bool EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo == *this || _convertTo.category() == Category::Integer; } @@ -2308,7 +2310,7 @@ unsigned EnumType::memberValue(ASTString const& _member) const solAssert(false, "Requested unknown enum value " + _member); } -bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const +BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const { if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) { @@ -2432,7 +2434,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get())) { m_parameterTypes.push_back(mappingType->keyType()); - m_parameterNames.push_back(""); + m_parameterNames.emplace_back(""); returnType = mappingType->valueType(); } else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType.get())) @@ -2441,7 +2443,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): // Return byte arrays as whole. break; returnType = arrayType->baseType(); - m_parameterNames.push_back(""); + m_parameterNames.emplace_back(""); m_parameterTypes.push_back(make_shared<IntegerType>(256)); } else @@ -2472,7 +2474,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): DataLocation::Memory, returnType )); - m_returnParameterNames.push_back(""); + m_returnParameterNames.emplace_back(""); } } @@ -2648,14 +2650,14 @@ bool FunctionType::operator==(Type const& _other) const return true; } -bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (m_kind == Kind::External && _convertTo == AddressType::address()) return true; return _convertTo.category() == category(); } -bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -2680,14 +2682,14 @@ bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const return true; } -TypePointer FunctionType::unaryOperatorResult(Token _operator) const +TypeResult FunctionType::unaryOperatorResult(Token _operator) const { if (_operator == Token::Delete) - return make_shared<TupleType>(); + return TypeResult(make_shared<TupleType>()); return TypePointer(); } -TypePointer FunctionType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FunctionType::binaryOperatorResult(Token _operator, TypePointer const& _other) const { if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) return TypePointer(); @@ -2841,14 +2843,11 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { MemberList::MemberMap members; if (m_kind == Kind::External) - members.push_back(MemberList::Member( - "selector", - make_shared<FixedBytesType>(4) - )); + members.emplace_back("selector", make_shared<FixedBytesType>(4)); if (m_kind != Kind::BareDelegateCall) { if (isPayable()) - members.push_back(MemberList::Member( + members.emplace_back( "value", make_shared<FunctionType>( parseElementaryTypeVector({"uint"}), @@ -2862,10 +2861,10 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con m_gasSet, m_valueSet ) - )); + ); } if (m_kind != Kind::Creation) - members.push_back(MemberList::Member( + members.emplace_back( "gas", make_shared<FunctionType>( parseElementaryTypeVector({"uint"}), @@ -2879,7 +2878,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con m_gasSet, m_valueSet ) - )); + ); return members; } default: @@ -3207,24 +3206,24 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current if (contract.isLibrary()) for (FunctionDefinition const* function: contract.definedFunctions()) if (function->isVisibleAsLibraryMember()) - members.push_back(MemberList::Member( + members.emplace_back( function->name(), FunctionType(*function).asCallableFunction(true), function - )); + ); if (isBase) { // We are accessing the type of a base contract, so add all public and protected // members. Note that this does not add inherited functions on purpose. for (Declaration const* decl: contract.inheritableMembers()) - members.push_back(MemberList::Member(decl->name(), decl->type(), decl)); + members.emplace_back(decl->name(), decl->type(), decl); } else { for (auto const& stru: contract.definedStructs()) - members.push_back(MemberList::Member(stru->name(), stru->type(), stru)); + members.emplace_back(stru->name(), stru->type(), stru); for (auto const& enu: contract.definedEnums()) - members.push_back(MemberList::Member(enu->name(), enu->type(), enu)); + members.emplace_back(enu->name(), enu->type(), enu); } } else if (m_actualType->category() == Category::Enum) @@ -3232,7 +3231,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current EnumDefinition const& enumDef = dynamic_cast<EnumType const&>(*m_actualType).enumDefinition(); auto enumType = make_shared<EnumType>(enumDef); for (ASTPointer<EnumValue> const& enumValue: enumDef.members()) - members.push_back(MemberList::Member(enumValue->name(), enumType)); + members.emplace_back(enumValue->name(), enumType); } return members; } @@ -3297,7 +3296,7 @@ MemberList::MemberMap ModuleType::nativeMembers(ContractDefinition const*) const MemberList::MemberMap symbols; for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols) for (Declaration const* symbol: symbolName.second) - symbols.push_back(MemberList::Member(symbolName.first, symbol->type(), symbol)); + symbols.emplace_back(symbolName.first, symbol->type(), symbol); return symbols; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 0f0548d3..bee00661 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -22,22 +22,23 @@ #pragma once -#include <liblangutil/Exceptions.h> -#include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/ASTEnums.h> +#include <libsolidity/ast/ASTForward.h> #include <libsolidity/parsing/Token.h> +#include <liblangutil/Exceptions.h> #include <libdevcore/Common.h> #include <libdevcore/CommonIO.h> +#include <libdevcore/Result.h> +#include <boost/optional.hpp> #include <boost/noncopyable.hpp> #include <boost/rational.hpp> -#include <boost/optional.hpp> -#include <memory> -#include <string> #include <map> +#include <memory> #include <set> +#include <string> namespace dev { @@ -50,6 +51,8 @@ using TypePointer = std::shared_ptr<Type const>; using FunctionTypePointer = std::shared_ptr<FunctionType const>; using TypePointers = std::vector<TypePointer>; using rational = boost::rational<dev::bigint>; +using TypeResult = Result<TypePointer>; +using BoolResult = Result<bool>; inline rational makeRational(bigint const& _numerator, bigint const& _denominator) { @@ -63,6 +66,7 @@ inline rational makeRational(bigint const& _numerator, bigint const& _denominato enum class DataLocation { Storage, CallData, Memory }; + /** * Helper class to compute storage offsets of members of structs and contracts. */ @@ -189,19 +193,19 @@ public: /// @returns an escaped identifier (will not contain any parenthesis or commas) static std::string escapeIdentifier(std::string const& _identifier); - virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const + virtual BoolResult isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } + virtual BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const { return isImplicitlyConvertibleTo(_convertTo); } /// @returns the resulting type of applying the given unary operator or an empty pointer if /// this is not possible. /// The default implementation does not allow any unary operator. - virtual TypePointer unaryOperatorResult(Token) const { return TypePointer(); } + virtual TypeResult unaryOperatorResult(Token) const { return TypePointer(); } /// @returns the resulting type of applying the given binary operator or an empty pointer if /// this is not possible. /// The default implementation allows comparison operators if a common type exists - virtual TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const + virtual TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const { return TokenTraits::isCompareOp(_operator) ? commonType(shared_from_this(), _other) : TypePointer(); } @@ -336,10 +340,10 @@ public: explicit AddressType(StateMutability _stateMutability); std::string richIdentifier() const override; - bool isImplicitlyConvertibleTo(Type const& _other) const override; - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; bool operator==(Type const& _other) const override; @@ -381,10 +385,10 @@ public: explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned); std::string richIdentifier() const override; - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; bool operator==(Type const& _other) const override; @@ -423,10 +427,10 @@ public: explicit FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, Modifier _modifier = Modifier::Unsigned); std::string richIdentifier() const override; - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; bool operator==(Type const& _other) const override; @@ -476,11 +480,10 @@ public: explicit RationalNumberType(rational const& _value, TypePointer const& _compatibleBytesType = TypePointer()): m_value(_value), m_compatibleBytesType(_compatibleBytesType) {} - - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; @@ -536,8 +539,8 @@ public: explicit StringLiteralType(Literal const& _literal); - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer binaryOperatorResult(Token, TypePointer const&) const override + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } @@ -570,12 +573,12 @@ public: explicit FixedBytesType(unsigned _bytes); - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } unsigned storageBytes() const override { return m_bytes; } @@ -598,11 +601,10 @@ private: class BoolType: public Type { public: - BoolType() {} Category category() const override { return Category::Bool; } std::string richIdentifier() const override { return "t_bool"; } - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override; unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; } unsigned storageBytes() const override { return 1; } @@ -624,8 +626,8 @@ public: explicit ReferenceType(DataLocation _location): m_location(_location) {} DataLocation location() const { return m_location; } - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token, TypePointer const&) const override + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } @@ -702,8 +704,8 @@ public: m_length(_length) {} - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; std::string richIdentifier() const override; bool operator==(const Type& _other) const override; unsigned calldataEncodedSize(bool _padded) const override; @@ -757,10 +759,10 @@ public: explicit ContractType(ContractDefinition const& _contract, bool _super = false): m_contract(_contract), m_super(_super) {} /// Contracts can be implicitly converted only to base contracts. - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; /// Contracts can only be explicitly converted to address types and base contracts. - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer unaryOperatorResult(Token _operator) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; unsigned calldataEncodedSize(bool _padded ) const override @@ -821,7 +823,7 @@ public: Category category() const override { return Category::Struct; } explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage): ReferenceType(_location), m_struct(_struct) {} - bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; + BoolResult isImplicitlyConvertibleTo(const Type& _convertTo) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; unsigned calldataEncodedSize(bool _padded) const override; @@ -876,7 +878,7 @@ class EnumType: public Type public: Category category() const override { return Category::Enum; } explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {} - TypePointer unaryOperatorResult(Token _operator) const override; + TypeResult unaryOperatorResult(Token _operator) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; unsigned calldataEncodedSize(bool _padded) const override @@ -889,7 +891,7 @@ public: std::string canonicalName() const override; bool isValueType() const override { return true; } - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; TypePointer encodingType() const override { return std::make_shared<IntegerType>(8 * int(storageBytes())); @@ -917,10 +919,10 @@ class TupleType: public Type public: Category category() const override { return Category::Tuple; } explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {} - bool isImplicitlyConvertibleTo(Type const& _other) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; std::string richIdentifier() const override; bool operator==(Type const& _other) const override; - TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } std::string toString(bool) const override; bool canBeStored() const override { return false; } u256 storageSize() const override; @@ -1065,10 +1067,10 @@ public: std::string richIdentifier() const override; bool operator==(Type const& _other) const override; - bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - TypePointer unaryOperatorResult(Token _operator) const override; - TypePointer binaryOperatorResult(Token, TypePointer const&) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token, TypePointer const&) const override; std::string canonicalName() const override; std::string toString(bool _short) const override; unsigned calldataEncodedSize(bool _padded) const override; @@ -1197,7 +1199,7 @@ public: std::string toString(bool _short) const override; std::string canonicalName() const override; bool canLiveOutsideStorage() const override { return false; } - TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } TypePointer encodingType() const override { return std::make_shared<IntegerType>(256); @@ -1230,7 +1232,7 @@ public: explicit TypeType(TypePointer const& _actualType): m_actualType(_actualType) {} TypePointer const& actualType() const { return m_actualType; } - TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } std::string richIdentifier() const override; bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } @@ -1255,7 +1257,7 @@ public: Category category() const override { return Category::Modifier; } explicit ModifierType(ModifierDefinition const& _modifier); - TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } bool canBeStored() const override { return false; } u256 storageSize() const override; bool canLiveOutsideStorage() const override { return false; } @@ -1281,7 +1283,7 @@ public: explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {} - TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } std::string richIdentifier() const override; bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } @@ -1308,7 +1310,7 @@ public: explicit MagicType(Kind _kind): m_kind(_kind) {} - TypePointer binaryOperatorResult(Token, TypePointer const&) const override + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } @@ -1339,9 +1341,9 @@ public: Category category() const override { return Category::InaccessibleDynamic; } std::string richIdentifier() const override { return "t_inaccessible"; } - bool isImplicitlyConvertibleTo(Type const&) const override { return false; } - bool isExplicitlyConvertibleTo(Type const&) const override { return false; } - TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } + BoolResult isImplicitlyConvertibleTo(Type const&) const override { return false; } + BoolResult isExplicitlyConvertibleTo(Type const&) const override { return false; } + TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } bool canBeStored() const override { return false; } bool canLiveOutsideStorage() const override { return false; } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index b02623de..c1ab03e3 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -24,7 +24,6 @@ #include <libsolidity/ast/AST.h> #include <libsolidity/codegen/CompilerUtils.h> - #include <libdevcore/Whiskers.h> #include <boost/algorithm/string/join.hpp> @@ -141,8 +140,8 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) vector<string> valueNamesLocal; for (size_t j = 0; j < sizeOnStack; j++) { - valueNamesLocal.push_back("value" + to_string(stackPos)); - valueReturnParams.push_back("value" + to_string(stackPos)); + valueNamesLocal.emplace_back("value" + to_string(stackPos)); + valueReturnParams.emplace_back("value" + to_string(stackPos)); stackPos++; } bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index d2132258..1e0cf7fa 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -22,14 +22,13 @@ #pragma once -#include <liblangutil/EVMVersion.h> - #include <libsolidity/ast/ASTForward.h> +#include <liblangutil/EVMVersion.h> -#include <vector> #include <functional> -#include <set> #include <map> +#include <set> +#include <vector> namespace dev { namespace solidity { diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 4878f9f3..f9678f7c 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -21,13 +21,15 @@ */ #include <libsolidity/codegen/ArrayUtils.h> -#include <libevmasm/Instruction.h> + +#include <libsolidity/ast/Types.h> #include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerUtils.h> -#include <libsolidity/ast/Types.h> -#include <liblangutil/Exceptions.h> #include <libsolidity/codegen/LValue.h> +#include <libevmasm/Instruction.h> +#include <liblangutil/Exceptions.h> + using namespace std; using namespace dev; using namespace langutil; diff --git a/libsolidity/codegen/AsmCodeGen.cpp b/libsolidity/codegen/AsmCodeGen.cpp new file mode 100644 index 00000000..c04c1c34 --- /dev/null +++ b/libsolidity/codegen/AsmCodeGen.cpp @@ -0,0 +1,197 @@ +/* + 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/>. +*/ +/** + * Adaptor between the abstract assembly and eth assembly. + */ + +#include <libsolidity/codegen/AsmCodeGen.h> + +#include <libyul/AsmData.h> +#include <libyul/AsmAnalysisInfo.h> + +#include <libyul/backends/evm/AbstractAssembly.h> +#include <libyul/backends/evm/EVMCodeTransform.h> + +#include <libevmasm/Assembly.h> +#include <libevmasm/AssemblyItem.h> +#include <libevmasm/Instruction.h> + +#include <liblangutil/SourceLocation.h> + +#include <libdevcore/FixedHash.h> + +#include <memory> +#include <functional> + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace yul; +using namespace dev::solidity; + +EthAssemblyAdapter::EthAssemblyAdapter(eth::Assembly& _assembly): + m_assembly(_assembly) +{ +} + +void EthAssemblyAdapter::setSourceLocation(SourceLocation const& _location) +{ + m_assembly.setSourceLocation(_location); +} + +int EthAssemblyAdapter::stackHeight() const +{ + return m_assembly.deposit(); +} + +void EthAssemblyAdapter::appendInstruction(solidity::Instruction _instruction) +{ + m_assembly.append(_instruction); +} + +void EthAssemblyAdapter::appendConstant(u256 const& _constant) +{ + m_assembly.append(_constant); +} + +void EthAssemblyAdapter::appendLabel(LabelID _labelId) +{ + m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId)); +} + +void EthAssemblyAdapter::appendLabelReference(LabelID _labelId) +{ + m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId)); +} + +size_t EthAssemblyAdapter::newLabelId() +{ + return assemblyTagToIdentifier(m_assembly.newTag()); +} + +size_t EthAssemblyAdapter::namedLabel(std::string const& _name) +{ + return assemblyTagToIdentifier(m_assembly.namedTag(_name)); +} + +void EthAssemblyAdapter::appendLinkerSymbol(std::string const& _linkerSymbol) +{ + m_assembly.appendLibraryAddress(_linkerSymbol); +} + +void EthAssemblyAdapter::appendJump(int _stackDiffAfter) +{ + appendInstruction(solidity::Instruction::JUMP); + m_assembly.adjustDeposit(_stackDiffAfter); +} + +void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter) +{ + appendLabelReference(_labelId); + appendJump(_stackDiffAfter); +} + +void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId) +{ + appendLabelReference(_labelId); + appendInstruction(solidity::Instruction::JUMPI); +} + +void EthAssemblyAdapter::appendBeginsub(LabelID, int) +{ + // TODO we could emulate that, though + solAssert(false, "BEGINSUB not implemented for EVM 1.0"); +} + +void EthAssemblyAdapter::appendJumpsub(LabelID, int, int) +{ + // TODO we could emulate that, though + solAssert(false, "JUMPSUB not implemented for EVM 1.0"); +} + +void EthAssemblyAdapter::appendReturnsub(int, int) +{ + // TODO we could emulate that, though + solAssert(false, "RETURNSUB not implemented for EVM 1.0"); +} + +void EthAssemblyAdapter::appendAssemblySize() +{ + m_assembly.appendProgramSize(); +} + +pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly() +{ + shared_ptr<eth::Assembly> assembly{make_shared<eth::Assembly>()}; + auto sub = m_assembly.newSub(assembly); + return {make_shared<EthAssemblyAdapter>(*assembly), size_t(sub.data())}; +} + +void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub) +{ + auto it = m_dataHashBySubId.find(_sub); + if (it == m_dataHashBySubId.end()) + m_assembly.pushSubroutineOffset(size_t(_sub)); + else + m_assembly << eth::AssemblyItem(eth::PushData, it->second); +} + +void EthAssemblyAdapter::appendDataSize(AbstractAssembly::SubID _sub) +{ + auto it = m_dataHashBySubId.find(_sub); + if (it == m_dataHashBySubId.end()) + m_assembly.pushSubroutineSize(size_t(_sub)); + else + m_assembly << u256(m_assembly.data(h256(it->second)).size()); +} + +AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data) +{ + eth::AssemblyItem pushData = m_assembly.newData(_data); + SubID subID = m_nextDataCounter++; + m_dataHashBySubId[subID] = pushData.data(); + return subID; +} + +EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(eth::AssemblyItem const& _tag) +{ + u256 id = _tag.data(); + solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large."); + return LabelID(id); +} + +void CodeGenerator::assemble( + Block const& _parsedData, + AsmAnalysisInfo& _analysisInfo, + eth::Assembly& _assembly, + ExternalIdentifierAccess const& _identifierAccess, + bool _useNamedLabelsForFunctions, + bool _optimize +) +{ + EthAssemblyAdapter assemblyAdapter(_assembly); + CodeTransform( + assemblyAdapter, + _analysisInfo, + _parsedData, + *EVMDialect::strictAssemblyForEVM(), + _optimize, + false, + _identifierAccess, + _useNamedLabelsForFunctions + )(_parsedData); +} diff --git a/libsolidity/codegen/AsmCodeGen.h b/libsolidity/codegen/AsmCodeGen.h new file mode 100644 index 00000000..516b0a36 --- /dev/null +++ b/libsolidity/codegen/AsmCodeGen.h @@ -0,0 +1,92 @@ +/* + 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/>. +*/ +/** + * Adaptor between the abstract assembly and eth assembly. + */ + +#pragma once + +#include <libyul/AsmAnalysis.h> +#include <libyul/backends/evm/AbstractAssembly.h> +#include <liblangutil/SourceLocation.h> +#include <functional> + +namespace yul +{ +struct Block; +} + +namespace dev +{ +namespace eth +{ +class Assembly; +class AssemblyItem; +} + +namespace solidity +{ + +class EthAssemblyAdapter: public yul::AbstractAssembly +{ +public: + explicit EthAssemblyAdapter(eth::Assembly& _assembly); + void setSourceLocation(langutil::SourceLocation const& _location) override; + int stackHeight() const override; + void appendInstruction(solidity::Instruction _instruction) override; + void appendConstant(u256 const& _constant) override; + void appendLabel(LabelID _labelId) override; + void appendLabelReference(LabelID _labelId) override; + size_t newLabelId() override; + size_t namedLabel(std::string const& _name) override; + void appendLinkerSymbol(std::string const& _linkerSymbol) override; + void appendJump(int _stackDiffAfter) override; + void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override; + void appendJumpToIf(LabelID _labelId) override; + void appendBeginsub(LabelID, int) override; + void appendJumpsub(LabelID, int, int) override; + void appendReturnsub(int, int) override; + void appendAssemblySize() override; + std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly() override; + void appendDataOffset(SubID _sub) override; + void appendDataSize(SubID _sub) override; + SubID appendData(dev::bytes const& _data) override; + +private: + static LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag); + + eth::Assembly& m_assembly; + std::map<SubID, dev::u256> m_dataHashBySubId; + size_t m_nextDataCounter = std::numeric_limits<size_t>::max() / 2; +}; + +class CodeGenerator +{ +public: + /// Performs code generation and appends generated to _assembly. + static void assemble( + yul::Block const& _parsedData, + yul::AsmAnalysisInfo& _analysisInfo, + dev::eth::Assembly& _assembly, + yul::ExternalIdentifierAccess const& _identifierAccess = yul::ExternalIdentifierAccess(), + bool _useNamedLabelsForFunctions = false, + bool _optimize = false + ); +}; + +} +} diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 55f1d252..a22e6e9d 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -21,8 +21,9 @@ */ #include <libsolidity/codegen/Compiler.h> -#include <libevmasm/Assembly.h> + #include <libsolidity/codegen/ContractCompiler.h> +#include <libevmasm/Assembly.h> using namespace std; using namespace dev; @@ -34,13 +35,13 @@ void Compiler::compileContract( bytes const& _metadata ) { - ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns); runtimeCompiler.compileContract(_contract, _contracts); m_runtimeContext.appendAuxiliaryData(_metadata); // This might modify m_runtimeContext because it can access runtime functions at // creation time. - ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); + ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1); m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); m_context.optimise(m_optimize, m_optimizeRuns); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 48d9e9d6..784d7f8c 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -24,11 +24,9 @@ #include <libsolidity/codegen/CompilerContext.h> #include <liblangutil/EVMVersion.h> - #include <libevmasm/Assembly.h> - -#include <ostream> #include <functional> +#include <ostream> namespace dev { namespace solidity { diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 5a3a233c..be681b2e 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -21,18 +21,22 @@ */ #include <libsolidity/codegen/CompilerContext.h> -#include <libsolidity/codegen/CompilerUtils.h> + #include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/AsmCodeGen.h> #include <libsolidity/codegen/Compiler.h> +#include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/interface/Version.h> -#include <liblangutil/SourceReferenceFormatter.h> + #include <libyul/AsmParser.h> -#include <libyul/AsmCodeGen.h> #include <libyul/AsmAnalysis.h> #include <libyul/AsmAnalysisInfo.h> +#include <libyul/backends/evm/EVMDialect.h> #include <libyul/YulString.h> + #include <liblangutil/ErrorReporter.h> #include <liblangutil/Scanner.h> +#include <liblangutil/SourceReferenceFormatter.h> #include <boost/algorithm/string/replace.hpp> @@ -361,7 +365,7 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--")); - auto parserResult = yul::Parser(errorReporter, yul::AsmFlavour::Strict).parse(scanner, false); + auto parserResult = yul::Parser(errorReporter, yul::EVMDialect::strictAssemblyForEVM()).parse(scanner, false); #ifdef SOL_OUTPUT_ASM cout << yul::AsmPrinter()(*parserResult) << endl; #endif @@ -373,7 +377,7 @@ void CompilerContext::appendInlineAssembly( errorReporter, m_evmVersion, boost::none, - yul::AsmFlavour::Strict, + yul::EVMDialect::strictAssemblyForEVM(), identifierAccess.resolve ).analyze(*parserResult); if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) @@ -386,8 +390,7 @@ void CompilerContext::appendInlineAssembly( for (auto const& error: errorReporter.errors()) message += SourceReferenceFormatter::formatExceptionInformation( *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error", - [&](string const&) -> Scanner const& { return *scanner; } + (error->type() == Error::Type::Warning) ? "Warning" : "Error" ); message += "-------------------------------------------\n"; @@ -395,7 +398,7 @@ void CompilerContext::appendInlineAssembly( } solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); - yul::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system); + CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system); // Reset the source location to the one of the node (instead of the CODEGEN source location) updateSourceLocation(); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 02369813..dedcd95f 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -22,24 +22,21 @@ #pragma once -#include <libsolidity/codegen/ABIFunctions.h> - -#include <liblangutil/EVMVersion.h> - +#include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/Types.h> -#include <libsolidity/ast/ASTAnnotations.h> +#include <libsolidity/codegen/ABIFunctions.h> -#include <libevmasm/Instruction.h> #include <libevmasm/Assembly.h> - +#include <libevmasm/Instruction.h> +#include <liblangutil/EVMVersion.h> #include <libdevcore/Common.h> +#include <functional> #include <ostream> #include <stack> #include <queue> #include <utility> -#include <functional> namespace dev { namespace solidity { diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 7d2ad9d2..bbc703c7 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -23,12 +23,10 @@ #include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/ABIFunctions.h> #include <libsolidity/codegen/ArrayUtils.h> #include <libsolidity/codegen/LValue.h> -#include <libsolidity/codegen/ABIFunctions.h> - #include <libevmasm/Instruction.h> - #include <libdevcore/Whiskers.h> using namespace std; @@ -1205,7 +1203,7 @@ void CompilerUtils::storeStringData(bytesConstRef _data) { //@todo provide both alternatives to the optimiser // stack: mempos - if (_data.size() <= 128) + if (_data.size() <= 32) { for (unsigned i = 0; i < _data.size(); i += 32) { diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 5f7dce22..7e4f47ba 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -22,8 +22,8 @@ #pragma once -#include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/ast/ASTForward.h> +#include <libsolidity/codegen/CompilerContext.h> namespace dev { namespace solidity { diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index aabdbb79..b051d260 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -20,19 +20,18 @@ * Solidity compiler. */ +#include <libsolidity/ast/AST.h> +#include <libsolidity/codegen/AsmCodeGen.h> +#include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/codegen/ContractCompiler.h> #include <libsolidity/codegen/ExpressionCompiler.h> -#include <libsolidity/codegen/CompilerUtils.h> -#include <libsolidity/ast/AST.h> -#include <libyul/AsmCodeGen.h> -#include <liblangutil/ErrorReporter.h> #include <libevmasm/Instruction.h> #include <libevmasm/Assembly.h> #include <libevmasm/GasMeter.h> +#include <liblangutil/ErrorReporter.h> #include <boost/range/adaptor/reversed.hpp> - #include <algorithm> using namespace std; @@ -268,6 +267,89 @@ void ContractCompiler::appendDelegatecallCheck() // "We have not been called via DELEGATECALL". } +void ContractCompiler::appendInternalSelector( + map<FixedHash<4>, eth::AssemblyItem const> const& _entryPoints, + vector<FixedHash<4>> const& _ids, + eth::AssemblyItem const& _notFoundTag, + size_t _runs +) +{ + // Code for selecting from n functions without split: + // n times: dup1, push4 <id_i>, eq, push2/3 <tag_i>, jumpi + // push2/3 <notfound> jump + // (called SELECT[n]) + // Code for selecting from n functions with split: + // dup1, push4 <pivot>, gt, push2/3<tag_less>, jumpi + // SELECT[n/2] + // tag_less: + // SELECT[n/2] + // + // This means each split adds 16-18 bytes of additional code (note the additional jump out!) + // The average execution cost if we do not split at all are: + // (3 + 3 + 3 + 3 + 10) * n/2 = 24 * n/2 = 12 * n + // If we split once: + // (3 + 3 + 3 + 3 + 10) + 24 * n/4 = 24 * (n/4 + 1) = 6 * n + 24; + // + // We should split if + // _runs * 12 * n > _runs * (6 * n + 24) + 17 * createDataGas + // <=> _runs * 6 * (n - 4) > 17 * createDataGas + // + // Which also means that the execution itself is not profitable + // unless we have at least 5 functions. + + // Start with some comparisons to avoid overflow, then do the actual comparison. + bool split = false; + if (_ids.size() <= 4) + split = false; + else if (_runs > (17 * eth::GasCosts::createDataGas) / 6) + split = true; + else + split = (_runs * 6 * (_ids.size() - 4) > 17 * eth::GasCosts::createDataGas); + + if (split) + { + size_t pivotIndex = _ids.size() / 2; + FixedHash<4> pivot{_ids.at(pivotIndex)}; + m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT; + eth::AssemblyItem lessTag{m_context.appendConditionalJump()}; + // Here, we have funid >= pivot + vector<FixedHash<4>> larger{_ids.begin() + pivotIndex, _ids.end()}; + appendInternalSelector(_entryPoints, larger, _notFoundTag, _runs); + m_context << lessTag; + // Here, we have funid < pivot + vector<FixedHash<4>> smaller{_ids.begin(), _ids.begin() + pivotIndex}; + appendInternalSelector(_entryPoints, smaller, _notFoundTag, _runs); + } + else + { + for (auto const& id: _ids) + { + m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ; + m_context.appendConditionalJumpTo(_entryPoints.at(id)); + } + m_context.appendJumpTo(_notFoundTag); + } +} + +namespace +{ + +// Helper function to check if any function is payable +bool hasPayableFunctions(ContractDefinition const& _contract) +{ + FunctionDefinition const* fallback = _contract.fallbackFunction(); + if (fallback && fallback->isPayable()) + return true; + + for (auto const& it: _contract.interfaceFunctions()) + if (it.second->isPayable()) + return true; + + return false; +} + +} + void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract) { map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions(); @@ -279,6 +361,15 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac } FunctionDefinition const* fallback = _contract.fallbackFunction(); + solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions"); + + bool needToAddCallvalueCheck = true; + if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary()) + { + appendCallValueCheck(); + needToAddCallvalueCheck = false; + } + eth::AssemblyItem notFound = m_context.newTag(); // directly jump to fallback if the data is too short to contain a function selector // also guards against short data @@ -287,22 +378,26 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // retrieve the function signature hash from the calldata if (!interfaceFunctions.empty()) + { CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); - // stack now is: <can-call-non-view-functions>? <funhash> - for (auto const& it: interfaceFunctions) - { - callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag())); - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << Instruction::EQ; - m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); + // stack now is: <can-call-non-view-functions>? <funhash> + vector<FixedHash<4>> sortedIDs; + for (auto const& it: interfaceFunctions) + { + callDataUnpackerEntryPoints.emplace(it.first, m_context.newTag()); + sortedIDs.emplace_back(it.first); + } + std::sort(sortedIDs.begin(), sortedIDs.end()); + appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimise_runs); } - m_context.appendJumpTo(notFound); m_context << notFound; + if (fallback) { solAssert(!_contract.isLibrary(), ""); - if (!fallback->isPayable()) + if (!fallback->isPayable() && needToAddCallvalueCheck) appendCallValueCheck(); solAssert(fallback->isFallback(), ""); @@ -332,7 +427,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context.setStackOffset(0); // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. - if (!functionType->isPayable() && !_contract.isLibrary()) + if (!functionType->isPayable() && !_contract.isLibrary() && needToAddCallvalueCheck) appendCallValueCheck(); // Return tag is used to jump out of the function. @@ -618,7 +713,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) } }; solAssert(_inlineAssembly.annotation().analysisInfo, ""); - yul::CodeGenerator::assemble( + CodeGenerator::assemble( _inlineAssembly.operations(), *_inlineAssembly.annotation().analysisInfo, m_context.nonConstAssembly(), @@ -656,14 +751,14 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); - m_breakTags.push_back({loopEnd, m_context.stackHeight()}); + m_breakTags.emplace_back(loopEnd, m_context.stackHeight()); m_context << loopStart; if (_whileStatement.isDoWhile()) { eth::AssemblyItem condition = m_context.newTag(); - m_continueTags.push_back({condition, m_context.stackHeight()}); + m_continueTags.emplace_back(condition, m_context.stackHeight()); _whileStatement.body().accept(*this); @@ -674,7 +769,7 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) } else { - m_continueTags.push_back({loopStart, m_context.stackHeight()}); + m_continueTags.emplace_back(loopStart, m_context.stackHeight()); compileExpression(_whileStatement.condition()); m_context << Instruction::ISZERO; m_context.appendConditionalJumpTo(loopEnd); @@ -705,8 +800,8 @@ bool ContractCompiler::visit(ForStatement const& _forStatement) if (_forStatement.initializationExpression()) _forStatement.initializationExpression()->accept(*this); - m_breakTags.push_back({loopEnd, m_context.stackHeight()}); - m_continueTags.push_back({loopNext, m_context.stackHeight()}); + m_breakTags.emplace_back(loopEnd, m_context.stackHeight()); + m_continueTags.emplace_back(loopNext, m_context.stackHeight()); m_context << loopStart; // if there is no terminating condition in for, default is to always be true @@ -932,7 +1027,7 @@ void ContractCompiler::appendModifierOrFunctionCode() if (codeBlock) { - m_returnTags.push_back({m_context.newTag(), m_context.stackHeight()}); + m_returnTags.emplace_back(m_context.newTag(), m_context.stackHeight()); codeBlock->accept(*this); solAssert(!m_returnTags.empty(), ""); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 001aec7c..40871f0d 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -22,11 +22,11 @@ #pragma once -#include <ostream> -#include <functional> #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/codegen/CompilerContext.h> #include <libevmasm/Assembly.h> +#include <functional> +#include <ostream> namespace dev { namespace solidity { @@ -38,8 +38,9 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise): + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise, size_t _optimise_runs = 200): m_optimise(_optimise), + m_optimise_runs(_optimise_runs), m_runtimeCompiler(_runtimeCompiler), m_context(_context) { @@ -81,6 +82,14 @@ private: /// This is done by inserting a specific push constant as the first instruction /// whose data will be modified in memory at deploy time. void appendDelegatecallCheck(); + /// Appends the function selector. Is called recursively to create a binary search tree. + /// @a _runs the number of intended executions of the contract to tune the split point. + void appendInternalSelector( + std::map<FixedHash<4>, eth::AssemblyItem const> const& _entryPoints, + std::vector<FixedHash<4>> const& _ids, + eth::AssemblyItem const& _notFoundTag, + size_t _runs + ); void appendFunctionSelector(ContractDefinition const& _contract); void appendCallValueCheck(); void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); @@ -122,6 +131,7 @@ private: void storeStackHeight(ASTNode const* _node); bool const m_optimise; + size_t const m_optimise_runs = 200; /// Pointer to the runtime compiler in case this is a creation compiler. ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 121585d9..be2709ae 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -20,21 +20,23 @@ * Solidity AST to EVM bytecode compiler for expressions. */ -#include <utility> -#include <numeric> -#include <boost/range/adaptor/reversed.hpp> -#include <boost/algorithm/string/replace.hpp> -#include <libdevcore/Common.h> -#include <libdevcore/Keccak256.h> -#include <libsolidity/ast/AST.h> #include <libsolidity/codegen/ExpressionCompiler.h> + +#include <libsolidity/ast/AST.h> #include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/codegen/LValue.h> -#include <libevmasm/GasMeter.h> +#include <libevmasm/GasMeter.h> +#include <libdevcore/Common.h> +#include <libdevcore/Keccak256.h> #include <libdevcore/Whiskers.h> +#include <boost/algorithm/string/replace.hpp> +#include <boost/range/adaptor/reversed.hpp> +#include <numeric> +#include <utility> + using namespace std; using namespace langutil; @@ -833,10 +835,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::RIPEMD160: { _functionCall.expression().accept(*this); - static const map<FunctionType::Kind, u256> contractAddresses{{FunctionType::Kind::ECRecover, 1}, - {FunctionType::Kind::SHA256, 2}, - {FunctionType::Kind::RIPEMD160, 3}}; - m_context << contractAddresses.find(function.kind())->second; + static map<FunctionType::Kind, u256> const contractAddresses{ + {FunctionType::Kind::ECRecover, 1}, + {FunctionType::Kind::SHA256, 2}, + {FunctionType::Kind::RIPEMD160, 3} + }; + m_context << contractAddresses.at(function.kind()); for (unsigned i = function.sizeOnStack(); i > 0; --i) m_context << swapInstruction(i); appendExternalFunctionCall(function, arguments); diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 2bfaab43..4a6e43ff 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -21,14 +21,17 @@ * Solidity AST to EVM bytecode compiler for expressions. */ -#include <functional> -#include <memory> -#include <boost/noncopyable.hpp> -#include <libdevcore/Common.h> -#include <liblangutil/SourceLocation.h> +#pragma once + #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/codegen/LValue.h> #include <liblangutil/Exceptions.h> +#include <liblangutil/SourceLocation.h> +#include <libdevcore/Common.h> + +#include <boost/noncopyable.hpp> +#include <functional> +#include <memory> namespace dev { namespace eth diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 6d71d36f..70dbee81 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -21,10 +21,11 @@ */ #include <libsolidity/codegen/LValue.h> -#include <libevmasm/Instruction.h> -#include <libsolidity/ast/Types.h> + #include <libsolidity/ast/AST.h> +#include <libsolidity/ast/Types.h> #include <libsolidity/codegen/CompilerUtils.h> +#include <libevmasm/Instruction.h> using namespace std; using namespace dev; diff --git a/libsolidity/codegen/LValue.h b/libsolidity/codegen/LValue.h index d854857b..3072ff11 100644 --- a/libsolidity/codegen/LValue.h +++ b/libsolidity/codegen/LValue.h @@ -22,10 +22,10 @@ #pragma once +#include <libsolidity/codegen/ArrayUtils.h> +#include <liblangutil/SourceLocation.h> #include <memory> #include <vector> -#include <liblangutil/SourceLocation.h> -#include <libsolidity/codegen/ArrayUtils.h> namespace dev { @@ -49,7 +49,7 @@ protected: m_context(_compilerContext), m_dataType(_dataType) {} public: - virtual ~LValue() {} + virtual ~LValue() = default; /// @returns the number of stack slots occupied by the lvalue reference virtual unsigned sizeOnStack() const { return 1; } /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index de5e4430..e7c8f015 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -18,7 +18,6 @@ #include <libsolidity/formal/CVC4Interface.h> #include <liblangutil/Exceptions.h> - #include <libdevcore/CommonIO.h> using namespace std; diff --git a/libsolidity/formal/CVC4Interface.h b/libsolidity/formal/CVC4Interface.h index bbe23855..89792364 100644 --- a/libsolidity/formal/CVC4Interface.h +++ b/libsolidity/formal/CVC4Interface.h @@ -18,7 +18,6 @@ #pragma once #include <libsolidity/formal/SolverInterface.h> - #include <boost/noncopyable.hpp> #if defined(__GLIBC__) diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index ebb09f0a..35c1e2f1 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -18,11 +18,11 @@ #include <libsolidity/formal/SMTChecker.h> #include <libsolidity/formal/SMTPortfolio.h> - #include <libsolidity/formal/VariableUsage.h> #include <libsolidity/formal/SymbolicTypes.h> #include <liblangutil/ErrorReporter.h> +#include <libdevcore/StringUtils.h> #include <boost/range/adaptor/map.hpp> #include <boost/algorithm/string/replace.hpp> @@ -58,8 +58,7 @@ void SMTChecker::analyze(SourceUnit const& _source, shared_ptr<Scanner> const& _ bool SMTChecker::visit(ContractDefinition const& _contract) { for (auto _var : _contract.stateVariables()) - if (_var->type()->isValueType()) - createVariable(*_var); + createVariable(*_var); return true; } @@ -88,14 +87,14 @@ bool SMTChecker::visit(FunctionDefinition const& _function) m_interface->reset(); m_pathConditions.clear(); m_expressions.clear(); - m_specialVariables.clear(); - m_uninterpretedFunctions.clear(); + m_globalContext.clear(); m_uninterpretedTerms.clear(); resetStateVariables(); initializeLocalVariables(_function); + m_loopExecutionHappened = false; + m_arrayAssignmentHappened = false; } - m_loopExecutionHappened = false; return true; } @@ -136,13 +135,24 @@ bool SMTChecker::visit(IfStatement const& _node) return false; } +// Here we consider the execution of two branches: +// Branch 1 assumes the loop condition to be true and executes the loop once, +// after resetting touched variables. +// Branch 2 assumes the loop condition to be false and skips the loop after +// visiting the condition (it might contain side-effects, they need to be considered) +// and does not erase knowledge. +// If the loop is a do-while, condition side-effects are lost since the body, +// executed once before the condition, might reassign variables. +// Variables touched by the loop are merged with Branch 2. bool SMTChecker::visit(WhileStatement const& _node) { + auto indicesBeforeLoop = copyVariableIndices(); auto touchedVariables = m_variableUsage->touchedVariables(_node); resetVariables(touchedVariables); + decltype(indicesBeforeLoop) indicesAfterLoop; if (_node.isDoWhile()) { - visitBranch(_node.body()); + indicesAfterLoop = visitBranch(_node.body()); // TODO the assertions generated in the body should still be active in the condition _node.condition().accept(*this); if (isRootFunction()) @@ -154,19 +164,31 @@ bool SMTChecker::visit(WhileStatement const& _node) if (isRootFunction()) checkBooleanNotConstant(_node.condition(), "While loop condition is always $VALUE."); - visitBranch(_node.body(), expr(_node.condition())); + indicesAfterLoop = visitBranch(_node.body(), expr(_node.condition())); } - m_loopExecutionHappened = true; - resetVariables(touchedVariables); + // We reset the execution to before the loop + // and visit the condition in case it's not a do-while. + // A do-while's body might have non-precise information + // in its first run about variables that are touched. + resetVariableIndices(indicesBeforeLoop); + if (!_node.isDoWhile()) + _node.condition().accept(*this); + + mergeVariables(touchedVariables, expr(_node.condition()), indicesAfterLoop, copyVariableIndices()); + + m_loopExecutionHappened = true; return false; } +// Here we consider the execution of two branches similar to WhileStatement. bool SMTChecker::visit(ForStatement const& _node) { if (_node.initializationExpression()) _node.initializationExpression()->accept(*this); + auto indicesBeforeLoop = copyVariableIndices(); + // Do not reset the init expression part. auto touchedVariables = m_variableUsage->touchedVariables(_node.body()); @@ -193,13 +215,19 @@ bool SMTChecker::visit(ForStatement const& _node) _node.body().accept(*this); if (_node.loopExpression()) _node.loopExpression()->accept(*this); - m_interface->pop(); - m_loopExecutionHappened = true; + auto indicesAfterLoop = copyVariableIndices(); + // We reset the execution to before the loop + // and visit the condition. + resetVariableIndices(indicesBeforeLoop); + if (_node.condition()) + _node.condition()->accept(*this); - resetVariables(touchedVariables); + auto forCondition = _node.condition() ? expr(*_node.condition()) : smt::Expression(true); + mergeVariables(touchedVariables, forCondition, indicesAfterLoop, copyVariableIndices()); + m_loopExecutionHappened = true; return false; } @@ -237,16 +265,14 @@ void SMTChecker::endVisit(Assignment const& _assignment) else if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_assignment.leftHandSide())) { VariableDeclaration const& decl = dynamic_cast<VariableDeclaration const&>(*identifier->annotation().referencedDeclaration); - if (knownVariable(decl)) - { - assignment(decl, _assignment.rightHandSide(), _assignment.location()); - defineExpr(_assignment, expr(_assignment.rightHandSide())); - } - else - m_errorReporter.warning( - _assignment.location(), - "Assertion checker does not yet implement such assignments." - ); + solAssert(knownVariable(decl), ""); + assignment(decl, _assignment.rightHandSide(), _assignment.location()); + defineExpr(_assignment, expr(_assignment.rightHandSide())); + } + else if (dynamic_cast<IndexAccess const*>(&_assignment.leftHandSide())) + { + arrayIndexAssignment(_assignment); + defineExpr(_assignment, expr(_assignment.rightHandSide())); } else m_errorReporter.warning( @@ -257,7 +283,11 @@ void SMTChecker::endVisit(Assignment const& _assignment) void SMTChecker::endVisit(TupleExpression const& _tuple) { - if (_tuple.isInlineArray() || _tuple.components().size() != 1) + if ( + _tuple.isInlineArray() || + _tuple.components().size() != 1 || + !isSupportedType(_tuple.components()[0]->annotation().type->category()) + ) m_errorReporter.warning( _tuple.location(), "Assertion checker does not yet implement tuples and inline arrays." @@ -271,14 +301,14 @@ void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _ checkCondition( _value < minValue(_type), _location, - "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")", + "Underflow (resulting value less than " + formatNumberReadable(_type.minValue()) + ")", "<result>", &_value ); checkCondition( _value > maxValue(_type), _location, - "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")", + "Overflow (resulting value larger than " + formatNumberReadable(_type.maxValue()) + ")", "<result>", &_value ); @@ -368,18 +398,30 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); std::vector<ASTPointer<Expression const>> const args = _funCall.arguments(); - if (funType.kind() == FunctionType::Kind::Assert) + switch (funType.kind()) + { + case FunctionType::Kind::Assert: visitAssert(_funCall); - else if (funType.kind() == FunctionType::Kind::Require) + break; + case FunctionType::Kind::Require: visitRequire(_funCall); - else if (funType.kind() == FunctionType::Kind::GasLeft) + break; + case FunctionType::Kind::GasLeft: visitGasLeft(_funCall); - else if (funType.kind() == FunctionType::Kind::BlockHash) - visitBlockHash(_funCall); - else if (funType.kind() == FunctionType::Kind::Internal) + break; + case FunctionType::Kind::Internal: inlineFunctionCall(_funCall); - else - { + break; + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + abstractFunctionCall(_funCall); + break; + default: m_errorReporter.warning( _funCall.location(), "Assertion checker does not yet implement this type of function call." @@ -411,8 +453,8 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) string gasLeft = "gasleft()"; // We increase the variable index since gasleft changes // inside a tx. - defineSpecialVariable(gasLeft, _funCall, true); - auto const& symbolicVar = m_specialVariables.at(gasLeft); + defineGlobalVariable(gasLeft, _funCall, true); + auto const& symbolicVar = m_globalContext.at(gasLeft); unsigned index = symbolicVar->index(); // We set the current value to unknown anyway to add type constraints. setUnknownValue(*symbolicVar); @@ -420,19 +462,11 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) m_interface->addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); } -void SMTChecker::visitBlockHash(FunctionCall const& _funCall) +void SMTChecker::eraseArrayKnowledge() { - string blockHash = "blockhash"; - auto const& arguments = _funCall.arguments(); - solAssert(arguments.size() == 1, ""); - smt::SortPointer paramSort = smtSort(*arguments.at(0)->annotation().type); - smt::SortPointer returnSort = smtSort(*_funCall.annotation().type); - defineUninterpretedFunction( - blockHash, - make_shared<smt::FunctionSort>(vector<smt::SortPointer>{paramSort}, returnSort) - ); - defineExpr(_funCall, m_uninterpretedFunctions.at(blockHash)({expr(*arguments.at(0))})); - m_uninterpretedTerms.push_back(&_funCall); + for (auto const& var: m_variables) + if (var.first->annotation().type->category() == Type::Category::Mapping) + newValue(*var.first); } void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) @@ -502,29 +536,32 @@ void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) } } +void SMTChecker::abstractFunctionCall(FunctionCall const& _funCall) +{ + vector<smt::Expression> smtArguments; + for (auto const& arg: _funCall.arguments()) + smtArguments.push_back(expr(*arg)); + defineExpr(_funCall, (*m_expressions.at(&_funCall.expression()))(smtArguments)); + m_uninterpretedTerms.insert(&_funCall); + setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, *m_interface); +} + void SMTChecker::endVisit(Identifier const& _identifier) { if (_identifier.annotation().lValueRequested) { // Will be translated as part of the node that requested the lvalue. } - else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) + else if (dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) { - if ( - fun->kind() == FunctionType::Kind::Assert || - fun->kind() == FunctionType::Kind::Require || - fun->kind() == FunctionType::Kind::GasLeft || - fun->kind() == FunctionType::Kind::BlockHash - ) - return; - createExpr(_identifier); + visitFunctionIdentifier(_identifier); } else if (isSupportedType(_identifier.annotation().type->category())) { if (VariableDeclaration const* decl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) defineExpr(_identifier, currentValue(*decl)); else if (_identifier.name() == "now") - defineSpecialVariable(_identifier.name(), _identifier); + defineGlobalVariable(_identifier.name(), _identifier); else // TODO: handle MagicVariableDeclaration here m_errorReporter.warning( @@ -534,6 +571,20 @@ void SMTChecker::endVisit(Identifier const& _identifier) } } +void SMTChecker::visitFunctionIdentifier(Identifier const& _identifier) +{ + auto const& fType = dynamic_cast<FunctionType const&>(*_identifier.annotation().type); + if (fType.returnParameterTypes().size() > 1) + { + m_errorReporter.warning( + _identifier.location(), + "Assertion checker does not yet support functions with more than one return parameter." + ); + } + defineGlobalFunction(fType.richIdentifier(), _identifier); + m_expressions.emplace(&_identifier, m_globalContext.at(fType.richIdentifier())); +} + void SMTChecker::endVisit(Literal const& _literal) { Type const& type = *_literal.annotation().type; @@ -585,7 +636,7 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) _memberAccess.location(), "Assertion checker does not yet support this expression." ); - defineSpecialVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess); + defineGlobalVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess); return false; } else @@ -597,30 +648,107 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) return true; } -void SMTChecker::defineSpecialVariable(string const& _name, Expression const& _expr, bool _increaseIndex) +void SMTChecker::endVisit(IndexAccess const& _indexAccess) { - if (!knownSpecialVariable(_name)) + shared_ptr<SymbolicVariable> array; + if (auto const& id = dynamic_cast<Identifier const*>(&_indexAccess.baseExpression())) + { + auto const& varDecl = dynamic_cast<VariableDeclaration const&>(*id->annotation().referencedDeclaration); + solAssert(knownVariable(varDecl), ""); + array = m_variables[&varDecl]; + } + else if (auto const& innerAccess = dynamic_cast<IndexAccess const*>(&_indexAccess.baseExpression())) + { + solAssert(knownExpr(*innerAccess), ""); + array = m_expressions[innerAccess]; + } + else + { + m_errorReporter.warning( + _indexAccess.location(), + "Assertion checker does not yet implement this expression." + ); + return; + } + + solAssert(array, ""); + defineExpr(_indexAccess, smt::Expression::select( + array->currentValue(), + expr(*_indexAccess.indexExpression()) + )); + setSymbolicUnknownValue( + expr(_indexAccess), + _indexAccess.annotation().type, + *m_interface + ); + m_uninterpretedTerms.insert(&_indexAccess); +} + +void SMTChecker::arrayAssignment() +{ + m_arrayAssignmentHappened = true; + eraseArrayKnowledge(); +} + +void SMTChecker::arrayIndexAssignment(Assignment const& _assignment) +{ + auto const& indexAccess = dynamic_cast<IndexAccess const&>(_assignment.leftHandSide()); + if (auto const& id = dynamic_cast<Identifier const*>(&indexAccess.baseExpression())) + { + auto const& varDecl = dynamic_cast<VariableDeclaration const&>(*id->annotation().referencedDeclaration); + solAssert(knownVariable(varDecl), ""); + smt::Expression store = smt::Expression::store( + m_variables[&varDecl]->currentValue(), + expr(*indexAccess.indexExpression()), + expr(_assignment.rightHandSide()) + ); + m_interface->addAssertion(newValue(varDecl) == store); + } + else if (dynamic_cast<IndexAccess const*>(&indexAccess.baseExpression())) + m_errorReporter.warning( + indexAccess.location(), + "Assertion checker does not yet implement assignments to multi-dimensional mappings or arrays." + ); + else + m_errorReporter.warning( + _assignment.location(), + "Assertion checker does not yet implement this expression." + ); +} + +void SMTChecker::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex) +{ + if (!knownGlobalSymbol(_name)) { auto result = newSymbolicVariable(*_expr.annotation().type, _name, *m_interface); - m_specialVariables.emplace(_name, result.second); + m_globalContext.emplace(_name, result.second); setUnknownValue(*result.second); if (result.first) m_errorReporter.warning( _expr.location(), - "Assertion checker does not yet support this special variable." + "Assertion checker does not yet support this global variable." ); } else if (_increaseIndex) - m_specialVariables.at(_name)->increaseIndex(); + m_globalContext.at(_name)->increaseIndex(); // The default behavior is not to increase the index since - // most of the special values stay the same throughout a tx. - defineExpr(_expr, m_specialVariables.at(_name)->currentValue()); + // most of the global values stay the same throughout a tx. + if (isSupportedType(_expr.annotation().type->category())) + defineExpr(_expr, m_globalContext.at(_name)->currentValue()); } -void SMTChecker::defineUninterpretedFunction(string const& _name, smt::SortPointer _sort) +void SMTChecker::defineGlobalFunction(string const& _name, Expression const& _expr) { - if (!m_uninterpretedFunctions.count(_name)) - m_uninterpretedFunctions.emplace(_name, m_interface->newVariable(_name, _sort)); + if (!knownGlobalSymbol(_name)) + { + auto result = newSymbolicVariable(*_expr.annotation().type, _name, *m_interface); + m_globalContext.emplace(_name, result.second); + if (result.first) + m_errorReporter.warning( + _expr.location(), + "Assertion checker does not yet support the type of this function." + ); + } } void SMTChecker::arithmeticOperation(BinaryOperation const& _op) @@ -753,6 +881,8 @@ void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expressio checkUnderOverflow(_value, *intType, _location); else if (dynamic_cast<AddressType const*>(type.get())) checkUnderOverflow(_value, IntegerType(160), _location); + else if (dynamic_cast<MappingType const*>(type.get())) + arrayAssignment(); m_interface->addAssertion(newValue(_variable) == _value); } @@ -797,18 +927,31 @@ void SMTChecker::checkCondition( } for (auto const& var: m_variables) { - expressionsToEvaluate.emplace_back(currentValue(*var.first)); - expressionNames.push_back(var.first->name()); + if (var.first->type()->isValueType()) + { + expressionsToEvaluate.emplace_back(currentValue(*var.first)); + expressionNames.push_back(var.first->name()); + } } - for (auto const& var: m_specialVariables) + for (auto const& var: m_globalContext) { - expressionsToEvaluate.emplace_back(var.second->currentValue()); - expressionNames.push_back(var.first); + auto const& type = var.second->type(); + if ( + type->isValueType() && + smtKind(type->category()) != smt::Kind::Function + ) + { + expressionsToEvaluate.emplace_back(var.second->currentValue()); + expressionNames.push_back(var.first); + } } for (auto const& uf: m_uninterpretedTerms) { - expressionsToEvaluate.emplace_back(expr(*uf)); - expressionNames.push_back(m_scanner->sourceAt(uf->location())); + if (uf->annotation().type->isValueType()) + { + expressionsToEvaluate.emplace_back(expr(*uf)); + expressionNames.push_back(m_scanner->sourceAt(uf->location())); + } } } smt::CheckResult result; @@ -820,6 +963,13 @@ void SMTChecker::checkCondition( loopComment = "\nNote that some information is erased after the execution of loops.\n" "You can re-introduce information using require()."; + if (m_arrayAssignmentHappened) + loopComment += + "\nNote that array aliasing is not supported," + " therefore all mapping information is erased after" + " a mapping local variable/parameter is assigned.\n" + "You can re-introduce information using require()."; + switch (result) { case smt::CheckResult::SATISFIABLE: @@ -838,19 +988,19 @@ void SMTChecker::checkCondition( for (auto const& eval: sortedModel) modelMessage << " " << eval.first << " = " << eval.second << "\n"; - m_errorReporter.warning(_location, message.str() + loopComment, SecondarySourceLocation().append(modelMessage.str(), SourceLocation())); + m_errorReporter.warning(_location, message.str(), SecondarySourceLocation().append(modelMessage.str(), SourceLocation()).append(loopComment, SourceLocation())); } else { message << "."; - m_errorReporter.warning(_location, message.str() + loopComment); + m_errorReporter.warning(_location, message.str(), SecondarySourceLocation().append(loopComment, SourceLocation())); } break; } case smt::CheckResult::UNSATISFIABLE: break; case smt::CheckResult::UNKNOWN: - m_errorReporter.warning(_location, _description + " might happen here." + loopComment); + m_errorReporter.warning(_location, _description + " might happen here.", SecondarySourceLocation().append(loopComment, SourceLocation())); break; case smt::CheckResult::CONFLICTING: m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound."); @@ -933,7 +1083,7 @@ SMTChecker::checkSatisfiableAndGenerateModel(vector<smt::Expression> const& _exp try { // Parse and re-format nicely - value = formatNumber(bigint(value)); + value = formatNumberReadable(bigint(value)); } catch (...) { } } @@ -952,7 +1102,11 @@ void SMTChecker::initializeFunctionCallParameters(FunctionDefinition const& _fun solAssert(funParams.size() == _callArgs.size(), ""); for (unsigned i = 0; i < funParams.size(); ++i) if (createVariable(*funParams[i])) + { m_interface->addAssertion(_callArgs[i] == newValue(*funParams[i])); + if (funParams[i]->annotation().type->category() == Type::Category::Mapping) + m_arrayAssignmentHappened = true; + } for (auto const& variable: _function.localVariables()) if (createVariable(*variable)) @@ -1115,9 +1269,9 @@ bool SMTChecker::knownExpr(Expression const& _e) const return m_expressions.count(&_e); } -bool SMTChecker::knownSpecialVariable(string const& _var) const +bool SMTChecker::knownGlobalSymbol(string const& _var) const { - return m_specialVariables.count(_var); + return m_globalContext.count(_var); } void SMTChecker::createExpr(Expression const& _e) @@ -1140,6 +1294,7 @@ void SMTChecker::createExpr(Expression const& _e) void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) { createExpr(_e); + solAssert(isSupportedType(*_e.annotation().type), "Equality operator applied to type that is not fully supported"); m_interface->addAssertion(expr(_e) == _value); } diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 34724848..f14d2ac0 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -22,13 +22,11 @@ #include <libsolidity/formal/SymbolicVariables.h> #include <libsolidity/ast/ASTVisitor.h> - #include <libsolidity/interface/ReadFile.h> - #include <liblangutil/Scanner.h> -#include <unordered_map> #include <string> +#include <unordered_map> #include <vector> namespace langutil @@ -79,21 +77,32 @@ private: void endVisit(Literal const& _node) override; void endVisit(Return const& _node) override; bool visit(MemberAccess const& _node) override; + void endVisit(IndexAccess const& _node) override; void arithmeticOperation(BinaryOperation const& _op); void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); - void visitAssert(FunctionCall const&); - void visitRequire(FunctionCall const&); - void visitGasLeft(FunctionCall const&); - void visitBlockHash(FunctionCall const&); + void visitAssert(FunctionCall const& _funCall); + void visitRequire(FunctionCall const& _funCall); + void visitGasLeft(FunctionCall const& _funCall); /// Visits the FunctionDefinition of the called function /// if available and inlines the return value. - void inlineFunctionCall(FunctionCall const&); - - void defineSpecialVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); - void defineUninterpretedFunction(std::string const& _name, smt::SortPointer _sort); + void inlineFunctionCall(FunctionCall const& _funCall); + /// Creates an uninterpreted function call. + void abstractFunctionCall(FunctionCall const& _funCall); + void visitFunctionIdentifier(Identifier const& _identifier); + + void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); + void defineGlobalFunction(std::string const& _name, Expression const& _expr); + /// Handles the side effects of assignment + /// to variable of some SMT array type + /// while aliasing is not supported. + void arrayAssignment(); + /// Handles assignment to SMT array index. + void arrayIndexAssignment(Assignment const& _assignment); + /// Erases information about SMT arrays. + void eraseArrayKnowledge(); /// Division expression in the given type. Requires special treatment because /// of rounding for signed division. @@ -176,8 +185,8 @@ private: /// Creates the expression and sets its value. void defineExpr(Expression const& _e, smt::Expression _value); - /// Checks if special variable was seen. - bool knownSpecialVariable(std::string const& _var) const; + /// Checks if special variable or function was seen. + bool knownGlobalSymbol(std::string const& _var) const; /// Adds a new path condition void pushPathCondition(smt::Expression const& _e); @@ -201,16 +210,16 @@ private: std::shared_ptr<smt::SolverInterface> m_interface; std::shared_ptr<VariableUsage> m_variableUsage; bool m_loopExecutionHappened = false; + bool m_arrayAssignmentHappened = false; /// An Expression may have multiple smt::Expression due to /// repeated calls to the same function. std::unordered_map<Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions; std::unordered_map<VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables; - std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> m_specialVariables; - /// Stores the declaration of an Uninterpreted Function. - std::unordered_map<std::string, smt::Expression> m_uninterpretedFunctions; + std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> m_globalContext; /// Stores the instances of an Uninterpreted Function applied to arguments. + /// These may be direct application of UFs or Array index access. /// Used to retrieve models. - std::vector<Expression const*> m_uninterpretedTerms; + std::set<Expression const*> m_uninterpretedTerms; std::vector<smt::Expression> m_pathConditions; langutil::ErrorReporter& m_errorReporter; std::shared_ptr<langutil::Scanner> m_scanner; diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index 3cfa01b1..a23dbe55 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -17,22 +17,20 @@ #include <libsolidity/formal/SMTLib2Interface.h> -#include <liblangutil/Exceptions.h> #include <libsolidity/interface/ReadFile.h> - +#include <liblangutil/Exceptions.h> #include <libdevcore/Keccak256.h> -#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/join.hpp> +#include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem/operations.hpp> -#include <cstdio> +#include <array> #include <fstream> #include <iostream> #include <memory> #include <stdexcept> #include <string> -#include <array> using namespace std; using namespace dev; diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index 55fc4096..d0bf4702 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -19,20 +19,17 @@ #include <libsolidity/formal/SolverInterface.h> -#include <liblangutil/Exceptions.h> #include <libsolidity/interface/ReadFile.h> - -#include <libdevcore/FixedHash.h> - +#include <liblangutil/Exceptions.h> #include <libdevcore/Common.h> +#include <libdevcore/FixedHash.h> #include <boost/noncopyable.hpp> - +#include <cstdio> #include <map> +#include <set> #include <string> #include <vector> -#include <cstdio> -#include <set> namespace dev { diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index 7f5ba37e..8c38bd2e 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -19,13 +19,10 @@ #include <libsolidity/formal/SolverInterface.h> - #include <libsolidity/interface/ReadFile.h> - #include <libdevcore/FixedHash.h> #include <boost/noncopyable.hpp> - #include <map> #include <vector> diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 4a4b3fb1..6e0b17ac 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -17,18 +17,16 @@ #pragma once -#include <liblangutil/Exceptions.h> #include <libsolidity/interface/ReadFile.h> - +#include <liblangutil/Exceptions.h> #include <libdevcore/Common.h> #include <libdevcore/Exceptions.h> #include <boost/noncopyable.hpp> - +#include <cstdio> #include <map> #include <string> #include <vector> -#include <cstdio> namespace dev { @@ -80,6 +78,8 @@ struct FunctionSort: public Sort [&](SortPointer _a, SortPointer _b) { return *_a == *_b; } )) return false; + solAssert(codomain, ""); + solAssert(_otherFunction->codomain, ""); return *codomain == *_otherFunction->codomain; } @@ -99,6 +99,10 @@ struct ArraySort: public Sort return false; auto _otherArray = dynamic_cast<ArraySort const*>(&_other); solAssert(_otherArray, ""); + solAssert(_otherArray->domain, ""); + solAssert(_otherArray->range, ""); + solAssert(domain, ""); + solAssert(range, ""); return *domain == *_otherArray->domain && *range == *_otherArray->range; } @@ -161,8 +165,9 @@ public: static Expression select(Expression _array, Expression _index) { solAssert(_array.sort->kind == Kind::Array, ""); - auto const& arraySort = dynamic_cast<ArraySort const*>(_array.sort.get()); + std::shared_ptr<ArraySort> arraySort = std::dynamic_pointer_cast<ArraySort>(_array.sort); solAssert(arraySort, ""); + solAssert(_index.sort, ""); solAssert(*arraySort->domain == *_index.sort, ""); return Expression( "select", @@ -176,14 +181,16 @@ public: static Expression store(Expression _array, Expression _index, Expression _element) { solAssert(_array.sort->kind == Kind::Array, ""); - auto const& arraySort = dynamic_cast<ArraySort const*>(_array.sort.get()); + std::shared_ptr<ArraySort> arraySort = std::dynamic_pointer_cast<ArraySort>(_array.sort); solAssert(arraySort, ""); + solAssert(_index.sort, ""); + solAssert(_element.sort, ""); solAssert(*arraySort->domain == *_index.sort, ""); solAssert(*arraySort->range == *_element.sort, ""); return Expression( "store", std::vector<Expression>{std::move(_array), std::move(_index), std::move(_element)}, - _array.sort + arraySort ); } diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index c297c807..269bff73 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -18,7 +18,6 @@ #include <libsolidity/formal/SymbolicTypes.h> #include <libsolidity/ast/Types.h> - #include <memory> using namespace std; @@ -38,17 +37,33 @@ smt::SortPointer dev::solidity::smtSort(Type const& _type) solAssert(fType, ""); vector<smt::SortPointer> parameterSorts = smtSort(fType->parameterTypes()); auto returnTypes = fType->returnParameterTypes(); - // TODO remove this when we support tuples. - solAssert(returnTypes.size() == 1, ""); - smt::SortPointer returnSort = smtSort(*returnTypes.at(0)); + smt::SortPointer returnSort; + // TODO change this when we support tuples. + if (returnTypes.size() == 0) + // We cannot declare functions without a return sort, so we use the smallest. + returnSort = make_shared<smt::Sort>(smt::Kind::Bool); + else if (returnTypes.size() > 1) + // Abstract sort. + returnSort = make_shared<smt::Sort>(smt::Kind::Int); + else + returnSort = smtSort(*returnTypes.at(0)); return make_shared<smt::FunctionSort>(parameterSorts, returnSort); } case smt::Kind::Array: { - solUnimplementedAssert(false, "Invalid type"); + if (isMapping(_type.category())) + { + auto mapType = dynamic_cast<MappingType const*>(&_type); + solAssert(mapType, ""); + return make_shared<smt::ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType())); + } + // TODO Solidity array + return make_shared<smt::Sort>(smt::Kind::Int); } + default: + // Abstract case. + return make_shared<smt::Sort>(smt::Kind::Int); } - solAssert(false, "Invalid type"); } vector<smt::SortPointer> dev::solidity::smtSort(vector<TypePointer> const& _types) @@ -65,13 +80,24 @@ smt::Kind dev::solidity::smtKind(Type::Category _category) return smt::Kind::Int; else if (isBool(_category)) return smt::Kind::Bool; - solAssert(false, "Invalid type"); + else if (isFunction(_category)) + return smt::Kind::Function; + else if (isMapping(_category)) + return smt::Kind::Array; + // Abstract case. + return smt::Kind::Int; } bool dev::solidity::isSupportedType(Type::Category _category) { return isNumber(_category) || isBool(_category) || + isMapping(_category); +} + +bool dev::solidity::isSupportedTypeDeclaration(Type::Category _category) +{ + return isSupportedType(_category) || isFunction(_category); } @@ -84,7 +110,7 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( bool abstract = false; shared_ptr<SymbolicVariable> var; TypePointer type = _type.shared_from_this(); - if (!isSupportedType(_type)) + if (!isSupportedTypeDeclaration(_type)) { abstract = true; var = make_shared<SymbolicIntVariable>(make_shared<IntegerType>(256), _uniqueName, _solver); @@ -92,7 +118,7 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( else if (isBool(_type.category())) var = make_shared<SymbolicBoolVariable>(type, _uniqueName, _solver); else if (isFunction(_type.category())) - var = make_shared<SymbolicIntVariable>(make_shared<IntegerType>(256), _uniqueName, _solver); + var = make_shared<SymbolicFunctionVariable>(type, _uniqueName, _solver); else if (isInteger(_type.category())) var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver); else if (isFixedBytes(_type.category())) @@ -112,6 +138,8 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( else var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver); } + else if (isMapping(_type.category())) + var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _solver); else solAssert(false, ""); return make_pair(abstract, var); @@ -122,6 +150,11 @@ bool dev::solidity::isSupportedType(Type const& _type) return isSupportedType(_type.category()); } +bool dev::solidity::isSupportedTypeDeclaration(Type const& _type) +{ + return isSupportedTypeDeclaration(_type.category()); +} + bool dev::solidity::isInteger(Type::Category _category) { return _category == Type::Category::Integer; @@ -160,6 +193,11 @@ bool dev::solidity::isFunction(Type::Category _category) return _category == Type::Category::Function; } +bool dev::solidity::isMapping(Type::Category _category) +{ + return _category == Type::Category::Mapping; +} + smt::Expression dev::solidity::minValue(IntegerType const& _type) { return smt::Expression(_type.minValue()); diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 984653b3..35c7bb8d 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -19,7 +19,6 @@ #include <libsolidity/formal/SolverInterface.h> #include <libsolidity/formal/SymbolicVariables.h> - #include <libsolidity/ast/AST.h> #include <libsolidity/ast/Types.h> @@ -34,10 +33,12 @@ std::vector<smt::SortPointer> smtSort(std::vector<TypePointer> const& _types); /// Returns the SMT kind that models the Solidity type type category _category. smt::Kind smtKind(Type::Category _category); -/// So far int, bool and address are supported. -/// Returns true if type is supported. +/// Returns true if type is fully supported (declaration and operations). bool isSupportedType(Type::Category _category); bool isSupportedType(Type const& _type); +/// Returns true if type is partially supported (declaration). +bool isSupportedTypeDeclaration(Type::Category _category); +bool isSupportedTypeDeclaration(Type const& _type); bool isInteger(Type::Category _category); bool isRational(Type::Category _category); @@ -46,6 +47,7 @@ bool isAddress(Type::Category _category); bool isNumber(Type::Category _category); bool isBool(Type::Category _category); bool isFunction(Type::Category _category); +bool isMapping(Type::Category _category); /// Returns a new symbolic variable, according to _type. /// Also returns whether the type is abstract or not, diff --git a/libsolidity/formal/SymbolicVariables.cpp b/libsolidity/formal/SymbolicVariables.cpp index efaeb97a..c4fc81da 100644 --- a/libsolidity/formal/SymbolicVariables.cpp +++ b/libsolidity/formal/SymbolicVariables.cpp @@ -18,7 +18,6 @@ #include <libsolidity/formal/SymbolicVariables.h> #include <libsolidity/formal/SymbolicTypes.h> - #include <libsolidity/ast/AST.h> using namespace std; @@ -37,16 +36,32 @@ SymbolicVariable::SymbolicVariable( { } +smt::Expression SymbolicVariable::currentValue() const +{ + return valueAtIndex(m_ssa->index()); +} + string SymbolicVariable::currentName() const { return uniqueSymbol(m_ssa->index()); } +smt::Expression SymbolicVariable::valueAtIndex(int _index) const +{ + return m_interface.newVariable(uniqueSymbol(_index), smtSort(*m_type)); +} + string SymbolicVariable::uniqueSymbol(unsigned _index) const { return m_uniqueName + "_" + to_string(_index); } +smt::Expression SymbolicVariable::increaseIndex() +{ + ++(*m_ssa); + return currentValue(); +} + SymbolicBoolVariable::SymbolicBoolVariable( TypePointer _type, string const& _uniqueName, @@ -57,11 +72,6 @@ SymbolicBoolVariable::SymbolicBoolVariable( solAssert(m_type->category() == Type::Category::Bool, ""); } -smt::Expression SymbolicBoolVariable::valueAtIndex(int _index) const -{ - return m_interface.newVariable(uniqueSymbol(_index), make_shared<smt::Sort>(smt::Kind::Bool)); -} - SymbolicIntVariable::SymbolicIntVariable( TypePointer _type, string const& _uniqueName, @@ -72,11 +82,6 @@ SymbolicIntVariable::SymbolicIntVariable( solAssert(isNumber(m_type->category()), ""); } -smt::Expression SymbolicIntVariable::valueAtIndex(int _index) const -{ - return m_interface.newVariable(uniqueSymbol(_index), make_shared<smt::Sort>(smt::Kind::Int)); -} - SymbolicAddressVariable::SymbolicAddressVariable( string const& _uniqueName, smt::SolverInterface& _interface @@ -93,3 +98,41 @@ SymbolicFixedBytesVariable::SymbolicFixedBytesVariable( SymbolicIntVariable(make_shared<IntegerType>(_numBytes * 8), _uniqueName, _interface) { } + +SymbolicFunctionVariable::SymbolicFunctionVariable( + TypePointer _type, + string const& _uniqueName, + smt::SolverInterface&_interface +): + SymbolicVariable(move(_type), _uniqueName, _interface), + m_declaration(m_interface.newVariable(currentName(), smtSort(*m_type))) +{ + solAssert(m_type->category() == Type::Category::Function, ""); +} + +void SymbolicFunctionVariable::resetDeclaration() +{ + m_declaration = m_interface.newVariable(currentName(), smtSort(*m_type)); +} + +smt::Expression SymbolicFunctionVariable::increaseIndex() +{ + ++(*m_ssa); + resetDeclaration(); + return currentValue(); +} + +smt::Expression SymbolicFunctionVariable::operator()(vector<smt::Expression> _arguments) const +{ + return m_declaration(_arguments); +} + +SymbolicMappingVariable::SymbolicMappingVariable( + TypePointer _type, + string const& _uniqueName, + smt::SolverInterface& _interface +): + SymbolicVariable(move(_type), _uniqueName, _interface) +{ + solAssert(isMapping(m_type->category()), ""); +} diff --git a/libsolidity/formal/SymbolicVariables.h b/libsolidity/formal/SymbolicVariables.h index fcf32760..86abf4f1 100644 --- a/libsolidity/formal/SymbolicVariables.h +++ b/libsolidity/formal/SymbolicVariables.h @@ -17,12 +17,9 @@ #pragma once -#include <libsolidity/formal/SSAVariable.h> - #include <libsolidity/formal/SolverInterface.h> - +#include <libsolidity/formal/SSAVariable.h> #include <libsolidity/ast/Types.h> - #include <memory> namespace dev @@ -46,19 +43,13 @@ public: virtual ~SymbolicVariable() = default; - smt::Expression currentValue() const - { - return valueAtIndex(m_ssa->index()); - } - + smt::Expression currentValue() const; std::string currentName() const; - - virtual smt::Expression valueAtIndex(int _index) const = 0; - - smt::Expression increaseIndex() + virtual smt::Expression valueAtIndex(int _index) const; + virtual smt::Expression increaseIndex(); + virtual smt::Expression operator()(std::vector<smt::Expression> /*_arguments*/) const { - ++(*m_ssa); - return currentValue(); + solAssert(false, "Function application to non-function."); } unsigned index() const { return m_ssa->index(); } @@ -86,9 +77,6 @@ public: std::string const& _uniqueName, smt::SolverInterface& _interface ); - -protected: - smt::Expression valueAtIndex(int _index) const; }; /** @@ -102,9 +90,6 @@ public: std::string const& _uniqueName, smt::SolverInterface& _interface ); - -protected: - smt::Expression valueAtIndex(int _index) const; }; /** @@ -132,5 +117,41 @@ public: ); }; +/** + * Specialization of SymbolicVariable for FunctionType + */ +class SymbolicFunctionVariable: public SymbolicVariable +{ +public: + SymbolicFunctionVariable( + TypePointer _type, + std::string const& _uniqueName, + smt::SolverInterface& _interface + ); + + smt::Expression increaseIndex(); + smt::Expression operator()(std::vector<smt::Expression> _arguments) const; + +private: + /// Creates a new function declaration. + void resetDeclaration(); + + /// Stores the current function declaration. + smt::Expression m_declaration; +}; + +/** + * Specialization of SymbolicVariable for Mapping + */ +class SymbolicMappingVariable: public SymbolicVariable +{ +public: + SymbolicMappingVariable( + TypePointer _type, + std::string const& _uniqueName, + smt::SolverInterface& _interface + ); +}; + } } diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index cb01dc61..4cbc3271 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -18,7 +18,6 @@ #include <libsolidity/formal/Z3Interface.h> #include <liblangutil/Exceptions.h> - #include <libdevcore/CommonIO.h> using namespace std; diff --git a/libsolidity/formal/Z3Interface.h b/libsolidity/formal/Z3Interface.h index 86e1badd..ee4d1551 100644 --- a/libsolidity/formal/Z3Interface.h +++ b/libsolidity/formal/Z3Interface.h @@ -18,9 +18,7 @@ #pragma once #include <libsolidity/formal/SolverInterface.h> - #include <boost/noncopyable.hpp> - #include <z3++.h> namespace dev diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index aefb34af..0d27109e 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -19,6 +19,7 @@ */ #include <libsolidity/interface/ABI.h> + #include <libsolidity/ast/AST.h> using namespace std; diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h index db70729d..082f3900 100644 --- a/libsolidity/interface/ABI.h +++ b/libsolidity/interface/ABI.h @@ -20,9 +20,9 @@ #pragma once -#include <string> -#include <memory> #include <json/json.h> +#include <memory> +#include <string> namespace dev { diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index f5eb7e41..69bceefc 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -22,18 +22,19 @@ #include <libsolidity/interface/AssemblyStack.h> +#include <libsolidity/codegen/AsmCodeGen.h> +#include <libevmasm/Assembly.h> #include <liblangutil/Scanner.h> -#include <libyul/AsmPrinter.h> -#include <libyul/AsmParser.h> + #include <libyul/AsmAnalysis.h> #include <libyul/AsmAnalysisInfo.h> -#include <libyul/AsmCodeGen.h> -#include <libyul/backends/evm/EVMCodeTransform.h> +#include <libyul/AsmParser.h> +#include <libyul/AsmPrinter.h> #include <libyul/backends/evm/EVMAssembly.h> +#include <libyul/backends/evm/EVMCodeTransform.h> +#include <libyul/backends/evm/EVMDialect.h> +#include <libyul/backends/evm/EVMObjectCompiler.h> #include <libyul/ObjectParser.h> - -#include <libevmasm/Assembly.h> - #include <libyul/optimiser/Suite.h> using namespace std; @@ -43,19 +44,19 @@ using namespace dev::solidity; namespace { -yul::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language) +shared_ptr<yul::Dialect> languageToDialect(AssemblyStack::Language _language) { switch (_language) { case AssemblyStack::Language::Assembly: - return yul::AsmFlavour::Loose; + return yul::EVMDialect::looseAssemblyForEVM(); case AssemblyStack::Language::StrictAssembly: - return yul::AsmFlavour::Strict; + return yul::EVMDialect::strictAssemblyForEVMObjects(); case AssemblyStack::Language::Yul: - return yul::AsmFlavour::Yul; + return yul::Dialect::yul(); } solAssert(false, ""); - return yul::AsmFlavour::Yul; + return yul::Dialect::yul(); } } @@ -72,7 +73,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string m_errors.clear(); m_analysisSuccessful = false; m_scanner = make_shared<Scanner>(CharStream(_source, _sourceName)); - m_parserResult = yul::ObjectParser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner, false); + m_parserResult = yul::ObjectParser(m_errorReporter, languageToDialect(m_language)).parse(m_scanner, false); if (!m_errorReporter.errors().empty()) return false; solAssert(m_parserResult, ""); @@ -84,21 +85,59 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string void AssemblyStack::optimize() { solAssert(m_language != Language::Assembly, "Optimization requested for loose assembly."); - yul::OptimiserSuite::run(*m_parserResult->code, *m_parserResult->analysisInfo); + solAssert(m_analysisSuccessful, "Analysis was not successful."); + m_analysisSuccessful = false; + optimize(*m_parserResult); solAssert(analyzeParsed(), "Invalid source code after optimization."); } bool AssemblyStack::analyzeParsed() { solAssert(m_parserResult, ""); - solAssert(m_parserResult->code, ""); - m_parserResult->analysisInfo = make_shared<yul::AsmAnalysisInfo>(); - yul::AsmAnalyzer analyzer(*m_parserResult->analysisInfo, m_errorReporter, m_evmVersion, boost::none, languageToAsmFlavour(m_language)); - m_analysisSuccessful = analyzer.analyze(*m_parserResult->code); + m_analysisSuccessful = analyzeParsed(*m_parserResult); return m_analysisSuccessful; } -MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const +bool AssemblyStack::analyzeParsed(yul::Object& _object) +{ + solAssert(_object.code, ""); + _object.analysisInfo = make_shared<yul::AsmAnalysisInfo>(); + yul::AsmAnalyzer analyzer(*_object.analysisInfo, m_errorReporter, m_evmVersion, boost::none, languageToDialect(m_language)); + bool success = analyzer.analyze(*_object.code); + for (auto& subNode: _object.subObjects) + if (auto subObject = dynamic_cast<yul::Object*>(subNode.get())) + if (!analyzeParsed(*subObject)) + success = false; + return success; +} + +void AssemblyStack::compileEVM(yul::AbstractAssembly& _assembly, bool _evm15, bool _optimize) const +{ + shared_ptr<yul::EVMDialect> dialect; + + if (m_language == Language::Assembly) + dialect = yul::EVMDialect::looseAssemblyForEVM(); + else if (m_language == AssemblyStack::Language::StrictAssembly) + dialect = yul::EVMDialect::strictAssemblyForEVMObjects(); + else if (m_language == AssemblyStack::Language::Yul) + dialect = yul::EVMDialect::yulForEVM(); + else + solAssert(false, "Invalid language."); + + yul::EVMObjectCompiler::compile(*m_parserResult, _assembly, *dialect, _evm15, _optimize); +} + +void AssemblyStack::optimize(yul::Object& _object) +{ + solAssert(_object.code, ""); + solAssert(_object.analysisInfo, ""); + for (auto& subNode: _object.subObjects) + if (auto subObject = dynamic_cast<yul::Object*>(subNode.get())) + optimize(*subObject); + yul::OptimiserSuite::run(*_object.code, *_object.analysisInfo); +} + +MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) const { solAssert(m_analysisSuccessful, ""); solAssert(m_parserResult, ""); @@ -111,7 +150,8 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { MachineAssemblyObject object; eth::Assembly assembly; - yul::CodeGenerator::assemble(*m_parserResult->code, *m_parserResult->analysisInfo, assembly); + EthAssemblyAdapter adapter(assembly); + compileEVM(adapter, false, _optimize); object.bytecode = make_shared<eth::LinkerObject>(assembly.assemble()); object.assembly = assembly.assemblyString(); return object; @@ -120,7 +160,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { MachineAssemblyObject object; yul::EVMAssembly assembly(true); - yul::CodeTransform(assembly, *m_parserResult->analysisInfo, m_language == Language::Yul, true)(*m_parserResult->code); + compileEVM(assembly, true, _optimize); object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize()); /// TODO: fill out text representation return object; diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 0d04ffec..01db6b61 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -29,13 +29,17 @@ #include <libevmasm/LinkerObject.h> -#include <string> #include <memory> +#include <string> namespace langutil { class Scanner; } +namespace yul +{ +class AbstractAssembly; +} namespace dev { @@ -73,7 +77,8 @@ public: void optimize(); /// Run the assembly step (should only be called after parseAndAnalyze). - MachineAssemblyObject assemble(Machine _machine) const; + /// @param _optimize does not run the optimizer but performs optimized code generation. + MachineAssemblyObject assemble(Machine _machine, bool _optimize = false) const; /// @returns the errors generated during parsing, analysis (and potentially assembly). langutil::ErrorList const& errors() const { return m_errors; } @@ -83,6 +88,11 @@ public: private: bool analyzeParsed(); + bool analyzeParsed(yul::Object& _object); + + void compileEVM(yul::AbstractAssembly& _assembly, bool _evm15, bool _optimize) const; + + void optimize(yul::Object& _object); Language m_language = Language::Assembly; EVMVersion m_evmVersion; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 610caea1..f9d889e7 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -24,26 +24,27 @@ #include <libsolidity/interface/CompilerStack.h> -#include <libsolidity/interface/Version.h> -#include <libsolidity/analysis/SemVerHandler.h> -#include <libsolidity/ast/AST.h> -#include <libsolidity/parsing/Parser.h> -#include <libsolidity/analysis/ContractLevelChecker.h> #include <libsolidity/analysis/ControlFlowAnalyzer.h> #include <libsolidity/analysis/ControlFlowGraph.h> +#include <libsolidity/analysis/ContractLevelChecker.h> +#include <libsolidity/analysis/DocStringAnalyser.h> #include <libsolidity/analysis/GlobalContext.h> #include <libsolidity/analysis/NameAndTypeResolver.h> -#include <libsolidity/analysis/TypeChecker.h> -#include <libsolidity/analysis/DocStringAnalyser.h> -#include <libsolidity/analysis/StaticAnalyzer.h> #include <libsolidity/analysis/PostTypeChecker.h> +#include <libsolidity/analysis/SemVerHandler.h> +#include <libsolidity/analysis/StaticAnalyzer.h> #include <libsolidity/analysis/SyntaxChecker.h> +#include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/analysis/ViewPureChecker.h> + +#include <libsolidity/ast/AST.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/formal/SMTChecker.h> #include <libsolidity/interface/ABI.h> #include <libsolidity/interface/Natspec.h> #include <libsolidity/interface/GasEstimator.h> +#include <libsolidity/interface/Version.h> +#include <libsolidity/parsing/Parser.h> #include <libyul/YulString.h> @@ -388,18 +389,27 @@ string const CompilerStack::lastContractName() const eth::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); return currentContract.compiler ? &contract(_contractName).compiler->assemblyItems() : nullptr; } eth::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); return currentContract.compiler ? &contract(_contractName).compiler->runtimeAssemblyItems() : nullptr; } string const* CompilerStack::sourceMapping(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& c = contract(_contractName); if (!c.sourceMapping) { @@ -411,6 +421,9 @@ string const* CompilerStack::sourceMapping(string const& _contractName) const string const* CompilerStack::runtimeSourceMapping(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& c = contract(_contractName); if (!c.runtimeSourceMapping) { @@ -446,17 +459,26 @@ std::string const CompilerStack::filesystemFriendlyName(string const& _contractN eth::LinkerObject const& CompilerStack::object(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + return contract(_contractName).object; } eth::LinkerObject const& CompilerStack::runtimeObject(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + return contract(_contractName).runtimeObject; } /// FIXME: cache this string string CompilerStack::assemblyString(string const& _contractName, StringMap _sourceCodes) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); if (currentContract.compiler) return currentContract.compiler->assemblyString(_sourceCodes); @@ -467,6 +489,9 @@ string CompilerStack::assemblyString(string const& _contractName, StringMap _sou /// FIXME: cache the JSON Json::Value CompilerStack::assemblyJSON(string const& _contractName, StringMap _sourceCodes) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); if (currentContract.compiler) return currentContract.compiler->assemblyJSON(_sourceCodes); @@ -493,13 +518,16 @@ map<string, unsigned> CompilerStack::sourceIndices() const Json::Value const& CompilerStack::contractABI(string const& _contractName) const { + if (m_stackState < AnalysisSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + return contractABI(contract(_contractName)); } Json::Value const& CompilerStack::contractABI(Contract const& _contract) const { if (m_stackState < AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -512,13 +540,16 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const Json::Value const& CompilerStack::natspecUser(string const& _contractName) const { + if (m_stackState < AnalysisSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + return natspecUser(contract(_contractName)); } Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const { if (m_stackState < AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -531,13 +562,16 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const Json::Value const& CompilerStack::natspecDev(string const& _contractName) const { + if (m_stackState < AnalysisSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + return natspecDev(contract(_contractName)); } Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const { if (m_stackState < AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -550,9 +584,12 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const { + if (m_stackState < AnalysisSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + Json::Value methodIdentifiers(Json::objectValue); for (auto const& it: contractDefinition(_contractName).interfaceFunctions()) - methodIdentifiers[it.second->externalSignature()] = toHex(it.first.ref()); + methodIdentifiers[it.second->externalSignature()] = it.first.hex(); return methodIdentifiers; } @@ -582,8 +619,8 @@ SourceUnit const& CompilerStack::ast(string const& _sourceName) const ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const { - if (m_stackState != CompilationSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + if (m_stackState < AnalysisSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); return *contract(_contractName).contract; } @@ -593,6 +630,9 @@ size_t CompilerStack::functionEntryPoint( FunctionDefinition const& _function ) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + shared_ptr<Compiler> const& compiler = contract(_contractName).compiler; if (!compiler) return 0; @@ -618,6 +658,22 @@ tuple<int, int, int, int> CompilerStack::positionFromSourceLocation(SourceLocati return make_tuple(++startLine, ++startColumn, ++endLine, ++endColumn); } + +h256 const& CompilerStack::Source::keccak256() const +{ + if (keccak256HashCached == h256{}) + keccak256HashCached = dev::keccak256(scanner->source()); + return keccak256HashCached; +} + +h256 const& CompilerStack::Source::swarmHash() const +{ + if (swarmHashCached == h256{}) + swarmHashCached = dev::swarmHash(scanner->source()); + return swarmHashCached; +} + + StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath) { solAssert(m_stackState < ParsingSuccessful, ""); @@ -859,16 +915,13 @@ string CompilerStack::createMetadata(Contract const& _contract) const continue; solAssert(s.second.scanner, "Scanner not available"); - meta["sources"][s.first]["keccak256"] = - "0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes()); + meta["sources"][s.first]["keccak256"] = "0x" + toHex(s.second.keccak256().asBytes()); if (m_metadataLiteralSources) meta["sources"][s.first]["content"] = s.second.scanner->source(); else { meta["sources"][s.first]["urls"] = Json::arrayValue; - meta["sources"][s.first]["urls"].append( - "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes()) - ); + meta["sources"][s.first]["urls"].append("bzzr://" + toHex(s.second.swarmHash().asBytes())); } } meta["settings"]["optimizer"]["enabled"] = m_optimize; @@ -895,7 +948,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const return jsonCompactPrint(meta); } -bytes CompilerStack::createCBORMetadata(string _metadata, bool _experimentalMode) +bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimentalMode) { bytes cborEncodedHash = // CBOR-encoding of the key "bzzr0" @@ -922,6 +975,9 @@ bytes CompilerStack::createCBORMetadata(string _metadata, bool _experimentalMode string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + string ret; map<string, unsigned> sourceIndicesMap = sourceIndices(); int prevStart = -1; @@ -1008,6 +1064,9 @@ Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) Json::Value CompilerStack::gasEstimates(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName)) return Json::Value(); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 2c7add3b..81d5009f 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -34,15 +34,14 @@ #include <libdevcore/Common.h> #include <libdevcore/FixedHash.h> -#include <json/json.h> - #include <boost/noncopyable.hpp> +#include <json/json.h> +#include <functional> +#include <memory> #include <ostream> #include <string> -#include <memory> #include <vector> -#include <functional> namespace langutil { @@ -262,7 +261,11 @@ private: std::shared_ptr<langutil::Scanner> scanner; std::shared_ptr<SourceUnit> ast; bool isLibrary = false; - void reset() { scanner.reset(); ast.reset(); } + h256 mutable keccak256HashCached; + h256 mutable swarmHashCached; + void reset() { *this = Source(); } + h256 const& keccak256() const; + h256 const& swarmHash() const; }; /// The state per contract. Filled gradually during compilation. @@ -316,7 +319,7 @@ private: std::string createMetadata(Contract const& _contract) const; /// @returns the metadata CBOR for the given serialised metadata JSON. - static bytes createCBORMetadata(std::string _metadata, bool _experimentalMode); + static bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode); /// @returns the computer source mapping string. std::string computeSourceMapping(eth::AssemblyItems const& _items) const; diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp index de6b2ce5..8ffcf951 100644 --- a/libsolidity/interface/GasEstimator.cpp +++ b/libsolidity/interface/GasEstimator.cpp @@ -20,18 +20,21 @@ * Gas consumption estimator working alongside the AST. */ -#include "GasEstimator.h" -#include <map> -#include <functional> -#include <memory> -#include <libdevcore/Keccak256.h> -#include <libevmasm/ControlFlowGraph.h> -#include <libevmasm/KnownState.h> -#include <libevmasm/PathGasMeter.h> +#include <libsolidity/interface/GasEstimator.h> + #include <libsolidity/ast/AST.h> #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/codegen/CompilerUtils.h> +#include <libevmasm/ControlFlowGraph.h> +#include <libevmasm/KnownState.h> +#include <libevmasm/PathGasMeter.h> +#include <libdevcore/Keccak256.h> + +#include <functional> +#include <map> +#include <memory> + using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libsolidity/interface/GasEstimator.h b/libsolidity/interface/GasEstimator.h index 214a3e58..f40cffeb 100644 --- a/libsolidity/interface/GasEstimator.h +++ b/libsolidity/interface/GasEstimator.h @@ -24,12 +24,12 @@ #include <liblangutil/EVMVersion.h> -#include <libevmasm/GasMeter.h> #include <libevmasm/Assembly.h> +#include <libevmasm/GasMeter.h> -#include <vector> -#include <map> #include <array> +#include <map> +#include <vector> namespace dev { diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index 11dde349..7a89abae 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -24,8 +24,9 @@ */ #include <libsolidity/interface/Natspec.h> -#include <boost/range/irange.hpp> + #include <libsolidity/ast/AST.h> +#include <boost/range/irange.hpp> using namespace std; using namespace dev; diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h index 0be4dda2..fbaa6d4d 100644 --- a/libsolidity/interface/Natspec.h +++ b/libsolidity/interface/Natspec.h @@ -25,9 +25,9 @@ #pragma once -#include <string> -#include <memory> #include <json/json.h> +#include <memory> +#include <string> namespace dev { diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h index 7068629d..3b3d747e 100644 --- a/libsolidity/interface/ReadFile.h +++ b/libsolidity/interface/ReadFile.h @@ -17,9 +17,9 @@ #pragma once -#include <string> -#include <functional> #include <boost/noncopyable.hpp> +#include <functional> +#include <string> namespace dev { diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 0eef50d2..137a4439 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -21,13 +21,17 @@ */ #include <libsolidity/interface/StandardCompiler.h> -#include <liblangutil/SourceReferenceFormatter.h> + #include <libsolidity/ast/ASTJsonConverter.h> +#include <liblangutil/SourceReferenceFormatter.h> #include <libevmasm/Instruction.h> #include <libdevcore/JSON.h> #include <libdevcore/Keccak256.h> +#include <boost/algorithm/cxx11/any_of.hpp> #include <boost/algorithm/string.hpp> +#include <boost/optional.hpp> +#include <algorithm> using namespace std; using namespace dev; @@ -69,12 +73,11 @@ Json::Value formatErrorWithException( bool const& _warning, string const& _type, string const& _component, - string const& _message, - function<Scanner const&(string const&)> const& _scannerFromSourceName + string const& _message ) { string message; - string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _type, _scannerFromSourceName); + string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _type); // NOTE: the below is partially a copy from SourceReferenceFormatter SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception); @@ -189,6 +192,31 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil return false; } +/// @returns true if any binary was requested, i.e. we actually have to perform compilation. +bool isBinaryRequested(Json::Value const& _outputSelection) +{ + if (!_outputSelection.isObject()) + return false; + + // This does not inculde "evm.methodIdentifiers" on purpose! + static vector<string> const outputsThatRequireBinaries{ + "*", + "metadata", // This is only generated at the end of compilation, but could be generated earlier. + "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", + "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", + "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", + "evm.bytecode.linkReferences", + "evm.gasEstimates", "evm.legacyAssembly", "evm.assembly" + }; + + for (auto const& fileRequests: _outputSelection) + for (auto const& requests: fileRequests) + for (auto const& output: outputsThatRequireBinaries) + if (isArtifactRequested(requests, output)) + return true; + return false; +} + Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences) { Json::Value ret(Json::objectValue); @@ -226,6 +254,99 @@ Json::Value collectEVMObject(eth::LinkerObject const& _object, string const* _so return output; } +boost::optional<Json::Value> checkKeys(Json::Value const& _input, set<string> const& _keys, string const& _name) +{ + if (!!_input && !_input.isObject()) + return formatFatalError("JSONError", "\"" + _name + "\" must be an object"); + + for (auto const& member: _input.getMemberNames()) + if (!_keys.count(member)) + return formatFatalError("JSONError", "Unknown key \"" + member + "\""); + + return boost::none; +} + +boost::optional<Json::Value> checkRootKeys(Json::Value const& _input) +{ + static set<string> keys{"auxiliaryInput", "language", "settings", "sources"}; + return checkKeys(_input, keys, "root"); +} + +boost::optional<Json::Value> checkSourceKeys(Json::Value const& _input, string const& _name) +{ + static set<string> keys{"content", "keccak256", "urls"}; + return checkKeys(_input, keys, "sources." + _name); +} + +boost::optional<Json::Value> checkAuxiliaryInputKeys(Json::Value const& _input) +{ + static set<string> keys{"smtlib2responses"}; + return checkKeys(_input, keys, "auxiliaryInput"); +} + +boost::optional<Json::Value> checkSettingsKeys(Json::Value const& _input) +{ + static set<string> keys{"evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; + return checkKeys(_input, keys, "settings"); +} + +boost::optional<Json::Value> checkOptimizerKeys(Json::Value const& _input) +{ + static set<string> keys{"enabled", "runs"}; + return checkKeys(_input, keys, "settings.optimizer"); +} + +boost::optional<Json::Value> checkMetadataKeys(Json::Value const& _input) +{ + static set<string> keys{"useLiteralContent"}; + return checkKeys(_input, keys, "settings.metadata"); +} + +boost::optional<Json::Value> checkOutputSelection(Json::Value const& _outputSelection) +{ + if (!!_outputSelection && !_outputSelection.isObject()) + return formatFatalError("JSONError", "\"settings.outputSelection\" must be an object"); + + for (auto const& sourceName: _outputSelection.getMemberNames()) + { + auto const& sourceVal = _outputSelection[sourceName]; + + if (!sourceVal.isObject()) + return formatFatalError( + "JSONError", + "\"settings.outputSelection." + sourceName + "\" must be an object" + ); + + for (auto const& contractName: sourceVal.getMemberNames()) + { + auto const& contractVal = sourceVal[contractName]; + + if (!contractVal.isArray()) + return formatFatalError( + "JSONError", + "\"settings.outputSelection." + + sourceName + + "." + + contractName + + "\" must be a string array" + ); + + for (auto const& output: contractVal) + if (!output.isString()) + return formatFatalError( + "JSONError", + "\"settings.outputSelection." + + sourceName + + "." + + contractName + + "\" must be a string array" + ); + } + } + + return boost::none; +} + } Json::Value StandardCompiler::compileInternal(Json::Value const& _input) @@ -235,6 +356,9 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (!_input.isObject()) return formatFatalError("JSONError", "Input is not a JSON object."); + if (auto result = checkRootKeys(_input)) + return *result; + if (_input["language"] != "Solidity") return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language."); @@ -252,8 +376,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { string hash; - if (!sources[sourceName].isObject()) - return formatFatalError("JSONError", "Source input is not a JSON object."); + if (auto result = checkSourceKeys(sources[sourceName], sourceName)) + return *result; if (sources[sourceName]["keccak256"].isString()) hash = sources[sourceName]["keccak256"].asString(); @@ -320,10 +444,18 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) } Json::Value const& auxInputs = _input["auxiliaryInput"]; + + if (auto result = checkAuxiliaryInputKeys(auxInputs)) + return *result; + if (!!auxInputs) { Json::Value const& smtlib2Responses = auxInputs["smtlib2responses"]; if (!!smtlib2Responses) + { + if (!smtlib2Responses.isObject()) + return formatFatalError("JSONError", "\"auxiliaryInput.smtlib2responses\" must be an object."); + for (auto const& hashString: smtlib2Responses.getMemberNames()) { h256 hash; @@ -336,12 +468,22 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) return formatFatalError("JSONError", "Invalid hex encoding of SMTLib2 auxiliary input."); } + if (!smtlib2Responses[hashString].isString()) + return formatFatalError( + "JSONError", + "\"smtlib2Responses." + hashString + "\" must be a string." + ); + m_compilerStack.addSMTLib2Response(hash, smtlib2Responses[hashString].asString()); } + } } Json::Value const& settings = _input.get("settings", Json::Value()); + if (auto result = checkSettingsKeys(settings)) + return *result; + if (settings.isMember("evmVersion")) { if (!settings["evmVersion"].isString()) @@ -352,11 +494,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.setEVMVersion(*version); } + if (settings.isMember("remappings") && !settings["remappings"].isArray()) + return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings."); + vector<CompilerStack::Remapping> remappings; for (auto const& remapping: settings.get("remappings", Json::Value())) { if (!remapping.isString()) - return formatFatalError("JSONError", "Remapping entry must be a string."); + return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings"); if (auto r = CompilerStack::parseRemapping(remapping.asString())) remappings.emplace_back(std::move(*r)); else @@ -367,6 +512,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (settings.isMember("optimizer")) { Json::Value optimizerSettings = settings["optimizer"]; + + if (auto result = checkOptimizerKeys(optimizerSettings)) + return *result; + if (optimizerSettings.isMember("enabled")) { if (!optimizerSettings["enabled"].isBool()) @@ -428,16 +577,27 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.setLibraries(libraries); Json::Value metadataSettings = settings.get("metadata", Json::Value()); + + if (auto result = checkMetadataKeys(metadataSettings)) + return *result; + m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool()); Json::Value outputSelection = settings.get("outputSelection", Json::Value()); + + if (auto jsonError = checkOutputSelection(outputSelection)) + return *jsonError; + m_compilerStack.setRequestedContractNames(requestedContractNames(outputSelection)); - auto scannerFromSourceName = [&](string const& _sourceName) -> Scanner const& { return m_compilerStack.scanner(_sourceName); }; + bool const binariesRequested = isBinaryRequested(outputSelection); try { - m_compilerStack.compile(); + if (binariesRequested) + m_compilerStack.compile(); + else + m_compilerStack.parseAndAnalyze(); for (auto const& error: m_compilerStack.errors()) { @@ -448,8 +608,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) err.type() == Error::Type::Warning, err.typeName(), "general", - "", - scannerFromSourceName + "" )); } } @@ -461,8 +620,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, _error.typeName(), "general", - "Uncaught error: ", - scannerFromSourceName + "Uncaught error: " )); } /// This should not be leaked from compile(). @@ -482,8 +640,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, "CompilerError", "general", - "Compiler error (" + _exception.lineInfo() + ")", - scannerFromSourceName + "Compiler error (" + _exception.lineInfo() + ")" )); } catch (InternalCompilerError const& _exception) @@ -493,8 +650,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, "InternalCompilerError", "general", - "Internal compiler error (" + _exception.lineInfo() + ")", - scannerFromSourceName + "Internal compiler error (" + _exception.lineInfo() + ")" )); } catch (UnimplementedFeatureError const& _exception) @@ -504,8 +660,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, "UnimplementedFeatureError", "general", - "Unimplemented feature (" + _exception.lineInfo() + ")", - scannerFromSourceName + "Unimplemented feature (" + _exception.lineInfo() + ")" )); } catch (Exception const& _exception) @@ -531,7 +686,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) bool const compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful; /// Inconsistent state - stop here to receive error reports from users - if (!compilationSuccess && errors.empty()) + if (((binariesRequested && !compilationSuccess) || !analysisSuccess) && errors.empty()) return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); Json::Value output = Json::objectValue; @@ -557,7 +712,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) } Json::Value contractsOutput = Json::objectValue; - for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>()) + for (string const& contractName: analysisSuccess ? m_compilerStack.contractNames() : vector<string>()) { size_t colon = contractName.rfind(':'); solAssert(colon != string::npos, ""); @@ -568,7 +723,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) Json::Value contractData(Json::objectValue); if (isArtifactRequested(outputSelection, file, name, "abi")) contractData["abi"] = m_compilerStack.contractABI(contractName); - if (isArtifactRequested(outputSelection, file, name, "metadata")) + if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "metadata")) contractData["metadata"] = m_compilerStack.metadata(contractName); if (isArtifactRequested(outputSelection, file, name, "userdoc")) contractData["userdoc"] = m_compilerStack.natspecUser(contractName); @@ -578,16 +733,16 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // EVM Json::Value evmData(Json::objectValue); // @TODO: add ir - if (isArtifactRequested(outputSelection, file, name, "evm.assembly")) + if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.assembly")) evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); - if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly")) + if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly")) evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers")) evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); - if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates")) + if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.gasEstimates")) evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); - if (isArtifactRequested( + if (compilationSuccess && isArtifactRequested( outputSelection, file, name, @@ -598,7 +753,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.sourceMapping(contractName) ); - if (isArtifactRequested( + if (compilationSuccess && isArtifactRequested( outputSelection, file, name, @@ -609,14 +764,18 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.runtimeSourceMapping(contractName) ); - contractData["evm"] = evmData; - - if (!contractsOutput.isMember(file)) - contractsOutput[file] = Json::objectValue; + if (!evmData.empty()) + contractData["evm"] = evmData; - contractsOutput[file][name] = contractData; + if (!contractData.empty()) + { + if (!contractsOutput.isMember(file)) + contractsOutput[file] = Json::objectValue; + contractsOutput[file][name] = contractData; + } } - output["contracts"] = contractsOutput; + if (!contractsOutput.empty()) + output["contracts"] = contractsOutput; return output; } diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index b785d557..efd46d40 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -21,11 +21,12 @@ */ #include <libsolidity/interface/Version.h> -#include <string> + +#include <liblangutil/Exceptions.h> #include <libdevcore/CommonData.h> #include <libdevcore/Common.h> -#include <liblangutil/Exceptions.h> #include <solidity/BuildInfo.h> +#include <string> using namespace dev; using namespace dev::solidity; diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h index 24c3555d..38d63ec6 100644 --- a/libsolidity/interface/Version.h +++ b/libsolidity/interface/Version.h @@ -22,8 +22,8 @@ #pragma once -#include <string> #include <libdevcore/Common.h> +#include <string> namespace dev { diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index d8927fea..d1d45150 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -1,17 +1,33 @@ +/* + 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/parsing/DocStringParser.h> + #include <liblangutil/ErrorReporter.h> #include <liblangutil/Exceptions.h> -#include <boost/range/irange.hpp> #include <boost/range/algorithm.hpp> +#include <boost/range/irange.hpp> using namespace std; using namespace dev; using namespace langutil; using namespace dev::solidity; - namespace { diff --git a/libsolidity/parsing/DocStringParser.h b/libsolidity/parsing/DocStringParser.h index c83b416d..671a2f34 100644 --- a/libsolidity/parsing/DocStringParser.h +++ b/libsolidity/parsing/DocStringParser.h @@ -22,8 +22,8 @@ #pragma once -#include <string> #include <libsolidity/ast/ASTAnnotations.h> +#include <string> namespace langutil { diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 6cab7be3..8a6bc343 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -20,13 +20,17 @@ * Solidity parser. */ -#include <cctype> -#include <vector> #include <libsolidity/parsing/Parser.h> + +#include <libsolidity/analysis/SemVerHandler.h> +#include <libsolidity/interface/Version.h> #include <libyul/AsmParser.h> -#include <liblangutil/SourceLocation.h> +#include <libyul/backends/evm/EVMDialect.h> #include <liblangutil/ErrorReporter.h> #include <liblangutil/Scanner.h> +#include <liblangutil/SourceLocation.h> +#include <cctype> +#include <vector> using namespace std; using namespace langutil; @@ -42,9 +46,9 @@ class Parser::ASTNodeFactory { public: explicit ASTNodeFactory(Parser const& _parser): - m_parser(_parser), m_location(_parser.position(), -1, _parser.source()) {} + m_parser(_parser), m_location{_parser.position(), -1, _parser.source()} {} ASTNodeFactory(Parser const& _parser, ASTPointer<ASTNode> const& _childNode): - m_parser(_parser), m_location(_childNode->location()) {} + m_parser(_parser), m_location{_childNode->location()} {} void markEndPosition() { m_location.end = m_parser.endPosition(); } void setLocation(SourceLocation const& _location) { m_location = _location; } @@ -104,6 +108,20 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) } } +void Parser::parsePragmaVersion(vector<Token> const& tokens, vector<string> const& literals) +{ + SemVerMatchExpressionParser parser(tokens, literals); + auto matchExpression = parser.parse(); + static SemVerVersion const currentVersion{string(VersionString)}; + // FIXME: only match for major version incompatibility + if (!matchExpression.matches(currentVersion)) + fatalParserError( + "Source file requires different compiler version (current compiler is " + + string(VersionString) + " - note that nightly builds are considered to be " + "strictly less than the released version" + ); +} + ASTPointer<PragmaDirective> Parser::parsePragmaDirective() { RecursionGuard recursionGuard(*this); @@ -132,6 +150,15 @@ ASTPointer<PragmaDirective> Parser::parsePragmaDirective() while (m_scanner->currentToken() != Token::Semicolon && m_scanner->currentToken() != Token::EOS); nodeFactory.markEndPosition(); expectToken(Token::Semicolon); + + if (literals.size() >= 2 && literals[0] == "solidity") + { + parsePragmaVersion( + vector<Token>(tokens.begin() + 1, tokens.end()), + vector<string>(literals.begin() + 1, literals.end()) + ); + } + return nodeFactory.createNode<PragmaDirective>(tokens, literals); } @@ -170,7 +197,7 @@ ASTPointer<ImportDirective> Parser::parseImportDirective() expectToken(Token::As); alias = expectIdentifierToken(); } - symbolAliases.push_back(make_pair(move(id), move(alias))); + symbolAliases.emplace_back(move(id), move(alias)); if (m_scanner->currentToken() != Token::Comma) break; m_scanner->next(); @@ -1012,7 +1039,7 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con m_scanner->next(); } - yul::Parser asmParser(m_errorReporter); + yul::Parser asmParser(m_errorReporter, yul::EVMDialect::looseAssemblyForEVM()); shared_ptr<yul::Block> block = asmParser.parse(m_scanner, true); nodeFactory.markEndPosition(); return nodeFactory.createNode<InlineAssembly>(_docString, block); @@ -1690,7 +1717,7 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath() index = parseExpression(); SourceLocation indexLocation = iap.path.front()->location(); indexLocation.end = endPosition(); - iap.indices.push_back(make_pair(index, indexLocation)); + iap.indices.emplace_back(index, indexLocation); expectToken(Token::RBrack); } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 15852096..b8d0e9a8 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -47,7 +47,10 @@ private: struct VarDeclParserOptions { + // This is actually not needed, but due to a defect in the C++ standard, we have to. + // https://stackoverflow.com/questions/17430377 VarDeclParserOptions() {} + bool allowVar = false; bool isStateVariable = false; bool allowIndexed = false; @@ -70,6 +73,7 @@ private: ///@{ ///@name Parsing functions for the AST nodes + void parsePragmaVersion(std::vector<Token> const& tokens, std::vector<std::string> const& literals); ASTPointer<PragmaDirective> parsePragmaDirective(); ASTPointer<ImportDirective> parseImportDirective(); ContractDefinition::ContractKind parseContractKind(); @@ -84,7 +88,7 @@ private: ASTPointer<EnumDefinition> parseEnumDefinition(); ASTPointer<EnumValue> parseEnumValue(); ASTPointer<VariableDeclaration> parseVariableDeclaration( - VarDeclParserOptions const& _options = VarDeclParserOptions(), + VarDeclParserOptions const& _options = {}, ASTPointer<TypeName> const& _lookAheadArrayType = ASTPointer<TypeName>() ); ASTPointer<ModifierDefinition> parseModifierDefinition(); @@ -98,7 +102,7 @@ private: ASTPointer<FunctionTypeName> parseFunctionType(); ASTPointer<Mapping> parseMapping(); ASTPointer<ParameterList> parseParameterList( - VarDeclParserOptions const& _options, + VarDeclParserOptions const& _options = {}, bool _allowEmpty = true ); ASTPointer<Block> parseBlock(ASTPointer<ASTString> const& _docString = {}); |