diff options
26 files changed, 612 insertions, 54 deletions
diff --git a/docs/assembly.rst b/docs/assembly.rst index 20fb0cd5..5bb9825a 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -282,14 +282,14 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | extcodehash(a) | | C | code hash of address a | +-------------------------+-----+---+-----------------------------------------------------------------+ -| create(v, p, s) | | F | create new contract with code mem[p...(p+s)) and send v wei | +| create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei | | | | | and return the new address | +-------------------------+-----+---+-----------------------------------------------------------------+ -| create2(v, n, p, s) | | C | create new contract with code mem[p...(p+s)) at address | -| | | | keccak256(0xff . self . n . keccak256(mem[p...(p+s))) | +| create2(v, p, n, s) | | C | create new contract with code mem[p...(p+n)) at address | +| | | | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | | | | | and send v wei and return the new address, where ``0xff`` is a | -| | | | 8 byte value, ``self`` is the current contract's address | -| | | | as a 20 byte value and ``n`` is a big-endian 256-bit value | +| | | | 8 byte value, ``this`` is the current contract's address | +| | | | as a 20 byte value and ``s`` is a big-endian 256-bit value | +-------------------------+-----+---+-----------------------------------------------------------------+ | call(g, a, v, in, | | F | call contract at address a with input mem[in...(in+insize)) | | insize, out, outsize) | | | providing g gas and v wei and output area | diff --git a/docs/contracts.rst b/docs/contracts.rst index faef3fc2..d4160eac 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -31,6 +31,11 @@ When a contract is created, its constructor_ (a function declared with the ``co A constructor is optional. Only one constructor is allowed, which means overloading is not supported. +After the constructor has executed, the final code of the contract is deployed to the +blockchain. This code includes all public and external functions and all functions +that are reachable from there through function calls. The deployed code does not +include the constructor code or internal functions only called from the constructor. + .. index:: constructor;arguments Internally, constructor arguments are passed :ref:`ABI encoded <ABI>` after the code of @@ -485,7 +490,11 @@ Functions can be declared ``view`` in which case they promise not to modify the .. note:: If the compiler's EVM target is Byzantium or newer (default) the opcode ``STATICCALL`` is used for ``view`` functions which enforces the state - to stay unmodified as part of the EVM execution. + to stay unmodified as part of the EVM execution. For library ``view`` functions + ``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``. + This means library ``view`` functions do not have run-time checks that prevent state + modifications. This should not impact security negatively because library code is + usually known at compile-time and the static checker performs compile-time checks. The following statements are considered modifying the state: @@ -1085,8 +1094,13 @@ initialisation code. Before the constructor code is executed, state variables are initialised to their specified value if you initialise them inline, or zero if you do not. -After the constructor has run, the final code of the contract is returned. The deployment of +After the constructor has run, the final code of the contract is deployed +to the blockchain. The deployment of the code costs additional gas linear to the length of the code. +This code includes all functions that are part of the public interface +and all functions that are reachable from there through function calls. +It does not include the constructor code or internal functions that are +only called from the constructor. Constructor functions can be either ``public`` or ``internal``. If there is no constructor, the contract will assume the default constructor, which is diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 12603f2e..8cc52c8f 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -34,18 +34,20 @@ Statically-sized variables (everything except mapping and dynamically-sized arra The elements of structs and arrays are stored after each other, just as if they were given explicitly. +Mappings and Dynamic Arrays +=========================== + Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash computation to find the starting position of the value or the array data. These starting positions are always full stack slots. -The mapping or the dynamic array itself -occupies an (unfilled) slot in storage at some position ``p`` according to the above rule (or by -recursively applying this rule for mappings to mappings or arrays of arrays). For a dynamic array, this slot stores the number of elements in the array (byte arrays and strings are an exception here, see below). For a mapping, the slot is unused (but it is needed so that two equal mappings after each other will use a different hash distribution). -Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key +The mapping or the dynamic array itself occupies a slot in storage at some position ``p`` +according to the above rule (or by recursively applying this rule for mappings of mappings or arrays of arrays). For dynamic arrays, +this slot stores the number of elements in the array (byte arrays and strings are an exception, see :ref:`below <bytes-and-string>`). +For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different +hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key ``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``. -``bytes`` and ``string`` store their data in the same slot where also the length is stored if they are short. In particular: If the data is at most ``31`` bytes long, it is stored in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``. If it is longer, the main slot stores ``length * 2 + 1`` and the data is stored as usual in ``keccak256(slot)``. - So for the following contract snippet:: pragma solidity >=0.4.0 <0.6.0; @@ -58,6 +60,21 @@ So for the following contract snippet:: The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``. +.. _bytes-and-string: + +``bytes`` and ``string`` +------------------------ + +``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same +slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored +in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``. +For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is +stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array +by checking if the lowest bit is set: short (not set) and long (set). + +.. note:: + Handling invalidly encoded slots is currently not supported but may be added in the future. + .. index: memory layout **************** diff --git a/docs/yul.rst b/docs/yul.rst index cfeec4db..a55445f3 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -415,14 +415,14 @@ The following functions must be available: +---------------------------------------------+-----------------------------------------------------------------+ | *Execution control* | +---------------------------------------------+-----------------------------------------------------------------+ -| create(v:u256, p:u256, s:u256) | create new contract with code mem[p..(p+s)) and send v wei | +| create(v:u256, p:u256, n:u256) | create new contract with code mem[p..(p+n)) and send v wei | | | and return the new address | +---------------------------------------------+-----------------------------------------------------------------+ -| create2(v:u256, n:u256, p:u256, s:u256) | create new contract with code mem[p...(p+s)) at address | -| | keccak256(0xff . self . n . keccak256(mem[p...(p+s))) | +| create2(v:u256, p:u256, n:u256, s:u256) | create new contract with code mem[p...(p+n)) at address | +| | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | | | and send v wei and return the new address, where ``0xff`` is a | -| | 8 byte value, ``self`` is the current contract's address | -| | as a 20 byte value and ``n`` is a big-endian 256-bit value | +| | 8 byte value, ``this`` is the current contract's address | +| | as a 20 byte value and ``s`` is a big-endian 256-bit value | +---------------------------------------------+-----------------------------------------------------------------+ | call(g:u256, a:u256, v:u256, in:u256, | call contract at address a with input mem[in..(in+insize)) | | insize:u256, out:u256, | providing g gas and v wei and output area | diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index e410af5c..98136b44 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -25,11 +25,14 @@ #include <libdevcore/Common.h> +#include <boost/optional.hpp> + #include <vector> #include <type_traits> #include <cstring> #include <string> #include <set> +#include <functional> namespace dev { @@ -229,6 +232,36 @@ bool contains(T const& _t, V const& _v) return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v); } + +/// Function that iterates over a vector, calling a function on each of its +/// elements. If that function returns a vector, the element is replaced by +/// the returned vector. During the iteration, the original vector is only valid +/// on the current element and after that. The actual replacement takes +/// place at the end, but already visited elements might be invalidated. +/// If nothing is replaced, no copy is performed. +template <class T> +void iterateReplacing(std::vector<T>& _vector, std::function<boost::optional<std::vector<T>>(T&)> _f) +{ + bool useModified = false; + std::vector<T> modifiedVector; + for (size_t i = 0; i < _vector.size(); ++i) + { + if (boost::optional<std::vector<T>> r = _f(_vector[i])) + { + if (!useModified) + { + std::move(_vector.begin(), _vector.begin() + i, back_inserter(modifiedVector)); + useModified = true; + } + modifiedVector += std::move(*r); + } + else if (useModified) + modifiedVector.emplace_back(std::move(_vector[i])); + } + if (useModified) + _vector = std::move(modifiedVector); +} + /// @returns true iff @a _str passess the hex address checksum test. /// @param _strict if false, hex strings with only uppercase or only lowercase letters /// are considered valid. diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 9693d02a..2005d087 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -23,7 +23,6 @@ #include <iostream> #include <cstdlib> #include <fstream> -#include <stdio.h> #if defined(_WIN32) #include <windows.h> #else diff --git a/libjulia/optimiser/ExpressionBreaker.cpp b/libjulia/optimiser/ExpressionBreaker.cpp new file mode 100644 index 00000000..2273fa98 --- /dev/null +++ b/libjulia/optimiser/ExpressionBreaker.cpp @@ -0,0 +1,105 @@ +/* + 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 turns complex expressions into multiple variable + * declarations. + */ + +#include <libjulia/optimiser/ExpressionBreaker.h> + +#include <libjulia/optimiser/ASTWalker.h> + +#include <libsolidity/inlineasm/AsmData.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; + +void ExpressionBreaker::operator()(FunctionalInstruction& _instruction) +{ + for (auto& arg: _instruction.arguments | boost::adaptors::reversed) + outlineExpression(arg); +} + +void ExpressionBreaker::operator()(FunctionCall& _funCall) +{ + for (auto& arg: _funCall.arguments | boost::adaptors::reversed) + outlineExpression(arg); +} + +void ExpressionBreaker::operator()(If& _if) +{ + outlineExpression(*_if.condition); + (*this)(_if.body); +} + +void ExpressionBreaker::operator()(Switch& _switch) +{ + outlineExpression(*_switch.expression); + for (auto& _case: _switch.cases) + // Do not visit the case expression, nothing to break there. + (*this)(_case.body); +} + +void ExpressionBreaker::operator()(ForLoop& _loop) +{ + (*this)(_loop.pre); + // Do not visit the condition because we cannot break expressions there. + (*this)(_loop.post); + (*this)(_loop.body); +} + +void ExpressionBreaker::operator()(Block& _block) +{ + vector<Statement> saved; + swap(saved, m_statementsToPrefix); + + function<boost::optional<vector<Statement>>(Statement&)> f = + [&](Statement& _statement) -> boost::optional<vector<Statement>> { + m_statementsToPrefix.clear(); + visit(_statement); + if (m_statementsToPrefix.empty()) + return {}; + m_statementsToPrefix.emplace_back(std::move(_statement)); + return std::move(m_statementsToPrefix); + }; + iterateReplacing(_block.statements, f); + + swap(saved, m_statementsToPrefix); +} + +void ExpressionBreaker::outlineExpression(Expression& _expr) +{ + if (_expr.type() != typeid(FunctionalInstruction) && _expr.type() != typeid(FunctionCall)) + return; + + visit(_expr); + + SourceLocation location = locationOf(_expr); + string var = m_nameDispenser.newName(""); + m_statementsToPrefix.emplace_back(VariableDeclaration{ + location, + {{TypedName{location, var, ""}}}, + make_shared<Expression>(std::move(_expr)) + }); + _expr = Identifier{location, var}; +} diff --git a/libjulia/optimiser/ExpressionBreaker.h b/libjulia/optimiser/ExpressionBreaker.h new file mode 100644 index 00000000..28cfdbc9 --- /dev/null +++ b/libjulia/optimiser/ExpressionBreaker.h @@ -0,0 +1,86 @@ +/* + 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 turns complex expressions into multiple variable + * declarations. + */ +#pragma once + +#include <libjulia/ASTDataForward.h> + +#include <libjulia/optimiser/ASTWalker.h> +#include <libjulia/optimiser/NameDispenser.h> + +#include <vector> + +namespace dev +{ +namespace julia +{ + +class NameCollector; + + +/** + * Optimiser component that modifies an AST in place, turning complex + * expressions into simple expressions and multiple variable declarations. + * + * Code of the form + * + * sstore(mul(x, 4), mload(y)) + * + * is transformed into + * + * let a1 := mload(y) + * let a2 := mul(x, 4) + * sstore(a2, a1) + * + * The transformation is not applied to loop conditions, because the loop control flow + * does not allow "outlining" the inner expressions in all cases. + * + * The final program should be in a form such that with the exception of a loop condition, + * function calls can only appear in the right-hand side of a variable declaration, + * assignments or expression statements and all arguments have to be constants or variables. + */ +class ExpressionBreaker: public ASTModifier +{ +public: + explicit ExpressionBreaker(NameDispenser& _nameDispenser): + m_nameDispenser(_nameDispenser) + { } + + virtual void operator()(FunctionalInstruction&) override; + virtual void operator()(FunctionCall&) override; + virtual void operator()(If&) override; + virtual void operator()(Switch&) override; + virtual void operator()(ForLoop&) override; + virtual void operator()(Block& _block) override; + +private: + /// Replaces the expression by a variable if it is a function call or functional + /// instruction. The declaration of the variable is appended to m_statementsToPrefix. + /// Recurses via visit(). + void outlineExpression(Expression& _expr); + + /// List of statements that should go in front of the currently visited AST element, + /// at the statement level. + std::vector<Statement> m_statementsToPrefix; + NameDispenser& m_nameDispenser; +}; + +} +} diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index d40a00d4..ab544388 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -76,7 +76,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) { solAssert(m_sourceUnit, ""); vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); - if (literals.size() == 0) + if (literals.empty()) m_errorReporter.syntaxError( _pragma.location(), "Experimental feature name is missing." diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index bc040623..39ea494d 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2123,7 +2123,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) auto& annotation = _memberAccess.annotation(); - if (possibleMembers.size() == 0) + if (possibleMembers.empty()) { if (initialMemberCount == 0) { diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index e45fc81d..195b2e2d 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -841,13 +841,8 @@ TypePointer RationalNumberType::forLiteral(Literal const& _literal) TypePointer compatibleBytesType; if (_literal.isHexNumber()) { - size_t digitCount = count_if( - _literal.value().begin() + 2, // skip "0x" - _literal.value().end(), - [](unsigned char _c) -> bool { return isxdigit(_c); } - ); - // require even number of digits - if (!(digitCount & 1)) + size_t const digitCount = _literal.valueWithoutUnderscores().length() - 2; + if (digitCount % 2 == 0 && (digitCount / 2) <= 32) compatibleBytesType = make_shared<FixedBytesType>(digitCount / 2); } @@ -861,8 +856,7 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal rational value; try { - ASTString valueString = _literal.value(); - boost::erase_all(valueString, "_");// Remove underscore separators + ASTString valueString = _literal.valueWithoutUnderscores(); auto expPoint = find(valueString.begin(), valueString.end(), 'e'); if (expPoint == valueString.end()) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index e26bc13a..3a0fccfb 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -965,7 +965,7 @@ void ContractCompiler::popScopedVariables(ASTNode const* _node) unsigned stackDiff = m_context.stackHeight() - blockHeight; CompilerUtils(m_context).popStackSlots(stackDiff); m_scopeStackHeight[m_modifierDepth].erase(_node); - if (m_scopeStackHeight[m_modifierDepth].size() == 0) + if (m_scopeStackHeight[m_modifierDepth].empty()) m_scopeStackHeight.erase(m_modifierDepth); } diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 19785817..a7973852 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -894,7 +894,7 @@ void SMTChecker::pushPathCondition(smt::Expression const& _e) smt::Expression SMTChecker::currentPathConditions() { - if (m_pathConditions.size() == 0) + if (m_pathConditions.empty()) return smt::Expression(true); return m_pathConditions.back(); } diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index f34bbffe..54cdc1c6 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -26,7 +26,7 @@ #include <boost/algorithm/string.hpp> -#include <ctype.h> +#include <cctype> #include <algorithm> using namespace std; @@ -97,7 +97,7 @@ assembly::Statement Parser::parseStatement() fatalParserError("Only one default case allowed."); else if (m_scanner->currentToken() == Token::Case) fatalParserError("Case not allowed after default case."); - if (_switch.cases.size() == 0) + if (_switch.cases.empty()) fatalParserError("Switch statement without any cases."); _switch.location.end = _switch.cases.back().body.location.end; return _switch; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index d1001c80..1f58245f 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -780,7 +780,7 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa // To provide a measure of backward-compatibility, if a contract is not located by its // fully-qualified name, a lookup will be attempted purely on the contract's name to see // if anything will satisfy. - if (_contractName.find(":") == string::npos) + if (_contractName.find(':') == string::npos) { for (auto const& contractEntry: m_contracts) { diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 2305da13..8300e8db 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -509,7 +509,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) bool const compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful; /// Inconsistent state - stop here to receive error reports from users - if (!compilationSuccess && (errors.size() == 0)) + if (!compilationSuccess && errors.empty()) return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); Json::Value output = Json::objectValue; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index b390459a..ca9a9b57 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -20,7 +20,7 @@ * Solidity parser. */ -#include <ctype.h> +#include <cctype> #include <vector> #include <libevmasm/SourceLocation.h> #include <libsolidity/parsing/Parser.h> @@ -554,7 +554,7 @@ ASTPointer<EnumDefinition> Parser::parseEnumDefinition() if (m_scanner->currentToken() != Token::Identifier) fatalParserError(string("Expected identifier after ','")); } - if (members.size() == 0) + if (members.empty()) parserError({"enum with no members is not allowed."}); nodeFactory.markEndPosition(); diff --git a/test/libdevcore/IterateReplacing.cpp b/test/libdevcore/IterateReplacing.cpp new file mode 100644 index 00000000..08cd1e22 --- /dev/null +++ b/test/libdevcore/IterateReplacing.cpp @@ -0,0 +1,97 @@ +/* + 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/>. +*/ +/** + * Unit tests for the iterateReplacing function + */ + +#include <libdevcore/CommonData.h> + +#include <test/Options.h> + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(IterateReplacing) + +BOOST_AUTO_TEST_CASE(no_replacement) +{ + vector<string> v{"abc", "def", "ghi"}; + function<boost::optional<vector<string>>(string&)> f = [](string&) -> boost::optional<vector<string>> { return {}; }; + iterateReplacing(v, f); + vector<string> expectation{"abc", "def", "ghi"}; + BOOST_CHECK(v == expectation); +} + +BOOST_AUTO_TEST_CASE(empty_input) +{ + vector<string> v; + function<boost::optional<vector<string>>(string&)> f = [](string&) -> boost::optional<vector<string>> { return {}; }; + iterateReplacing(v, f); + vector<string> expectation; + BOOST_CHECK(v == expectation); +} + +BOOST_AUTO_TEST_CASE(delete_some) +{ + vector<string> v{"abc", "def", "ghi"}; + function<boost::optional<vector<string>>(string&)> f = [](string& _s) -> boost::optional<vector<string>> { + if (_s == "def") + return vector<string>(); + else + return {}; + }; + iterateReplacing(v, f); + vector<string> expectation{"abc", "ghi"}; + BOOST_CHECK(v == expectation); +} + +BOOST_AUTO_TEST_CASE(inject_some_start) +{ + vector<string> v{"abc", "def", "ghi"}; + function<boost::optional<vector<string>>(string&)> f = [](string& _s) -> boost::optional<vector<string>> { + if (_s == "abc") + return vector<string>{"x", "y"}; + else + return {}; + }; + iterateReplacing(v, f); + vector<string> expectation{"x", "y", "def", "ghi"}; + BOOST_CHECK(v == expectation); +} + +BOOST_AUTO_TEST_CASE(inject_some_end) +{ + vector<string> v{"abc", "def", "ghi"}; + function<boost::optional<vector<string>>(string&)> f = [](string& _s) -> boost::optional<vector<string>> { + if (_s == "ghi") + return vector<string>{"x", "y"}; + else + return {}; + }; + iterateReplacing(v, f); + vector<string> expectation{"abc", "def", "x", "y"}; + BOOST_CHECK(v == expectation); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/libjulia/ExpressionBreaker.cpp b/test/libjulia/ExpressionBreaker.cpp new file mode 100644 index 00000000..de8d2251 --- /dev/null +++ b/test/libjulia/ExpressionBreaker.cpp @@ -0,0 +1,156 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for the expression breaker. + */ + +#include <test/libjulia/Common.h> + +#include <libjulia/optimiser/ExpressionBreaker.h> +#include <libjulia/optimiser/NameCollector.h> + +#include <libsolidity/inlineasm/AsmPrinter.h> + +#include <boost/test/unit_test.hpp> + +#include <boost/range/adaptors.hpp> +#include <boost/algorithm/string/join.hpp> + +using namespace std; +using namespace dev; +using namespace dev::julia; +using namespace dev::julia::test; +using namespace dev::solidity; + + +#define CHECK(_original, _expectation)\ +do\ +{\ + auto result = parse(_original, false);\ + NameDispenser nameDispenser;\ + nameDispenser.m_usedNames = NameCollector(*result.first).names();\ + ExpressionBreaker{nameDispenser}(*result.first);\ + BOOST_CHECK_EQUAL(assembly::AsmPrinter{}(*result.first), format(_expectation, false));\ +}\ +while(false) + +BOOST_AUTO_TEST_SUITE(YulExpressionBreaker) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + CHECK("{ }", "{ }"); +} + +BOOST_AUTO_TEST_CASE(trivial) +{ + CHECK( + "{ mstore(add(calldataload(2), mload(3)), 8) }", + "{ let _1 := mload(3) let _2 := calldataload(2) let _3 := add(_2, _1) mstore(_3, 8) }" + ); +} + +BOOST_AUTO_TEST_CASE(control_flow) +{ + string input = R"({ + let x := calldataload(0) + if mul(add(x, 2), 3) { + for { let a := 2 } lt(a, mload(a)) { a := add(a, mul(a, 2)) } { + let b := mul(add(a, 2), 4) + sstore(b, mul(b, 2)) + } + } + })"; + string expectation = R"({ + let x := calldataload(0) + let _1 := add(x, 2) + let _2 := mul(_1, 3) + if _2 + { + for { let a := 2 } lt(a, mload(a)) + { + let _3 := mul(a, 2) + a := add(a, _3) + } + { + let _4 := add(a, 2) + let b := mul(_4, 4) + let _5 := mul(b, 2) + sstore(b, _5) + } + } + })"; + CHECK(input, expectation); +} + +BOOST_AUTO_TEST_CASE(switch_) +{ + string input = R"({ + let x := 8 + switch add(2, calldataload(0)) + case 0 { sstore(0, mload(2)) } + default { mstore(0, mload(3)) } + x := add(mload(3), 4) + })"; + string expectation = R"({ + let x := 8 + let _1 := calldataload(0) + let _2 := add(2, _1) + switch _2 + case 0 { + let _3 := mload(2) + sstore(0, _3) + } + default { + let _4 := mload(3) + mstore(0, _4) + } + let _5 := mload(3) + x := add(_5, 4) + })"; + + CHECK(input, expectation); +} + +BOOST_AUTO_TEST_CASE(inside_function) +{ + string input = R"({ + let x := mul(f(0, mload(7)), 3) + function f(a, b) -> c { + c := mul(a, mload(add(b, c))) + } + sstore(x, f(mload(2), mload(2))) + })"; + string expectation = R"({ + let _1 := mload(7) + let _2 := f(0, _1) + let x := mul(_2, 3) + function f(a, b) -> c + { + let _3 := add(b, c) + let _4 := mload(_3) + c := mul(a, _4) + } + let _5 := mload(2) + let _6 := mload(2) + let _7 := f(_6, _5) + sstore(x, _7) + })"; + + CHECK(input, expectation); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/ASTJSONTest.cpp b/test/libsolidity/ASTJSONTest.cpp index 05839c1f..be482d99 100644 --- a/test/libsolidity/ASTJSONTest.cpp +++ b/test/libsolidity/ASTJSONTest.cpp @@ -22,7 +22,6 @@ #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/throw_exception.hpp> -#include <cctype> #include <fstream> #include <memory> #include <stdexcept> diff --git a/test/libsolidity/LibSolc.cpp b/test/libsolidity/LibSolc.cpp index 61e5ebba..94fed7e8 100644 --- a/test/libsolidity/LibSolc.cpp +++ b/test/libsolidity/LibSolc.cpp @@ -52,7 +52,7 @@ Json::Value compileMulti(string const& _input, bool _callback) { string output( _callback ? - compileJSONCallback(_input.c_str(), dev::test::Options::get().optimize, NULL) : + compileJSONCallback(_input.c_str(), dev::test::Options::get().optimize, nullptr) : compileJSONMulti(_input.c_str(), dev::test::Options::get().optimize) ); Json::Value ret; @@ -62,7 +62,7 @@ Json::Value compileMulti(string const& _input, bool _callback) Json::Value compile(string const& _input) { - string output(compileStandard(_input.c_str(), NULL)); + string output(compileStandard(_input.c_str(), nullptr)); Json::Value ret; BOOST_REQUIRE(jsonParseStrict(output, ret)); return ret; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index f65c8b27..7a496e64 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4519,6 +4519,31 @@ BOOST_AUTO_TEST_CASE(library_call_protection) ABI_CHECK(callContractFunction("pu()"), encodeArgs(2)); } + +BOOST_AUTO_TEST_CASE(library_staticcall_delegatecall) +{ + char const* sourceCode = R"( + library Lib { + function x() public view returns (uint) { + return 1; + } + } + contract Test { + uint t; + function f() public returns (uint) { + t = 2; + return this.g(); + } + function g() public view returns (uint) { + return Lib.x(); + } + } + )"; + compileAndRun(sourceCode, 0, "Lib"); + compileAndRun(sourceCode, 0, "Test", bytes(), map<string, Address>{{"Lib", m_contractAddress}}); + ABI_CHECK(callContractFunction("f()"), encodeArgs(1)); +} + BOOST_AUTO_TEST_CASE(store_bytes) { // this test just checks that the copy loop does not mess up the stack diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index 1de42300..91d1681f 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -20,7 +20,6 @@ #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/throw_exception.hpp> -#include <cctype> #include <fstream> #include <memory> #include <stdexcept> diff --git a/test/libsolidity/syntaxTests/types/rational_number_huge.sol b/test/libsolidity/syntaxTests/types/rational_number_huge.sol new file mode 100644 index 00000000..378de201 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/rational_number_huge.sol @@ -0,0 +1,10 @@ +contract C { + function f(uint y) public pure { + // fits FixedBytes with exactly 32-bytes + y = 0xffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000; // FixedBytes (32) + + // fits exactly into FixedBytes (32), ensures underscored literals won't hurt + y = 0xffffffff00000000ffffffff00000000ffffffff00000000ffffffff_00000000; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/types/rational_number_huge_fail.sol b/test/libsolidity/syntaxTests/types/rational_number_huge_fail.sol new file mode 100644 index 00000000..08e50656 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/rational_number_huge_fail.sol @@ -0,0 +1,8 @@ +contract C { + function f(uint y) public pure { + // one byte too long for storing in Fixedbytes (would require 33 bytes) + y = 0xffffffff00000000ffffffff00000000ffffffff00000000ffffffff000000001; + } +} +// ---- +// TypeError: (142-209): Type int_const 1852...(71 digits omitted)...7281 is not implicitly convertible to expected type uint256. diff --git a/test/tools/fuzzer.cpp b/test/tools/fuzzer.cpp index ce4b721f..bb020f8c 100644 --- a/test/tools/fuzzer.cpp +++ b/test/tools/fuzzer.cpp @@ -28,6 +28,7 @@ #include <boost/program_options.hpp> #include <string> +#include <sstream> #include <iostream> using namespace std; @@ -48,15 +49,17 @@ string contains(string const& _haystack, vector<string> const& _needles) return ""; } -void testConstantOptimizer() +void testConstantOptimizer(string const& input) { if (!quiet) cout << "Testing constant optimizer" << endl; vector<u256> numbers; - while (!cin.eof()) + stringstream sin(input); + + while (!sin.eof()) { h256 data; - cin.read(reinterpret_cast<char*>(data.data()), 32); + sin.read(reinterpret_cast<char*>(data.data()), 32); numbers.push_back(u256(data)); } if (!quiet) @@ -86,7 +89,7 @@ void testConstantOptimizer() void runCompiler(string input) { - string outputString(compileStandard(input.c_str(), NULL)); + string outputString(compileStandard(input.c_str(), nullptr)); Json::Value output; if (!jsonParseStrict(outputString, output)) { @@ -108,20 +111,18 @@ void runCompiler(string input) } } -void testStandardCompiler() +void testStandardCompiler(string const& input) { if (!quiet) cout << "Testing compiler via JSON interface." << endl; - string input = readStandardInput(); runCompiler(input); } -void testCompiler(bool optimize) +void testCompiler(string const& input, bool optimize) { if (!quiet) cout << "Testing compiler " << (optimize ? "with" : "without") << " optimizer." << endl; - string input = readStandardInput(); Json::Value config = Json::objectValue; config["language"] = "Solidity"; @@ -168,15 +169,24 @@ Allowed options)", "Expects a binary string of up to 32 bytes on stdin." ) ( + "input-file", + po::value<string>(), + "input file" + ) + ( "without-optimizer", "Run without optimizations. Cannot be used together with standard-json." ); + // All positional options should be interpreted as input files + po::positional_options_description filesPositions; + filesPositions.add("input-file", 1); + po::variables_map arguments; try { po::command_line_parser cmdLineParser(argc, argv); - cmdLineParser.options(options); + cmdLineParser.options(options).positional(filesPositions); po::store(cmdLineParser.run(), arguments); } catch (po::error const& _exception) @@ -185,17 +195,23 @@ Allowed options)", return 1; } + string input; + if (arguments.count("input-file")) + input = readFileAsString(arguments["input-file"].as<string>()); + else + input = readStandardInput(); + if (arguments.count("quiet")) quiet = true; if (arguments.count("help")) cout << options; else if (arguments.count("const-opt")) - testConstantOptimizer(); + testConstantOptimizer(input); else if (arguments.count("standard-json")) - testStandardCompiler(); + testStandardCompiler(input); else - testCompiler(!arguments.count("without-optimizer")); + testCompiler(input, !arguments.count("without-optimizer")); return 0; } |