diff --git a/Changelog.md b/Changelog.md index 5684adbdf..19c3755b4 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 fa5e68a69..a2d7ce719 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 42905ede4..fb405475d 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 `_. + +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 b7859c1f0..f12e8aa87 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 013842602..336dc894d 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -21,6 +21,7 @@ */ #include + #include #include #include @@ -130,62 +131,9 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map(&_node)) - { - m_currentScope = m_scopes[contract->scope()].get(); - solAssert(!!m_currentScope, ""); - - for (ASTPointer const& baseContract: contract->baseContracts()) - if (!resolveNamesAndTypes(*baseContract, true)) - success = false; - - m_currentScope = m_scopes[contract].get(); - - if (success) - { - linearizeBaseContracts(*contract); - vector 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 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 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 NameAndTypeResolver::cleanedDeclarations( solAssert(_declarations.size() > 1, ""); vector 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(*it) || dynamic_cast(*it) || dynamic_cast(*it), - "Found overloading involving something not a function or a variable"); + solAssert( + dynamic_cast(declaration) || + dynamic_cast(declaration) || + dynamic_cast(declaration), + "Found overloading involving something not a function or a variable." + ); - shared_ptr 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 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(&_node)) + { + bool success = true; + m_currentScope = m_scopes[contract->scope()].get(); + solAssert(!!m_currentScope, ""); + + for (ASTPointer const& baseContract: contract->baseContracts()) + if (!resolveNamesAndTypes(*baseContract, true)) + success = false; + + m_currentScope = m_scopes[contract].get(); + + if (success) + { + linearizeBaseContracts(*contract); + vector 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 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 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 828b566fc..038a887b4 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 c06181d89..37bcb2d9a 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 23ac6b07c..dce343d33 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 28cb9acc3..4025831ea 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 75dee6dbb..ff42cda90 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1652,6 +1652,7 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const for (ASTPointer 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(returnType.get())) { for (auto const& member: structType->members(nullptr)) + { + solAssert(member.type, ""); if (member.type->category() != Category::Mapping) { if (auto arrayType = dynamic_cast(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 43c3b27a9..faa7dabdf 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 d622ff54c..d61b5803e 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; +using Statement = boost::variant; /// 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 value; }; /// Functional instruction, e.g. "mul(mload(20), add(2, x))" struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector arguments; }; +struct FunctionCall { SourceLocation location; Identifier functionName; std::vector arguments; }; /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr value; }; /// Block that creates a scope (frees declared stack variables) struct Block { SourceLocation location; std::vector statements; }; +/// Function definition ("function f(a, b) -> (d, e) { ... }") +struct FunctionDefinition { SourceLocation location; std::string name; std::vector arguments; std::vector returns; Block body; }; struct LocationExtractor: boost::static_visitor { diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 46a2730d9..0fc0a34f8 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(); 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)); - 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(); + 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) - { - if (m_scanner->currentToken() != Token::Comma) - fatalParserError(string( - "Expected comma (" + - instrInfo.name + - " expects " + - boost::lexical_cast(args) + - " arguments)" - )); - else - m_scanner->next(); - } + funDef.arguments.push_back(expectAsmIdentifier()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); } - ret.location.end = endPosition(); - if (m_scanner->currentToken() == Token::Comma) - fatalParserError( - string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast(args) + " arguments)") - ); expectToken(Token::RParen); - return ret; + if (m_scanner->currentToken() == Token::Sub) + { + expectToken(Token::Sub); + expectToken(Token::GreaterThan); + expectToken(Token::LParen); + while (true) + { + funDef.returns.push_back(expectAsmIdentifier()); + if (m_scanner->currentToken() == Token::RParen) + break; + expectToken(Token::Comma); + } + expectToken(Token::RParen); + } + 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)); + 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(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(args) + " arguments)") + ); + expectToken(Token::RParen); + return ret; + } + else if (_instruction.type() == typeid(Identifier)) + { + FunctionCall ret; + ret.functionName = std::move(boost::get(_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 643548dd0..4b4a24ae5 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -67,7 +67,9 @@ protected: std::map 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 ab2a03ff0..a70b0b786 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 39069d021..a7a1de0a5 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 609aaab36..4d56ec9d6 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 fc48654a0..cb714efe2 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 000000000..85a8fe99b --- /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 . +*/ +/** + * Executable for use with AFL . + * Reads a single source from stdin and signals a failure for internal errors. + */ + +#include + +#include +#include + +using namespace std; + +extern "C" +{ +extern char const* compileJSON(char const* _input, bool _optimize); +} + +string contains(string const& _haystack, vector 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{ + "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 8744d96f8..9035599ba 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -20,14 +20,19 @@ * Unit tests for inline assembly. */ -#include -#include -#include -#include +#include "../TestHelper.h" + #include +#include #include #include -#include "../TestHelper.h" +#include +#include + +#include + +#include +#include using namespace std; @@ -41,31 +46,44 @@ namespace test namespace { -bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true) +boost::optional parseAndReturnFirstError(string const& _source, bool _assemble = false, bool _allowWarnings = true) { assembly::InlineAssemblyStack stack; + bool success = false; try { - if (!stack.parse(std::make_shared(CharStream(_source)))) - return false; - if (_assemble) - { + success = stack.parse(std::make_shared(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 (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) - return false; + if (!success) + { + BOOST_CHECK_EQUAL(stack.errors().size(), 1); + return *stack.errors().front(); + } + 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 1a4f3cdc9..a1ebc3008 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() }