diff options
55 files changed, 1012 insertions, 203 deletions
diff --git a/Changelog.md b/Changelog.md index 7c4ac925..a87c8dd4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,9 +3,12 @@ Features: * Build System: Update internal dependency of jsoncpp to 1.8.4, which introduces more strictness and reduces memory usage. * Code Generator: Use native shift instructions on target Constantinople. + * Gas Estimator: Only explore paths with higher gas costs. This reduces accuracy but greatly improves the speed of gas estimation. * Optimizer: Remove unnecessary masking of the result of known short instructions (``ADDRESS``, ``CALLER``, ``ORIGIN`` and ``COINBASE``). + * Parser: Display nicer error messages by showing the actual tokens and not internal names. * Type Checker: Deprecate the ``years`` unit denomination and raise a warning for it (or an error as experimental 0.5.0 feature). * Type Checker: Make literals (without explicit type casting) an error for tight packing as experimental 0.5.0 feature. + * Type Checker: Warn about wildcard tuple assignments (this will turn into an error with version 0.5.0). Bugfixes: * Type Checker: Show proper error when trying to ``emit`` a non-event. diff --git a/appveyor.yml b/appveyor.yml index 5fd85482..81ec711d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,10 +31,12 @@ branches: only: - release - develop -os: Visual Studio 2015 configuration: - RelWithDebInfo environment: + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 # This is used for pushing to solidity-test-bytecodes priv_key: secure: yYGwg4rhCdHfwuv2mFjaNEDwAx3IKUbp0D5fMGpaKefnfk+BiMS5bqSHRiOj91PZ91P9pUk2Vu+eNuS4hTFCf1zFGfrOhlJ4Ij0xSyU5m/LQr590Mo+f7W94Xc8ubgo6j2hp9qH/szTqTzmAkmxKO5TLlWjVzVny2t/s5o5UprLS1/MdzDNLjpVNXR03oKfdWUV9a2l6+PejXCbqyUCagh6BByZqeAPbDcil6eAfxu4EPX83Fuurof+KqFzIWycBG5qK1pTipn2pxiA0QKuUrD8y8VNL0S23NTgxoxSp7nPVMd3K0qRSzPM5lrqS7Z8i3evkVwPbuhu0gSiV08jGVahH2snQ3JGYsH2D4KmVn/xiVBeJ0lRplYlfZF0GUu7iJ+DDxi6wBPhW9A25/NyD/mx7Ub2dLheyWi8AjdSCzhfRD+4We8FQQeHRo3Q0kAohFmlCXdXhrcwOOloId8r6xYwg+hWxHTt2Oe9CKwXfmiPjgl/Gd6lYgLpyyfJ8drQ6tjO/pybLEa10v74qYNdVW5LaLIsRUM9Jm/FDVTrOGYtPndi87mF+/tBJIaXXNz0EMl5xvsKW0SBfUMV49zoDDKZZgWyO9U/cfViEUi7Sdn9QLsBWLZfSgBQNkq3WGZVKPq58OxEWT9dUghQHlSVh2qWF/NUx0TRBjiJl9JM56ENTMD00y18eDcXNCeLLVYB+R1axabUPdXivrO+BrWQK94IWxKEJ+YYN8WVJWAO5T/EBDKwgiXGneePwJ75WP7XCLtuYxqjC+CeW3xBVCzCEeZB/VVBvt7fhmtcoeZZ6tAS10h0yY5WWZ/EUVorj+c/FrMm7Nlpcrd1p4hciffePSLVg+yvy9/xTuM9trYWMgj4xcDQbYsaeItHO2Z3EiUoCgNdUw6rONiNwS/XBApWhCcklWm0/g62h2gOa7/hnKG6p2omQzYw+cOzWbF9+DBzoTSXXZXqbUshVee+CD+iYJKleGYSdbMdM89HW4HyskHk6HgM1ggE8CsgD1pMhXtqLTYZBlvsZCBkHPkD9NhGD2DtrNOmJOW8xwkL2/Il6roDF4n856XNdsjvd++rvQoKr58SkyApCJeCo3sfVres0W22g+7If2b2kWC4/DphrFkeaceFzJOctBUrwstvQBXIVOcadU978A3E7jvTaMR4JL9kC/iPOUVNjNRNM/gNvTlf3CIyMMszFeftjEBGnCZaSpht2RtNapRQQb6QPkOP88nufQVZq/TP1ECmvdTUWJ7kSnAupu6u8oH2x2IIm/KKeIwSYU5rGxjRb36DwgXCHcwfRYo3VNorwTeZGj4q1TSM9PuvgzNg//gKZW6VRa+HdNm/40ZGpDsOrr55tOBqfpq9k5RmevqW/OMZS3xUuArKdYLQY75t9eWcbHSgFN2ZY1KEdyEEvVKgs6Q4lEnSSulGxroRxTU5BOoA0V4tCeCUoSPD3FB93WsO9fBPzNsqOuBtDdIkApefzc1pT38uKpmVfggKUsoWUdqMXAWqCDWr2uw9EE900RJpEY6mIEWhkcro5LAMwaqByOGpqFFUkH+UWTC102eVHEmjxKpC6c6cSzoKKU6Ckd+jVRFO7TvmVe1MKCwjXj8lcAfAM2gQ+XehtrQdIBhAmCrnzurfz2u9tKVdpiADC1ig+kMs1/HX2713LYVXzDKdk+duQ94SVtGv9F2Iv+KN5oq4UFgll6VGt7GHsJOrYYf/wrOfB09IkpmjNygvcpmmSdcXXF8ulDD6KHTGEGUlFwLOpEwKx+zX2ZvviStHhN8KsoTKSVSueDmSSI63HdTS7FxfrHJc1yAzsdqEN5g5eV/z2Fn34qy64mdFSAZMF5zsbWZYFpc9ef3llF5aRcuD90JWT2VC7rB2jeGEtiwGkDlqKzxqRvJk06wTK6+n5RncN66bDaksulOPJMAR/bRW7dinV8T6yIvybuhqDetxJQP6eyAnW4xr1YxIAG4BXGZV6XAPTgOG2oGvMdncxkcLQHXVu07x39ySqP/m2MBxn0zF3DmaqrSPIRMhS8gG3d/23Jux3YHDEOBHjdJSdwqs5F5+QBFPV2rmJnpcSoW4d3M119XI20L914c62R7wY4e6+qmi3ydQU9g6p8psZgaE3TuMsyzX3k4C30nC/3gWT+zl253NjZwfbzIdHu5LWNDY9kEHtKzLP @@ -62,11 +64,12 @@ install: before_build: - if not exist build mkdir build - cd build - - cmake -G "Visual Studio 14 2015 Win64" .. -DTESTS=On + - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2015" ( cmake -G "Visual Studio 14 2015 Win64" .. -DTESTS=On ) + else ( cmake -G "Visual Studio 15 2017 Win64" .. -DTESTS=On ) build_script: - msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal - cd %APPVEYOR_BUILD_FOLDER% - - scripts\release.bat %CONFIGURATION% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" ( scripts\release.bat %CONFIGURATION% 2017 ) - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short test_script: diff --git a/docs/contracts.rst b/docs/contracts.rst index 9551fcc7..3576cd7b 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -24,8 +24,8 @@ Creating contracts programatically on Ethereum is best done via using the JavaSc As of today it has a method called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract>`_ to facilitate contract creation. -When a contract is created, its constructor (a function with the same -name as the contract) is executed once. +When a contract is created, its constructor (a function declared with the +``constructor`` keyword) is executed once. A constructor is optional. Only one constructor is allowed, and this means overloading is not supported. diff --git a/docs/control-structures.rst b/docs/control-structures.rst index f3c351dd..f18e1e10 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -293,12 +293,7 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di // Common trick to swap values -- does not work for non-value storage types. (x, y) = (y, x); // Components can be left out (also for variable declarations). - // If the tuple ends in an empty component, - // the rest of the values are discarded. - (data.length,) = f(); // Sets the length to 7 - // The same can be done on the left side. - // If the tuple begins in an empty component, the beginning values are discarded. - (,data[3]) = f(); // Sets data[3] to 2 + (data.length,,) = f(); // Sets the length to 7 // Components can only be left out at the left-hand-side of assignments, with // one exception: (x,) = (1,); @@ -307,6 +302,11 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di } } +.. note:: + Prior to version 0.4.24 it was possible to assign to tuples of smaller size, either + filling up on the left or on the right side (which ever was empty). This is + now deprecated, both sides have to have the same number of components. + Complications for Arrays and Structs ------------------------------------ diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 4cb34fbd..a6f8ca87 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -120,11 +120,11 @@ Error Handling -------------- ``assert(bool condition)``: - throws if the condition is not met - to be used for internal errors. + invalidates the transaction if the condition is not met - to be used for internal errors. ``require(bool condition)``: - throws if the condition is not met - to be used for errors in inputs or external components. + reverts if the condition is not met - to be used for errors in inputs or external components. ``require(bool condition, string message)``: - throws if the condition is not met - to be used for errors in inputs or external components. Also provides an error message. + reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message. ``revert()``: abort execution and revert state changes ``revert(string reason)``: diff --git a/libevmasm/PathGasMeter.cpp b/libevmasm/PathGasMeter.cpp index 3fe682b7..cdadba76 100644 --- a/libevmasm/PathGasMeter.cpp +++ b/libevmasm/PathGasMeter.cpp @@ -43,7 +43,7 @@ GasMeter::GasConsumption PathGasMeter::estimateMax( auto path = unique_ptr<GasPath>(new GasPath()); path->index = _startIndex; path->state = _state->copy(); - m_queue.push_back(move(path)); + queue(move(path)); GasMeter::GasConsumption gas; while (!m_queue.empty() && !gas.isInfinite) @@ -51,12 +51,23 @@ GasMeter::GasConsumption PathGasMeter::estimateMax( return gas; } +void PathGasMeter::queue(std::unique_ptr<GasPath>&& _newPath) +{ + if ( + m_highestGasUsagePerJumpdest.count(_newPath->index) && + _newPath->gas < m_highestGasUsagePerJumpdest.at(_newPath->index) + ) + return; + m_highestGasUsagePerJumpdest[_newPath->index] = _newPath->gas; + m_queue[_newPath->index] = move(_newPath); +} + GasMeter::GasConsumption PathGasMeter::handleQueueItem() { assertThrow(!m_queue.empty(), OptimizerException, ""); - unique_ptr<GasPath> path = move(m_queue.back()); - m_queue.pop_back(); + unique_ptr<GasPath> path = move(m_queue.rbegin()->second); + m_queue.erase(--m_queue.end()); shared_ptr<KnownState> state = path->state; GasMeter meter(state, m_evmVersion, path->largestMemoryAccess); @@ -117,7 +128,7 @@ GasMeter::GasConsumption PathGasMeter::handleQueueItem() newPath->largestMemoryAccess = meter.largestMemoryAccess(); newPath->state = state->copy(); newPath->visitedJumpdests = path->visitedJumpdests; - m_queue.push_back(move(newPath)); + queue(move(newPath)); } if (branchStops) diff --git a/libevmasm/PathGasMeter.h b/libevmasm/PathGasMeter.h index 2527d7fb..9537b176 100644 --- a/libevmasm/PathGasMeter.h +++ b/libevmasm/PathGasMeter.h @@ -58,9 +58,17 @@ public: GasMeter::GasConsumption estimateMax(size_t _startIndex, std::shared_ptr<KnownState> const& _state); private: + /// Adds a new path item to the queue, but only if we do not already have + /// a higher gas usage at that point. + /// This is not exact as different state might influence higher gas costs at a later + /// point in time, but it greatly reduces computational overhead. + void queue(std::unique_ptr<GasPath>&& _newPath); GasMeter::GasConsumption handleQueueItem(); - std::vector<std::unique_ptr<GasPath>> m_queue; + /// Map of jumpdest -> gas path, so not really a queue. We only have one queued up + /// item per jumpdest, because of the behaviour of `queue` above. + std::map<size_t, std::unique_ptr<GasPath>> m_queue; + std::map<size_t, GasMeter::GasConsumption> m_highestGasUsagePerJumpdest; std::map<u256, size_t> m_tagPositions; AssemblyItems const& m_items; solidity::EVMVersion m_evmVersion; diff --git a/libjulia/optimiser/Disambiguator.cpp b/libjulia/optimiser/Disambiguator.cpp index df2984e5..b988dba7 100644 --- a/libjulia/optimiser/Disambiguator.cpp +++ b/libjulia/optimiser/Disambiguator.cpp @@ -38,17 +38,7 @@ string Disambiguator::translateIdentifier(string const& _originalName) Scope::Identifier const* id = m_scopes.back()->lookup(_originalName); solAssert(id, ""); if (!m_translations.count(id)) - { - string translated = _originalName; - size_t suffix = 0; - while (m_usedNames.count(translated)) - { - suffix++; - translated = _originalName + "_" + std::to_string(suffix); - } - m_usedNames.insert(translated); - m_translations[id] = translated; - } + m_translations[id] = m_nameDispenser.newName(_originalName); return m_translations.at(id); } diff --git a/libjulia/optimiser/Disambiguator.h b/libjulia/optimiser/Disambiguator.h index 18ffd157..6fc8a615 100644 --- a/libjulia/optimiser/Disambiguator.h +++ b/libjulia/optimiser/Disambiguator.h @@ -23,6 +23,7 @@ #include <libjulia/ASTDataForward.h> #include <libjulia/optimiser/ASTCopier.h> +#include <libjulia/optimiser/NameDispenser.h> #include <libsolidity/inlineasm/AsmAnalysisInfo.h> @@ -60,7 +61,7 @@ protected: std::vector<solidity::assembly::Scope*> m_scopes; std::map<void const*, std::string> m_translations; - std::set<std::string> m_usedNames; + NameDispenser m_nameDispenser; }; } diff --git a/libjulia/optimiser/FullInliner.cpp b/libjulia/optimiser/FullInliner.cpp new file mode 100644 index 00000000..05d70729 --- /dev/null +++ b/libjulia/optimiser/FullInliner.cpp @@ -0,0 +1,262 @@ +/* + 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/>. +*/ +/** + * Optimiser component that performs function inlining for arbitrary functions. + */ + +#include <libjulia/optimiser/FullInliner.h> + +#include <libjulia/optimiser/ASTCopier.h> +#include <libjulia/optimiser/ASTWalker.h> +#include <libjulia/optimiser/NameCollector.h> +#include <libjulia/optimiser/Semantics.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <libdevcore/CommonData.h> + +#include <boost/range/adaptor/reversed.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::solidity; + + + +FullInliner::FullInliner(Block& _ast): + m_ast(_ast) +{ + solAssert(m_ast.statements.size() >= 1, ""); + solAssert(m_ast.statements.front().type() == typeid(Block), ""); + m_nameDispenser.m_usedNames = NameCollector(m_ast).names(); + + for (size_t i = 1; i < m_ast.statements.size(); ++i) + { + solAssert(m_ast.statements.at(i).type() == typeid(FunctionDefinition), ""); + FunctionDefinition& fun = boost::get<FunctionDefinition>(m_ast.statements.at(i)); + m_functions[fun.name] = &fun; + m_functionsToVisit.insert(&fun); + } +} + +void FullInliner::run() +{ + solAssert(m_ast.statements[0].type() == typeid(Block), ""); + InlineModifier(*this, m_nameDispenser, "").visit(m_ast.statements[0]); + while (!m_functionsToVisit.empty()) + handleFunction(**m_functionsToVisit.begin()); +} + +void FullInliner::handleFunction(FunctionDefinition& _fun) +{ + if (!m_functionsToVisit.count(&_fun)) + return; + m_functionsToVisit.erase(&_fun); + (InlineModifier(*this, m_nameDispenser, _fun.name))(_fun.body); +} + +void InlineModifier::operator()(FunctionalInstruction& _instruction) +{ + visitArguments(_instruction.arguments); +} + +void InlineModifier::operator()(FunctionCall&) +{ + solAssert(false, "Should be handled in visit() instead."); +} + +void InlineModifier::operator()(ForLoop& _loop) +{ + (*this)(_loop.pre); + // Do not visit the condition because we cannot inline there. + (*this)(_loop.post); + (*this)(_loop.body); +} + +void InlineModifier::operator()(Block& _block) +{ + // This is only used if needed to minimize the number of move operations. + vector<Statement> modifiedStatements; + for (size_t i = 0; i < _block.statements.size(); ++i) + { + visit(_block.statements.at(i)); + if (!m_statementsToPrefix.empty()) + { + if (modifiedStatements.empty()) + std::move( + _block.statements.begin(), + _block.statements.begin() + i, + back_inserter(modifiedStatements) + ); + modifiedStatements += std::move(m_statementsToPrefix); + m_statementsToPrefix.clear(); + } + if (!modifiedStatements.empty()) + modifiedStatements.emplace_back(std::move(_block.statements[i])); + } + if (!modifiedStatements.empty()) + _block.statements = std::move(modifiedStatements); +} + +void InlineModifier::visit(Expression& _expression) +{ + if (_expression.type() != typeid(FunctionCall)) + return ASTModifier::visit(_expression); + + FunctionCall& funCall = boost::get<FunctionCall>(_expression); + FunctionDefinition& fun = m_driver.function(funCall.functionName.name); + + m_driver.handleFunction(fun); + + // TODO: Insert good heuristic here. Perhaps implement that inside the driver. + bool doInline = funCall.functionName.name != m_currentFunction; + + if (fun.returnVariables.size() > 1) + doInline = false; + + { + vector<string> argNames; + vector<string> argTypes; + for (auto const& arg: fun.parameters) + { + argNames.push_back(fun.name + "_" + arg.name); + argTypes.push_back(arg.type); + } + visitArguments(funCall.arguments, argNames, argTypes, doInline); + } + + if (!doInline) + return; + + map<string, string> variableReplacements; + for (size_t i = 0; i < funCall.arguments.size(); ++i) + variableReplacements[fun.parameters[i].name] = boost::get<Identifier>(funCall.arguments[i]).name; + if (fun.returnVariables.empty()) + _expression = noop(funCall.location); + else + { + string returnVariable = fun.returnVariables[0].name; + variableReplacements[returnVariable] = newName(fun.name + "_" + returnVariable); + + m_statementsToPrefix.emplace_back(VariableDeclaration{ + funCall.location, + {{funCall.location, variableReplacements[returnVariable], fun.returnVariables[0].type}}, + {} + }); + _expression = Identifier{funCall.location, variableReplacements[returnVariable]}; + } + m_statementsToPrefix.emplace_back(BodyCopier(m_nameDispenser, fun.name + "_", variableReplacements)(fun.body)); +} + +void InlineModifier::visit(Statement& _statement) +{ + ASTModifier::visit(_statement); + // Replace pop(0) expression statemets (and others) by empty blocks. + if (_statement.type() == typeid(ExpressionStatement)) + { + ExpressionStatement& expSt = boost::get<ExpressionStatement&>(_statement); + if (expSt.expression.type() == typeid(FunctionalInstruction)) + { + FunctionalInstruction& funInstr = boost::get<FunctionalInstruction&>(expSt.expression); + if (funInstr.instruction == solidity::Instruction::POP) + if (MovableChecker(funInstr.arguments.at(0)).movable()) + _statement = Block{expSt.location, {}}; + } + } +} + +void InlineModifier::visitArguments( + vector<Expression>& _arguments, + vector<string> const& _nameHints, + vector<string> const& _types, + bool _moveToFront +) +{ + // If one of the elements moves parts to the front, all other elements right of it + // also have to be moved to the front to keep the order of evaluation. + vector<Statement> prefix; + for (size_t i = 0; i < _arguments.size(); ++i) + { + auto& arg = _arguments[i]; + // TODO optimize vector operations, check that it actually moves + auto internalPrefix = visitRecursively(arg); + if (!internalPrefix.empty()) + { + _moveToFront = true; + // We go through the arguments left to right, so we have to invert + // the prefixes. + prefix = std::move(internalPrefix) + std::move(prefix); + } + else if (_moveToFront) + { + auto location = locationOf(arg); + string var = newName(i < _nameHints.size() ? _nameHints[i] : ""); + prefix.emplace(prefix.begin(), VariableDeclaration{ + location, + {{TypedName{location, var, i < _types.size() ? _types[i] : ""}}}, + make_shared<Expression>(std::move(arg)) + }); + arg = Identifier{location, var}; + } + } + m_statementsToPrefix += std::move(prefix); +} + +vector<Statement> InlineModifier::visitRecursively(Expression& _expression) +{ + vector<Statement> saved; + saved.swap(m_statementsToPrefix); + visit(_expression); + saved.swap(m_statementsToPrefix); + return saved; +} + +string InlineModifier::newName(string const& _prefix) +{ + return m_nameDispenser.newName(_prefix); +} + +Expression InlineModifier::noop(SourceLocation const& _location) +{ + return FunctionalInstruction{_location, solidity::Instruction::POP, { + Literal{_location, assembly::LiteralKind::Number, "0", ""} + }}; +} + +Statement BodyCopier::operator()(VariableDeclaration const& _varDecl) +{ + for (auto const& var: _varDecl.variables) + m_variableReplacements[var.name] = m_nameDispenser.newName(m_varNamePrefix + var.name); + return ASTCopier::operator()(_varDecl); +} + +Statement BodyCopier::operator()(FunctionDefinition const& _funDef) +{ + solAssert(false, "Function hoisting has to be done before function inlining."); + return _funDef; +} + +string BodyCopier::translateIdentifier(string const& _name) +{ + if (m_variableReplacements.count(_name)) + return m_variableReplacements.at(_name); + else + return _name; +} diff --git a/libjulia/optimiser/FullInliner.h b/libjulia/optimiser/FullInliner.h new file mode 100644 index 00000000..d3628e1a --- /dev/null +++ b/libjulia/optimiser/FullInliner.h @@ -0,0 +1,178 @@ +/* + 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/>. +*/ +/** + * Optimiser component that performs function inlining for arbitrary functions. + */ +#pragma once + +#include <libjulia/ASTDataForward.h> + +#include <libjulia/optimiser/ASTCopier.h> +#include <libjulia/optimiser/ASTWalker.h> +#include <libjulia/optimiser/NameDispenser.h> + +#include <libsolidity/interface/Exceptions.h> + +#include <boost/variant.hpp> +#include <boost/optional.hpp> + +#include <set> + +namespace dev +{ +namespace julia +{ + +class NameCollector; + + +/** + * Optimiser component that modifies an AST in place, inlining arbitrary functions. + * + * Code of the form + * + * function f(a, b) -> c { ... } + * h(g(x(...), f(arg1(...), arg2(...)), y(...)), z(...)) + * + * is transformed into + * + * function f(a, b) -> c { ... } + * + * let z1 := z(...) let y1 := y(...) let a2 := arg2(...) let a1 := arg1(...) + * let c1 := 0 + * { code of f, with replacements: a -> a1, b -> a2, c -> c1, d -> d1 } + * h(g(x(...), c1, y1), z1) + * + * No temporary variable is created for expressions that are "movable" + * (i.e. they are "pure", have no side-effects and also do not depend on other code + * that might have side-effects). + * + * This component can only be used on sources with unique names and with hoisted functions, + * i.e. the root node has to be a block that itself contains a single block followed by all + * function definitions. + */ +class FullInliner: public ASTModifier +{ +public: + explicit FullInliner(Block& _ast); + + void run(); + + /// Perform inlining operations inside the given function. + void handleFunction(FunctionDefinition& _function); + + FunctionDefinition& function(std::string _name) { return *m_functions.at(_name); } + +private: + /// The AST to be modified. The root block itself will not be modified, because + /// we store pointers to functions. + Block& m_ast; + std::map<std::string, FunctionDefinition*> m_functions; + std::set<FunctionDefinition*> m_functionsToVisit; + NameDispenser m_nameDispenser; +}; + +/** + * Class that walks the AST of a block that does not contain function definitions and perform + * the actual code modifications. + */ +class InlineModifier: public ASTModifier +{ +public: + InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, std::string _functionName): + m_currentFunction(std::move(_functionName)), + m_driver(_driver), + m_nameDispenser(_nameDispenser) + { } + ~InlineModifier() + { + solAssert(m_statementsToPrefix.empty(), ""); + } + + virtual void operator()(FunctionalInstruction&) override; + virtual void operator()(FunctionCall&) override; + virtual void operator()(ForLoop&) override; + virtual void operator()(Block& _block) override; + + using ASTModifier::visit; + virtual void visit(Expression& _expression) override; + virtual void visit(Statement& _st) override; + +private: + + /// Visits a list of expressions (usually an argument list to a function call) and tries + /// to inline them. If one of them is inlined, all right of it have to be moved to the front + /// (to keep the order of evaluation). If @a _moveToFront is true, all elements are moved + /// to the front. @a _nameHints and @_types are used for the newly created variables, but + /// both can be empty. + void visitArguments( + std::vector<Expression>& _arguments, + std::vector<std::string> const& _nameHints = {}, + std::vector<std::string> const& _types = {}, + bool _moveToFront = false + ); + + /// Visits an expression, but saves and restores the current statements to prefix and returns + /// the statements that should be prefixed for @a _expression. + std::vector<Statement> visitRecursively(Expression& _expression); + + std::string newName(std::string const& _prefix); + + /// @returns an expression returning nothing. + Expression noop(SourceLocation const& _location); + + /// List of statements that should go in front of the currently visited AST element, + /// at the statement level. + std::vector<Statement> m_statementsToPrefix; + std::string m_currentFunction; + FullInliner& m_driver; + NameDispenser& m_nameDispenser; +}; + +/** + * Creates a copy of a block that is supposed to be the body of a function. + * Applies replacements to referenced variables and creates new names for + * variable declarations. + */ +class BodyCopier: public ASTCopier +{ +public: + BodyCopier( + NameDispenser& _nameDispenser, + std::string const& _varNamePrefix, + std::map<std::string, std::string> const& _variableReplacements + ): + m_nameDispenser(_nameDispenser), + m_varNamePrefix(_varNamePrefix), + m_variableReplacements(_variableReplacements) + {} + + using ASTCopier::operator (); + + virtual Statement operator()(VariableDeclaration const& _varDecl) override; + virtual Statement operator()(FunctionDefinition const& _funDef) override; + + virtual std::string translateIdentifier(std::string const& _name) override; + + NameDispenser& m_nameDispenser; + std::string const& m_varNamePrefix; + std::map<std::string, std::string> m_variableReplacements; +}; + + +} +} diff --git a/libjulia/optimiser/NameCollector.cpp b/libjulia/optimiser/NameCollector.cpp index 510ee289..c0d0b707 100644 --- a/libjulia/optimiser/NameCollector.cpp +++ b/libjulia/optimiser/NameCollector.cpp @@ -35,7 +35,6 @@ void NameCollector::operator()(VariableDeclaration const& _varDecl) void NameCollector::operator ()(FunctionDefinition const& _funDef) { m_names.insert(_funDef.name); - m_functions[_funDef.name] = &_funDef; for (auto const arg: _funDef.parameters) m_names.insert(arg.name); for (auto const ret: _funDef.returnVariables) diff --git a/libjulia/optimiser/NameCollector.h b/libjulia/optimiser/NameCollector.h index 2d4a1d4b..29856172 100644 --- a/libjulia/optimiser/NameCollector.h +++ b/libjulia/optimiser/NameCollector.h @@ -37,15 +37,18 @@ namespace julia class NameCollector: public ASTWalker { public: + explicit NameCollector(Block const& _block) + { + (*this)(_block); + } + using ASTWalker::operator (); virtual void operator()(VariableDeclaration const& _varDecl) override; virtual void operator()(FunctionDefinition const& _funDef) override; - std::set<std::string> const& names() const { return m_names; } - std::map<std::string, FunctionDefinition const*> const& functions() const { return m_functions; } + std::set<std::string> names() const { return m_names; } private: std::set<std::string> m_names; - std::map<std::string, FunctionDefinition const*> m_functions; }; /** diff --git a/libjulia/optimiser/NameDispenser.cpp b/libjulia/optimiser/NameDispenser.cpp new file mode 100644 index 00000000..e4f0e4f6 --- /dev/null +++ b/libjulia/optimiser/NameDispenser.cpp @@ -0,0 +1,38 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Optimiser component that can create new unique names. + */ + +#include <libjulia/optimiser/NameDispenser.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; + +string NameDispenser::newName(string const& _prefix) +{ + string name = _prefix; + size_t suffix = 0; + while (name.empty() || m_usedNames.count(name)) + { + suffix++; + name = _prefix + "_" + std::to_string(suffix); + } + m_usedNames.insert(name); + return name; +} diff --git a/libjulia/optimiser/NameDispenser.h b/libjulia/optimiser/NameDispenser.h new file mode 100644 index 00000000..91c43d54 --- /dev/null +++ b/libjulia/optimiser/NameDispenser.h @@ -0,0 +1,37 @@ +/* + 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/>. +*/ +/** + * Optimiser component that can create new unique names. + */ +#pragma once + +#include <set> +#include <string> + +namespace dev +{ +namespace julia +{ + +struct NameDispenser +{ + std::string newName(std::string const& _prefix); + std::set<std::string> m_usedNames; +}; + +} +} diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 32cf1b18..a222bdf0 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1076,6 +1076,7 @@ void TypeChecker::endVisit(EmitStatement const& _emit) bool TypeChecker::visit(VariableDeclarationStatement const& _statement) { + bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); if (!_statement.initialValue()) { // No initial value is only permitted for single variables with specified type. @@ -1092,7 +1093,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (varDecl.referenceLocation() == VariableDeclaration::Location::Default) errorText += " Did you mean '<type> memory " + varDecl.name() + "'?"; solAssert(m_scope, ""); - if (m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) + if (v050) m_errorReporter.declarationError(varDecl.location(), errorText); else m_errorReporter.warning(varDecl.location(), errorText); @@ -1132,12 +1133,33 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) ") in value for variable assignment (0) needed" ); } - else if (valueTypes.size() != variables.size() && !variables.front() && !variables.back()) - m_errorReporter.fatalTypeError( - _statement.location(), - "Wildcard both at beginning and end of variable declaration list is only allowed " - "if the number of components is equal." - ); + else if (valueTypes.size() != variables.size()) + { + if (v050) + m_errorReporter.fatalTypeError( + _statement.location(), + "Different number of components on the left hand side (" + + toString(variables.size()) + + ") than on the right hand side (" + + toString(valueTypes.size()) + + ")." + ); + else if (!variables.front() && !variables.back()) + m_errorReporter.fatalTypeError( + _statement.location(), + "Wildcard both at beginning and end of variable declaration list is only allowed " + "if the number of components is equal." + ); + else + m_errorReporter.warning( + _statement.location(), + "Different number of components on the left hand side (" + + toString(variables.size()) + + ") than on the right hand side (" + + toString(valueTypes.size()) + + ")." + ); + } size_t minNumValues = variables.size(); if (!variables.empty() && (!variables.back() || !variables.front())) --minNumValues; @@ -1335,6 +1357,7 @@ bool TypeChecker::visit(Conditional const& _conditional) bool TypeChecker::visit(Assignment const& _assignment) { + bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); requireLValue(_assignment.leftHandSide()); TypePointer t = type(_assignment.leftHandSide()); _assignment.annotation().type = t; @@ -1347,11 +1370,29 @@ bool TypeChecker::visit(Assignment const& _assignment) ); // Sequenced assignments of tuples is not valid, make the result a "void" type. _assignment.annotation().type = make_shared<TupleType>(); + expectType(_assignment.rightHandSide(), *tupleType); // expectType does not cause fatal errors, so we have to check again here. - if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get())) + if (TupleType const* rhsType = dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get())) + { checkDoubleStorageAssignment(_assignment); + // @todo For 0.5.0, this code shoud move to TupleType::isImplicitlyConvertibleTo, + // but we cannot do it right now. + if (rhsType->components().size() != tupleType->components().size()) + { + string message = + "Different number of components on the left hand side (" + + toString(tupleType->components().size()) + + ") than on the right hand side (" + + toString(rhsType->components().size()) + + ")."; + if (v050) + m_errorReporter.typeError(_assignment.location(), message); + else + m_errorReporter.warning(_assignment.location(), message); + } + } } else if (t->category() == Type::Category::Mapping) { diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index fc1ff0eb..a39e799c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -110,7 +110,7 @@ void CompilerUtils::loadFromMemoryDynamic( bool _padToWordBoundaries, bool _keepUpdatedMemoryOffset ) -{ +{ if (_keepUpdatedMemoryOffset) m_context << Instruction::DUP1; @@ -396,7 +396,7 @@ void CompilerUtils::encodeToMemory( // leave end_of_mem as dyn head pointer m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; dynPointers++; - solAssert((argSize + dynPointers) < 16, "Stack too deep, try using less variables."); + solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables."); } else { @@ -741,7 +741,7 @@ void CompilerUtils::convertType( else if (targetTypeCategory == Type::Category::FixedPoint) { solAssert( - stackTypeCategory == Type::Category::Integer || + stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::RationalNumber || stackTypeCategory == Type::Category::FixedPoint, "Invalid conversion to FixedMxNType requested." diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 7f618e07..d3b0808b 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -276,7 +276,7 @@ assembly::Expression Parser::parseExpression() int args = instructionInfo(instr.instruction).args; if (args > 0 && currentToken() != Token::LParen) fatalParserError(string( - "Expected token \"(\" (\"" + + "Expected '(' (instruction \"" + instructionNames().at(instr.instruction) + "\" expects " + boost::lexical_cast<string>(args) + @@ -504,7 +504,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) /// check for premature closing parentheses if (currentToken() == Token::RParen) fatalParserError(string( - "Expected expression (\"" + + "Expected expression (instruction \"" + instructionNames().at(instr) + "\" expects " + boost::lexical_cast<string>(args) + @@ -516,7 +516,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) { if (currentToken() != Token::Comma) fatalParserError(string( - "Expected comma (\"" + + "Expected ',' (instruction \"" + instructionNames().at(instr) + "\" expects " + boost::lexical_cast<string>(args) + @@ -529,7 +529,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) ret.location.end = endPosition(); if (currentToken() == Token::Comma) fatalParserError(string( - "Expected ')' (\"" + + "Expected ')' (instruction \"" + instructionNames().at(instr) + "\" expects " + boost::lexical_cast<string>(args) + diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 37732a37..49745e29 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -528,7 +528,7 @@ ASTPointer<EnumDefinition> Parser::parseEnumDefinition() break; expectToken(Token::Comma); if (m_scanner->currentToken() != Token::Identifier) - fatalParserError(string("Expected Identifier after ','")); + fatalParserError(string("Expected identifier after ','")); } if (members.size() == 0) parserError({"enum with no members is not allowed."}); diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index 617a1779..d0c7a551 100644 --- a/libsolidity/parsing/ParserBase.cpp +++ b/libsolidity/parsing/ParserBase.cpp @@ -68,35 +68,24 @@ void ParserBase::expectToken(Token::Value _value, bool _advance) Token::Value tok = m_scanner->currentToken(); if (tok != _value) { - if (Token::isReservedKeyword(tok)) + auto tokenName = [this](Token::Value _token) { - fatalParserError( - string("Expected token ") + - string(Token::name(_value)) + - string(" got reserved keyword '") + - string(Token::name(tok)) + - string("'") - ); - } - else if (Token::isElementaryTypeName(tok)) //for the sake of accuracy in reporting - { - ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); - fatalParserError( - string("Expected token ") + - string(Token::name(_value)) + - string(" got '") + - elemTypeName.toString() + - string("'") - ); - } - else - fatalParserError( - string("Expected token ") + - string(Token::name(_value)) + - string(" got '") + - string(Token::name(m_scanner->currentToken())) + - string("'") - ); + if (_token == Token::Identifier) + return string("identifier"); + else if (_token == Token::EOS) + return string("end of source"); + else if (Token::isReservedKeyword(_token)) + return string("reserved keyword '") + Token::friendlyName(_token) + "'"; + else if (Token::isElementaryTypeName(_token)) //for the sake of accuracy in reporting + { + ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); + return string("'") + elemTypeName.toString() + "'"; + } + else + return string("'") + Token::friendlyName(_token) + "'"; + }; + + fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok)); } if (_advance) m_scanner->next(); diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index 805fbf5d..4d7a7bc6 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -304,6 +304,17 @@ public: return m_string[tok]; } + static std::string friendlyName(Value tok) + { + char const* ret = toString(tok); + if (ret == nullptr) + { + ret = name(tok); + solAssert(ret != nullptr, ""); + } + return std::string(ret); + } + // @returns the precedence > 0 for binary and compare // operators; returns 0 otherwise. static int precedence(Value tok) diff --git a/scripts/release.bat b/scripts/release.bat index 54d85862..be95b35e 100644 --- a/scripts/release.bat +++ b/scripts/release.bat @@ -27,7 +27,19 @@ REM Copyright (c) 2016 solidity contributors. REM --------------------------------------------------------------------------- set CONFIGURATION=%1 +set VERSION=%2 + +IF "%VERSION%"=="2015" ( + set "DLLS=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" +) ELSE ( + + IF EXIST "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\14.13.26020\x86\Microsoft.VC141.CRT\" ( + set "DLLS=C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\14.13.26020\x86\Microsoft.VC141.CRT\msvc*.dll" + ) ELSE ( + set "DLLS=C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Redist\MSVC\14.13.26020\x86\Microsoft.VC141.CRT\msvc*.dll" + ) +) 7z a solidity-windows.zip ^ .\build\solc\%CONFIGURATION%\solc.exe .\build\test\%CONFIGURATION%\soltest.exe ^ - "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" + "%DLLS%" diff --git a/test/libjulia/Inliner.cpp b/test/libjulia/Inliner.cpp index 88b51f28..464dcd93 100644 --- a/test/libjulia/Inliner.cpp +++ b/test/libjulia/Inliner.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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/>. + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. */ /** * @date 2017 @@ -23,6 +23,9 @@ #include <libjulia/optimiser/ExpressionInliner.h> #include <libjulia/optimiser/InlinableExpressionFunctionFinder.h> +#include <libjulia/optimiser/FullInliner.h> +#include <libjulia/optimiser/FunctionHoister.h> +#include <libjulia/optimiser/FunctionGrouper.h> #include <libsolidity/inlineasm/AsmPrinter.h> @@ -58,8 +61,17 @@ string inlineFunctions(string const& _source, bool _julia = true) ExpressionInliner(ast).run(); return assembly::AsmPrinter(_julia)(ast); } +string fullInline(string const& _source, bool _julia = true) +{ + Block ast = disambiguate(_source, _julia); + (FunctionHoister{})(ast); + (FunctionGrouper{})(ast);\ + FullInliner(ast).run(); + return assembly::AsmPrinter(_julia)(ast); +} } + BOOST_AUTO_TEST_SUITE(IuliaInlinableFunctionFilter) BOOST_AUTO_TEST_CASE(smoke_test) @@ -197,3 +209,135 @@ BOOST_AUTO_TEST_CASE(double_recursive_calls) } BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(IuliaFullInliner) + +BOOST_AUTO_TEST_CASE(simple) +{ + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a) -> x { let r := mul(a, a) x := add(r, r) }" + "let y := add(f(sload(mload(2))), mload(7))" + "}", false), + format("{" + "{" + "let _1 := mload(7)" + "let f_a := sload(mload(2))" + "let f_x" + "{" + "let f_r := mul(f_a, f_a)" + "f_x := add(f_r, f_r)" + "}" + "let y := add(f_x, _1)" + "}" + "function f(a) -> x" + "{" + "let r := mul(a, a)" + "x := add(r, r)" + "}" + "}", false) + ); +} + +BOOST_AUTO_TEST_CASE(multi_fun) +{ + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a) -> x { x := add(a, a) }" + "function g(b, c) -> y { y := mul(mload(c), f(b)) }" + "let y := g(f(3), 7)" + "}", false), + format("{" + "{" + "let g_c := 7 " + "let f_a_1 := 3 " + "let f_x_1 " + "{ f_x_1 := add(f_a_1, f_a_1) } " + "let g_y " + "{" + "let g_f_a := f_x_1 " + "let g_f_x " + "{" + "g_f_x := add(g_f_a, g_f_a)" + "}" + "g_y := mul(mload(g_c), g_f_x)" + "}" + "let y_1 := g_y" + "}" + "function f(a) -> x" + "{" + "x := add(a, a)" + "}" + "function g(b, c) -> y" + "{" + "let f_a := b " + "let f_x " + "{" + "f_x := add(f_a, f_a)" + "}" + "y := mul(mload(c), f_x)" + "}" + "}", false) + ); +} + +BOOST_AUTO_TEST_CASE(move_up_rightwards_arguments) +{ + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a, b, c) -> x { x := add(a, b) x := mul(x, c) }" + "let y := add(mload(1), add(f(mload(2), mload(3), mload(4)), mload(5)))" + "}", false), + format("{" + "{" + "let _1 := mload(5)" + "let f_c := mload(4)" + "let f_b := mload(3)" + "let f_a := mload(2)" + "let f_x" + "{" + "f_x := add(f_a, f_b)" + "f_x := mul(f_x, f_c)" + "}" + "let y := add(mload(1), add(f_x, _1))" + "}" + "function f(a, b, c) -> x" + "{" + "x := add(a, b)" + "x := mul(x, c)" + "}" + "}", false) + ); +} + +BOOST_AUTO_TEST_CASE(pop_result) +{ + // This tests that `pop(r)` is removed. + BOOST_CHECK_EQUAL( + fullInline("{" + "function f(a) -> x { let r := mul(a, a) x := add(r, r) }" + "pop(add(f(7), 2))" + "}", false), + format("{" + "{" + "let _1 := 2 " + "let f_a := 7 " + "let f_x " + "{" + "let f_r := mul(f_a, f_a) " + "f_x := add(f_r, f_r)" + "}" + "{" + "}" + "}" + "function f(a) -> x" + "{" + "let r := mul(a, a) " + "x := add(r, r)" + "}" + "}", false) + ); +} + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index 9d66658e..96261dec 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -212,10 +212,10 @@ BOOST_AUTO_TEST_CASE(tokens_as_identifers) BOOST_AUTO_TEST_CASE(lacking_types) { - CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected token Identifier got 'Assign'"); - CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected token Colon got 'RBrace'"); - CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected token Colon got 'RParen'"); - CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected token Colon got 'LBrace'"); + CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected identifier but got '='"); + CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected ':' but got '}'"); + CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected ':' but got ')'"); + CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected ':' but got '{'"); } BOOST_AUTO_TEST_CASE(invalid_types) @@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(if_statement) BOOST_AUTO_TEST_CASE(if_statement_invalid) { CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); - CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected token LBrace"); + CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected '{' but got reserved keyword 'let'"); // TODO change this to an error once we check types. BOOST_CHECK(successParse("{ if 42:u256 { } }")); } diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 0d66456c..f16d9abe 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -307,6 +307,46 @@ BOOST_AUTO_TEST_CASE(regular_functions_exclude_fallback) testCreationTimeGas(sourceCode); testRunTimeGas("x()", vector<bytes>{encodeArgs()}); } + +BOOST_AUTO_TEST_CASE(complex_control_flow) +{ + // This crashed the gas estimator previously (or took a very long time). + // Now we do not follow branches if they start out with lower gas costs than the ones + // we previously considered. This of course reduces accuracy. + char const* sourceCode = R"( + contract log { + function ln(int128 x) constant returns (int128 result) { + int128 t = x / 256; + int128 y = 5545177; + x = t; + t = x * 16; if (t <= 1000000) { x = t; y = y - 2772588; } + t = x * 4; if (t <= 1000000) { x = t; y = y - 1386294; } + t = x * 2; if (t <= 1000000) { x = t; y = y - 693147; } + t = x + x / 2; if (t <= 1000000) { x = t; y = y - 405465; } + t = x + x / 4; if (t <= 1000000) { x = t; y = y - 223144; } + t = x + x / 8; if (t <= 1000000) { x = t; y = y - 117783; } + t = x + x / 16; if (t <= 1000000) { x = t; y = y - 60624; } + t = x + x / 32; if (t <= 1000000) { x = t; y = y - 30771; } + t = x + x / 64; if (t <= 1000000) { x = t; y = y - 15504; } + t = x + x / 128; if (t <= 1000000) { x = t; y = y - 7782; } + t = x + x / 256; if (t <= 1000000) { x = t; y = y - 3898; } + t = x + x / 512; if (t <= 1000000) { x = t; y = y - 1951; } + t = x + x / 1024; if (t <= 1000000) { x = t; y = y - 976; } + t = x + x / 2048; if (t <= 1000000) { x = t; y = y - 488; } + t = x + x / 4096; if (t <= 1000000) { x = t; y = y - 244; } + t = x + x / 8192; if (t <= 1000000) { x = t; y = y - 122; } + t = x + x / 16384; if (t <= 1000000) { x = t; y = y - 61; } + t = x + x / 32768; if (t <= 1000000) { x = t; y = y - 31; } + t = x + x / 65536; if (t <= 1000000) { y = y - 15; } + return y; + } + } + )"; + testCreationTimeGas(sourceCode); + // max gas is used for small x + testRunTimeGas("ln(int128)", vector<bytes>{encodeArgs(0), encodeArgs(10), encodeArgs(105), encodeArgs(30000)}); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 0ced1792..181ca959 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -170,7 +170,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_AUTO_TEST_CASE(surplus_input) { - CHECK_PARSE_ERROR("{ } { }", ParserError, "Expected token EOS"); + CHECK_PARSE_ERROR("{ } { }", ParserError, "Expected end of source but got '{'"); } BOOST_AUTO_TEST_CASE(simple_instructions) @@ -246,7 +246,7 @@ BOOST_AUTO_TEST_CASE(functional) BOOST_AUTO_TEST_CASE(functional_partial) { - CHECK_PARSE_ERROR("{ let x := byte }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ let x := byte }", ParserError, "Expected '(' (instruction \"byte\" expects 2 arguments)"); } BOOST_AUTO_TEST_CASE(functional_partial_success) @@ -290,10 +290,10 @@ BOOST_AUTO_TEST_CASE(if_statement_scope) BOOST_AUTO_TEST_CASE(if_statement_invalid) { - CHECK_PARSE_ERROR("{ if mload {} }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ if mload {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)"); BOOST_CHECK("{ if calldatasize() {}"); CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); - CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace"); + CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected '{' but got reserved keyword 'let'"); } BOOST_AUTO_TEST_CASE(switch_statement) @@ -320,7 +320,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case) BOOST_AUTO_TEST_CASE(switch_invalid_expression) { CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); - CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)"); CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } @@ -341,7 +341,7 @@ BOOST_AUTO_TEST_CASE(switch_invalid_case) BOOST_AUTO_TEST_CASE(switch_invalid_body) { - CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected token LBrace got 'Identifier'"); + CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected '{' but got identifier"); } BOOST_AUTO_TEST_CASE(for_statement) @@ -353,10 +353,10 @@ BOOST_AUTO_TEST_CASE(for_statement) BOOST_AUTO_TEST_CASE(for_invalid_expression) { CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected."); - CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected token LBrace got 'Number'"); - CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected token LBrace got 'Number'"); - CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected token LBrace got 'Number'"); - CHECK_PARSE_ERROR("{ for {} mload {} {} }", ParserError, "Expected token \"(\""); + CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected '{' but got 'Number'"); + CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected '{' but got 'Number'"); + CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected '{' but got 'Number'"); + CHECK_PARSE_ERROR("{ for {} mload {} {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)"); CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); } @@ -437,13 +437,13 @@ BOOST_AUTO_TEST_CASE(invalid_tuple_assignment) BOOST_AUTO_TEST_CASE(instruction_too_few_arguments) { - CHECK_PARSE_ERROR("{ mul() }", ParserError, "Expected expression (\"mul\" expects 2 arguments)"); - CHECK_PARSE_ERROR("{ mul(1) }", ParserError, "Expected comma (\"mul\" expects 2 arguments)"); + CHECK_PARSE_ERROR("{ mul() }", ParserError, "Expected expression (instruction \"mul\" expects 2 arguments)"); + CHECK_PARSE_ERROR("{ mul(1) }", ParserError, "Expected ',' (instruction \"mul\" expects 2 arguments)"); } BOOST_AUTO_TEST_CASE(instruction_too_many_arguments) { - CHECK_PARSE_ERROR("{ mul(1, 2, 3) }", ParserError, "Expected ')' (\"mul\" expects 2 arguments)"); + CHECK_PARSE_ERROR("{ mul(1, 2, 3) }", ParserError, "Expected ')' (instruction \"mul\" expects 2 arguments)"); } BOOST_AUTO_TEST_CASE(recursion_depth) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index a2540302..91fd1fff 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2894,7 +2894,7 @@ BOOST_AUTO_TEST_CASE(dynamic_return_types_not_possible) contract C { function f(uint) public returns (string); function g() public { - var (x,) = this.f(2); + var x = this.f(2); // we can assign to x but it is not usable. bytes(x).length; } @@ -5833,80 +5833,6 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop) CHECK_SUCCESS(text); } -BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - (x, y) = (y, x); - } - } - )"; - CHECK_WARNING(text, "This assignment performs two copies to storage."); -} - -BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_right) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - (x, y, ) = (y, x, 1, 2); - } - } - )"; - CHECK_WARNING(text, "This assignment performs two copies to storage."); -} - -BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_left) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - (,x, y) = (1, 2, y, x); - } - } - )"; - CHECK_WARNING(text, "This assignment performs two copies to storage."); -} - -BOOST_AUTO_TEST_CASE(nowarn_swap_memory) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - function f() pure public { - S memory x; - S memory y; - (x, y) = (y, x); - } - } - )"; - CHECK_SUCCESS_NO_WARNINGS(text); -} - -BOOST_AUTO_TEST_CASE(nowarn_swap_storage_pointers) -{ - char const* text = R"( - contract C { - struct S { uint a; uint b; } - S x; S y; - function f() public { - S storage x_local = x; - S storage y_local = y; - S storage z_local = x; - (x, y_local, x_local, z_local) = (y, x_local, y_local, y); - } - } - )"; - CHECK_SUCCESS_NO_WARNINGS(text); -} - BOOST_AUTO_TEST_CASE(warn_unused_local) { char const* text = R"( diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index f428f892..77686b03 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -992,7 +992,7 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved) for (const auto& keyword: keywords) { auto text = std::string("contract ") + keyword + " {}"; - CHECK_PARSE_ERROR(text.c_str(), "Expected token Identifier got reserved keyword"); + CHECK_PARSE_ERROR(text.c_str(), "Expected identifier but got reserved keyword"); } } diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index f816905c..560c9013 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -326,8 +326,8 @@ BOOST_AUTO_TEST_CASE(compilation_error) { BOOST_CHECK_EQUAL( dev::jsonCompactPrint(error), - "{\"component\":\"general\",\"formattedMessage\":\"fileA:1:23: ParserError: Expected token Identifier got 'RBrace'\\n" - "contract A { function }\\n ^\\n\",\"message\":\"Expected token Identifier got 'RBrace'\"," + "{\"component\":\"general\",\"formattedMessage\":\"fileA:1:23: ParserError: Expected identifier but got '}'\\n" + "contract A { function }\\n ^\\n\",\"message\":\"Expected identifier but got '}'\"," "\"severity\":\"error\",\"sourceLocation\":{\"end\":22,\"file\":\"fileA\",\"start\":22},\"type\":\"ParserError\"}" ); } diff --git a/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol b/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol index b7763d28..766b98b3 100644 --- a/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol +++ b/test/libsolidity/syntaxTests/functionTypes/function_type_constructor_local.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// ParserError: (118-118): Expected token Semicolon got 'Identifier' +// ParserError: (118-118): Expected ';' but got identifier diff --git a/test/libsolidity/syntaxTests/parsing/constant_is_keyword.sol b/test/libsolidity/syntaxTests/parsing/constant_is_keyword.sol index 59fe8518..da3fa1c6 100644 --- a/test/libsolidity/syntaxTests/parsing/constant_is_keyword.sol +++ b/test/libsolidity/syntaxTests/parsing/constant_is_keyword.sol @@ -2,4 +2,4 @@ contract Foo { uint constant = 4; } // ---- -// ParserError: (30-30): Expected token Identifier got 'Assign' +// ParserError: (30-30): Expected identifier but got '=' diff --git a/test/libsolidity/syntaxTests/parsing/emit_without_event.sol b/test/libsolidity/syntaxTests/parsing/emit_without_event.sol index 5916fc2b..1af1d4ab 100644 --- a/test/libsolidity/syntaxTests/parsing/emit_without_event.sol +++ b/test/libsolidity/syntaxTests/parsing/emit_without_event.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// ParserError: (49-49): Expected token LParen got 'Semicolon' +// ParserError: (49-49): Expected '(' but got ';' diff --git a/test/libsolidity/syntaxTests/parsing/event_with_no_argument_list.sol b/test/libsolidity/syntaxTests/parsing/event_with_no_argument_list.sol index ae2591db..850c3627 100644 --- a/test/libsolidity/syntaxTests/parsing/event_with_no_argument_list.sol +++ b/test/libsolidity/syntaxTests/parsing/event_with_no_argument_list.sol @@ -2,4 +2,4 @@ contract c { event e; } // ---- -// ParserError: (21-21): Expected token LParen got 'Semicolon' +// ParserError: (21-21): Expected '(' but got ';' diff --git a/test/libsolidity/syntaxTests/parsing/external_variable.sol b/test/libsolidity/syntaxTests/parsing/external_variable.sol index 1d2e65e6..2de70e23 100644 --- a/test/libsolidity/syntaxTests/parsing/external_variable.sol +++ b/test/libsolidity/syntaxTests/parsing/external_variable.sol @@ -2,4 +2,4 @@ contract c { uint external x; } // ---- -// ParserError: (19-19): Expected token Identifier got 'External' +// ParserError: (19-19): Expected identifier but got 'external' diff --git a/test/libsolidity/syntaxTests/parsing/fixed_literal_with_double_radix.sol b/test/libsolidity/syntaxTests/parsing/fixed_literal_with_double_radix.sol index 43bb61fa..b3adc03d 100644 --- a/test/libsolidity/syntaxTests/parsing/fixed_literal_with_double_radix.sol +++ b/test/libsolidity/syntaxTests/parsing/fixed_literal_with_double_radix.sol @@ -2,4 +2,4 @@ contract A { fixed40x40 pi = 3.14.15; } // ---- -// ParserError: (34-34): Expected token Semicolon got 'Number' +// ParserError: (34-34): Expected ';' but got 'Number' diff --git a/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_modifiers.sol b/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_modifiers.sol index 12480459..198d250b 100644 --- a/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_modifiers.sol +++ b/test/libsolidity/syntaxTests/parsing/function_type_as_storage_variable_with_modifiers.sol @@ -2,4 +2,4 @@ contract test { function (uint, uint) modifier1() returns (uint) f1; } // ---- -// ParserError: (66-66): Expected token LBrace got 'Identifier' +// ParserError: (66-66): Expected '{' but got identifier diff --git a/test/libsolidity/syntaxTests/parsing/local_const_variable.sol b/test/libsolidity/syntaxTests/parsing/local_const_variable.sol index 55673160..f06e2501 100644 --- a/test/libsolidity/syntaxTests/parsing/local_const_variable.sol +++ b/test/libsolidity/syntaxTests/parsing/local_const_variable.sol @@ -6,4 +6,4 @@ contract Foo { } } // ---- -// ParserError: (67-67): Expected token Semicolon got 'Constant' +// ParserError: (67-67): Expected ';' but got 'constant' diff --git a/test/libsolidity/syntaxTests/parsing/location_specifiers_for_state_variables.sol b/test/libsolidity/syntaxTests/parsing/location_specifiers_for_state_variables.sol index 0fc85177..9cc7a4fa 100644 --- a/test/libsolidity/syntaxTests/parsing/location_specifiers_for_state_variables.sol +++ b/test/libsolidity/syntaxTests/parsing/location_specifiers_for_state_variables.sol @@ -2,4 +2,4 @@ contract Foo { uint[] memory x; } // ---- -// ParserError: (23-23): Expected token Identifier got 'Memory' +// ParserError: (23-23): Expected identifier but got 'memory' diff --git a/test/libsolidity/syntaxTests/parsing/malformed_enum_declaration.sol b/test/libsolidity/syntaxTests/parsing/malformed_enum_declaration.sol index 5a6eb270..9fc7efff 100644 --- a/test/libsolidity/syntaxTests/parsing/malformed_enum_declaration.sol +++ b/test/libsolidity/syntaxTests/parsing/malformed_enum_declaration.sol @@ -2,4 +2,4 @@ contract c { enum foo { WARNING,} } // ---- -// ParserError: (33-33): Expected Identifier after ',' +// ParserError: (33-33): Expected identifier after ',' diff --git a/test/libsolidity/syntaxTests/parsing/missing_parameter_name_in_named_args.sol b/test/libsolidity/syntaxTests/parsing/missing_parameter_name_in_named_args.sol index 3604f3b2..b950c76a 100644 --- a/test/libsolidity/syntaxTests/parsing/missing_parameter_name_in_named_args.sol +++ b/test/libsolidity/syntaxTests/parsing/missing_parameter_name_in_named_args.sol @@ -3,4 +3,4 @@ contract test { function b() returns (uint r) { r = a({: 1, : 2, : 3}); } } // ---- -// ParserError: (143-143): Expected token Identifier got 'Colon' +// ParserError: (143-143): Expected identifier but got ':' diff --git a/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol b/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol index bb1d015b..15927f50 100644 --- a/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol +++ b/test/libsolidity/syntaxTests/parsing/missing_variable_name_in_declaration.sol @@ -2,4 +2,4 @@ contract test { uint256 ; } // ---- -// ParserError: (28-28): Expected token Identifier got 'Semicolon' +// ParserError: (28-28): Expected identifier but got ';' diff --git a/test/libsolidity/syntaxTests/parsing/modifier_without_semicolon.sol b/test/libsolidity/syntaxTests/parsing/modifier_without_semicolon.sol index 0d719db4..1b488837 100644 --- a/test/libsolidity/syntaxTests/parsing/modifier_without_semicolon.sol +++ b/test/libsolidity/syntaxTests/parsing/modifier_without_semicolon.sol @@ -2,4 +2,4 @@ contract c { modifier mod { if (msg.sender == 0) _ } } // ---- -// ParserError: (52-52): Expected token Semicolon got 'RBrace' +// ParserError: (52-52): Expected ';' but got '}' diff --git a/test/libsolidity/syntaxTests/parsing/payable_accessor.sol b/test/libsolidity/syntaxTests/parsing/payable_accessor.sol index 44b04afd..46bf6e13 100644 --- a/test/libsolidity/syntaxTests/parsing/payable_accessor.sol +++ b/test/libsolidity/syntaxTests/parsing/payable_accessor.sol @@ -2,4 +2,4 @@ contract test { uint payable x; } // ---- -// ParserError: (22-22): Expected token Identifier got 'Payable' +// ParserError: (22-22): Expected identifier but got 'payable' diff --git a/test/libsolidity/syntaxTests/parsing/tuples_without_commas.sol b/test/libsolidity/syntaxTests/parsing/tuples_without_commas.sol index d0e376b0..e283e0bb 100644 --- a/test/libsolidity/syntaxTests/parsing/tuples_without_commas.sol +++ b/test/libsolidity/syntaxTests/parsing/tuples_without_commas.sol @@ -4,4 +4,4 @@ contract C { } } // ---- -// ParserError: (42-42): Expected token Comma got 'Number' +// ParserError: (42-42): Expected ',' but got 'Number' diff --git a/test/libsolidity/syntaxTests/parsing/var_array.sol b/test/libsolidity/syntaxTests/parsing/var_array.sol index 86fc4fcb..d635a04b 100644 --- a/test/libsolidity/syntaxTests/parsing/var_array.sol +++ b/test/libsolidity/syntaxTests/parsing/var_array.sol @@ -2,4 +2,4 @@ contract Foo { function f() { var[] a; } } // ---- -// ParserError: (34-34): Expected token Identifier got 'LBrack' +// ParserError: (34-34): Expected identifier but got '[' diff --git a/test/libsolidity/syntaxTests/tupleAssignments/error_fill.sol b/test/libsolidity/syntaxTests/tupleAssignments/error_fill.sol new file mode 100644 index 00000000..5b7f870b --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/error_fill.sol @@ -0,0 +1,12 @@ +pragma experimental "v0.5.0"; +contract C { + function f() public pure returns (uint, uint, bytes32) { + uint a; + bytes32 b; + (a,) = f(); + (,b) = f(); + } +} +// ---- +// TypeError: (126-136): Different number of components on the left hand side (2) than on the right hand side (3). +// TypeError: (140-150): Different number of components on the left hand side (2) than on the right hand side (3). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/large_component_count.sol b/test/libsolidity/syntaxTests/tupleAssignments/large_component_count.sol new file mode 100644 index 00000000..bbf21d7e --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/large_component_count.sol @@ -0,0 +1,24 @@ +pragma experimental "v0.5.0"; +contract C { + function g() public pure returns ( + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint, + uint + ) { } + function f() public pure returns (uint, uint, bytes32) { + uint a; + uint b; + (,,,,a,,,,b,,,,) = g(); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/tupleAssignments/nowarn_explicit_singleton_token_expression.sol b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_explicit_singleton_token_expression.sol new file mode 100644 index 00000000..3262781b --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_explicit_singleton_token_expression.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + uint a; + (a,) = (uint(1),); + } +} +// ---- +// Warning: (53-70): Different number of components on the left hand side (2) than on the right hand side (1). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_memory.sol b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_memory.sol new file mode 100644 index 00000000..b20bbf90 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_memory.sol @@ -0,0 +1,8 @@ +contract C { + struct S { uint a; uint b; } + function f() pure public { + S memory x; + S memory y; + (x, y) = (y, x); + } +} diff --git a/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_storage_pointers.sol b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_storage_pointers.sol new file mode 100644 index 00000000..5f7a18f7 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/nowarn_swap_storage_pointers.sol @@ -0,0 +1,10 @@ + contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + S storage x_local = x; + S storage y_local = y; + S storage z_local = x; + (x, y_local, x_local, z_local) = (y, x_local, y_local, y); + } + } diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_assignment.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_assignment.sol new file mode 100644 index 00000000..a079a509 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_assignment.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure returns (uint, uint, bytes32) { + uint a; + bytes32 b; + (a,) = f(); + (,b) = f(); + } +} +// ---- +// Warning: (96-106): Different number of components on the left hand side (2) than on the right hand side (3). +// Warning: (110-120): Different number of components on the left hand side (2) than on the right hand side (3). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_vardecl.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_vardecl.sol new file mode 100644 index 00000000..1d243c7c --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_fill_vardecl.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure returns (uint, uint, uint, uint) { + // Can later be replaced by (uint a, uint b,) = f(); + var (a,b,) = f(); + a; b; + } +} +// ---- +// Warning: (136-137): Use of the "var" keyword is deprecated. +// Warning: (138-139): Use of the "var" keyword is deprecated. +// Warning: (131-147): Different number of components on the left hand side (3) than on the right hand side (4). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies.sol new file mode 100644 index 00000000..e4c3e694 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies.sol @@ -0,0 +1,9 @@ +contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + (x, y) = (y, x); + } +} +// ---- +// Warning: (79-94): This assignment performs two copies to storage. Since storage copies do not first copy to a temporary location, one of them might be overwritten before the second is executed and thus may have unexpected effects. It is safer to perform the copies separately or assign to storage pointers first. diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_left.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_left.sol new file mode 100644 index 00000000..b2979804 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_left.sol @@ -0,0 +1,10 @@ +contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + (,x, y) = (1, 2, y, x); + } +} +// ---- +// Warning: (79-101): This assignment performs two copies to storage. Since storage copies do not first copy to a temporary location, one of them might be overwritten before the second is executed and thus may have unexpected effects. It is safer to perform the copies separately or assign to storage pointers first. +// Warning: (79-101): Different number of components on the left hand side (3) than on the right hand side (4). diff --git a/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_right.sol b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_right.sol new file mode 100644 index 00000000..aa35d7d4 --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/warn_multiple_storage_storage_copies_fill_right.sol @@ -0,0 +1,10 @@ +contract C { + struct S { uint a; uint b; } + S x; S y; + function f() public { + (x, y, ) = (y, x, 1, 2); + } +} +// ---- +// Warning: (79-102): This assignment performs two copies to storage. Since storage copies do not first copy to a temporary location, one of them might be overwritten before the second is executed and thus may have unexpected effects. It is safer to perform the copies separately or assign to storage pointers first. +// Warning: (79-102): Different number of components on the left hand side (3) than on the right hand side (4). |