From 23fa0a538702e3329a004da3b53fecff4ebeb185 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 16 Sep 2020 16:24:11 +0200 Subject: [PATCH 01/21] Test run for CircleCI windows builds. --- .circleci/config.yml | 30 ++++++++++++++++++++++++++++++ .circleci/soltest.ps1 | 7 +++++++ scripts/install_evmone.ps1 | 7 +++++++ 3 files changed, 44 insertions(+) create mode 100755 .circleci/soltest.ps1 create mode 100644 scripts/install_evmone.ps1 diff --git a/.circleci/config.yml b/.circleci/config.yml index 55204b8e5..1408babb3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -250,6 +250,16 @@ defaults: requires: - b_ubu_ossfuzz + - workflow_win: &workflow_win + <<: *workflow_trigger_on_tags + requires: + - b_win + + - workflow_win_release: &workflow_win_release + <<: *workflow_trigger_on_tags + requires: + - b_win_release + # -------------------------------------------------------------------------- # Notification Templates - gitter_notify_failure: &gitter_notify_failure @@ -911,6 +921,24 @@ jobs: environment: FORCE_RELEASE: ON + t_win: &t_win + executor: + name: win/default + shell: powershell.exe + steps: + - checkout + - attach_workspace: + at: build + - run: + name: "Install evmone" + command: scripts/install_evmone.ps1 + - run: + name: "Run soltest" + command: .circleci/soltest.ps1 + - store_artifacts: *artifacts_test_results + + t_win_release: + <<: *t_win workflows: version: 2 @@ -966,6 +994,8 @@ workflows: # Windows build and tests - b_win: *workflow_trigger_on_tags - b_win_release: *workflow_trigger_on_tags + - t_win: *workflow_win + - t_win_release: *workflow_win_release nightly: diff --git a/.circleci/soltest.ps1 b/.circleci/soltest.ps1 new file mode 100755 index 000000000..ab5cb4ba7 --- /dev/null +++ b/.circleci/soltest.ps1 @@ -0,0 +1,7 @@ +cd "$PSScriptRoot\.." + +.\build\solc\Release\solc.exe --version + +mkdir test_results +.\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result.xml -- --no-smt +.\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result_opt.xml -- --optimize --no-smt diff --git a/scripts/install_evmone.ps1 b/scripts/install_evmone.ps1 new file mode 100644 index 000000000..3b728d923 --- /dev/null +++ b/scripts/install_evmone.ps1 @@ -0,0 +1,7 @@ +# Needed for Invoke-WebRequest to work via CI. +$progressPreference = "silentlyContinue" + +Invoke-WebRequest -URI "https://github.com/ethereum/evmone/releases/download/v0.5.0/evmone-0.5.0-windows-amd64.zip" -OutFile "evmone.zip" +tar -xf evmone.zip "bin/evmone.dll" +mkdir deps +mv bin/evmone.dll deps From a1de862135ab1706ca6065162e1cdbdfd5627820 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 17 Sep 2020 13:08:14 +0200 Subject: [PATCH 02/21] Set ErrorActionPreference on all powershell scripts and add explicit error checks. --- .circleci/build_win.ps1 | 5 +++++ .circleci/soltest.ps1 | 5 +++++ scripts/install_deps.ps1 | 3 +++ scripts/install_evmone.ps1 | 2 ++ 4 files changed, 15 insertions(+) diff --git a/.circleci/build_win.ps1 b/.circleci/build_win.ps1 index 4df23de42..66f9be33a 100644 --- a/.circleci/build_win.ps1 +++ b/.circleci/build_win.ps1 @@ -1,3 +1,5 @@ +$ErrorActionPreference = "Stop" + cd "$PSScriptRoot\.." if ("$Env:FORCE_RELEASE") { @@ -9,5 +11,8 @@ mkdir build cd build $boost_dir=(Resolve-Path $PSScriptRoot\..\deps\boost\lib\cmake\Boost-*) ..\deps\cmake\bin\cmake -G "Visual Studio 16 2019" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_INSTALL_PREFIX="$PSScriptRoot\..\upload" .. +if ( -not $? ) { throw "CMake configure failed." } msbuild solidity.sln /p:Configuration=Release /m:5 /v:minimal +if ( -not $? ) { throw "Build failed." } ..\deps\cmake\bin\cmake --build . -j 5 --target install --config Release +if ( -not $? ) { throw "Install target failed." } diff --git a/.circleci/soltest.ps1 b/.circleci/soltest.ps1 index ab5cb4ba7..6b67adb13 100755 --- a/.circleci/soltest.ps1 +++ b/.circleci/soltest.ps1 @@ -1,7 +1,12 @@ +$ErrorActionPreference = "Stop" + cd "$PSScriptRoot\.." .\build\solc\Release\solc.exe --version +if ( -not $? ) { throw "Cannot execute solc --version." } mkdir test_results .\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result.xml -- --no-smt +if ( -not $? ) { throw "Unoptimized soltest run failed." } .\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result_opt.xml -- --optimize --no-smt +if ( -not $? ) { throw "Optimized soltest run failed." } \ No newline at end of file diff --git a/scripts/install_deps.ps1 b/scripts/install_deps.ps1 index bcf10eed9..c1301c74e 100644 --- a/scripts/install_deps.ps1 +++ b/scripts/install_deps.ps1 @@ -1,3 +1,5 @@ +$ErrorActionPreference = "Stop" + # Needed for Invoke-WebRequest to work via CI. $progressPreference = "silentlyContinue" @@ -12,4 +14,5 @@ tar -xf boost.zip cd boost_1_74_0 .\bootstrap.bat .\b2 -j4 -d0 link=static runtime-link=static variant=release threading=multi address-model=64 --with-filesystem --with-system --with-program_options --with-test --prefix="$PSScriptRoot\..\deps\boost" install +if ( -not $? ) { throw "Error building boost." } cd .. diff --git a/scripts/install_evmone.ps1 b/scripts/install_evmone.ps1 index 3b728d923..ba69dab34 100644 --- a/scripts/install_evmone.ps1 +++ b/scripts/install_evmone.ps1 @@ -1,3 +1,5 @@ +$ErrorActionPreference = "Stop" + # Needed for Invoke-WebRequest to work via CI. $progressPreference = "silentlyContinue" From e716c9e2b9adbba48abf655ffbcc7035371486dd Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 17 Sep 2020 14:46:24 +0200 Subject: [PATCH 03/21] Run solc.exe after build run just to make sure no error was missed. --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1408babb3..daefe4472 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -913,6 +913,9 @@ jobs: - run: name: "Building solidity" command: .circleci/build_win.ps1 + - run: + name: "Run solc.exe to make sure build was successful." + command: .\build\solc\Release\solc.exe --version - store_artifacts: *artifact_solc_windows - persist_to_workspace: *artifacts_build_dir From 684fff34a9c1a714fafe7171292c6b08c4faa51d Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 28 Jul 2020 20:26:22 +0200 Subject: [PATCH 04/21] Replace exp by shl. --- Changelog.md | 2 +- libevmasm/RuleList.h | 17 +++++++++++++- .../exp_simplifications.yul | 22 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 test/libyul/yulOptimizerTests/expressionSimplifier/exp_simplifications.yul diff --git a/Changelog.md b/Changelog.md index 33f2c2ef5..ba393c0ee 100644 --- a/Changelog.md +++ b/Changelog.md @@ -14,7 +14,7 @@ Compiler Features: * Yul Optimizer: Inline into functions further down in the call graph first. * Yul Optimizer: Try to simplify function names. * Yul IR Generator: Report source locations related to unimplemented features. - + * Optimizer: Optimize ``exp`` when base is 0, 1 or 2. Bugfixes: * Code generator: Fix internal error on stripping dynamic types from return parameters on EVM versions without ``RETURNDATACOPY``. diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 4adeb34c9..883de4c9f 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -615,12 +615,13 @@ std::vector> evmRuleList( Pattern, Pattern, Pattern, - Pattern, + Pattern X, Pattern, Pattern ) { using Builtins = typename Pattern::Builtins; + using Word = typename Pattern::Word; std::vector> rules; if (_evmVersion.hasSelfBalance()) @@ -629,6 +630,20 @@ std::vector> evmRuleList( []() -> Pattern { return Instruction::SELFBALANCE; } }); + rules.emplace_back( + Builtins::EXP(0, X), + [=]() -> Pattern { return Builtins::ISZERO(X); } + ); + rules.emplace_back( + Builtins::EXP(1, X), + [=]() -> Pattern { return Word(1); } + ); + if (_evmVersion.hasBitwiseShifting()) + rules.emplace_back( + Builtins::EXP(2, X), + [=]() -> Pattern { return Builtins::SHL(X, 1); } + ); + return rules; } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/exp_simplifications.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/exp_simplifications.yul new file mode 100644 index 000000000..879b05883 --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/exp_simplifications.yul @@ -0,0 +1,22 @@ +{ + let t := calldataload(0) + sstore(0, exp(0, t)) + sstore(1, exp(1, t)) + sstore(2, exp(2, t)) + // The following should not be simplified + sstore(3, exp(8, t)) +} +// ==== +// EVMVersion: >=constantinople +// ---- +// step: expressionSimplifier +// +// { +// let _1 := 0 +// let t := calldataload(_1) +// sstore(_1, iszero(t)) +// sstore(1, 1) +// let _8 := 2 +// sstore(_8, shl(t, 1)) +// sstore(3, exp(8, t)) +// } From 89e696ca1b69b7db99e8e06bf96fc337cfee48cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Sep 2020 17:06:08 +0200 Subject: [PATCH 05/21] Allow using zero in patterns passed to isoltest --test --- test/tools/IsolTestOptions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tools/IsolTestOptions.cpp b/test/tools/IsolTestOptions.cpp index 84e2babe9..943832677 100644 --- a/test/tools/IsolTestOptions.cpp +++ b/test/tools/IsolTestOptions.cpp @@ -77,7 +77,7 @@ bool IsolTestOptions::parse(int _argc, char const* const* _argv) void IsolTestOptions::validate() const { - static std::string filterString{"[a-zA-Z1-9_/*]*"}; + static std::string filterString{"[a-zA-Z0-9_/*]*"}; static std::regex filterExpression{filterString}; assertThrow( regex_match(testFilter, filterExpression), From 1a4cc4e64d028fd5edcd87c77a0649fe983452da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Sep 2020 15:02:29 +0200 Subject: [PATCH 06/21] Fix type check for nested arrays in abi.encode/decode functions in ABIEncoderV1 - Without this fix, nested arrays are not detected as unsupported and compiler fails on an UnimplementedError. - Now it's consistent with how structs are handled in ABIEncoderV1. --- Changelog.md | 1 + libsolidity/ast/Types.cpp | 10 ++++++++-- .../abi_encodePacked_nested_dynamic_array.sol | 7 +++++++ .../abi_encodePacked_nested_dynamic_array_v2.sol | 9 +++++++++ .../abi_encode_nested_dynamic_array.sol | 7 +++++++ .../abi_encode_nested_dynamic_array_v2.sol | 9 +++++++++ .../abidecode/abi_decode_nested_dynamic_array.sol | 7 +++++++ .../abi_decode_nested_dynamic_array_v2.sol | 9 +++++++++ .../abidecode/abi_decode_struct.sol | 11 +++++++++++ .../abidecode/abi_decode_struct_v2.sol | 13 +++++++++++++ 10 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array.sol create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array_v2.sol create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array.sol create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array_v2.sol create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array.sol create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array_v2.sol create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct.sol create mode 100644 test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct_v2.sol diff --git a/Changelog.md b/Changelog.md index 33f2c2ef5..7716edd93 100644 --- a/Changelog.md +++ b/Changelog.md @@ -18,6 +18,7 @@ Compiler Features: Bugfixes: * Code generator: Fix internal error on stripping dynamic types from return parameters on EVM versions without ``RETURNDATACOPY``. + * Type Checker: Add missing check against nested dynamic arrays in ABI encoding functions when ABIEncoderV2 is disabled. * Type Checker: Disallow ``virtual`` for modifiers in libraries. * Type Checker: Correct the warning for homonymous, but not shadowing declarations. * ViewPureChecker: Prevent visibility check on constructors. diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index b0418dee5..070bcb483 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -404,10 +404,16 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c return encodingType; TypePointer baseType = encodingType; while (auto const* arrayType = dynamic_cast(baseType)) + { baseType = arrayType->baseType(); - if (dynamic_cast(baseType)) - if (!_encoderV2) + + auto const* baseArrayType = dynamic_cast(baseType); + if (!_encoderV2 && baseArrayType && baseArrayType->isDynamicallySized()) return nullptr; + } + if (!_encoderV2 && dynamic_cast(baseType)) + return nullptr; + return encodingType; } diff --git a/test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array.sol b/test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array.sol new file mode 100644 index 000000000..3645cf592 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array.sol @@ -0,0 +1,7 @@ +contract C { + function f() public pure { + abi.encodePacked([new uint[](5), new uint[](7)]); + } +} +// ---- +// TypeError 9578: (69-99): Type not supported in packed mode. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array_v2.sol b/test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array_v2.sol new file mode 100644 index 000000000..54a81e839 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abi_encodePacked_nested_dynamic_array_v2.sol @@ -0,0 +1,9 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f() public pure { + abi.encodePacked([new uint[](5), new uint[](7)]); + } +} +// ---- +// TypeError 9578: (104-134): Type not supported in packed mode. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array.sol b/test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array.sol new file mode 100644 index 000000000..e4e5a85dc --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array.sol @@ -0,0 +1,7 @@ +contract C { + function test() public pure { + abi.encode([new uint[](5), new uint[](7)]); + } +} +// ---- +// TypeError 2056: (66-96): This type cannot be encoded. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array_v2.sol b/test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array_v2.sol new file mode 100644 index 000000000..911c7b6fd --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abi_encode_nested_dynamic_array_v2.sol @@ -0,0 +1,9 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f() public pure { + abi.encode([new uint[](5), new uint[](7)]); + } +} +// ---- +// Warning 6133: (87-129): Statement has no effect. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array.sol new file mode 100644 index 000000000..d1820aab1 --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array.sol @@ -0,0 +1,7 @@ +contract C { + function f() public pure { + abi.decode("1234", (uint[][3])); + } +} +// ---- +// TypeError 9611: (72-81): Decoding type uint256[] memory[3] memory not supported. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array_v2.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array_v2.sol new file mode 100644 index 000000000..55f3b169b --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_nested_dynamic_array_v2.sol @@ -0,0 +1,9 @@ +pragma experimental ABIEncoderV2; + +contract C { + function f() public pure { + abi.decode("1234", (uint[][3])); + } +} +// ---- +// Warning 6133: (87-118): Statement has no effect. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct.sol new file mode 100644 index 000000000..b8b49ccdb --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct.sol @@ -0,0 +1,11 @@ +struct S { + uint x; +} + +contract C { + function f() public pure { + abi.decode("1234", (S)); + } +} +// ---- +// TypeError 9611: (98-99): Decoding type struct S memory not supported. diff --git a/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct_v2.sol b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct_v2.sol new file mode 100644 index 000000000..80c9c527a --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/abidecode/abi_decode_struct_v2.sol @@ -0,0 +1,13 @@ +pragma experimental ABIEncoderV2; + +struct S { + uint x; +} + +contract C { + function f() public pure { + abi.decode("1234", (S)); + } +} +// ---- +// Warning 6133: (113-136): Statement has no effect. From dc62d763eb31dedcd257db833b2d247cad91cd06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Sep 2020 17:12:54 +0200 Subject: [PATCH 07/21] Move syntax tests for named arguments from namedAndTypeResolution/ to functionCalls/ --- .../named_arguments_duplicate_parameter.sol} | 0 .../named_arguments_empty.sol} | 0 .../named_arguments_invalid_name.sol} | 0 .../named_arguments_wrong_count.sol} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename test/libsolidity/syntaxTests/{nameAndTypeResolution/102_duplicate_parameter_names_in_named_args.sol => functionCalls/named_arguments_duplicate_parameter.sol} (100%) rename test/libsolidity/syntaxTests/{nameAndTypeResolution/101_empty_in_named_args.sol => functionCalls/named_arguments_empty.sol} (100%) rename test/libsolidity/syntaxTests/{nameAndTypeResolution/103_invalid_parameter_names_in_named_args.sol => functionCalls/named_arguments_invalid_name.sol} (100%) rename test/libsolidity/syntaxTests/{nameAndTypeResolution/100_error_count_in_named_args.sol => functionCalls/named_arguments_wrong_count.sol} (100%) diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/102_duplicate_parameter_names_in_named_args.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_duplicate_parameter.sol similarity index 100% rename from test/libsolidity/syntaxTests/nameAndTypeResolution/102_duplicate_parameter_names_in_named_args.sol rename to test/libsolidity/syntaxTests/functionCalls/named_arguments_duplicate_parameter.sol diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/101_empty_in_named_args.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_empty.sol similarity index 100% rename from test/libsolidity/syntaxTests/nameAndTypeResolution/101_empty_in_named_args.sol rename to test/libsolidity/syntaxTests/functionCalls/named_arguments_empty.sol diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/103_invalid_parameter_names_in_named_args.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol similarity index 100% rename from test/libsolidity/syntaxTests/nameAndTypeResolution/103_invalid_parameter_names_in_named_args.sol rename to test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/100_error_count_in_named_args.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_wrong_count.sol similarity index 100% rename from test/libsolidity/syntaxTests/nameAndTypeResolution/100_error_count_in_named_args.sol rename to test/libsolidity/syntaxTests/functionCalls/named_arguments_wrong_count.sol From 4045f41c8df10564b18249ad256e74d18fe35f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Sep 2020 17:20:43 +0200 Subject: [PATCH 08/21] Make the test for invalid named arguments more robust - Add more parameters and use different types. - Now it's clear that the name used in the error message is wrong. --- .../functionCalls/named_arguments_invalid_name.sol | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol index b3b8c727c..0784a4b9a 100644 --- a/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol @@ -1,11 +1,13 @@ contract test { - function a(uint a, uint b) public returns (uint r) { - r = a + b; + function f(uint a, bool b, bytes memory c, uint d, bool e) public returns (uint r) { + if (b && !e) + r = a + d; + else + r = c.length; } - function b() public returns (uint r) { - r = a({a: 1, c: 2}); + function g() public returns (uint r) { + r = f({c: "abc", x: 1, e: 2, a: 11, b: 12}); } } // ---- -// Warning 2519: (31-37): This declaration shadows an existing declaration. -// TypeError 4974: (153-168): Named argument "c" does not match function declaration. +// TypeError 4974: (249-288): Named argument "a" does not match function declaration. From aae640dd3a4525bcf2e6d76b3d9917589b0b1df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 17 Sep 2020 17:21:47 +0200 Subject: [PATCH 09/21] Fix wrong name used in error message when reporting invalid named argument --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 11 +++++------ .../functionCalls/named_arguments_invalid_name.sol | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Changelog.md b/Changelog.md index 33f2c2ef5..407652bde 100644 --- a/Changelog.md +++ b/Changelog.md @@ -19,6 +19,7 @@ Compiler Features: Bugfixes: * Code generator: Fix internal error on stripping dynamic types from return parameters on EVM versions without ``RETURNDATACOPY``. * Type Checker: Disallow ``virtual`` for modifiers in libraries. + * Type Checker: Correct the error message for invalid named parameter in a call to refer to the right argument. * Type Checker: Correct the warning for homonymous, but not shadowing declarations. * ViewPureChecker: Prevent visibility check on constructors. * Type system: Fix internal error on implicit conversion of contract instance to the type of its ``super``. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2d2096c55..e280fd85b 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2091,18 +2091,17 @@ void TypeChecker::typeCheckFunctionGeneralChecks( { bool not_all_mapped = false; - for (size_t i = 0; i < paramArgMap.size(); i++) + for (size_t i = 0; i < argumentNames.size(); i++) { size_t j; - for (j = 0; j < argumentNames.size(); j++) - if (parameterNames[i] == *argumentNames[j]) + for (j = 0; j < parameterNames.size(); j++) + if (parameterNames[j] == *argumentNames[i]) break; - if (j < argumentNames.size()) - paramArgMap[i] = arguments[j].get(); + if (j < parameterNames.size()) + paramArgMap[j] = arguments[i].get(); else { - paramArgMap[i] = nullptr; not_all_mapped = true; m_errorReporter.typeError( 4974_error, diff --git a/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol b/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol index 0784a4b9a..0c19ff416 100644 --- a/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol +++ b/test/libsolidity/syntaxTests/functionCalls/named_arguments_invalid_name.sol @@ -10,4 +10,4 @@ contract test { } } // ---- -// TypeError 4974: (249-288): Named argument "a" does not match function declaration. +// TypeError 4974: (249-288): Named argument "x" does not match function declaration. From f4b42d1c7276980d555c1be8ad1984ed413a94b0 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 2 Jul 2020 12:48:20 +0200 Subject: [PATCH 10/21] Add stack limit evader. --- docs/yul.rst | 14 + libsolidity/codegen/ir/IRGenerationContext.h | 6 + libsolidity/codegen/ir/IRGenerator.cpp | 21 +- libsolidity/codegen/ir/IRGenerator.h | 4 +- .../codegen/ir/IRGeneratorForStatements.cpp | 1 + libyul/CMakeLists.txt | 6 + libyul/CompilabilityChecker.cpp | 13 +- libyul/CompilabilityChecker.h | 16 +- libyul/backends/evm/EVMDialect.cpp | 17 + libyul/backends/wasm/EVMToEwasmTranslator.cpp | 3 + libyul/optimiser/FunctionCallFinder.cpp | 39 ++ libyul/optimiser/FunctionCallFinder.h | 47 +++ libyul/optimiser/StackCompressor.cpp | 2 +- libyul/optimiser/StackLimitEvader.cpp | 142 ++++++++ libyul/optimiser/StackLimitEvader.h | 66 ++++ libyul/optimiser/StackToMemoryMover.cpp | 210 +++++++++++ libyul/optimiser/StackToMemoryMover.h | 100 ++++++ libyul/optimiser/Suite.cpp | 8 + .../output | 8 +- .../ir_compiler_subobjects/output | 33 +- .../args | 1 + .../input.sol | 7 + .../output | 40 +++ .../args | 1 + .../input.sol | 8 + .../output | 38 ++ test/cmdlineTests/name_simplifier/output | 4 +- .../cmdlineTests/optimizer_array_sload/output | 4 +- .../output.json | 4 +- .../standard_ir_requested/output.json | 4 +- test/cmdlineTests/yul_optimizer_steps/output | 4 +- .../yul_string_format_ascii/output.json | 4 +- .../output.json | 4 +- .../output.json | 4 +- .../yul_string_format_ascii_long/output.json | 4 +- .../yul_string_format_hex/output.json | 4 +- .../viaYul/stackLimitEvasion/inlined.sol | 53 +++ .../viaYul/stackLimitEvasion/non_inlined.sol | 57 +++ test/libyul/CompilabilityChecker.cpp | 2 +- test/libyul/YulOptimizerTest.cpp | 55 +++ .../ewasmTranslationTests/memoryguard.yul | 12 + .../fakeStackLimitEvader/connected.yul | 62 ++++ .../fakeStackLimitEvader/function_arg.yul | 20 ++ .../fakeStackLimitEvader/outer_block.yul | 13 + .../fakeStackLimitEvader/stub.yul | 88 +++++ .../stackLimitEvader/cycle.yul | 95 +++++ .../stackLimitEvader/cycle_after.yul | 95 +++++ .../stackLimitEvader/cycle_after_2.yul | 100 ++++++ .../stackLimitEvader/cycle_before.yul | 103 ++++++ .../stackLimitEvader/cycle_before_2.yul | 108 ++++++ .../stackLimitEvader/cycle_before_after.yul | 110 ++++++ .../stackLimitEvader/function_arg.yul | 88 +++++ .../stackLimitEvader/stub.yul | 98 +++++ .../stackLimitEvader/tree.yul | 339 ++++++++++++++++++ 54 files changed, 2323 insertions(+), 66 deletions(-) create mode 100644 libyul/optimiser/FunctionCallFinder.cpp create mode 100644 libyul/optimiser/FunctionCallFinder.h create mode 100644 libyul/optimiser/StackLimitEvader.cpp create mode 100644 libyul/optimiser/StackLimitEvader.h create mode 100644 libyul/optimiser/StackToMemoryMover.cpp create mode 100644 libyul/optimiser/StackToMemoryMover.h create mode 100644 test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/args create mode 100644 test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/input.sol create mode 100644 test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/output create mode 100644 test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/args create mode 100644 test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/input.sol create mode 100644 test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/output create mode 100644 test/libsolidity/semanticTests/viaYul/stackLimitEvasion/inlined.sol create mode 100644 test/libsolidity/semanticTests/viaYul/stackLimitEvasion/non_inlined.sol create mode 100644 test/libyul/ewasmTranslationTests/memoryguard.yul create mode 100644 test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul create mode 100644 test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul create mode 100644 test/libyul/yulOptimizerTests/fakeStackLimitEvader/outer_block.yul create mode 100644 test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul create mode 100644 test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul diff --git a/docs/yul.rst b/docs/yul.rst index 281cb8d63..0e30a9082 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -952,6 +952,20 @@ option. See :ref:`Using the Commandline Compiler ` for details about the Solidity linker. +memoryguard +^^^^^^^^^^^ + +This function is available in the EVM dialect with objects. The caller of +``let ptr := memoryguard(size)`` promises that they only use memory in either +the range ``[0, size)`` or the unbounded range above ``ptr``. The Yul optimizer +promises to only use the memory range ``[size, ptr)`` for its purposes. +If the optimizer does not need to reserve any memory, it holds that ``ptr := size``. + +``memoryguard`` can be called multiple times, but needs to have the same literal as argument +within one Yul subobject. If at least one ``memoryguard`` call is found in a subobject, +the Yul optimiser will try to perform experimental steps like the stack limit evader, +which attempts to move stack variables that would otherwise be unreachable +to memory. .. _yul-object: diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index a833fdfa5..8f102dfda 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -142,6 +142,9 @@ public: std::set& subObjectsCreated() { return m_subObjects; } + bool inlineAssemblySeen() const { return m_inlineAssemblySeen; } + void setInlineAssemblySeen() { m_inlineAssemblySeen = true; } + private: langutil::EVMVersion m_evmVersion; RevertStrings m_revertStrings; @@ -159,6 +162,9 @@ private: MultiUseYulFunctionCollector m_functions; size_t m_varCounter = 0; + /// Flag indicating whether any inline assembly block was seen. + bool m_inlineAssemblySeen = false; + /// Function definitions queued for code generation. They're the Solidity functions whose calls /// were discovered by the IR generator during AST traversal. /// Note that the queue gets filled in a lazy way - new definitions can be added while the diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 151b8c9fd..54098cc8f 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -92,7 +92,7 @@ string IRGenerator::generate( Whiskers t(R"( object "" { code { - + let := () @@ -103,7 +103,7 @@ string IRGenerator::generate( } object "" { code { - + } @@ -118,7 +118,6 @@ string IRGenerator::generate( m_context.registerImmutableVariable(*var); t("CreationObject", IRNames::creationObject(_contract)); - t("memoryInit", memoryInit()); t("notLibrary", !_contract.isLibrary()); FunctionDefinition const* constructor = _contract.constructor(); @@ -143,6 +142,7 @@ string IRGenerator::generate( InternalDispatchMap internalDispatchMap = generateInternalDispatchFunctions(); t("functions", m_context.functionCollector().requestedFunctions()); t("subObjects", subObjectSources(m_context.subObjectsCreated())); + t("memoryInitCreation", memoryInit(!m_context.inlineAssemblySeen())); resetContext(_contract); @@ -158,6 +158,7 @@ string IRGenerator::generate( generateInternalDispatchFunctions(); t("runtimeFunctions", m_context.functionCollector().requestedFunctions()); t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated())); + t("memoryInitRuntime", memoryInit(!m_context.inlineAssemblySeen())); return t.render(); } @@ -651,16 +652,22 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) return t.render(); } -string IRGenerator::memoryInit() +string IRGenerator::memoryInit(bool _useMemoryGuard) { // This function should be called at the beginning of the EVM call frame // and thus can assume all memory to be zero, including the contents of // the "zero memory area" (the position CompilerUtils::zeroPointer points to). return - Whiskers{"mstore(, )"} + Whiskers{ + _useMemoryGuard ? + "mstore(, memoryguard())" : + "mstore(, )" + } ("memPtr", to_string(CompilerUtils::freeMemoryPointer)) - ("freeMemoryStart", to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory())) - .render(); + ( + "freeMemoryStart", + to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory()) + ).render(); } void IRGenerator::resetContext(ContractDefinition const& _contract) diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index e57b8535e..94433912d 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -100,7 +100,9 @@ private: std::string dispatchRoutine(ContractDefinition const& _contract); - std::string memoryInit(); + /// @a _useMemoryGuard If true, use a memory guard, allowing the optimiser + /// to perform memory optimizations. + std::string memoryInit(bool _useMemoryGuard); void resetContext(ContractDefinition const& _contract); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 176f876bd..548351c60 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1858,6 +1858,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) { setLocation(_inlineAsm); + m_context.setInlineAssemblySeen(); CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences}; yul::Statement modified = bodyCopier(_inlineAsm.operations()); diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index bbc52d8f2..a6d41bb07 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -105,6 +105,8 @@ add_library(yul optimiser/ForLoopInitRewriter.h optimiser/FullInliner.cpp optimiser/FullInliner.h + optimiser/FunctionCallFinder.cpp + optimiser/FunctionCallFinder.h optimiser/FunctionGrouper.cpp optimiser/FunctionGrouper.h optimiser/FunctionHoister.cpp @@ -150,6 +152,10 @@ add_library(yul optimiser/SimplificationRules.h optimiser/StackCompressor.cpp optimiser/StackCompressor.h + optimiser/StackLimitEvader.cpp + optimiser/StackLimitEvader.h + optimiser/StackToMemoryMover.cpp + optimiser/StackToMemoryMover.h optimiser/StructuralSimplifier.cpp optimiser/StructuralSimplifier.h optimiser/Substitution.cpp diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 2a99e3c79..4b1a99a74 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -33,7 +33,7 @@ using namespace solidity; using namespace solidity::yul; using namespace solidity::util; -map CompilabilityChecker::run( +CompilabilityChecker::CompilabilityChecker( Dialect const& _dialect, Object const& _object, bool _optimizeStackAllocation @@ -63,12 +63,11 @@ map CompilabilityChecker::run( ); transform(*_object.code); - std::map functions; for (StackTooDeepError const& error: transform.stackErrors()) - functions[error.functionName] = max(error.depth, functions[error.functionName]); - - return functions; + { + unreachableVariables[error.functionName].emplace(error.variable); + int& deficit = stackDeficit[error.functionName]; + deficit = std::max(error.depth, deficit); + } } - else - return {}; } diff --git a/libyul/CompilabilityChecker.h b/libyul/CompilabilityChecker.h index 1267640bf..ba9191e28 100644 --- a/libyul/CompilabilityChecker.h +++ b/libyul/CompilabilityChecker.h @@ -33,22 +33,20 @@ namespace solidity::yul /** * Component that checks whether all variables are reachable on the stack and - * returns a mapping from function name to the largest stack difference found - * in that function (no entry present if that function is compilable). + * provides a mapping from function name to the largest stack difference found + * in that function (no entry present if that function is compilable), as well + * as the set of unreachable variables for each function. * * This only works properly if the outermost block is compilable and * functions are not nested. Otherwise, it might miss reporting some functions. * * Only checks the code of the object itself, does not descend into sub-objects. */ -class CompilabilityChecker +struct CompilabilityChecker { -public: - static std::map run( - Dialect const& _dialect, - Object const& _object, - bool _optimizeStackAllocation - ); + CompilabilityChecker(Dialect const& _dialect, Object const& _object, bool _optimizeStackAllocation); + std::map> unreachableVariables; + std::map stackDeficit; }; } diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index dc648248b..2c3b6be6d 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -142,6 +142,23 @@ map createBuiltins(langutil::EVMVersion _evmVe Expression const& arg = _call.arguments.front(); _assembly.appendLinkerSymbol(std::get(arg).value.str()); })); + + builtins.emplace(createFunction( + "memoryguard", + 1, + 1, + SideEffects{}, + {LiteralKind::Number}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext&, + function _visitExpression + ) { + visitArguments(_assembly, _call, _visitExpression); + }) + ); + builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {LiteralKind::String}, []( FunctionCall const& _call, AbstractAssembly& _assembly, diff --git a/libyul/backends/wasm/EVMToEwasmTranslator.cpp b/libyul/backends/wasm/EVMToEwasmTranslator.cpp index 4495ea13b..4f9b0ed08 100644 --- a/libyul/backends/wasm/EVMToEwasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEwasmTranslator.cpp @@ -1211,6 +1211,9 @@ function revert(x1, x2, x3, x4, y1, y2, y3, y4) { function invalid() { unreachable() } +function memoryguard(x:i64) -> y1, y2, y3, y4 { + y4 := x +} } )"}; diff --git a/libyul/optimiser/FunctionCallFinder.cpp b/libyul/optimiser/FunctionCallFinder.cpp new file mode 100644 index 000000000..d5a6afcc9 --- /dev/null +++ b/libyul/optimiser/FunctionCallFinder.cpp @@ -0,0 +1,39 @@ +/* + 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 . +*/ + +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::yul; + +vector FunctionCallFinder::run(Block& _block, YulString _functionName) +{ + FunctionCallFinder functionCallFinder(_functionName); + functionCallFinder(_block); + return functionCallFinder.m_calls; +} + +FunctionCallFinder::FunctionCallFinder(YulString _functionName): m_functionName(_functionName) {} + +void FunctionCallFinder::operator()(FunctionCall& _functionCall) +{ + ASTModifier::operator()(_functionCall); + if (_functionCall.functionName.name == m_functionName) + m_calls.emplace_back(&_functionCall); +} \ No newline at end of file diff --git a/libyul/optimiser/FunctionCallFinder.h b/libyul/optimiser/FunctionCallFinder.h new file mode 100644 index 000000000..365f86688 --- /dev/null +++ b/libyul/optimiser/FunctionCallFinder.h @@ -0,0 +1,47 @@ +/* + 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 . +*/ +/** + * AST walker that finds all calls to a function of a given name. + */ + +#pragma once + +#include + +#include + +namespace solidity::yul +{ + +/** + * AST walker that finds all calls to a function of a given name. + * + * Prerequisite: Disambiguator + */ +class FunctionCallFinder: ASTModifier +{ +public: + static std::vector run(Block& _block, YulString _functionName); +private: + FunctionCallFinder(YulString _functionName); + using ASTModifier::operator(); + void operator()(FunctionCall& _functionCall) override; + YulString m_functionName; + std::vector m_calls; +}; + +} diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index d011c64b8..98850f03e 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -168,7 +168,7 @@ bool StackCompressor::run( bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code); for (size_t iterations = 0; iterations < _maxIterations; iterations++) { - map stackSurplus = CompilabilityChecker::run(_dialect, _object, _optimizeStackAllocation); + map stackSurplus = CompilabilityChecker(_dialect, _object, _optimizeStackAllocation).stackDeficit; if (stackSurplus.empty()) return true; diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp new file mode 100644 index 000000000..1849a9074 --- /dev/null +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -0,0 +1,142 @@ +/* + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::yul; + +namespace +{ +// Walks the call graph using a Depth-First-Search assigning memory offsets to variables. +// - The leaves of the call graph will get the lowest offsets, increasing towards the root. +// - ``nextAvailableSlot`` maps a function to the next available slot that can be used by another +// function that calls it. +// - For each function starting from the root of the call graph: +// - Visit all children that are not already visited. +// - Determine the maximum value ``n`` of the values of ``nextAvailableSlot`` among the children. +// - If the function itself contains variables that need memory slots, but is contained in a cycle, +// abort the process as failure. +// - If not, assign each variable its slot starting from ``n`` (incrementing it). +// - Assign ``n`` to ``nextAvailableSlot`` of the function. +struct MemoryOffsetAllocator +{ + uint64_t run(YulString _function = YulString{}) + { + if (nextAvailableSlot.count(_function)) + return nextAvailableSlot[_function]; + + // Assign to zero early to guard against recursive calls. + nextAvailableSlot[_function] = 0; + + uint64_t nextSlot = 0; + if (callGraph.count(_function)) + for (YulString child: callGraph.at(_function)) + nextSlot = std::max(run(child), nextSlot); + + if (unreachableVariables.count(_function)) + { + yulAssert(!slotAllocations.count(_function), ""); + auto& assignedSlots = slotAllocations[_function]; + for (YulString variable: unreachableVariables.at(_function)) + if (variable.empty()) + { + // TODO: Too many function arguments or return parameters. + } + else + assignedSlots[variable] = nextSlot++; + } + + return nextAvailableSlot[_function] = nextSlot; + } + + map> const& unreachableVariables; + map> const& callGraph; + + map> slotAllocations{}; + map nextAvailableSlot{}; +}; + +u256 literalArgumentValue(FunctionCall const& _call) +{ + yulAssert(_call.arguments.size() == 1, ""); + Literal const* literal = std::get_if(&_call.arguments.front()); + yulAssert(literal && literal->kind == LiteralKind::Number, ""); + return valueOfLiteral(*literal); +} +} + +void StackLimitEvader::run( + OptimiserStepContext& _context, + Object& _object, + map> const& _unreachableVariables +) +{ + yulAssert(_object.code, ""); + auto const* evmDialect = dynamic_cast(&_context.dialect); + yulAssert( + evmDialect && evmDialect->providesObjectAccess(), + "StackLimitEvader can only be run on objects using the EVMDialect with object access." + ); + + vector memoryGuardCalls = FunctionCallFinder::run( + *_object.code, + "memoryguard"_yulstring + ); + // Do not optimise, if no ``memoryguard`` call is found. + if (memoryGuardCalls.empty()) + return; + + // Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort). + u256 reservedMemory = literalArgumentValue(*memoryGuardCalls.front()); + for (FunctionCall const* getFreeMemoryStartCall: memoryGuardCalls) + if (reservedMemory != literalArgumentValue(*getFreeMemoryStartCall)) + return; + + CallGraph callGraph = CallGraphGenerator::callGraph(*_object.code); + + // We cannot move variables in recursive functions to fixed memory offsets. + for (YulString function: callGraph.recursiveFunctions()) + if (_unreachableVariables.count(function)) + return; + + MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls}; + uint64_t requiredSlots = memoryOffsetAllocator.run(); + + StackToMemoryMover{_context, reservedMemory, memoryOffsetAllocator.slotAllocations}(*_object.code); + reservedMemory += 32 * requiredSlots; + YulString reservedMemoryString{util::toCompactHexWithPrefix(reservedMemory)}; + for (FunctionCall* memoryGuardCall: memoryGuardCalls) + { + Literal* literal = std::get_if(&memoryGuardCall->arguments.front()); + yulAssert(literal && literal->kind == LiteralKind::Number, ""); + literal->value = reservedMemoryString; + } +} diff --git a/libyul/optimiser/StackLimitEvader.h b/libyul/optimiser/StackLimitEvader.h new file mode 100644 index 000000000..4fc351f73 --- /dev/null +++ b/libyul/optimiser/StackLimitEvader.h @@ -0,0 +1,66 @@ +/* + 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 . +*/ +/** + * Optimisation stage that assigns memory offsets to variables that would become unreachable if + * assigned a stack slot as usual and replaces references and assignments to them by mload and mstore calls. + */ + +#pragma once + +#include + +namespace solidity::yul +{ + +struct Object; + +/** + * Optimisation stage that assigns memory offsets to variables that would become unreachable if + * assigned a stack slot as usual. + * + * Uses CompilabilityChecker to determine which variables in which functions are unreachable. + * + * Only variables outside of functions contained in cycles in the call graph are considered. Thereby it is possible + * to assign globally fixed memory offsets to the variable. If a variable in a function contained in a cycle in the + * call graph is reported as unreachable, the process is aborted. + * + * Offsets are assigned to the variables, s.t. on every path through the call graph each variable gets a unique offset + * in memory. However, distinct paths through the call graph can use the same memory offsets for their variables. + * + * The current arguments to the ``memoryguard`` calls are used as base memory offset and then replaced by the offset past + * the last memory offset used for a variable on any path through the call graph. + * + * Finally, the StackToMemoryMover is called to actually move the variables to their offsets in memory. + * + * Prerequisite: Disambiguator + */ +class StackLimitEvader +{ +public: + /// @a _unreachableVariables can be determined by the CompilabilityChecker. + /// Can only be run on the EVM dialect with objects. + /// Abort and do nothing, if no ``memoryguard`` call or several ``memoryguard`` calls + /// with non-matching arguments are found, or if any of the @a _unreachableVariables + /// are contained in a recursive function. + static void run( + OptimiserStepContext& _context, + Object& _object, + std::map> const& _unreachableVariables + ); +}; + +} diff --git a/libyul/optimiser/StackToMemoryMover.cpp b/libyul/optimiser/StackToMemoryMover.cpp new file mode 100644 index 000000000..2c1cfa227 --- /dev/null +++ b/libyul/optimiser/StackToMemoryMover.cpp @@ -0,0 +1,210 @@ +/* + 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 . +*/ +#include +#include +#include + +#include + +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::yul; + +namespace +{ +void appendMemoryStore( + vector& _statements, + langutil::SourceLocation const& _loc, + YulString _mpos, + Expression _value +) +{ + _statements.emplace_back(ExpressionStatement{_loc, FunctionCall{ + _loc, + Identifier{_loc, "mstore"_yulstring}, + { + Literal{_loc, LiteralKind::Number, _mpos, {}}, + std::move(_value) + } + }}); +} +} + +StackToMemoryMover::StackToMemoryMover( + OptimiserStepContext& _context, + u256 _reservedMemory, + map> const& _memorySlots +): m_reservedMemory(std::move(_reservedMemory)), m_memorySlots(_memorySlots), m_nameDispenser(_context.dispenser) +{ + auto const* evmDialect = dynamic_cast(&_context.dialect); + yulAssert( + evmDialect && evmDialect->providesObjectAccess(), + "StackToMemoryMover can only be run on objects using the EVMDialect with object access." + ); + + if (m_memorySlots.count(YulString{})) + // If the global scope contains variables to be moved, start with those as if it were a function. + m_currentFunctionMemorySlots = &m_memorySlots.at(YulString{}); +} + +void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition) +{ + map const* saved = m_currentFunctionMemorySlots; + if (m_memorySlots.count(_functionDefinition.name)) + { + m_currentFunctionMemorySlots = &m_memorySlots.at(_functionDefinition.name); + for (TypedName const& param: _functionDefinition.parameters + _functionDefinition.returnVariables) + if (m_currentFunctionMemorySlots->count(param.name)) + { + // TODO: we cannot handle function parameters yet. + m_currentFunctionMemorySlots = nullptr; + break; + } + } + else + m_currentFunctionMemorySlots = nullptr; + ASTModifier::operator()(_functionDefinition); + m_currentFunctionMemorySlots = saved; +} + +void StackToMemoryMover::operator()(Block& _block) +{ + using OptionalStatements = std::optional>; + if (!m_currentFunctionMemorySlots) + { + ASTModifier::operator()(_block); + return; + } + auto containsVariableNeedingEscalation = [&](auto const& _variables) { + return util::contains_if(_variables, [&](auto const& var) { + return m_currentFunctionMemorySlots->count(var.name); + }); + }; + auto rewriteAssignmentOrVariableDeclaration = [&]( + langutil::SourceLocation const& _loc, + auto const& _variables, + std::unique_ptr _value + ) -> std::vector { + if (_variables.size() == 1) + { + std::vector result; + appendMemoryStore( + result, + _loc, + memoryOffset(_variables.front().name), + _value ? *std::move(_value) : Literal{_loc, LiteralKind::Number, "0"_yulstring, {}} + ); + return result; + } + + VariableDeclaration tempDecl{_loc, {}, std::move(_value)}; + vector memoryAssignments; + vector variableAssignments; + for (auto& var: _variables) + { + YulString tempVarName = m_nameDispenser.newName(var.name); + tempDecl.variables.emplace_back(TypedName{var.location, tempVarName, {}}); + + if (m_currentFunctionMemorySlots->count(var.name)) + appendMemoryStore(memoryAssignments, _loc, memoryOffset(var.name), Identifier{_loc, tempVarName}); + else if constexpr (std::is_same_v, Identifier>) + variableAssignments.emplace_back(Assignment{ + _loc, { Identifier{var.location, var.name} }, + make_unique(Identifier{_loc, tempVarName}) + }); + else + variableAssignments.emplace_back(VariableDeclaration{ + _loc, {std::move(var)}, + make_unique(Identifier{_loc, tempVarName}) + }); + } + std::vector result; + result.emplace_back(std::move(tempDecl)); + std::reverse(memoryAssignments.begin(), memoryAssignments.end()); + result += std::move(memoryAssignments); + std::reverse(variableAssignments.begin(), variableAssignments.end()); + result += std::move(variableAssignments); + return result; + }; + + util::iterateReplacing( + _block.statements, + [&](Statement& _statement) + { + auto defaultVisit = [&]() { ASTModifier::visit(_statement); return OptionalStatements{}; }; + return std::visit(util::GenericVisitor{ + [&](Assignment& _assignment) -> OptionalStatements + { + if (!containsVariableNeedingEscalation(_assignment.variableNames)) + return defaultVisit(); + visit(*_assignment.value); + return {rewriteAssignmentOrVariableDeclaration( + _assignment.location, + _assignment.variableNames, + std::move(_assignment.value) + )}; + }, + [&](VariableDeclaration& _varDecl) -> OptionalStatements + { + if (!containsVariableNeedingEscalation(_varDecl.variables)) + return defaultVisit(); + if (_varDecl.value) + visit(*_varDecl.value); + return {rewriteAssignmentOrVariableDeclaration( + _varDecl.location, + _varDecl.variables, + std::move(_varDecl.value) + )}; + }, + [&](auto&) { return defaultVisit(); } + }, _statement); + }); +} + +void StackToMemoryMover::visit(Expression& _expression) +{ + if ( + Identifier* identifier = std::get_if(&_expression); + identifier && m_currentFunctionMemorySlots && m_currentFunctionMemorySlots->count(identifier->name) + ) + { + langutil::SourceLocation loc = identifier->location; + _expression = FunctionCall { + loc, + Identifier{loc, "mload"_yulstring}, { + Literal { + loc, + LiteralKind::Number, + memoryOffset(identifier->name), + {} + } + } + }; + } + else + ASTModifier::visit(_expression); +} + +YulString StackToMemoryMover::memoryOffset(YulString _variable) +{ + yulAssert(m_currentFunctionMemorySlots, ""); + return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * m_currentFunctionMemorySlots->at(_variable))}; +} + diff --git a/libyul/optimiser/StackToMemoryMover.h b/libyul/optimiser/StackToMemoryMover.h new file mode 100644 index 000000000..d4ef2f737 --- /dev/null +++ b/libyul/optimiser/StackToMemoryMover.h @@ -0,0 +1,100 @@ +/* + 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 . +*/ +/** + * Optimisation stage that moves Yul variables from stack to memory. + */ + +#pragma once + +#include +#include +#include + +namespace solidity::yul +{ + +/** + * Optimisation stage that moves Yul variables from stack to memory. + * It takes a map from functions names and variable names to memory offsets. + * It then transforms the AST as follows: + * + * Single variable declarations are replaced by mstore's as follows: + * If a is in the map, replace + * let a + * by + * mstore(, 0) + * respectively, replace + * let a := expr + * by + * mstore(, expr) + * + * In a multi-variable declaration, variables to be moved are replaced by fresh variables and then moved to memory: + * If b and d are in the map, replace + * let a, b, c, d := f() + * by + * let _1, _2, _3, _4 := f() + * mstore(, _4) + * mstore(, _2) + * let c := _3 + * let a := _1 + * + * Assignments to single variables are replaced by mstore's: + * If a is in the map, replace + * a := expr + * by + * mstore(, expr) + * + * Assignments to multiple variables are split up similarly to multi-variable declarations: + * If b and d are in the map, replace + * a, b, c, d := f() + * by + * let _1, _2, _3, _4 := f() + * mstore(, _4) + * mstore(, _2) + * c := _3 + * a := _1 + * + * Replace all references to a variable ``a`` in the map by ``mload()``. + * + * If a visited function has arguments or return parameters that are contained in the map, + * the entire function is skipped (no local variables in the function will be moved at all). + * + * Prerequisite: Disambiguator, ForLoopInitRewriter. + */ +class StackToMemoryMover: ASTModifier +{ +public: + StackToMemoryMover( + OptimiserStepContext& _context, + u256 _reservedMemory, + std::map> const& _memoryOffsets + ); + + using ASTModifier::operator(); + + void operator()(FunctionDefinition& _functionDefinition) override; + void operator()(Block& _block) override; + void visit(Expression& _expression) override; +private: + YulString memoryOffset(YulString _variable); + u256 m_reservedMemory; + std::map> const& m_memorySlots; + NameDispenser& m_nameDispenser; + std::map const* m_currentFunctionMemorySlots = nullptr; +}; + +} \ No newline at end of file diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 98683b453..11249c23d 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,7 @@ #include #include +#include using namespace std; using namespace solidity; @@ -124,6 +126,12 @@ void OptimiserSuite::run( { yulAssert(_meter, ""); ConstantOptimiser{*dialect, *_meter}(ast); + if (dialect->providesObjectAccess()) + StackLimitEvader::run(suite.m_context, _object, CompilabilityChecker{ + _dialect, + _object, + _optimizeStackAllocation + }.unreachableVariables); } else if (dynamic_cast(&_dialect)) { diff --git a/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output index fd372367a..6af2128e1 100644 --- a/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output +++ b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output @@ -9,7 +9,7 @@ Optimized IR: object "C_6" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } let _1 := datasize("C_6_deployed") codecopy(0, dataoffset("C_6_deployed"), _1) @@ -19,7 +19,7 @@ object "C_6" { object "C_6_deployed" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) revert(0, 0) } } @@ -37,7 +37,7 @@ Optimized IR: object "D_9" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } let _1 := datasize("D_9_deployed") codecopy(0, dataoffset("D_9_deployed"), _1) @@ -47,7 +47,7 @@ object "D_9" { object "D_9_deployed" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) revert(0, 0) } } diff --git a/test/cmdlineTests/ir_compiler_subobjects/output b/test/cmdlineTests/ir_compiler_subobjects/output index 6b5f8e677..012c19828 100644 --- a/test/cmdlineTests/ir_compiler_subobjects/output +++ b/test/cmdlineTests/ir_compiler_subobjects/output @@ -9,7 +9,7 @@ Optimized IR: object "C_2" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } let _1 := datasize("C_2_deployed") codecopy(0, dataoffset("C_2_deployed"), _1) @@ -19,7 +19,7 @@ object "C_2" { object "C_2_deployed" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) revert(0, 0) } } @@ -37,7 +37,7 @@ Optimized IR: object "D_13" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } let _1 := datasize("D_13_deployed") codecopy(0, dataoffset("D_13_deployed"), _1) @@ -47,20 +47,21 @@ object "D_13" { object "D_13_deployed" { code { { - mstore(64, 128) + let _1 := memoryguard(0x80) + mstore(64, _1) if iszero(lt(calldatasize(), 4)) { - let _1 := 0 - if eq(0x26121ff0, shr(224, calldataload(_1))) + let _2 := 0 + if eq(0x26121ff0, shr(224, calldataload(_2))) { - if callvalue() { revert(_1, _1) } - if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } - let _2 := datasize("C_2") - let _3 := add(128, _2) - if or(gt(_3, 0xffffffffffffffff), lt(_3, 128)) { revert(_1, _1) } - datacopy(128, dataoffset("C_2"), _2) - pop(create(_1, 128, _2)) - return(allocateMemory(_1), _1) + if callvalue() { revert(_2, _2) } + if slt(add(calldatasize(), not(3)), _2) { revert(_2, _2) } + let _3 := datasize("C_2") + let _4 := add(_1, _3) + if or(gt(_4, 0xffffffffffffffff), lt(_4, _1)) { revert(_2, _2) } + datacopy(_1, dataoffset("C_2"), _3) + pop(create(_2, _1, sub(_4, _1))) + return(allocateMemory(_2), _2) } } revert(0, 0) @@ -76,7 +77,7 @@ object "D_13" { object "C_2" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } let _1 := datasize("C_2_deployed") codecopy(0, dataoffset("C_2_deployed"), _1) @@ -86,7 +87,7 @@ object "D_13" { object "C_2_deployed" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) revert(0, 0) } } diff --git a/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/args b/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/args new file mode 100644 index 000000000..cae21e720 --- /dev/null +++ b/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/args @@ -0,0 +1 @@ +--ir-optimized --optimize \ No newline at end of file diff --git a/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/input.sol b/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/input.sol new file mode 100644 index 000000000..6dd033f13 --- /dev/null +++ b/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/input.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0.0; + +contract D { + constructor() { assembly {}} + function f() public pure {} +} diff --git a/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/output b/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/output new file mode 100644 index 000000000..5314caa65 --- /dev/null +++ b/test/cmdlineTests/ir_with_assembly_no_memoryguard_creation/output @@ -0,0 +1,40 @@ +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "D_11" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("D_11_deployed") + codecopy(0, dataoffset("D_11_deployed"), _1) + return(0, _1) + } + } + object "D_11_deployed" { + code { + { + let _1 := memoryguard(0x80) + mstore(64, _1) + if iszero(lt(calldatasize(), 4)) + { + let _2 := 0 + if eq(0x26121ff0, shr(224, calldataload(_2))) + { + if callvalue() { revert(_2, _2) } + if slt(add(calldatasize(), not(3)), _2) { revert(_2, _2) } + if gt(_1, 0xffffffffffffffff) { revert(_2, _2) } + mstore(64, _1) + return(_1, _2) + } + } + revert(0, 0) + } + } + } +} diff --git a/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/args b/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/args new file mode 100644 index 000000000..cae21e720 --- /dev/null +++ b/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/args @@ -0,0 +1 @@ +--ir-optimized --optimize \ No newline at end of file diff --git a/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/input.sol b/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/input.sol new file mode 100644 index 000000000..caef2b75e --- /dev/null +++ b/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/input.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0.0; + +contract D { + function f() public pure { + assembly {} + } +} diff --git a/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/output b/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/output new file mode 100644 index 000000000..25d7edfb2 --- /dev/null +++ b/test/cmdlineTests/ir_with_assembly_no_memoryguard_runtime/output @@ -0,0 +1,38 @@ +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "D_7" { + code { + { + mstore(64, memoryguard(0x80)) + if callvalue() { revert(0, 0) } + let _1 := datasize("D_7_deployed") + codecopy(0, dataoffset("D_7_deployed"), _1) + return(0, _1) + } + } + object "D_7_deployed" { + code { + { + mstore(64, 128) + if iszero(lt(calldatasize(), 4)) + { + let _1 := 0 + if eq(0x26121ff0, shr(224, calldataload(_1))) + { + if callvalue() { revert(_1, _1) } + if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } + mstore(64, 128) + return(128, _1) + } + } + revert(0, 0) + } + } + } +} diff --git a/test/cmdlineTests/name_simplifier/output b/test/cmdlineTests/name_simplifier/output index f2ec8e0cc..085729802 100644 --- a/test/cmdlineTests/name_simplifier/output +++ b/test/cmdlineTests/name_simplifier/output @@ -9,7 +9,7 @@ Optimized IR: object "C_56" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } let _1 := datasize("C_56_deployed") codecopy(0, dataoffset("C_56_deployed"), _1) @@ -19,7 +19,7 @@ object "C_56" { object "C_56_deployed" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if iszero(lt(calldatasize(), 4)) { let _1 := 0 diff --git a/test/cmdlineTests/optimizer_array_sload/output b/test/cmdlineTests/optimizer_array_sload/output index 31c17a0a5..515738473 100644 --- a/test/cmdlineTests/optimizer_array_sload/output +++ b/test/cmdlineTests/optimizer_array_sload/output @@ -9,7 +9,7 @@ Optimized IR: object "Arraysum_33" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } let _1 := datasize("Arraysum_33_deployed") codecopy(0, dataoffset("Arraysum_33_deployed"), _1) @@ -19,7 +19,7 @@ object "Arraysum_33" { object "Arraysum_33_deployed" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if iszero(lt(calldatasize(), 4)) { let _1 := 0 diff --git a/test/cmdlineTests/standard_irOptimized_requested/output.json b/test/cmdlineTests/standard_irOptimized_requested/output.json index ba3cb8b2a..09e7730f5 100644 --- a/test/cmdlineTests/standard_irOptimized_requested/output.json +++ b/test/cmdlineTests/standard_irOptimized_requested/output.json @@ -7,7 +7,7 @@ object \"C_6\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if callvalue() { revert(0, 0) } constructor_C_6() codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) @@ -17,7 +17,7 @@ object \"C_6\" { } object \"C_6_deployed\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if iszero(lt(calldatasize(), 4)) { let selector := shift_right_224_unsigned(calldataload(0)) diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index f63075090..cae921a47 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -8,7 +8,7 @@ object \"C_6\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if callvalue() { revert(0, 0) } constructor_C_6() @@ -24,7 +24,7 @@ object \"C_6\" { } object \"C_6_deployed\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if iszero(lt(calldatasize(), 4)) { diff --git a/test/cmdlineTests/yul_optimizer_steps/output b/test/cmdlineTests/yul_optimizer_steps/output index 847c731e9..b37f0b44e 100644 --- a/test/cmdlineTests/yul_optimizer_steps/output +++ b/test/cmdlineTests/yul_optimizer_steps/output @@ -9,7 +9,7 @@ Optimized IR: object "C_6" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if callvalue() { revert(0, 0) } codecopy(0, dataoffset("C_6_deployed"), datasize("C_6_deployed")) return(0, datasize("C_6_deployed")) @@ -18,7 +18,7 @@ object "C_6" { object "C_6_deployed" { code { { - mstore(64, 128) + mstore(64, memoryguard(0x80)) if iszero(lt(calldatasize(), 4)) { let selector := shift_right_224_unsigned(calldataload(0)) diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json index 0609704c5..ad1c92433 100644 --- a/test/cmdlineTests/yul_string_format_ascii/output.json +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -8,7 +8,7 @@ object \"C_10\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if callvalue() { revert(0, 0) } constructor_C_10() @@ -24,7 +24,7 @@ object \"C_10\" { } object \"C_10_deployed\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if iszero(lt(calldatasize(), 4)) { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json index 1bc00b338..d1907f22c 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -8,7 +8,7 @@ object \"C_10\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if callvalue() { revert(0, 0) } constructor_C_10() @@ -24,7 +24,7 @@ object \"C_10\" { } object \"C_10_deployed\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if iszero(lt(calldatasize(), 4)) { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json index fdef77781..0f981bf16 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -8,7 +8,7 @@ object \"C_10\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if callvalue() { revert(0, 0) } constructor_C_10() @@ -24,7 +24,7 @@ object \"C_10\" { } object \"C_10_deployed\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if iszero(lt(calldatasize(), 4)) { diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json index 2a845796d..ecbed13d3 100644 --- a/test/cmdlineTests/yul_string_format_ascii_long/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -8,7 +8,7 @@ object \"C_10\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if callvalue() { revert(0, 0) } constructor_C_10() @@ -24,7 +24,7 @@ object \"C_10\" { } object \"C_10_deployed\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if iszero(lt(calldatasize(), 4)) { diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json index 784baf2f4..86a658440 100644 --- a/test/cmdlineTests/yul_string_format_hex/output.json +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -8,7 +8,7 @@ object \"C_10\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if callvalue() { revert(0, 0) } constructor_C_10() @@ -24,7 +24,7 @@ object \"C_10\" { } object \"C_10_deployed\" { code { - mstore(64, 128) + mstore(64, memoryguard(128)) if iszero(lt(calldatasize(), 4)) { diff --git a/test/libsolidity/semanticTests/viaYul/stackLimitEvasion/inlined.sol b/test/libsolidity/semanticTests/viaYul/stackLimitEvasion/inlined.sol new file mode 100644 index 000000000..b9c050b46 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/stackLimitEvasion/inlined.sol @@ -0,0 +1,53 @@ +contract C { + uint256[1024] s; + function f() public returns (uint256 x) { + x = 42; + uint256 x0 = s[0]; + uint256 x1 = s[1]; + uint256 x2 = s[2]; + uint256 x3 = s[3]; + uint256 x4 = s[4]; + uint256 x5 = s[5]; + uint256 x6 = s[6]; + uint256 x7 = s[7]; + uint256 x8 = s[8]; + uint256 x9 = s[9]; + uint256 x10 = s[10]; + uint256 x11 = s[11]; + uint256 x12 = s[12]; + uint256 x13 = s[13]; + uint256 x14 = s[14]; + uint256 x15 = s[15]; + uint256 x16 = s[16]; + uint256 x17 = s[17]; + uint256 x18 = s[18]; + s[1000] = x0 + 2; + s[118] = x18; + s[117] = x17; + s[116] = x16; + s[115] = x15; + s[114] = x14; + s[113] = x13; + s[112] = x12; + s[111] = x11; + s[110] = x10; + s[109] = x9; + s[108] = x8; + s[107] = x7; + s[106] = x6; + s[105] = x5; + s[104] = x4; + s[103] = x3; + s[102] = x2; + s[101] = x1; + s[100] = x0; + } + function test() public view returns(uint256) { + return s[1000]; + } +} +// ==== +// compileViaYul: true +// ---- +// f() -> 0x2a +// test() -> 2 diff --git a/test/libsolidity/semanticTests/viaYul/stackLimitEvasion/non_inlined.sol b/test/libsolidity/semanticTests/viaYul/stackLimitEvasion/non_inlined.sol new file mode 100644 index 000000000..9fa3f7117 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/stackLimitEvasion/non_inlined.sol @@ -0,0 +1,57 @@ +contract C { + uint256[1024] s; + function g() public returns (uint256) { + // try to prevent inlining + return f() + f() + f() + f() + f(); + } + function f() public returns (uint256 x) { + x = 42; + uint256 x0 = s[0]; + uint256 x1 = s[1]; + uint256 x2 = s[2]; + uint256 x3 = s[3]; + uint256 x4 = s[4]; + uint256 x5 = s[5]; + uint256 x6 = s[6]; + uint256 x7 = s[7]; + uint256 x8 = s[8]; + uint256 x9 = s[9]; + uint256 x10 = s[10]; + uint256 x11 = s[11]; + uint256 x12 = s[12]; + uint256 x13 = s[13]; + uint256 x14 = s[14]; + uint256 x15 = s[15]; + uint256 x16 = s[16]; + uint256 x17 = s[17]; + uint256 x18 = s[18]; + s[1000] = x0 + 2; + s[118] = x18; + s[117] = x17; + s[116] = x16; + s[115] = x15; + s[114] = x14; + s[113] = x13; + s[112] = x12; + s[111] = x11; + s[110] = x10; + s[109] = x9; + s[108] = x8; + s[107] = x7; + s[106] = x6; + s[105] = x5; + s[104] = x4; + s[103] = x3; + s[102] = x2; + s[101] = x1; + s[100] = x0; + } + function test() public view returns(uint256) { + return s[1000]; + } +} +// ==== +// compileViaYul: true +// ---- +// f() -> 0x2a +// test() -> 2 diff --git a/test/libyul/CompilabilityChecker.cpp b/test/libyul/CompilabilityChecker.cpp index ad1a6fd5e..e8890a8bb 100644 --- a/test/libyul/CompilabilityChecker.cpp +++ b/test/libyul/CompilabilityChecker.cpp @@ -39,7 +39,7 @@ string check(string const& _input) Object obj; std::tie(obj.code, obj.analysisInfo) = yul::test::parse(_input, false); BOOST_REQUIRE(obj.code); - map functions = CompilabilityChecker::run(EVMDialect::strictAssemblyForEVM(solidity::test::CommonOptions::get().evmVersion()), obj, true); + auto functions = CompilabilityChecker(EVMDialect::strictAssemblyForEVM(solidity::test::CommonOptions::get().evmVersion()), obj, true).stackDeficit; string out; for (auto const& function: functions) out += function.first.str() + ": " + to_string(function.second) + " "; diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index fc5177fe4..e61b5d2d8 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +72,7 @@ #include #include #include +#include #include #include @@ -378,6 +381,58 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line obj.analysisInfo = m_analysisInfo; OptimiserSuite::run(*m_dialect, &meter, obj, true, solidity::frontend::OptimiserSettings::DefaultYulOptimiserSteps); } + else if (m_optimizerStep == "stackLimitEvader") + { + yul::Object obj; + obj.code = m_object->code; + obj.analysisInfo = m_analysisInfo; + disambiguate(); + StackLimitEvader::run(*m_context, obj, CompilabilityChecker{ + *m_dialect, + obj, + true + }.unreachableVariables); + } + else if (m_optimizerStep == "fakeStackLimitEvader") + { + yul::Object obj; + obj.code = m_object->code; + obj.analysisInfo = m_analysisInfo; + disambiguate(); + // Mark all variables with a name starting with "$" for escalation to memory. + struct FakeUnreachableGenerator: ASTWalker + { + map> fakeUnreachables; + using ASTWalker::operator(); + void operator()(FunctionDefinition const& _function) override + { + YulString originalFunctionName = m_currentFunction; + m_currentFunction = _function.name; + ASTWalker::operator()(_function); + m_currentFunction = originalFunctionName; + } + void visitVariableName(YulString _var) + { + if (!_var.empty() && _var.str().front() == '$') + fakeUnreachables[m_currentFunction].insert(_var); + } + void operator()(VariableDeclaration const& _varDecl) override + { + for (auto const& var: _varDecl.variables) + visitVariableName(var.name); + ASTWalker::operator()(_varDecl); + } + void operator()(Identifier const& _identifier) override + { + visitVariableName(_identifier.name); + ASTWalker::operator()(_identifier); + } + YulString m_currentFunction = YulString{}; + }; + FakeUnreachableGenerator fakeUnreachableGenerator; + fakeUnreachableGenerator(*obj.code); + StackLimitEvader::run(*m_context, obj, fakeUnreachableGenerator.fakeUnreachables); + } else { AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Invalid optimizer step: " << m_optimizerStep << endl; diff --git a/test/libyul/ewasmTranslationTests/memoryguard.yul b/test/libyul/ewasmTranslationTests/memoryguard.yul new file mode 100644 index 000000000..5aafc88d2 --- /dev/null +++ b/test/libyul/ewasmTranslationTests/memoryguard.yul @@ -0,0 +1,12 @@ +{ + mstore(0x40, memoryguard(0x0102030405060708)) + sstore(1, mload(0x40)) +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 0000000000000000000000000000000000000000000000000102030405060708 +// 80: 0000000000000000000000000000000000000000000000000102030405060708 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000000000000000000102030405060708 diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul new file mode 100644 index 000000000..075da88cd --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul @@ -0,0 +1,62 @@ +{ + mstore(0x40, memoryguard(0)) + function g() -> a, b { + a := 21 + let $c := 1 + b,a,$c := z() + } + function f() -> x { + let $x2 + $x2 := 42 + let $x3, $x4 := g() + x := mul(add($x2, $x3), h($x4)) + sstore($x3, $x4) + } + function h(v) -> a { + let x, $z, y := z() + a, $z, v := z() + } + function z() -> a,b,c { let $x := 0 } + sstore(0, f()) + let x, y := g() +} +// ---- +// step: fakeStackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// function g() -> a, b +// { +// a := 21 +// mstore(0x20, 1) +// let b_1, a_2, $c_3 := z() +// mstore(0x20, $c_3) +// a := a_2 +// b := b_1 +// } +// function f() -> x +// { +// mstore(0x60, 0) +// mstore(0x60, 42) +// let $x3_4, $x4_5 := g() +// mstore(0x80, $x4_5) +// mstore(0x40, $x3_4) +// x := mul(add(mload(0x60), mload(0x40)), h(mload(0x80))) +// sstore(mload(0x40), mload(0x80)) +// } +// function h(v) -> a_1 +// { +// let x_2_6, $z_7, y_8 := z() +// mstore(0x20, $z_7) +// let y := y_8 +// let x_2 := x_2_6 +// let a_1_9, $z_10, v_11 := z() +// mstore(0x20, $z_10) +// v := v_11 +// a_1 := a_1_9 +// } +// function z() -> a_3, b_4, c +// { mstore(0x00, 0) } +// sstore(0, f()) +// let x_5, y_6 := g() +// } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul new file mode 100644 index 000000000..033e0b3c0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul @@ -0,0 +1,20 @@ +{ + mstore(0x40, memoryguard(0)) + let $x := 0 + sstore(0, $x) + function h($hx) -> y { + y := $hx + } + sstore(1, h(32)) +} +// ---- +// step: fakeStackLimitEvader +// +// { +// mstore(0x40, memoryguard(0x40)) +// mstore(0x20, 0) +// sstore(0, mload(0x20)) +// function h($hx) -> y +// { y := $hx } +// sstore(1, h(32)) +// } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/outer_block.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/outer_block.yul new file mode 100644 index 000000000..023100cdd --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/outer_block.yul @@ -0,0 +1,13 @@ +{ + mstore(0x40, memoryguard(0x80)) + let $x := 42 + sstore(42, $x) +} +// ---- +// step: fakeStackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// mstore(0x80, 42) +// sstore(42, mload(0x80)) +// } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul new file mode 100644 index 000000000..9c792e63f --- /dev/null +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul @@ -0,0 +1,88 @@ +{ + mstore(0x40, memoryguard(0)) + function f() { + let $fx + let $fy := 42 + sstore($fx, $fy) + $fx := 21 + } + function g(gx) { + let $gx, $gy := tuple2() + { $gx, $gy := tuple2() } + { $gx, gx := tuple2() } + { gx, $gy := tuple2() } + } + function h(hx, hy, hz, hw) { + let $hx, $hy, $hz, $hw := tuple4() + { hx, $hy, hz, $hw := tuple4() } + { $hx, $hy, hz, hw := tuple4() } + } + function tuple2() -> a, b {} + function tuple4() -> a, b, c, d {} + f() + g(0) + h(1, 2, 3, 4) +} +// ---- +// step: fakeStackLimitEvader +// +// { +// mstore(0x40, memoryguard(0x80)) +// function f() +// { +// mstore(0x20, 0) +// mstore(0x00, 42) +// sstore(mload(0x20), mload(0x00)) +// mstore(0x20, 21) +// } +// function g(gx) +// { +// let $gx_1, $gy_2 := tuple2() +// mstore(0x20, $gy_2) +// mstore(0x00, $gx_1) +// { +// let $gx_3, $gy_4 := tuple2() +// mstore(0x20, $gy_4) +// mstore(0x00, $gx_3) +// } +// { +// let $gx_5, gx_6 := tuple2() +// mstore(0x00, $gx_5) +// gx := gx_6 +// } +// { +// let gx_7, $gy_8 := tuple2() +// mstore(0x20, $gy_8) +// gx := gx_7 +// } +// } +// function h(hx, hy, hz, hw) +// { +// let $hx_9, $hy_10, $hz_11, $hw_12 := tuple4() +// mstore(0x60, $hw_12) +// mstore(0x00, $hz_11) +// mstore(0x20, $hy_10) +// mstore(0x40, $hx_9) +// { +// let hx_13, $hy_14, hz_15, $hw_16 := tuple4() +// mstore(0x60, $hw_16) +// mstore(0x20, $hy_14) +// hz := hz_15 +// hx := hx_13 +// } +// { +// let $hx_17, $hy_18, hz_19, hw_20 := tuple4() +// mstore(0x20, $hy_18) +// mstore(0x40, $hx_17) +// hw := hw_20 +// hz := hz_19 +// } +// } +// function tuple2() -> a, b +// { } +// function tuple4() -> a_1, b_2, c, d +// { } +// f() +// g(0) +// h(1, 2, 3, 4) +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul new file mode 100644 index 000000000..37da7f9b8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle.yul @@ -0,0 +1,95 @@ +{ + mstore(0x40, memoryguard(128)) + sstore(0, g(sload(3))) + function g(x) -> v { + v := f() + } + function f() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + sstore(23, g(sload(42))) + } +} +// ---- +// step: stackLimitEvader +// +// { +// mstore(0x40, memoryguard(128)) +// sstore(0, g(sload(3))) +// function g(x) -> v +// { v := f() } +// function f() -> v_1 +// { +// let a1 := calldataload(mul(1, 4)) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// a1 := calldataload(mul(0, 4)) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, a1) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), a1) +// sstore(23, g(sload(42))) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul new file mode 100644 index 000000000..4480d012e --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after.yul @@ -0,0 +1,95 @@ +{ + mstore(0x40, memoryguard(128)) + sstore(0, f()) + function f() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + sstore(23, h()) + } + function h() -> v { + v := h() + } +} +// ---- +// step: stackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, f()) +// function f() -> v +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0x80)) +// sstore(23, h()) +// } +// function h() -> v_1 +// { v_1 := h() } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul new file mode 100644 index 000000000..4468165f1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_after_2.yul @@ -0,0 +1,100 @@ +{ + mstore(0x40, memoryguard(128)) + sstore(0, f()) + function f() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + sstore(23, h()) + } + function h() -> v { + v := i() + } + function i() -> v { + v := h() + } +} +// ---- +// step: stackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, f()) +// function f() -> v +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0x80)) +// sstore(23, h()) +// } +// function h() -> v_1 +// { v_1 := i() } +// function i() -> v_2 +// { v_2 := h() } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul new file mode 100644 index 000000000..e6819ea90 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before.yul @@ -0,0 +1,103 @@ +{ + mstore(0x40, memoryguard(128)) + sstore(0, g(sload(3))) + function g(x) -> v { + switch lt(x, 3) + case 0 { + v := f() + } + case 1 { + v := g(sub(x,1)) + } + } + function f() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + } +} +// ---- +// step: stackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, g(sload(3))) +// function g(x) -> v +// { +// switch lt(x, 3) +// case 0 { v := f() } +// case 1 { v := g(sub(x, 1)) } +// } +// function f() -> v_1 +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0x80)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul new file mode 100644 index 000000000..a0259b180 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_2.yul @@ -0,0 +1,108 @@ +{ + mstore(0x40, memoryguard(128)) + sstore(0, g(sload(3))) + function g(x) -> v { + switch lt(x, 3) + case 0 { + v := h(x) + } + case 1 { + v := g(sub(x,f())) + } + } + function h(x) -> v { + v := g(x) + } + function f() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + } +} +// ---- +// step: stackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, g(sload(3))) +// function g(x) -> v +// { +// switch lt(x, 3) +// case 0 { v := h(x) } +// case 1 { v := g(sub(x, f())) } +// } +// function h(x_1) -> v_2 +// { v_2 := g(x_1) } +// function f() -> v_3 +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0x80)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul new file mode 100644 index 000000000..f5c068d5e --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/cycle_before_after.yul @@ -0,0 +1,110 @@ +{ + mstore(0x40, memoryguard(128)) + sstore(0, g(sload(0))) + function g(x) -> v { + switch lt(x, 3) + case 0 { + v := f() + } + case 1 { + v := g(sub(x,1)) + } + } + function f() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + sstore(23, h()) + } + function h() -> v { + v := h() + } +} +// ---- +// step: stackLimitEvader +// +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, g(sload(0))) +// function g(x) -> v +// { +// switch lt(x, 3) +// case 0 { v := f() } +// case 1 { v := g(sub(x, 1)) } +// } +// function f() -> v_1 +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0x80)) +// sstore(23, h()) +// } +// function h() -> v_2 +// { v_2 := h() } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul new file mode 100644 index 000000000..7bc4d2e80 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/function_arg.yul @@ -0,0 +1,88 @@ +{ + { + mstore(0x40, memoryguard(128)) + sstore(0, f(0)) + } + function f(a1) -> v { + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + } +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(0, f(0)) +// } +// function f(a1) -> v +// { +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, a1) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), a1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul new file mode 100644 index 000000000..ce12229b6 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/stub.yul @@ -0,0 +1,98 @@ +{ + { + mstore(0x40, memoryguard(128)) + sstore(g(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16), f()) + } + function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16) -> v { + // Should be, but cannot yet be escalated. + v := b16 + } + function f() -> v{ + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + } +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0xa0)) +// sstore(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), f()) +// } +// function g(b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16) -> v +// { v := b16 } +// function f() -> v_1 +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(mul(4, 4)) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, 4), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0x80)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul new file mode 100644 index 000000000..357f6bb68 --- /dev/null +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul @@ -0,0 +1,339 @@ +{ + { + mstore(0x40, memoryguard(128)) + sstore(23, f()) + } + function f() -> v{ + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(g()) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,h()), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + } + function g() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + v := i() + } + function h() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(10,4)) + a1 := calldataload(mul(0,4)) + a2 := calldataload(mul(1,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + let a18 := calldataload(mul(18,4)) + let a19 := calldataload(mul(19,4)) + sstore(0, add(a1, a2)) + sstore(mul(17,4), a19) + sstore(mul(17,4), a18) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + v := i() + } + function i() -> v { + let a1 := calldataload(mul(1,4)) + let a2 := calldataload(mul(2,4)) + let a3 := calldataload(mul(3,4)) + let a4 := calldataload(mul(4,4)) + let a5 := calldataload(mul(5,4)) + let a6 := calldataload(mul(6,4)) + let a7 := calldataload(mul(7,4)) + let a8 := calldataload(mul(8,4)) + let a9 := calldataload(mul(9,4)) + a1 := calldataload(mul(0,4)) + let a10 := calldataload(mul(10,4)) + let a11 := calldataload(mul(11,4)) + let a12 := calldataload(mul(12,4)) + let a13 := calldataload(mul(13,4)) + let a14 := calldataload(mul(14,4)) + let a15 := calldataload(mul(15,4)) + let a16 := calldataload(mul(16,4)) + let a17 := calldataload(mul(17,4)) + sstore(0, a1) + sstore(mul(17,4), a17) + sstore(mul(16,4), a16) + sstore(mul(15,4), a15) + sstore(mul(14,4), a14) + sstore(mul(13,4), a13) + sstore(mul(12,4), a12) + sstore(mul(11,4), a11) + sstore(mul(10,4), a10) + sstore(mul(9,4), a9) + sstore(mul(8,4), a8) + sstore(mul(7,4), a7) + sstore(mul(6,4), a6) + sstore(mul(5,4), a5) + sstore(mul(4,4), a4) + sstore(mul(3,4), a3) + sstore(mul(2,4), a2) + sstore(mul(1,4), a1) + v := sload(mul(42,8)) + } +} +// ---- +// step: stackLimitEvader +// +// { +// { +// mstore(0x40, memoryguard(0x0100)) +// sstore(23, f()) +// } +// function f() -> v +// { +// mstore(0xe0, calldataload(mul(1, 4))) +// let a2 := calldataload(mul(2, 4)) +// let a3 := calldataload(mul(3, 4)) +// let a4 := calldataload(g()) +// let a5 := calldataload(mul(5, 4)) +// let a6 := calldataload(mul(6, 4)) +// let a7 := calldataload(mul(7, 4)) +// let a8 := calldataload(mul(8, 4)) +// let a9 := calldataload(mul(9, 4)) +// mstore(0xe0, calldataload(mul(0, 4))) +// let a10 := calldataload(mul(10, 4)) +// let a11 := calldataload(mul(11, 4)) +// let a12 := calldataload(mul(12, 4)) +// let a13 := calldataload(mul(13, 4)) +// let a14 := calldataload(mul(14, 4)) +// let a15 := calldataload(mul(15, 4)) +// let a16 := calldataload(mul(16, 4)) +// let a17 := calldataload(mul(17, 4)) +// sstore(0, mload(0xe0)) +// sstore(mul(17, 4), a17) +// sstore(mul(16, 4), a16) +// sstore(mul(15, 4), a15) +// sstore(mul(14, 4), a14) +// sstore(mul(13, 4), a13) +// sstore(mul(12, 4), a12) +// sstore(mul(11, 4), a11) +// sstore(mul(10, 4), a10) +// sstore(mul(9, 4), a9) +// sstore(mul(8, h()), a8) +// sstore(mul(7, 4), a7) +// sstore(mul(6, 4), a6) +// sstore(mul(5, 4), a5) +// sstore(mul(4, 4), a4) +// sstore(mul(3, 4), a3) +// sstore(mul(2, 4), a2) +// sstore(mul(1, 4), mload(0xe0)) +// } +// function g() -> v_1 +// { +// mstore(0xa0, calldataload(mul(1, 4))) +// let a2_3 := calldataload(mul(2, 4)) +// let a3_4 := calldataload(mul(3, 4)) +// let a4_5 := calldataload(mul(4, 4)) +// let a5_6 := calldataload(mul(5, 4)) +// let a6_7 := calldataload(mul(6, 4)) +// let a7_8 := calldataload(mul(7, 4)) +// let a8_9 := calldataload(mul(8, 4)) +// let a9_10 := calldataload(mul(9, 4)) +// mstore(0xa0, calldataload(mul(0, 4))) +// let a10_11 := calldataload(mul(10, 4)) +// let a11_12 := calldataload(mul(11, 4)) +// let a12_13 := calldataload(mul(12, 4)) +// let a13_14 := calldataload(mul(13, 4)) +// let a14_15 := calldataload(mul(14, 4)) +// let a15_16 := calldataload(mul(15, 4)) +// let a16_17 := calldataload(mul(16, 4)) +// let a17_18 := calldataload(mul(17, 4)) +// sstore(0, mload(0xa0)) +// sstore(mul(17, 4), a17_18) +// sstore(mul(16, 4), a16_17) +// sstore(mul(15, 4), a15_16) +// sstore(mul(14, 4), a14_15) +// sstore(mul(13, 4), a13_14) +// sstore(mul(12, 4), a12_13) +// sstore(mul(11, 4), a11_12) +// sstore(mul(10, 4), a10_11) +// sstore(mul(9, 4), a9_10) +// sstore(mul(8, 4), a8_9) +// sstore(mul(7, 4), a7_8) +// sstore(mul(6, 4), a6_7) +// sstore(mul(5, 4), a5_6) +// sstore(mul(4, 4), a4_5) +// sstore(mul(3, 4), a3_4) +// sstore(mul(2, 4), a2_3) +// sstore(mul(1, 4), mload(0xa0)) +// v_1 := i() +// } +// function h() -> v_19 +// { +// mstore(0xc0, calldataload(mul(1, 4))) +// mstore(0xa0, calldataload(mul(2, 4))) +// let a3_22 := calldataload(mul(3, 4)) +// let a4_23 := calldataload(mul(4, 4)) +// let a5_24 := calldataload(mul(5, 4)) +// let a6_25 := calldataload(mul(6, 4)) +// let a7_26 := calldataload(mul(7, 4)) +// let a8_27 := calldataload(mul(8, 4)) +// let a9_28 := calldataload(mul(9, 4)) +// let a10_29 := calldataload(mul(10, 4)) +// let a11_30 := calldataload(mul(10, 4)) +// mstore(0xc0, calldataload(mul(0, 4))) +// mstore(0xa0, calldataload(mul(1, 4))) +// let a12_31 := calldataload(mul(12, 4)) +// let a13_32 := calldataload(mul(13, 4)) +// let a14_33 := calldataload(mul(14, 4)) +// let a15_34 := calldataload(mul(15, 4)) +// let a16_35 := calldataload(mul(16, 4)) +// let a17_36 := calldataload(mul(17, 4)) +// let a18 := calldataload(mul(18, 4)) +// let a19 := calldataload(mul(19, 4)) +// sstore(0, add(mload(0xc0), mload(0xa0))) +// sstore(mul(17, 4), a19) +// sstore(mul(17, 4), a18) +// sstore(mul(17, 4), a17_36) +// sstore(mul(16, 4), a16_35) +// sstore(mul(15, 4), a15_34) +// sstore(mul(14, 4), a14_33) +// sstore(mul(13, 4), a13_32) +// sstore(mul(12, 4), a12_31) +// sstore(mul(11, 4), a11_30) +// sstore(mul(10, 4), a10_29) +// sstore(mul(9, 4), a9_28) +// sstore(mul(8, 4), a8_27) +// sstore(mul(7, 4), a7_26) +// sstore(mul(6, 4), a6_25) +// sstore(mul(5, 4), a5_24) +// sstore(mul(4, 4), a4_23) +// sstore(mul(3, 4), a3_22) +// sstore(mul(2, 4), mload(0xa0)) +// sstore(mul(1, 4), mload(0xc0)) +// v_19 := i() +// } +// function i() -> v_37 +// { +// mstore(0x80, calldataload(mul(1, 4))) +// let a2_39 := calldataload(mul(2, 4)) +// let a3_40 := calldataload(mul(3, 4)) +// let a4_41 := calldataload(mul(4, 4)) +// let a5_42 := calldataload(mul(5, 4)) +// let a6_43 := calldataload(mul(6, 4)) +// let a7_44 := calldataload(mul(7, 4)) +// let a8_45 := calldataload(mul(8, 4)) +// let a9_46 := calldataload(mul(9, 4)) +// mstore(0x80, calldataload(mul(0, 4))) +// let a10_47 := calldataload(mul(10, 4)) +// let a11_48 := calldataload(mul(11, 4)) +// let a12_49 := calldataload(mul(12, 4)) +// let a13_50 := calldataload(mul(13, 4)) +// let a14_51 := calldataload(mul(14, 4)) +// let a15_52 := calldataload(mul(15, 4)) +// let a16_53 := calldataload(mul(16, 4)) +// let a17_54 := calldataload(mul(17, 4)) +// sstore(0, mload(0x80)) +// sstore(mul(17, 4), a17_54) +// sstore(mul(16, 4), a16_53) +// sstore(mul(15, 4), a15_52) +// sstore(mul(14, 4), a14_51) +// sstore(mul(13, 4), a13_50) +// sstore(mul(12, 4), a12_49) +// sstore(mul(11, 4), a11_48) +// sstore(mul(10, 4), a10_47) +// sstore(mul(9, 4), a9_46) +// sstore(mul(8, 4), a8_45) +// sstore(mul(7, 4), a7_44) +// sstore(mul(6, 4), a6_43) +// sstore(mul(5, 4), a5_42) +// sstore(mul(4, 4), a4_41) +// sstore(mul(3, 4), a3_40) +// sstore(mul(2, 4), a2_39) +// sstore(mul(1, 4), mload(0x80)) +// v_37 := sload(mul(42, 8)) +// } +// } From 0eb32b3f60be40cf80e3d9f0f6b4a06180da88f8 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Wed, 16 Sep 2020 10:47:13 +0200 Subject: [PATCH 11/21] Rephrase documentation of memoryguard --- docs/yul.rst | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/yul.rst b/docs/yul.rst index 0e30a9082..cbffd9e5c 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -956,16 +956,22 @@ memoryguard ^^^^^^^^^^^ This function is available in the EVM dialect with objects. The caller of -``let ptr := memoryguard(size)`` promises that they only use memory in either -the range ``[0, size)`` or the unbounded range above ``ptr``. The Yul optimizer -promises to only use the memory range ``[size, ptr)`` for its purposes. -If the optimizer does not need to reserve any memory, it holds that ``ptr := size``. +``let ptr := memoryguard(size)`` (where ``size`` has to be a literal number) +promises that they only use memory in either the range ``[0, size)`` or the +unbounded range above ``ptr``. + +Since the presence of a ``memoryguard`` call indicates that all memory access +adheres to this restriction, it allows the optimizer to perform additional +optimization steps, for example the stack limit evader, which attempts to move +stack variables that would otherwise be unreachable to memory. + +The Yul optimizer promises to only use the memory range ``[size, ptr)`` for its purposes. +If the optimizer does not need to reserve any memory, it holds that ``ptr == size``. ``memoryguard`` can be called multiple times, but needs to have the same literal as argument within one Yul subobject. If at least one ``memoryguard`` call is found in a subobject, -the Yul optimiser will try to perform experimental steps like the stack limit evader, -which attempts to move stack variables that would otherwise be unreachable -to memory. +the additional optimiser steps will be run on it. + .. _yul-object: From 976abf471562300b1a95506468b3f15ad59f2bb1 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 17 Sep 2020 17:25:37 +0200 Subject: [PATCH 12/21] Easy review fixes. --- docs/yul.rst | 2 +- libsolidity/codegen/ir/IRGenerator.cpp | 10 ++++- libyul/optimiser/StackLimitEvader.cpp | 53 +++++++++++++------------ libyul/optimiser/StackToMemoryMover.cpp | 43 +++++++++++--------- libyul/optimiser/StackToMemoryMover.h | 22 ++++++++-- 5 files changed, 79 insertions(+), 51 deletions(-) diff --git a/docs/yul.rst b/docs/yul.rst index cbffd9e5c..619e23bab 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -958,7 +958,7 @@ memoryguard This function is available in the EVM dialect with objects. The caller of ``let ptr := memoryguard(size)`` (where ``size`` has to be a literal number) promises that they only use memory in either the range ``[0, size)`` or the -unbounded range above ``ptr``. +unbounded range starting at ``ptr``. Since the presence of a ``memoryguard`` call indicates that all memory access adheres to this restriction, it allows the optimizer to perform additional diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 54098cc8f..d820b4aa7 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -142,7 +142,10 @@ string IRGenerator::generate( InternalDispatchMap internalDispatchMap = generateInternalDispatchFunctions(); t("functions", m_context.functionCollector().requestedFunctions()); t("subObjects", subObjectSources(m_context.subObjectsCreated())); - t("memoryInitCreation", memoryInit(!m_context.inlineAssemblySeen())); + + // This has to be called only after all other code generation for the creation object is complete. + bool creationInvolvesAssembly = m_context.inlineAssemblySeen(); + t("memoryInitCreation", memoryInit(!creationInvolvesAssembly)); resetContext(_contract); @@ -158,7 +161,10 @@ string IRGenerator::generate( generateInternalDispatchFunctions(); t("runtimeFunctions", m_context.functionCollector().requestedFunctions()); t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated())); - t("memoryInitRuntime", memoryInit(!m_context.inlineAssemblySeen())); + + // This has to be called only after all other code generation for the runtime object is complete. + bool runtimeInvolvesAssembly = m_context.inlineAssemblySeen(); + t("memoryInitRuntime", memoryInit(!runtimeInvolvesAssembly)); return t.render(); } diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index 1849a9074..85adbd98b 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -35,31 +35,33 @@ using namespace solidity::yul; namespace { -// Walks the call graph using a Depth-First-Search assigning memory offsets to variables. -// - The leaves of the call graph will get the lowest offsets, increasing towards the root. -// - ``nextAvailableSlot`` maps a function to the next available slot that can be used by another -// function that calls it. -// - For each function starting from the root of the call graph: -// - Visit all children that are not already visited. -// - Determine the maximum value ``n`` of the values of ``nextAvailableSlot`` among the children. -// - If the function itself contains variables that need memory slots, but is contained in a cycle, -// abort the process as failure. -// - If not, assign each variable its slot starting from ``n`` (incrementing it). -// - Assign ``n`` to ``nextAvailableSlot`` of the function. +/** + * Walks the call graph using a Depth-First-Search assigning memory slots to variables. + * - The leaves of the call graph will get the lowest slot, increasing towards the root. + * - ``slotsRequiredForFunction`` maps a function to the number of slots it requires (which is also the + * next available slot that can be used by another function that calls this function). + * - For each function starting from the root of the call graph: + * - Visit all children that are not already visited. + * - Determine the maximum value ``n`` of the values of ``slotsRequiredForFunction`` among the children. + * - If the function itself contains variables that need memory slots, but is contained in a cycle, + * abort the process as failure. + * - If not, assign each variable its slot starting from ``n`` (incrementing it). + * - Assign ``n`` to ``slotsRequiredForFunction`` of the function. + */ struct MemoryOffsetAllocator { uint64_t run(YulString _function = YulString{}) { - if (nextAvailableSlot.count(_function)) - return nextAvailableSlot[_function]; + if (slotsRequiredForFunction.count(_function)) + return slotsRequiredForFunction[_function]; // Assign to zero early to guard against recursive calls. - nextAvailableSlot[_function] = 0; + slotsRequiredForFunction[_function] = 0; - uint64_t nextSlot = 0; + uint64_t requiredSlots = 0; if (callGraph.count(_function)) for (YulString child: callGraph.at(_function)) - nextSlot = std::max(run(child), nextSlot); + requiredSlots = std::max(run(child), requiredSlots); if (unreachableVariables.count(_function)) { @@ -71,17 +73,17 @@ struct MemoryOffsetAllocator // TODO: Too many function arguments or return parameters. } else - assignedSlots[variable] = nextSlot++; + assignedSlots[variable] = requiredSlots++; } - return nextAvailableSlot[_function] = nextSlot; + return slotsRequiredForFunction[_function] = requiredSlots; } map> const& unreachableVariables; map> const& callGraph; map> slotAllocations{}; - map nextAvailableSlot{}; + map slotsRequiredForFunction{}; }; u256 literalArgumentValue(FunctionCall const& _call) @@ -116,8 +118,8 @@ void StackLimitEvader::run( // Make sure all calls to ``memoryguard`` we found have the same value as argument (otherwise, abort). u256 reservedMemory = literalArgumentValue(*memoryGuardCalls.front()); - for (FunctionCall const* getFreeMemoryStartCall: memoryGuardCalls) - if (reservedMemory != literalArgumentValue(*getFreeMemoryStartCall)) + for (FunctionCall const* memoryGuardCall: memoryGuardCalls) + if (reservedMemory != literalArgumentValue(*memoryGuardCall)) return; CallGraph callGraph = CallGraphGenerator::callGraph(*_object.code); @@ -130,13 +132,14 @@ void StackLimitEvader::run( MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls}; uint64_t requiredSlots = memoryOffsetAllocator.run(); - StackToMemoryMover{_context, reservedMemory, memoryOffsetAllocator.slotAllocations}(*_object.code); + StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, *_object.code); + + yulAssert(requiredSlots < std::numeric_limits::max() / 32, ""); reservedMemory += 32 * requiredSlots; - YulString reservedMemoryString{util::toCompactHexWithPrefix(reservedMemory)}; - for (FunctionCall* memoryGuardCall: memoryGuardCalls) + for (FunctionCall* memoryGuardCall: FunctionCallFinder::run(*_object.code, "memoryguard"_yulstring)) { Literal* literal = std::get_if(&memoryGuardCall->arguments.front()); yulAssert(literal && literal->kind == LiteralKind::Number, ""); - literal->value = reservedMemoryString; + literal->value = YulString{util::toCompactHexWithPrefix(reservedMemory)}; } } diff --git a/libyul/optimiser/StackToMemoryMover.cpp b/libyul/optimiser/StackToMemoryMover.cpp index 2c1cfa227..19a83c323 100644 --- a/libyul/optimiser/StackToMemoryMover.cpp +++ b/libyul/optimiser/StackToMemoryMover.cpp @@ -29,14 +29,14 @@ using namespace solidity::yul; namespace { -void appendMemoryStore( - vector& _statements, +vector generateMemoryStore( langutil::SourceLocation const& _loc, YulString _mpos, Expression _value ) { - _statements.emplace_back(ExpressionStatement{_loc, FunctionCall{ + vector result; + result.emplace_back(ExpressionStatement{_loc, FunctionCall{ _loc, Identifier{_loc, "mstore"_yulstring}, { @@ -44,9 +44,21 @@ void appendMemoryStore( std::move(_value) } }}); + return result; } } +void StackToMemoryMover::run( + OptimiserStepContext& _context, + u256 _reservedMemory, + map> const& _memorySlots, + Block& _block +) +{ + StackToMemoryMover stackToMemoryMover(_context, _reservedMemory, _memorySlots); + stackToMemoryMover(_block); +} + StackToMemoryMover::StackToMemoryMover( OptimiserStepContext& _context, u256 _reservedMemory, @@ -66,22 +78,20 @@ StackToMemoryMover::StackToMemoryMover( void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition) { - map const* saved = m_currentFunctionMemorySlots; if (m_memorySlots.count(_functionDefinition.name)) { + map const* saved = m_currentFunctionMemorySlots; m_currentFunctionMemorySlots = &m_memorySlots.at(_functionDefinition.name); for (TypedName const& param: _functionDefinition.parameters + _functionDefinition.returnVariables) if (m_currentFunctionMemorySlots->count(param.name)) { // TODO: we cannot handle function parameters yet. - m_currentFunctionMemorySlots = nullptr; - break; + m_currentFunctionMemorySlots = saved; + return; } + ASTModifier::operator()(_functionDefinition); + m_currentFunctionMemorySlots = saved; } - else - m_currentFunctionMemorySlots = nullptr; - ASTModifier::operator()(_functionDefinition); - m_currentFunctionMemorySlots = saved; } void StackToMemoryMover::operator()(Block& _block) @@ -103,16 +113,11 @@ void StackToMemoryMover::operator()(Block& _block) std::unique_ptr _value ) -> std::vector { if (_variables.size() == 1) - { - std::vector result; - appendMemoryStore( - result, + return generateMemoryStore( _loc, memoryOffset(_variables.front().name), _value ? *std::move(_value) : Literal{_loc, LiteralKind::Number, "0"_yulstring, {}} ); - return result; - } VariableDeclaration tempDecl{_loc, {}, std::move(_value)}; vector memoryAssignments; @@ -123,7 +128,7 @@ void StackToMemoryMover::operator()(Block& _block) tempDecl.variables.emplace_back(TypedName{var.location, tempVarName, {}}); if (m_currentFunctionMemorySlots->count(var.name)) - appendMemoryStore(memoryAssignments, _loc, memoryOffset(var.name), Identifier{_loc, tempVarName}); + memoryAssignments += generateMemoryStore(_loc, memoryOffset(var.name), Identifier{_loc, tempVarName}); else if constexpr (std::is_same_v, Identifier>) variableAssignments.emplace_back(Assignment{ _loc, { Identifier{var.location, var.name} }, @@ -186,10 +191,10 @@ void StackToMemoryMover::visit(Expression& _expression) ) { langutil::SourceLocation loc = identifier->location; - _expression = FunctionCall { + _expression = FunctionCall{ loc, Identifier{loc, "mload"_yulstring}, { - Literal { + Literal{ loc, LiteralKind::Number, memoryOffset(identifier->name), diff --git a/libyul/optimiser/StackToMemoryMover.h b/libyul/optimiser/StackToMemoryMover.h index d4ef2f737..fa8701aaa 100644 --- a/libyul/optimiser/StackToMemoryMover.h +++ b/libyul/optimiser/StackToMemoryMover.h @@ -73,23 +73,37 @@ namespace solidity::yul * If a visited function has arguments or return parameters that are contained in the map, * the entire function is skipped (no local variables in the function will be moved at all). * - * Prerequisite: Disambiguator, ForLoopInitRewriter. + * Prerequisite: Disambiguator, ForLoopInitRewriter, FunctionHoister. */ class StackToMemoryMover: ASTModifier { public: - StackToMemoryMover( + /** + * Runs the stack to memory mover. + * @param _reservedMemory Is the amount of previously reserved memory, + * i.e. the lowest memory offset to which variables can be moved. + * @param _memorySlots A map from variables to a slot in memory. The offset to which a variables will be moved + * is given by _reservedMemory plus 32 times its entry in @a _memorySlots. + */ + static void run( OptimiserStepContext& _context, u256 _reservedMemory, - std::map> const& _memoryOffsets + std::map> const& _memorySlots, + Block& _block ); - using ASTModifier::operator(); void operator()(FunctionDefinition& _functionDefinition) override; void operator()(Block& _block) override; void visit(Expression& _expression) override; private: + StackToMemoryMover( + OptimiserStepContext& _context, + u256 _reservedMemory, + std::map> const& _memorySlots + ); + + /// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal. YulString memoryOffset(YulString _variable); u256 m_reservedMemory; std::map> const& m_memorySlots; From 48f620fb50f1205abe5fbaff3da5878891d46b50 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 17 Sep 2020 17:36:18 +0200 Subject: [PATCH 13/21] Reverse the order of the memory offsets assigned to variables. --- libyul/optimiser/StackLimitEvader.cpp | 2 +- libyul/optimiser/StackToMemoryMover.cpp | 16 ++++++-- libyul/optimiser/StackToMemoryMover.h | 11 ++++-- .../fakeStackLimitEvader/connected.yul | 20 +++++----- .../fakeStackLimitEvader/function_arg.yul | 4 +- .../fakeStackLimitEvader/stub.yul | 36 +++++++++--------- .../stackLimitEvader/tree.yul | 38 +++++++++---------- 7 files changed, 70 insertions(+), 57 deletions(-) diff --git a/libyul/optimiser/StackLimitEvader.cpp b/libyul/optimiser/StackLimitEvader.cpp index 85adbd98b..98be932cb 100644 --- a/libyul/optimiser/StackLimitEvader.cpp +++ b/libyul/optimiser/StackLimitEvader.cpp @@ -132,7 +132,7 @@ void StackLimitEvader::run( MemoryOffsetAllocator memoryOffsetAllocator{_unreachableVariables, callGraph.functionCalls}; uint64_t requiredSlots = memoryOffsetAllocator.run(); - StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, *_object.code); + StackToMemoryMover::run(_context, reservedMemory, memoryOffsetAllocator.slotAllocations, requiredSlots, *_object.code); yulAssert(requiredSlots < std::numeric_limits::max() / 32, ""); reservedMemory += 32 * requiredSlots; diff --git a/libyul/optimiser/StackToMemoryMover.cpp b/libyul/optimiser/StackToMemoryMover.cpp index 19a83c323..136a6043d 100644 --- a/libyul/optimiser/StackToMemoryMover.cpp +++ b/libyul/optimiser/StackToMemoryMover.cpp @@ -52,18 +52,24 @@ void StackToMemoryMover::run( OptimiserStepContext& _context, u256 _reservedMemory, map> const& _memorySlots, + uint64_t _numRequiredSlots, Block& _block ) { - StackToMemoryMover stackToMemoryMover(_context, _reservedMemory, _memorySlots); + StackToMemoryMover stackToMemoryMover(_context, _reservedMemory, _memorySlots, _numRequiredSlots); stackToMemoryMover(_block); } StackToMemoryMover::StackToMemoryMover( OptimiserStepContext& _context, u256 _reservedMemory, - map> const& _memorySlots -): m_reservedMemory(std::move(_reservedMemory)), m_memorySlots(_memorySlots), m_nameDispenser(_context.dispenser) + map> const& _memorySlots, + uint64_t _numRequiredSlots +): +m_reservedMemory(std::move(_reservedMemory)), +m_memorySlots(_memorySlots), +m_numRequiredSlots(_numRequiredSlots), +m_nameDispenser(_context.dispenser) { auto const* evmDialect = dynamic_cast(&_context.dialect); yulAssert( @@ -210,6 +216,8 @@ void StackToMemoryMover::visit(Expression& _expression) YulString StackToMemoryMover::memoryOffset(YulString _variable) { yulAssert(m_currentFunctionMemorySlots, ""); - return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * m_currentFunctionMemorySlots->at(_variable))}; + uint64_t slot = m_currentFunctionMemorySlots->at(_variable); + yulAssert(slot < m_numRequiredSlots, ""); + return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))}; } diff --git a/libyul/optimiser/StackToMemoryMover.h b/libyul/optimiser/StackToMemoryMover.h index fa8701aaa..cf582aba6 100644 --- a/libyul/optimiser/StackToMemoryMover.h +++ b/libyul/optimiser/StackToMemoryMover.h @@ -82,13 +82,16 @@ public: * Runs the stack to memory mover. * @param _reservedMemory Is the amount of previously reserved memory, * i.e. the lowest memory offset to which variables can be moved. - * @param _memorySlots A map from variables to a slot in memory. The offset to which a variables will be moved - * is given by _reservedMemory plus 32 times its entry in @a _memorySlots. + * @param _memorySlots A map from variables to a slot in memory. Based on the slot a unique offset in the memory range + * between _reservedMemory and _reservedMemory + 32 * _numRequiredSlots is calculated for each + * variable. + * @param _numRequiredSlots The number of slots required in total. The maximum value that may occur in @a _memorySlots. */ static void run( OptimiserStepContext& _context, u256 _reservedMemory, std::map> const& _memorySlots, + uint64_t _numRequiredSlots, Block& _block ); using ASTModifier::operator(); @@ -100,13 +103,15 @@ private: StackToMemoryMover( OptimiserStepContext& _context, u256 _reservedMemory, - std::map> const& _memorySlots + std::map> const& _memorySlots, + uint64_t _numRequiredSlots ); /// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal. YulString memoryOffset(YulString _variable); u256 m_reservedMemory; std::map> const& m_memorySlots; + uint64_t m_numRequiredSlots = 0; NameDispenser& m_nameDispenser; std::map const* m_currentFunctionMemorySlots = nullptr; }; diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul index 075da88cd..82ab48a51 100644 --- a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/connected.yul @@ -28,35 +28,35 @@ // function g() -> a, b // { // a := 21 -// mstore(0x20, 1) +// mstore(0x60, 1) // let b_1, a_2, $c_3 := z() -// mstore(0x20, $c_3) +// mstore(0x60, $c_3) // a := a_2 // b := b_1 // } // function f() -> x // { -// mstore(0x60, 0) -// mstore(0x60, 42) +// mstore(0x20, 0) +// mstore(0x20, 42) // let $x3_4, $x4_5 := g() -// mstore(0x80, $x4_5) +// mstore(0x00, $x4_5) // mstore(0x40, $x3_4) -// x := mul(add(mload(0x60), mload(0x40)), h(mload(0x80))) -// sstore(mload(0x40), mload(0x80)) +// x := mul(add(mload(0x20), mload(0x40)), h(mload(0x00))) +// sstore(mload(0x40), mload(0x00)) // } // function h(v) -> a_1 // { // let x_2_6, $z_7, y_8 := z() -// mstore(0x20, $z_7) +// mstore(0x60, $z_7) // let y := y_8 // let x_2 := x_2_6 // let a_1_9, $z_10, v_11 := z() -// mstore(0x20, $z_10) +// mstore(0x60, $z_10) // v := v_11 // a_1 := a_1_9 // } // function z() -> a_3, b_4, c -// { mstore(0x00, 0) } +// { mstore(0x80, 0) } // sstore(0, f()) // let x_5, y_6 := g() // } diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul index 033e0b3c0..6ce2e34b4 100644 --- a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/function_arg.yul @@ -12,8 +12,8 @@ // // { // mstore(0x40, memoryguard(0x40)) -// mstore(0x20, 0) -// sstore(0, mload(0x20)) +// mstore(0x00, 0) +// sstore(0, mload(0x00)) // function h($hx) -> y // { y := $hx } // sstore(1, h(32)) diff --git a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul index 9c792e63f..d2fcb116e 100644 --- a/test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul +++ b/test/libyul/yulOptimizerTests/fakeStackLimitEvader/stub.yul @@ -30,50 +30,50 @@ // mstore(0x40, memoryguard(0x80)) // function f() // { -// mstore(0x20, 0) -// mstore(0x00, 42) -// sstore(mload(0x20), mload(0x00)) -// mstore(0x20, 21) +// mstore(0x40, 0) +// mstore(0x60, 42) +// sstore(mload(0x40), mload(0x60)) +// mstore(0x40, 21) // } // function g(gx) // { // let $gx_1, $gy_2 := tuple2() -// mstore(0x20, $gy_2) -// mstore(0x00, $gx_1) +// mstore(0x40, $gy_2) +// mstore(0x60, $gx_1) // { // let $gx_3, $gy_4 := tuple2() -// mstore(0x20, $gy_4) -// mstore(0x00, $gx_3) +// mstore(0x40, $gy_4) +// mstore(0x60, $gx_3) // } // { // let $gx_5, gx_6 := tuple2() -// mstore(0x00, $gx_5) +// mstore(0x60, $gx_5) // gx := gx_6 // } // { // let gx_7, $gy_8 := tuple2() -// mstore(0x20, $gy_8) +// mstore(0x40, $gy_8) // gx := gx_7 // } // } // function h(hx, hy, hz, hw) // { // let $hx_9, $hy_10, $hz_11, $hw_12 := tuple4() -// mstore(0x60, $hw_12) -// mstore(0x00, $hz_11) -// mstore(0x20, $hy_10) -// mstore(0x40, $hx_9) +// mstore(0x00, $hw_12) +// mstore(0x60, $hz_11) +// mstore(0x40, $hy_10) +// mstore(0x20, $hx_9) // { // let hx_13, $hy_14, hz_15, $hw_16 := tuple4() -// mstore(0x60, $hw_16) -// mstore(0x20, $hy_14) +// mstore(0x00, $hw_16) +// mstore(0x40, $hy_14) // hz := hz_15 // hx := hx_13 // } // { // let $hx_17, $hy_18, hz_19, hw_20 := tuple4() -// mstore(0x20, $hy_18) -// mstore(0x40, $hx_17) +// mstore(0x40, $hy_18) +// mstore(0x20, $hx_17) // hw := hw_20 // hz := hz_19 // } diff --git a/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul b/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul index 357f6bb68..8407df5d0 100644 --- a/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul +++ b/test/libyul/yulOptimizerTests/stackLimitEvader/tree.yul @@ -174,7 +174,7 @@ // } // function f() -> v // { -// mstore(0xe0, calldataload(mul(1, 4))) +// mstore(0x80, calldataload(mul(1, 4))) // let a2 := calldataload(mul(2, 4)) // let a3 := calldataload(mul(3, 4)) // let a4 := calldataload(g()) @@ -183,7 +183,7 @@ // let a7 := calldataload(mul(7, 4)) // let a8 := calldataload(mul(8, 4)) // let a9 := calldataload(mul(9, 4)) -// mstore(0xe0, calldataload(mul(0, 4))) +// mstore(0x80, calldataload(mul(0, 4))) // let a10 := calldataload(mul(10, 4)) // let a11 := calldataload(mul(11, 4)) // let a12 := calldataload(mul(12, 4)) @@ -192,7 +192,7 @@ // let a15 := calldataload(mul(15, 4)) // let a16 := calldataload(mul(16, 4)) // let a17 := calldataload(mul(17, 4)) -// sstore(0, mload(0xe0)) +// sstore(0, mload(0x80)) // sstore(mul(17, 4), a17) // sstore(mul(16, 4), a16) // sstore(mul(15, 4), a15) @@ -209,11 +209,11 @@ // sstore(mul(4, 4), a4) // sstore(mul(3, 4), a3) // sstore(mul(2, 4), a2) -// sstore(mul(1, 4), mload(0xe0)) +// sstore(mul(1, 4), mload(0x80)) // } // function g() -> v_1 // { -// mstore(0xa0, calldataload(mul(1, 4))) +// mstore(0xc0, calldataload(mul(1, 4))) // let a2_3 := calldataload(mul(2, 4)) // let a3_4 := calldataload(mul(3, 4)) // let a4_5 := calldataload(mul(4, 4)) @@ -222,7 +222,7 @@ // let a7_8 := calldataload(mul(7, 4)) // let a8_9 := calldataload(mul(8, 4)) // let a9_10 := calldataload(mul(9, 4)) -// mstore(0xa0, calldataload(mul(0, 4))) +// mstore(0xc0, calldataload(mul(0, 4))) // let a10_11 := calldataload(mul(10, 4)) // let a11_12 := calldataload(mul(11, 4)) // let a12_13 := calldataload(mul(12, 4)) @@ -231,7 +231,7 @@ // let a15_16 := calldataload(mul(15, 4)) // let a16_17 := calldataload(mul(16, 4)) // let a17_18 := calldataload(mul(17, 4)) -// sstore(0, mload(0xa0)) +// sstore(0, mload(0xc0)) // sstore(mul(17, 4), a17_18) // sstore(mul(16, 4), a16_17) // sstore(mul(15, 4), a15_16) @@ -248,13 +248,13 @@ // sstore(mul(4, 4), a4_5) // sstore(mul(3, 4), a3_4) // sstore(mul(2, 4), a2_3) -// sstore(mul(1, 4), mload(0xa0)) +// sstore(mul(1, 4), mload(0xc0)) // v_1 := i() // } // function h() -> v_19 // { -// mstore(0xc0, calldataload(mul(1, 4))) -// mstore(0xa0, calldataload(mul(2, 4))) +// mstore(0xa0, calldataload(mul(1, 4))) +// mstore(0xc0, calldataload(mul(2, 4))) // let a3_22 := calldataload(mul(3, 4)) // let a4_23 := calldataload(mul(4, 4)) // let a5_24 := calldataload(mul(5, 4)) @@ -264,8 +264,8 @@ // let a9_28 := calldataload(mul(9, 4)) // let a10_29 := calldataload(mul(10, 4)) // let a11_30 := calldataload(mul(10, 4)) -// mstore(0xc0, calldataload(mul(0, 4))) -// mstore(0xa0, calldataload(mul(1, 4))) +// mstore(0xa0, calldataload(mul(0, 4))) +// mstore(0xc0, calldataload(mul(1, 4))) // let a12_31 := calldataload(mul(12, 4)) // let a13_32 := calldataload(mul(13, 4)) // let a14_33 := calldataload(mul(14, 4)) @@ -274,7 +274,7 @@ // let a17_36 := calldataload(mul(17, 4)) // let a18 := calldataload(mul(18, 4)) // let a19 := calldataload(mul(19, 4)) -// sstore(0, add(mload(0xc0), mload(0xa0))) +// sstore(0, add(mload(0xa0), mload(0xc0))) // sstore(mul(17, 4), a19) // sstore(mul(17, 4), a18) // sstore(mul(17, 4), a17_36) @@ -292,13 +292,13 @@ // sstore(mul(5, 4), a5_24) // sstore(mul(4, 4), a4_23) // sstore(mul(3, 4), a3_22) -// sstore(mul(2, 4), mload(0xa0)) -// sstore(mul(1, 4), mload(0xc0)) +// sstore(mul(2, 4), mload(0xc0)) +// sstore(mul(1, 4), mload(0xa0)) // v_19 := i() // } // function i() -> v_37 // { -// mstore(0x80, calldataload(mul(1, 4))) +// mstore(0xe0, calldataload(mul(1, 4))) // let a2_39 := calldataload(mul(2, 4)) // let a3_40 := calldataload(mul(3, 4)) // let a4_41 := calldataload(mul(4, 4)) @@ -307,7 +307,7 @@ // let a7_44 := calldataload(mul(7, 4)) // let a8_45 := calldataload(mul(8, 4)) // let a9_46 := calldataload(mul(9, 4)) -// mstore(0x80, calldataload(mul(0, 4))) +// mstore(0xe0, calldataload(mul(0, 4))) // let a10_47 := calldataload(mul(10, 4)) // let a11_48 := calldataload(mul(11, 4)) // let a12_49 := calldataload(mul(12, 4)) @@ -316,7 +316,7 @@ // let a15_52 := calldataload(mul(15, 4)) // let a16_53 := calldataload(mul(16, 4)) // let a17_54 := calldataload(mul(17, 4)) -// sstore(0, mload(0x80)) +// sstore(0, mload(0xe0)) // sstore(mul(17, 4), a17_54) // sstore(mul(16, 4), a16_53) // sstore(mul(15, 4), a15_52) @@ -333,7 +333,7 @@ // sstore(mul(4, 4), a4_41) // sstore(mul(3, 4), a3_40) // sstore(mul(2, 4), a2_39) -// sstore(mul(1, 4), mload(0x80)) +// sstore(mul(1, 4), mload(0xe0)) // v_37 := sload(mul(42, 8)) // } // } From f3c2d6cfdc0043b8fd2de51a53e707ae968e0e8c Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Thu, 17 Sep 2020 18:00:08 +0200 Subject: [PATCH 14/21] Move memory load and store functions to the dialect. --- libyul/Dialect.h | 3 +++ libyul/backends/evm/EVMDialect.h | 2 ++ libyul/optimiser/StackToMemoryMover.cpp | 18 +++++++++++++++--- libyul/optimiser/StackToMemoryMover.h | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/libyul/Dialect.h b/libyul/Dialect.h index d47c09659..78b950d58 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -72,6 +72,9 @@ struct Dialect: boost::noncopyable virtual BuiltinFunction const* equalityFunction(YulString /* _type */) const { return nullptr; } virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; } + virtual BuiltinFunction const* memoryStoreFunction(YulString /* _type */) const { return nullptr; } + virtual BuiltinFunction const* memoryLoadFunction(YulString /* _type */) const { return nullptr; } + /// Check whether the given type is legal for the given literal value. /// Should only be called if the type exists in the dialect at all. virtual bool validTypeForLiteral(LiteralKind _kind, YulString _value, YulString _type) const; diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index fe1c67a4a..8cac1e0be 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -73,6 +73,8 @@ struct EVMDialect: public Dialect BuiltinFunctionForEVM const* discardFunction(YulString /*_type*/) const override { return builtin("pop"_yulstring); } BuiltinFunctionForEVM const* equalityFunction(YulString /*_type*/) const override { return builtin("eq"_yulstring); } BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); } + BuiltinFunctionForEVM const* memoryStoreFunction(YulString /*_type*/) const override { return builtin("mstore"_yulstring); } + BuiltinFunctionForEVM const* memoryLoadFunction(YulString /*_type*/) const override { return builtin("mload"_yulstring); } static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _version); diff --git a/libyul/optimiser/StackToMemoryMover.cpp b/libyul/optimiser/StackToMemoryMover.cpp index 136a6043d..385d508f2 100644 --- a/libyul/optimiser/StackToMemoryMover.cpp +++ b/libyul/optimiser/StackToMemoryMover.cpp @@ -30,15 +30,18 @@ using namespace solidity::yul; namespace { vector generateMemoryStore( + Dialect const& _dialect, langutil::SourceLocation const& _loc, YulString _mpos, Expression _value ) { + BuiltinFunction const* memoryStoreFunction = _dialect.memoryStoreFunction(_dialect.defaultType); + yulAssert(memoryStoreFunction, ""); vector result; result.emplace_back(ExpressionStatement{_loc, FunctionCall{ _loc, - Identifier{_loc, "mstore"_yulstring}, + Identifier{_loc, memoryStoreFunction->name}, { Literal{_loc, LiteralKind::Number, _mpos, {}}, std::move(_value) @@ -66,6 +69,7 @@ StackToMemoryMover::StackToMemoryMover( map> const& _memorySlots, uint64_t _numRequiredSlots ): +m_context(_context), m_reservedMemory(std::move(_reservedMemory)), m_memorySlots(_memorySlots), m_numRequiredSlots(_numRequiredSlots), @@ -120,6 +124,7 @@ void StackToMemoryMover::operator()(Block& _block) ) -> std::vector { if (_variables.size() == 1) return generateMemoryStore( + m_context.dialect, _loc, memoryOffset(_variables.front().name), _value ? *std::move(_value) : Literal{_loc, LiteralKind::Number, "0"_yulstring, {}} @@ -134,7 +139,12 @@ void StackToMemoryMover::operator()(Block& _block) tempDecl.variables.emplace_back(TypedName{var.location, tempVarName, {}}); if (m_currentFunctionMemorySlots->count(var.name)) - memoryAssignments += generateMemoryStore(_loc, memoryOffset(var.name), Identifier{_loc, tempVarName}); + memoryAssignments += generateMemoryStore( + m_context.dialect, + _loc, + memoryOffset(var.name), + Identifier{_loc, tempVarName} + ); else if constexpr (std::is_same_v, Identifier>) variableAssignments.emplace_back(Assignment{ _loc, { Identifier{var.location, var.name} }, @@ -196,10 +206,12 @@ void StackToMemoryMover::visit(Expression& _expression) identifier && m_currentFunctionMemorySlots && m_currentFunctionMemorySlots->count(identifier->name) ) { + BuiltinFunction const* memoryLoadFunction = m_context.dialect.memoryLoadFunction(m_context.dialect.defaultType); + yulAssert(memoryLoadFunction, ""); langutil::SourceLocation loc = identifier->location; _expression = FunctionCall{ loc, - Identifier{loc, "mload"_yulstring}, { + Identifier{loc, memoryLoadFunction->name}, { Literal{ loc, LiteralKind::Number, diff --git a/libyul/optimiser/StackToMemoryMover.h b/libyul/optimiser/StackToMemoryMover.h index cf582aba6..ea38ea119 100644 --- a/libyul/optimiser/StackToMemoryMover.h +++ b/libyul/optimiser/StackToMemoryMover.h @@ -109,6 +109,8 @@ private: /// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal. YulString memoryOffset(YulString _variable); + + OptimiserStepContext& m_context; u256 m_reservedMemory; std::map> const& m_memorySlots; uint64_t m_numRequiredSlots = 0; From 71af44b27b97646395e440e6c3dca307c29e6201 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 21 Sep 2020 11:49:25 +0200 Subject: [PATCH 15/21] Add note about extcodesize check. --- docs/control-structures.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 3487cb0ce..f06041f79 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -105,8 +105,12 @@ otherwise, the ``value`` option would not be available. parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost. -Function calls cause exceptions if the called contract does not exist (in the -sense that the account does not contain code) or if the called contract itself +Due to the fact that the EVM considers a call to a non-existing contract to +always succeed, Solidity uses the ``extcodesize`` opcode to check that +the contract that is about to be called actually exists (it contains code) +and causes an exception if it does not. + +Function calls also cause exceptions if the called contract itself throws an exception or goes out of gas. .. warning:: From 8979174f02a662cfae3f77cabaf5415938faa575 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 21 Sep 2020 12:23:41 +0200 Subject: [PATCH 16/21] Re-add the release script still used by appveyor. --- scripts/release.bat | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 scripts/release.bat diff --git a/scripts/release.bat b/scripts/release.bat new file mode 100644 index 000000000..caa56fc9a --- /dev/null +++ b/scripts/release.bat @@ -0,0 +1,38 @@ +@ECHO OFF + +REM --------------------------------------------------------------------------- +REM Batch file for implementing release flow for solidity for Windows. +REM +REM The documentation for solidity is hosted at: +REM +REM https://solidity.readthedocs.org +REM +REM --------------------------------------------------------------------------- +REM This file is part of solidity. +REM +REM solidity is free software: you can redistribute it and/or modify +REM it under the terms of the GNU General Public License as published by +REM the Free Software Foundation, either version 3 of the License, or +REM (at your option) any later version. +REM +REM solidity is distributed in the hope that it will be useful, +REM but WITHOUT ANY WARRANTY; without even the implied warranty of +REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +REM GNU General Public License for more details. +REM +REM You should have received a copy of the GNU General Public License +REM along with solidity. If not, see +REM +REM Copyright (c) 2016 solidity contributors. +REM --------------------------------------------------------------------------- + +set CONFIGURATION=%1 +set VERSION=%2 + +set "DLLS=MSVC_DLLS_NOT_FOUND" +FOR /d %%d IN ("C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Redist\MSVC\*" + "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\*") DO set "DLLS=%%d\x86\Microsoft.VC141.CRT\msvc*.dll" + +7z a solidity-windows.zip ^ + .\build\solc\%CONFIGURATION%\solc.exe .\build\test\%CONFIGURATION%\soltest.exe ^ + "%DLLS%" From 6b77a20134010f7e07fb475af8b021ae3b791696 Mon Sep 17 00:00:00 2001 From: a3d4 Date: Wed, 16 Sep 2020 01:06:54 +0200 Subject: [PATCH 17/21] Introduce CompositeType --- libsolidity/analysis/ContractLevelChecker.cpp | 3 +- libsolidity/analysis/StaticAnalyzer.cpp | 21 +++-- libsolidity/ast/Types.cpp | 84 +++++-------------- libsolidity/ast/Types.h | 53 ++++++++++-- .../iceRegressionTests/oversized_var.sol | 2 + .../largeTypes/large_storage_structs.sol | 10 ++- .../largeTypes/oversized_array.sol | 2 +- .../largeTypes/oversized_contract.sol | 2 +- .../oversized_contract_inheritance.sol | 2 +- .../largeTypes/oversized_struct.sol | 2 +- .../largeTypes/storage_parameter.sol | 1 + 11 files changed, 97 insertions(+), 85 deletions(-) diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index ff6b30e79..e8804d359 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -465,7 +465,6 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract) { bigint size = 0; - vector variables; for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (VariableDeclaration const* variable: contract->stateVariables()) if (!(variable->isConstant() || variable->immutable())) @@ -473,7 +472,7 @@ void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract) size += variable->annotation().type->storageSizeUpperBound(); if (size >= bigint(1) << 256) { - m_errorReporter.typeError(7676_error, _contract.location(), "Contract too large for storage."); + m_errorReporter.typeError(7676_error, _contract.location(), "Contract requires too much storage."); break; } } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index fefea474a..c9be0a3d3 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -158,17 +158,16 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable) } if (_variable.isStateVariable() || _variable.referenceLocation() == VariableDeclaration::Location::Storage) - { - TypePointer varType = _variable.annotation().type; - for (Type const* subtype: frontend::oversizedSubtypes(*varType)) - { - string message = "Type " + subtype->toString(true) + - " covers a large part of storage and thus makes collisions likely." - " Either use mappings or dynamic arrays and allow their size to be increased only" - " in small quantities per transaction."; - m_errorReporter.warning(7325_error, _variable.typeName().location(), message); - } - } + if (auto varType = dynamic_cast(_variable.annotation().type)) + for (Type const* type: varType->fullDecomposition()) + if (type->storageSizeUpperBound() >= (bigint(1) << 64)) + { + string message = "Type " + type->toString(true) + + " covers a large part of storage and thus makes collisions likely." + " Either use mappings or dynamic arrays and allow their size to be increased only" + " in small quantities per transaction."; + m_errorReporter.warning(7325_error, _variable.typeName().location(), message); + } return true; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index b0418dee5..af52f63d9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -44,6 +44,7 @@ #include #include +#include #include using namespace std; @@ -54,57 +55,6 @@ using namespace solidity::frontend; namespace { -struct TypeComp -{ - bool operator()(Type const* lhs, Type const* rhs) const - { - solAssert(lhs && rhs, ""); - return lhs->richIdentifier() < rhs->richIdentifier(); - } -}; -using TypeSet = std::set; - -void oversizedSubtypesInner( - Type const& _type, - bool _includeType, - set& _structsSeen, - TypeSet& _oversizedSubtypes -) -{ - switch (_type.category()) - { - case Type::Category::Array: - { - auto const& t = dynamic_cast(_type); - if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64) - _oversizedSubtypes.insert(&t); - oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes); - break; - } - case Type::Category::Struct: - { - auto const& t = dynamic_cast(_type); - if (_structsSeen.count(&t.structDefinition())) - return; - if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64) - _oversizedSubtypes.insert(&t); - _structsSeen.insert(&t.structDefinition()); - for (auto const& m: t.members(nullptr)) - oversizedSubtypesInner(*m.type, false, _structsSeen, _oversizedSubtypes); - _structsSeen.erase(&t.structDefinition()); - break; - } - case Type::Category::Mapping: - { - auto const* valueType = dynamic_cast(_type).valueType(); - oversizedSubtypesInner(*valueType, true, _structsSeen, _oversizedSubtypes); - break; - } - default: - break; - } -} - /// Check whether (_base ** _exp) fits into 4096 bits. bool fitsPrecisionExp(bigint const& _base, bigint const& _exp) { @@ -201,16 +151,6 @@ util::Result transformParametersToExternal(TypePointers const& _pa } -vector solidity::frontend::oversizedSubtypes(frontend::Type const& _type) -{ - set structsSeen; - TypeSet oversized; - oversizedSubtypesInner(_type, true, structsSeen, oversized); - vector res; - copy(oversized.cbegin(), oversized.cend(), back_inserter(res)); - return res; -} - void Type::clearCache() const { m_members.clear(); @@ -1612,6 +1552,21 @@ TypeResult ContractType::unaryOperatorResult(Token _operator) const return nullptr; } +vector CompositeType::fullDecomposition() const +{ + vector res = {this}; + unordered_set seen = {richIdentifier()}; + for (size_t k = 0; k < res.size(); ++k) + if (auto composite = dynamic_cast(res[k])) + for (Type const* next: composite->decomposition()) + if (seen.count(next->richIdentifier()) == 0) + { + seen.insert(next->richIdentifier()); + res.push_back(next); + } + return res; +} + Type const* ReferenceType::withLocation(DataLocation _location, bool _isPointer) const { return TypeProvider::withLocation(this, _location, _isPointer); @@ -2649,6 +2604,13 @@ vector> StructType::makeStackItems() const solAssert(false, ""); } +vector StructType::decomposition() const +{ + vector res; + for (MemberList::Member const& member: members(nullptr)) + res.push_back(member.type); + return res; +} TypePointer EnumType::encodingType() const { diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 9b32475ca..f70398a9f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -60,8 +60,6 @@ using BoolResult = util::Result; namespace solidity::frontend { -std::vector oversizedSubtypes(frontend::Type const& _type); - inline rational makeRational(bigint const& _numerator, bigint const& _denominator) { solAssert(_denominator != 0, "division by zero"); @@ -694,11 +692,37 @@ public: TypeResult interfaceType(bool) const override { return this; } }; +/** + * Base class for types which can be thought of as several elements of other types put together. + * For example a struct is composed of its members, an array is composed of multiple copies of its + * base element and a mapping is composed of its value type elements (note that keys are not + * stored anywhere). + */ +class CompositeType: public Type +{ +protected: + CompositeType() = default; + +public: + /// @returns a list containing the type itself, elements of its decomposition, + /// elements of decomposition of these elements and so on, up to non-composite types. + /// Each type is included only once. + std::vector fullDecomposition() const; + +protected: + /// @returns a list of types that together make up the data part of this type. + /// Contains all types that will have to be implicitly stored, whenever an object of this type is stored. + /// In particular, it returns the base type for arrays and array slices, the member types for structs, + /// the component types for tuples and the value type for mappings + /// (note that the key type of a mapping is *not* part of the list). + virtual std::vector decomposition() const = 0; +}; + /** * Base class used by types which are not value types and can be stored either in storage, memory * or calldata. This is currently used by arrays and structs. */ -class ReferenceType: public Type +class ReferenceType: public CompositeType { protected: explicit ReferenceType(DataLocation _location): m_location(_location) {} @@ -829,6 +853,8 @@ public: protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override { return {m_baseType}; } + private: /// String is interpreted as a subtype of Bytes. enum class ArrayKind { Ordinary, Bytes, String }; @@ -869,6 +895,8 @@ public: protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override { return {m_arrayType.baseType()}; } + private: ArrayType const& m_arrayType; }; @@ -994,6 +1022,8 @@ public: protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override; + private: StructDefinition const& m_struct; // Caches for interfaceType(bool) @@ -1044,7 +1074,7 @@ private: * Type that can hold a finite sequence of values of different types. * In some cases, the components are empty pointers (when used as placeholders). */ -class TupleType: public Type +class TupleType: public CompositeType { public: explicit TupleType(std::vector _types = {}): m_components(std::move(_types)) {} @@ -1067,6 +1097,16 @@ public: protected: std::vector> makeStackItems() const override; + std::vector decomposition() const override + { + // Currently calling TupleType::decomposition() is not expected, because we cannot declare a variable of a tuple type. + // If that changes, before removing the solAssert, make sure the function does the right thing and is used properly. + // Note that different tuple members can have different data locations, so using decomposition() to check + // the tuple validity for a data location might require special care. + solUnimplemented("Tuple decomposition is not expected."); + return m_components; + } + private: std::vector const m_components; }; @@ -1349,7 +1389,7 @@ private: * The type of a mapping, there is one distinct type per key/value type pair. * Mappings always occupy their own storage slot, but do not actually use it. */ -class MappingType: public Type +class MappingType: public CompositeType { public: MappingType(Type const* _keyType, Type const* _valueType): @@ -1373,6 +1413,9 @@ public: Type const* keyType() const { return m_keyType; } Type const* valueType() const { return m_valueType; } +protected: + std::vector decomposition() const override { return {m_valueType}; } + private: TypePointer m_keyType; TypePointer m_valueType; diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol b/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol index a6894c9a3..761e60a1a 100644 --- a/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol +++ b/test/libsolidity/syntaxTests/iceRegressionTests/oversized_var.sol @@ -13,6 +13,8 @@ contract b { } // ---- // Warning 7325: (66-67): Type struct b.c covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (66-67): Type uint256[14474011154664524427946373126085988481658748083205070504932198000989141204992] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (111-112): Type struct b.c covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (111-112): Type uint256[14474011154664524427946373126085988481658748083205070504932198000989141204992] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (152-169): Type function ()[984770902183611232881] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 2072: (152-180): Unused local variable. diff --git a/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol b/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol index 9b14dea3d..cbc912a35 100644 --- a/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol +++ b/test/libsolidity/syntaxTests/largeTypes/large_storage_structs.sol @@ -56,13 +56,19 @@ contract C { } // ---- // Warning 7325: (106-108): Type struct C.S0 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (106-108): Type struct C.P[101] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (171-173): Type struct C.S1 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (171-173): Type struct C.P[102] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (341-343): Type struct C.P[103] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (341-343): Type struct C.P[104] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. -// Warning 7325: (505-507): Type uint256[100000000000000000002] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. -// Warning 7325: (505-507): Type uint256[100000000000000000004] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (505-507): Type struct C.Q0 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[1][][100000000000000000001] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[][100000000000000000003] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[100000000000000000004] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (505-507): Type uint256[100000000000000000002] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (576-578): Type struct C.Q1 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (576-578): Type uint256[1][][100000000000000000005] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (647-649): Type uint256[100000000000000000006] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (715-717): Type struct C.Q3 covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (715-717): Type uint256[][100000000000000000007] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 7325: (783-785): Type uint256[100000000000000000008] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol index 123773403..4bf6c86be 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol @@ -4,5 +4,5 @@ contract C { uint[2**255][2] a; } // ---- -// TypeError 7676: (60-97): Contract too large for storage. +// TypeError 7676: (60-97): Contract requires too much storage. // TypeError 1534: (77-94): Type too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol index 83eacfed0..f6c82a823 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol @@ -5,4 +5,4 @@ contract C { uint[2**255] b; } // ---- -// TypeError 7676: (60-114): Contract too large for storage. +// TypeError 7676: (60-114): Contract requires too much storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol index 72f3035fe..a181e3a58 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol @@ -7,4 +7,4 @@ contract D is C { uint[2**255] b; } // ---- -// TypeError 7676: (95-134): Contract too large for storage. +// TypeError 7676: (95-134): Contract requires too much storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol index 757ec9e4a..d1c818756 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol @@ -8,5 +8,5 @@ contract C { S s; } // ---- -// TypeError 7676: (60-152): Contract too large for storage. +// TypeError 7676: (60-152): Contract requires too much storage. // TypeError 1534: (146-149): Type too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol b/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol index ea1dd97b8..836a49b03 100644 --- a/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol +++ b/test/libsolidity/syntaxTests/largeTypes/storage_parameter.sol @@ -4,3 +4,4 @@ contract C { } // ---- // Warning 7325: (64-65): Type struct C.S covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 7325: (64-65): Type uint256[57896044618658097711785492504343953926634992332820282019728792003956564819968] covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. From b3953e39db3e1516c85e15e0887c09f983fb638f Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Wed, 2 Sep 2020 13:19:58 -0500 Subject: [PATCH 18/21] Update ReleaseChecklist.md: Add Homebrew formula bump description. --- ReleaseChecklist.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md index 069098f20..2071058f0 100644 --- a/ReleaseChecklist.md +++ b/ReleaseChecklist.md @@ -30,6 +30,7 @@ - [ ] Take the tarball from the upload directory (its name should be ``solidity_x.x.x.tar.gz``, otherwise ``prerelease.txt`` was missing in the step before) and upload the source tarball to the release page. ### Homebrew and MacOS + - [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/Homebrew/homebrew-core/blob/master/Formula/solidity.rb - [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb - [ ] Take the binary from the ``b_osx`` run of the released commit in circle-ci and add it to the release page as ``solc-macos``. From dd81d0555954678119d3920861645bb310f193ad Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Thu, 10 Sep 2020 12:01:23 +0200 Subject: [PATCH 19/21] Make annotations ``SetOnce`` or ``optional`` where feasible --- libsolidity/analysis/ContractLevelChecker.cpp | 13 ++- libsolidity/analysis/ImmutableValidator.cpp | 2 +- libsolidity/analysis/NameAndTypeResolver.cpp | 2 +- libsolidity/analysis/StaticAnalyzer.cpp | 6 +- libsolidity/analysis/TypeChecker.cpp | 107 ++++++++++++------ libsolidity/ast/AST.cpp | 2 +- libsolidity/ast/ASTAnnotations.h | 19 ++-- libsolidity/ast/ASTJsonConverter.cpp | 100 +++++++++++----- libsolidity/ast/Types.cpp | 16 +-- libsolidity/codegen/ExpressionCompiler.cpp | 4 +- libsolidity/interface/CompilerStack.cpp | 8 +- libsolutil/SetOnce.h | 2 + .../ASTJSON/assembly/nested_functions.json | 1 - .../assembly/nested_functions_legacy.json | 1 - test/libsolidity/ASTJSON/assembly/switch.json | 1 - .../ASTJSON/assembly/switch_legacy.json | 1 - .../SolidityNameAndTypeResolution.cpp | 12 +- tools/solidityUpgrade/Upgrade060.cpp | 2 +- 18 files changed, 194 insertions(+), 105 deletions(-) diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index e8804d359..d0ca9e2a4 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -51,6 +51,8 @@ bool hasEqualNameAndParameters(T const& _a, B const& _b) bool ContractLevelChecker::check(ContractDefinition const& _contract) { + _contract.annotation().unimplementedDeclarations = std::vector(); + checkDuplicateFunctions(_contract); checkDuplicateEvents(_contract); m_overrideChecker.check(_contract); @@ -210,9 +212,10 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c // Set to not fully implemented if at least one flag is false. // Note that `_contract.annotation().unimplementedDeclarations` has already been // pre-filled by `checkBaseConstructorArguments`. + // for (auto const& proxy: proxies) if (proxy.unimplemented()) - _contract.annotation().unimplementedDeclarations.push_back(proxy.declaration()); + _contract.annotation().unimplementedDeclarations->push_back(proxy.declaration()); if (_contract.abstract()) { @@ -229,17 +232,17 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c if ( _contract.contractKind() == ContractKind::Contract && !_contract.abstract() && - !_contract.annotation().unimplementedDeclarations.empty() + !_contract.annotation().unimplementedDeclarations->empty() ) { SecondarySourceLocation ssl; - for (auto declaration: _contract.annotation().unimplementedDeclarations) + for (auto declaration: *_contract.annotation().unimplementedDeclarations) ssl.append("Missing implementation: ", declaration->location()); m_errorReporter.typeError( 3656_error, _contract.location(), ssl, - "Contract \"" + _contract.annotation().canonicalName + "\" should be marked as abstract." + "Contract \"" + *_contract.annotation().canonicalName + "\" should be marked as abstract." ); } } @@ -289,7 +292,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons if (FunctionDefinition const* constructor = contract->constructor()) if (contract != &_contract && !constructor->parameters().empty()) if (!_contract.annotation().baseConstructorArguments.count(constructor)) - _contract.annotation().unimplementedDeclarations.push_back(constructor); + _contract.annotation().unimplementedDeclarations->push_back(constructor); } void ContractLevelChecker::annotateBaseConstructorArguments( diff --git a/libsolidity/analysis/ImmutableValidator.cpp b/libsolidity/analysis/ImmutableValidator.cpp index 93f676c6f..2cf34ae6d 100644 --- a/libsolidity/analysis/ImmutableValidator.cpp +++ b/libsolidity/analysis/ImmutableValidator.cpp @@ -167,7 +167,7 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va // If this is not an ordinary assignment, we write and read at the same time. bool write = _expression.annotation().willBeWrittenTo; - bool read = !_expression.annotation().willBeWrittenTo || !_expression.annotation().lValueOfOrdinaryAssignment; + bool read = !_expression.annotation().willBeWrittenTo || !*_expression.annotation().lValueOfOrdinaryAssignment; if (write) { if (!m_currentConstructor) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 990382aee..49f8b6d7c 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -74,7 +74,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map(node.get())) { - string const& path = imp->annotation().absolutePath; + string const& path = *imp->annotation().absolutePath; if (!_sourceUnits.count(path)) { m_errorReporter.declarationError( diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index c9be0a3d3..abfeae23d 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -185,7 +185,7 @@ bool StaticAnalyzer::visit(Return const& _return) bool StaticAnalyzer::visit(ExpressionStatement const& _statement) { - if (_statement.expression().annotation().isPure) + if (*_statement.expression().annotation().isPure) m_errorReporter.warning( 6133_error, _statement.location(), @@ -287,7 +287,7 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly) bool StaticAnalyzer::visit(BinaryOperation const& _operation) { if ( - _operation.rightExpression().annotation().isPure && + *_operation.rightExpression().annotation().isPure && (_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod) ) if (auto rhs = dynamic_cast( @@ -312,7 +312,7 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall) if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod) { solAssert(_functionCall.arguments().size() == 3, ""); - if (_functionCall.arguments()[2]->annotation().isPure) + if (*_functionCall.arguments()[2]->annotation().isPure) if (auto lastArg = dynamic_cast( ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2]) )) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index e280fd85b..6f351faba 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -538,7 +538,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (!_variable.value()) m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable."); - else if (!_variable.value()->annotation().isPure) + else if (!*_variable.value()->annotation().isPure) m_errorReporter.typeError( 8349_error, _variable.value()->location(), @@ -1296,11 +1296,14 @@ bool TypeChecker::visit(Conditional const& _conditional) } } + _conditional.annotation().isConstant = false; _conditional.annotation().type = commonType; _conditional.annotation().isPure = - _conditional.condition().annotation().isPure && - _conditional.trueExpression().annotation().isPure && - _conditional.falseExpression().annotation().isPure; + *_conditional.condition().annotation().isPure && + *_conditional.trueExpression().annotation().isPure && + *_conditional.falseExpression().annotation().isPure; + + _conditional.annotation().isLValue = false; if (_conditional.annotation().willBeWrittenTo) m_errorReporter.typeError( @@ -1354,6 +1357,9 @@ bool TypeChecker::visit(Assignment const& _assignment) ); TypePointer t = type(_assignment.leftHandSide()); _assignment.annotation().type = t; + _assignment.annotation().isPure = false; + _assignment.annotation().isLValue = false; + _assignment.annotation().isConstant = false; checkExpressionAssignment(*t, _assignment.leftHandSide()); @@ -1401,6 +1407,7 @@ bool TypeChecker::visit(Assignment const& _assignment) bool TypeChecker::visit(TupleExpression const& _tuple) { + _tuple.annotation().isConstant = false; vector> const& components = _tuple.components(); TypePointers types; @@ -1413,7 +1420,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) { requireLValue( *component, - _tuple.annotation().lValueOfOrdinaryAssignment + *_tuple.annotation().lValueOfOrdinaryAssignment ); types.push_back(type(*component)); } @@ -1425,6 +1432,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) _tuple.annotation().type = TypeProvider::tuple(move(types)); // If some of the components are not LValues, the error is reported above. _tuple.annotation().isLValue = true; + _tuple.annotation().isPure = false; } else { @@ -1464,7 +1472,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) else if (inlineArrayType) inlineArrayType = Type::commonType(inlineArrayType, types[i]); } - if (!components[i]->annotation().isPure) + if (!*components[i]->annotation().isPure) isPure = false; } _tuple.annotation().isPure = isPure; @@ -1495,6 +1503,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) _tuple.annotation().type = TypeProvider::tuple(move(types)); } + _tuple.annotation().isLValue = false; } return false; } @@ -1522,7 +1531,9 @@ bool TypeChecker::visit(UnaryOperation const& _operation) t = subExprType; } _operation.annotation().type = t; - _operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure; + _operation.annotation().isConstant = false; + _operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure; + _operation.annotation().isLValue = false; return false; } @@ -1553,8 +1564,10 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) TypeProvider::boolean() : commonType; _operation.annotation().isPure = - _operation.leftExpression().annotation().isPure && - _operation.rightExpression().annotation().isPure; + *_operation.leftExpression().annotation().isPure && + *_operation.rightExpression().annotation().isPure; + _operation.annotation().isLValue = false; + _operation.annotation().isConstant = false; if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL) { @@ -2174,7 +2187,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) for (ASTPointer const& argument: arguments) { argument->accept(*this); - if (!argument->annotation().isPure) + if (!*argument->annotation().isPure) argumentsArePure = false; } @@ -2197,6 +2210,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) // Determine function call kind and function type for this FunctionCall node FunctionCallAnnotation& funcCallAnno = _functionCall.annotation(); FunctionTypePointer functionType = nullptr; + funcCallAnno.isConstant = false; + + bool isLValue = false; // Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node switch (expressionType->category()) @@ -2208,7 +2224,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) // Purity for function calls also depends upon the callee and its FunctionType funcCallAnno.isPure = argumentsArePure && - _functionCall.expression().annotation().isPure && + *_functionCall.expression().annotation().isPure && functionType && functionType->isPure(); @@ -2216,7 +2232,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) functionType->kind() == FunctionType::Kind::ArrayPush || functionType->kind() == FunctionType::Kind::ByteArrayPush ) - funcCallAnno.isLValue = functionType->parameterTypes().empty(); + isLValue = functionType->parameterTypes().empty(); break; @@ -2236,13 +2252,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) ); functionType = dynamic_cast(*actualType).constructorType(); funcCallAnno.kind = FunctionCallKind::StructConstructorCall; - funcCallAnno.isPure = argumentsArePure; } else - { funcCallAnno.kind = FunctionCallKind::TypeConversion; - funcCallAnno.isPure = argumentsArePure; - } + + funcCallAnno.isPure = argumentsArePure; break; } @@ -2255,6 +2269,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) break; } + funcCallAnno.isLValue = isLValue; + // Determine return types switch (*funcCallAnno.kind) { @@ -2325,6 +2341,9 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) _functionCallOptions.expression().accept(*this); + _functionCallOptions.annotation().isPure = false; + _functionCallOptions.annotation().isConstant = false; + auto expressionFunctionType = dynamic_cast(type(_functionCallOptions.expression())); if (!expressionFunctionType) { @@ -2455,6 +2474,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) TypePointer type = _newExpression.typeName().annotation().type; solAssert(!!type, "Type name not resolved."); + _newExpression.annotation().isConstant = false; + if (auto contractName = dynamic_cast(&_newExpression.typeName())) { auto contract = dynamic_cast(&dereference(*contractName)); @@ -2485,6 +2506,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) } _newExpression.annotation().type = FunctionType::newExpressionType(*contract); + _newExpression.annotation().isPure = false; } else if (type->category() == Type::Category::Array) { @@ -2541,6 +2563,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) auto& annotation = _memberAccess.annotation(); + annotation.isConstant = false; + if (possibleMembers.empty()) { if (initialMemberCount == 0 && !dynamic_cast(exprType)) @@ -2673,11 +2697,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) functionType && functionType->kind() == FunctionType::Kind::Declaration ) - annotation.isPure = _memberAccess.expression().annotation().isPure; + annotation.isPure = *_memberAccess.expression().annotation().isPure; } } else if (exprType->category() == Type::Category::Module) - annotation.isPure = _memberAccess.expression().annotation().isPure; + { + annotation.isPure = *_memberAccess.expression().annotation().isPure; + annotation.isLValue = false; + } + else + annotation.isLValue = false; // TODO some members might be pure, but for example `address(0x123).balance` is not pure // although every subexpression is, so leaving this limited for now. @@ -2693,11 +2722,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ) if (auto const* parentAccess = dynamic_cast(&_memberAccess.expression())) { - annotation.isPure = parentAccess->expression().annotation().isPure; + bool isPure = *parentAccess->expression().annotation().isPure; if (auto const* exprInt = dynamic_cast(&parentAccess->expression())) if (exprInt->name() == "this" || exprInt->name() == "super") - annotation.isPure = true; + isPure = true; + + annotation.isPure = isPure; } + if (auto magicType = dynamic_cast(exprType)) { if (magicType->kind() == MagicType::Kind::ABI) @@ -2745,16 +2777,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) annotation.isPure = true; } + if (!annotation.isPure.set()) + annotation.isPure = false; + return false; } bool TypeChecker::visit(IndexAccess const& _access) { + _access.annotation().isConstant = false; _access.baseExpression().accept(*this); TypePointer baseType = type(_access.baseExpression()); TypePointer resultType = nullptr; bool isLValue = false; - bool isPure = _access.baseExpression().annotation().isPure; + bool isPure = *_access.baseExpression().annotation().isPure; Expression const* index = _access.indexExpression(); switch (baseType->category()) { @@ -2856,7 +2892,7 @@ bool TypeChecker::visit(IndexAccess const& _access) } _access.annotation().type = resultType; _access.annotation().isLValue = isLValue; - if (index && !index->annotation().isPure) + if (index && !*index->annotation().isPure) isPure = false; _access.annotation().isPure = isPure; @@ -2865,21 +2901,22 @@ bool TypeChecker::visit(IndexAccess const& _access) bool TypeChecker::visit(IndexRangeAccess const& _access) { + _access.annotation().isConstant = false; _access.baseExpression().accept(*this); bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays - bool isPure = _access.baseExpression().annotation().isPure; + bool isPure = *_access.baseExpression().annotation().isPure; if (Expression const* start = _access.startExpression()) { expectType(*start, *TypeProvider::uint256()); - if (!start->annotation().isPure) + if (!*start->annotation().isPure) isPure = false; } if (Expression const* end = _access.endExpression()) { expectType(*end, *TypeProvider::uint256()); - if (!end->annotation().isPure) + if (!*end->annotation().isPure) isPure = false; } @@ -3022,20 +3059,22 @@ bool TypeChecker::visit(Identifier const& _identifier) !!annotation.referencedDeclaration, "Referenced declaration is null after overload resolution." ); + bool isConstant = false; annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.type = annotation.referencedDeclaration->type(); solAssert(annotation.type, "Declaration referenced before type could be determined."); if (auto variableDeclaration = dynamic_cast(annotation.referencedDeclaration)) - annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); + annotation.isPure = isConstant = variableDeclaration->isConstant(); else if (dynamic_cast(annotation.referencedDeclaration)) - { - if (dynamic_cast(annotation.type)) - annotation.isPure = true; - } + annotation.isPure = dynamic_cast(annotation.type); else if (dynamic_cast(annotation.type)) annotation.isPure = true; else if (dynamic_cast(annotation.type)) annotation.isPure = true; + else + annotation.isPure = false; + + annotation.isConstant = isConstant; // Check for deprecated function names. // The check is done here for the case without an actual function call. @@ -3076,6 +3115,8 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) { _expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability())); _expr.annotation().isPure = true; + _expr.annotation().isLValue = false; + _expr.annotation().isConstant = false; } void TypeChecker::endVisit(Literal const& _literal) @@ -3131,6 +3172,8 @@ void TypeChecker::endVisit(Literal const& _literal) m_errorReporter.fatalTypeError(2826_error, _literal.location(), "Invalid literal value."); _literal.annotation().isPure = true; + _literal.annotation().isLValue = false; + _literal.annotation().isConstant = false; } void TypeChecker::endVisit(UsingForDirective const& _usingFor) @@ -3215,11 +3258,11 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss _expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment; _expression.accept(*this); - if (_expression.annotation().isLValue) + if (*_expression.annotation().isLValue) return; auto [errorId, description] = [&]() -> tuple { - if (_expression.annotation().isConstant) + if (*_expression.annotation().isConstant) return { 6520_error, "Cannot assign to a constant variable." }; if (auto indexAccess = dynamic_cast(&_expression)) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index dd3e99854..7094ccf83 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -492,7 +492,7 @@ CallableDeclaration const* Scopable::functionOrModifierDefinition() const string Scopable::sourceUnitName() const { - return sourceUnit().annotation().path; + return *sourceUnit().annotation().path; } DeclarationAnnotation& Declaration::annotation() const diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 768bac502..2aa48d03e 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -47,6 +47,7 @@ namespace solidity::frontend class Type; using TypePointer = Type const*; +using namespace util; struct ASTAnnotation { @@ -88,9 +89,9 @@ struct StructurallyDocumentedAnnotation struct SourceUnitAnnotation: ASTAnnotation { /// The "absolute" (in the compiler sense) path of this source unit. - std::string path; + SetOnce path; /// The exported symbols (all global symbols). - std::map> exportedSymbols; + SetOnce>> exportedSymbols; /// Experimental features. std::set experimentalFeatures; }; @@ -122,7 +123,7 @@ struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation struct ImportAnnotation: DeclarationAnnotation { /// The absolute path of the source unit to import. - std::string absolutePath; + SetOnce absolutePath; /// The actual source unit. SourceUnit const* sourceUnit = nullptr; }; @@ -130,7 +131,7 @@ struct ImportAnnotation: DeclarationAnnotation struct TypeDeclarationAnnotation: DeclarationAnnotation { /// The name of this type, prefixed by proper namespaces if globally accessible. - std::string canonicalName; + SetOnce canonicalName; }; struct StructDeclarationAnnotation: TypeDeclarationAnnotation @@ -149,7 +150,7 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation { /// List of functions and modifiers without a body. Can also contain functions from base classes. - std::vector unimplementedDeclarations; + std::optional> unimplementedDeclarations; /// List of all (direct and indirect) base contracts in order from derived to /// base, including the contract itself. std::vector linearizedBaseContracts; @@ -243,16 +244,16 @@ struct ExpressionAnnotation: ASTAnnotation /// Inferred type of the expression. TypePointer type = nullptr; /// Whether the expression is a constant variable - bool isConstant = false; + SetOnce isConstant; /// Whether the expression is pure, i.e. compile-time constant. - bool isPure = false; + SetOnce isPure; /// Whether it is an LValue (i.e. something that can be assigned to). - bool isLValue = false; + SetOnce isLValue; /// Whether the expression is used in a context where the LValue is actually required. bool willBeWrittenTo = false; /// Whether the expression is an lvalue that is only assigned. /// Would be false for --, ++, delete, +=, -=, .... - bool lValueOfOrdinaryAssignment = false; + SetOnce lValueOfOrdinaryAssignment; /// Types and - if given - names of arguments if the expr. is a function /// that is called, used for overload resolution diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 6d6d40775..34ae3c2b8 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -39,10 +39,33 @@ #include #include #include +#include using namespace std; using namespace solidity::langutil; +namespace +{ + +template typename C> +void addIfSet(std::vector>& _attributes, string const& _name, C const& _value) +{ + if constexpr (std::is_same_v, solidity::util::SetOnce>) + { + if (!_value.set()) + return; + } + else if constexpr (std::is_same_v, optional>) + { + if (!_value.has_value()) + return; + } + + _attributes.emplace_back(_name, *_value); +} + +} + namespace solidity::frontend { @@ -181,12 +204,14 @@ void ASTJsonConverter::appendExpressionAttributes( { std::vector> exprAttributes = { make_pair("typeDescriptions", typePointerToJson(_annotation.type)), - make_pair("isConstant", _annotation.isConstant), - make_pair("isPure", _annotation.isPure), - make_pair("isLValue", _annotation.isLValue), make_pair("lValueRequested", _annotation.willBeWrittenTo), make_pair("argumentTypes", typePointerToJson(_annotation.arguments)) }; + + addIfSet(exprAttributes, "isLValue", _annotation.isLValue); + addIfSet(exprAttributes, "isPure", _annotation.isPure); + addIfSet(exprAttributes, "isConstant", _annotation.isConstant); + _attributes += exprAttributes; } @@ -214,23 +239,27 @@ Json::Value ASTJsonConverter::toJson(ASTNode const& _node) bool ASTJsonConverter::visit(SourceUnit const& _node) { - Json::Value exportedSymbols = Json::objectValue; - for (auto const& sym: _node.annotation().exportedSymbols) + std::vector> attributes = { + make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue), + make_pair("nodes", toJson(_node.nodes())) + }; + + if (_node.annotation().exportedSymbols.set()) { - exportedSymbols[sym.first] = Json::arrayValue; - for (Declaration const* overload: sym.second) - exportedSymbols[sym.first].append(nodeId(*overload)); - } - setJsonNode( - _node, - "SourceUnit", + Json::Value exportedSymbols = Json::objectValue; + for (auto const& sym: *_node.annotation().exportedSymbols) { - make_pair("absolutePath", _node.annotation().path), - make_pair("exportedSymbols", move(exportedSymbols)), - make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue), - make_pair("nodes", toJson(_node.nodes())) + exportedSymbols[sym.first] = Json::arrayValue; + for (Declaration const* overload: sym.second) + exportedSymbols[sym.first].append(nodeId(*overload)); } - ); + + attributes.emplace_back("exportedSymbols", exportedSymbols); + }; + + addIfSet(attributes, "absolutePath", _node.annotation().path); + + setJsonNode(_node, "SourceUnit", std::move(attributes)); return false; } @@ -249,10 +278,12 @@ bool ASTJsonConverter::visit(ImportDirective const& _node) { std::vector> attributes = { make_pair("file", _node.path()), - make_pair("absolutePath", _node.annotation().absolutePath), make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)), make_pair("scope", idOrNull(_node.scope())) }; + + addIfSet(attributes, "absolutePath", _node.annotation().absolutePath); + attributes.emplace_back("unitAlias", _node.name()); Json::Value symbolAliases(Json::arrayValue); for (auto const& symbolAlias: _node.symbolAliases()) @@ -270,18 +301,23 @@ bool ASTJsonConverter::visit(ImportDirective const& _node) bool ASTJsonConverter::visit(ContractDefinition const& _node) { - setJsonNode(_node, "ContractDefinition", { + std::vector> attributes = { make_pair("name", _node.name()), make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), make_pair("contractKind", contractKind(_node.contractKind())), make_pair("abstract", _node.abstract()), - make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()), - make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), make_pair("baseContracts", toJson(_node.baseContracts())), make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)), make_pair("nodes", toJson(_node.subNodes())), make_pair("scope", idOrNull(_node.scope())) - }); + }; + + if (_node.annotation().unimplementedDeclarations.has_value()) + attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty()); + if (!_node.annotation().linearizedBaseContracts.empty()) + attributes.emplace_back("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)); + + setJsonNode(_node, "ContractDefinition", std::move(attributes)); return false; } @@ -305,23 +341,31 @@ bool ASTJsonConverter::visit(UsingForDirective const& _node) bool ASTJsonConverter::visit(StructDefinition const& _node) { - setJsonNode(_node, "StructDefinition", { + std::vector> attributes = { make_pair("name", _node.name()), make_pair("visibility", Declaration::visibilityToString(_node.visibility())), - make_pair("canonicalName", _node.annotation().canonicalName), make_pair("members", toJson(_node.members())), make_pair("scope", idOrNull(_node.scope())) - }); + }; + + addIfSet(attributes,"canonicalName", _node.annotation().canonicalName); + + setJsonNode(_node, "StructDefinition", std::move(attributes)); + return false; } bool ASTJsonConverter::visit(EnumDefinition const& _node) { - setJsonNode(_node, "EnumDefinition", { + std::vector> attributes = { make_pair("name", _node.name()), - make_pair("canonicalName", _node.annotation().canonicalName), make_pair("members", toJson(_node.members())) - }); + }; + + addIfSet(attributes,"canonicalName", _node.annotation().canonicalName); + + setJsonNode(_node, "EnumDefinition", std::move(attributes)); + return false; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index af52f63d9..b4d2887f1 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2109,7 +2109,7 @@ string ContractType::toString(bool) const string ContractType::canonicalName() const { - return m_contract.annotation().canonicalName; + return *m_contract.annotation().canonicalName; } MemberList::MemberMap ContractType::nativeMembers(ASTNode const*) const @@ -2360,7 +2360,7 @@ bool StructType::containsNestedMapping() const string StructType::toString(bool _short) const { - string ret = "struct " + m_struct.annotation().canonicalName; + string ret = "struct " + *m_struct.annotation().canonicalName; if (!_short) ret += " " + stringForReferencePart(); return ret; @@ -2539,7 +2539,7 @@ string StructType::signatureInExternalFunction(bool _structsByName) const string StructType::canonicalName() const { - return m_struct.annotation().canonicalName; + return *m_struct.annotation().canonicalName; } FunctionTypePointer StructType::constructorType() const @@ -2646,12 +2646,12 @@ unsigned EnumType::storageBytes() const string EnumType::toString(bool) const { - return string("enum ") + m_enum.annotation().canonicalName; + return string("enum ") + *m_enum.annotation().canonicalName; } string EnumType::canonicalName() const { - return m_enum.annotation().canonicalName; + return *m_enum.annotation().canonicalName; } size_t EnumType::numberOfMembers() const @@ -3124,7 +3124,7 @@ string FunctionType::toString(bool _short) const auto const* functionDefinition = dynamic_cast(m_declaration); solAssert(functionDefinition, ""); if (auto const* contract = dynamic_cast(functionDefinition->scope())) - name += contract->annotation().canonicalName + "."; + name += *contract->annotation().canonicalName + "."; name += functionDefinition->name(); } name += '('; @@ -3915,7 +3915,7 @@ bool ModuleType::operator==(Type const& _other) const MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const { MemberList::MemberMap symbols; - for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols) + for (auto const& symbolName: *m_sourceUnit.annotation().exportedSymbols) for (Declaration const* symbol: symbolName.second) symbols.emplace_back(symbolName.first, symbol->type(), symbol); return symbols; @@ -3923,7 +3923,7 @@ MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const string ModuleType::toString(bool) const { - return string("module \"") + m_sourceUnit.annotation().path + string("\""); + return string("module \"") + *m_sourceUnit.annotation().path + string("\""); } string MagicType::richIdentifier() const diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index f92117689..f096583db 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -779,7 +779,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(function.parameterTypes().size() == 1, ""); if (m_context.revertStrings() == RevertStrings::Strip) { - if (!arguments.front()->annotation().isPure) + if (!*arguments.front()->annotation().isPure) { arguments.front()->accept(*this); utils().popStackElement(*arguments.front()->annotation().type); @@ -1078,7 +1078,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(function.kind() == FunctionType::Kind::Require, ""); if (m_context.revertStrings() == RevertStrings::Strip) { - if (!arguments.at(1)->annotation().isPure) + if (!*arguments.at(1)->annotation().isPure) { arguments.at(1)->accept(*this); utils().popStackElement(*arguments.at(1)->annotation().type); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 32469e566..db7cab879 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1097,7 +1097,7 @@ void CompilerStack::resolveImports() for (ASTPointer const& node: _source->ast->nodes()) if (ImportDirective const* import = dynamic_cast(node.get())) { - string const& path = import->annotation().absolutePath; + string const& path = *import->annotation().absolutePath; solAssert(m_sources.count(path), ""); import->annotation().sourceUnit = m_sources[path].ast.get(); toposort(&m_sources[path]); @@ -1295,9 +1295,9 @@ string CompilerStack::createMetadata(Contract const& _contract) const /// All the source files (including self), which should be included in the metadata. set referencedSources; - referencedSources.insert(_contract.contract->sourceUnit().annotation().path); + referencedSources.insert(*_contract.contract->sourceUnit().annotation().path); for (auto const sourceUnit: _contract.contract->sourceUnit().referencedSourceUnits(true)) - referencedSources.insert(sourceUnit->annotation().path); + referencedSources.insert(*sourceUnit->annotation().path); meta["sources"] = Json::objectValue; for (auto const& s: m_sources) @@ -1363,7 +1363,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const meta["settings"]["evmVersion"] = m_evmVersion.name(); meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] = - _contract.contract->annotation().canonicalName; + *_contract.contract->annotation().canonicalName; meta["settings"]["remappings"] = Json::arrayValue; set remappings; diff --git a/libsolutil/SetOnce.h b/libsolutil/SetOnce.h index 289258e7b..007424ff1 100644 --- a/libsolutil/SetOnce.h +++ b/libsolutil/SetOnce.h @@ -79,6 +79,8 @@ public: /// @throws BadSetOnceAccess when the stored value has not yet been set T const* operator->() const { return std::addressof(**this); } + /// @return true if a value was assigned + bool set() const { return m_value.has_value(); } private: std::optional m_value = std::nullopt; }; diff --git a/test/libsolidity/ASTJSON/assembly/nested_functions.json b/test/libsolidity/ASTJSON/assembly/nested_functions.json index f945494ba..984c05cc9 100644 --- a/test/libsolidity/ASTJSON/assembly/nested_functions.json +++ b/test/libsolidity/ASTJSON/assembly/nested_functions.json @@ -16,7 +16,6 @@ "baseContracts": [], "contractDependencies": [], "contractKind": "contract", - "fullyImplemented": true, "id": 8, "linearizedBaseContracts": [ diff --git a/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json b/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json index 393646221..4f5bfb268 100644 --- a/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json @@ -25,7 +25,6 @@ null ], "contractKind": "contract", - "fullyImplemented": true, "linearizedBaseContracts": [ 8 diff --git a/test/libsolidity/ASTJSON/assembly/switch.json b/test/libsolidity/ASTJSON/assembly/switch.json index 9ae71f15c..9a2d1f5c5 100644 --- a/test/libsolidity/ASTJSON/assembly/switch.json +++ b/test/libsolidity/ASTJSON/assembly/switch.json @@ -16,7 +16,6 @@ "baseContracts": [], "contractDependencies": [], "contractKind": "contract", - "fullyImplemented": true, "id": 6, "linearizedBaseContracts": [ diff --git a/test/libsolidity/ASTJSON/assembly/switch_legacy.json b/test/libsolidity/ASTJSON/assembly/switch_legacy.json index 92e2e2c61..beddc74e1 100644 --- a/test/libsolidity/ASTJSON/assembly/switch_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/switch_legacy.json @@ -25,7 +25,6 @@ null ], "contractKind": "contract", - "fullyImplemented": true, "linearizedBaseContracts": [ 6 diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 65a8026a4..1ecd0e9f9 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(function_no_implementation) std::vector> nodes = sourceUnit->nodes(); ContractDefinition* contract = dynamic_cast(nodes[1].get()); BOOST_REQUIRE(contract); - BOOST_CHECK(!contract->annotation().unimplementedDeclarations.empty()); + BOOST_CHECK(!contract->annotation().unimplementedDeclarations->empty()); BOOST_CHECK(!contract->definedFunctions()[0]->isImplemented()); } @@ -68,10 +68,10 @@ BOOST_AUTO_TEST_CASE(abstract_contract) ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().unimplementedDeclarations.empty()); + BOOST_CHECK(!base->annotation().unimplementedDeclarations->empty()); BOOST_CHECK(!base->definedFunctions()[0]->isImplemented()); BOOST_REQUIRE(derived); - BOOST_CHECK(derived->annotation().unimplementedDeclarations.empty()); + BOOST_CHECK(derived->annotation().unimplementedDeclarations->empty()); BOOST_CHECK(derived->definedFunctions()[0]->isImplemented()); } @@ -87,9 +87,9 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().unimplementedDeclarations.empty()); + BOOST_CHECK(!base->annotation().unimplementedDeclarations->empty()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().unimplementedDeclarations.empty()); + BOOST_CHECK(!derived->annotation().unimplementedDeclarations->empty()); } BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) @@ -104,7 +104,7 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) BOOST_CHECK_EQUAL(nodes.size(), 3); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().unimplementedDeclarations.empty()); + BOOST_CHECK(!derived->annotation().unimplementedDeclarations->empty()); } BOOST_AUTO_TEST_CASE(function_canonical_signature) diff --git a/tools/solidityUpgrade/Upgrade060.cpp b/tools/solidityUpgrade/Upgrade060.cpp index 58aa94d15..a5d2f0af6 100644 --- a/tools/solidityUpgrade/Upgrade060.cpp +++ b/tools/solidityUpgrade/Upgrade060.cpp @@ -100,7 +100,7 @@ inline string appendVirtual(FunctionDefinition const& _function) void AbstractContract::endVisit(ContractDefinition const& _contract) { - bool isFullyImplemented = _contract.annotation().unimplementedDeclarations.empty(); + bool isFullyImplemented = _contract.annotation().unimplementedDeclarations->empty(); if ( !isFullyImplemented && From 709d25bd3df6afdd29f9576f4e062dd14db77ec5 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 14 Sep 2020 20:22:50 +0100 Subject: [PATCH 20/21] [SMTChecker] Support address type conversion with literals --- Changelog.md | 1 + libsolidity/formal/SMTEncoder.cpp | 11 ++++++-- ...modifier_inline_function_inside_branch.sol | 2 -- .../typecast/address_literal.sol | 25 +++++++++++++++++++ .../types/tuple_extra_parens_7.sol | 1 - 5 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 test/libsolidity/smtCheckerTests/typecast/address_literal.sol diff --git a/Changelog.md b/Changelog.md index fb05610a5..3cab5b5b8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Compiler Features: * SMTChecker: Support shifts. * SMTChecker: Support structs. * SMTChecker: Support ``type(T).min``, ``type(T).max``, and ``type(I).interfaceId``. + * SMTChecker: Support ``address`` type conversion with literals, e.g. ``address(0)``. * Yul Optimizer: Prune unused parameters in functions. * Yul Optimizer: Inline into functions further down in the call graph first. * Yul Optimizer: Try to simplify function names. diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 90df7a908..9113f526f 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -792,12 +792,19 @@ void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall) auto argument = _funCall.arguments().front(); unsigned argSize = argument->annotation().type->storageBytes(); unsigned castSize = _funCall.annotation().type->storageBytes(); - if (argSize == castSize) + auto const& funCallCategory = _funCall.annotation().type->category(); + // Allow casting number literals to address. + // TODO: remove the isNegative() check once the type checker disallows this + if ( + auto const* numberType = dynamic_cast(argument->annotation().type); + numberType && !numberType->isNegative() && (funCallCategory == Type::Category::Address) + ) + defineExpr(_funCall, numberType->literalValue(nullptr)); + else if (argSize == castSize) defineExpr(_funCall, expr(*argument)); else { m_context.setUnknownValue(*m_context.expression(_funCall)); - auto const& funCallCategory = _funCall.annotation().type->category(); // TODO: truncating and bytesX needs a different approach because of right padding. if (funCallCategory == Type::Category::Integer || funCallCategory == Type::Category::Address) { diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_inline_function_inside_branch.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_inline_function_inside_branch.sol index bb46594fe..11df23d93 100644 --- a/test/libsolidity/smtCheckerTests/modifiers/modifier_inline_function_inside_branch.sol +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_inline_function_inside_branch.sol @@ -17,5 +17,3 @@ contract C } } // ---- -// Warning 5084: (205-215): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (205-215): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/typecast/address_literal.sol b/test/libsolidity/smtCheckerTests/typecast/address_literal.sol new file mode 100644 index 000000000..0a3d990ae --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/address_literal.sol @@ -0,0 +1,25 @@ +pragma experimental SMTChecker; + +contract C { + address x; // We know that this is "zero initialised". + function f() public view { + address a = address(0); + assert(x == address(0)); + assert(x == a); + } + + function g() public pure { + address a = address(0); + address b = address(1); + address c = address(0); + address d = a; + address e = address(0x12345678); + assert(c == d); + assert(a == c); + assert(e == address(305419896)); + // This is untrue. + assert(a == b); + } +} +// ---- +// Warning 6328: (487-501): Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/types/tuple_extra_parens_7.sol b/test/libsolidity/smtCheckerTests/types/tuple_extra_parens_7.sol index 824d84c3e..254e58f7b 100644 --- a/test/libsolidity/smtCheckerTests/types/tuple_extra_parens_7.sol +++ b/test/libsolidity/smtCheckerTests/types/tuple_extra_parens_7.sol @@ -11,4 +11,3 @@ contract C { } } // ---- -// Warning 5084: (142-152): Type conversion is not yet fully supported and might yield false positives. From f4ee4cd479ac4c71f3a115419dcb171279a05455 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Tue, 22 Sep 2020 20:51:28 +0200 Subject: [PATCH 21/21] Update tests --- .../control_flow/function_call_inside_branch.sol | 3 --- .../control_flow/function_call_inside_branch_2.sol | 5 ----- .../control_flow/function_call_inside_branch_3.sol | 5 ----- .../control_flow/function_call_inside_branch_4.sol | 4 ---- .../control_flow/function_call_inside_else_branch.sol | 3 --- .../control_flow/function_call_inside_modifier_branch.sol | 3 --- ...nction_call_inside_placeholder_inside_modifier_branch.sol | 3 --- .../external_calls/external_hash_known_code_state.sol | 1 - .../external_hash_known_code_state_reentrancy_indirect.sol | 1 - .../external_hash_known_code_state_reentrancy_unsafe.sol | 1 - .../external_calls/external_hash_known_code_state_unsafe.sol | 1 - .../functions/function_external_call_should_not_inline_1.sol | 1 - .../functions/function_external_call_should_not_inline_2.sol | 1 - test/libsolidity/smtCheckerTests/special/msg_sender_2.sol | 1 - test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol | 2 -- 15 files changed, 35 deletions(-) diff --git a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch.sol b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch.sol index fc54d5a01..c32bdd983 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch.sol @@ -15,6 +15,3 @@ contract C } } // ---- -// Warning 5084: (208-218): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (208-218): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_2.sol b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_2.sol index 0114ab988..1bff12f86 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_2.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_2.sol @@ -20,8 +20,3 @@ contract C } } // ---- -// Warning 5084: (271-281): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (271-281): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (186-196): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (271-281): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_3.sol b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_3.sol index f00035905..d14a2e6bc 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_3.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_3.sol @@ -20,8 +20,3 @@ contract C } } // ---- -// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (189-199): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_4.sol b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_4.sol index e8f08cc3e..bdb7b9fd8 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_4.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_branch_4.sol @@ -25,7 +25,3 @@ contract C } // ---- -// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (123-133): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (189-199): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (275-285): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_else_branch.sol b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_else_branch.sol index 23ba4ff05..8c3099bf1 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_else_branch.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_else_branch.sol @@ -16,6 +16,3 @@ contract C } } // ---- -// Warning 5084: (219-229): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (134-144): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (219-229): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_modifier_branch.sol b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_modifier_branch.sol index 755d44ff6..f79be2298 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_modifier_branch.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_modifier_branch.sol @@ -19,6 +19,3 @@ contract C } } // ---- -// Warning 5084: (249-259): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (118-128): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (249-259): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_placeholder_inside_modifier_branch.sol b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_placeholder_inside_modifier_branch.sol index e8d22edcf..5366b22d4 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_placeholder_inside_modifier_branch.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/function_call_inside_placeholder_inside_modifier_branch.sol @@ -20,6 +20,3 @@ contract C } } // ---- -// Warning 5084: (247-257): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (162-172): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (247-257): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol index 2ed28acfe..5f90e9e98 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state.sol @@ -35,4 +35,3 @@ contract C { } // ---- // Warning 6328: (528-565): Assertion violation happens here. -// Warning 5084: (544-554): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol index e4e32f894..3aefbb82f 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol @@ -44,4 +44,3 @@ contract C { // ---- // Warning 6328: (452-466): Assertion violation happens here. // Warning 6328: (470-496): Assertion violation happens here. -// Warning 5084: (92-102): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_unsafe.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_unsafe.sol index 92ecdccc5..33127dcb8 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_unsafe.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_unsafe.sol @@ -36,4 +36,3 @@ contract C { // ---- // Warning 6328: (381-395): Assertion violation happens here. // Warning 6328: (399-425): Assertion violation happens here. -// Warning 5084: (116-126): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_unsafe.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_unsafe.sol index 580ae471b..b0e96286b 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_unsafe.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_unsafe.sol @@ -40,4 +40,3 @@ contract C { // ---- // Warning 6328: (435-461): Assertion violation happens here. // Warning 6328: (594-631): Assertion violation happens here. -// Warning 5084: (610-620): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_1.sol b/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_1.sol index f32cfa4fa..8b8ceed1a 100644 --- a/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_1.sol +++ b/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_1.sol @@ -13,4 +13,3 @@ contract C { } } // ---- -// Warning 5084: (198-208): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_2.sol b/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_2.sol index 06dc94cce..897367d48 100644 --- a/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_2.sol +++ b/test/libsolidity/smtCheckerTests/functions/function_external_call_should_not_inline_2.sol @@ -13,4 +13,3 @@ contract C { } } // ---- -// Warning 5084: (107-117): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol b/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol index df5620d8b..65a5b2bc7 100644 --- a/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol +++ b/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol @@ -10,4 +10,3 @@ contract C } } // ---- -// Warning 5084: (98-108): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol b/test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol index 0bce016c9..c76e4a9c7 100644 --- a/test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol +++ b/test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol @@ -8,5 +8,3 @@ contract C } } // ---- -// Warning 5084: (98-108): Type conversion is not yet fully supported and might yield false positives. -// Warning 5084: (125-135): Type conversion is not yet fully supported and might yield false positives.