diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/CMakeLists.txt | 22 | ||||
-rw-r--r-- | test/RPCSession.cpp | 49 | ||||
-rw-r--r-- | test/RPCSession.h | 10 | ||||
-rwxr-xr-x | test/cmdlineTests.sh | 23 | ||||
-rw-r--r-- | test/fuzzer.cpp | 91 | ||||
-rw-r--r-- | test/libsolidity/InlineAssembly.cpp | 124 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 36 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 77 |
8 files changed, 362 insertions, 70 deletions
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() } |