From 2c93278719d31f248ba4833f6dc1cc7829327b27 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Fri, 17 Jul 2020 10:00:25 +0200 Subject: [PATCH 01/11] Fix push().push() --- Changelog.md | 1 + libsolidity/formal/SMTEncoder.cpp | 24 ++++++++++++++++++- .../array_members/push_push_no_args_1.sol | 9 +++++++ .../push_push_no_args_1_fail.sol | 12 ++++++++++ .../array_members/push_push_no_args_2.sol | 11 +++++++++ .../push_push_no_args_2_fail.sol | 15 ++++++++++++ .../array_members/storage_pointer_push_1.sol | 18 ++++++++++++++ .../storage_pointer_push_1_safe.sol | 15 ++++++++++++ 8 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1.sol create mode 100644 test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1_fail.sol create mode 100644 test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2.sol create mode 100644 test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2_fail.sol create mode 100644 test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1.sol create mode 100644 test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1_safe.sol diff --git a/Changelog.md b/Changelog.md index e0987a919..134bd5df8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ Bugfixes: * SMTChecker: Fix internal error when using bitwise operators on fixed bytes type. * SMTChecker: Fix internal error when using compound bitwise operator assignments on array indices inside branches. * SMTChecker: Fix error in events with indices of type static array. + * SMTChecker: Fix internal error in sequential storage array pushes (``push().push()`). * Type Checker: Fix overload resolution in combination with ``{value: ...}``. * Type Checker: Fix internal compiler error related to oversized types. * Code Generator: Avoid double cleanup when copying to memory. diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index e3f16b2ef..417a16921 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -1077,7 +1077,7 @@ void SMTEncoder::arrayPush(FunctionCall const& _funCall) m_context.addAssertion(symbArray->length() == oldLength + 1); if (arguments.empty()) - defineExpr(_funCall, element); + defineExpr(_funCall, smtutil::Expression::select(symbArray->elements(), oldLength)); arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue()); } @@ -1119,6 +1119,28 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression } else if (auto const* indexAccess = dynamic_cast(&_expr)) arrayIndexAssignment(*indexAccess, _array); + else if (auto const* funCall = dynamic_cast(&_expr)) + { + FunctionType const& funType = dynamic_cast(*funCall->expression().annotation().type); + if (funType.kind() == FunctionType::Kind::ArrayPush) + { + auto memberAccess = dynamic_cast(&funCall->expression()); + solAssert(memberAccess, ""); + auto symbArray = dynamic_pointer_cast(m_context.expression(memberAccess->expression())); + solAssert(symbArray, ""); + + auto oldLength = symbArray->length(); + auto store = smtutil::Expression::store( + symbArray->elements(), + symbArray->length() - 1, + _array + ); + symbArray->increaseIndex(); + m_context.addAssertion(symbArray->elements() == store); + m_context.addAssertion(symbArray->length() == oldLength); + arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue()); + } + } else if (dynamic_cast(&_expr)) m_errorReporter.warning( 9599_error, diff --git a/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1.sol b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1.sol new file mode 100644 index 000000000..8acd7998a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; +contract C { + int[][] array2d; + function l() public { + array2d.push().push(); + assert(array2d.length > 0); + assert(array2d[array2d.length - 1].length > 0); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1_fail.sol b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1_fail.sol new file mode 100644 index 000000000..a85638189 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_1_fail.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; +contract C { + int[][] array2d; + function l() public { + array2d.push().push(); + assert(array2d.length > 2); + assert(array2d[array2d.length - 1].length > 3); + } +} +// ---- +// Warning 4661: (113-139): Assertion violation happens here +// Warning 4661: (143-189): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2.sol b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2.sol new file mode 100644 index 000000000..b6fd8e8c9 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; +contract C { + int[][][] array2d; + function l() public { + array2d.push().push().push(); + assert(array2d.length > 0); + uint last = array2d[array2d.length - 1].length; + assert(last > 0); + assert(array2d[array2d.length - 1][last - 1].length > 0); + } +} diff --git a/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2_fail.sol b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2_fail.sol new file mode 100644 index 000000000..3d22b862a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/push_push_no_args_2_fail.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; +contract C { + int[][][] array2d; + function l() public { + array2d.push().push().push(); + assert(array2d.length > 2); + uint last = array2d[array2d.length - 1].length; + assert(last > 3); + assert(array2d[array2d.length - 1][last - 1].length > 4); + } +} +// ---- +// Warning 4661: (122-148): Assertion violation happens here +// Warning 4661: (202-218): Assertion violation happens here +// Warning 4661: (222-278): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1.sol b/test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1.sol new file mode 100644 index 000000000..527d06534 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; +contract C { + int[][] array2d; + function l() public { + s().push(); + // False positive. + // Knowledge is erased because `s()` is a storage pointer. + assert(array2d[2].length > 0); + } + function s() internal returns (int[] storage) { + array2d.push(); + array2d.push(); + array2d.push(); + return array2d[2]; + } +} +// ---- +// Warning 4661: (184-213): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1_safe.sol b/test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1_safe.sol new file mode 100644 index 000000000..843b46b16 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/array_members/storage_pointer_push_1_safe.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; +contract C { + int[][] array2d; + function l() public { + s(); + array2d[2].push(); + assert(array2d[2].length > 0); + } + function s() internal returns (int[] storage) { + array2d.push(); + array2d.push(); + array2d.push(); + return array2d[2]; + } +} From 1b95a5e1fcb8c8e506b0cc09fcb48cbbb1aec724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiny=E7=86=8A?= Date: Tue, 21 Jul 2020 16:28:13 +0800 Subject: [PATCH 02/11] Update simplified chinese translation link. The implified chinese translation version now host on https://learnblockchain.cn/docs/solidity/ . This url will be keep updating . --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 89976a8d4..d616d6e88 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -77,7 +77,7 @@ version stands as a reference. * `Japanese `_ * `Korean `_ (in progress) * `Russian `_ (rather outdated) -* `Simplified Chinese `_ (in progress) +* `Simplified Chinese `_ (in progress) * `Spanish `_ * `Turkish `_ (partial) From 952101996cb6a198e493e253a07e7184e8359bbf Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Tue, 16 Jun 2020 17:13:46 +0200 Subject: [PATCH 03/11] Removing expectDeposit() from AsmAnalysis as unused function --- libyul/AsmAnalysis.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 8d8db59ab..552191c4d 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -102,7 +102,6 @@ private: /// Vists the expression and expects it to return a single boolean value. /// Reports an error otherwise. void expectBoolExpression(Expression const& _expr); - bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location); /// Verifies that a variable to be assigned to exists, can be assigned to /// and has the same type as the value. From c11bb24a514fc303b61c15658005b78261aca093 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 21 Jul 2020 12:10:33 +0200 Subject: [PATCH 04/11] Add example about array literals. --- docs/types/reference-types.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst index 5bd242f06..17a52f43b 100644 --- a/docs/types/reference-types.rst +++ b/docs/types/reference-types.rst @@ -237,6 +237,23 @@ memory arrays, i.e. the following is not possible: It is planned to remove this restriction in the future, but it creates some complications because of how arrays are passed in the ABI. +If you want to initialize dynamically-sized arrays, you have to assign the +individual elements: + +:: + + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.4.0 <0.7.0; + + contract C { + function f() public pure { + uint[] memory x = new uint[](3); + x[0] = 1; + x[1] = 3; + x[2] = 4; + } + } + .. index:: ! array;length, length, push, pop, !array;push, !array;pop .. _array-members: From 4067bab7ddac83818916aed840dd6d43396fb723 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 6 Jul 2020 19:32:48 +0200 Subject: [PATCH 05/11] Add corpus based multi source fuzzer Co-authored-by: Leonardo --- test/TestCaseReader.cpp | 7 +++++++ test/TestCaseReader.h | 1 + test/tools/fuzzer_common.cpp | 6 +++--- test/tools/fuzzer_common.h | 9 ++++++++- test/tools/ossfuzz/CMakeLists.txt | 4 ++-- test/tools/ossfuzz/solc_noopt_ossfuzz.cpp | 17 ++++++++++++++++- test/tools/ossfuzz/solc_opt_ossfuzz.cpp | 19 +++++++++++++++++-- 7 files changed, 54 insertions(+), 9 deletions(-) diff --git a/test/TestCaseReader.cpp b/test/TestCaseReader.cpp index 7b3611d82..d38e883ea 100644 --- a/test/TestCaseReader.cpp +++ b/test/TestCaseReader.cpp @@ -38,6 +38,13 @@ TestCaseReader::TestCaseReader(string const& _filename): m_unreadSettings = m_settings; } +TestCaseReader::TestCaseReader(istringstream const& _str) +{ + tie(m_sources, m_lineNumber) = parseSourcesAndSettingsWithLineNumber( + static_cast(const_cast(_str)) + ); +} + string const& TestCaseReader::source() const { if (m_sources.sources.size() != 1) diff --git a/test/TestCaseReader.h b/test/TestCaseReader.h index 720c26338..620565aee 100644 --- a/test/TestCaseReader.h +++ b/test/TestCaseReader.h @@ -42,6 +42,7 @@ class TestCaseReader public: TestCaseReader() = default; explicit TestCaseReader(std::string const& _filename); + explicit TestCaseReader(std::istringstream const& _testCode); SourceMap const& sources() const { return m_sources; } std::string const& source() const; diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index 437ab5d22..cb153e0e8 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -72,16 +72,16 @@ void FuzzerUtil::testCompilerJsonInterface(string const& _input, bool _optimize, runCompiler(jsonCompactPrint(config), _quiet); } -void FuzzerUtil::testCompiler(string const& _input, bool _optimize) +void FuzzerUtil::testCompiler(StringMap const& _input, bool _optimize, unsigned _rand) { frontend::CompilerStack compiler; - EVMVersion evmVersion = s_evmVersions[_input.size() % s_evmVersions.size()]; + EVMVersion evmVersion = s_evmVersions[_rand % s_evmVersions.size()]; frontend::OptimiserSettings optimiserSettings; if (_optimize) optimiserSettings = frontend::OptimiserSettings::standard(); else optimiserSettings = frontend::OptimiserSettings::minimal(); - compiler.setSources({{"", _input}}); + compiler.setSources(_input); compiler.setEVMVersion(evmVersion); compiler.setOptimiserSettings(optimiserSettings); try diff --git a/test/tools/fuzzer_common.h b/test/tools/fuzzer_common.h index c8749d9b1..cd30adc97 100644 --- a/test/tools/fuzzer_common.h +++ b/test/tools/fuzzer_common.h @@ -16,6 +16,9 @@ */ // SPDX-License-Identifier: GPL-3.0 +#include + +#include #include /** @@ -28,5 +31,9 @@ struct FuzzerUtil static void testCompilerJsonInterface(std::string const& _input, bool _optimize, bool _quiet); static void testConstantOptimizer(std::string const& _input, bool _quiet); static void testStandardCompiler(std::string const& _input, bool _quiet); - static void testCompiler(std::string const& _input, bool _optimize); + /// Compiles @param _input which is a map of input file name to source code + /// string with optimisation turned on if @param _optimize is true + /// (off otherwise) and a pseudo-random @param _rand that selects the EVM + /// version to be compiled for. + static void testCompiler(solidity::StringMap const& _input, bool _optimize, unsigned _rand); }; diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 9ad7b9c80..a799bd736 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -23,11 +23,11 @@ if (OSSFUZZ) endif() if (OSSFUZZ) - add_executable(solc_opt_ossfuzz solc_opt_ossfuzz.cpp ../fuzzer_common.cpp) + add_executable(solc_opt_ossfuzz solc_opt_ossfuzz.cpp ../fuzzer_common.cpp ../../TestCaseReader.cpp) target_link_libraries(solc_opt_ossfuzz PRIVATE libsolc evmasm) set_target_properties(solc_opt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) - add_executable(solc_noopt_ossfuzz solc_noopt_ossfuzz.cpp ../fuzzer_common.cpp) + add_executable(solc_noopt_ossfuzz solc_noopt_ossfuzz.cpp ../fuzzer_common.cpp ../../TestCaseReader.cpp) target_link_libraries(solc_noopt_ossfuzz PRIVATE libsolc evmasm) set_target_properties(solc_noopt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) diff --git a/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp index e2f3a294f..8d615c0bd 100644 --- a/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp +++ b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp @@ -18,6 +18,11 @@ #include +#include + +#include + +using namespace solidity::frontend::test; using namespace std; extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) @@ -25,7 +30,17 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) if (_size <= 600) { string input(reinterpret_cast(_data), _size); - FuzzerUtil::testCompiler(input, /*optimize=*/false); + map sourceCode; + try + { + TestCaseReader t = TestCaseReader(std::istringstream(input)); + sourceCode = t.sources().sources; + } + catch (runtime_error const&) + { + return 0; + } + FuzzerUtil::testCompiler(sourceCode, /*optimize=*/false, /*_rand=*/_size); } return 0; } diff --git a/test/tools/ossfuzz/solc_opt_ossfuzz.cpp b/test/tools/ossfuzz/solc_opt_ossfuzz.cpp index bff19407f..0d9c7f44e 100644 --- a/test/tools/ossfuzz/solc_opt_ossfuzz.cpp +++ b/test/tools/ossfuzz/solc_opt_ossfuzz.cpp @@ -18,14 +18,29 @@ #include +#include + +#include + +using namespace solidity::frontend::test; using namespace std; extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) { if (_size <= 600) { - string input(reinterpret_cast(_data), _size); - FuzzerUtil::testCompiler(input, /*optimize=*/true); + string input(reinterpret_cast(_data), _size); + map sourceCode; + try + { + TestCaseReader t = TestCaseReader(std::istringstream(input)); + sourceCode = t.sources().sources; + } + catch (runtime_error const&) + { + return 0; + } + FuzzerUtil::testCompiler(sourceCode, /*optimize=*/true, /*rand=*/_size); } return 0; } From 0a3c9ec358573d601ae6f101bc85bb92df2bcc7c Mon Sep 17 00:00:00 2001 From: a3d4 Date: Thu, 16 Jul 2020 22:06:55 +0200 Subject: [PATCH 06/11] Print source file names for uncovered error codes. Use more consistent naming. --- scripts/error_codes.py | 121 ++++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/scripts/error_codes.py b/scripts/error_codes.py index da317d45c..8ab9b04dc 100755 --- a/scripts/error_codes.py +++ b/scripts/error_codes.py @@ -36,24 +36,26 @@ def in_comment(source, pos): return slash_star_pos > star_slash_pos -def find_ids_in_source_file(file_name, ids): +def find_ids_in_source_file(file_name, id_to_file_names): source = read_file(file_name) for m in re.finditer(SOURCE_FILE_PATTERN, source): if in_comment(source, m.start()): continue underscore_pos = m.group(0).index("_") id = m.group(0)[0:underscore_pos] - if id in ids: - ids[id] += 1 + if id in id_to_file_names: + id_to_file_names[id].append(file_name) else: - ids[id] = 1 + id_to_file_names[id] = [file_name] -def get_used_ids(file_names): - used_ids = {} +def find_ids_in_source_files(file_names): + """Returns a dictionary with list of source files for every appearance of every id""" + + id_to_file_names = {} for file_name in file_names: - find_ids_in_source_file(file_name, used_ids) - return used_ids + find_ids_in_source_file(file_name, id_to_file_names) + return id_to_file_names def get_next_id(available_ids): @@ -63,7 +65,7 @@ def get_next_id(available_ids): return next_id -def fix_ids_in_file(file_name, available_ids, used_ids): +def fix_ids_in_source_file(file_name, id_to_count, available_ids): source = read_file(file_name) k = 0 @@ -75,11 +77,11 @@ def fix_ids_in_file(file_name, available_ids, used_ids): id = m.group(0)[0:underscore_pos] # incorrect id or id has a duplicate somewhere - if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or used_ids[id] > 1): - assert id in used_ids + if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or id_to_count[id] > 1): + assert id in id_to_count new_id = get_next_id(available_ids) - assert new_id not in used_ids - used_ids[id] -= 1 + assert new_id not in id_to_count + id_to_count[id] -= 1 else: new_id = id @@ -94,10 +96,15 @@ def fix_ids_in_file(file_name, available_ids, used_ids): print(f"Fixed file: {file_name}") -def fix_ids(used_ids, file_names): - available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys() +def fix_ids_in_source_files(file_names, id_to_count): + """ + Fixes ids in given source files; + id_to_count contains number of appearances of every id in sources + """ + + available_ids = {str(id) for id in range(1000, 10000)} - id_to_count.keys() for file_name in file_names: - fix_ids_in_file(file_name, available_ids, used_ids) + fix_ids_in_source_file(file_name, id_to_count, available_ids) def find_files(top_dir, sub_dirs, extensions): @@ -121,10 +128,12 @@ def find_ids_in_test_file(file_name): def find_ids_in_test_files(file_names): - used_ids = set() + """Returns a set containing all ids in tests""" + + ids = set() for file_name in file_names: - used_ids |= find_ids_in_test_file(file_name) - return used_ids + ids |= find_ids_in_test_file(file_name) + return ids def find_ids_in_cmdline_test_err(file_name): @@ -142,7 +151,23 @@ def print_ids(ids): print(id, end="") -def examine_id_coverage(top_dir, used_ids): +def print_ids_per_file(ids, id_to_file_names, top_dir): + file_name_to_ids = {} + for id in ids: + for file_name in id_to_file_names[id]: + relpath = path.relpath(file_name, top_dir) + if relpath not in file_name_to_ids: + file_name_to_ids[relpath] = [] + file_name_to_ids[relpath].append(id) + + for file_name in sorted(file_name_to_ids): + print(file_name) + for id in sorted(file_name_to_ids[file_name]): + print(f" {id}", end="") + print() + + +def examine_id_coverage(top_dir, source_id_to_file_names): test_sub_dirs = [ path.join("test", "libsolidity", "errorRecoveryTests"), path.join("test", "libsolidity", "smtCheckerTests"), @@ -153,27 +178,28 @@ def examine_id_coverage(top_dir, used_ids): test_sub_dirs, [".sol"] ) - covered_ids = find_ids_in_test_files(test_file_names) + source_ids = source_id_to_file_names.keys() + test_ids = find_ids_in_test_files(test_file_names) # special case, we are interested in warnings which are ignored by regular tests: # Warning (1878): SPDX license identifier not provided in source file. .... # Warning (3420): Source file does not specify required compiler version! - covered_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "error_codes", "err")) + test_ids |= find_ids_in_cmdline_test_err(path.join(top_dir, "test", "cmdlineTests", "error_codes", "err")) - print(f"IDs in source files: {len(used_ids)}") - print(f"IDs in test files : {len(covered_ids)} ({len(covered_ids) - len(used_ids)})") + print(f"IDs in source files: {len(source_ids)}") + print(f"IDs in test files : {len(test_ids)} ({len(test_ids) - len(source_ids)})") print() - unused_covered_ids = covered_ids - used_ids - if len(unused_covered_ids) != 0: + test_only_ids = test_ids - source_ids + if len(test_only_ids) != 0: print("Error. The following error codes found in tests, but not in sources:") - print_ids(unused_covered_ids) + print_ids(test_only_ids) return 1 - used_uncovered_ids = used_ids - covered_ids - if len(used_uncovered_ids) != 0: + source_only_ids = source_ids - test_ids + if len(source_only_ids) != 0: print("The following error codes found in sources, but not in tests:") - print_ids(used_uncovered_ids) + print_ids_per_file(source_only_ids, source_id_to_file_names, top_dir) print("\n\nPlease make sure to add appropriate tests.") return 1 @@ -187,22 +213,22 @@ def main(argv): fix = False no_confirm = False examine_coverage = False - next = False + next_id = False opts, args = getopt.getopt(argv, "", ["check", "fix", "no-confirm", "examine-coverage", "next"]) for opt, arg in opts: - if opt == '--check': + if opt == "--check": check = True elif opt == "--fix": fix = True - elif opt == '--no-confirm': + elif opt == "--no-confirm": no_confirm = True - elif opt == '--examine-coverage': + elif opt == "--examine-coverage": examine_coverage = True - elif opt == '--next': - next = True + elif opt == "--next": + next_id = True - if [check, fix, examine_coverage, next].count(True) != 1: + if [check, fix, examine_coverage, next_id].count(True) != 1: print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next") exit(1) @@ -213,32 +239,34 @@ def main(argv): ["libevmasm", "liblangutil", "libsolc", "libsolidity", "libsolutil", "libyul", "solc"], [".h", ".cpp"] ) - used_ids = get_used_ids(source_file_names) + source_id_to_file_names = find_ids_in_source_files(source_file_names) ok = True - for id in sorted(used_ids): + for id in sorted(source_id_to_file_names): if len(id) != 4: print(f"ID {id} length != 4") ok = False if id[0] == "0": print(f"ID {id} starts with zero") ok = False - if used_ids[id] > 1: - print(f"ID {id} appears {used_ids[id]} times") + if len(source_id_to_file_names[id]) > 1: + print(f"ID {id} appears {len(source_id_to_file_names[id])} times") ok = False if examine_coverage: if not ok: print("Incorrect IDs have to be fixed before applying --examine-coverage") - res = examine_id_coverage(cwd, used_ids.keys()) + exit(1) + res = examine_id_coverage(cwd, source_id_to_file_names) exit(res) random.seed() - if next: + if next_id: if not ok: print("Incorrect IDs have to be fixed before applying --next") - available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys() + exit(1) + available_ids = {str(id) for id in range(1000, 10000)} - source_id_to_file_names.keys() next_id = get_next_id(available_ids) print(f"Next ID: {next_id}") exit(0) @@ -263,7 +291,10 @@ def main(argv): if answer not in "yY": exit(1) - fix_ids(used_ids, source_file_names) + # number of appearances for every id + source_id_to_count = { id: len(file_names) for id, file_names in source_id_to_file_names.items() } + + fix_ids_in_source_files(source_file_names, source_id_to_count) print("Fixing completed") exit(2) From 18fbbfa5ca4e62e6d371123de215e065c15e16e2 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Tue, 21 Jul 2020 14:37:22 +0200 Subject: [PATCH 07/11] Fuzzer: Count step by number of interpreted statements Co-authored-by: chriseth --- test/libyul/EwasmTranslationTest.cpp | 2 +- test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp | 10 +++++---- test/tools/yulInterpreter/Interpreter.cpp | 22 ++++++++++++++------ test/tools/yulInterpreter/Interpreter.h | 4 ++++ 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/test/libyul/EwasmTranslationTest.cpp b/test/libyul/EwasmTranslationTest.cpp index dd732f9a8..be9ed77d9 100644 --- a/test/libyul/EwasmTranslationTest.cpp +++ b/test/libyul/EwasmTranslationTest.cpp @@ -99,7 +99,7 @@ string EwasmTranslationTest::interpret() { InterpreterState state; state.maxTraceSize = 10000; - state.maxSteps = 100000; + state.maxSteps = 1000000; try { Interpreter::run(state, WasmDialect{}, *m_object->code); diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index 4c8ddb94f..c95b4db7f 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -97,15 +97,17 @@ DEFINE_PROTO_FUZZER(Program const& _input) EVMDialect::strictAssemblyForEVMObjects(version) ); - if (termReason == yulFuzzerUtil::TerminationReason::StepLimitReached) + if ( + termReason == yulFuzzerUtil::TerminationReason::StepLimitReached || + termReason == yulFuzzerUtil::TerminationReason::TraceLimitReached + ) return; stack.optimize(); - termReason = yulFuzzerUtil::interpret( + yulFuzzerUtil::interpret( os2, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(version), - (yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 4) + EVMDialect::strictAssemblyForEVMObjects(version) ); bool isTraceEq = (os1.str() == os2.str()); diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index ba946e732..a8496793f 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -145,6 +145,11 @@ void Interpreter::operator()(ForLoop const& _forLoop) } while (evaluate(*_forLoop.condition) != 0) { + // Increment step for each loop iteration for loops with + // an empty body and post blocks to prevent a deadlock. + if (_forLoop.body.statements.size() == 0 && _forLoop.post.statements.size() == 0) + incrementStep(); + m_state.controlFlowState = ControlFlowState::Default; (*this)(_forLoop.body); if (m_state.controlFlowState == ControlFlowState::Break || m_state.controlFlowState == ControlFlowState::Leave) @@ -176,12 +181,6 @@ void Interpreter::operator()(Leave const&) void Interpreter::operator()(Block const& _block) { - m_state.numSteps++; - if (m_state.maxSteps > 0 && m_state.numSteps >= m_state.maxSteps) - { - m_state.trace.emplace_back("Interpreter execution step limit reached."); - throw StepLimitReached(); - } enterScope(_block); // Register functions. for (auto const& statement: _block.statements) @@ -193,6 +192,7 @@ void Interpreter::operator()(Block const& _block) for (auto const& statement: _block.statements) { + incrementStep(); visit(statement); if (m_state.controlFlowState != ControlFlowState::Default) break; @@ -235,6 +235,16 @@ void Interpreter::leaveScope() yulAssert(m_scope, ""); } +void Interpreter::incrementStep() +{ + m_state.numSteps++; + if (m_state.maxSteps > 0 && m_state.numSteps >= m_state.maxSteps) + { + m_state.trace.emplace_back("Interpreter execution step limit reached."); + throw StepLimitReached(); + } +} + void ExpressionEvaluator::operator()(Literal const& _literal) { static YulString const trueString("true"); diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index a52c098ed..00e02f6e2 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -154,6 +154,10 @@ private: void enterScope(Block const& _block); void leaveScope(); + /// Increment interpreter step count, throwing exception if step limit + /// is reached. + void incrementStep(); + Dialect const& m_dialect; InterpreterState& m_state; /// Values of variables. From e02878329dd3b73ad7a8dad039815793631f9ffe Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 21 Jul 2020 15:53:00 +0200 Subject: [PATCH 08/11] Prepare for 0.6.12 release. --- Changelog.md | 35 +++++++++++++++++++---------------- docs/bugs_by_version.json | 4 ++++ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Changelog.md b/Changelog.md index 990b78e2b..79cbf3e10 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,27 +1,30 @@ -### 0.6.12 (unreleased) +### 0.6.12 (2020-07-22) Language Features: + * NatSpec: Implement tag ``@inheritdoc`` to copy documentation from a specific base contract. * Wasm backend: Add ``i32.ctz``, ``i64.ctz``, ``i32.popcnt``, and ``i64.popcnt``. -Compiler Features: - * Code Generator: Evaluate ``keccak256`` of string literals at compile-time. - * Peephole Optimizer: Remove unnecessary masking of tags. - * Yul EVM Code Transform: Free stack slots directly after visiting the right-hand-side of variable declarations instead of at the end of the statement only. - * NatSpec: Implement tag ``@inheritdoc`` to copy documentation from a specific contract. -Bugfixes: - * SMTChecker: Fix internal error when using bitwise operators on fixed bytes type. - * SMTChecker: Fix internal error when using compound bitwise operator assignments on array indices inside branches. - * SMTChecker: Fix error in events with indices of type static array. - * SMTChecker: Fix internal error in sequential storage array pushes (``push().push()`). - * Type Checker: Fix overload resolution in combination with ``{value: ...}``. - * Type Checker: Fix internal compiler error related to oversized types. +Compiler Features: * Code Generator: Avoid double cleanup when copying to memory. - -Compiler Features: - * Build System: Update internal dependency of jsoncpp to 1.9.3. + * Code Generator: Evaluate ``keccak256`` of string literals at compile-time. * Optimizer: Add rule to remove shifts inside the byte opcode. * Peephole Optimizer: Add rule to remove swap after dup. + * Peephole Optimizer: Remove unnecessary masking of tags. + * Yul EVM Code Transform: Free stack slots directly after visiting the right-hand-side of variable declarations instead of at the end of the statement only. + + +Bugfixes: + * SMTChecker: Fix error in events with indices of type static array. + * SMTChecker: Fix internal error in sequential storage array pushes (``push().push()``). + * SMTChecker: Fix internal error when using bitwise operators on fixed bytes type. + * SMTChecker: Fix internal error when using compound bitwise operator assignments on array indices inside branches. + * Type Checker: Fix internal compiler error related to oversized types. + * Type Checker: Fix overload resolution in combination with ``{value: ...}``. + + +Build System: + * Update internal dependency of jsoncpp to 1.9.3. ### 0.6.11 (2020-07-07) diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 000ab3ef8..fc9782c47 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -1113,6 +1113,10 @@ "bugs": [], "released": "2020-07-07" }, + "0.6.12": { + "bugs": [], + "released": "2020-07-22" + }, "0.6.2": { "bugs": [ "MissingEscapingInFormatting", From 6f97e6153c57f879de93f39590293d794f10b391 Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Wed, 17 Jun 2020 11:17:35 +0200 Subject: [PATCH 09/11] [yul] Adding support for accessing subobjects via `.` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kamil ƚliwak --- libevmasm/Assembly.cpp | 80 +++++++++++--- libevmasm/Assembly.h | 10 ++ libevmasm/AssemblyItem.cpp | 18 ++- libevmasm/AssemblyItem.h | 2 +- libyul/AsmAnalysis.cpp | 2 +- libyul/AssemblyStack.cpp | 2 +- libyul/CompilabilityChecker.cpp | 8 +- libyul/Object.cpp | 60 ++++++++-- libyul/Object.h | 21 +++- libyul/backends/evm/AbstractAssembly.h | 4 +- libyul/backends/evm/AsmCodeGen.cpp | 26 +++-- libyul/backends/evm/AsmCodeGen.h | 4 +- libyul/backends/evm/EVMAssembly.cpp | 4 +- libyul/backends/evm/EVMAssembly.h | 4 +- libyul/backends/evm/EVMDialect.cpp | 26 +++-- libyul/backends/evm/EVMObjectCompiler.cpp | 6 +- libyul/backends/evm/NoOutputAssembly.cpp | 4 +- libyul/backends/evm/NoOutputAssembly.h | 4 +- libyul/backends/wasm/EVMToEwasmTranslator.cpp | 2 +- .../input.json | 17 +++ .../output.json | 4 + test/libevmasm/Assembler.cpp | 19 ++++ test/libyul/Common.cpp | 2 +- .../libyul/objectCompiler/subObjectAccess.yul | 104 ++++++++++++++++++ 24 files changed, 357 insertions(+), 76 deletions(-) create mode 100644 test/cmdlineTests/standard_yul_object_invalid_sub/input.json create mode 100644 test/cmdlineTests/standard_yul_object_invalid_sub/output.json create mode 100644 test/libyul/objectCompiler/subObjectAccess.yul diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index b3a0dbde0..b1de71e3b 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -92,8 +92,8 @@ string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& class Functionalizer { public: - Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes): - m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes) + Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes, Assembly const& _assembly): + m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly) {} void feed(AssemblyItem const& _item) @@ -104,6 +104,9 @@ public: m_location = _item.location(); printLocation(); } + + string expression = _item.toAssemblyText(m_assembly); + if (!( _item.canBeFunctional() && _item.returnValues() <= 1 && @@ -111,10 +114,9 @@ public: )) { flush(); - m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl; + m_out << m_prefix << (_item.type() == Tag ? "" : " ") << expression << endl; return; } - string expression = _item.toAssemblyText(); if (_item.arguments() > 0) { expression += "("; @@ -160,13 +162,14 @@ private: ostream& m_out; string const& m_prefix; StringMap const& m_sourceCodes; + Assembly const& m_assembly; }; } void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const { - Functionalizer f(_out, _prefix, _sourceCodes); + Functionalizer f(_out, _prefix, _sourceCodes, *this); for (auto const& i: m_items) f.feed(i); @@ -639,7 +642,7 @@ LinkerObject const& Assembly::assemble() const case PushSubSize: { assertThrow(i.data() <= numeric_limits::max(), AssemblyException, ""); - auto s = m_subs.at(static_cast(i.data()))->assemble().bytecode.size(); + auto s = subAssemblyById(static_cast(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); uint8_t b = max(1, util::bytesRequired(s)); ret.bytecode.push_back((uint8_t)Instruction::PUSH1 - 1 + b); @@ -707,25 +710,20 @@ LinkerObject const& Assembly::assemble() const // Append an INVALID here to help tests find miscompilation. ret.bytecode.push_back(uint8_t(Instruction::INVALID)); - for (size_t i = 0; i < m_subs.size(); ++i) + for (auto const& [subIdPath, bytecodeOffset]: subRef) { - auto references = subRef.equal_range(i); - if (references.first == references.second) - continue; - for (auto ref = references.first; ref != references.second; ++ref) - { - bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); - toBigEndian(ret.bytecode.size(), r); - } - ret.append(m_subs[i]->assemble()); + bytesRef r(ret.bytecode.data() + bytecodeOffset, bytesPerDataRef); + toBigEndian(ret.bytecode.size(), r); + ret.append(subAssemblyById(subIdPath)->assemble()); } + for (auto const& i: tagRef) { size_t subId; size_t tagId; tie(subId, tagId) = i.second; assertThrow(subId == numeric_limits::max() || subId < m_subs.size(), AssemblyException, "Invalid sub id"); - std::vector const& tagPositions = + vector const& tagPositions = subId == numeric_limits::max() ? m_tagPositionsInBytecode : m_subs[subId]->m_tagPositionsInBytecode; @@ -758,3 +756,51 @@ LinkerObject const& Assembly::assemble() const } return ret; } + +vector Assembly::decodeSubPath(size_t _subObjectId) const +{ + if (_subObjectId < m_subs.size()) + return {_subObjectId}; + + auto subIdPathIt = find_if( + m_subPaths.begin(), + m_subPaths.end(), + [_subObjectId](auto const& subId) { return subId.second == _subObjectId; } + ); + + assertThrow(subIdPathIt != m_subPaths.end(), AssemblyException, ""); + return subIdPathIt->first; +} + +size_t Assembly::encodeSubPath(vector const& _subPath) +{ + assertThrow(!_subPath.empty(), AssemblyException, ""); + if (_subPath.size() == 1) + { + assertThrow(_subPath[0] < m_subs.size(), AssemblyException, ""); + return _subPath[0]; + } + + if (m_subPaths.find(_subPath) == m_subPaths.end()) + { + size_t objectId = numeric_limits::max() - m_subPaths.size(); + assertThrow(objectId >= m_subs.size(), AssemblyException, ""); + m_subPaths[_subPath] = objectId; + } + + return m_subPaths[_subPath]; +} + +Assembly const* Assembly::subAssemblyById(size_t _subId) const +{ + vector subIds = decodeSubPath(_subId); + Assembly const* currentAssembly = this; + for (size_t currentSubId: subIds) + { + currentAssembly = currentAssembly->m_subs.at(currentSubId).get(); + assertThrow(currentAssembly, AssemblyException, ""); + } + + assertThrow(currentAssembly != this, AssemblyException, ""); + return currentAssembly; +} diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 312521b71..354304dcb 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -146,6 +146,9 @@ public: /// Mark this assembly as invalid. Calling ``assemble`` on it will throw. void markAsInvalid() { m_invalid = true; } + std::vector decodeSubPath(size_t _subObjectId) const; + size_t encodeSubPath(std::vector const& _subPath); + protected: /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. Also takes an argument containing the tags of this assembly @@ -166,6 +169,9 @@ private: static std::string toStringInHex(u256 _value); bool m_invalid = false; + + Assembly const* subAssemblyById(size_t _subId) const; + protected: /// 0 is reserved for exception unsigned m_usedTags = 1; @@ -179,6 +185,10 @@ protected: std::map m_libraries; ///< Identifiers of libraries to be linked. std::map m_immutables; ///< Identifiers of immutables. + /// Map from a vector representing a path to a particular sub assembly to sub assembly id. + /// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value). + std::map, size_t> m_subPaths; + mutable LinkerObject m_assembledObject; mutable std::vector m_tagPositionsInBytecode; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 80d34f57c..e70e844d6 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -18,7 +18,10 @@ #include +#include + #include +#include #include #include @@ -170,7 +173,7 @@ string AssemblyItem::getJumpTypeAsString() const } } -string AssemblyItem::toAssemblyText() const +string AssemblyItem::toAssemblyText(Assembly const& _assembly) const { string text; switch (type()) @@ -208,11 +211,18 @@ string AssemblyItem::toAssemblyText() const text = string("data_") + util::toHex(data()); break; case PushSub: - text = string("dataOffset(sub_") + to_string(static_cast(data())) + ")"; - break; case PushSubSize: - text = string("dataSize(sub_") + to_string(static_cast(data())) + ")"; + { + vector subPathComponents; + for (size_t subPathComponentId: _assembly.decodeSubPath(static_cast(data()))) + subPathComponents.emplace_back("sub_" + to_string(subPathComponentId)); + text = + (type() == PushSub ? "dataOffset"s : "dataSize"s) + + "(" + + solidity::util::joinHumanReadable(subPathComponents, ".") + + ")"; break; + } case PushProgramSize: text = string("bytecodeSize"); break; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index dd65b109f..ac3a2c9d8 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -152,7 +152,7 @@ public: void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared(_value); } u256 const* pushedValue() const { return m_pushedValue.get(); } - std::string toAssemblyText() const; + std::string toAssemblyText(Assembly const& _assembly) const; size_t m_modifierDepth = 0; diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index e85ecd876..934cff0a4 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -72,7 +72,7 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, errors, _dialect, {}, - _object.dataNames() + _object.qualifiedDataNames() ).analyze(*_object.code); yulAssert(success && !errors.hasErrors(), "Invalid assembly/yul code."); return analysisInfo; diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index b8824740d..542de51d9 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -138,7 +138,7 @@ bool AssemblyStack::analyzeParsed(Object& _object) m_errorReporter, languageToDialect(m_language, m_evmVersion), {}, - _object.dataNames() + _object.qualifiedDataNames() ); bool success = analyzer.analyze(*_object.code); for (auto& subNode: _object.subObjects) diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 0d80a510f..2a99e3c79 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -39,7 +39,7 @@ map CompilabilityChecker::run( bool _optimizeStackAllocation ) { - if (EVMDialect const* evmDialect = dynamic_cast(&_dialect)) + if (auto const* evmDialect = dynamic_cast(&_dialect)) { NoOutputEVMDialect noOutputDialect(*evmDialect); @@ -48,8 +48,10 @@ map CompilabilityChecker::run( BuiltinContext builtinContext; builtinContext.currentObject = &_object; - for (auto name: _object.dataNames()) - builtinContext.subIDs[name] = 1; + if (!_object.name.empty()) + builtinContext.subIDs[_object.name] = 1; + for (auto const& subNode: _object.subObjects) + builtinContext.subIDs[subNode->name] = 1; NoOutputAssembly assembly; CodeTransform transform( assembly, diff --git a/libyul/Object.cpp b/libyul/Object.cpp index 1086e38cf..72206c4b6 100644 --- a/libyul/Object.cpp +++ b/libyul/Object.cpp @@ -24,9 +24,10 @@ #include #include -#include #include +#include +#include #include using namespace std; @@ -62,13 +63,54 @@ string Object::toString(Dialect const* _dialect) const return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}"; } -set Object::dataNames() const +set Object::qualifiedDataNames() const { - set names; - names.insert(name); - for (auto const& subObject: subIndexByName) - names.insert(subObject.first); - // The empty name is not valid - names.erase(YulString{}); - return names; + set qualifiedNames = name.empty() ? set{} : set{name}; + for (shared_ptr const& subObjectNode: subObjects) + { + yulAssert(qualifiedNames.count(subObjectNode->name) == 0, ""); + qualifiedNames.insert(subObjectNode->name); + if (auto const* subObject = dynamic_cast(subObjectNode.get())) + for (YulString const& subSubObj: subObject->qualifiedDataNames()) + if (subObject->name != subSubObj) + { + yulAssert(qualifiedNames.count(YulString{subObject->name.str() + "." + subSubObj.str()}) == 0, ""); + qualifiedNames.insert(YulString{subObject->name.str() + "." + subSubObj.str()}); + } + } + + yulAssert(qualifiedNames.count(YulString{}) == 0, ""); + qualifiedNames.erase(YulString{}); + return qualifiedNames; +} + +vector Object::pathToSubObject(YulString _qualifiedName) const +{ + yulAssert(_qualifiedName != name, ""); + yulAssert(subIndexByName.count(name) == 0, ""); + + if (boost::algorithm::starts_with(_qualifiedName.str(), name.str() + ".")) + _qualifiedName = YulString{_qualifiedName.str().substr(name.str().length() + 1)}; + yulAssert(!_qualifiedName.empty(), ""); + + vector subObjectPathComponents; + boost::algorithm::split(subObjectPathComponents, _qualifiedName.str(), boost::is_any_of(".")); + + vector path; + Object const* object = this; + for (string const& currentSubObjectName: subObjectPathComponents) + { + yulAssert(!currentSubObjectName.empty(), ""); + auto subIndexIt = object->subIndexByName.find(YulString{currentSubObjectName}); + yulAssert( + subIndexIt != object->subIndexByName.end(), + "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code." + ); + object = dynamic_cast(object->subObjects[subIndexIt->second].get()); + yulAssert(object, "Assembly object <" + _qualifiedName.str() + "> not found or does not contain code."); + yulAssert(object->subId != numeric_limits::max(), ""); + path.push_back({object->subId}); + } + + return path; } diff --git a/libyul/Object.h b/libyul/Object.h index 3602e7177..deb52eb25 100644 --- a/libyul/Object.h +++ b/libyul/Object.h @@ -44,6 +44,8 @@ struct ObjectNode virtual std::string toString(Dialect const* _dialect) const = 0; std::string toString() { return toString(nullptr); } + /// Name of the object. + /// Can be empty since .yul files can also just contain code, without explicitly placing it in an object. YulString name; }; @@ -68,8 +70,23 @@ public: std::string toString(Dialect const* _dialect) const override; /// @returns the set of names of data objects accessible from within the code of - /// this object. - std::set dataNames() const; + /// this object, including the name of object itself + std::set qualifiedDataNames() const; + + /// @returns vector of subIDs if possible to reach subobject with @a _qualifiedName, throws otherwise + /// For "B.C" should return vector of two values if success (subId of B and subId of C in B). + /// In object "A" if called for "A.B" will return only one value (subId for B) + /// will return empty vector for @a _qualifiedName that equals to object name. + /// Example: + /// A1{ B2{ C3, D3 }, E2{ F3{ G4, K4, H4{ I5 } } } } + /// pathToSubObject("A1.E2.F3.H4") == {1, 0, 2} + /// pathToSubObject("E2.F3.H4") == {1, 0, 2} + /// pathToSubObject("A1.E2") == {1} + /// The path must not lead to a @a Data object (will throw in that case). + std::vector pathToSubObject(YulString _qualifiedName) const; + + /// sub id for object if it is subobject of another object, max value if it is not subobject + size_t subId = std::numeric_limits::max(); std::shared_ptr code; std::vector> subObjects; diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 9ff0c3846..a8327c346 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -102,9 +102,9 @@ public: /// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset. virtual std::pair, SubID> createSubAssembly() = 0; /// Appends the offset of the given sub-assembly or data. - virtual void appendDataOffset(SubID _sub) = 0; + virtual void appendDataOffset(std::vector const& _subPath) = 0; /// Appends the size of the given sub-assembly or data. - virtual void appendDataSize(SubID _sub) = 0; + virtual void appendDataSize(std::vector const& _subPath) = 0; /// Appends the given data to the assembly and returns its ID. virtual SubID appendData(bytes const& _data) = 0; diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index c2204ab95..15d646755 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -147,22 +147,28 @@ pair, AbstractAssembly::SubID> EthAssemblyAdapter:: return {make_shared(*assembly), static_cast(sub.data())}; } -void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub) +void EthAssemblyAdapter::appendDataOffset(vector const& _subPath) { - auto it = m_dataHashBySubId.find(_sub); - if (it == m_dataHashBySubId.end()) - m_assembly.pushSubroutineOffset(_sub); - else + if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) + { + yulAssert(_subPath.size() == 1, ""); m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second); + return; + } + + m_assembly.pushSubroutineOffset(m_assembly.encodeSubPath(_subPath)); } -void EthAssemblyAdapter::appendDataSize(AbstractAssembly::SubID _sub) +void EthAssemblyAdapter::appendDataSize(vector const& _subPath) { - auto it = m_dataHashBySubId.find(_sub); - if (it == m_dataHashBySubId.end()) - m_assembly.pushSubroutineSize(static_cast(_sub)); - else + if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) + { + yulAssert(_subPath.size() == 1, ""); m_assembly << u256(m_assembly.data(h256(it->second)).size()); + return; + } + + m_assembly.pushSubroutineSize(m_assembly.encodeSubPath(_subPath)); } AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data) diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index 3192a9133..ee56f7fcb 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -58,8 +58,8 @@ public: void appendReturnsub(int, int) override; void appendAssemblySize() override; std::pair, SubID> createSubAssembly() override; - void appendDataOffset(SubID _sub) override; - void appendDataSize(SubID _sub) override; + void appendDataOffset(std::vector const& _subPath) override; + void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; void appendImmutable(std::string const& _identifier) override; diff --git a/libyul/backends/evm/EVMAssembly.cpp b/libyul/backends/evm/EVMAssembly.cpp index a4a1d0a27..99945383e 100644 --- a/libyul/backends/evm/EVMAssembly.cpp +++ b/libyul/backends/evm/EVMAssembly.cpp @@ -202,12 +202,12 @@ pair, AbstractAssembly::SubID> EVMAssembly::createS return {}; } -void EVMAssembly::appendDataOffset(AbstractAssembly::SubID) +void EVMAssembly::appendDataOffset(vector const&) { yulAssert(false, "Data not implemented."); } -void EVMAssembly::appendDataSize(AbstractAssembly::SubID) +void EVMAssembly::appendDataSize(vector const&) { yulAssert(false, "Data not implemented."); } diff --git a/libyul/backends/evm/EVMAssembly.h b/libyul/backends/evm/EVMAssembly.h index f67aad8b8..6e6878476 100644 --- a/libyul/backends/evm/EVMAssembly.h +++ b/libyul/backends/evm/EVMAssembly.h @@ -80,8 +80,8 @@ public: /// Append the assembled size as a constant. void appendAssemblySize() override; std::pair, SubID> createSubAssembly() override; - void appendDataOffset(SubID _sub) override; - void appendDataSize(SubID _sub) override; + void appendDataOffset(std::vector const& _subPath) override; + void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; void appendImmutable(std::string const& _identifier) override; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 4b9acb0c7..c552b6e22 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -144,7 +144,7 @@ map createBuiltins(langutil::EVMVersion _evmVe FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, - std::function + std::function const& ) { yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); @@ -154,18 +154,19 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendAssemblySize(); else { - yulAssert( - _context.subIDs.count(dataName) != 0, - "Could not find assembly object <" + dataName.str() + ">." - ); - _assembly.appendDataSize(_context.subIDs.at(dataName)); + vector subIdPath = + _context.subIDs.count(dataName) == 0 ? + _context.currentObject->pathToSubObject(dataName) : + vector{_context.subIDs.at(dataName)}; + yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">."); + _assembly.appendDataSize(subIdPath); } })); builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, {true}, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, - std::function + std::function const& ) { yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); @@ -175,11 +176,12 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendConstant(0); else { - yulAssert( - _context.subIDs.count(dataName) != 0, - "Could not find assembly object <" + dataName.str() + ">." - ); - _assembly.appendDataOffset(_context.subIDs.at(dataName)); + vector subIdPath = + _context.subIDs.count(dataName) == 0 ? + _context.currentObject->pathToSubObject(dataName) : + vector{_context.subIDs.at(dataName)}; + yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">."); + _assembly.appendDataOffset(subIdPath); } })); builtins.emplace(createFunction( diff --git a/libyul/backends/evm/EVMObjectCompiler.cpp b/libyul/backends/evm/EVMObjectCompiler.cpp index 91f23b596..5eb83a6a8 100644 --- a/libyul/backends/evm/EVMObjectCompiler.cpp +++ b/libyul/backends/evm/EVMObjectCompiler.cpp @@ -26,6 +26,7 @@ #include #include +#include using namespace solidity::yul; using namespace std; @@ -41,11 +42,12 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize) BuiltinContext context; context.currentObject = &_object; - for (auto& subNode: _object.subObjects) - if (Object* subObject = dynamic_cast(subNode.get())) + for (auto const& subNode: _object.subObjects) + if (auto* subObject = dynamic_cast(subNode.get())) { auto subAssemblyAndID = m_assembly.createSubAssembly(); context.subIDs[subObject->name] = subAssemblyAndID.second; + subObject->subId = subAssemblyAndID.second; compile(*subObject, *subAssemblyAndID.first, m_dialect, m_evm15, _optimize); } else diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 0d840ef66..733795523 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -132,12 +132,12 @@ pair, AbstractAssembly::SubID> NoOutputAssembly::cr return {}; } -void NoOutputAssembly::appendDataOffset(AbstractAssembly::SubID) +void NoOutputAssembly::appendDataOffset(std::vector const&) { appendInstruction(evmasm::Instruction::PUSH1); } -void NoOutputAssembly::appendDataSize(AbstractAssembly::SubID) +void NoOutputAssembly::appendDataSize(std::vector const&) { appendInstruction(evmasm::Instruction::PUSH1); } diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 9f853032b..b2153e42a 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -68,8 +68,8 @@ public: void appendAssemblySize() override; std::pair, SubID> createSubAssembly() override; - void appendDataOffset(SubID _sub) override; - void appendDataSize(SubID _sub) override; + void appendDataOffset(std::vector const& _subPath) override; + void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; void appendImmutable(std::string const& _identifier) override; diff --git a/libyul/backends/wasm/EVMToEwasmTranslator.cpp b/libyul/backends/wasm/EVMToEwasmTranslator.cpp index abb898828..dc180b240 100644 --- a/libyul/backends/wasm/EVMToEwasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEwasmTranslator.cpp @@ -1249,7 +1249,7 @@ Object EVMToEwasmTranslator::run(Object const& _object) ErrorList errors; ErrorReporter errorReporter(errors); - AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.dataNames()); + AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.qualifiedDataNames()); if (!analyzer.analyze(*ret.code)) { string message = "Invalid code generated after EVM to wasm translation.\n"; diff --git a/test/cmdlineTests/standard_yul_object_invalid_sub/input.json b/test/cmdlineTests/standard_yul_object_invalid_sub/input.json new file mode 100644 index 000000000..ccc142d25 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_invalid_sub/input.json @@ -0,0 +1,17 @@ +{ + "language": "Yul", + "sources": + { + "A": + { + "content": "object \"NamedObject\" { code { let x := dataoffset(\"NamedObject.\") sstore(add(x, 0), 0) } object \"OtherObject\" { code { revert(0, 0) } } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["*"], "": [ "*" ] } + } + } +} \ No newline at end of file diff --git a/test/cmdlineTests/standard_yul_object_invalid_sub/output.json b/test/cmdlineTests/standard_yul_object_invalid_sub/output.json new file mode 100644 index 000000000..5008d4916 --- /dev/null +++ b/test/cmdlineTests/standard_yul_object_invalid_sub/output.json @@ -0,0 +1,4 @@ +{"errors":[{"component":"general","formattedMessage":"A:1:40: TypeError: Unknown data object \"NamedObject.\". +object \"NamedObject\" { code { let x := dataoffset(\"NamedObject.\") sstore(add(x, 0), 0) } object \"OtherObject\" { code { revert(0, 0) } } } + ^--------^ +","message":"Unknown data object \"NamedObject.\".","severity":"error","sourceLocation":{"end":49,"file":"A","start":39},"type":"TypeError"}]} diff --git a/test/libevmasm/Assembler.cpp b/test/libevmasm/Assembler.cpp index c2140ace6..c93d7b58d 100644 --- a/test/libevmasm/Assembler.cpp +++ b/test/libevmasm/Assembler.cpp @@ -29,6 +29,7 @@ #include #include #include +#include using namespace std; using namespace solidity::langutil; @@ -254,6 +255,24 @@ BOOST_AUTO_TEST_CASE(immutable) ); } +BOOST_AUTO_TEST_CASE(subobject_encode_decode) +{ + Assembly assembly; + + shared_ptr subAsmPtr = make_shared(); + shared_ptr subSubAsmPtr = make_shared(); + + assembly.appendSubroutine(subAsmPtr); + subAsmPtr->appendSubroutine(subSubAsmPtr); + + BOOST_CHECK(assembly.encodeSubPath({0}) == 0); + BOOST_REQUIRE_THROW(assembly.encodeSubPath({1}), solidity::evmasm::AssemblyException); + BOOST_REQUIRE_THROW(assembly.decodeSubPath(1), solidity::evmasm::AssemblyException); + + vector subPath{0, 0}; + BOOST_CHECK(assembly.decodeSubPath(assembly.encodeSubPath(subPath)) == subPath); +} + BOOST_AUTO_TEST_SUITE_END() } // end namespaces diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index 57ee7ffa6..b0b07a8fb 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -90,7 +90,7 @@ pair, shared_ptr> yul::test::parse( if (!parserResult->code || errorReporter.hasErrors()) return {}; shared_ptr analysisInfo = make_shared(); - AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->dataNames()); + AsmAnalyzer analyzer(*analysisInfo, errorReporter, _dialect, {}, parserResult->qualifiedDataNames()); // TODO this should be done recursively. if (!analyzer.analyze(*parserResult->code) || errorReporter.hasErrors()) return {}; diff --git a/test/libyul/objectCompiler/subObjectAccess.yul b/test/libyul/objectCompiler/subObjectAccess.yul new file mode 100644 index 000000000..4fddc2483 --- /dev/null +++ b/test/libyul/objectCompiler/subObjectAccess.yul @@ -0,0 +1,104 @@ +object "A" { + code { + pop(dataoffset("A")) + pop(datasize("A")) + pop(dataoffset("B")) + pop(datasize("B")) + pop(dataoffset("B.C")) + pop(datasize("B.C")) + pop(dataoffset("B.E")) + pop(datasize("B.E")) + pop(dataoffset("B.C.D")) + pop(datasize("B.C.D")) + } + + data "data1" "Hello, World!" + + object "B" { + code { + pop(dataoffset("C")) + pop(datasize("C")) + pop(dataoffset("E")) + pop(datasize("E")) + pop(dataoffset("C.D")) + pop(datasize("C.D")) + } + object "C" { + code { + pop(dataoffset("D")) + pop(datasize("D")) + } + object "D" { + code { + invalid() + } + } + } + object "E" { + code { + invalid() + } + } + } +} +// ---- +// Assembly: +// /* "source":26:46 */ +// pop(0x00) +// /* "source":51:69 */ +// pop(bytecodeSize) +// /* "source":74:94 */ +// pop(dataOffset(sub_0)) +// /* "source":99:117 */ +// pop(dataSize(sub_0)) +// /* "source":122:144 */ +// pop(dataOffset(sub_0.sub_0)) +// /* "source":149:169 */ +// pop(dataSize(sub_0.sub_0)) +// /* "source":174:196 */ +// pop(dataOffset(sub_0.sub_1)) +// /* "source":201:221 */ +// pop(dataSize(sub_0.sub_1)) +// /* "source":226:250 */ +// pop(dataOffset(sub_0.sub_0.sub_0)) +// /* "source":255:277 */ +// pop(dataSize(sub_0.sub_0.sub_0)) +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// +// sub_0: assembly { +// /* "source":347:367 */ +// pop(dataOffset(sub_0)) +// /* "source":374:392 */ +// pop(dataSize(sub_0)) +// /* "source":399:419 */ +// pop(dataOffset(sub_1)) +// /* "source":426:444 */ +// pop(dataSize(sub_1)) +// /* "source":451:473 */ +// pop(dataOffset(sub_0.sub_0)) +// /* "source":480:500 */ +// pop(dataSize(sub_0.sub_0)) +// stop +// +// sub_0: assembly { +// /* "source":545:565 */ +// pop(dataOffset(sub_0)) +// /* "source":574:592 */ +// pop(dataSize(sub_0)) +// stop +// +// sub_0: assembly { +// /* "source":645:654 */ +// invalid +// } +// } +// +// sub_1: assembly { +// /* "source":717:726 */ +// invalid +// } +// } +// Bytecode: 600050604650601f50601d50603e50600850603d50600150603c50600150fe601350600850601b50600150601c50600150fe600750600150fefefefefefe600750600150fefe +// Opcodes: PUSH1 0x0 POP PUSH1 0x46 POP PUSH1 0x1F POP PUSH1 0x1D POP PUSH1 0x3E POP PUSH1 0x8 POP PUSH1 0x3D POP PUSH1 0x1 POP PUSH1 0x3C POP PUSH1 0x1 POP INVALID PUSH1 0x13 POP PUSH1 0x8 POP PUSH1 0x1B POP PUSH1 0x1 POP PUSH1 0x1C POP PUSH1 0x1 POP INVALID PUSH1 0x7 POP PUSH1 0x1 POP INVALID INVALID INVALID INVALID INVALID INVALID PUSH1 0x7 POP PUSH1 0x1 POP INVALID INVALID +// SourceMappings: 26:20:0:-:0;;51:18;;74:20;;99:18;;122:22;;149:20;;174:22;;201:20;;226:24;;255:22; From 546e9af24dd6b6bda046a3903124960deb532e17 Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Wed, 15 Jul 2020 17:26:51 +0200 Subject: [PATCH 10/11] [Sol->Yul] Supporting .runtimeCode --- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 3 +-- .../semanticTests/various/code_access_content.sol | 3 ++- .../semanticTests/various/code_access_runtime.sol | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 0489f3f68..d28ee2e9c 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1491,7 +1491,6 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) solAssert(false, "Blockhash has been removed."); else if (member == "creationCode" || member == "runtimeCode") { - solUnimplementedAssert(member != "runtimeCode", ""); TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); m_context.subObjectsCreated().insert(&contract); @@ -1503,7 +1502,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) )") ("allocationFunction", m_utils.allocationFunction()) ("size", m_context.newYulVariable()) - ("objectName", IRNames::creationObject(contract)) + ("objectName", IRNames::creationObject(contract) + (member == "runtimeCode" ? "." + IRNames::runtimeObject(contract) : "")) ("result", IRVariable(_memberAccess).commaSeparatedList()).render(); } else if (member == "name") diff --git a/test/libsolidity/semanticTests/various/code_access_content.sol b/test/libsolidity/semanticTests/various/code_access_content.sol index 2c115d6c8..c0a74fe1a 100644 --- a/test/libsolidity/semanticTests/various/code_access_content.sol +++ b/test/libsolidity/semanticTests/various/code_access_content.sol @@ -36,7 +36,8 @@ contract C { return true; } } - +// ==== +// compileViaYul: also // ---- // testRuntime() -> true // testCreation() -> true diff --git a/test/libsolidity/semanticTests/various/code_access_runtime.sol b/test/libsolidity/semanticTests/various/code_access_runtime.sol index cc1401c6f..36264f84d 100644 --- a/test/libsolidity/semanticTests/various/code_access_runtime.sol +++ b/test/libsolidity/semanticTests/various/code_access_runtime.sol @@ -10,18 +10,18 @@ contract D { } } - contract C { function test() public returns (uint256) { - D d = new D(); - bytes32 hash; - assembly { hash := extcodehash(d) } - assert(hash == keccak256(type(D).runtimeCode)); - return 42; + D d = new D(); + bytes32 hash; + assembly { hash := extcodehash(d) } + assert(hash == keccak256(type(D).runtimeCode)); + return 42; } } // ==== // EVMVersion: >=constantinople +// compileViaYul: also // ---- // test() -> 42 From 7e0a291fac680fc9af269b4f7d8f3f4c51a4caee Mon Sep 17 00:00:00 2001 From: a3d4 Date: Fri, 17 Jul 2020 00:37:16 +0200 Subject: [PATCH 11/11] Add "Only one receive function is allowed"-error to syntax tests --- .../fallback_function_twice.sol} | 0 .../duplicateFunctions/receive_function_thrice.sol | 8 ++++++++ 2 files changed, 8 insertions(+) rename test/libsolidity/syntaxTests/{nameAndTypeResolution/078_fallback_function_twice.sol => duplicateFunctions/fallback_function_twice.sol} (100%) create mode 100644 test/libsolidity/syntaxTests/duplicateFunctions/receive_function_thrice.sol diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/078_fallback_function_twice.sol b/test/libsolidity/syntaxTests/duplicateFunctions/fallback_function_twice.sol similarity index 100% rename from test/libsolidity/syntaxTests/nameAndTypeResolution/078_fallback_function_twice.sol rename to test/libsolidity/syntaxTests/duplicateFunctions/fallback_function_twice.sol diff --git a/test/libsolidity/syntaxTests/duplicateFunctions/receive_function_thrice.sol b/test/libsolidity/syntaxTests/duplicateFunctions/receive_function_thrice.sol new file mode 100644 index 000000000..2160dc055 --- /dev/null +++ b/test/libsolidity/syntaxTests/duplicateFunctions/receive_function_thrice.sol @@ -0,0 +1,8 @@ +contract C { + receive() external payable { } + receive() external payable { } + receive() external payable { } +} +// ---- +// DeclarationError 4046: (52-82): Only one receive function is allowed. +// DeclarationError 4046: (87-117): Only one receive function is allowed.