diff options
35 files changed, 690 insertions, 215 deletions
diff --git a/Changelog.md b/Changelog.md index bd514cfe..5eb1e401 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Features: * Add ``assert(condition)``, which throws if condition is false. + * Introduce ``.transfer(value)`` for sending Ether. * Code generator: Support ``revert()`` to abort with rolling back, but not consuming all gas. * Inline assembly: Support ``revert`` (EIP140) as an opcode. * Type system: Support explicit conversion of external function to address. @@ -9,7 +10,13 @@ Features: Bugfixes: * Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``). * Commandline interface: Do not try creating paths ``.`` and ``..``. + * Type system: Fix a crash caused by continuing on fatal errors in the code. * Type system: Disallow arrays with negative length. + * Type system: Fix a crash related to invalid binary operators. + * Type system: Disallow ``var`` declaration with empty tuple type. + * Type system: Correctly convert function argument types to pointers for member functions. + * Inline assembly: Charge one stack slot for non-value types during analysis. + * Assembly output: Print source location before the operation it refers to instead of after. ### 0.4.9 (2017-01-31) diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index fa5e68a6..a2d7ce71 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -81,7 +81,7 @@ This is as opposed to the more intuitive sending pattern: mostSent = msg.value; } - function becomeRichest() returns (bool) { + function becomeRichest() payable returns (bool) { if (msg.value > mostSent) { // Check if call succeeds to prevent an attacker // from trapping the previous person's funds in diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 019714f8..83d3eac9 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -395,6 +395,7 @@ Currently, Solidity automatically generates a runtime exception in the following #. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). #. If your contract receives Ether via a public getter function. #. If you call a zero-initialized variable of internal function type. +#. If a ``.transfer()`` fails. While a user-provided exception is generated in the following situations: #. Calling ``throw``. diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index d2fc5b1c..8a68ae5b 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -660,16 +660,6 @@ https://github.com/ethereum/wiki/wiki/Subtleties After a successful CREATE operation's sub-execution, if the operation returns x, 5 * len(x) gas is subtracted from the remaining gas before the contract is created. If the remaining gas is less than 5 * len(x), then no gas is subtracted, the code of the created contract becomes the empty string, but this is not treated as an exceptional condition - no reverts happen. -How do I use ``.send()``? -========================= - -If you want to send 20 Ether from a contract to the address ``x``, you use ``x.send(20 ether);``. -Here, ``x`` can be a plain address or a contract. If the contract already explicitly defines -a function ``send`` (and thus overwrites the special function), you can use ``address(x).send(20 ether);``. - -Note that the call to ``send`` may fail in certain conditions, such as if you have insufficient funds, so you should always check the return value. -``send`` returns ``true`` if the send was successful and ``false`` otherwise. - What does the following strange check do in the Custom Token contract? ====================================================================== diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 42905ede..fb405475 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -102,6 +102,22 @@ We will re-add the pre-built bottles soon. brew install solidity brew linkapps solidity +If you need a specific version of Solidity you can install a +Homebrew formula directly from Github. + +View +`solidity.rb commits on Github <https://github.com/ethereum/homebrew-ethereum/commits/master/solidity.rb>`_. + +Follow the history links until you have a raw file link of a +specific commit of ``solidity.rb``. + +Install it using ``brew``: + +.. code:: bash + + brew unlink solidity + # Install 0.4.8 + brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb .. _building-from-source: @@ -264,4 +280,4 @@ Example: 3. a breaking change is introduced - version is bumped to 0.5.0 4. the 0.5.0 release is made -This behaviour works well with the version pragma.
\ No newline at end of file +This behaviour works well with the version pragma. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 6e272eaa..e3ec0efb 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -464,8 +464,9 @@ Global Variables - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address -- ``<address>.balance`` (``uint256``): balance of the address in Wei -- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to address, returns ``false`` on failure +- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei +- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure +- ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure .. index:: visibility, public, private, external, internal diff --git a/docs/types.rst b/docs/types.rst index 69c23e6e..f7d1d54f 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -64,7 +64,7 @@ expression ``x << y`` is equivalent to ``x * 2**y`` and ``x >> y`` is equivalent to ``x / 2**y``. This means that shifting negative numbers sign extends. Shifting by a negative amount throws a runtime exception. -.. index:: address, balance, send, call, callcode, delegatecall +.. index:: address, balance, send, call, callcode, delegatecall, transfer .. _address: @@ -80,27 +80,31 @@ Operators: Members of Addresses ^^^^^^^^^^^^^^^^^^^^ -* ``balance`` and ``send`` +* ``balance`` and ``transfer`` For a quick reference, see :ref:`address_related`. It is possible to query the balance of an address using the property ``balance`` -and to send Ether (in units of wei) to an address using the ``send`` function: +and to send Ether (in units of wei) to an address using the ``transfer`` function: :: address x = 0x123; address myAddress = this; - if (x.balance < 10 && myAddress.balance >= 10) x.send(10); + if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); .. note:: - If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``send`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted. In this case, ``send`` returns ``false``. + If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. + +* ``send`` + +Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``. .. warning:: There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order - to make safe Ether transfers, always check the return value of ``send`` or even better: - Use a pattern where the recipient withdraws the money. + to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: + use a pattern where the recipient withdraws the money. * ``call``, ``callcode`` and ``delegatecall`` diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 72741b67..49fe5d84 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -130,6 +130,8 @@ Address Related balance of the :ref:`address` in Wei ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure +``<address>.transfer(uint256 amount)``: + send given amount of Wei to :ref:`address`, throws on failure For more information, see the section on :ref:`address`. diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index 4622774a..37cdbed9 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -41,6 +41,9 @@ struct Exception: virtual std::exception, virtual boost::exception Exception(std::string _message = std::string()): m_message(std::move(_message)) {} const char* what() const noexcept override { return m_message.empty() ? std::exception::what() : m_message.c_str(); } + /// @returns "FileName:LineNumber" referring to the point where the exception was thrown. + std::string lineInfo() const; + private: std::string m_message; }; diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index b7859c1f..f12e8aa8 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -130,8 +130,8 @@ public: if (!_item.location().isEmpty() && _item.location() != m_location) { flush(); - printLocation(); m_location = _item.location(); + printLocation(); } if (!( _item.canBeFunctional() && diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 01384260..336dc894 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -21,6 +21,7 @@ */ #include <libsolidity/analysis/NameAndTypeResolver.h> + #include <libsolidity/ast/AST.h> #include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/interface/Exceptions.h> @@ -130,62 +131,9 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode) { - bool success = true; try { - if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node)) - { - m_currentScope = m_scopes[contract->scope()].get(); - solAssert(!!m_currentScope, ""); - - for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts()) - if (!resolveNamesAndTypes(*baseContract, true)) - success = false; - - m_currentScope = m_scopes[contract].get(); - - if (success) - { - linearizeBaseContracts(*contract); - vector<ContractDefinition const*> properBases( - ++contract->annotation().linearizedBaseContracts.begin(), - contract->annotation().linearizedBaseContracts.end() - ); - - for (ContractDefinition const* base: properBases) - importInheritedScope(*base); - } - - // these can contain code, only resolve parameters for now - for (ASTPointer<ASTNode> const& node: contract->subNodes()) - { - m_currentScope = m_scopes[contract].get(); - if (!resolveNamesAndTypes(*node, false)) - success = false; - } - - if (!success) - return false; - - if (!_resolveInsideCode) - return success; - - m_currentScope = m_scopes[contract].get(); - - // now resolve references inside the code - for (ASTPointer<ASTNode> const& node: contract->subNodes()) - { - m_currentScope = m_scopes[contract].get(); - if (!resolveNamesAndTypes(*node, true)) - success = false; - } - } - else - { - if (m_scopes.count(&_node)) - m_currentScope = m_scopes[&_node].get(); - return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node); - } + return resolveNamesAndTypesInternal(_node, _resolveInsideCode); } catch (FatalError const&) { @@ -193,7 +141,6 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi throw; // Something is weird here, rather throw again. return false; } - return success; } bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) @@ -249,21 +196,25 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations( solAssert(_declarations.size() > 1, ""); vector<Declaration const*> uniqueFunctions; - for (auto it = _declarations.begin(); it != _declarations.end(); ++it) + for (Declaration const* declaration: _declarations) { - solAssert(*it, ""); + solAssert(declaration, ""); // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1 - solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<EventDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it), - "Found overloading involving something not a function or a variable"); + solAssert( + dynamic_cast<FunctionDefinition const*>(declaration) || + dynamic_cast<EventDefinition const*>(declaration) || + dynamic_cast<VariableDeclaration const*>(declaration), + "Found overloading involving something not a function or a variable." + ); - shared_ptr<FunctionType const> functionType { (*it)->functionType(false) }; + FunctionTypePointer functionType { declaration->functionType(false) }; if (!functionType) - functionType = (*it)->functionType(true); - solAssert(functionType, "failed to determine the function type of the overloaded"); + functionType = declaration->functionType(true); + solAssert(functionType, "Failed to determine the function type of the overloaded."); for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) if (!parameter) - reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); + reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context."); if (uniqueFunctions.end() == find_if( uniqueFunctions.begin(), @@ -276,11 +227,73 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations( return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType); } )) - uniqueFunctions.push_back(*it); + uniqueFunctions.push_back(declaration); } return uniqueFunctions; } +bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode) +{ + if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node)) + { + bool success = true; + m_currentScope = m_scopes[contract->scope()].get(); + solAssert(!!m_currentScope, ""); + + for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts()) + if (!resolveNamesAndTypes(*baseContract, true)) + success = false; + + m_currentScope = m_scopes[contract].get(); + + if (success) + { + linearizeBaseContracts(*contract); + vector<ContractDefinition const*> properBases( + ++contract->annotation().linearizedBaseContracts.begin(), + contract->annotation().linearizedBaseContracts.end() + ); + + for (ContractDefinition const* base: properBases) + importInheritedScope(*base); + } + + // these can contain code, only resolve parameters for now + for (ASTPointer<ASTNode> const& node: contract->subNodes()) + { + m_currentScope = m_scopes[contract].get(); + if (!resolveNamesAndTypes(*node, false)) + { + success = false; + break; + } + } + + if (!success) + return false; + + if (!_resolveInsideCode) + return success; + + m_currentScope = m_scopes[contract].get(); + + // now resolve references inside the code + for (ASTPointer<ASTNode> const& node: contract->subNodes()) + { + m_currentScope = m_scopes[contract].get(); + if (!resolveNamesAndTypes(*node, true)) + success = false; + } + return success; + } + else + { + if (m_scopes.count(&_node)) + m_currentScope = m_scopes[&_node].get(); + return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node); + } +} + void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) { auto iterator = m_scopes.find(&_base); diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 828b566f..038a887b 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -89,6 +89,9 @@ public: ); private: + /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors. + bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true); + /// Imports all members declared directly in the given contract (i.e. does not import inherited members) /// into the current scope if they are not present already. void importInheritedScope(ContractDefinition const& _base); diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index c06181d8..37bcb2d9 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -35,14 +35,7 @@ using namespace dev::solidity; bool ReferencesResolver::resolve(ASTNode const& _root) { - try - { - _root.accept(*this); - } - catch (FatalError const&) - { - solAssert(m_errorOccurred, ""); - } + _root.accept(*this); return !m_errorOccurred; } diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 23ac6b07..dce343d3 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -52,7 +52,7 @@ public: m_resolveInsideCode(_resolveInsideCode) {} - /// @returns true if no errors during resolving + /// @returns true if no errors during resolving and throws exceptions on fatal errors. bool resolve(ASTNode const& _root); private: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 28cb9acc..ff55ef1f 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -611,7 +611,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); if (var->isLocalVariable()) pushes = var->type()->sizeOnStack(); - else if (var->type()->isValueType()) + else if (!var->type()->isValueType()) pushes = 1; else pushes = 2; // slot number, intra slot offset @@ -824,6 +824,11 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) else solAssert(false, ""); } + else if (*var.annotation().type == TupleType()) + typeError( + var.location(), + "Cannot declare variable with void (empty tuple) type." + ); var.accept(*this); } else diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 5b7b4a2c..7f267cc9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -252,9 +252,9 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) { if (!_a || !_b) return TypePointer(); - else if (_b->isImplicitlyConvertibleTo(*_a->mobileType())) + else if (_a->mobileType() && _b->isImplicitlyConvertibleTo(*_a->mobileType())) return _a->mobileType(); - else if (_a->isImplicitlyConvertibleTo(*_b->mobileType())) + else if (_b->mobileType() && _a->isImplicitlyConvertibleTo(*_b->mobileType())) return _b->mobileType(); else return TypePointer(); @@ -464,7 +464,8 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true, false, true)}, {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true, false, true)}, {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)}, - {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)} + {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}, + {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Location::Transfer)} }; else return MemberList::MemberMap(); @@ -1652,6 +1653,7 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const for (ASTPointer<VariableDeclaration> const& variable: m_struct.members()) { TypePointer type = variable->annotation().type; + solAssert(type, ""); // Skip all mapping members if we are not in storage. if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) continue; @@ -1895,7 +1897,10 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons size_t si = fillRight ? i : components().size() - i - 1; size_t ti = fillRight ? i : targetComponents.size() - i - 1; if (components()[si] && targetComponents[ti]) + { tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[ti]); + solAssert(tempComponents[ti], ""); + } } return make_shared<TupleType>(tempComponents); } @@ -1964,6 +1969,8 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): if (auto structType = dynamic_cast<StructType const*>(returnType.get())) { for (auto const& member: structType->members(nullptr)) + { + solAssert(member.type, ""); if (member.type->category() != Category::Mapping) { if (auto arrayType = dynamic_cast<ArrayType const*>(member.type.get())) @@ -1972,6 +1979,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): retParams.push_back(member.type); retParamNames.push_back(member.name); } + } } else { @@ -2093,6 +2101,7 @@ string FunctionType::identifier() const case Location::BareDelegateCall: id += "baredelegatecall"; break; case Location::Creation: id += "creation"; break; case Location::Send: id += "send"; break; + case Location::Transfer: id += "transfer"; break; case Location::SHA3: id += "sha3"; break; case Location::Selfdestruct: id += "selfdestruct"; break; case Location::Revert: id += "revert"; break; @@ -2484,7 +2493,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) { auto refType = dynamic_cast<ReferenceType const*>(t.get()); if (refType && refType->location() == DataLocation::CallData) - parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, false)); + parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, true)); else parameterTypes.push_back(t); } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 3546e522..022b67c4 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -826,6 +826,7 @@ public: BareDelegateCall, ///< DELEGATECALL without function hash Creation, ///< external call using CREATE Send, ///< CALL, but without data and gas + Transfer, ///< CALL, but without data and throws on error SHA3, ///< SHA3 Selfdestruct, ///< SELFDESTRUCT Revert, ///< REVERT diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 41cfcb69..5192ffa6 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -220,6 +220,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType( _assignment.leftHandSide().annotation().type ); + solAssert(rightIntermediateType, ""); utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded); _assignment.leftHandSide().accept(*this); @@ -395,6 +396,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) TypePointer leftTargetType = commonType; TypePointer rightTargetType = Token::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType; + solAssert(rightTargetType, ""); // for commutative operators, push the literal as late as possible to allow improved optimization auto isLiteral = [](Expression const& _e) @@ -616,6 +618,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments.front()->accept(*this); break; case Location::Send: + case Location::Transfer: _functionCall.expression().accept(*this); // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. @@ -644,6 +647,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) ), {} ); + if (function.location() == Location::Transfer) + { + // Check if zero (out of stack or not enough balance). + m_context << Instruction::ISZERO; + m_context.appendConditionalInvalid(); + } break; case Location::Selfdestruct: arguments.front()->accept(*this); @@ -808,6 +817,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments[0]->accept(*this); // stack: newLength storageSlot slotOffset argValue TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); + solAssert(type, ""); utils().convertType(*arguments[0]->annotation().type, *type); utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack()); @@ -959,6 +969,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case FunctionType::Location::Bare: case FunctionType::Location::BareCallCode: case FunctionType::Location::BareDelegateCall: + case FunctionType::Location::Transfer: _memberAccess.expression().accept(*this); m_context << funType->externalIdentifier(); break; @@ -1040,7 +1051,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); m_context << Instruction::BALANCE; } - else if ((set<string>{"send", "call", "callcode", "delegatecall"}).count(member)) + else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member)) utils().convertType( *_memberAccess.expression().annotation().type, IntegerType(0, IntegerType::Modifier::Address), diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 43c3b27a..faa7dabd 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -190,6 +190,10 @@ public: } (*this)(_instr.instruction); } + void operator()(assembly::FunctionCall const&) + { + solAssert(false, "Function call not removed during desugaring phase."); + } void operator()(Label const& _label) { m_state.assembly.setSourceLocation(_label.location); @@ -249,7 +253,10 @@ public: _block.location ); } - + } + void operator()(assembly::FunctionDefinition const&) + { + solAssert(false, "Function definition not removed during desugaring phase."); } private: diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index d622ff54..d61b5803 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -48,17 +48,22 @@ struct Label { SourceLocation location; std::string name; }; struct Assignment { SourceLocation location; Identifier variableName; }; struct FunctionalAssignment; struct VariableDeclaration; +struct FunctionDefinition; +struct FunctionCall; struct Block; -using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionalInstruction, VariableDeclaration, Block>; +using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>; /// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand /// side and requires x to occupy exactly one stack slot. struct FunctionalAssignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; }; /// Functional instruction, e.g. "mul(mload(20), add(2, x))" struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; }; +struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; }; /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr<Statement> value; }; /// Block that creates a scope (frees declared stack variables) struct Block { SourceLocation location; std::vector<Statement> statements; }; +/// Function definition ("function f(a, b) -> (d, e) { ... }") +struct FunctionDefinition { SourceLocation location; std::string name; std::vector<std::string> arguments; std::vector<std::string> returns; Block body; }; struct LocationExtractor: boost::static_visitor<SourceLocation> { diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 46a2730d..0fc0a34f 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -62,6 +62,8 @@ assembly::Statement Parser::parseStatement() { case Token::Let: return parseVariableDeclaration(); + case Token::Function: + return parseFunctionDefinition(); case Token::LBrace: return parseBlock(); case Token::Assign: @@ -214,10 +216,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() { VariableDeclaration varDecl = createWithLocation<VariableDeclaration>(); expectToken(Token::Let); - varDecl.name = m_scanner->currentLiteral(); - if (instructions().count(varDecl.name)) - fatalParserError("Cannot use instruction names for identifier names."); - expectToken(Token::Identifier); + varDecl.name = expectAsmIdentifier(); expectToken(Token::Colon); expectToken(Token::Assign); varDecl.value.reset(new Statement(parseExpression())); @@ -225,44 +224,107 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() return varDecl; } -FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction) +assembly::FunctionDefinition Parser::parseFunctionDefinition() { - if (_instruction.type() != typeid(Instruction)) - fatalParserError("Assembly instruction required in front of \"(\")"); - FunctionalInstruction ret; - ret.instruction = std::move(boost::get<Instruction>(_instruction)); - ret.location = ret.instruction.location; - solidity::Instruction instr = ret.instruction.instruction; - InstructionInfo instrInfo = instructionInfo(instr); - if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16) - fatalParserError("DUPi instructions not allowed for functional notation"); - if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16) - fatalParserError("SWAPi instructions not allowed for functional notation"); - + FunctionDefinition funDef = createWithLocation<FunctionDefinition>(); + expectToken(Token::Function); + funDef.name = expectAsmIdentifier(); expectToken(Token::LParen); - unsigned args = unsigned(instrInfo.args); - for (unsigned i = 0; i < args; ++i) + while (m_scanner->currentToken() != Token::RParen) { - ret.arguments.emplace_back(parseExpression()); - if (i != args - 1) + funDef.arguments.push_back(expectAsmIdentifier()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); + } + expectToken(Token::RParen); + if (m_scanner->currentToken() == Token::Sub) + { + expectToken(Token::Sub); + expectToken(Token::GreaterThan); + expectToken(Token::LParen); + while (true) { - if (m_scanner->currentToken() != Token::Comma) - fatalParserError(string( - "Expected comma (" + - instrInfo.name + - " expects " + - boost::lexical_cast<string>(args) + - " arguments)" - )); - else - m_scanner->next(); + funDef.returns.push_back(expectAsmIdentifier()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); } + expectToken(Token::RParen); } - ret.location.end = endPosition(); - if (m_scanner->currentToken() == Token::Comma) - fatalParserError( - string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)") - ); - expectToken(Token::RParen); - return ret; + funDef.body = parseBlock(); + funDef.location.end = funDef.body.location.end; + return funDef; +} + +assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _instruction) +{ + if (_instruction.type() == typeid(Instruction)) + { + FunctionalInstruction ret; + ret.instruction = std::move(boost::get<Instruction>(_instruction)); + ret.location = ret.instruction.location; + solidity::Instruction instr = ret.instruction.instruction; + InstructionInfo instrInfo = instructionInfo(instr); + if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16) + fatalParserError("DUPi instructions not allowed for functional notation"); + if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16) + fatalParserError("SWAPi instructions not allowed for functional notation"); + expectToken(Token::LParen); + unsigned args = unsigned(instrInfo.args); + for (unsigned i = 0; i < args; ++i) + { + ret.arguments.emplace_back(parseExpression()); + if (i != args - 1) + { + if (m_scanner->currentToken() != Token::Comma) + fatalParserError(string( + "Expected comma (" + + instrInfo.name + + " expects " + + boost::lexical_cast<string>(args) + + " arguments)" + )); + else + m_scanner->next(); + } + } + ret.location.end = endPosition(); + if (m_scanner->currentToken() == Token::Comma) + fatalParserError( + string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)") + ); + expectToken(Token::RParen); + return ret; + } + else if (_instruction.type() == typeid(Identifier)) + { + FunctionCall ret; + ret.functionName = std::move(boost::get<Identifier>(_instruction)); + ret.location = ret.functionName.location; + expectToken(Token::LParen); + while (m_scanner->currentToken() != Token::RParen) + { + ret.arguments.emplace_back(parseExpression()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); + } + ret.location.end = endPosition(); + expectToken(Token::RParen); + return ret; + } + else + fatalParserError("Assembly instruction or function name required in front of \"(\")"); + + return {}; +} + +string Parser::expectAsmIdentifier() +{ + string name = m_scanner->currentLiteral(); + if (instructions().count(name)) + fatalParserError("Cannot use instruction names for identifier names."); + expectToken(Token::Identifier); + return name; } diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 643548dd..4b4a24ae 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -67,7 +67,9 @@ protected: std::map<std::string, dev::solidity::Instruction> const& instructions(); Statement parseElementaryOperation(bool _onlySinglePusher = false); VariableDeclaration parseVariableDeclaration(); - FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction); + FunctionDefinition parseFunctionDefinition(); + Statement parseFunctionalInstruction(Statement&& _instruction); + std::string expectAsmIdentifier(); }; } diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index ab2a03ff..a70b0b78 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -112,6 +112,24 @@ string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDecl return "let " + _variableDeclaration.name + " := " + boost::apply_visitor(*this, *_variableDeclaration.value); } +string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefinition) +{ + string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")"; + if (!_functionDefinition.returns.empty()) + out += " -> (" + boost::algorithm::join(_functionDefinition.returns, ", ") + ")"; + return out + "\n" + (*this)(_functionDefinition.body); +} + +string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall) +{ + return + (*this)(_functionCall.functionName) + "(" + + boost::algorithm::join( + _functionCall.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), + ", " ) + + ")"; +} + string AsmPrinter::operator()(Block const& _block) { if (_block.statements.empty()) diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h index 39069d02..a7a1de0a 100644 --- a/libsolidity/inlineasm/AsmPrinter.h +++ b/libsolidity/inlineasm/AsmPrinter.h @@ -53,6 +53,8 @@ public: std::string operator()(assembly::Assignment const& _assignment); std::string operator()(assembly::FunctionalAssignment const& _functionalAssignment); std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); + std::string operator()(assembly::FunctionDefinition const& _functionDefinition); + std::string operator()(assembly::FunctionCall const& _functionCall); std::string operator()(assembly::Block const& _block); }; diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp index 90a680b4..41890b91 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/libsolidity/interface/Exceptions.cpp @@ -23,6 +23,7 @@ #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Utils.h> +using namespace std; using namespace dev; using namespace dev::solidity; @@ -56,3 +57,16 @@ Error::Error(Type _type): m_type(_type) break; } } + +string Exception::lineInfo() const +{ + char const* const* file = boost::get_error_info<boost::throw_file>(*this); + int const* line = boost::get_error_info<boost::throw_line>(*this); + string ret; + if (file) + ret += *file; + ret += ':'; + if (line) + ret += boost::lexical_cast<string>(*line); + return ret; +} diff --git a/scripts/uniqueErrors.sh b/scripts/uniqueErrors.sh new file mode 100755 index 00000000..eee1df90 --- /dev/null +++ b/scripts/uniqueErrors.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +REPO=$(dirname $0)/.. + +echo "Finding unique failures..." +( +for x in $* +do + echo -n $x " # " + # This subshell is a workaround to prevent the shell from printing + # "Aborted" + ("$REPO"/build/test/solfuzzer < "$x" || true) 2>&1 | head -n 1 +done +) | sort -u -t'#' -k 2 diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index d761b541..6ebd1a55 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -184,15 +184,15 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback } catch (CompilerError const& exception) { - errors.append(formatError(exception, "Compiler error", scannerFromSourceName)); + errors.append(formatError(exception, "Compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (InternalCompilerError const& exception) { - errors.append(formatError(exception, "Internal compiler error", scannerFromSourceName)); + errors.append(formatError(exception, "Internal compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (UnimplementedFeatureError const& exception) { - errors.append(formatError(exception, "Unimplemented feature", scannerFromSourceName)); + errors.append(formatError(exception, "Unimplemented feature (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (Exception const& exception) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 609aaab3..4d56ec9d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,23 +7,9 @@ aux_source_directory(libsolidity SRC_LIST) aux_source_directory(contracts SRC_LIST) aux_source_directory(liblll SRC_LIST) -get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) +list(REMOVE_ITEM SRC_LIST "./fuzzer.cpp") -# search for test names and create ctest tests -enable_testing() -foreach(file ${SRC_LIST}) - file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/${file} test_list_raw REGEX "BOOST_.*TEST_(SUITE|CASE)") - set(TestSuite "DEFAULT") - foreach(test_raw ${test_list_raw}) - string(REGEX REPLACE ".*TEST_(SUITE|CASE)\\(([^ ,\\)]*).*" "\\1 \\2" test ${test_raw}) - if(test MATCHES "^SUITE .*") - string(SUBSTRING ${test} 6 -1 TestSuite) - elseif(test MATCHES "^CASE .*") - string(SUBSTRING ${test} 5 -1 TestCase) - add_test(NAME ${TestSuite}/${TestCase} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND test -t ${TestSuite}/${TestCase}) - endif(test MATCHES "^SUITE .*") - endforeach(test_raw) -endforeach(file) +get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) file(GLOB HEADERS "*.h" "*/*.h") set(EXECUTABLE soltest) @@ -34,5 +20,5 @@ eth_use(${EXECUTABLE} REQUIRED Solidity::solidity Solidity::lll) include_directories(BEFORE ..) target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) -enable_testing() -set(CTEST_OUTPUT_ON_FAILURE TRUE) +add_executable(solfuzzer fuzzer.cpp) +target_link_libraries(solfuzzer soljson) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index ff00d783..be8774bc 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -16,19 +16,20 @@ The Implementation originally from https://msdn.microsoft.com/en-us/library/windows/desktop/aa365592(v=vs.85).aspx */ -/** @file RPCSession.cpp - * @author Dimtiry Khokhlov <dimitry@ethdev.com> - * @author Alex Beregszaszi - * @date 2016 - */ +/// @file RPCSession.cpp +/// Low-level IPC communication between the test framework and the Ethereum node. + +#include "RPCSession.h" -#include <string> -#include <stdio.h> -#include <thread> #include <libdevcore/CommonData.h> + #include <json/reader.h> #include <json/writer.h> -#include "RPCSession.h" + +#include <string> +#include <stdio.h> +#include <thread> +#include <chrono> using namespace std; using namespace dev; @@ -107,13 +108,24 @@ string IPCSocket::sendRequest(string const& _req) return string(m_readBuf, m_readBuf + cbRead); #else if (send(m_socket, _req.c_str(), _req.length(), 0) != (ssize_t)_req.length()) - BOOST_FAIL("Writing on IPC failed"); + BOOST_FAIL("Writing on IPC failed."); - ssize_t ret = recv(m_socket, m_readBuf, sizeof(m_readBuf), 0); + auto start = chrono::steady_clock::now(); + ssize_t ret; + do + { + ret = recv(m_socket, m_readBuf, sizeof(m_readBuf), 0); + // Also consider closed socket an error. + if (ret < 0) + BOOST_FAIL("Reading on IPC failed."); + } + while ( + ret == 0 && + chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count() < m_readTimeOutMS + ); - // Also consider closed socket an error. - if (ret <= 0) - BOOST_FAIL("Reading on IPC failed"); + if (ret == 0) + BOOST_FAIL("Timeout reading on IPC."); return string(m_readBuf, m_readBuf + ret); #endif @@ -186,12 +198,17 @@ string RPCSession::eth_getStorageRoot(string const& _address, string const& _blo void RPCSession::personal_unlockAccount(string const& _address, string const& _password, int _duration) { - BOOST_REQUIRE(rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }) == true); + BOOST_REQUIRE_MESSAGE( + rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }), + "Error unlocking account " + _address + ); } string RPCSession::personal_newAccount(string const& _password) { - return rpcCall("personal_newAccount", { quote(_password) }).asString(); + string addr = rpcCall("personal_newAccount", { quote(_password) }).asString(); + BOOST_MESSAGE("Created account " + addr); + return addr; } void RPCSession::test_setChainParams(vector<string> const& _accounts) diff --git a/test/RPCSession.h b/test/RPCSession.h index 414db323..843036e1 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -28,11 +28,13 @@ #include <sys/un.h> #endif +#include <json/value.h> + +#include <boost/test/unit_test.hpp> + #include <string> #include <stdio.h> #include <map> -#include <json/value.h> -#include <boost/test/unit_test.hpp> #if defined(_WIN32) class IPCSocket : public boost::noncopyable @@ -60,8 +62,12 @@ public: std::string const& path() const { return m_path; } private: + std::string m_path; int m_socket; + /// Socket read timeout in milliseconds. Needs to be large because the key generation routine + /// might take long. + unsigned static constexpr m_readTimeOutMS = 15000; char m_readBuf[512000]; }; #endif diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index fc48654a..cb714efe 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -31,7 +31,7 @@ set -e REPO_ROOT="$(dirname "$0")"/.. SOLC="$REPO_ROOT/build/solc/solc" - # Compile all files in std and examples. +# Compile all files in std and examples. for f in "$REPO_ROOT"/std/*.sol do @@ -46,6 +46,21 @@ do test -z "$output" -a "$failed" -eq 0 done -# Test library checksum -echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 -! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null +echo "Testing library checksum..." +echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 +! echo '' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null + +echo "Testing soljson via the fuzzer..." +TMPDIR=$(mktemp -d) +( + cd "$REPO_ROOT" + REPO_ROOT=$(pwd) # make it absolute + cd "$TMPDIR" + "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/contracts/* "$REPO_ROOT"/test/libsolidity/*EndToEnd* + for f in *.sol + do + "$REPO_ROOT"/build/test/solfuzzer < "$f" + done +) +rm -rf "$TMPDIR" +echo "Done." diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp new file mode 100644 index 00000000..410313c5 --- /dev/null +++ b/test/fuzzer.cpp @@ -0,0 +1,91 @@ +/* + 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/>. +*/ +/** + * Executable for use with AFL <http://lcamtuf.coredump.cx/afl>. + * Reads a single source from stdin and signals a failure for internal errors. + */ + +#include <json/json.h> + +#include <string> +#include <iostream> + +using namespace std; + +extern "C" +{ +extern char const* compileJSON(char const* _input, bool _optimize); +} + +string contains(string const& _haystack, vector<string> const& _needles) +{ + for (string const& needle: _needles) + if (_haystack.find(needle) != string::npos) + return needle; + return ""; +} + +int main() +{ + string input; + while (!cin.eof()) + { + string s; + getline(cin, s); + input += s + '\n'; + } + + bool optimize = true; + string outputString(compileJSON(input.c_str(), optimize)); + Json::Value outputJson; + if (!Json::Reader().parse(outputString, outputJson)) + { + cout << "Compiler produced invalid JSON output." << endl; + abort(); + } + if (outputJson.isMember("errors")) + { + if (!outputJson["errors"].isArray()) + { + cout << "Output JSON has \"errors\" but it is not an array." << endl; + abort(); + } + for (Json::Value const& error: outputJson["errors"]) + { + string invalid = contains(error.asString(), vector<string>{ + "Internal compiler error", + "Exception during compilation", + "Unknown exception during compilation", + "Unknown exception while generating contract data output", + "Unknown exception while generating formal method output", + "Unknown exception while generating source name output", + "Unknown error while generating JSON" + }); + if (!invalid.empty()) + { + cout << "Invalid error: \"" << error.asString() << "\"" << endl; + abort(); + } + } + } + else if (!outputJson.isMember("contracts")) + { + cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl; + abort(); + } + return 0; +} diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 8744d96f..9035599b 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -20,14 +20,19 @@ * Unit tests for inline assembly. */ -#include <string> -#include <memory> -#include <libevmasm/Assembly.h> -#include <libsolidity/parsing/Scanner.h> +#include "../TestHelper.h" + #include <libsolidity/inlineasm/AsmStack.h> +#include <libsolidity/parsing/Scanner.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/ast/AST.h> -#include "../TestHelper.h" +#include <test/libsolidity/ErrorCheck.h> +#include <libevmasm/Assembly.h> + +#include <boost/optional.hpp> + +#include <string> +#include <memory> using namespace std; @@ -41,31 +46,44 @@ namespace test namespace { -bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true) +boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _assemble = false, bool _allowWarnings = true) { assembly::InlineAssemblyStack stack; + bool success = false; try { - if (!stack.parse(std::make_shared<Scanner>(CharStream(_source)))) - return false; - if (_assemble) - { + success = stack.parse(std::make_shared<Scanner>(CharStream(_source))); + if (success && _assemble) stack.assemble(); - if (!stack.errors().empty()) - if (!_allowWarnings || !Error::containsOnlyWarnings(stack.errors())) - return false; - } } catch (FatalError const&) { - if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) - return false; + BOOST_FAIL("Fatal error leaked."); + success = false; + } + if (!success) + { + BOOST_CHECK_EQUAL(stack.errors().size(), 1); + return *stack.errors().front(); } - if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) - return false; + else + { + // If success is true, there might still be an error in the assembly stage. + if (_allowWarnings && Error::containsOnlyWarnings(stack.errors())) + return {}; + else if (!stack.errors().empty()) + { + if (!_allowWarnings) + BOOST_CHECK_EQUAL(stack.errors().size(), 1); + return *stack.errors().front(); + } + } + return {}; +} - BOOST_CHECK(Error::containsOnlyWarnings(stack.errors())); - return true; +bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true) +{ + return !parseAndReturnFirstError(_source, _assemble, _allowWarnings); } bool successAssemble(string const& _source, bool _allowWarnings = true) @@ -73,6 +91,14 @@ bool successAssemble(string const& _source, bool _allowWarnings = true) return successParse(_source, true, _allowWarnings); } +Error expectError(std::string const& _source, bool _assemble, bool _allowWarnings = false) +{ + + auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings); + BOOST_REQUIRE(error); + return *error; +} + void parsePrintCompare(string const& _source) { assembly::InlineAssemblyStack stack; @@ -83,6 +109,21 @@ void parsePrintCompare(string const& _source) } +#define CHECK_ERROR(text, assemble, typ, substring) \ +do \ +{ \ + Error err = expectError((text), (assemble), false); \ + BOOST_CHECK(err.type() == (Error::Type::typ)); \ + BOOST_CHECK(searchErrorMessage(err, (substring))); \ +} while(0) + +#define CHECK_PARSE_ERROR(text, type, substring) \ +CHECK_ERROR(text, false, type, substring) + +#define CHECK_ASSEMBLE_ERROR(text, type, substring) \ +CHECK_ERROR(text, true, type, substring) + + BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly) @@ -159,6 +200,21 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); } +BOOST_AUTO_TEST_CASE(function_definitions) +{ + BOOST_CHECK(successParse("{ function f() { } function g(a) -> (x) { } }")); +} + +BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) +{ + BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> (x, y) { } }")); +} + +BOOST_AUTO_TEST_CASE(function_calls) +{ + BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }")); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(Printing) @@ -209,6 +265,16 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) parsePrintCompare(parsed); } +BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) +{ + parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> (x, y)\n {\n }\n}"); +} + +BOOST_AUTO_TEST_CASE(function_calls) +{ + parsePrintCompare("{\n g(1, mul(2, x), f(mul(2, 3)))\n x()\n}"); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(Analysis) @@ -220,7 +286,7 @@ BOOST_AUTO_TEST_CASE(string_literals) BOOST_AUTO_TEST_CASE(oversize_string_literals) { - BOOST_CHECK(!successAssemble("{ let x := \"123456789012345678901234567890123\" }")); + CHECK_ASSEMBLE_ERROR("{ let x := \"123456789012345678901234567890123\" }", TypeError, "String literal too long"); } BOOST_AUTO_TEST_CASE(assignment_after_tag) @@ -230,15 +296,16 @@ BOOST_AUTO_TEST_CASE(assignment_after_tag) BOOST_AUTO_TEST_CASE(magic_variables) { - BOOST_CHECK(!successAssemble("{ this }")); - BOOST_CHECK(!successAssemble("{ ecrecover }")); + CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique"); + CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique"); BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }")); } BOOST_AUTO_TEST_CASE(imbalanced_stack) { BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false)); - BOOST_CHECK(!successAssemble("{ 1 }", false)); + CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves"); + CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes"); BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false)); } @@ -254,20 +321,17 @@ BOOST_AUTO_TEST_CASE(designated_invalid_instruction) BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration) { - // Error message: "Cannot use instruction names for identifier names." - BOOST_CHECK(!successAssemble("{ let gas := 1 }")); + CHECK_ASSEMBLE_ERROR("{ let gas := 1 }", ParserError, "Cannot use instruction names for identifier names."); } BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment) { - // Error message: "Identifier expected, got instruction name." - BOOST_CHECK(!successAssemble("{ 2 =: gas }")); + CHECK_ASSEMBLE_ERROR("{ 2 =: gas }", ParserError, "Identifier expected, got instruction name."); } BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment) { - // Error message: "Cannot use instruction names for identifier names." - BOOST_CHECK(!successAssemble("{ gas := 2 }")); + CHECK_ASSEMBLE_ERROR("{ gas := 2 }", ParserError, "Label name / variable name must precede \":\""); } BOOST_AUTO_TEST_CASE(revert) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 19665a26..130b0d3a 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1681,6 +1681,42 @@ BOOST_AUTO_TEST_CASE(send_ether) BOOST_CHECK_EQUAL(balanceAt(address), amount); } +BOOST_AUTO_TEST_CASE(transfer_ether) +{ + char const* sourceCode = R"( + contract A { + function A() payable {} + function a(address addr, uint amount) returns (uint) { + addr.transfer(amount); + return this.balance; + } + function b(address addr, uint amount) { + addr.transfer(amount); + } + } + + contract B { + } + + contract C { + function () payable { + throw; + } + } + )"; + compileAndRun(sourceCode, 0, "B"); + u160 const nonPayableRecipient = m_contractAddress; + compileAndRun(sourceCode, 0, "C"); + u160 const oogRecipient = m_contractAddress; + compileAndRun(sourceCode, 20, "A"); + u160 payableRecipient(23); + BOOST_CHECK(callContractFunction("a(address,uint256)", payableRecipient, 10) == encodeArgs(10)); + BOOST_CHECK_EQUAL(balanceAt(payableRecipient), 10); + BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10); + BOOST_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10) == encodeArgs()); + BOOST_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10) == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(log0) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 1a4f3cdc..3b137572 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2950,6 +2950,19 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_6) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(tuple_assignment_from_void_function) +{ + char const* text = R"( + contract C { + function f() { } + function g() { + var (x,) = (f(), f()); + } + } + )"; + CHECK_ERROR(text, TypeError, "Cannot declare variable with void (empty tuple) type."); +} + BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity) { char const* text = R"( @@ -4736,6 +4749,23 @@ BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid) CHECK_ERROR(text, TypeError, ""); } +BOOST_AUTO_TEST_CASE(external_function_to_function_type_calldata_parameter) +{ + // This is a test that checks that the type of the `bytes` parameter is + // correctly changed from its own type `bytes calldata` to `bytes memory` + // when converting to a function type. + char const* text = R"( + contract C { + function f(function(bytes memory x) external g) { } + function callback(bytes x) external {} + function g() { + f(this.callback); + } + } + )"; + CHECK_SUCCESS(text); +} + BOOST_AUTO_TEST_CASE(external_function_type_to_address) { char const* text = R"( @@ -4852,6 +4882,19 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) CHECK_WARNING(text, "Inline assembly block is not balanced"); } +BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) +{ + char const* text = R"( + contract c { + uint8 x; + function f() { + assembly { x pop } + } + } + )"; + CHECK_WARNING(text, "Inline assembly block is not balanced"); +} + BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) { char const* text = R"( @@ -5079,6 +5122,40 @@ BOOST_AUTO_TEST_CASE(invalid_address_length) CHECK_WARNING(text, "checksum"); } +BOOST_AUTO_TEST_CASE(early_exit_on_fatal_errors) +{ + // This tests a crash that occured because we did not stop for fatal errors. + char const* text = R"( + contract C { + struct S { + ftring a; + } + S public s; + function s() s { + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique"); +} + +BOOST_AUTO_TEST_CASE(address_methods) +{ + char const* text = R"( + contract C { + function f() { + address addr; + uint balance = addr.balance; + bool callRet = addr.call(); + bool callcodeRet = addr.callcode(); + bool delegatecallRet = addr.delegatecall(); + bool sendRet = addr.send(1); + addr.transfer(1); + } + } + )"; + CHECK_SUCCESS(text); +} + BOOST_AUTO_TEST_SUITE_END() } |