diff options
125 files changed, 1456 insertions, 797 deletions
diff --git a/.travis.yml b/.travis.yml index d68c79ba..8da17c45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,18 @@ matrix: env: - ZIP_SUFFIX=ubuntu-trusty - SOLC_STOREBYTECODE=On + before_install: + - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + - sudo add-apt-repository -y ppa:mhier/libboost-latest + - sudo add-apt-repository -y ppa:hvr/z3 + - sudo apt-get update -qq + install: + - sudo apt-get install -qq g++-8 gcc-8 + - sudo apt-get install -qq libboost1.67-dev + - sudo apt-get install -qq libleveldb1 + - sudo apt-get install -qq libz3-dev + - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 + - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 90 - os: linux dist: trusty @@ -63,6 +75,18 @@ matrix: env: - ZIP_SUFFIX=ubuntu-trusty-clang - SOLC_STOREBYTECODE=On + before_install: + - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + - sudo add-apt-repository -y ppa:mhier/libboost-latest + - sudo add-apt-repository -y ppa:hvr/z3 + - sudo apt-get update -qq + install: + - sudo apt-get install -qq g++-8 gcc-8 + - sudo apt-get install -qq libboost1.67-dev + - sudo apt-get install -qq libleveldb1 + - sudo apt-get install -qq libz3-dev + - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 + - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 90 # Docker target, which generates a statically linked alpine image - os: linux @@ -159,18 +183,15 @@ cache: install: - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh) - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh) + +before_script: # Disable tests unless run on the release branch, on tags or with daily cron - #- if [ "$TRAVIS_BRANCH" != release -a -z "$TRAVIS_TAG" -a "$TRAVIS_EVENT_TYPE" != cron ]; then SOLC_TESTS=Off; fi - - SOLC_TESTS=Off + - if [ "$TRAVIS_BRANCH" != release -a -z "$TRAVIS_TAG" -a "$TRAVIS_EVENT_TYPE" != cron ]; then SOLC_TESTS=Off; fi - if [ "$TRAVIS_BRANCH" = release -o -n "$TRAVIS_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi - echo -n "$TRAVIS_COMMIT" > commit_hash.txt - -before_script: - test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh) - test $SOLC_DOCKER != On || (scripts/docker_build.sh) - - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE - && scripts/release.sh $ZIP_SUFFIX - && scripts/create_source_tarball.sh) + - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE -DBoost_USE_STATIC_LIBS=OFF && scripts/create_source_tarball.sh) script: - test $SOLC_EMSCRIPTEN != On -o $SOLC_TESTS != On || (scripts/test_emscripten.sh) @@ -204,7 +225,7 @@ deploy: - release - /^v\d/ # This is the deploy target for the native build (Linux and macOS) - # which generates ZIPs per commit and the source tarball. + # which generates the source tarball. # # This runs for each tag that is created and adds the corresponding files. - provider: releases diff --git a/Changelog.md b/Changelog.md index 6a366ca0..838fd8be 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,12 +1,17 @@ ### 0.5.2 (unreleased) Language Features: + * Control Flow Graph: Detect every access to uninitialized storage pointers. Compiler Features: * Inline Assembly: Improve error messages around invalid function argument count. + * Code Generator: Use codecopy for string constants more aggressively. * Code Generator: Use binary search for dispatch function if more efficient. The size/speed tradeoff can be tuned using ``--optimize-runs``. + * SMTChecker: Support mathematical and cryptographic functions in an uninterpreted way. + * Static Analyzer: Do not warn about unused variables or state mutability for functions with an empty body. * Type Checker: Add an additional reason to be displayed when type conversion fails. + * Yul: Support object access via ``datasize``, ``dataoffset`` and ``datacopy`` in standalone assembly mode. Bugfixes: diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index b4cc6656..70505fdb 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -25,8 +25,8 @@ eth_add_cxx_compiler_flag_if_supported(-Wimplicit-fallthrough) if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) - # Use ISO C++11 standard language. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + # Use ISO C++14 standard language. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") # Enables all the warnings about constructions that some users consider questionable, # and that are easy to avoid. Also enable some extra warning flags that are not diff --git a/cmake/jsoncpp.cmake b/cmake/jsoncpp.cmake index ea3218ef..318a526e 100644 --- a/cmake/jsoncpp.cmake +++ b/cmake/jsoncpp.cmake @@ -15,9 +15,9 @@ set(JSONCPP_INCLUDE_DIR "${prefix}/include") # versions used in the CI runs. if(EMSCRIPTEN) # Do not include all flags in CMAKE_CXX_FLAGS for emscripten, - # but only use -std=c++11. Using all flags causes build failures + # but only use -std=c++14. Using all flags causes build failures # at the moment. - set(JSONCPP_CXX_FLAGS -std=c++11) + set(JSONCPP_CXX_FLAGS -std=c++14) else() set(JSONCPP_CXX_FLAGS ${CMAKE_CXX_FLAGS}) endif() diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index f3c5b1f7..a4ef3684 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -9,35 +9,6 @@ This list was originally compiled by `fivedogit <mailto:fivedogit@gmail.com>`_. Basic Questions *************** -What is the transaction "payload"? -================================== - -This is just the bytecode "data" sent along with the request. - - -Create a contract that can be killed and return funds -===================================================== - -First, a word of warning: Killing contracts sounds like a good idea, because "cleaning up" -is always good, but as seen above, it does not really clean up. Furthermore, -if Ether is sent to removed contracts, the Ether will be forever lost. - -If you want to deactivate your contracts, it is preferable to **disable** them by changing some -internal state which causes all functions to throw. This will make it impossible -to use the contract and ether sent to the contract will be returned automatically. - -Now to answering the question: Inside a constructor, ``msg.sender`` is the -creator. Save it. Then ``selfdestruct(creator);`` to kill and return funds. - -`example <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/05_greeter.sol>`_ - -Note that if you ``import "mortal"`` at the top of your contracts and declare -``contract SomeContract is mortal { ...`` and compile with a compiler that already -has it (which includes `Remix <https://remix.ethereum.org/>`_), then -``kill()`` is taken care of for you. Once a contract is "mortal", then you can -``contractname.kill.sendTransaction({from:eth.coinbase})``, just the same as my -examples. - If I return an ``enum``, I only get integer values in web3.js. How to get the named values? =========================================================================================== diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index ad84b200..fa36fc6a 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -267,7 +267,7 @@ Single-line comments (``//``) and multi-line comments (``/*...*/``) are possible (these are NEL, LS and PS), it will lead to a parser error. Additionally, there is another type of comment called a natspec comment, -for which the documentation is not yet written. They are written with a +which is detailed in the :ref:`style guide<natspec>`. They are written with a triple slash (``///``) or a double asterisk block(``/** ... */``) and they should be used directly above function declarations or statements. You can use `Doxygen <https://en.wikipedia.org/wiki/Doxygen>`_-style tags inside these comments to document diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 017d5b81..5a6f3875 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -224,9 +224,9 @@ for displaying the current position in the source code inside a debugger or for breakpoint handling. Both kinds of source mappings use integer identifiers to refer to source files. -These are regular array indices into a list of source files usually called -``"sourceList"``, which is part of the combined-json and the output of -the json / npm compiler. +The identifier of a source file is stored in +``output['sources'][sourceName]['id']`` where ``output`` is the output of the +standard-json compiler interface parsed as JSON. .. note :: In the case of instructions that are not associated with any particular source file, diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 68fee871..bf1be93e 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -1093,3 +1093,64 @@ General Recommendations ======================= TODO + +.. _natspec: + +******* +NatSpec +******* + +Solidity contracts can have a form of comments that are the basis of the +Ethereum Natural Language Specification Format. + +Add comments above functions or contracts following `doxygen <http://www.doxygen.nl>`_ notation +of one or multiple lines starting with `///` or a +multiline comment starting with `/**` and ending with `*/`. + +For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments +added looks like the one below:: + + pragma solidity >=0.4.0 <0.6.0; + + /// @author The Solidity Team + /// @title A simple storage example + contract SimpleStorage { + uint storedData; + + /// Store `x`. + /// @param x the new value to store + /// @dev stores the number in the state variable `storedData` + function set(uint x) public { + storedData = x; + } + + /// Return the stored value. + /// @dev retrieves the value of the state variable `storedData` + /// @return the stored value + function get() public view returns (uint) { + return storedData; + } + } + +Natspec uses doxygen style tags with some special meaning. +If no tag is used, then the comment applies to ``@notice``. +The ``@notice`` tag is the main NatSpec tag and its audience is +users of the contract who have never seen the source code, so it should make +as little assumptions about the inner details as possible. +All tags are optional. + ++-------------+-------------------------------------------+-------------------------------+ +| Tag | Description | Context | ++=============+===========================================+===============================+ +| ``@title`` | A title that describes the contract | contract, interface | ++-------------+-------------------------------------------+-------------------------------+ +| ``@author`` | The name of the author | contract, interface, function | ++-------------+-------------------------------------------+-------------------------------+ +| ``@notice`` | Explanation of functionality | contract, interface, function | ++-------------+-------------------------------------------+-------------------------------+ +| ``@dev`` | Any extra details | contract, interface, function | ++-------------+-------------------------------------------+-------------------------------+ +| ``@param`` | Parameter type followed by parameter name | function | ++-------------+-------------------------------------------+-------------------------------+ +| ``@return`` | The return value of a contract's function | function | ++-------------+-------------------------------------------+-------------------------------+ diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 9ba6caa5..4749ef1f 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -200,15 +200,27 @@ Input Description "MyLib": "0x123123..." } } - // The following can be used to select desired outputs. - // If this field is omitted, then the compiler loads and does type checking, but will not generate any outputs apart from errors. - // The first level key is the file name and the second is the contract name, where empty contract name refers to the file itself, - // while the star refers to all of the contracts. + // The following can be used to select desired outputs based + // on file and contract names. + // If this field is omitted, then the compiler loads and does type checking, + // but will not generate any outputs apart from errors. + // The first level key is the file name and the second level key is the contract name. + // An empty contract name is used for outputs that are not tied to a contract + // but to the whole source file like the AST. + // A star as contract name refers to all contracts in the file. + // Similarly, a star as a file name matches all files. + // To select all outputs the compiler can possibly generate, use + // "outputSelection: { "*": { "*": [ "*" ], "": [ "*" ] } }" + // but note that this might slow down the compilation process needlessly. // // The available output types are as follows: - // abi - ABI + // + // File level (needs empty string as contract name): // ast - AST of all source files // legacyAST - legacy AST of all source files + // + // Contract level (needs the contract name or "*"): + // abi - ABI // devdoc - Developer documentation (natspec) // userdoc - User documentation (natspec) // metadata - Metadata @@ -281,7 +293,7 @@ Output Description // This contains the file-level outputs. In can be limited/filtered by the outputSelection settings. sources: { "sourceFile.sol": { - // Identifier (used in source maps) + // Identifier of the source (used in source maps) id: 1, // The AST object ast: {}, diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index fe58f0aa..a7e1ed97 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -17,6 +17,7 @@ #include <libsolidity/analysis/ControlFlowAnalyzer.h> #include <liblangutil/SourceLocation.h> +#include <boost/range/algorithm/sort.hpp> using namespace std; using namespace langutil; @@ -33,131 +34,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..859d826a 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.h +++ b/libsolidity/analysis/ControlFlowAnalyzer.h @@ -37,12 +37,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..a7cc9508 100644 --- a/libsolidity/analysis/ControlFlowGraph.cpp +++ b/libsolidity/analysis/ControlFlowGraph.cpp @@ -29,20 +29,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 +51,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..db8e1565 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,8 +96,8 @@ 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. */ @@ -85,19 +117,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 +124,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 +136,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 +145,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/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index ac88a052..76641c04 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -27,6 +27,7 @@ #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> @@ -321,7 +322,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) errorsIgnored, EVMVersion(), errorTypeForLoose, - yul::Dialect::looseAssemblyForEVM(), + yul::EVMDialect::looseAssemblyForEVM(), resolver ).analyze(_inlineAssembly.operations()); return false; diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 38391841..4d0f3461 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -63,21 +63,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/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d41415c0..5bd96f8d 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -27,6 +27,8 @@ #include <libyul/AsmAnalysisInfo.h> #include <libyul/AsmData.h> +#include <libyul/backends/evm/EVMDialect.h> + #include <liblangutil/ErrorReporter.h> #include <libdevcore/Algorithms.h> @@ -658,7 +660,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) m_errorReporter, m_evmVersion, Error::Type::SyntaxError, - yul::Dialect::looseAssemblyForEVM(), + yul::EVMDialect::looseAssemblyForEVM(), identifierAccess ); if (!analyzer.analyze(_inlineAssembly.operations())) diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 1112d682..bd3071b3 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -156,6 +156,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/codegen/AsmCodeGen.cpp b/libsolidity/codegen/AsmCodeGen.cpp index 3f770f62..c04c1c34 100644 --- a/libsolidity/codegen/AsmCodeGen.cpp +++ b/libsolidity/codegen/AsmCodeGen.cpp @@ -188,9 +188,9 @@ void CodeGenerator::assemble( assemblyAdapter, _analysisInfo, _parsedData, + *EVMDialect::strictAssemblyForEVM(), _optimize, false, - false, _identifierAccess, _useNamedLabelsForFunctions )(_parsedData); diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index dac09c2e..0b018e1e 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -31,6 +31,7 @@ #include <libyul/AsmAnalysis.h> #include <libyul/AsmAnalysisInfo.h> #include <libyul/YulString.h> +#include <libyul/backends/evm/EVMDialect.h> #include <liblangutil/ErrorReporter.h> #include <liblangutil/Scanner.h> @@ -361,7 +362,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::Dialect::strictAssemblyForEVM()).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 +374,7 @@ void CompilerContext::appendInlineAssembly( errorReporter, m_evmVersion, boost::none, - yul::Dialect::strictAssemblyForEVM(), + yul::EVMDialect::strictAssemblyForEVM(), identifierAccess.resolve ).analyze(*parserResult); if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 7d2ad9d2..259f1620 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -1205,7 +1205,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/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 17dc11ac..dc14f4c2 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -90,8 +90,7 @@ 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); @@ -268,16 +267,9 @@ 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 m_errorReporter.warning( @@ -288,7 +280,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." @@ -399,18 +395,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." @@ -442,8 +450,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); @@ -451,21 +459,6 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall) m_interface->addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); } -void SMTChecker::visitBlockHash(FunctionCall const& _funCall) -{ - 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); -} - void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) { FunctionDefinition const* _funDef = nullptr; @@ -533,29 +526,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.push_back(&_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( @@ -565,6 +561,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; @@ -616,7 +626,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 @@ -628,30 +638,39 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess) return true; } -void SMTChecker::defineSpecialVariable(string const& _name, Expression const& _expr, bool _increaseIndex) +void SMTChecker::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex) { - if (!knownSpecialVariable(_name)) + 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) @@ -831,10 +850,13 @@ void SMTChecker::checkCondition( 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); + if (smtKind(var.second->type()->category()) != smt::Kind::Function) + { + expressionsToEvaluate.emplace_back(var.second->currentValue()); + expressionNames.push_back(var.first); + } } for (auto const& uf: m_uninterpretedTerms) { @@ -1147,9 +1169,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) @@ -1172,6 +1194,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..9f8c04ab 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -84,16 +84,18 @@ private: 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 inlineFunctionCall(FunctionCall const& _funCall); + /// Creates an uninterpreted function call. + void abstractFunctionCall(FunctionCall const& _funCall); + void visitFunctionIdentifier(Identifier const& _identifier); - void defineSpecialVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); - void defineUninterpretedFunction(std::string const& _name, smt::SortPointer _sort); + void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); + void defineGlobalFunction(std::string const& _name, Expression const& _expr); /// Division expression in the given type. Requires special treatment because /// of rounding for signed division. @@ -176,8 +178,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); @@ -205,9 +207,7 @@ private: /// 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. /// Used to retrieve models. std::vector<Expression const*> m_uninterpretedTerms; diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index c297c807..3cfaa271 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -38,15 +38,25 @@ 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"); } + default: + // Abstract case. + return make_shared<smt::Sort>(smt::Kind::Int); } solAssert(false, "Invalid type"); } @@ -65,13 +75,21 @@ 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; + // Abstract case. + return smt::Kind::Int; } bool dev::solidity::isSupportedType(Type::Category _category) { return isNumber(_category) || - isBool(_category) || + isBool(_category); +} + +bool dev::solidity::isSupportedTypeDeclaration(Type::Category _category) +{ + return isSupportedType(_category) || isFunction(_category); } @@ -84,7 +102,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 +110,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())) @@ -122,6 +140,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; diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 984653b3..2c568f5b 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -34,10 +34,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); diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 5952d914..b97e00ae 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -32,6 +32,7 @@ #include <libyul/backends/evm/EVMObjectCompiler.h> #include <libyul/backends/evm/EVMCodeTransform.h> #include <libyul/backends/evm/EVMAssembly.h> +#include <libyul/backends/evm/EVMDialect.h> #include <libyul/ObjectParser.h> #include <libevmasm/Assembly.h> @@ -45,14 +46,14 @@ using namespace dev::solidity; namespace { -yul::Dialect languageToDialect(AssemblyStack::Language _language) +shared_ptr<yul::Dialect> languageToDialect(AssemblyStack::Language _language) { switch (_language) { case AssemblyStack::Language::Assembly: - return yul::Dialect::looseAssemblyForEVM(); + return yul::EVMDialect::looseAssemblyForEVM(); case AssemblyStack::Language::StrictAssembly: - return yul::Dialect::strictAssemblyForEVMObjects(); + return yul::EVMDialect::strictAssemblyForEVMObjects(); case AssemblyStack::Language::Yul: return yul::Dialect::yul(); } @@ -112,6 +113,22 @@ bool AssemblyStack::analyzeParsed(yul::Object& _object) 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, ""); @@ -136,7 +153,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) MachineAssemblyObject object; eth::Assembly assembly; EthAssemblyAdapter adapter(assembly); - yul::EVMObjectCompiler::compile(*m_parserResult, adapter, m_language == Language::Yul, false, _optimize); + compileEVM(adapter, false, _optimize); object.bytecode = make_shared<eth::LinkerObject>(assembly.assemble()); object.assembly = assembly.assemblyString(); return object; @@ -145,7 +162,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) { MachineAssemblyObject object; yul::EVMAssembly assembly(true); - yul::EVMObjectCompiler::compile(*m_parserResult, assembly, m_language == Language::Yul, true, _optimize); + 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 6cfefcd8..c8e3d35a 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -36,6 +36,10 @@ namespace langutil { class Scanner; } +namespace yul +{ +class AbstractAssembly; +} namespace dev { @@ -86,6 +90,8 @@ 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; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 0b4eca7d..26f13f93 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -24,6 +24,7 @@ #include <vector> #include <libsolidity/parsing/Parser.h> #include <libyul/AsmParser.h> +#include <libyul/backends/evm/EVMDialect.h> #include <liblangutil/SourceLocation.h> #include <liblangutil/ErrorReporter.h> #include <liblangutil/Scanner.h> @@ -1012,7 +1013,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); diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 1be1cf1a..821da005 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -101,7 +101,7 @@ bool AsmAnalyzer::operator()(Literal const& _literal) } else if (_literal.kind == LiteralKind::Boolean) { - solAssert(m_dialect.flavour == AsmFlavour::Yul, ""); + solAssert(m_dialect->flavour == AsmFlavour::Yul, ""); solAssert(_literal.value == YulString{string("true")} || _literal.value == YulString{string("false")}, ""); } m_info.stackHeightInfo[&_literal] = m_stackHeight; @@ -164,7 +164,7 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { - solAssert(m_dialect.flavour != AsmFlavour::Yul, ""); + solAssert(m_dialect->flavour != AsmFlavour::Yul, ""); bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) if (!expectExpression(arg)) @@ -182,9 +182,9 @@ bool AsmAnalyzer::operator()(ExpressionStatement const& _statement) { int initialStackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, _statement.expression); - if (m_stackHeight != initialStackHeight && (m_dialect.flavour != AsmFlavour::Loose || m_errorTypeForLoose)) + if (m_stackHeight != initialStackHeight && (m_dialect->flavour != AsmFlavour::Loose || m_errorTypeForLoose)) { - Error::Type errorType = m_dialect.flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; + Error::Type errorType = m_dialect->flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; string msg = "Top-level expressions are not supposed to return values (this expression returns " + to_string(m_stackHeight - initialStackHeight) + @@ -299,7 +299,7 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) bool success = true; size_t parameters = 0; size_t returns = 0; - if (BuiltinFunction const* f = m_dialect.builtins->query(_funCall.functionName.name)) + if (BuiltinFunction const* f = m_dialect->builtin(_funCall.functionName.name)) { // TODO: compare types, too parameters = f->parameters.size(); @@ -569,7 +569,7 @@ Scope& AsmAnalyzer::scope(Block const* _block) } void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) { - if (m_dialect.flavour != AsmFlavour::Yul) + if (m_dialect->flavour != AsmFlavour::Yul) return; if (!builtinTypes.count(type)) @@ -629,7 +629,7 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) { - if (m_dialect.flavour != AsmFlavour::Loose) + if (m_dialect->flavour != AsmFlavour::Loose) solAssert(m_errorTypeForLoose && *m_errorTypeForLoose != Error::Type::Warning, ""); m_errorReporter.error( @@ -644,7 +644,7 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio void AsmAnalyzer::checkLooseFeature(SourceLocation const& _location, string const& _description) { - if (m_dialect.flavour != AsmFlavour::Loose) + if (m_dialect->flavour != AsmFlavour::Loose) solAssert(false, _description); else if (m_errorTypeForLoose) m_errorReporter.error(*m_errorTypeForLoose, _location, _description); diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index ec2b8868..21cc1142 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -59,7 +59,7 @@ public: langutil::ErrorReporter& _errorReporter, dev::solidity::EVMVersion _evmVersion, boost::optional<langutil::Error::Type> _errorTypeForLoose, - Dialect _dialect = Dialect::looseAssemblyForEVM(), + std::shared_ptr<Dialect> _dialect, ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver() ): m_resolver(_resolver), @@ -115,7 +115,7 @@ private: AsmAnalysisInfo& m_info; langutil::ErrorReporter& m_errorReporter; dev::solidity::EVMVersion m_evmVersion; - Dialect m_dialect = Dialect::looseAssemblyForEVM(); + std::shared_ptr<Dialect> m_dialect; boost::optional<langutil::Error::Type> m_errorTypeForLoose; }; diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 33bb42f9..c7302063 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -107,14 +107,14 @@ Statement Parser::parseStatement() return parseForLoop(); case Token::Assign: { - if (m_dialect.flavour != AsmFlavour::Loose) + if (m_dialect->flavour != AsmFlavour::Loose) break; StackAssignment assignment = createWithLocation<StackAssignment>(); advance(); expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = YulString(currentLiteral()); - if (m_dialect.builtins->query(assignment.variableName.name)) + if (m_dialect->builtin(assignment.variableName.name)) fatalParserError("Identifier expected, got builtin symbol."); else if (instructions().count(assignment.variableName.name.str())) fatalParserError("Identifier expected, got instruction name."); @@ -176,9 +176,9 @@ Statement Parser::parseStatement() if (currentToken() == Token::Assign && peekNextToken() != Token::Colon) { Assignment assignment = createWithLocation<Assignment>(identifier.location); - if (m_dialect.builtins->query(identifier.name)) + if (m_dialect->builtin(identifier.name)) fatalParserError("Cannot assign to builtin function \"" + identifier.name.str() + "\"."); - else if (m_dialect.flavour != AsmFlavour::Yul && instructions().count(identifier.name.str())) + else if (m_dialect->flavour != AsmFlavour::Yul && instructions().count(identifier.name.str())) fatalParserError("Cannot use instruction names for identifier names."); advance(); assignment.variableNames.emplace_back(identifier); @@ -189,7 +189,7 @@ Statement Parser::parseStatement() else { // label - if (m_dialect.flavour != AsmFlavour::Loose) + if (m_dialect->flavour != AsmFlavour::Loose) fatalParserError("Labels are not supported."); Label label = createWithLocation<Label>(identifier.location); label.name = identifier.name; @@ -197,7 +197,7 @@ Statement Parser::parseStatement() } } default: - if (m_dialect.flavour != AsmFlavour::Loose) + if (m_dialect->flavour != AsmFlavour::Loose) fatalParserError("Call or assignment expected."); break; } @@ -273,7 +273,7 @@ Expression Parser::parseExpression() instructionNames().at(instr.instruction) + "\" not allowed in this context." ); - if (m_dialect.flavour != AsmFlavour::Loose && currentToken() != Token::LParen) + if (m_dialect->flavour != AsmFlavour::Loose && currentToken() != Token::LParen) fatalParserError( "Non-functional instructions are not allowed in this context." ); @@ -293,7 +293,7 @@ Expression Parser::parseExpression() else if (operation.type() == typeid(Instruction)) { // Instructions not taking arguments are allowed as expressions. - solAssert(m_dialect.flavour == AsmFlavour::Loose, ""); + solAssert(m_dialect->flavour == AsmFlavour::Loose, ""); Instruction& instr = boost::get<Instruction>(operation); return FunctionalInstruction{std::move(instr.location), instr.instruction, {}}; } @@ -362,9 +362,9 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() else literal = YulString{currentLiteral()}; // first search the set of builtins, then the instructions. - if (m_dialect.builtins->query(literal)) + if (m_dialect->builtin(literal)) ret = Identifier{location(), literal}; - else if (m_dialect.flavour != AsmFlavour::Yul && instructions().count(literal.str())) + else if (m_dialect->flavour != AsmFlavour::Yul && instructions().count(literal.str())) { dev::solidity::Instruction const& instr = instructions().at(literal.str()); ret = Instruction{location(), instr}; @@ -405,7 +405,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() {} }; advance(); - if (m_dialect.flavour == AsmFlavour::Yul) + if (m_dialect->flavour == AsmFlavour::Yul) { expectToken(Token::Colon); literal.location.end = endPosition(); @@ -418,7 +418,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() } default: fatalParserError( - m_dialect.flavour == AsmFlavour::Yul ? + m_dialect->flavour == AsmFlavour::Yul ? "Literal or identifier expected." : "Literal, identifier or instruction expected." ); @@ -488,7 +488,7 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) RecursionGuard recursionGuard(*this); if (_initialOp.type() == typeid(Instruction)) { - solAssert(m_dialect.flavour != AsmFlavour::Yul, "Instructions are invalid in Yul"); + solAssert(m_dialect->flavour != AsmFlavour::Yul, "Instructions are invalid in Yul"); Instruction& instruction = boost::get<Instruction>(_initialOp); FunctionalInstruction ret; ret.instruction = instruction.instruction; @@ -559,7 +559,7 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) } else fatalParserError( - m_dialect.flavour == AsmFlavour::Yul ? + m_dialect->flavour == AsmFlavour::Yul ? "Function name expected." : "Assembly instruction or function name required in front of \"(\")" ); @@ -572,7 +572,7 @@ TypedName Parser::parseTypedName() RecursionGuard recursionGuard(*this); TypedName typedName = createWithLocation<TypedName>(); typedName.name = expectAsmIdentifier(); - if (m_dialect.flavour == AsmFlavour::Yul) + if (m_dialect->flavour == AsmFlavour::Yul) { expectToken(Token::Colon); typedName.location.end = endPosition(); @@ -584,7 +584,7 @@ TypedName Parser::parseTypedName() YulString Parser::expectAsmIdentifier() { YulString name = YulString{currentLiteral()}; - if (m_dialect.flavour == AsmFlavour::Yul) + if (m_dialect->flavour == AsmFlavour::Yul) { switch (currentToken()) { @@ -598,7 +598,7 @@ YulString Parser::expectAsmIdentifier() break; } } - else if (m_dialect.builtins->query(name)) + else if (m_dialect->builtin(name)) fatalParserError("Cannot use builtin function name \"" + name.str() + "\" as identifier name."); else if (instructions().count(name.str())) fatalParserError("Cannot use instruction names for identifier names."); diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h index b40a717c..a02f0b4d 100644 --- a/libyul/AsmParser.h +++ b/libyul/AsmParser.h @@ -38,7 +38,7 @@ namespace yul class Parser: public langutil::ParserBase { public: - explicit Parser(langutil::ErrorReporter& _errorReporter, Dialect _dialect = Dialect::looseAssemblyForEVM()): + explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr<Dialect> _dialect): ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {} /// Parses an inline assembly block starting with `{` and ending with `}`. @@ -86,7 +86,7 @@ protected: static bool isValidNumberLiteral(std::string const& _literal); private: - Dialect m_dialect = Dialect::looseAssemblyForEVM(); + std::shared_ptr<Dialect> m_dialect; }; } diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 7d1ec6ba..74a5703c 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(yul ObjectParser.cpp backends/evm/EVMAssembly.cpp backends/evm/EVMCodeTransform.cpp + backends/evm/EVMDialect.cpp backends/evm/EVMObjectCompiler.cpp optimiser/ASTCopier.cpp optimiser/ASTWalker.cpp diff --git a/libyul/Dialect.cpp b/libyul/Dialect.cpp index 99718787..f6985c17 100644 --- a/libyul/Dialect.cpp +++ b/libyul/Dialect.cpp @@ -20,57 +20,10 @@ #include <libyul/Dialect.h> +#include <libyul/Object.h> +#include <libyul/backends/evm/AbstractAssembly.h> + #include <map> using namespace yul; using namespace std; - -namespace -{ - -void addFunction( - map<YulString, BuiltinFunction>& _repository, - string const& _name, - size_t _params, - size_t _returns, - bool _movable -) -{ - _repository[YulString{_name}] = BuiltinFunction{ - YulString{_name}, - vector<Type>(_params), - vector<Type>(_returns), - _movable - }; -} - -class GenericBuiltins: public Builtins -{ -public: - GenericBuiltins(map<YulString, BuiltinFunction> const& _functions): m_functions(_functions) {} - BuiltinFunction const* query(YulString _name) const - { - auto it = m_functions.find(_name); - if (it != end(m_functions)) - return &it->second; - else - return nullptr; - } -private: - map<YulString, BuiltinFunction> const& m_functions; -}; - -} - -Dialect Dialect::strictAssemblyForEVMObjects() -{ - static map<YulString, BuiltinFunction> functions; - if (functions.empty()) - { - addFunction(functions, "datasize", 1, 1, true); - addFunction(functions, "dataoffset", 1, 1, true); - addFunction(functions, "datacopy", 3, 0, false); - } - // The EVM instructions will be moved to builtins at some point. - return Dialect{AsmFlavour::Strict, std::make_shared<GenericBuiltins>(functions)}; -} diff --git a/libyul/Dialect.h b/libyul/Dialect.h index b78f1aaf..2def566c 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -22,7 +22,9 @@ #include <libyul/YulString.h> -#include <memory> +#include <boost/noncopyable.hpp> + +#include <vector> namespace yul { @@ -45,38 +47,19 @@ struct BuiltinFunction bool movable; }; -/** - * Class to query for builtin functions and their semantics. - */ -struct Builtins +struct Dialect: boost::noncopyable { - virtual ~Builtins() = default; + AsmFlavour const flavour = AsmFlavour::Loose; /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. - virtual BuiltinFunction const* query(YulString /*_name*/) const { return nullptr; } -}; + virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; } -struct Dialect -{ - AsmFlavour flavour = AsmFlavour::Loose; - std::shared_ptr<Builtins> builtins; + Dialect(AsmFlavour _flavour): flavour(_flavour) {} + virtual ~Dialect() {} - Dialect(AsmFlavour _flavour, std::shared_ptr<Builtins> _builtins): - flavour(_flavour), builtins(std::move(_builtins)) - {} - static Dialect looseAssemblyForEVM() - { - return Dialect{AsmFlavour::Loose, std::make_shared<Builtins>()}; - } - static Dialect strictAssemblyForEVM() - { - // The EVM instructions will be moved to builtins at some point. - return Dialect{AsmFlavour::Strict, std::make_shared<Builtins>()}; - } - static Dialect strictAssemblyForEVMObjects(); - static Dialect yul() + static std::shared_ptr<Dialect> yul() { // Will have to add builtins later. - return Dialect{AsmFlavour::Yul, std::make_shared<Builtins>()}; + return std::make_shared<Dialect>(AsmFlavour::Yul); } }; diff --git a/libyul/ObjectParser.h b/libyul/ObjectParser.h index 59efb8ab..e39f24cd 100644 --- a/libyul/ObjectParser.h +++ b/libyul/ObjectParser.h @@ -47,7 +47,7 @@ class ObjectParser: public langutil::ParserBase public: explicit ObjectParser( langutil::ErrorReporter& _errorReporter, - Dialect _dialect = Dialect::looseAssemblyForEVM() + std::shared_ptr<Dialect> _dialect ): ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {} @@ -67,7 +67,7 @@ private: YulString parseUniqueName(Object const* _containingObject); void addNamedSubObject(Object& _container, YulString _name, std::shared_ptr<ObjectNode> _subObject); - Dialect m_dialect; + std::shared_ptr<Dialect> m_dialect; }; } diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 9d8e9a06..bd18985c 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -97,7 +97,7 @@ CodeTransform::CodeTransform( AsmAnalysisInfo& _analysisInfo, Block const& _block, bool _allowStackOpt, - bool _yul, + EVMDialect const& _dialect, bool _evm15, ExternalIdentifierAccess const& _identifierAccess, bool _useNamedLabelsForFunctions, @@ -106,8 +106,8 @@ CodeTransform::CodeTransform( ): m_assembly(_assembly), m_info(_analysisInfo), + m_dialect(_dialect), m_allowStackOpt(_allowStackOpt), - m_yul(_yul), m_evm15(_evm15), m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions), m_identifierAccess(_identifierAccess), @@ -267,35 +267,46 @@ void CodeTransform::operator()(FunctionCall const& _call) { solAssert(m_scope, ""); - m_assembly.setSourceLocation(_call.location); - EVMAssembly::LabelID returnLabel(-1); // only used for evm 1.0 - if (!m_evm15) + if (BuiltinFunctionForEVM const* builtin = m_dialect.builtin(_call.functionName.name)) { - returnLabel = m_assembly.newLabelId(); - m_assembly.appendLabelReference(returnLabel); - m_stackAdjustment++; + builtin->generateCode(_call, m_assembly, [&]() { + for (auto const& arg: _call.arguments | boost::adaptors::reversed) + visitExpression(arg); + m_assembly.setSourceLocation(_call.location); + }); } - - Scope::Function* function = nullptr; - solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor( - [=](Scope::Variable&) { solAssert(false, "Expected function name."); }, - [=](Scope::Label&) { solAssert(false, "Expected function name."); }, - [&](Scope::Function& _function) { function = &_function; } - )), "Function name not found."); - solAssert(function, ""); - solAssert(function->arguments.size() == _call.arguments.size(), ""); - for (auto const& arg: _call.arguments | boost::adaptors::reversed) - visitExpression(arg); - m_assembly.setSourceLocation(_call.location); - if (m_evm15) - m_assembly.appendJumpsub(functionEntryID(_call.functionName.name, *function), function->arguments.size(), function->returns.size()); else { - m_assembly.appendJumpTo(functionEntryID(_call.functionName.name, *function), function->returns.size() - function->arguments.size() - 1); - m_assembly.appendLabel(returnLabel); - m_stackAdjustment--; + m_assembly.setSourceLocation(_call.location); + EVMAssembly::LabelID returnLabel(-1); // only used for evm 1.0 + if (!m_evm15) + { + returnLabel = m_assembly.newLabelId(); + m_assembly.appendLabelReference(returnLabel); + m_stackAdjustment++; + } + + Scope::Function* function = nullptr; + solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor( + [=](Scope::Variable&) { solAssert(false, "Expected function name."); }, + [=](Scope::Label&) { solAssert(false, "Expected function name."); }, + [&](Scope::Function& _function) { function = &_function; } + )), "Function name not found."); + solAssert(function, ""); + solAssert(function->arguments.size() == _call.arguments.size(), ""); + for (auto const& arg: _call.arguments | boost::adaptors::reversed) + visitExpression(arg); + m_assembly.setSourceLocation(_call.location); + if (m_evm15) + m_assembly.appendJumpsub(functionEntryID(_call.functionName.name, *function), function->arguments.size(), function->returns.size()); + else + { + m_assembly.appendJumpTo(functionEntryID(_call.functionName.name, *function), function->returns.size() - function->arguments.size() - 1); + m_assembly.appendLabel(returnLabel); + m_stackAdjustment--; + } + checkStackHeight(&_call); } - checkStackHeight(&_call); } void CodeTransform::operator()(FunctionalInstruction const& _instruction) @@ -506,7 +517,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) m_info, _function.body, m_allowStackOpt, - m_yul, + m_dialect, m_evm15, m_identifierAccess, m_useNamedLabelsForFunctions, diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index 8927e999..28ef4e45 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -20,9 +20,9 @@ #include <libyul/backends/evm/EVMAssembly.h> +#include <libyul/backends/evm/EVMDialect.h> #include <libyul/optimiser/ASTWalker.h> #include <libyul/AsmDataForward.h> - #include <libyul/AsmScope.h> #include <boost/variant.hpp> @@ -87,8 +87,8 @@ public: AbstractAssembly& _assembly, AsmAnalysisInfo& _analysisInfo, Block const& _block, + EVMDialect const& _dialect, bool _allowStackOpt = false, - bool _yul = false, bool _evm15 = false, ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess(), bool _useNamedLabelsForFunctions = false @@ -97,7 +97,7 @@ public: _analysisInfo, _block, _allowStackOpt, - _yul, + _dialect, _evm15, _identifierAccess, _useNamedLabelsForFunctions, @@ -115,7 +115,7 @@ protected: AsmAnalysisInfo& _analysisInfo, Block const& _block, bool _allowStackOpt, - bool _yul, + EVMDialect const& _dialect, bool _evm15, ExternalIdentifierAccess const& _identifierAccess, bool _useNamedLabelsForFunctions, @@ -179,10 +179,10 @@ private: AbstractAssembly& m_assembly; AsmAnalysisInfo& m_info; Scope* m_scope = nullptr; + EVMDialect const& m_dialect; bool const m_allowStackOpt = true; - bool m_yul = false; - bool m_evm15 = false; - bool m_useNamedLabelsForFunctions = false; + bool const m_evm15 = false; + bool const m_useNamedLabelsForFunctions = false; ExternalIdentifierAccess m_identifierAccess; /// Adjustment between the stack height as determined during the analysis phase /// and the stack height in the assembly. This is caused by an initial stack being present diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp new file mode 100644 index 00000000..33ee19d4 --- /dev/null +++ b/libyul/backends/evm/EVMDialect.cpp @@ -0,0 +1,141 @@ +/* + 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/>. +*/ +/** + * Yul dialects for EVM. + */ + +#include <libyul/backends/evm/EVMDialect.h> + +#include <libyul/AsmAnalysisInfo.h> +#include <libyul/AsmData.h> +#include <libyul/Object.h> +#include <libyul/backends/evm/AbstractAssembly.h> + +#include <liblangutil/Exceptions.h> + +#include <libyul/Exceptions.h> + +#include <boost/range/adaptor/reversed.hpp> + +using namespace std; +using namespace dev; +using namespace yul; +using namespace dev::solidity; + + +EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess): + Dialect(_flavour), m_objectAccess(_objectAccess) +{ + // The EVM instructions will be moved to builtins at some point. + if (!m_objectAccess) + return; + + addFunction("datasize", 1, 1, true, [this]( + FunctionCall const& _call, + AbstractAssembly& _assembly, + std::function<void()> + ) { + yulAssert(m_currentObject, "No object available."); + yulAssert(_call.arguments.size() == 1, ""); + Expression const& arg = _call.arguments.front(); + YulString dataName = boost::get<Literal>(arg).value; + if (m_currentObject->name == dataName) + _assembly.appendAssemblySize(); + else + _assembly.appendDataSize(m_subIDs.at(dataName)); + }); + addFunction("dataoffset", 1, 1, true, [this]( + FunctionCall const& _call, + AbstractAssembly& _assembly, + std::function<void()> + ) { + yulAssert(m_currentObject, "No object available."); + yulAssert(_call.arguments.size() == 1, ""); + Expression const& arg = _call.arguments.front(); + YulString dataName = boost::get<Literal>(arg).value; + if (m_currentObject->name == dataName) + _assembly.appendConstant(0); + else + _assembly.appendDataOffset(m_subIDs.at(dataName)); + }); + addFunction("datacopy", 3, 0, false, []( + FunctionCall const&, + AbstractAssembly& _assembly, + std::function<void()> _visitArguments + ) { + _visitArguments(); + _assembly.appendInstruction(solidity::Instruction::CODECOPY); + }); +} + +BuiltinFunctionForEVM const* EVMDialect::builtin(YulString _name) const +{ + auto it = m_functions.find(_name); + if (it != m_functions.end()) + return &it->second; + else + return nullptr; +} + +shared_ptr<EVMDialect> EVMDialect::looseAssemblyForEVM() +{ + return make_shared<EVMDialect>(AsmFlavour::Loose, false); +} + +shared_ptr<EVMDialect> EVMDialect::strictAssemblyForEVM() +{ + return make_shared<EVMDialect>(AsmFlavour::Strict, false); +} + +shared_ptr<EVMDialect> EVMDialect::strictAssemblyForEVMObjects() +{ + return make_shared<EVMDialect>(AsmFlavour::Strict, true); +} + +shared_ptr<yul::EVMDialect> EVMDialect::yulForEVM() +{ + return make_shared<EVMDialect>(AsmFlavour::Yul, false); +} + +void EVMDialect::setSubIDs(map<YulString, AbstractAssembly::SubID> _subIDs) +{ + yulAssert(m_objectAccess, "Sub IDs set with dialect that does not support object access."); + m_subIDs = std::move(_subIDs); +} + +void EVMDialect::setCurrentObject(Object const* _object) +{ + yulAssert(m_objectAccess, "Current object set with dialect that does not support object access."); + m_currentObject = _object; +} + +void EVMDialect::addFunction( + string _name, + size_t _params, + size_t _returns, + bool _movable, + std::function<void(FunctionCall const&, AbstractAssembly&, std::function<void()>)> _generateCode +) +{ + YulString name{std::move(_name)}; + BuiltinFunctionForEVM& f = m_functions[name]; + f.name = name; + f.parameters.resize(_params); + f.returns.resize(_returns); + f.movable = _movable; + f.generateCode = std::move(_generateCode); +} diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h new file mode 100644 index 00000000..feb00b03 --- /dev/null +++ b/libyul/backends/evm/EVMDialect.h @@ -0,0 +1,85 @@ +/* + 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/>. +*/ +/** + * Yul dialects for EVM. + */ + +#pragma once + +#include <libyul/Dialect.h> + +#include <libyul/backends/evm/AbstractAssembly.h> + +#include <map> + +namespace yul +{ + +class YulString; +using Type = YulString; +struct FunctionCall; +struct Object; + +struct BuiltinFunctionForEVM: BuiltinFunction +{ + /// Function to generate code for the given function call and append it to the abstract + /// assembly. The third parameter is called to visit (and generate code for) the arguments + /// from right to left. + std::function<void(FunctionCall const&, AbstractAssembly&, std::function<void()>)> generateCode; +}; + +/** + * Yul dialect for EVM as a backend. + * The main difference is that the builtin functions take an AbstractAssembly for the + * code generation. + */ +struct EVMDialect: public Dialect +{ + EVMDialect(AsmFlavour _flavour, bool _objectAccess); + + /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. + BuiltinFunctionForEVM const* builtin(YulString _name) const override; + + static std::shared_ptr<EVMDialect> looseAssemblyForEVM(); + static std::shared_ptr<EVMDialect> strictAssemblyForEVM(); + static std::shared_ptr<EVMDialect> strictAssemblyForEVMObjects(); + static std::shared_ptr<EVMDialect> yulForEVM(); + + bool providesObjectAccess() const { return m_objectAccess; } + + /// Sets the mapping of current sub assembly IDs. Used during code generation. + void setSubIDs(std::map<YulString, AbstractAssembly::SubID> _subIDs); + /// Sets the current object. Used during code generation. + void setCurrentObject(Object const* _object); + +private: + void addFunction( + std::string _name, + size_t _params, + size_t _returns, + bool _movable, + std::function<void(FunctionCall const&, AbstractAssembly&, std::function<void()>)> _generateCode + ); + + bool m_objectAccess; + Object const* m_currentObject = nullptr; + /// Mapping from named objects to abstract assembly sub IDs. + std::map<YulString, AbstractAssembly::SubID> m_subIDs; + std::map<YulString, BuiltinFunctionForEVM> m_functions; +}; + +} diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp index 13d4b756..3f7634b2 100644 --- a/libyul/backends/evm/EVMObjectCompiler.cpp +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -21,15 +21,17 @@ #include <libyul/backends/evm/EVMObjectCompiler.h> #include <libyul/backends/evm/EVMCodeTransform.h> +#include <libyul/backends/evm/EVMDialect.h> + #include <libyul/Object.h> #include <libyul/Exceptions.h> using namespace yul; using namespace std; -void EVMObjectCompiler::compile(Object& _object, AbstractAssembly& _assembly, bool _yul, bool _evm15, bool _optimize) +void EVMObjectCompiler::compile(Object& _object, AbstractAssembly& _assembly, EVMDialect& _dialect, bool _evm15, bool _optimize) { - EVMObjectCompiler compiler(_assembly, _yul, _evm15); + EVMObjectCompiler compiler(_assembly, _dialect, _evm15); compiler.run(_object, _optimize); } @@ -42,7 +44,7 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) { auto subAssemblyAndID = m_assembly.createSubAssembly(); subIDs[subObject->name] = subAssemblyAndID.second; - compile(*subObject, *subAssemblyAndID.first, m_yul, m_evm15, _optimize); + compile(*subObject, *subAssemblyAndID.first, m_dialect, m_evm15, _optimize); } else { @@ -50,7 +52,13 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) subIDs[data.name] = m_assembly.appendData(data.data); } + if (m_dialect.providesObjectAccess()) + { + m_dialect.setSubIDs(std::move(subIDs)); + m_dialect.setCurrentObject(&_object); + } + yulAssert(_object.analysisInfo, "No analysis info."); yulAssert(_object.code, "No code."); - CodeTransform{m_assembly, *_object.analysisInfo, *_object.code, _optimize, m_yul, m_evm15}(*_object.code); + CodeTransform{m_assembly, *_object.analysisInfo, *_object.code, m_dialect, _optimize, m_evm15}(*_object.code); } diff --git a/libyul/backends/evm/EVMObjectCompiler.h b/libyul/backends/evm/EVMObjectCompiler.h index 826b82a4..057521a8 100644 --- a/libyul/backends/evm/EVMObjectCompiler.h +++ b/libyul/backends/evm/EVMObjectCompiler.h @@ -23,20 +23,21 @@ namespace yul { struct Object; class AbstractAssembly; +struct EVMDialect; class EVMObjectCompiler { public: - static void compile(Object& _object, AbstractAssembly& _assembly, bool _yul, bool _evm15, bool _optimize); + static void compile(Object& _object, AbstractAssembly& _assembly, EVMDialect& _dialect, bool _evm15, bool _optimize); private: - EVMObjectCompiler(AbstractAssembly& _assembly, bool _yul, bool _evm15): - m_assembly(_assembly), m_yul(_yul), m_evm15(_evm15) + EVMObjectCompiler(AbstractAssembly& _assembly, EVMDialect& _dialect, bool _evm15): + m_assembly(_assembly), m_dialect(_dialect), m_evm15(_evm15) {} void run(Object& _object, bool _optimize); AbstractAssembly& m_assembly; - bool m_yul = false; + EVMDialect& m_dialect; bool m_evm15 = false; }; diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index 5fcfdae4..bdf4cb2a 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -55,7 +55,7 @@ void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements) if (expressionAlwaysTrue(*_ifStmt.condition)) return {std::move(_ifStmt.body.statements)}; else if (expressionAlwaysFalse(*_ifStmt.condition)) - return {{}}; + return {vector<Statement>{}}; return {}; }, [](Switch& _switchStmt) -> OptionalStatements { diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 36a8ef7f..e842bd53 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -114,7 +114,7 @@ Priority: extra Maintainer: Christian (Buildserver key) <builds@ethereum.org> Build-Depends: ${Z3DEPENDENCY}debhelper (>= 9.0.0), cmake, - g++-4.8, + g++-8, git, libgmp-dev, libboost-all-dev, @@ -168,7 +168,7 @@ override_dh_shlibdeps: dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info override_dh_auto_configure: - dh_auto_configure -- -DINSTALL_LLLC=Off + dh_auto_configure -- -DINSTALL_LLLC=Off -DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8 EOF cat <<EOF > debian/copyright Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 034aaef3..d6e75cb9 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -160,6 +160,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) "LLLEndToEndTest", #endif "GasMeterTests", + "GasCostTests", "SolidityEndToEndTest", "SolidityOptimizer" }) diff --git a/test/cmdlineTests/data_storage.sol b/test/cmdlineTests/data_storage.sol new file mode 100644 index 00000000..cc602cc9 --- /dev/null +++ b/test/cmdlineTests/data_storage.sol @@ -0,0 +1,15 @@ +pragma solidity >=0.0; + +contract C { + function f() pure public { + require(false, "1234567890123456789012345678901"); + require(false, "12345678901234567890123456789012"); + require(false, "123456789012345678901234567890123"); + require(false, "1234567890123456789012345678901234"); + require(false, "12345678901234567890123456789012345"); + require(false, "123456789012345678901234567890123456"); + require(false, "123456789012345678901234567890121234567890123456789012345678901"); + require(false, "1234567890123456789012345678901212345678901234567890123456789012"); + require(false, "12345678901234567890123456789012123456789012345678901234567890123"); + } +} diff --git a/test/cmdlineTests/data_storage.sol.args b/test/cmdlineTests/data_storage.sol.args new file mode 100644 index 00000000..3684987e --- /dev/null +++ b/test/cmdlineTests/data_storage.sol.args @@ -0,0 +1 @@ +--gas diff --git a/test/cmdlineTests/data_storage.sol.stdout b/test/cmdlineTests/data_storage.sol.stdout new file mode 100644 index 00000000..4a5250f7 --- /dev/null +++ b/test/cmdlineTests/data_storage.sol.stdout @@ -0,0 +1,7 @@ + +======= data_storage.sol:C ======= +Gas estimation: +construction: + 306 + 264400 = 264706 +external: + f(): 263 diff --git a/test/libsolidity/GasCosts.cpp b/test/libsolidity/GasCosts.cpp new file mode 100644 index 00000000..15658a91 --- /dev/null +++ b/test/libsolidity/GasCosts.cpp @@ -0,0 +1,89 @@ +/* + 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/>. +*/ +/** + * Tests that check that the cost of certain operations stay within range. + */ + +#include <test/libsolidity/SolidityExecutionFramework.h> + +#include <cmath> + +using namespace std; +using namespace langutil; +using namespace dev::eth; +using namespace dev::solidity; +using namespace dev::test; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +#define CHECK_GAS(_gasNoOpt, _gasOpt, _tolerance) \ + do \ + { \ + u256 gasOpt{_gasOpt}; \ + u256 gasNoOpt{_gasNoOpt}; \ + u256 tolerance{_tolerance}; \ + u256 gas = m_optimize ? gasOpt : gasNoOpt; \ + u256 diff = gas < m_gasUsed ? m_gasUsed - gas : gas - m_gasUsed; \ + BOOST_CHECK_MESSAGE( \ + diff <= tolerance, \ + "Gas used: " + \ + m_gasUsed.str() + \ + " - expected: " + \ + gas.str() + \ + " (tolerance: " + \ + tolerance.str() + \ + ")" \ + ); \ + } while(0) + +BOOST_FIXTURE_TEST_SUITE(GasCostTests, SolidityExecutionFramework) + +BOOST_AUTO_TEST_CASE(string_storage) +{ + char const* sourceCode = R"( + contract C { + function f() pure public { + require(false, "Not Authorized. This function can only be called by the custodian or owner of this contract"); + } + } + )"; + compileAndRun(sourceCode); + + if (Options::get().evmVersion() <= EVMVersion::byzantium()) + CHECK_GAS(134435, 130591, 100); + else + CHECK_GAS(127225, 124873, 100); + if (Options::get().evmVersion() >= EVMVersion::byzantium()) + { + callContractFunction("f()"); + if (Options::get().evmVersion() == EVMVersion::byzantium()) + CHECK_GAS(21551, 21526, 20); + else + CHECK_GAS(21546, 21526, 20); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 28b6b7b5..8d219d16 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -12246,7 +12246,7 @@ BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once) compileAndRun(sourceCode); BOOST_CHECK_LE( double(m_compiler.object("Double").bytecode.size()), - 1.1 * double(m_compiler.object("Single").bytecode.size()) + 1.2 * double(m_compiler.object("Single").bytecode.size()) ); } diff --git a/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple.sol b/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple.sol index 3793f411..2ecff63e 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple.sol @@ -12,3 +12,4 @@ contract C } // ---- +// Warning: (153-156): Assertion checker does not yet implement tuples and inline arrays. diff --git a/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple_fail.sol b/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple_fail.sol index e3c80594..a9bde9e4 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple_fail.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_identity_as_tuple_fail.sol @@ -12,4 +12,5 @@ contract C } // ---- +// Warning: (153-156): Assertion checker does not yet implement tuples and inline arrays. // Warning: (163-176): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/special/msg_data.sol b/test/libsolidity/smtCheckerTests/special/msg_data.sol index 7e748f09..b667f971 100644 --- a/test/libsolidity/smtCheckerTests/special/msg_data.sol +++ b/test/libsolidity/smtCheckerTests/special/msg_data.sol @@ -8,7 +8,6 @@ contract C } // ---- // Warning: (86-101): Assertion checker does not yet support this expression. -// Warning: (86-94): Assertion checker does not yet support this special variable. -// Warning: (86-94): Assertion checker does not yet implement this type. +// Warning: (86-94): Assertion checker does not yet support this global variable. // Warning: (86-101): Internal error: Expression undefined for SMT solver. // Warning: (79-106): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/bytes_1.sol b/test/libsolidity/smtCheckerTests/types/bytes_1.sol new file mode 100644 index 00000000..fb957421 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/bytes_1.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(bytes memory b) public pure returns (bytes memory) { + bytes memory c = b; + return b; + } +} +// ---- +// Warning: (113-127): Unused local variable. +// Warning: (113-127): Assertion checker does not yet support the type of this variable. +// Warning: (58-72): Assertion checker does not yet support the type of this variable. +// Warning: (95-107): Assertion checker does not yet support the type of this variable. +// Warning: (130-131): Internal error: Expression undefined for SMT solver. +// Warning: (130-131): Assertion checker does not yet implement this type. diff --git a/test/libsolidity/smtCheckerTestsJSON/multi.json b/test/libsolidity/smtCheckerTestsJSON/multi.json index 2ed5150d..df45fbf4 100644 --- a/test/libsolidity/smtCheckerTestsJSON/multi.json +++ b/test/libsolidity/smtCheckerTestsJSON/multi.json @@ -1,11 +1,11 @@ { - "auxiliaryInput": - { - "smtlib2responses": - { - "0x0426cd198d1e7123a28ffac2b759a666b86508ad046babf5166500dd6d8ed308": "unsat\n(error \"line 31 column 26: model is not available\")", - "0xa51ca41ae407f5a727f27101cbc079834743cc8955f9f585582034ca634953f6": "sat\n((|EVALEXPR_0| 1))", - "0xe9477f683ff20aa57fcb08682150f86c5917e1d4c0686b278ab9b73446d0682c": "sat\n((|EVALEXPR_0| 0))" - } - } + "auxiliaryInput": + { + "smtlib2responses": + { + "0x47826ec8b83cfec30171b34f944aed6c33ecd12f02b9ad522ca45317c9bd5f45": "sat\n((|EVALEXPR_0| 1))", + "0x734c058efe9d6ec224de3cf2bdfa6c936a1c9c159c040c128d631145b2fed195": "unsat\n", + "0xa0c072acdbe5181dd56cbad8960cb5bb0b9e97fd598cfd895467bd73bbcca028": "sat\n((|EVALEXPR_0| 0))" + } + } } diff --git a/test/libsolidity/smtCheckerTestsJSON/simple.json b/test/libsolidity/smtCheckerTestsJSON/simple.json index fd976b63..b82d93b8 100644 --- a/test/libsolidity/smtCheckerTestsJSON/simple.json +++ b/test/libsolidity/smtCheckerTestsJSON/simple.json @@ -1,9 +1,9 @@ { - "auxiliaryInput": - { - "smtlib2responses": - { - "0xe9477f683ff20aa57fcb08682150f86c5917e1d4c0686b278ab9b73446d0682c": "sat\n((|EVALEXPR_0| 0))" - } - } + "auxiliaryInput": + { + "smtlib2responses": + { + "0xa0c072acdbe5181dd56cbad8960cb5bb0b9e97fd598cfd895467bd73bbcca028": "sat\n((|EVALEXPR_0| 0))\n" + } + } } diff --git a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol index 35420b6d..dbd3db08 100644 --- a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol @@ -2,4 +2,4 @@ contract C { function f() internal pure returns (mapping(uint=>uint) storage r) { } } // ---- -// TypeError: (53-82): 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. +// TypeError: (53-82): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol index 52a8b3d7..476e799b 100644 --- a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol @@ -2,4 +2,4 @@ contract C { function f() internal pure returns (mapping(uint=>uint) storage) {} } // ---- -// TypeError: (53-80): 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. +// TypeError: (53-80): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol index cad9b8e8..a6df889d 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// TypeError: (87-96): 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. +// TypeError: (87-96): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_fine.sol deleted file mode 100644 index 0d3db856..00000000 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_fine.sol +++ /dev/null @@ -1,26 +0,0 @@ -contract C { - struct S { bool f; } - S s; - function f() internal returns (S storage c) { - assembly { - sstore(c_slot, sload(s_slot)) - } - } - function g(bool flag) internal returns (S storage c) { - // control flow in assembly will not be analyzed for now, - // so this will not issue an error - assembly { - if flag { - sstore(c_slot, sload(s_slot)) - } - } - } - function h() internal returns (S storage c) { - // any reference from assembly will be sufficient for now, - // so this will not issue an error - assembly { - sstore(s_slot, sload(c_slot)) - } - } -} -// ---- diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol index eb574c96..b868d61d 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol @@ -45,8 +45,8 @@ contract C { } } // ---- -// TypeError: (87-98): 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. -// TypeError: (223-234): 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. -// TypeError: (440-451): 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. -// TypeError: (654-665): 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. -// TypeError: (871-882): 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. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (223-234): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (440-451): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (654-665): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (871-882): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol index 9aa580a4..d2dec9c1 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol @@ -12,5 +12,5 @@ contract C { } } // ---- -// TypeError: (87-98): 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. -// TypeError: (182-193): 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. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (182-193): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol index f3e55318..12ec31e4 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol @@ -14,5 +14,5 @@ contract C { } } // ---- -// TypeError: (96-107): 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. -// TypeError: (186-197): 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. +// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (186-197): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol index 42342979..ee1e08fd 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol @@ -18,5 +18,5 @@ contract C { } } // ---- -// TypeError: (249-258): 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. -// TypeError: (367-376): 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. +// TypeError: (249-258): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (367-376): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol index d0ad8245..e3579628 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol @@ -13,6 +13,6 @@ contract C { } } // ---- -// TypeError: (87-98): 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. -// TypeError: (176-187): 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. -// TypeError: (264-275): 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. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (176-187): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (264-275): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol index 6d10287b..d5ad73c5 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol @@ -9,5 +9,5 @@ contract C { } } // ---- -// TypeError: (96-107): 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. -// TypeError: (200-211): 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. +// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (200-211): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol index e7b4fae7..aabb76dd 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol @@ -8,4 +8,4 @@ contract C { } } // ---- -// TypeError: (87-98): 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. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol new file mode 100644 index 00000000..96767402 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol @@ -0,0 +1,8 @@ +contract C { + function f() internal view returns(uint[] storage a) + { + uint b = a[0]; + revert(); + b; + } +}
\ No newline at end of file diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol new file mode 100644 index 00000000..bfcbbef5 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol @@ -0,0 +1,9 @@ +contract C { + uint[] r; + function f() internal view returns (uint[] storage s) { + assembly { pop(s_slot) } + s = r; + } +} +// ---- +// TypeError: (92-126): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/functionType.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/functionType.sol new file mode 100644 index 00000000..1d683eff --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/functionType.sol @@ -0,0 +1,8 @@ +contract C { + // Make sure function parameters and return values are not considered + // for uninitialized return detection in the control flow analysis. + function f(function(uint[] storage) internal returns (uint[] storage)) internal pure + returns (function(uint[] storage) internal returns (uint[] storage)) + { + } +} diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fail.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fail.sol new file mode 100644 index 00000000..90d228fa --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fail.sol @@ -0,0 +1,8 @@ +contract C { + modifier m1(uint[] storage a) { _; } + modifier m2(uint[] storage a) { _; } + uint[] s; + function f() m1(b) m2(b = s) internal view returns (uint[] storage b) {} +} +// ---- +// TypeError: (129-130): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fine.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fine.sol new file mode 100644 index 00000000..af133929 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fine.sol @@ -0,0 +1,6 @@ +contract C { + modifier m1(uint[] storage a) { _; } + modifier m2(uint[] storage a) { _; } + uint[] s; + function f() m1(b = s) m2(b) internal view returns (uint[] storage b) {} +}
\ No newline at end of file diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_post_access.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_post_access.sol new file mode 100644 index 00000000..b9f08615 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_post_access.sol @@ -0,0 +1,13 @@ +contract C { + uint[] s; + modifier mod(uint[] storage b) { + _; + b[0] = 0; + } + function f() mod(a) internal returns (uint[] storage a) + { + a = s; + } +} +// ---- +// TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_pre_access.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_pre_access.sol new file mode 100644 index 00000000..81618aec --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_pre_access.sol @@ -0,0 +1,13 @@ +contract C { + uint[] s; + modifier mod(uint[] storage b) { + b[0] = 0; + _; + } + function f() mod(a) internal returns (uint[] storage a) + { + a = s; + } +} +// ---- +// TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/smoke.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/smoke.sol new file mode 100644 index 00000000..41ced51d --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/smoke.sol @@ -0,0 +1,10 @@ +contract C { + uint[] s; + function f() internal returns (uint[] storage a) + { + a[0] = 0; + a = s; + } +} +// ---- +// TypeError: (94-95): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/struct.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/struct.sol new file mode 100644 index 00000000..b4f2be5d --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/struct.sol @@ -0,0 +1,11 @@ +contract C { + struct S { uint a; } + S s; + function f() internal returns (S storage r) + { + r.a = 0; + r = s; + } +} +// ---- +// TypeError: (109-110): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol new file mode 100644 index 00000000..b941ad34 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol @@ -0,0 +1,10 @@ +contract C { + uint[] s; + function f() internal returns (uint[] storage a) + { + revert(); + a[0] = 0; + a = s; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol b/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol index 7a276f95..95d4e02a 100644 --- a/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol +++ b/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol @@ -8,3 +8,9 @@ library L { function i(uint[] calldata, uint[] storage) external pure returns (S storage x) {return x; } } // ---- +// TypeError: (197-198): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (203-204): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (359-360): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (365-366): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (460-461): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (557-558): This variable is of storage pointer type and can be accessed without prior assignment. diff --git a/test/libsolidity/syntaxTests/functionTypes/valid_function_type_variables.sol b/test/libsolidity/syntaxTests/functionTypes/valid_function_type_variables.sol index e7d2c9a9..65198706 100644 --- a/test/libsolidity/syntaxTests/functionTypes/valid_function_type_variables.sol +++ b/test/libsolidity/syntaxTests/functionTypes/valid_function_type_variables.sol @@ -18,9 +18,3 @@ contract test { function(uint) pure internal h = fh; } // ---- -// Warning: (20-47): Function state mutability can be restricted to pure -// Warning: (52-81): Function state mutability can be restricted to pure -// Warning: (86-115): Function state mutability can be restricted to pure -// Warning: (120-149): Function state mutability can be restricted to pure -// Warning: (154-183): Function state mutability can be restricted to pure -// Warning: (188-217): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/004_reference_to_later_declaration.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/004_reference_to_later_declaration.sol index e112e16c..a554d3ab 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/004_reference_to_later_declaration.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/004_reference_to_later_declaration.sol @@ -3,4 +3,3 @@ contract test { function f() public {} } // ---- -// Warning: (53-75): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/009_type_checking_function_call.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/009_type_checking_function_call.sol index abe2beac..3872b1c3 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/009_type_checking_function_call.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/009_type_checking_function_call.sol @@ -3,4 +3,3 @@ contract test { function g(uint256, bool) public returns (uint256) { } } // ---- -// Warning: (88-142): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/049_function_external_call_allowed_conversion.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/049_function_external_call_allowed_conversion.sol index ec72adeb..bda0b946 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/049_function_external_call_allowed_conversion.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/049_function_external_call_allowed_conversion.sol @@ -7,5 +7,3 @@ contract Test { function g (C c) external {} } // ---- -// Warning: (125-128): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (113-141): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/051_function_internal_allowed_conversion.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/051_function_internal_allowed_conversion.sol index aedc7b0b..5a9616ac 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/051_function_internal_allowed_conversion.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/051_function_internal_allowed_conversion.sol @@ -9,5 +9,3 @@ contract Test { } } // ---- -// Warning: (68-71): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (56-82): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/055_inheritance_diamond_basic.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/055_inheritance_diamond_basic.sol index c07e59e2..2e235ee0 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/055_inheritance_diamond_basic.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/055_inheritance_diamond_basic.sol @@ -5,5 +5,3 @@ contract derived is root, inter2, inter1 { function g() public { f(); rootFunction(); } } // ---- -// Warning: (16-49): Function state mutability can be restricted to pure -// Warning: (129-151): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/057_legal_override_direct.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/057_legal_override_direct.sol index 062507ee..bf6459cb 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/057_legal_override_direct.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/057_legal_override_direct.sol @@ -1,6 +1,3 @@ contract B { function f() public {} } contract C is B { function f(uint i) public {} } // ---- -// Warning: (67-73): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (13-35): Function state mutability can be restricted to pure -// Warning: (56-84): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/058_legal_override_indirect.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/058_legal_override_indirect.sol index f59da472..461bbbf2 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/058_legal_override_indirect.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/058_legal_override_indirect.sol @@ -2,6 +2,3 @@ contract A { function f(uint a) public {} } contract B { function f() public {} } contract C is A, B { } // ---- -// Warning: (24-30): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (13-41): Function state mutability can be restricted to pure -// Warning: (57-79): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/060_complex_inheritance.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/060_complex_inheritance.sol index c7e42238..ce3b622b 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/060_complex_inheritance.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/060_complex_inheritance.sol @@ -3,4 +3,3 @@ contract B { function f() public {} function g() public returns (uint8) {} } contract C is A, B { } // ---- // Warning: (35-42): Unused local variable. -// Warning: (95-133): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/061_missing_base_constructor_arguments.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/061_missing_base_constructor_arguments.sol index 8ebb46aa..31be70ca 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/061_missing_base_constructor_arguments.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/061_missing_base_constructor_arguments.sol @@ -1,4 +1,3 @@ contract A { constructor(uint a) public { } } contract B is A { } // ---- -// Warning: (25-31): Unused function parameter. Remove or comment out the variable name to silence this warning. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/062_base_constructor_arguments_override.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/062_base_constructor_arguments_override.sol index 8ebb46aa..31be70ca 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/062_base_constructor_arguments_override.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/062_base_constructor_arguments_override.sol @@ -1,4 +1,3 @@ contract A { constructor(uint a) public { } } contract B is A { } // ---- -// Warning: (25-31): Unused function parameter. Remove or comment out the variable name to silence this warning. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/096_access_to_default_function_visibility.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/096_access_to_default_function_visibility.sol index 9251df73..4f89e69e 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/096_access_to_default_function_visibility.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/096_access_to_default_function_visibility.sol @@ -5,4 +5,3 @@ contract d { function g() public { c(0).f(); } } // ---- -// Warning: (17-39): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/104_empty_name_input_parameter.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/104_empty_name_input_parameter.sol index 824543ef..af392402 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/104_empty_name_input_parameter.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/104_empty_name_input_parameter.sol @@ -2,4 +2,3 @@ contract test { function f(uint) public { } } // ---- -// Warning: (20-47): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/106_empty_name_return_parameter.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/106_empty_name_return_parameter.sol index a2ffc6e1..b524cd97 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/106_empty_name_return_parameter.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/106_empty_name_return_parameter.sol @@ -2,4 +2,3 @@ contract test { function f() public returns (bool) { } } // ---- -// Warning: (20-58): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/216_function_argument_storage_to_mem.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/216_function_argument_storage_to_mem.sol index c5175a41..0f4388d0 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/216_function_argument_storage_to_mem.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/216_function_argument_storage_to_mem.sol @@ -6,5 +6,3 @@ contract C { } } // ---- -// Warning: (91-106): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (80-122): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/256_using_for_overload.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/256_using_for_overload.sol index 155281f5..f033b85b 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/256_using_for_overload.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/256_using_for_overload.sol @@ -11,4 +11,3 @@ contract C { } } // ---- -// Warning: (128-189): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/260_library_memory_struct.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/260_library_memory_struct.sol index 20d8afa5..ee5bcda9 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/260_library_memory_struct.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/260_library_memory_struct.sol @@ -5,4 +5,3 @@ library c { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// Warning: (75-116): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/288_conditional_with_all_types.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/288_conditional_with_all_types.sol index e9ab08ba..a92c07f3 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/288_conditional_with_all_types.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/288_conditional_with_all_types.sol @@ -85,7 +85,5 @@ contract C { } // ---- // Warning: (1005-1019): This declaration shadows an existing declaration. -// Warning: (90-116): Function state mutability can be restricted to pure -// Warning: (121-147): Function state mutability can be restricted to pure // Warning: (257-642): Function state mutability can be restricted to pure // Warning: (647-1227): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/345_unused_return_value.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/345_unused_return_value.sol index 7f640505..109e8dfb 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/345_unused_return_value.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/345_unused_return_value.sol @@ -5,4 +5,3 @@ contract test { } } // ---- -// Warning: (20-57): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/376_inline_assembly_in_modifier.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/376_inline_assembly_in_modifier.sol index 0032f99e..87a7b28d 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/376_inline_assembly_in_modifier.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/376_inline_assembly_in_modifier.sol @@ -10,4 +10,3 @@ contract test { } } // ---- -// Warning: (122-151): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/403_return_structs.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/403_return_structs.sol index 2575954e..b4088068 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/403_return_structs.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/403_return_structs.sol @@ -7,4 +7,3 @@ contract C { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// Warning: (112-164): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/437_warn_unused_function_parameter.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/437_warn_unused_function_parameter.sol index 8a36eaad..4227cbb9 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/437_warn_unused_function_parameter.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/437_warn_unused_function_parameter.sol @@ -3,4 +3,3 @@ contract C { } } // ---- -// Warning: (28-34): Unused function parameter. Remove or comment out the variable name to silence this warning. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/439_warn_unused_return_parameter.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/439_warn_unused_return_parameter.sol index b1422c4f..b4c9be35 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/439_warn_unused_return_parameter.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/439_warn_unused_return_parameter.sol @@ -3,4 +3,3 @@ contract C { } } // ---- -// Warning: (51-57): Unused function parameter. Remove or comment out the variable name to silence this warning. diff --git a/test/libsolidity/syntaxTests/parsing/empty_function.sol b/test/libsolidity/syntaxTests/parsing/empty_function.sol index 320a0bcc..3f42e4e3 100644 --- a/test/libsolidity/syntaxTests/parsing/empty_function.sol +++ b/test/libsolidity/syntaxTests/parsing/empty_function.sol @@ -3,7 +3,3 @@ contract test { function functionName(bytes20 arg1, address addr) public view returns (int id) { } } // ---- -// Warning: (58-70): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (72-84): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (107-113): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (36-118): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/external_function.sol b/test/libsolidity/syntaxTests/parsing/external_function.sol index 3aa3ceec..0315b200 100644 --- a/test/libsolidity/syntaxTests/parsing/external_function.sol +++ b/test/libsolidity/syntaxTests/parsing/external_function.sol @@ -2,4 +2,3 @@ contract c { function x() external {} } // ---- -// Warning: (17-41): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/function_normal_comments.sol b/test/libsolidity/syntaxTests/parsing/function_normal_comments.sol index 94e1e60a..231be9c2 100644 --- a/test/libsolidity/syntaxTests/parsing/function_normal_comments.sol +++ b/test/libsolidity/syntaxTests/parsing/function_normal_comments.sol @@ -4,6 +4,3 @@ contract test { function functionName(bytes32 input) public returns (bytes32 out) {} } // ---- -// Warning: (97-110): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (128-139): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (75-143): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_assignment.sol b/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_assignment.sol index 11e77f25..42d8717a 100644 --- a/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_assignment.sol +++ b/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_assignment.sol @@ -3,7 +3,3 @@ contract test { function (uint, uint) internal returns (uint) f1 = f; } // ---- -// Warning: (31-37): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (39-45): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (63-69): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (20-73): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/function_type_in_expression.sol b/test/libsolidity/syntaxTests/parsing/function_type_in_expression.sol index 3defb5ea..000c2011 100644 --- a/test/libsolidity/syntaxTests/parsing/function_type_in_expression.sol +++ b/test/libsolidity/syntaxTests/parsing/function_type_in_expression.sol @@ -5,9 +5,5 @@ contract test { } } // ---- -// Warning: (31-37): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (39-45): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (63-69): Unused function parameter. Remove or comment out the variable name to silence this warning. // Warning: (108-156): Unused local variable. -// Warning: (20-73): Function state mutability can be restricted to pure // Warning: (78-167): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/library_simple.sol b/test/libsolidity/syntaxTests/parsing/library_simple.sol index 006ff307..c892e3ed 100644 --- a/test/libsolidity/syntaxTests/parsing/library_simple.sol +++ b/test/libsolidity/syntaxTests/parsing/library_simple.sol @@ -2,4 +2,3 @@ library Lib { function f() public { } } // ---- -// Warning: (18-41): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/modifier_invocation.sol b/test/libsolidity/syntaxTests/parsing/modifier_invocation.sol index cf986efe..6fa007c7 100644 --- a/test/libsolidity/syntaxTests/parsing/modifier_invocation.sol +++ b/test/libsolidity/syntaxTests/parsing/modifier_invocation.sol @@ -4,4 +4,3 @@ contract c { function f() public mod1(7) mod2 { } } // ---- -// Warning: (135-171): Function state mutability can be restricted to view diff --git a/test/libsolidity/syntaxTests/parsing/multiple_functions_natspec_documentation.sol b/test/libsolidity/syntaxTests/parsing/multiple_functions_natspec_documentation.sol index 85d9e6a8..ba090c53 100644 --- a/test/libsolidity/syntaxTests/parsing/multiple_functions_natspec_documentation.sol +++ b/test/libsolidity/syntaxTests/parsing/multiple_functions_natspec_documentation.sol @@ -10,15 +10,3 @@ contract test { function functionName4(bytes32 input) public returns (bytes32 out) {} } // ---- -// Warning: (97-110): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (128-139): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (203-216): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (234-245): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (304-317): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (335-346): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (410-423): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (441-452): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (74-143): Function state mutability can be restricted to pure -// Warning: (180-249): Function state mutability can be restricted to pure -// Warning: (281-350): Function state mutability can be restricted to pure -// Warning: (387-456): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/no_function_params.sol b/test/libsolidity/syntaxTests/parsing/no_function_params.sol index 5a024bdb..1f7c85a3 100644 --- a/test/libsolidity/syntaxTests/parsing/no_function_params.sol +++ b/test/libsolidity/syntaxTests/parsing/no_function_params.sol @@ -3,4 +3,3 @@ contract test { function functionName() public {} } // ---- -// Warning: (36-69): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/single_function_param.sol b/test/libsolidity/syntaxTests/parsing/single_function_param.sol index 955f20f0..8dbac272 100644 --- a/test/libsolidity/syntaxTests/parsing/single_function_param.sol +++ b/test/libsolidity/syntaxTests/parsing/single_function_param.sol @@ -3,6 +3,3 @@ contract test { function functionName(bytes32 input) public returns (bytes32 out) {} } // ---- -// Warning: (58-71): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (89-100): Unused function parameter. Remove or comment out the variable name to silence this warning. -// Warning: (36-104): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/visibility_specifiers.sol b/test/libsolidity/syntaxTests/parsing/visibility_specifiers.sol index db890b37..24071388 100644 --- a/test/libsolidity/syntaxTests/parsing/visibility_specifiers.sol +++ b/test/libsolidity/syntaxTests/parsing/visibility_specifiers.sol @@ -9,6 +9,3 @@ contract c { } // ---- // Warning: (58-71): This declaration shadows an existing declaration. -// Warning: (89-111): Function state mutability can be restricted to pure -// Warning: (116-144): Function state mutability can be restricted to pure -// Warning: (149-182): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/viewPureChecker/suggest_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/suggest_pure.sol index 87719eb3..5ec6d06f 100644 --- a/test/libsolidity/syntaxTests/viewPureChecker/suggest_pure.sol +++ b/test/libsolidity/syntaxTests/viewPureChecker/suggest_pure.sol @@ -2,4 +2,3 @@ contract C { function g() view public { } } // ---- -// Warning: (17-45): Function state mutability can be restricted to pure diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index 0f2529de..a337fa8d 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -29,6 +29,7 @@ #include <libyul/AsmParser.h> #include <libyul/AsmAnalysis.h> #include <libyul/AsmPrinter.h> +#include <libyul/backends/evm/EVMDialect.h> #include <liblangutil/Scanner.h> #include <liblangutil/ErrorReporter.h> @@ -54,7 +55,7 @@ void yul::test::printErrors(ErrorList const& _errors) pair<shared_ptr<Block>, shared_ptr<yul::AsmAnalysisInfo>> yul::test::parse(string const& _source, bool _yul) { - Dialect dialect = _yul ? yul::Dialect::yul() : yul::Dialect::strictAssemblyForEVM(); + shared_ptr<Dialect> dialect = _yul ? yul::Dialect::yul() : yul::EVMDialect::strictAssemblyForEVM(); ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared<Scanner>(CharStream(_source, "")); diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index eb2e735a..5e9fba86 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -49,7 +49,7 @@ namespace test namespace { -bool parse(string const& _source, Dialect const& _dialect, ErrorReporter& errorReporter) +bool parse(string const& _source, std::shared_ptr<Dialect> _dialect, ErrorReporter& errorReporter) { try { @@ -74,7 +74,7 @@ bool parse(string const& _source, Dialect const& _dialect, ErrorReporter& errorR return false; } -boost::optional<Error> parseAndReturnFirstError(string const& _source, Dialect const& _dialect, bool _allowWarnings = true) +boost::optional<Error> parseAndReturnFirstError(string const& _source, shared_ptr<Dialect> _dialect, bool _allowWarnings = true) { ErrorList errors; ErrorReporter errorReporter(errors); @@ -99,12 +99,12 @@ boost::optional<Error> parseAndReturnFirstError(string const& _source, Dialect c return {}; } -bool successParse(std::string const& _source, Dialect const& _dialect = Dialect::yul(), bool _allowWarnings = true) +bool successParse(std::string const& _source, shared_ptr<Dialect> _dialect = Dialect::yul(), bool _allowWarnings = true) { return !parseAndReturnFirstError(_source, _dialect, _allowWarnings); } -Error expectError(std::string const& _source, Dialect const& _dialect = Dialect::yul(), bool _allowWarnings = false) +Error expectError(std::string const& _source, shared_ptr<Dialect> _dialect = Dialect::yul(), bool _allowWarnings = false) { auto error = parseAndReturnFirstError(_source, _dialect, _allowWarnings); @@ -306,16 +306,17 @@ BOOST_AUTO_TEST_CASE(if_statement_invalid) BOOST_AUTO_TEST_CASE(builtins_parser) { - struct SimpleBuiltins: public Builtins + struct SimpleDialect: public Dialect { - BuiltinFunction const* query(YulString _name) const override + SimpleDialect(): Dialect(AsmFlavour::Strict) {} + BuiltinFunction const* builtin(YulString _name) const override { return _name == YulString{"builtin"} ? &f : nullptr; } BuiltinFunction f; }; - Dialect dialect(AsmFlavour::Strict, make_shared<SimpleBuiltins>()); + shared_ptr<Dialect> dialect = make_shared<SimpleDialect>(); CHECK_ERROR_DIALECT("{ let builtin := 6 }", ParserError, "Cannot use builtin function name \"builtin\" as identifier name.", dialect); CHECK_ERROR_DIALECT("{ function builtin() {} }", ParserError, "Cannot use builtin function name \"builtin\" as identifier name.", dialect); CHECK_ERROR_DIALECT("{ builtin := 6 }", ParserError, "Cannot assign to builtin function \"builtin\".", dialect); @@ -323,16 +324,17 @@ BOOST_AUTO_TEST_CASE(builtins_parser) BOOST_AUTO_TEST_CASE(builtins_analysis) { - struct SimpleBuiltinsAnalysis: public Builtins + struct SimpleDialect: public Dialect { - yul::BuiltinFunction const* query(YulString _name) const override + SimpleDialect(): Dialect(AsmFlavour::Strict) {} + BuiltinFunction const* builtin(YulString _name) const override { - return _name == YulString("builtin") ? &m_builtin : nullptr; + return _name == YulString{"builtin"} ? &f : nullptr; } - BuiltinFunction m_builtin{YulString{"builtin"}, vector<Type>(2), vector<Type>(3), false}; + BuiltinFunction f{YulString{"builtin"}, vector<Type>(2), vector<Type>(3), false}; }; - Dialect dialect(AsmFlavour::Strict, make_shared<SimpleBuiltinsAnalysis>()); + shared_ptr<Dialect> dialect = make_shared<SimpleDialect>(); BOOST_CHECK(successParse("{ let a, b, c := builtin(1, 2) }", dialect)); CHECK_ERROR_DIALECT("{ let a, b, c := builtin(1) }", TypeError, "Function expects 2 arguments but got 1", dialect); CHECK_ERROR_DIALECT("{ let a, b := builtin(1, 2) }", DeclarationError, "Variable count mismatch: 2 variables and 3 values.", dialect); diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index ba7eced3..68226e33 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -41,6 +41,7 @@ #include <libyul/optimiser/RedundantAssignEliminator.h> #include <libyul/optimiser/StructuralSimplifier.h> #include <libyul/optimiser/Suite.h> +#include <libyul/backends/evm/EVMDialect.h> #include <libyul/AsmPrinter.h> #include <libyul/AsmParser.h> #include <libyul/AsmAnalysis.h> @@ -262,7 +263,7 @@ void YulOptimizerTest::printIndented(ostream& _stream, string const& _output, st bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) { - yul::Dialect dialect = m_yul ? yul::Dialect::yul() : yul::Dialect::strictAssemblyForEVM(); + shared_ptr<yul::Dialect> dialect = m_yul ? yul::Dialect::yul() : yul::EVMDialect::strictAssemblyForEVM(); ErrorList errors; ErrorReporter errorReporter(errors); shared_ptr<Scanner> scanner = make_shared<Scanner>(CharStream(m_source, "")); diff --git a/test/libyul/objectCompiler/datacopy.yul b/test/libyul/objectCompiler/datacopy.yul new file mode 100644 index 00000000..2259e5dc --- /dev/null +++ b/test/libyul/objectCompiler/datacopy.yul @@ -0,0 +1,48 @@ +object "a" { + code { + datacopy(0, dataoffset("sub"), datasize("sub")) + return(0, datasize("sub")) + } + object "sub" { + code { + sstore(0, dataoffset("sub")) + mstore(0, datasize("data1")) + } + data "data1" "Hello, World!" + } +} +// ---- +// Assembly: +// /* "source":26:73 */ +// dataSize(sub_0) +// dataOffset(sub_0) +// /* "source":35:36 */ +// 0x00 +// /* "source":26:73 */ +// codecopy +// /* "source":78:104 */ +// dataSize(sub_0) +// /* "source":85:86 */ +// 0x00 +// /* "source":78:104 */ +// return +// stop +// +// sub_0: assembly { +// /* "source":143:171 */ +// 0x00 +// /* "source":150:151 */ +// 0x00 +// /* "source":143:171 */ +// sstore +// /* "source":178:206 */ +// 0x0d +// /* "source":185:186 */ +// 0x00 +// /* "source":178:206 */ +// mstore +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// } +// Bytecode: 600b600d600039600b6000f3fe6000600055600d600052fe +// Opcodes: PUSH1 0xB PUSH1 0xD PUSH1 0x0 CODECOPY PUSH1 0xB PUSH1 0x0 RETURN INVALID PUSH1 0x0 PUSH1 0x0 SSTORE PUSH1 0xD PUSH1 0x0 MSTORE INVALID diff --git a/test/libyul/objectCompiler/dataoffset_code.yul b/test/libyul/objectCompiler/dataoffset_code.yul new file mode 100644 index 00000000..725267f2 --- /dev/null +++ b/test/libyul/objectCompiler/dataoffset_code.yul @@ -0,0 +1,29 @@ +object "a" { + code { sstore(0, dataoffset("sub")) } + object "sub" { + code { sstore(0, 8) } + data "data1" "Hello, World!" + } +} +// ---- +// Assembly: +// /* "source":22:50 */ +// dataOffset(sub_0) +// /* "source":29:30 */ +// 0x00 +// /* "source":22:50 */ +// sstore +// stop +// +// sub_0: assembly { +// /* "source":91:92 */ +// 0x08 +// /* "source":88:89 */ +// 0x00 +// /* "source":81:93 */ +// sstore +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// } +// Bytecode: 6006600055fe6008600055fe +// Opcodes: PUSH1 0x6 PUSH1 0x0 SSTORE INVALID PUSH1 0x8 PUSH1 0x0 SSTORE INVALID diff --git a/test/libyul/objectCompiler/dataoffset_data.yul b/test/libyul/objectCompiler/dataoffset_data.yul new file mode 100644 index 00000000..9a0a461d --- /dev/null +++ b/test/libyul/objectCompiler/dataoffset_data.yul @@ -0,0 +1,16 @@ +object "a" { + code { sstore(0, dataoffset("data1")) } + data "data1" "Hello, World!" +} +// ---- +// Assembly: +// /* "source":22:52 */ +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f +// /* "source":29:30 */ +// 0x00 +// /* "source":22:52 */ +// sstore +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// Bytecode: 6006600055fe48656c6c6f2c20576f726c6421 +// Opcodes: PUSH1 0x6 PUSH1 0x0 SSTORE INVALID 0x48 PUSH6 0x6C6C6F2C2057 PUSH16 0x726C6421000000000000000000000000 diff --git a/test/libyul/objectCompiler/dataoffset_self.yul b/test/libyul/objectCompiler/dataoffset_self.yul new file mode 100644 index 00000000..b7740735 --- /dev/null +++ b/test/libyul/objectCompiler/dataoffset_self.yul @@ -0,0 +1,16 @@ +object "a" { + code { sstore(0, dataoffset("a")) } + data "data1" "Hello, World!" +} +// ---- +// Assembly: +// /* "source":22:48 */ +// 0x00 +// /* "source":29:30 */ +// 0x00 +// /* "source":22:48 */ +// sstore +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// Bytecode: 6000600055fe +// Opcodes: PUSH1 0x0 PUSH1 0x0 SSTORE INVALID diff --git a/test/libyul/objectCompiler/datasize_code.yul b/test/libyul/objectCompiler/datasize_code.yul new file mode 100644 index 00000000..cff68515 --- /dev/null +++ b/test/libyul/objectCompiler/datasize_code.yul @@ -0,0 +1,29 @@ +object "a" { + code { sstore(0, datasize("sub")) } + object "sub" { + code { sstore(0, 8) } + data "data1" "Hello, World!" + } +} +// ---- +// Assembly: +// /* "source":22:48 */ +// dataSize(sub_0) +// /* "source":29:30 */ +// 0x00 +// /* "source":22:48 */ +// sstore +// stop +// +// sub_0: assembly { +// /* "source":89:90 */ +// 0x08 +// /* "source":86:87 */ +// 0x00 +// /* "source":79:91 */ +// sstore +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// } +// Bytecode: 6006600055fe +// Opcodes: PUSH1 0x6 PUSH1 0x0 SSTORE INVALID diff --git a/test/libyul/objectCompiler/datasize_data.yul b/test/libyul/objectCompiler/datasize_data.yul new file mode 100644 index 00000000..f8341469 --- /dev/null +++ b/test/libyul/objectCompiler/datasize_data.yul @@ -0,0 +1,16 @@ +object "a" { + code { sstore(0, datasize("data1")) } + data "data1" "Hello, World!" +} +// ---- +// Assembly: +// /* "source":22:50 */ +// 0x0d +// /* "source":29:30 */ +// 0x00 +// /* "source":22:50 */ +// sstore +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// Bytecode: 600d600055fe +// Opcodes: PUSH1 0xD PUSH1 0x0 SSTORE INVALID diff --git a/test/libyul/objectCompiler/datasize_self.yul b/test/libyul/objectCompiler/datasize_self.yul new file mode 100644 index 00000000..4579fe67 --- /dev/null +++ b/test/libyul/objectCompiler/datasize_self.yul @@ -0,0 +1,16 @@ +object "a" { + code { sstore(0, datasize("a")) } + data "data1" "Hello, World!" +} +// ---- +// Assembly: +// /* "source":22:46 */ +// bytecodeSize +// /* "source":29:30 */ +// 0x00 +// /* "source":22:46 */ +// sstore +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// Bytecode: 6006600055fe +// Opcodes: PUSH1 0x6 PUSH1 0x0 SSTORE INVALID diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 477de42f..3dc35f73 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -49,6 +49,8 @@ #include <libyul/optimiser/StructuralSimplifier.h> #include <libyul/optimiser/VarDeclPropagator.h> +#include <libyul/backends/evm/EVMDialect.h> + #include <libdevcore/JSON.h> #include <boost/program_options.hpp> @@ -83,7 +85,7 @@ public: { ErrorReporter errorReporter(m_errors); shared_ptr<Scanner> scanner = make_shared<Scanner>(CharStream(_input, "")); - m_ast = yul::Parser(errorReporter, yul::Dialect::strictAssemblyForEVM()).parse(scanner, false); + m_ast = yul::Parser(errorReporter, yul::EVMDialect::strictAssemblyForEVM()).parse(scanner, false); if (!m_ast || !errorReporter.errors().empty()) { cout << "Error parsing source." << endl; @@ -96,7 +98,7 @@ public: errorReporter, EVMVersion::byzantium(), langutil::Error::Type::SyntaxError, - Dialect::strictAssemblyForEVM() + EVMDialect::strictAssemblyForEVM() ); if (!analyzer.analyze(*m_ast) || !errorReporter.errors().empty()) { |