diff options
author | chriseth <chris@ethereum.org> | 2017-02-24 17:39:55 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-24 17:39:55 +0800 |
commit | 92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6 (patch) | |
tree | 4f5d35f34c7598e0640576376d9e4544378ed0f8 | |
parent | dcc16c81e26f31141ae766096873b5fd7741cdf5 (diff) | |
parent | bec3c6fab6bf02aea5664be4423f45e98db22e8e (diff) | |
download | dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.gz dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.bz2 dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.lz dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.xz dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.zst dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.zip |
Merge branch 'develop' into fixNoMobile
-rw-r--r-- | Changelog.md | 3 | ||||
-rw-r--r-- | docs/common-patterns.rst | 2 | ||||
-rw-r--r-- | docs/installing-solidity.rst | 18 | ||||
-rw-r--r-- | libevmasm/Assembly.cpp | 2 | ||||
-rw-r--r-- | libsolidity/analysis/NameAndTypeResolver.cpp | 141 | ||||
-rw-r--r-- | libsolidity/analysis/NameAndTypeResolver.h | 3 | ||||
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.cpp | 9 | ||||
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.h | 2 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 2 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 4 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmCodeGen.cpp | 9 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmData.h | 7 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.cpp | 138 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.h | 4 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmPrinter.cpp | 18 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmPrinter.h | 2 | ||||
-rw-r--r-- | test/CMakeLists.txt | 22 | ||||
-rwxr-xr-x | test/cmdlineTests.sh | 23 | ||||
-rw-r--r-- | test/fuzzer.cpp | 92 | ||||
-rw-r--r-- | test/libsolidity/InlineAssembly.cpp | 124 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 29 |
21 files changed, 484 insertions, 170 deletions
diff --git a/Changelog.md b/Changelog.md index 5684adbd..19c3755b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,8 +9,11 @@ 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. + * 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/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/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..4025831e 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 diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 75dee6db..ff42cda9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1652,6 +1652,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; @@ -1967,6 +1968,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())) @@ -1975,6 +1978,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): retParams.push_back(member.type); retParamNames.push_back(member.name); } + } } else { 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/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/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..85a8fe99 --- /dev/null +++ b/test/fuzzer.cpp @@ -0,0 +1,92 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * 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>{ + "Compiler error", + "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: \"" << invalid << "\"" << 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/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 1a4f3cdc..a1ebc300 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4852,6 +4852,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 +5092,22 @@ 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_SUITE_END() } |