mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #6853 from ethereum/develop
Merge develop inte release for 0.5.9
This commit is contained in:
		
						commit
						e560f70d8e
					
				| @ -30,7 +30,9 @@ defaults: | ||||
|       command: scripts/tests.sh --junit_report test_results | ||||
|   - run_regressions: &run_regressions | ||||
|       name: Regression tests | ||||
|       command: scripts/regressions.py -o test_results | ||||
|       command: | | ||||
|         export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" | ||||
|         scripts/regressions.py -o test_results | ||||
|   - solc_artifact: &solc_artifact | ||||
|       path: build/solc/solc | ||||
|       destination: solc | ||||
| @ -43,12 +45,14 @@ defaults: | ||||
|   - ossfuzz_artifacts: &ossfuzz_artifacts | ||||
|       root: build | ||||
|       paths: | ||||
|         - test/tools/ossfuzz/solc_opt_ossfuzz | ||||
|         - test/tools/ossfuzz/solc_noopt_ossfuzz | ||||
|         - test/tools/ossfuzz/const_opt_ossfuzz | ||||
|         - test/tools/ossfuzz/solc_noopt_ossfuzz | ||||
|         - test/tools/ossfuzz/solc_opt_ossfuzz | ||||
|         - test/tools/ossfuzz/strictasm_assembly_ossfuzz | ||||
|         - test/tools/ossfuzz/strictasm_diff_ossfuzz | ||||
|         - test/tools/ossfuzz/yul_proto_ossfuzz | ||||
|         - test/tools/ossfuzz/strictasm_opt_ossfuzz | ||||
|         - test/tools/ossfuzz/yul_proto_diff_ossfuzz | ||||
|         - test/tools/ossfuzz/yul_proto_ossfuzz | ||||
| 
 | ||||
| version: 2 | ||||
| jobs: | ||||
| @ -134,6 +138,24 @@ jobs: | ||||
|           command: | | ||||
|             test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js | ||||
| 
 | ||||
|   test_emscripten_external_colony: | ||||
|     docker: | ||||
|       - image: circleci/node:10 | ||||
|     environment: | ||||
|       TERM: xterm | ||||
|     steps: | ||||
|       - checkout | ||||
|       - attach_workspace: | ||||
|           at: /tmp/workspace | ||||
|       - run: | ||||
|           name: Install test dependencies | ||||
|           command: | | ||||
|             sudo apt-get -qy install lsof | ||||
|       - run: | ||||
|           name: External ColonyNetworks tests | ||||
|           command: | | ||||
|             test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js | ||||
| 
 | ||||
|   build_x86_linux: | ||||
|     docker: | ||||
|       - image: buildpack-deps:bionic | ||||
| @ -337,6 +359,7 @@ jobs: | ||||
|             ulimit -a | ||||
|             # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). | ||||
|             ulimit -s 16384 | ||||
|             export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" | ||||
|             build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test | ||||
|       - run: | ||||
|           name: Run commandline tests with ASAN | ||||
| @ -344,6 +367,7 @@ jobs: | ||||
|             ulimit -a | ||||
|             # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). | ||||
|             ulimit -s 16384 | ||||
|             export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" | ||||
|             test/cmdlineTests.sh | ||||
|       - store_test_results: | ||||
|           path: test_results/ | ||||
| @ -416,11 +440,11 @@ jobs: | ||||
| 
 | ||||
|   build_x86_linux_ossfuzz: | ||||
|     docker: | ||||
|       - image: buildpack-deps:cosmic | ||||
|       - image: buildpack-deps:disco | ||||
|     environment: | ||||
|       TERM: xterm | ||||
|       CC: /usr/bin/clang-7 | ||||
|       CXX: /usr/bin/clang++-7 | ||||
|       CC: /usr/bin/clang-8 | ||||
|       CXX: /usr/bin/clang++-8 | ||||
|       CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake | ||||
|     steps: | ||||
|       - checkout | ||||
| @ -428,16 +452,18 @@ jobs: | ||||
|           name: Install build dependencies | ||||
|           command: | | ||||
|             apt-get -qq update | ||||
|             apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\* | ||||
|             apt-get -qy install wget clang-8 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\* | ||||
|             ./scripts/install_lpm.sh | ||||
|             ./scripts/install_libfuzzer.sh | ||||
|             # Install evmone and dependencies (intx and ethash) | ||||
|             ./scripts/install_evmone.sh | ||||
|       - run: *setup_prerelease_commit_hash | ||||
|       - run: *run_build_ossfuzz | ||||
|       - persist_to_workspace: *ossfuzz_artifacts | ||||
| 
 | ||||
|   test_x86_ossfuzz_regression: | ||||
|     docker: | ||||
|       - image: buildpack-deps:cosmic | ||||
|       - image: buildpack-deps:disco | ||||
|     environment: | ||||
|       TERM: xterm | ||||
|     steps: | ||||
| @ -448,13 +474,11 @@ jobs: | ||||
|           name: Install dependencies | ||||
|           command: | | ||||
|             apt-get -qq update | ||||
|             apt-get -qy install libcvc4-dev llvm-7-dev | ||||
|             apt-get -qy install libcvc4-dev llvm-8-dev | ||||
|             ./scripts/download_ossfuzz_corpus.sh | ||||
|             update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1 | ||||
|             update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1 | ||||
|       - run: mkdir -p test_results | ||||
|       - run: *run_regressions | ||||
|       - store_test_results: | ||||
|           path: test_results/ | ||||
|       - store_artifacts: | ||||
|           path: test_results/ | ||||
|           destination: test_results/ | ||||
| @ -471,14 +495,6 @@ workflows: | ||||
|           <<: *build_on_tags | ||||
|           requires: | ||||
|             - build_emscripten | ||||
|       - test_emscripten_external_zeppelin: | ||||
|           <<: *build_on_tags | ||||
|           requires: | ||||
|             - build_emscripten | ||||
|       - test_emscripten_external_gnosis: | ||||
|           <<: *build_on_tags | ||||
|           requires: | ||||
|             - build_emscripten | ||||
|       - build_x86_linux: *build_on_tags | ||||
|       - build_x86_linux_cxx17: *build_on_tags | ||||
|       - build_x86_clang7_asan: *build_on_tags | ||||
| @ -521,8 +537,13 @@ workflows: | ||||
|           <<: *build_on_tags | ||||
|           requires: | ||||
|             - build_emscripten | ||||
|       - test_emscripten_external_colony: | ||||
|           <<: *build_on_tags | ||||
|           requires: | ||||
|             - build_emscripten | ||||
|       - build_x86_linux_ossfuzz: *build_on_tags | ||||
|       - test_x86_ossfuzz_regression: | ||||
|           <<: *build_on_tags | ||||
|           requires: | ||||
|             - build_x86_linux_ossfuzz | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							| @ -33,8 +33,8 @@ Please provide a *minimal* source code example to trigger the bug you have found | ||||
| Please also mention any command line flags that are necessary for triggering the bug. | ||||
| Provide as much information as necessary to reproduce the bug. | ||||
| 
 | ||||
| ``` | ||||
| ```solidity | ||||
| // Some *minimal* Solidity source code to reproduce the bug. | ||||
| // ... | ||||
| ``` | ||||
| --> | ||||
| --> | ||||
|  | ||||
| @ -10,7 +10,7 @@ include(EthPolicy) | ||||
| eth_policy() | ||||
| 
 | ||||
| # project name and version should be set after cmake_policy CMP0048 | ||||
| set(PROJECT_VERSION "0.5.8") | ||||
| set(PROJECT_VERSION "0.5.9") | ||||
| project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) | ||||
| 
 | ||||
| option(LLL "Build LLL" OFF) | ||||
|  | ||||
							
								
								
									
										36
									
								
								Changelog.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								Changelog.md
									
									
									
									
									
								
							| @ -1,3 +1,38 @@ | ||||
| ### 0.5.9 (2019-05-28) | ||||
| 
 | ||||
| Language Features: | ||||
|  * Inline Assembly: Revert change introduced in 0.5.7: The ``callvalue()`` instruction does not require ``payable`` anymore. | ||||
|  * Static Analyzer: Disallow libraries calling themselves externally. | ||||
| 
 | ||||
| 
 | ||||
| Compiler Features: | ||||
|  * Assembler: Encode the compiler version in the deployed bytecode. | ||||
|  * Code Generator: Fix handling of structs of dynamic size as constructor parameters. | ||||
|  * Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch. | ||||
|  * Inline Assembly: Disallow the combination of ``msize()`` and the Yul optimizer. | ||||
|  * Metadata: Add IPFS hashes of source files. | ||||
|  * Optimizer: Add rule to simplify SHL/SHR combinations. | ||||
|  * Optimizer: Add rules for multiplication and division by left-shifted one. | ||||
|  * SMTChecker: Support inherited state variables. | ||||
|  * SMTChecker: Support tuples and function calls with multiple return values. | ||||
|  * SMTChecker: Support ``delete``. | ||||
|  * SMTChecker: Inline external function calls to ``this``. | ||||
|  * Yul Optimizer: Simplify single-run ``for`` loops to ``if`` statements. | ||||
|  * Yul Optimizer: Optimize representation of numbers. | ||||
|  * Yul Optimizer: Do not inline recursive functions. | ||||
|  * Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used. | ||||
| 
 | ||||
| Bugfixes: | ||||
|  * Code Generator: Explicitly turn uninitialized internal function pointers into invalid functions when loaded from storage. | ||||
|  * Code Generator: Fix assertion failure when assigning structs containing array of mapping. | ||||
|  * Compiler Internals: Reset the Yul string repository before each compilation, freeing up memory. | ||||
|  * SMTChecker: Fix bad cast in base constructor modifier. | ||||
|  * SMTChecker: Fix internal error when visiting state variable inherited from base class. | ||||
|  * SMTChecker: Fix internal error in fixed point operations. | ||||
|  * SMTChecker: Fix internal error in assignment to unsupported type. | ||||
|  * SMTChecker: Fix internal error in branching when inlining function calls that modify local variables. | ||||
| 
 | ||||
| 
 | ||||
| ### 0.5.8 (2019-04-30) | ||||
| 
 | ||||
| Important Bugfixes: | ||||
| @ -33,6 +68,7 @@ Bugfixes: | ||||
|  * SMTChecker: SSA control-flow did not take into account state variables that were modified inside inlined functions that were called inside branches. | ||||
|  * Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation. | ||||
|  * Type System: Allow direct call to base class functions that have overloads. | ||||
|  * Type System: Warn about shadowing builtin variables if user variables are named ``this`` or ``super``. | ||||
|  * Yul: Properly register functions and disallow shadowing between function variables and variables in the outside scope. | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -37,6 +37,9 @@ function(create_build_info NAME) | ||||
| 		-DETH_BUILD_COMPILER="${ETH_BUILD_COMPILER}" | ||||
| 		-DETH_BUILD_PLATFORM="${ETH_BUILD_PLATFORM}" | ||||
| 		-DPROJECT_VERSION="${PROJECT_VERSION}" | ||||
| 		-DPROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}" | ||||
| 		-DPROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}" | ||||
| 		-DPROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}" | ||||
| 		-P "${ETH_SCRIPTS_DIR}/buildinfo.cmake" | ||||
| 		) | ||||
| 	include_directories("${PROJECT_BINARY_DIR}/include") | ||||
|  | ||||
| @ -144,7 +144,13 @@ else () | ||||
| endif () | ||||
| 
 | ||||
| if (SANITIZE) | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=${SANITIZE}") | ||||
| 	# Perform case-insensitive string compare | ||||
| 	string(TOLOWER "${SANITIZE}" san) | ||||
| 	# -fno-omit-frame-pointer gives more informative stack trace in case of an error | ||||
| 	# -fsanitize-address-use-after-scope throws an error when a variable is used beyond its scope | ||||
| 	if (san STREQUAL "address") | ||||
| 		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope") | ||||
| 	endif() | ||||
| endif() | ||||
| 
 | ||||
| # Code coverage support. | ||||
|  | ||||
| @ -1,6 +1,9 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #define ETH_PROJECT_VERSION "@PROJECT_VERSION@" | ||||
| #define ETH_PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ | ||||
| #define ETH_PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ | ||||
| #define ETH_PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ | ||||
| #define SOL_COMMIT_HASH "@SOL_COMMIT_HASH@" | ||||
| #define ETH_BUILD_TYPE "@ETH_BUILD_TYPE@" | ||||
| #define ETH_BUILD_OS "@ETH_BUILD_OS@" | ||||
|  | ||||
| @ -67,7 +67,7 @@ jsoncpp: | ||||
|   license you like. | ||||
| 
 | ||||
| scanner/token: | ||||
|   The liblangutil/{CharStream,Scanner,Token}.{h,cpp} files are dervied from | ||||
|   The liblangutil/{CharStream,Scanner,Token}.{h,cpp} files are derived from | ||||
|   code originating from the V8 project licensed under the following terms: | ||||
| 
 | ||||
|   Copyright 2006-2012, the V8 project authors. All rights reserved. | ||||
|  | ||||
| @ -214,10 +214,11 @@ Given the contract: | ||||
| 
 | ||||
|     pragma solidity >=0.4.16 <0.7.0; | ||||
| 
 | ||||
| 
 | ||||
|     contract Foo { | ||||
|       function bar(bytes3[2] memory) public pure {} | ||||
|       function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; } | ||||
|       function sam(bytes memory, bool, uint[] memory) public pure {} | ||||
|         function bar(bytes3[2] memory) public pure {} | ||||
|         function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; } | ||||
|         function sam(bytes memory, bool, uint[] memory) public pure {} | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| @ -485,12 +486,13 @@ For example, | ||||
| 
 | ||||
|     pragma solidity >=0.5.0 <0.7.0; | ||||
| 
 | ||||
| 
 | ||||
|     contract Test { | ||||
|       constructor() public { b = hex"12345678901234567890123456789012"; } | ||||
|       event Event(uint indexed a, bytes32 b); | ||||
|       event Event2(uint indexed a, bytes32 b); | ||||
|       function foo(uint a) public { emit Event(a, b); } | ||||
|       bytes32 b; | ||||
|         constructor() public { b = hex"12345678901234567890123456789012"; } | ||||
|         event Event(uint indexed a, bytes32 b); | ||||
|         event Event2(uint indexed a, bytes32 b); | ||||
|         function foo(uint a) public { emit Event(a, b); } | ||||
|         bytes32 b; | ||||
|     } | ||||
| 
 | ||||
| would result in the JSON: | ||||
| @ -533,11 +535,12 @@ As an example, the code | ||||
|     pragma solidity >=0.4.19 <0.7.0; | ||||
|     pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| 
 | ||||
|     contract Test { | ||||
|       struct S { uint a; uint[] b; T[] c; } | ||||
|       struct T { uint x; uint y; } | ||||
|       function f(S memory s, T memory t, uint a) public; | ||||
|       function g() public returns (S memory s, T memory t, uint a); | ||||
|         struct S { uint a; uint[] b; T[] c; } | ||||
|         struct T { uint x; uint y; } | ||||
|         function f(S memory s, T memory t, uint a) public; | ||||
|         function g() public returns (S memory s, T memory t, uint a); | ||||
|     } | ||||
| 
 | ||||
| would result in the JSON: | ||||
|  | ||||
| @ -104,47 +104,48 @@ efficient code, for example: | ||||
| 
 | ||||
|     pragma solidity >=0.4.16 <0.7.0; | ||||
| 
 | ||||
| 
 | ||||
|     library VectorSum { | ||||
|         // This function is less efficient because the optimizer currently fails to | ||||
|         // remove the bounds checks in array access. | ||||
|         function sumSolidity(uint[] memory _data) public pure returns (uint o_sum) { | ||||
|         function sumSolidity(uint[] memory _data) public pure returns (uint sum) { | ||||
|             for (uint i = 0; i < _data.length; ++i) | ||||
|                 o_sum += _data[i]; | ||||
|                 sum += _data[i]; | ||||
|         } | ||||
| 
 | ||||
|         // We know that we only access the array in bounds, so we can avoid the check. | ||||
|         // 0x20 needs to be added to an array because the first slot contains the | ||||
|         // array length. | ||||
|         function sumAsm(uint[] memory _data) public pure returns (uint o_sum) { | ||||
|         function sumAsm(uint[] memory _data) public pure returns (uint sum) { | ||||
|             for (uint i = 0; i < _data.length; ++i) { | ||||
|                 assembly { | ||||
|                     o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20)))) | ||||
|                     sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20)))) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Same as above, but accomplish the entire code within inline assembly. | ||||
|         function sumPureAsm(uint[] memory _data) public pure returns (uint o_sum) { | ||||
|         function sumPureAsm(uint[] memory _data) public pure returns (uint sum) { | ||||
|             assembly { | ||||
|                // Load the length (first 32 bytes) | ||||
|                let len := mload(_data) | ||||
|                 // Load the length (first 32 bytes) | ||||
|                 let len := mload(_data) | ||||
| 
 | ||||
|                // Skip over the length field. | ||||
|                // | ||||
|                // Keep temporary variable so it can be incremented in place. | ||||
|                // | ||||
|                // NOTE: incrementing _data would result in an unusable | ||||
|                //       _data variable after this assembly block | ||||
|                let data := add(_data, 0x20) | ||||
|                 // Skip over the length field. | ||||
|                 // | ||||
|                 // Keep temporary variable so it can be incremented in place. | ||||
|                 // | ||||
|                 // NOTE: incrementing _data would result in an unusable | ||||
|                 //       _data variable after this assembly block | ||||
|                 let data := add(_data, 0x20) | ||||
| 
 | ||||
|                // Iterate until the bound is not met. | ||||
|                for | ||||
|                    { let end := add(data, mul(len, 0x20)) } | ||||
|                    lt(data, end) | ||||
|                    { data := add(data, 0x20) } | ||||
|                { | ||||
|                    o_sum := add(o_sum, mload(data)) | ||||
|                } | ||||
|                 // Iterate until the bound is not met. | ||||
|                 for | ||||
|                     { let end := add(data, mul(len, 0x20)) } | ||||
|                     lt(data, end) | ||||
|                     { data := add(data, 0x20) } | ||||
|                 { | ||||
|                     sum := add(sum, mload(data)) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @ -693,12 +694,13 @@ We consider the runtime bytecode of the following Solidity program:: | ||||
| 
 | ||||
|     pragma solidity >=0.4.16 <0.7.0; | ||||
| 
 | ||||
| 
 | ||||
|     contract C { | ||||
|       function f(uint x) public pure returns (uint y) { | ||||
|         y = 1; | ||||
|         for (uint i = 0; i < x; i++) | ||||
|           y = 2 * y; | ||||
|       } | ||||
|         function f(uint x) public pure returns (uint y) { | ||||
|             y = 1; | ||||
|             for (uint i = 0; i < x; i++) | ||||
|                 y = 2 * y; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| The following assembly will be generated:: | ||||
|  | ||||
| @ -1,4 +1,15 @@ | ||||
| [ | ||||
|     { | ||||
|         "name": "DynamicConstructorArgumentsClippedABIV2", | ||||
|         "summary": "A contract's constructor that takes structs or arrays that contain dynamically-sized arrays reverts or decodes to invalid data.", | ||||
|         "description": "During construction of a contract, constructor parameters are copied from the code section to memory for decoding. The amount of bytes to copy was calculated incorrectly in case all parameters are statically-sized but contain dynamically-sized arrays as struct members or inner arrays. Such types are only available if ABIEncoderV2 is activated.", | ||||
|         "introduced": "0.4.16", | ||||
|         "fixed": "0.5.9", | ||||
|         "severity": "very low", | ||||
|         "conditions": { | ||||
|             "ABIEncoderV2": true | ||||
|         } | ||||
|     }, | ||||
|     { | ||||
|         "name": "UninitializedFunctionPointerInConstructor", | ||||
|         "summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.", | ||||
|  | ||||
| @ -452,6 +452,7 @@ | ||||
|     }, | ||||
|     "0.4.16": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ExpExponentCleanup", | ||||
| @ -462,6 +463,7 @@ | ||||
|     }, | ||||
|     "0.4.17": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ExpExponentCleanup", | ||||
| @ -473,6 +475,7 @@ | ||||
|     }, | ||||
|     "0.4.18": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ExpExponentCleanup", | ||||
| @ -483,6 +486,7 @@ | ||||
|     }, | ||||
|     "0.4.19": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ABIEncoderV2PackedStorage_0.4.x", | ||||
| @ -510,6 +514,7 @@ | ||||
|     }, | ||||
|     "0.4.20": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ABIEncoderV2PackedStorage_0.4.x", | ||||
| @ -521,6 +526,7 @@ | ||||
|     }, | ||||
|     "0.4.21": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ABIEncoderV2PackedStorage_0.4.x", | ||||
| @ -532,6 +538,7 @@ | ||||
|     }, | ||||
|     "0.4.22": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ABIEncoderV2PackedStorage_0.4.x", | ||||
| @ -543,6 +550,7 @@ | ||||
|     }, | ||||
|     "0.4.23": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ABIEncoderV2PackedStorage_0.4.x", | ||||
| @ -553,6 +561,7 @@ | ||||
|     }, | ||||
|     "0.4.24": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ABIEncoderV2PackedStorage_0.4.x", | ||||
| @ -563,6 +572,7 @@ | ||||
|     }, | ||||
|     "0.4.25": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor_0.4.x", | ||||
|             "IncorrectEventSignatureInLibraries_0.4.x", | ||||
|             "ABIEncoderV2PackedStorage_0.4.x" | ||||
| @ -570,7 +580,9 @@ | ||||
|         "released": "2018-09-12" | ||||
|     }, | ||||
|     "0.4.26": { | ||||
|         "bugs": [], | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2" | ||||
|         ], | ||||
|         "released": "2019-04-29" | ||||
|     }, | ||||
|     "0.4.3": { | ||||
| @ -677,6 +689,7 @@ | ||||
|     }, | ||||
|     "0.5.0": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries", | ||||
|             "ABIEncoderV2PackedStorage" | ||||
| @ -685,6 +698,7 @@ | ||||
|     }, | ||||
|     "0.5.1": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries", | ||||
|             "ABIEncoderV2PackedStorage" | ||||
| @ -693,6 +707,7 @@ | ||||
|     }, | ||||
|     "0.5.2": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries", | ||||
|             "ABIEncoderV2PackedStorage" | ||||
| @ -701,6 +716,7 @@ | ||||
|     }, | ||||
|     "0.5.3": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries", | ||||
|             "ABIEncoderV2PackedStorage" | ||||
| @ -709,6 +725,7 @@ | ||||
|     }, | ||||
|     "0.5.4": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries", | ||||
|             "ABIEncoderV2PackedStorage" | ||||
| @ -717,6 +734,7 @@ | ||||
|     }, | ||||
|     "0.5.5": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries", | ||||
|             "ABIEncoderV2PackedStorage", | ||||
| @ -727,6 +745,7 @@ | ||||
|     }, | ||||
|     "0.5.6": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries", | ||||
|             "ABIEncoderV2PackedStorage", | ||||
| @ -736,13 +755,20 @@ | ||||
|     }, | ||||
|     "0.5.7": { | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2", | ||||
|             "UninitializedFunctionPointerInConstructor", | ||||
|             "IncorrectEventSignatureInLibraries" | ||||
|         ], | ||||
|         "released": "2019-03-26" | ||||
|     }, | ||||
|     "0.5.8": { | ||||
|         "bugs": [], | ||||
|         "bugs": [ | ||||
|             "DynamicConstructorArgumentsClippedABIV2" | ||||
|         ], | ||||
|         "released": "2019-04-30" | ||||
|     }, | ||||
|     "0.5.9": { | ||||
|         "bugs": [], | ||||
|         "released": "2019-05-28" | ||||
|     } | ||||
| } | ||||
| @ -35,6 +35,7 @@ This means that cyclic creation dependencies are impossible. | ||||
| 
 | ||||
|     pragma solidity >=0.4.22 <0.7.0; | ||||
| 
 | ||||
| 
 | ||||
|     contract OwnedToken { | ||||
|         // `TokenCreator` is a contract type that is defined below. | ||||
|         // It is fine to reference it as long as it is not used | ||||
| @ -86,10 +87,11 @@ This means that cyclic creation dependencies are impossible. | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     contract TokenCreator { | ||||
|         function createToken(bytes32 name) | ||||
|            public | ||||
|            returns (OwnedToken tokenAddress) | ||||
|             public | ||||
|             returns (OwnedToken tokenAddress) | ||||
|         { | ||||
|             // Create a new `Token` contract and return its address. | ||||
|             // From the JavaScript side, the return type is | ||||
|  | ||||
| @ -59,7 +59,9 @@ logs that match a topic with a certain address value: | ||||
| 
 | ||||
| The hash of the signature of the event is one of the topics, except if you | ||||
| declared the event with the ``anonymous`` specifier. This means that it is | ||||
| not possible to filter for specific anonymous events by name. | ||||
| not possible to filter for specific anonymous events by name, you can | ||||
| only filter by the contract address. The advantage of anonymous events | ||||
| is that they are cheaper to deploy and call. | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|  | ||||
| @ -12,7 +12,9 @@ is called, except when the contract name is explicitly given or the | ||||
| 
 | ||||
| When a contract inherits from other contracts, only a single | ||||
| contract is created on the blockchain, and the code from all the base contracts | ||||
| is compiled into the created contract. | ||||
| is compiled into the created contract. This means that all internal calls | ||||
| to functions of base contracts also just use internal function calls | ||||
| (``super.f(..)`` will use JUMP and not a message call). | ||||
| 
 | ||||
| The general inheritance system is very similar to | ||||
| `Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_, | ||||
| @ -25,21 +27,24 @@ Details are given in the following example. | ||||
| 
 | ||||
|     pragma solidity >=0.5.0 <0.7.0; | ||||
| 
 | ||||
|     contract owned { | ||||
| 
 | ||||
|     contract Owned { | ||||
|         constructor() public { owner = msg.sender; } | ||||
|         address payable owner; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // Use `is` to derive from another contract. Derived | ||||
|     // contracts can access all non-private members including | ||||
|     // internal functions and state variables. These cannot be | ||||
|     // accessed externally via `this`, though. | ||||
|     contract mortal is owned { | ||||
|     contract Mortal is Owned { | ||||
|         function kill() public { | ||||
|             if (msg.sender == owner) selfdestruct(owner); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // These abstract contracts are only provided to make the | ||||
|     // interface known to the compiler. Note the function | ||||
|     // without body. If a contract does not implement all | ||||
| @ -48,15 +53,17 @@ Details are given in the following example. | ||||
|         function lookup(uint id) public returns (address adr); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     contract NameReg { | ||||
|         function register(bytes32 name) public; | ||||
|         function unregister() public; | ||||
|      } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // Multiple inheritance is possible. Note that `owned` is | ||||
|     // also a base class of `mortal`, yet there is only a single | ||||
|     // instance of `owned` (as for virtual inheritance in C++). | ||||
|     contract named is owned, mortal { | ||||
|     contract Named is Owned, Mortal { | ||||
|         constructor(bytes32 name) public { | ||||
|             Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); | ||||
|             NameReg(config.lookup(1)).register(name); | ||||
| @ -73,22 +80,23 @@ Details are given in the following example. | ||||
|                 NameReg(config.lookup(1)).unregister(); | ||||
|                 // It is still possible to call a specific | ||||
|                 // overridden function. | ||||
|                 mortal.kill(); | ||||
|                 Mortal.kill(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     // If a constructor takes an argument, it needs to be | ||||
|     // provided in the header (or modifier-invocation-style at | ||||
|     // the constructor of the derived contract (see below)). | ||||
|     contract PriceFeed is owned, mortal, named("GoldFeed") { | ||||
|        function updateInfo(uint newInfo) public { | ||||
|           if (msg.sender == owner) info = newInfo; | ||||
|        } | ||||
|     contract PriceFeed is Owned, Mortal, Named("GoldFeed") { | ||||
|         function updateInfo(uint newInfo) public { | ||||
|             if (msg.sender == owner) info = newInfo; | ||||
|         } | ||||
| 
 | ||||
|        function get() public view returns(uint r) { return info; } | ||||
|         function get() public view returns(uint r) { return info; } | ||||
| 
 | ||||
|        uint info; | ||||
|         uint info; | ||||
|     } | ||||
| 
 | ||||
| Note that above, we call ``mortal.kill()`` to "forward" the | ||||
|  | ||||
| @ -49,46 +49,48 @@ more advanced example to implement a set). | ||||
| 
 | ||||
|     pragma solidity >=0.4.22 <0.7.0; | ||||
| 
 | ||||
| 
 | ||||
|     library Set { | ||||
|       // We define a new struct datatype that will be used to | ||||
|       // hold its data in the calling contract. | ||||
|       struct Data { mapping(uint => bool) flags; } | ||||
|         // We define a new struct datatype that will be used to | ||||
|         // hold its data in the calling contract. | ||||
|         struct Data { mapping(uint => bool) flags; } | ||||
| 
 | ||||
|       // Note that the first parameter is of type "storage | ||||
|       // reference" and thus only its storage address and not | ||||
|       // its contents is passed as part of the call.  This is a | ||||
|       // special feature of library functions.  It is idiomatic | ||||
|       // to call the first parameter `self`, if the function can | ||||
|       // be seen as a method of that object. | ||||
|       function insert(Data storage self, uint value) | ||||
|           public | ||||
|           returns (bool) | ||||
|       { | ||||
|           if (self.flags[value]) | ||||
|               return false; // already there | ||||
|           self.flags[value] = true; | ||||
|           return true; | ||||
|       } | ||||
|         // Note that the first parameter is of type "storage | ||||
|         // reference" and thus only its storage address and not | ||||
|         // its contents is passed as part of the call.  This is a | ||||
|         // special feature of library functions.  It is idiomatic | ||||
|         // to call the first parameter `self`, if the function can | ||||
|         // be seen as a method of that object. | ||||
|         function insert(Data storage self, uint value) | ||||
|             public | ||||
|             returns (bool) | ||||
|         { | ||||
|             if (self.flags[value]) | ||||
|                 return false; // already there | ||||
|             self.flags[value] = true; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|       function remove(Data storage self, uint value) | ||||
|           public | ||||
|           returns (bool) | ||||
|       { | ||||
|           if (!self.flags[value]) | ||||
|               return false; // not there | ||||
|           self.flags[value] = false; | ||||
|           return true; | ||||
|       } | ||||
|         function remove(Data storage self, uint value) | ||||
|             public | ||||
|             returns (bool) | ||||
|         { | ||||
|             if (!self.flags[value]) | ||||
|                 return false; // not there | ||||
|             self.flags[value] = false; | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|       function contains(Data storage self, uint value) | ||||
|           public | ||||
|           view | ||||
|           returns (bool) | ||||
|       { | ||||
|           return self.flags[value]; | ||||
|       } | ||||
|         function contains(Data storage self, uint value) | ||||
|             public | ||||
|             view | ||||
|             returns (bool) | ||||
|         { | ||||
|             return self.flags[value]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     contract C { | ||||
|         Set.Data knownValues; | ||||
| 
 | ||||
|  | ||||
| @ -43,7 +43,7 @@ For state variables, ``external`` is not possible. | ||||
| .. note:: | ||||
|     Everything that is inside a contract is visible to | ||||
|     all observers external to the blockchain. Making something ``private`` | ||||
|     only prevents other contracts from accessing and modifying | ||||
|     only prevents other contracts from reading or modifying | ||||
|     the information, but it will still be visible to the | ||||
|     whole world outside of the blockchain. | ||||
| 
 | ||||
|  | ||||
| @ -96,7 +96,7 @@ The option ``--no-smt`` disables the tests that require ``libz3`` and | ||||
| ``--no-ipc`` disables those that require ``aleth``. | ||||
| 
 | ||||
| If you want to run the ipc tests (that test the semantics of the generated code), | ||||
| you need to install `aleth <https://github.com/ethereum/aleth/releases/download/v1.6.0-rc.1/aleth-1.6.0-rc.1-linux-x86_64.tar.gz>`_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``. | ||||
| you need to install `aleth <https://github.com/ethereum/aleth/releases/download/v1.6.0/aleth-1.6.0-linux-x86_64.tar.gz>`_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``. | ||||
| 
 | ||||
| To run the actual tests, use: ``./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc``. | ||||
| 
 | ||||
|  | ||||
| @ -4,10 +4,18 @@ | ||||
| Modular Contracts | ||||
| ***************** | ||||
| 
 | ||||
| A modular approach to building your contracts helps you prevent overflow risks | ||||
| and unexpected tokens. In the example below, the contract uses the ``move`` method | ||||
| A modular approach to building your contracts helps you reduce the complexity | ||||
| and improve the readability which will help to identify bugs and vulnerabilities | ||||
| during development and code review. | ||||
| If you specify and control the behaviour or each module in isolation, the | ||||
| interactions you have to consider are only those between the module specifications | ||||
| and not every other moving part of the contract. | ||||
| In the example below, the contract uses the ``move`` method | ||||
| of the ``Balances`` :ref:`library <libraries>` to check that balances sent between | ||||
| addresses match what you expect. | ||||
| addresses match what you expect. In this way, the ``Balances`` library | ||||
| provides an isolated component that properly tracks balances of accounts. | ||||
| It is easy to verify that the ``Balances`` library never produces negative balances or overflows | ||||
| and the sum of all balances is an invariant across the lifetime of the contract. | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|  | ||||
| @ -20,10 +20,11 @@ Remix | ||||
| 
 | ||||
| *We recommend Remix for small contracts and for quickly learning Solidity.* | ||||
| 
 | ||||
| `Access Remix online <https://remix.ethereum.org/>`_, you don't need to install anything. | ||||
| `Access Remix online <https://remix.ethereum.org/>`_, you do not need to install anything. | ||||
| If you want to use it without connection to the Internet, go to | ||||
| https://github.com/ethereum/remix-live/tree/gh-pages and download the ``.zip`` file as | ||||
| explained on that page. | ||||
| explained on that page. Remix is also a convenient option for testing nightly builds | ||||
| without installing multiple Solidity versions. | ||||
| 
 | ||||
| Further options on this page detail installing commandline Solidity compiler software | ||||
| on your computer. Choose a commandline compiler if you are working on a larger contract | ||||
| @ -60,17 +61,36 @@ Please refer to the solc-js repository for instructions. | ||||
| Docker | ||||
| ====== | ||||
| 
 | ||||
| We provide up to date docker builds for the compiler. The ``stable`` | ||||
| repository contains released versions while the ``nightly`` | ||||
| repository contains potentially unstable changes in the develop branch. | ||||
| Docker images of Solidity builds are available using the ``solc`` image from the ``ethereum`` organisation. | ||||
| Use the ``stable`` tag for the latest released version, and ``nightly`` for potentially unstable changes in the develop branch. | ||||
| 
 | ||||
| The Docker image runs the compiler executable, so you can pass all compiler arguments to it. | ||||
| For example, the command below pulls the stable version of the ``solc`` image (if you do not have it already), | ||||
| and runs it in a new container, passing the ``--help`` argument. | ||||
| 
 | ||||
| .. code-block:: bash | ||||
| 
 | ||||
|     docker run ethereum/solc:stable --version | ||||
|     docker run ethereum/solc:stable --help | ||||
| 
 | ||||
| Currently, the docker image only contains the compiler executable, | ||||
| so you have to do some additional work to link in the source and | ||||
| output directories. | ||||
| You can also specify release build versions in the tag, for example, for the 0.5.4 release. | ||||
| 
 | ||||
| .. code-block:: bash | ||||
| 
 | ||||
|     docker run ethereum/solc:0.5.4 --help | ||||
| 
 | ||||
| To use the Docker image to compile Solidity files on the host machine mount a | ||||
| local folder for input and output, and specify the contract to compile. For example. | ||||
| 
 | ||||
| .. code-block:: bash | ||||
| 
 | ||||
|     docker run -v /local/path:/sources ethereum/solc:stable -o /sources/output --abi --bin /sources/Contract.sol | ||||
| 
 | ||||
| You can also use the standard JSON interface (which is recommended when using the compiler with tooling). | ||||
| When using this interface it is not necessary to mount any directories. | ||||
| 
 | ||||
| .. code-block:: bash | ||||
| 
 | ||||
|     docker run ethereum/solc:stable --standard-json < input.json > output.json | ||||
| 
 | ||||
| Binary Packages | ||||
| =============== | ||||
|  | ||||
| @ -31,10 +31,9 @@ Storage Example | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| The first line simply tells that the source code is written for | ||||
| Solidity version 0.4.0 or anything newer that does not break functionality | ||||
| (up to, but not including, version 0.7.0). This is to ensure that the | ||||
| contract is not compilable with a new (breaking) compiler version, where it could behave differently. | ||||
| The first line tells you that the source code is written for | ||||
| Solidity version 0.4.0, or a newer version of the language up to, but not including version 0.7.0. | ||||
| This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently. | ||||
| :ref:`Pragmas<pragma>` are common instructions for compilers about how to treat the | ||||
| source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_). | ||||
| 
 | ||||
| @ -42,9 +41,9 @@ A contract in the sense of Solidity is a collection of code (its *functions*) an | ||||
| data (its *state*) that resides at a specific address on the Ethereum | ||||
| blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of | ||||
| type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot | ||||
| in a database that can be queried and altered by calling functions of the | ||||
| in a database that you can query and alter by calling functions of the | ||||
| code that manages the database. In the case of Ethereum, this is always the owning | ||||
| contract. And in this case, the functions ``set`` and ``get`` can be used to modify | ||||
| contract. In this case, the functions ``set`` and ``get`` can be used to modify | ||||
| or retrieve the value of the variable. | ||||
| 
 | ||||
| To access a state variable, you do not need the prefix ``this.`` as is common in | ||||
| @ -53,9 +52,9 @@ other languages. | ||||
| This contract does not do much yet apart from (due to the infrastructure | ||||
| built by Ethereum) allowing anyone to store a single number that is accessible by | ||||
| anyone in the world without a (feasible) way to prevent you from publishing | ||||
| this number. Of course, anyone could just call ``set`` again with a different value | ||||
| and overwrite your number, but the number will still be stored in the history | ||||
| of the blockchain. Later, we will see how you can impose access restrictions | ||||
| this number. Anyone could call ``set`` again with a different value | ||||
| and overwrite your number, but the number is still stored in the history | ||||
| of the blockchain. Later, you will see how you can impose access restrictions | ||||
| so that only you can alter the number. | ||||
| 
 | ||||
| .. note:: | ||||
| @ -64,7 +63,7 @@ so that only you can alter the number. | ||||
| 
 | ||||
| .. warning:: | ||||
|     Be careful with using Unicode text, as similar looking (or even identical) characters can | ||||
|     have different code points and as such will be encoded as a different byte array. | ||||
|     have different code points and as such are encoded as a different byte array. | ||||
| 
 | ||||
| .. index:: ! subcurrency | ||||
| 
 | ||||
|  | ||||
| @ -124,30 +124,35 @@ Encoding of the Metadata Hash in the Bytecode | ||||
| ============================================= | ||||
| 
 | ||||
| Because we might support other ways to retrieve the metadata file in the future, | ||||
| the mapping ``{"bzzr0": <Swarm hash>}`` is stored | ||||
| the mapping ``{"bzzr0": <Swarm hash>, "solc": <compiler version>}`` is stored | ||||
| `CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might | ||||
| contain more keys (see below) and the beginning of that | ||||
| encoding is not easy to find, its length is added in a two-byte big-endian | ||||
| encoding. The current version of the Solidity compiler usually adds the following | ||||
| to the end of the deployed bytecode:: | ||||
| 
 | ||||
|     0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29 | ||||
|     0xa2 | ||||
|     0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> | ||||
|     0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> | ||||
|     0x00 0x32 | ||||
| 
 | ||||
| So in order to retrieve the data, the end of the deployed bytecode can be checked | ||||
| to match that pattern and use the Swarm hash to retrieve the file. | ||||
| 
 | ||||
| Whereas release builds of solc use a 3 byte encoding of the version as shown | ||||
| above (one byte each for major, minor and patch version number), prerelease builds | ||||
| will instead use a complete version string including commit hash and build date. | ||||
| 
 | ||||
| .. note:: | ||||
|   The CBOR mapping can also contain other keys, so it is better to fully | ||||
|   decode the data instead of relying on it starting with ``0xa165``. | ||||
|   decode the data instead of relying on it starting with ``0xa265``. | ||||
|   For example, if any experimental features that affect code generation | ||||
|   are used, the mapping will also contain ``"experimental": true``. | ||||
|   Furthermore, we are planning to add the compiler version to the mapping | ||||
|   to ease source-verification and scanning for bugs. | ||||
| 
 | ||||
| .. note:: | ||||
|   The compiler currently uses the "swarm version 0" hash of the metadata, | ||||
|   but this might change in the future, so do not rely on this sequence | ||||
|   to start with ``0xa1 0x65 'b' 'z' 'z' 'r' '0'``. We might also | ||||
|   to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '0'``. We might also | ||||
|   add additional data to this CBOR structure, so the | ||||
|   best option is to use a proper CBOR parser. | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,8 @@ to take too much care, but if you manage your bank account using that web servic | ||||
| you should be more careful. | ||||
| 
 | ||||
| This section will list some pitfalls and general security recommendations but | ||||
| can, of course, never be complete. Also, keep in mind that even if your | ||||
| can, of course, never be complete. | ||||
| Also, keep in mind that even if your | ||||
| smart contract code is bug-free, the compiler or the platform itself might | ||||
| have a bug. A list of some publicly known security-relevant bugs of the compiler | ||||
| can be found in the | ||||
| @ -31,6 +32,10 @@ Solidity compiler. | ||||
| As always, with open source documentation, please help us extend this section | ||||
| (especially, some examples would not hurt)! | ||||
| 
 | ||||
| NOTE: In addition to the list below, you can find more security recommendations and best practices | ||||
| `in Guy Lando's knowledge list <https://github.com/guylando/KnowledgeLists/blob/master/EthereumSmartContracts.md>`_ and | ||||
| `the Consensys GitHub repo <https://consensys.github.io/smart-contract-best-practices/>`_. | ||||
| 
 | ||||
| ******** | ||||
| Pitfalls | ||||
| ******** | ||||
|  | ||||
| @ -26,11 +26,11 @@ doing, an explicit type conversion is sometimes possible. Note that this may | ||||
| give you some unexpected behaviour and allows you to bypass some security | ||||
| features of the compiler, so be sure to test that the | ||||
| result is what you want! Take the following example where you are converting | ||||
| a negative ``int8`` to a ``uint``: | ||||
| a negative ``int`` to a ``uint``: | ||||
| 
 | ||||
| :: | ||||
| 
 | ||||
|     int8 y = -3; | ||||
|     int  y = -3; | ||||
|     uint x = uint(y); | ||||
| 
 | ||||
| At the end of this code snippet, ``x`` will have the value ``0xfffff..fd`` (64 hex | ||||
|  | ||||
| @ -7,6 +7,8 @@ If ``a`` is an LValue (i.e. a variable or something that can be assigned to), th | ||||
| 
 | ||||
| ``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change. | ||||
| 
 | ||||
| .. _delete: | ||||
| 
 | ||||
| delete | ||||
| ------ | ||||
| 
 | ||||
|  | ||||
| @ -217,13 +217,13 @@ Array Members | ||||
|     For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. | ||||
|     Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. | ||||
|     Increasing the length adds new zero-initialised elements to the array. | ||||
|     Reducing the length performs an implicit :ref:``delete`` on each of the | ||||
|     Reducing the length performs an implicit :ref:`delete<delete>` on each of the | ||||
|     removed elements. If you try to resize a non-dynamic array that isn't in | ||||
|     storage, you receive a ``Value must be an lvalue`` error. | ||||
| **push**: | ||||
|      Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length. | ||||
| **pop**: | ||||
|      Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:``delete`` on the removed element. | ||||
|      Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:`delete<delete>` on the removed element. | ||||
| 
 | ||||
| .. warning:: | ||||
|     If you use ``.length--`` on an empty array, it causes an underflow and | ||||
| @ -234,7 +234,7 @@ Array Members | ||||
|     storage is assumed to be zero-initialised, while decreasing | ||||
|     the length has at least linear cost (but in most cases worse than linear), | ||||
|     because it includes explicitly clearing the removed | ||||
|     elements similar to calling :ref:``delete`` on them. | ||||
|     elements similar to calling :ref:`delete<delete>` on them. | ||||
| 
 | ||||
| .. note:: | ||||
|     It is not yet possible to use arrays of arrays in external functions | ||||
| @ -371,7 +371,8 @@ shown in the following example: | ||||
|             campaignID = numCampaigns++; // campaignID is return variable | ||||
|             // Creates new struct in memory and copies it to storage. | ||||
|             // We leave out the mapping type, because it is not valid in memory. | ||||
|             // If structs are copied (even from storage to storage), mapping types | ||||
|             // If structs are copied (even from storage to storage), | ||||
|             // types that are not valid outside of storage (ex. mappings and array of mappings) | ||||
|             // are always omitted, because they cannot be enumerated. | ||||
|             campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); | ||||
|         } | ||||
|  | ||||
| @ -194,7 +194,7 @@ Mathematical and Cryptographic Functions | ||||
|     the ecrecover function remained unchanged. | ||||
| 
 | ||||
|     This is usually not a problem unless you require signatures to be unique or | ||||
|     use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/docs/cryptography_ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue. | ||||
|     use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/v2.3.0/api/cryptography#ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue. | ||||
| 
 | ||||
| .. note:: | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										47
									
								
								docs/yul.rst
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								docs/yul.rst
									
									
									
									
									
								
							| @ -47,20 +47,21 @@ if the backend changes. For a list of mandatory built-in functions, see the sect | ||||
| 
 | ||||
| The following example program assumes that the EVM opcodes ``mul``, ``div`` | ||||
| and ``mod`` are available either natively or as functions and computes exponentiation. | ||||
| As per the warning above, the following code is untyped and can be compiled using ``solc --strict-assembly``. | ||||
| 
 | ||||
| .. code:: | ||||
| 
 | ||||
|     { | ||||
|         function power(base:u256, exponent:u256) -> result:u256 | ||||
|         function power(base, exponent) -> result | ||||
|         { | ||||
|             switch exponent | ||||
|             case 0:u256 { result := 1:u256 } | ||||
|             case 1:u256 { result := base } | ||||
|             case 0 { result := 1 } | ||||
|             case 1 { result := base } | ||||
|             default | ||||
|             { | ||||
|                 result := power(mul(base, base), div(exponent, 2:u256)) | ||||
|                 switch mod(exponent, 2:u256) | ||||
|                     case 1:u256 { result := mul(base, result) } | ||||
|                 result := power(mul(base, base), div(exponent, 2)) | ||||
|                 switch mod(exponent, 2) | ||||
|                     case 1 { result := mul(base, result) } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @ -72,10 +73,10 @@ and ``add`` to be available. | ||||
| .. code:: | ||||
| 
 | ||||
|     { | ||||
|         function power(base:u256, exponent:u256) -> result:u256 | ||||
|         function power(base, exponent) -> result | ||||
|         { | ||||
|             result := 1:u256 | ||||
|             for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) } | ||||
|             result := 1 | ||||
|             for { let i := 0 } lt(i, exponent) { i := add(i, 1) } | ||||
|             { | ||||
|                 result := mul(result, base) | ||||
|             } | ||||
| @ -593,15 +594,21 @@ An example Yul Object is shown below: | ||||
|     // are in scope without nested access. | ||||
|     object "Contract1" { | ||||
|         code { | ||||
|             function allocate(size) -> ptr { | ||||
|                 ptr := mload(0x40) | ||||
|                 if iszero(ptr) { ptr := 0x60 } | ||||
|                 mstore(0x40, add(ptr, size)) | ||||
|             } | ||||
| 
 | ||||
|             // first create "runtime.Contract2" | ||||
|             let size = datasize("runtime.Contract2") | ||||
|             let offset = allocate(size) | ||||
|             let size := datasize("runtime.Contract2") | ||||
|             let offset := allocate(size) | ||||
|             // This will turn into a memory->memory copy for eWASM and | ||||
|             // a codecopy for EVM | ||||
|             datacopy(offset, dataoffset("runtime.Contract2"), size) | ||||
|             // constructor parameter is a single number 0x1234 | ||||
|             mstore(add(offset, size), 0x1234) | ||||
|             create(offset, add(size, 32)) | ||||
|             pop(create(offset, add(size, 32), 0)) | ||||
| 
 | ||||
|             // now return the runtime object (this is | ||||
|             // constructor code) | ||||
| @ -617,16 +624,22 @@ An example Yul Object is shown below: | ||||
| 
 | ||||
|         object "runtime" { | ||||
|             code { | ||||
|                 function allocate(size) -> ptr { | ||||
|                     ptr := mload(0x40) | ||||
|                     if iszero(ptr) { ptr := 0x60 } | ||||
|                     mstore(0x40, add(ptr, size)) | ||||
|                 } | ||||
| 
 | ||||
|                 // runtime code | ||||
| 
 | ||||
|                 let size = datasize("Contract2") | ||||
|                 let offset = allocate(size) | ||||
|                 let size := datasize("Contract2") | ||||
|                 let offset := allocate(size) | ||||
|                 // This will turn into a memory->memory copy for eWASM and | ||||
|                 // a codecopy for EVM | ||||
|                 datacopy(offset, dataoffset("Contract2"), size) | ||||
|                 // constructor parameter is a single number 0x1234 | ||||
|                 mstore(add(offset, size), 0x1234) | ||||
|                 create(offset, add(size, 32)) | ||||
|                 pop(create(offset, add(size, 32), 0)) | ||||
|             } | ||||
| 
 | ||||
|             // Embedded object. Use case is that the outside is a factory contract, | ||||
| @ -640,9 +653,9 @@ An example Yul Object is shown below: | ||||
|                     code { | ||||
|                         // code here ... | ||||
|                     } | ||||
|                  } | ||||
|                 } | ||||
| 
 | ||||
|                  data "Table1" hex"4123" | ||||
|                 data "Table1" hex"4123" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -12,10 +12,13 @@ set(sources | ||||
| 	FixedHash.h | ||||
| 	IndentedWriter.cpp | ||||
| 	IndentedWriter.h | ||||
| 	IpfsHash.cpp | ||||
| 	IpfsHash.h | ||||
| 	JSON.cpp | ||||
| 	JSON.h | ||||
| 	Keccak256.cpp | ||||
| 	Keccak256.h | ||||
| 	picosha2.h | ||||
| 	Result.h | ||||
| 	StringUtils.cpp | ||||
| 	StringUtils.h | ||||
|  | ||||
| @ -91,6 +91,20 @@ inline u256 s2u(s256 _u) | ||||
| 		return u256(c_end + _u); | ||||
| } | ||||
| 
 | ||||
| inline u256 exp256(u256 _base, u256 _exponent) | ||||
| { | ||||
| 	using boost::multiprecision::limb_type; | ||||
| 	u256 result = 1; | ||||
| 	while (_exponent) | ||||
| 	{ | ||||
| 		if (boost::multiprecision::bit_test(_exponent, 0)) | ||||
| 			result *= _base; | ||||
| 		_base *= _base; | ||||
| 		_exponent >>= 1; | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes) | ||||
| { | ||||
| 	std::ostringstream ss; | ||||
|  | ||||
| @ -111,7 +111,7 @@ enum class HexCase | ||||
| /// @example toHex("A\x69") == "4169"
 | ||||
| std::string toHex(bytes const& _data, HexPrefix _prefix = HexPrefix::DontAdd, HexCase _case = HexCase::Lower); | ||||
| 
 | ||||
| /// Converts a (printable) ASCII hex character into the correspnding integer value.
 | ||||
| /// Converts a (printable) ASCII hex character into the corresponding integer value.
 | ||||
| /// @example fromHex('A') == 10 && fromHex('f') == 15 && fromHex('5') == 5
 | ||||
| int fromHex(char _i, WhenError _throw); | ||||
| 
 | ||||
| @ -188,12 +188,6 @@ inline bytes toCompactBigEndian(uint8_t _val, unsigned _min = 0) | ||||
| 	return (_min || _val) ? bytes{ _val } : bytes{}; | ||||
| } | ||||
| 
 | ||||
| /// Workarounds shift left bug in boost <1.65.1.
 | ||||
| template <class S> S bigintShiftLeftWorkaround(S const& _a, unsigned _b) | ||||
| { | ||||
| 	return (S)(bigint(_a) << _b); | ||||
| } | ||||
| 
 | ||||
| /// Convenience function for conversion of a u256 to hex
 | ||||
| inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd) | ||||
| { | ||||
| @ -352,4 +346,27 @@ inline std::string findAnyOf(std::string const& _haystack, std::vector<std::stri | ||||
| 			return needle; | ||||
| 	return ""; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| namespace detail | ||||
| { | ||||
| template<typename T> | ||||
| void variadicEmplaceBack(std::vector<T>&) {} | ||||
| template<typename T, typename A, typename... Args> | ||||
| void variadicEmplaceBack(std::vector<T>& _vector, A&& _a, Args&&... _args) | ||||
| { | ||||
| 	_vector.emplace_back(std::forward<A>(_a)); | ||||
| 	variadicEmplaceBack(_vector, std::forward<Args>(_args)...); | ||||
| } | ||||
| } | ||||
| 
 | ||||
| template<typename T, typename... Args> | ||||
| std::vector<T> make_vector(Args&&... _args) | ||||
| { | ||||
| 	std::vector<T> result; | ||||
| 	result.reserve(sizeof...(_args)); | ||||
| 	detail::variadicEmplaceBack(result, std::forward<Args>(_args)...); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -47,6 +47,7 @@ private: | ||||
| DEV_SIMPLE_EXCEPTION(InvalidAddress); | ||||
| DEV_SIMPLE_EXCEPTION(BadHexCharacter); | ||||
| DEV_SIMPLE_EXCEPTION(FileError); | ||||
| DEV_SIMPLE_EXCEPTION(DataTooLong); | ||||
| 
 | ||||
| // error information to be added to exceptions
 | ||||
| using errinfo_invalidSymbol = boost::error_info<struct tag_invalidSymbol, char>; | ||||
|  | ||||
							
								
								
									
										93
									
								
								libdevcore/IpfsHash.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								libdevcore/IpfsHash.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| #include <libdevcore/IpfsHash.h> | ||||
| 
 | ||||
| #include <libdevcore/Exceptions.h> | ||||
| #include <libdevcore/picosha2.h> | ||||
| #include <libdevcore/CommonData.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| bytes varintEncoding(size_t _n) | ||||
| { | ||||
| 	bytes encoded; | ||||
| 	while (_n > 0x7f) | ||||
| 	{ | ||||
| 		encoded.emplace_back(uint8_t(0x80 | (_n & 0x7f))); | ||||
| 		_n >>= 7; | ||||
| 	} | ||||
| 	encoded.emplace_back(_n); | ||||
| 	return encoded; | ||||
| } | ||||
| 
 | ||||
| string base58Encode(bytes const& _data) | ||||
| { | ||||
| 	static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"}; | ||||
| 	bigint data(toHex(_data, HexPrefix::Add)); | ||||
| 	string output; | ||||
| 	while (data) | ||||
| 	{ | ||||
| 		output += alphabet[size_t(data % alphabet.size())]; | ||||
| 		data /= alphabet.size(); | ||||
| 	} | ||||
| 	reverse(output.begin(), output.end()); | ||||
| 	return output; | ||||
| } | ||||
| } | ||||
| 
 | ||||
| bytes dev::ipfsHash(string _data) | ||||
| { | ||||
| 	if (_data.length() >= 1024 * 256) | ||||
| 		BOOST_THROW_EXCEPTION( | ||||
| 			DataTooLong() << | ||||
| 			errinfo_comment("Ipfs hash for large (chunked) files not yet implemented.") | ||||
| 		); | ||||
| 
 | ||||
| 	bytes lengthAsVarint = varintEncoding(_data.size()); | ||||
| 
 | ||||
| 	bytes protobufEncodedData; | ||||
| 	// Type: File
 | ||||
| 	protobufEncodedData += bytes{0x08, 0x02}; | ||||
| 	if (!_data.empty()) | ||||
| 	{ | ||||
| 		// Data (length delimited bytes)
 | ||||
| 		protobufEncodedData += bytes{0x12}; | ||||
| 		protobufEncodedData += lengthAsVarint; | ||||
| 		protobufEncodedData += asBytes(std::move(_data)); | ||||
| 	} | ||||
| 	// filesize: length as varint
 | ||||
| 	protobufEncodedData += bytes{0x18} + lengthAsVarint; | ||||
| 
 | ||||
| 	// PBDag:
 | ||||
| 	// Data: (length delimited bytes)
 | ||||
| 	size_t protobufLength = protobufEncodedData.size(); | ||||
| 	bytes blockData = bytes{0x0a} + varintEncoding(protobufLength) + std::move(protobufEncodedData); | ||||
| 	// TODO Handle "large" files with multiple blocks
 | ||||
| 
 | ||||
| 	// Multihash: sha2-256, 256 bits
 | ||||
| 	bytes hash = bytes{0x12, 0x20} + picosha2::hash256(std::move(blockData)); | ||||
| 	return hash; | ||||
| } | ||||
| 
 | ||||
| string dev::ipfsHashBase58(string _data) | ||||
| { | ||||
| 	return base58Encode(ipfsHash(std::move(_data))); | ||||
| } | ||||
							
								
								
									
										37
									
								
								libdevcore/IpfsHash.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								libdevcore/IpfsHash.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libdevcore/Common.h> | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| 
 | ||||
| /// Compute the "ipfs hash" of a file with the content @a _data.
 | ||||
| /// The output will be the multihash of the UnixFS protobuf encoded data.
 | ||||
| /// As hash function it will use sha2-256.
 | ||||
| /// The effect is that the hash should be identical to the one produced by
 | ||||
| /// the command `ipfs add <filename>`.
 | ||||
| bytes ipfsHash(std::string _data); | ||||
| 
 | ||||
| /// Compute the "ipfs hash" as above, but encoded in base58 as used by ipfs / bitcoin.
 | ||||
| std::string ipfsHashBase58(std::string _data); | ||||
| 
 | ||||
| } | ||||
| @ -74,6 +74,22 @@ std::string joinHumanReadable | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| /// Joins collection of strings just like joinHumanReadable, but prepends the separator
 | ||||
| /// unless the collection is empty.
 | ||||
| template<class T> | ||||
| std::string joinHumanReadablePrefixed | ||||
| ( | ||||
| 	T const& _list, | ||||
| 	std::string const& _separator = ", ", | ||||
| 	std::string const& _lastSeparator = "" | ||||
| ) | ||||
| { | ||||
| 	if (begin(_list) == end(_list)) | ||||
| 		return {}; | ||||
| 	else | ||||
| 		return _separator + joinHumanReadable(_list, _separator, _lastSeparator); | ||||
| } | ||||
| 
 | ||||
| /// Formats large numbers to be easily readable by humans.
 | ||||
| /// Returns decimal representation for smaller numbers; hex for large numbers.
 | ||||
| /// "Special" numbers, powers-of-two and powers-of-two minus 1, are returned in
 | ||||
|  | ||||
| @ -30,64 +30,73 @@ | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| 
 | ||||
| Whiskers::Whiskers(string const& _template): | ||||
| m_template(_template) | ||||
| Whiskers::Whiskers(string _template): | ||||
| 	m_template(move(_template)) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value) | ||||
| Whiskers& Whiskers::operator()(string _parameter, string _value) | ||||
| { | ||||
| 	assertThrow( | ||||
| 		m_parameters.count(_parameter) == 0, | ||||
| 		WhiskersError, | ||||
| 		_parameter + " already set." | ||||
| 	); | ||||
| 	assertThrow( | ||||
| 		m_listParameters.count(_parameter) == 0, | ||||
| 		WhiskersError, | ||||
| 		_parameter + " already set as list parameter." | ||||
| 	); | ||||
| 	m_parameters[_parameter] = _value; | ||||
| 
 | ||||
| 	checkParameterUnknown(_parameter); | ||||
| 	m_parameters[move(_parameter)] = move(_value); | ||||
| 	return *this; | ||||
| } | ||||
| 
 | ||||
| Whiskers& Whiskers::operator ()( | ||||
| 	string const& _listParameter, | ||||
| 	vector<map<string, string>> const& _values | ||||
| Whiskers& Whiskers::operator()(string _parameter, bool _value) | ||||
| { | ||||
| 	checkParameterUnknown(_parameter); | ||||
| 	m_conditions[move(_parameter)] = _value; | ||||
| 	return *this; | ||||
| } | ||||
| 
 | ||||
| Whiskers& Whiskers::operator()( | ||||
| 	string _listParameter, | ||||
| 	vector<map<string, string>> _values | ||||
| ) | ||||
| { | ||||
| 	assertThrow( | ||||
| 		m_listParameters.count(_listParameter) == 0, | ||||
| 		WhiskersError, | ||||
| 		_listParameter + " already set." | ||||
| 	); | ||||
| 	assertThrow( | ||||
| 		m_parameters.count(_listParameter) == 0, | ||||
| 		WhiskersError, | ||||
| 		_listParameter + " already set as value parameter." | ||||
| 	); | ||||
| 	m_listParameters[_listParameter] = _values; | ||||
| 
 | ||||
| 	checkParameterUnknown(_listParameter); | ||||
| 	m_listParameters[move(_listParameter)] = move(_values); | ||||
| 	return *this; | ||||
| } | ||||
| 
 | ||||
| string Whiskers::render() const | ||||
| { | ||||
| 	return replace(m_template, m_parameters, m_listParameters); | ||||
| 	return replace(m_template, m_parameters, m_conditions, m_listParameters); | ||||
| } | ||||
| 
 | ||||
| void Whiskers::checkParameterUnknown(string const& _parameter) | ||||
| { | ||||
| 	assertThrow( | ||||
| 		!m_parameters.count(_parameter), | ||||
| 		WhiskersError, | ||||
| 		_parameter + " already set as value parameter." | ||||
| 	); | ||||
| 	assertThrow( | ||||
| 		!m_conditions.count(_parameter), | ||||
| 		WhiskersError, | ||||
| 		_parameter + " already set as condition parameter." | ||||
| 	); | ||||
| 	assertThrow( | ||||
| 		!m_listParameters.count(_parameter), | ||||
| 		WhiskersError, | ||||
| 		_parameter + " already set as list parameter." | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| string Whiskers::replace( | ||||
| 	string const& _template, | ||||
| 	StringMap const& _parameters, | ||||
| 	map<string, bool> const& _conditions, | ||||
| 	map<string, vector<StringMap>> const& _listParameters | ||||
| ) | ||||
| { | ||||
| 	using namespace boost; | ||||
| 	static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>"); | ||||
| 	static regex listOrTag("<([^#/?!>]+)>|<#([^>]+)>(.*?)</\\2>|<\\?([^>]+)>(.*?)(<!\\4>(.*?))?</\\4>"); | ||||
| 	return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string | ||||
| 	{ | ||||
| 		string tagName(_match[1]); | ||||
| 		string listName(_match[2]); | ||||
| 		string conditionName(_match[4]); | ||||
| 		if (!tagName.empty()) | ||||
| 		{ | ||||
| 			assertThrow( | ||||
| @ -99,20 +108,32 @@ string Whiskers::replace( | ||||
| 			); | ||||
| 			return _parameters.at(tagName); | ||||
| 		} | ||||
| 		else | ||||
| 		else if (!listName.empty()) | ||||
| 		{ | ||||
| 			string listName(_match[2]); | ||||
| 			string templ(_match[3]); | ||||
| 			assertThrow(!listName.empty(), WhiskersError, ""); | ||||
| 			assertThrow( | ||||
| 				_listParameters.count(listName), | ||||
| 				WhiskersError, "List parameter " + listName + " not set." | ||||
| 			); | ||||
| 			string replacement; | ||||
| 			for (auto const& parameters: _listParameters.at(listName)) | ||||
| 				replacement += replace(templ, joinMaps(_parameters, parameters)); | ||||
| 				replacement += replace(templ, joinMaps(_parameters, parameters), _conditions); | ||||
| 			return replacement; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			assertThrow(!conditionName.empty(), WhiskersError, ""); | ||||
| 			assertThrow( | ||||
| 				_conditions.count(conditionName), | ||||
| 				WhiskersError, "Condition parameter " + conditionName + " not set." | ||||
| 			); | ||||
| 			return replace( | ||||
| 				_conditions.at(conditionName) ? _match[5] : _match[7], | ||||
| 				_parameters, | ||||
| 				_conditions, | ||||
| 				_listParameters | ||||
| 			); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -34,45 +34,63 @@ namespace dev | ||||
| 
 | ||||
| DEV_SIMPLE_EXCEPTION(WhiskersError); | ||||
| 
 | ||||
| ///
 | ||||
| /// Moustache-like templates.
 | ||||
| ///
 | ||||
| /// Usage:
 | ||||
| ///     std::vector<std::map<std::string, std::string>> listValues(2);
 | ||||
| ///     listValues[0]["k"] = "key1";
 | ||||
| ///     listValues[0]["v"] = "value1";
 | ||||
| ///     listValues[1]["k"] = "key2";
 | ||||
| ///     listValues[1]["v"] = "value2";
 | ||||
| ///     auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>")
 | ||||
| ///         ("p1", "HEAD")
 | ||||
| ///         ("list", listValues)
 | ||||
| ///         .render();
 | ||||
| ///
 | ||||
| /// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
 | ||||
| ///
 | ||||
| /// Note that lists cannot themselves contain lists - this would be a future feature.
 | ||||
| /**
 | ||||
|  * Moustache-like templates. | ||||
|  * | ||||
|  * Usage: | ||||
|  *     std::vector<std::map<std::string, std::string>> listValues(2); | ||||
|  *     listValues[0]["k"] = "key1"; | ||||
|  *     listValues[0]["v"] = "value1"; | ||||
|  *     listValues[1]["k"] = "key2"; | ||||
|  *     listValues[1]["v"] = "value2"; | ||||
|  *     auto s = Whiskers("<?c><p1><!c>y</c>\n<#list><k> -> <v>\n</list>") | ||||
|  *         ("p1", "HEAD") | ||||
|  *         ("c", true) | ||||
|  *         ("list", listValues) | ||||
|  *         .render(); | ||||
|  * | ||||
|  * results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" | ||||
|  * | ||||
|  * Note that lists cannot themselves contain lists - this would be a future feature. | ||||
|  * | ||||
|  * The elements are: | ||||
|  *  - Regular parameter: <name> | ||||
|  *    just replaced | ||||
|  *  - Condition parameter: <?name>...<!name>...</name>, where "<!name>" is optional | ||||
|  *    replaced (and recursively expanded) by the first part if the condition is true | ||||
|  *    and by the second (or empty string if missing) if the condition is false | ||||
|  *  - List parameter: <#list>...</list> | ||||
|  *    The part between the tags is repeated as often as values are provided | ||||
|  *    in the mapping. Each list element can have its own parameter -> value mapping. | ||||
|  */ | ||||
| class Whiskers | ||||
| { | ||||
| public: | ||||
| 	using StringMap = std::map<std::string, std::string>; | ||||
| 	using StringListMap = std::map<std::string, std::vector<StringMap>>; | ||||
| 
 | ||||
| 	explicit Whiskers(std::string const& _template); | ||||
| 	explicit Whiskers(std::string _template); | ||||
| 
 | ||||
| 	/// Sets a single parameter, <paramName>.
 | ||||
| 	Whiskers& operator()(std::string const& _parameter, std::string const& _value); | ||||
| 	/// Sets a single regular parameter, <paramName>.
 | ||||
| 	Whiskers& operator()(std::string _parameter, std::string _value); | ||||
| 	Whiskers& operator()(std::string _parameter, char const* _value) { return (*this)(_parameter, std::string{_value}); } | ||||
| 	/// Sets a condition parameter, <?paramName>...<!paramName>...</paramName>
 | ||||
| 	Whiskers& operator()(std::string _parameter, bool _value); | ||||
| 	/// Sets a list parameter, <#listName> </listName>.
 | ||||
| 	Whiskers& operator()( | ||||
| 		std::string const& _listParameter, | ||||
| 		std::vector<StringMap> const& _values | ||||
| 		std::string _listParameter, | ||||
| 		std::vector<StringMap> _values | ||||
| 	); | ||||
| 
 | ||||
| 	std::string render() const; | ||||
| 
 | ||||
| private: | ||||
| 	void checkParameterUnknown(std::string const& _parameter); | ||||
| 
 | ||||
| 	static std::string replace( | ||||
| 		std::string const& _template, | ||||
| 		StringMap const& _parameters, | ||||
| 		std::map<std::string, bool> const& _conditions, | ||||
| 		StringListMap const& _listParameters = StringListMap() | ||||
| 	); | ||||
| 
 | ||||
| @ -81,6 +99,7 @@ private: | ||||
| 
 | ||||
| 	std::string m_template; | ||||
| 	StringMap m_parameters; | ||||
| 	std::map<std::string, bool> m_conditions; | ||||
| 	StringListMap m_listParameters; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										288
									
								
								libdevcore/picosha2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								libdevcore/picosha2.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,288 @@ | ||||
| /*
 | ||||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (C) 2014 okdshin | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
| */ | ||||
| #pragma once | ||||
| 
 | ||||
| //picosha2:20140213
 | ||||
| #include <cstdint> | ||||
| #include <iostream> | ||||
| #include <vector> | ||||
| #include <iterator> | ||||
| #include <cassert> | ||||
| #include <sstream> | ||||
| #include <algorithm> | ||||
| 
 | ||||
| namespace picosha2 | ||||
| { | ||||
| 
 | ||||
| namespace detail | ||||
| { | ||||
| 
 | ||||
| inline uint8_t mask_8bit(uint8_t x) | ||||
| { | ||||
| 	return x & 0xff; | ||||
| } | ||||
| 
 | ||||
| inline uint32_t mask_32bit(uint32_t x) | ||||
| { | ||||
| 	return x & 0xffffffff; | ||||
| } | ||||
| 
 | ||||
| static uint32_t const add_constant[64] = { | ||||
| 	0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, | ||||
| 	0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | ||||
| 	0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, | ||||
| 	0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | ||||
| 	0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, | ||||
| 	0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | ||||
| 	0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, | ||||
| 	0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | ||||
| 	0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, | ||||
| 	0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | ||||
| 	0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, | ||||
| 	0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | ||||
| 	0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, | ||||
| 	0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | ||||
| 	0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, | ||||
| 	0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 | ||||
| }; | ||||
| 
 | ||||
| static uint32_t const initial_message_digest[8] = { | ||||
| 	0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, | ||||
| 	0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 | ||||
| }; | ||||
| 
 | ||||
| inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z) | ||||
| { | ||||
| 	return (x & y) ^ ((~x) & z); | ||||
| } | ||||
| 
 | ||||
| inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z) | ||||
| { | ||||
| 	return (x & y) ^ (x & z) ^ (y & z); | ||||
| } | ||||
| 
 | ||||
| inline uint32_t rotr(uint32_t x, std::size_t n) | ||||
| { | ||||
| 	assert(n < 32); | ||||
| 	return mask_32bit((x >> n) | (x << (32 - n))); | ||||
| } | ||||
| 
 | ||||
| inline uint32_t bsig0(uint32_t x) | ||||
| { | ||||
| 	return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); | ||||
| } | ||||
| 
 | ||||
| inline uint32_t bsig1(uint32_t x) | ||||
| { | ||||
| 	return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); | ||||
| } | ||||
| 
 | ||||
| inline uint32_t shr(uint32_t x, std::size_t n) | ||||
| { | ||||
| 	assert(n < 32); | ||||
| 	return x >> n; | ||||
| } | ||||
| 
 | ||||
| inline uint32_t ssig0(uint32_t x) | ||||
| { | ||||
| 	return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3); | ||||
| } | ||||
| 
 | ||||
| inline uint32_t ssig1(uint32_t x) | ||||
| { | ||||
| 	return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10); | ||||
| } | ||||
| 
 | ||||
| template<typename RaIter1, typename RaIter2> | ||||
| void hash256_block(RaIter1 message_digest, RaIter2 first, RaIter2 last) | ||||
| { | ||||
| 	(void)last; // FIXME: check this is valid
 | ||||
| 	uint32_t w[64]; | ||||
| 	std::fill(w, w+64, 0); | ||||
| 	for (std::size_t i = 0; i < 16; ++i) | ||||
| 		w[i] = (static_cast<uint32_t>(mask_8bit(*(first + i * 4))) << 24) | ||||
| 			| (static_cast<uint32_t>(mask_8bit(*(first + i * 4 + 1))) << 16) | ||||
| 			| (static_cast<uint32_t>(mask_8bit(*(first + i * 4 + 2))) << 8) | ||||
| 			| (static_cast<uint32_t>(mask_8bit(*(first + i * 4 + 3)))); | ||||
| 	for (std::size_t i = 16; i < 64; ++i) | ||||
| 		w[i] = mask_32bit(ssig1(w[i-2])+w[i-7]+ssig0(w[i-15])+w[i-16]); | ||||
| 
 | ||||
| 	uint32_t a = *message_digest; | ||||
| 	uint32_t b = *(message_digest + 1); | ||||
| 	uint32_t c = *(message_digest + 2); | ||||
| 	uint32_t d = *(message_digest + 3); | ||||
| 	uint32_t e = *(message_digest + 4); | ||||
| 	uint32_t f = *(message_digest + 5); | ||||
| 	uint32_t g = *(message_digest + 6); | ||||
| 	uint32_t h = *(message_digest + 7); | ||||
| 
 | ||||
| 	for (std::size_t i = 0; i < 64; ++i) | ||||
| 	{ | ||||
| 		uint32_t temp1 = h+bsig1(e)+ch(e,f,g)+add_constant[i]+w[i]; | ||||
| 		uint32_t temp2 = bsig0(a)+maj(a,b,c); | ||||
| 		h = g; | ||||
| 		g = f; | ||||
| 		f = e; | ||||
| 		e = mask_32bit(d+temp1); | ||||
| 		d = c; | ||||
| 		c = b; | ||||
| 		b = a; | ||||
| 		a = mask_32bit(temp1+temp2); | ||||
| 	} | ||||
| 	*message_digest += a; | ||||
| 	*(message_digest+1) += b; | ||||
| 	*(message_digest+2) += c; | ||||
| 	*(message_digest+3) += d; | ||||
| 	*(message_digest+4) += e; | ||||
| 	*(message_digest+5) += f; | ||||
| 	*(message_digest+6) += g; | ||||
| 	*(message_digest+7) += h; | ||||
| 	for (std::size_t i = 0; i < 8; ++i) | ||||
| 		*(message_digest+i) = mask_32bit(*(message_digest+i)); | ||||
| } | ||||
| 
 | ||||
| }//namespace detail
 | ||||
| 
 | ||||
| class hash256_one_by_one | ||||
| { | ||||
| public: | ||||
| 	hash256_one_by_one() | ||||
| 	{ | ||||
| 		init(); | ||||
| 	} | ||||
| 
 | ||||
| 	void init() | ||||
| 	{ | ||||
| 		buffer_.clear(); | ||||
| 		std::fill(data_length_digits_, data_length_digits_ + 4, 0); | ||||
| 		std::copy(detail::initial_message_digest, detail::initial_message_digest+8, h_); | ||||
| 	} | ||||
| 
 | ||||
| 	template<typename RaIter> | ||||
| 	void process(RaIter first, RaIter last) | ||||
| 	{ | ||||
| 		add_to_data_length(std::distance(first, last)); | ||||
| 		std::copy(first, last, std::back_inserter(buffer_)); | ||||
| 		std::size_t i = 0; | ||||
| 		for (;i + 64 <= buffer_.size(); i+=64) | ||||
| 			detail::hash256_block(h_, buffer_.begin()+i, buffer_.begin()+i+64); | ||||
| 		buffer_.erase(buffer_.begin(), buffer_.begin()+i); | ||||
| 	} | ||||
| 
 | ||||
| 	void finish() | ||||
| 	{ | ||||
| 		uint8_t temp[64]; | ||||
| 		std::fill(temp, temp+64, 0); | ||||
| 		std::size_t remains = buffer_.size(); | ||||
| 		std::copy(buffer_.begin(), buffer_.end(), temp); | ||||
| 		temp[remains] = 0x80; | ||||
| 
 | ||||
| 		if (remains > 55) | ||||
| 		{ | ||||
| 			std::fill(temp+remains+1, temp+64, 0); | ||||
| 			detail::hash256_block(h_, temp, temp+64); | ||||
| 			std::fill(temp, temp+64-4, 0); | ||||
| 		} | ||||
| 		else | ||||
| 			std::fill(temp+remains+1, temp+64-4, 0); | ||||
| 
 | ||||
| 		write_data_bit_length(&(temp[56])); | ||||
| 		detail::hash256_block(h_, temp, temp+64); | ||||
| 	} | ||||
| 
 | ||||
| 	template<typename OutIter> | ||||
| 	void get_hash_bytes(OutIter first, OutIter last) const | ||||
| 	{ | ||||
| 		for (uint32_t const* iter = h_; iter != h_ + 8; ++iter) | ||||
| 			for (std::size_t i = 0; i < 4 && first != last; ++i) | ||||
| 				*(first++) = detail::mask_8bit(static_cast<uint8_t>(*iter >> (24 - 8 * i))); | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	void add_to_data_length(uint32_t n) | ||||
| 	{ | ||||
| 		uint32_t carry = 0; | ||||
| 		data_length_digits_[0] += n; | ||||
| 		for (std::size_t i = 0; i < 4; ++i) | ||||
| 		{ | ||||
| 			data_length_digits_[i] += carry; | ||||
| 			if (data_length_digits_[i] >= 65536u) | ||||
| 			{ | ||||
| 				carry = data_length_digits_[i] >> 16; | ||||
| 				data_length_digits_[i] &= 65535u; | ||||
| 			} | ||||
| 			else | ||||
| 				break; | ||||
| 		} | ||||
| 	} | ||||
| 	void write_data_bit_length(uint8_t* begin) | ||||
| 	{ | ||||
| 		uint32_t data_bit_length_digits[4]; | ||||
| 		std::copy( | ||||
| 			data_length_digits_, data_length_digits_ + 4, | ||||
| 			data_bit_length_digits | ||||
| 		); | ||||
| 
 | ||||
| 		// convert byte length to bit length (multiply 8 or shift 3 times left)
 | ||||
| 		uint32_t carry = 0; | ||||
| 		for (std::size_t i = 0; i < 4; ++i) | ||||
| 		{ | ||||
| 			uint32_t before_val = data_bit_length_digits[i]; | ||||
| 			data_bit_length_digits[i] <<= 3; | ||||
| 			data_bit_length_digits[i] |= carry; | ||||
| 			data_bit_length_digits[i] &= 65535u; | ||||
| 			carry = (before_val >> (16-3)) & 65535u; | ||||
| 		} | ||||
| 
 | ||||
| 		// write data_bit_length
 | ||||
| 		for (int i = 3; i >= 0; --i) | ||||
| 		{ | ||||
| 			(*begin++) = static_cast<uint8_t>(data_bit_length_digits[i] >> 8); | ||||
| 			(*begin++) = static_cast<uint8_t>(data_bit_length_digits[i]); | ||||
| 		} | ||||
| 	} | ||||
| 	std::vector<uint8_t> buffer_; | ||||
| 	uint32_t data_length_digits_[4]; //as 64bit integer (16bit x 4 integer)
 | ||||
| 	uint32_t h_[8]; | ||||
| }; | ||||
| 
 | ||||
| template<typename RaIter, typename OutIter> | ||||
| void hash256(RaIter first, RaIter last, OutIter first2, OutIter last2) | ||||
| { | ||||
| 	hash256_one_by_one hasher; | ||||
| 	//hasher.init();
 | ||||
| 	hasher.process(first, last); | ||||
| 	hasher.finish(); | ||||
| 	hasher.get_hash_bytes(first2, last2); | ||||
| } | ||||
| 
 | ||||
| template <typename RaContainer> | ||||
| std::vector<uint8_t> hash256(RaContainer const& _src) | ||||
| { | ||||
| 	std::vector<uint8_t> ret(32); | ||||
| 	hash256(_src.begin(), _src.end(), ret.begin(), ret.end()); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| }//namespace picosha2
 | ||||
| @ -25,6 +25,8 @@ public: | ||||
| 	using mutable_value_type = typename std::conditional<std::is_const<_T>::value, typename std::remove_const<_T>::type, _T>::type; | ||||
| 	using string_type = typename std::conditional<std::is_const<_T>::value, std::string const, std::string>::type; | ||||
| 	using vector_type = typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const, std::vector<_T>>::type; | ||||
| 	using iterator = _T*; | ||||
| 	using const_iterator = _T const*; | ||||
| 
 | ||||
| 	static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data."); | ||||
| 
 | ||||
|  | ||||
| @ -151,6 +151,7 @@ bigint CodeCopyMethod::gasNeeded() const | ||||
| AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const | ||||
| { | ||||
| 	bytes data = toBigEndian(m_value); | ||||
| 	assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding."); | ||||
| 	AssemblyItems actualCopyRoutine = copyRoutine(); | ||||
| 	actualCopyRoutine[4] = _assembly.newData(data); | ||||
| 	return actualCopyRoutine; | ||||
| @ -159,15 +160,25 @@ AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const | ||||
| AssemblyItems const& CodeCopyMethod::copyRoutine() | ||||
| { | ||||
| 	AssemblyItems static copyRoutine{ | ||||
| 		// constant to be reused 3+ times
 | ||||
| 		u256(0), | ||||
| 
 | ||||
| 		// back up memory
 | ||||
| 		// mload(0)
 | ||||
| 		Instruction::DUP1, | ||||
| 		Instruction::MLOAD, // back up memory
 | ||||
| 		Instruction::MLOAD, | ||||
| 
 | ||||
| 		// codecopy(0, <offset>, 32)
 | ||||
| 		u256(32), | ||||
| 		AssemblyItem(PushData, u256(1) << 16), // has to be replaced
 | ||||
| 		AssemblyItem(PushData, u256(1) << 16), // replaced above in actualCopyRoutine[4]
 | ||||
| 		Instruction::DUP4, | ||||
| 		Instruction::CODECOPY, | ||||
| 
 | ||||
| 		// mload(0)
 | ||||
| 		Instruction::DUP2, | ||||
| 		Instruction::MLOAD, | ||||
| 
 | ||||
| 		// restore original memory
 | ||||
| 		Instruction::SWAP2, | ||||
| 		Instruction::MSTORE | ||||
| 	}; | ||||
| @ -211,11 +222,16 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) | ||||
| 			if (lowerPart != 0) | ||||
| 				newRoutine += findRepresentation(u256(abs(lowerPart))); | ||||
| 			if (m_params.evmVersion.hasBitwiseShifting()) | ||||
| 				newRoutine += AssemblyItems{u256(1), u256(bits), Instruction::SHL}; | ||||
| 			{ | ||||
| 				newRoutine += findRepresentation(upperPart); | ||||
| 				newRoutine += AssemblyItems{u256(bits), Instruction::SHL}; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP}; | ||||
| 			if (upperPart != 1) | ||||
| 				newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; | ||||
| 				if (upperPart != 1) | ||||
| 					newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; | ||||
| 			} | ||||
| 			if (lowerPart > 0) | ||||
| 				newRoutine += AssemblyItems{Instruction::ADD}; | ||||
| 			else if (lowerPart < 0) | ||||
|  | ||||
| @ -47,6 +47,13 @@ template <class S> S modWorkaround(S const& _a, S const& _b) | ||||
| 	return (S)(bigint(_a) % bigint(_b)); | ||||
| } | ||||
| 
 | ||||
| // This works around a bug fixed with Boost 1.64.
 | ||||
| // https://www.boost.org/doc/libs/1_68_0/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64
 | ||||
| inline u256 shlWorkaround(u256 const& _x, unsigned _amount) | ||||
| { | ||||
| 	return u256((bigint(_x) << _amount) & u256(-1)); | ||||
| } | ||||
| 
 | ||||
| // simplificationRuleList below was split up into parts to prevent
 | ||||
| // stack overflows in the JavaScript optimizer for emscripten builds
 | ||||
| // that affected certain browser versions.
 | ||||
| @ -93,7 +100,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1( | ||||
| 		{{Instruction::SHL, {A, B}}, [=]{ | ||||
| 			if (A.d() > 255) | ||||
| 				return u256(0); | ||||
| 			return bigintShiftLeftWorkaround(B.d(), unsigned(A.d())); | ||||
| 			return shlWorkaround(B.d(), unsigned(A.d())); | ||||
| 		}, false}, | ||||
| 		{{Instruction::SHR, {A, B}}, [=]{ | ||||
| 			if (A.d() > 255) | ||||
| @ -365,6 +372,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7( | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Combine two SHL by constant
 | ||||
| 	rules.push_back({ | ||||
| 		// SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X)
 | ||||
| 		{Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}}, | ||||
| @ -378,6 +386,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7( | ||||
| 		false | ||||
| 	}); | ||||
| 
 | ||||
| 	// Combine two SHR by constant
 | ||||
| 	rules.push_back({ | ||||
| 		// SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X)
 | ||||
| 		{Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}}, | ||||
| @ -391,6 +400,93 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7( | ||||
| 		false | ||||
| 	}); | ||||
| 
 | ||||
| 	// Combine SHL-SHR by constant
 | ||||
| 	rules.push_back({ | ||||
| 		// SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask)
 | ||||
| 		{Instruction::SHR, {{B}, {Instruction::SHL, {{A}, {X}}}}}, | ||||
| 		[=]() -> Pattern { | ||||
| 			u256 mask = shlWorkaround(u256(-1), unsigned(A.d())) >> unsigned(B.d()); | ||||
| 
 | ||||
| 			if (A.d() > B.d()) | ||||
| 				return {Instruction::AND, {{Instruction::SHL, {A.d() - B.d(), X}}, mask}}; | ||||
| 			else if (B.d() > A.d()) | ||||
| 				return {Instruction::AND, {{Instruction::SHR, {B.d() - A.d(), X}}, mask}}; | ||||
| 			else | ||||
| 				return {Instruction::AND, {X, mask}}; | ||||
| 		}, | ||||
| 		false, | ||||
| 		[=] { return A.d() < 256 && B.d() < 256; } | ||||
| 	}); | ||||
| 
 | ||||
| 	// Combine SHR-SHL by constant
 | ||||
| 	rules.push_back({ | ||||
| 		// SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask)
 | ||||
| 		{Instruction::SHL, {{B}, {Instruction::SHR, {{A}, {X}}}}}, | ||||
| 		[=]() -> Pattern { | ||||
| 			u256 mask = shlWorkaround(u256(-1) >> unsigned(A.d()), unsigned(B.d())); | ||||
| 
 | ||||
| 			if (A.d() > B.d()) | ||||
| 				return {Instruction::AND, {{Instruction::SHR, {A.d() - B.d(), X}}, mask}}; | ||||
| 			else if (B.d() > A.d()) | ||||
| 				return {Instruction::AND, {{Instruction::SHL, {B.d() - A.d(), X}}, mask}}; | ||||
| 			else | ||||
| 				return {Instruction::AND, {X, mask}}; | ||||
| 		}, | ||||
| 		false, | ||||
| 		[=] { return A.d() < 256 && B.d() < 256; } | ||||
| 	}); | ||||
| 
 | ||||
| 	// Move AND with constant across SHL and SHR by constant
 | ||||
| 	for (auto shiftOp: {Instruction::SHL, Instruction::SHR}) | ||||
| 	{ | ||||
| 		auto replacement = [=]() -> Pattern { | ||||
| 			u256 mask = | ||||
| 				shiftOp == Instruction::SHL ? | ||||
| 				shlWorkaround(A.d(), unsigned(B.d())) : | ||||
| 				A.d() >> unsigned(B.d()); | ||||
| 			return {Instruction::AND, {{shiftOp, {B.d(), X}}, std::move(mask)}}; | ||||
| 		}; | ||||
| 		rules.push_back({ | ||||
| 			// SH[L/R](B, AND(X, A)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
 | ||||
| 			{shiftOp, {{B}, {Instruction::AND, {{X}, {A}}}}}, | ||||
| 			replacement, | ||||
| 			false, | ||||
| 			[=] { return B.d() < 256; } | ||||
| 		}); | ||||
| 		rules.push_back({ | ||||
| 			// SH[L/R](B, AND(A, X)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
 | ||||
| 			{shiftOp, {{B}, {Instruction::AND, {{A}, {X}}}}}, | ||||
| 			replacement, | ||||
| 			false, | ||||
| 			[=] { return B.d() < 256; } | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	rules.push_back({ | ||||
| 		// MUL(X, SHL(Y, 1)) -> SHL(Y, X)
 | ||||
| 		{Instruction::MUL, {X, {Instruction::SHL, {Y, u256(1)}}}}, | ||||
| 		[=]() -> Pattern { | ||||
| 			return {Instruction::SHL, {Y, X}}; | ||||
| 		}, | ||||
| 		false | ||||
| 	}); | ||||
| 	rules.push_back({ | ||||
| 		// MUL(SHL(X, 1), Y) -> SHL(X, Y)
 | ||||
| 		{Instruction::MUL, {{Instruction::SHL, {X, u256(1)}}, Y}}, | ||||
| 		[=]() -> Pattern { | ||||
| 			return {Instruction::SHL, {X, Y}}; | ||||
| 		}, | ||||
| 		false | ||||
| 	}); | ||||
| 
 | ||||
| 	rules.push_back({ | ||||
| 		// DIV(X, SHL(Y, 1)) -> SHR(Y, X)
 | ||||
| 		{Instruction::DIV, {X, {Instruction::SHL, {Y, u256(1)}}}}, | ||||
| 		[=]() -> Pattern { | ||||
| 			return {Instruction::SHR, {Y, X}}; | ||||
| 		}, | ||||
| 		false | ||||
| 	}); | ||||
| 
 | ||||
| 	std::function<bool()> feasibilityFunction = [=]() { | ||||
| 		if (B.d() > 256) | ||||
|  | ||||
| @ -136,7 +136,13 @@ bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item) | ||||
| { | ||||
| 	if (_item.type() != Operation) | ||||
| 		return false; | ||||
| 	switch (_item.instruction()) | ||||
| 	else | ||||
| 		return terminatesControlFlow(_item.instruction()); | ||||
| } | ||||
| 
 | ||||
| bool SemanticInformation::terminatesControlFlow(Instruction _instruction) | ||||
| { | ||||
| 	switch (_instruction) | ||||
| 	{ | ||||
| 	case Instruction::RETURN: | ||||
| 	case Instruction::SELFDESTRUCT: | ||||
| @ -202,6 +208,22 @@ bool SemanticInformation::movable(Instruction _instruction) | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool SemanticInformation::sideEffectFree(Instruction _instruction) | ||||
| { | ||||
| 	// These are not really functional.
 | ||||
| 	assertThrow(!isDupInstruction(_instruction) && !isSwapInstruction(_instruction), AssemblyException, ""); | ||||
| 
 | ||||
| 	return !instructionInfo(_instruction).sideEffects; | ||||
| } | ||||
| 
 | ||||
| bool SemanticInformation::sideEffectFreeIfNoMSize(Instruction _instruction) | ||||
| { | ||||
| 	if (_instruction == Instruction::KECCAK256 || _instruction == Instruction::MLOAD) | ||||
| 		return true; | ||||
| 	else | ||||
| 		return sideEffectFree(_instruction); | ||||
| } | ||||
| 
 | ||||
| bool SemanticInformation::invalidatesMemory(Instruction _instruction) | ||||
| { | ||||
| 	switch (_instruction) | ||||
|  | ||||
| @ -48,6 +48,7 @@ struct SemanticInformation | ||||
| 	static bool isJumpInstruction(AssemblyItem const& _item); | ||||
| 	static bool altersControlFlow(AssemblyItem const& _item); | ||||
| 	static bool terminatesControlFlow(AssemblyItem const& _item); | ||||
| 	static bool terminatesControlFlow(Instruction _instruction); | ||||
| 	/// @returns false if the value put on the stack by _item depends on anything else than
 | ||||
| 	/// the information in the current block header, memory, storage or stack.
 | ||||
| 	static bool isDeterministic(AssemblyItem const& _item); | ||||
| @ -55,6 +56,16 @@ struct SemanticInformation | ||||
| 	/// without altering the semantics. This means it cannot depend on storage or memory,
 | ||||
| 	/// cannot have any side-effects, but it can depend on a call-constant state of the blockchain.
 | ||||
| 	static bool movable(Instruction _instruction); | ||||
| 	/// @returns true if the instruction can be removed without changing the semantics.
 | ||||
| 	/// This does not mean that it has to be deterministic or retrieve information from
 | ||||
| 	/// somewhere else than purely the values of its arguments.
 | ||||
| 	static bool sideEffectFree(Instruction _instruction); | ||||
| 	/// @returns true if the instruction can be removed without changing the semantics.
 | ||||
| 	/// This does not mean that it has to be deterministic or retrieve information from
 | ||||
| 	/// somewhere else than purely the values of its arguments.
 | ||||
| 	/// If true, the instruction is still allowed to influence the value returned by the
 | ||||
| 	/// msize instruction.
 | ||||
| 	static bool sideEffectFreeIfNoMSize(Instruction _instruction); | ||||
| 	/// @returns true if the given instruction modifies memory.
 | ||||
| 	static bool invalidatesMemory(Instruction _instruction); | ||||
| 	/// @returns true if the given instruction modifies storage (even indirectly).
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ set(sources | ||||
| 	ErrorReporter.cpp | ||||
| 	ErrorReporter.h | ||||
| 	EVMVersion.h | ||||
| 	EVMVersion.cpp | ||||
| 	Exceptions.cpp | ||||
| 	Exceptions.h | ||||
| 	ParserBase.cpp | ||||
|  | ||||
| @ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount) | ||||
| 	return get(); | ||||
| } | ||||
| 
 | ||||
| char CharStream::setPosition(size_t _location) | ||||
| { | ||||
| 	solAssert(_location <= m_source.size(), "Attempting to set position past end of source."); | ||||
| 	m_position = _location; | ||||
| 	return get(); | ||||
| } | ||||
| 
 | ||||
| string CharStream::lineAtPosition(int _position) const | ||||
| { | ||||
| 	// if _position points to \n, it returns the line before the \n
 | ||||
| @ -106,5 +113,3 @@ tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const | ||||
| 	} | ||||
| 	return tuple<int, int>(lineNumber, searchPosition - lineStart); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -76,7 +76,13 @@ public: | ||||
| 
 | ||||
| 	char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; } | ||||
| 	char advanceAndGet(size_t _chars = 1); | ||||
| 	/// Sets scanner position to @ _amount characters backwards in source text.
 | ||||
| 	/// @returns The character of the current location after update is returned.
 | ||||
| 	char rollback(size_t _amount); | ||||
| 	/// Sets scanner position to @ _location if it refers a valid offset in m_source.
 | ||||
| 	/// If not, nothing is done.
 | ||||
| 	/// @returns The character of the current location after update is returned.
 | ||||
| 	char setPosition(size_t _location); | ||||
| 
 | ||||
| 	void reset() { m_position = 0; } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										47
									
								
								liblangutil/EVMVersion.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								liblangutil/EVMVersion.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * EVM versioning. | ||||
|  */ | ||||
| 
 | ||||
| #include <liblangutil/EVMVersion.h> | ||||
| 
 | ||||
| using namespace langutil; | ||||
| using namespace dev::eth; | ||||
| 
 | ||||
| bool EVMVersion::hasOpcode(Instruction _opcode) const | ||||
| { | ||||
| 	switch (_opcode) | ||||
| 	{ | ||||
| 	case Instruction::RETURNDATACOPY: | ||||
| 	case Instruction::RETURNDATASIZE: | ||||
| 		return supportsReturndata(); | ||||
| 	case Instruction::STATICCALL: | ||||
| 		return hasStaticCall(); | ||||
| 	case Instruction::SHL: | ||||
| 	case Instruction::SHR: | ||||
| 	case Instruction::SAR: | ||||
| 		return hasBitwiseShifting(); | ||||
| 	case Instruction::CREATE2: | ||||
| 		return hasCreate2(); | ||||
| 	case Instruction::EXTCODEHASH: | ||||
| 		return hasExtCodeHash(); | ||||
| 	default: | ||||
| 		return true; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -20,11 +20,14 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libevmasm/Instruction.h> | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| #include <boost/optional.hpp> | ||||
| #include <boost/operators.hpp> | ||||
| 
 | ||||
| 
 | ||||
| namespace langutil | ||||
| { | ||||
| 
 | ||||
| @ -78,6 +81,8 @@ public: | ||||
| 	bool hasCreate2() const { return *this >= constantinople(); } | ||||
| 	bool hasExtCodeHash() const { return *this >= constantinople(); } | ||||
| 
 | ||||
| 	bool hasOpcode(dev::eth::Instruction _opcode) const; | ||||
| 
 | ||||
| 	/// Whether we have to retain the costs for the call opcode itself (false),
 | ||||
| 	/// or whether we can just forward easily all remaining gas (true).
 | ||||
| 	bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } | ||||
| @ -90,5 +95,4 @@ private: | ||||
| 	Version m_version = Version::Petersburg; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -86,6 +86,11 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se | ||||
| 	m_errorList.push_back(err); | ||||
| } | ||||
| 
 | ||||
| bool ErrorReporter::hasExcessiveErrors() const | ||||
| { | ||||
| 	return m_errorCount > c_maxErrorsAllowed; | ||||
| } | ||||
| 
 | ||||
| bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) | ||||
| { | ||||
| 	if (_type == Error::Type::Warning) | ||||
|  | ||||
| @ -118,6 +118,9 @@ public: | ||||
| 		return m_errorCount > 0; | ||||
| 	} | ||||
| 
 | ||||
| 	// @returns true if the maximum error count has been reached.
 | ||||
| 	bool hasExcessiveErrors() const; | ||||
| 
 | ||||
| private: | ||||
| 	void error( | ||||
| 		Error::Type _type, | ||||
| @ -149,4 +152,3 @@ private: | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const | ||||
| 	return m_scanner->peekNextToken(); | ||||
| } | ||||
| 
 | ||||
| std::string ParserBase::currentLiteral() const | ||||
| string ParserBase::currentLiteral() const | ||||
| { | ||||
| 	return m_scanner->currentLiteral(); | ||||
| } | ||||
| @ -57,34 +57,83 @@ Token ParserBase::advance() | ||||
| 	return m_scanner->next(); | ||||
| } | ||||
| 
 | ||||
| string ParserBase::tokenName(Token _token) | ||||
| { | ||||
| 	if (_token == Token::Identifier) | ||||
| 		return "identifier"; | ||||
| 	else if (_token == Token::EOS) | ||||
| 		return "end of source"; | ||||
| 	else if (TokenTraits::isReservedKeyword(_token)) | ||||
| 		return "reserved keyword '" + TokenTraits::friendlyName(_token) + "'"; | ||||
| 	else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
 | ||||
| 	{ | ||||
| 		ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); | ||||
| 		return "'" + elemTypeName.toString() + "'"; | ||||
| 	} | ||||
| 	else | ||||
| 		return "'" + TokenTraits::friendlyName(_token) + "'"; | ||||
| } | ||||
| 
 | ||||
| void ParserBase::expectToken(Token _value, bool _advance) | ||||
| { | ||||
| 	Token tok = m_scanner->currentToken(); | ||||
| 	if (tok != _value) | ||||
| 	{ | ||||
| 		auto tokenName = [this](Token _token) | ||||
| 		{ | ||||
| 			if (_token == Token::Identifier) | ||||
| 				return string("identifier"); | ||||
| 			else if (_token == Token::EOS) | ||||
| 				return string("end of source"); | ||||
| 			else if (TokenTraits::isReservedKeyword(_token)) | ||||
| 				return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'"; | ||||
| 			else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
 | ||||
| 			{ | ||||
| 				ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); | ||||
| 				return string("'") + elemTypeName.toString() + "'"; | ||||
| 			} | ||||
| 			else | ||||
| 				return string("'") + TokenTraits::friendlyName(_token) + "'"; | ||||
| 		}; | ||||
| 
 | ||||
| 		fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok)); | ||||
| 		string const expectedToken = ParserBase::tokenName(_value); | ||||
| 		if (m_parserErrorRecovery) | ||||
| 			parserError("Expected " + expectedToken + " but got " + tokenName(tok)); | ||||
| 		else | ||||
| 			fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok)); | ||||
| 		// Do not advance so that recovery can sync or make use of the current token.
 | ||||
| 		// This is especially useful if the expected token
 | ||||
| 		// is the only one that is missing and is at the end of a construct.
 | ||||
| 		// "{ ... ; }" is such an example.
 | ||||
| 		//        ^
 | ||||
| 		_advance = false; | ||||
| 	} | ||||
| 	if (_advance) | ||||
| 		m_scanner->next(); | ||||
| } | ||||
| 
 | ||||
| void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance) | ||||
| { | ||||
| 	Token tok = m_scanner->currentToken(); | ||||
| 	if (tok != _value) | ||||
| 	{ | ||||
| 		int startPosition = position(); | ||||
| 		SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()}; | ||||
| 		while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS) | ||||
| 			m_scanner->next(); | ||||
| 
 | ||||
| 		string const expectedToken = ParserBase::tokenName(_value); | ||||
| 		string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " +  ParserBase::tokenName(tok) +  "instead."; | ||||
| 		if (m_scanner->currentToken() == Token::EOS) | ||||
| 		{ | ||||
| 			// rollback to where the token started, and raise exception to be caught at a higher level.
 | ||||
| 			m_scanner->setPosition(startPosition); | ||||
| 			m_inParserRecovery = true; | ||||
| 			fatalParserError(errorLoc, msg); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (m_inParserRecovery) | ||||
| 				parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + "."); | ||||
| 			else | ||||
| 				parserError(errorLoc, msg + "Recovered at next " + expectedToken); | ||||
| 			m_inParserRecovery = false; | ||||
| 		} | ||||
| 	} | ||||
| 	else if (m_inParserRecovery) | ||||
| 	{ | ||||
| 		string expectedToken = ParserBase::tokenName(_value); | ||||
| 		parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + "."); | ||||
| 		m_inParserRecovery = false; | ||||
| 	} | ||||
| 
 | ||||
| 	if (_advance) | ||||
| 		m_scanner->next(); | ||||
| } | ||||
| 
 | ||||
| void ParserBase::increaseRecursionDepth() | ||||
| { | ||||
| 	m_recursionDepth++; | ||||
| @ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth() | ||||
| 	m_recursionDepth--; | ||||
| } | ||||
| 
 | ||||
| void ParserBase::parserWarning(string const& _description) | ||||
| { | ||||
| 	m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description); | ||||
| } | ||||
| 
 | ||||
| void ParserBase::parserError(SourceLocation const& _location, string const& _description) | ||||
| { | ||||
| 	m_errorReporter.parserError(_location, _description); | ||||
| } | ||||
| 
 | ||||
| void ParserBase::parserError(string const& _description) | ||||
| { | ||||
| 	m_errorReporter.parserError(SourceLocation{position(), endPosition(), source()}, _description); | ||||
| 	parserError(SourceLocation{position(), endPosition(), source()}, _description); | ||||
| } | ||||
| 
 | ||||
| void ParserBase::fatalParserError(string const& _description) | ||||
| { | ||||
| 	m_errorReporter.fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); | ||||
| 	fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); | ||||
| } | ||||
| 
 | ||||
| void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description) | ||||
| { | ||||
| 	m_errorReporter.fatalParserError(_location, _description); | ||||
| } | ||||
|  | ||||
| @ -36,7 +36,14 @@ class Scanner; | ||||
| class ParserBase | ||||
| { | ||||
| public: | ||||
| 	explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} | ||||
| 	/// Set @a _parserErrorRecovery to true for additional error
 | ||||
| 	/// recovery.  This is experimental and intended for use
 | ||||
| 	/// by front-end tools that need partial AST information even
 | ||||
| 	/// when errors occur.
 | ||||
| 	explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter) | ||||
| 	{ | ||||
| 		m_parserErrorRecovery = _parserErrorRecovery; | ||||
| 	} | ||||
| 
 | ||||
| 	std::shared_ptr<CharStream> source() const { return m_scanner->charStream(); } | ||||
| 
 | ||||
| @ -62,10 +69,17 @@ protected: | ||||
| 
 | ||||
| 	///@{
 | ||||
| 	///@name Helper functions
 | ||||
| 	/// If current token value is not _value, throw exception otherwise advance token.
 | ||||
| 	/// If current token value is not @a _value, throw exception otherwise advance token
 | ||||
| 	//  @a if _advance is true and error recovery is in effect.
 | ||||
| 	void expectToken(Token _value, bool _advance = true); | ||||
| 
 | ||||
| 	/// Like expectToken but if there is an error ignores tokens until
 | ||||
| 	/// the expected token or EOS is seen. If EOS is encountered, back up to the error point,
 | ||||
| 	/// and throw an exception so that a higher grammar rule has an opportunity to recover.
 | ||||
| 	void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true); | ||||
| 	Token currentToken() const; | ||||
| 	Token peekNextToken() const; | ||||
| 	std::string tokenName(Token _token); | ||||
| 	std::string currentLiteral() const; | ||||
| 	Token advance(); | ||||
| 	///@}
 | ||||
| @ -77,16 +91,26 @@ protected: | ||||
| 	/// Creates a @ref ParserError and annotates it with the current position and the
 | ||||
| 	/// given @a _description.
 | ||||
| 	void parserError(std::string const& _description); | ||||
| 	void parserError(SourceLocation const& _location, std::string const& _description); | ||||
| 
 | ||||
| 	/// Creates a @ref ParserWarning and annotates it with the current position and the
 | ||||
| 	/// given @a _description.
 | ||||
| 	void parserWarning(std::string const& _description); | ||||
| 
 | ||||
| 	/// Creates a @ref ParserError and annotates it with the current position and the
 | ||||
| 	/// given @a _description. Throws the FatalError.
 | ||||
| 	void fatalParserError(std::string const& _description); | ||||
| 	void fatalParserError(SourceLocation const& _location, std::string const& _description); | ||||
| 
 | ||||
| 	std::shared_ptr<Scanner> m_scanner; | ||||
| 	/// The reference to the list of errors and warning to add errors/warnings during parsing
 | ||||
| 	ErrorReporter& m_errorReporter; | ||||
| 	/// Current recursion depth during parsing.
 | ||||
| 	size_t m_recursionDepth = 0; | ||||
| 	/// True if we are in parser error recovery. Usually this means we are scanning for
 | ||||
| 	/// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages.
 | ||||
| 	bool m_inParserRecovery = false; | ||||
| 	bool m_parserErrorRecovery = false; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -156,6 +156,13 @@ void Scanner::reset() | ||||
| 	next(); | ||||
| } | ||||
| 
 | ||||
| void Scanner::setPosition(size_t _offset) | ||||
| { | ||||
| 	m_char = m_source->setPosition(_offset); | ||||
| 	scanToken(); | ||||
| 	next(); | ||||
| } | ||||
| 
 | ||||
| void Scanner::supportPeriodInIdentifier(bool _value) | ||||
| { | ||||
| 	m_supportPeriodInIdentifier = _value; | ||||
|  | ||||
| @ -110,6 +110,9 @@ public: | ||||
| 	/// @returns the next token and advances input
 | ||||
| 	Token next(); | ||||
| 
 | ||||
| 	/// Set scanner to a specific offset. This is used in error recovery.
 | ||||
| 	void setPosition(size_t _offset); | ||||
| 
 | ||||
| 	///@{
 | ||||
| 	///@name Information about the current token
 | ||||
| 
 | ||||
|  | ||||
| @ -21,10 +21,11 @@ | ||||
|  */ | ||||
| 
 | ||||
| #include <libsolc/libsolc.h> | ||||
| #include <libdevcore/Common.h> | ||||
| #include <libdevcore/JSON.h> | ||||
| #include <libsolidity/interface/StandardCompiler.h> | ||||
| #include <libsolidity/interface/Version.h> | ||||
| #include <libyul/YulString.h> | ||||
| #include <libdevcore/Common.h> | ||||
| #include <libdevcore/JSON.h> | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| @ -100,6 +101,9 @@ extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _ | ||||
| } | ||||
| extern void solidity_free() noexcept | ||||
| { | ||||
| 	// This is called right before each compilation, but not at the end, so additional memory
 | ||||
| 	// can be freed here.
 | ||||
| 	yul::YulStringRepository::reset(); | ||||
| 	s_outputBuffer.clear(); | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -73,6 +73,8 @@ set(sources | ||||
| 	codegen/ir/IRGeneratorForStatements.h | ||||
| 	codegen/ir/IRGenerationContext.cpp | ||||
| 	codegen/ir/IRGenerationContext.h | ||||
| 	codegen/ir/IRLValue.cpp | ||||
| 	codegen/ir/IRLValue.h | ||||
| 	formal/EncodingContext.cpp | ||||
| 	formal/EncodingContext.h | ||||
| 	formal/SMTChecker.cpp | ||||
|  | ||||
| @ -37,16 +37,17 @@ namespace solidity | ||||
| { | ||||
| 
 | ||||
| NameAndTypeResolver::NameAndTypeResolver( | ||||
| 	vector<Declaration const*> const& _globals, | ||||
| 	GlobalContext& _globalContext, | ||||
| 	map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes, | ||||
| 	ErrorReporter& _errorReporter | ||||
| ) : | ||||
| 	m_scopes(_scopes), | ||||
| 	m_errorReporter(_errorReporter) | ||||
| 	m_errorReporter(_errorReporter), | ||||
| 	m_globalContext(_globalContext) | ||||
| { | ||||
| 	if (!m_scopes[nullptr]) | ||||
| 		m_scopes[nullptr].reset(new DeclarationContainer()); | ||||
| 	for (Declaration const* declaration: _globals) | ||||
| 	for (Declaration const* declaration: _globalContext.declarations()) | ||||
| 	{ | ||||
| 		solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration."); | ||||
| 	} | ||||
| @ -57,7 +58,7 @@ bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode | ||||
| 	// The helper registers all declarations in m_scopes as a side-effect of its construction.
 | ||||
| 	try | ||||
| 	{ | ||||
| 		DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope); | ||||
| 		DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, m_globalContext, _currentScope); | ||||
| 	} | ||||
| 	catch (langutil::FatalError const&) | ||||
| 	{ | ||||
| @ -272,6 +273,10 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res | ||||
| 		setScope(contract->scope()); | ||||
| 		solAssert(!!m_currentScope, ""); | ||||
| 
 | ||||
| 		m_globalContext.setCurrentContract(*contract); | ||||
| 		updateDeclaration(*m_globalContext.currentSuper()); | ||||
| 		updateDeclaration(*m_globalContext.currentThis()); | ||||
| 
 | ||||
| 		for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts()) | ||||
| 			if (!resolveNamesAndTypes(*baseContract, true)) | ||||
| 				success = false; | ||||
| @ -452,11 +457,13 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper( | ||||
| 	map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes, | ||||
| 	ASTNode& _astRoot, | ||||
| 	ErrorReporter& _errorReporter, | ||||
| 	GlobalContext& _globalContext, | ||||
| 	ASTNode const* _currentScope | ||||
| ): | ||||
| 	m_scopes(_scopes), | ||||
| 	m_currentScope(_currentScope), | ||||
| 	m_errorReporter(_errorReporter) | ||||
| 	m_errorReporter(_errorReporter), | ||||
| 	m_globalContext(_globalContext) | ||||
| { | ||||
| 	_astRoot.accept(*this); | ||||
| 	solAssert(m_currentScope == _currentScope, "Scopes not correctly closed."); | ||||
| @ -560,6 +567,10 @@ bool DeclarationRegistrationHelper::visit(ImportDirective& _import) | ||||
| 
 | ||||
| bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract) | ||||
| { | ||||
| 	m_globalContext.setCurrentContract(_contract); | ||||
| 	m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, false, true); | ||||
| 	m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true); | ||||
| 
 | ||||
| 	registerDeclaration(_contract, true); | ||||
| 	_contract.annotation().canonicalName = currentCanonicalName(); | ||||
| 	return true; | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/analysis/DeclarationContainer.h> | ||||
| #include <libsolidity/analysis/GlobalContext.h> | ||||
| #include <libsolidity/analysis/ReferencesResolver.h> | ||||
| #include <libsolidity/ast/ASTAnnotations.h> | ||||
| #include <libsolidity/ast/ASTVisitor.h> | ||||
| @ -53,7 +54,7 @@ public: | ||||
| 	/// @param _scopes mapping of scopes to be used (usually default constructed), these
 | ||||
| 	/// are filled during the lifetime of this object.
 | ||||
| 	NameAndTypeResolver( | ||||
| 		std::vector<Declaration const*> const& _globals, | ||||
| 		GlobalContext& _globalContext, | ||||
| 		std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes, | ||||
| 		langutil::ErrorReporter& _errorReporter | ||||
| 	); | ||||
| @ -131,6 +132,7 @@ private: | ||||
| 
 | ||||
| 	DeclarationContainer* m_currentScope = nullptr; | ||||
| 	langutil::ErrorReporter& m_errorReporter; | ||||
| 	GlobalContext& m_globalContext; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
| @ -148,6 +150,7 @@ public: | ||||
| 		std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes, | ||||
| 		ASTNode& _astRoot, | ||||
| 		langutil::ErrorReporter& _errorReporter, | ||||
| 		GlobalContext& _globalContext, | ||||
| 		ASTNode const* _currentScope = nullptr | ||||
| 	); | ||||
| 
 | ||||
| @ -200,6 +203,7 @@ private: | ||||
| 	ASTNode const* m_currentScope = nullptr; | ||||
| 	VariableScope* m_currentFunction = nullptr; | ||||
| 	langutil::ErrorReporter& m_errorReporter; | ||||
| 	GlobalContext& m_globalContext; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -309,6 +309,19 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall) | ||||
| 							"Arithmetic modulo zero." | ||||
| 						); | ||||
| 		} | ||||
| 		if ( | ||||
| 			m_currentContract->isLibrary() && | ||||
| 			functionType->kind() == FunctionType::Kind::DelegateCall && | ||||
| 			functionType->declaration().scope() == m_currentContract | ||||
| 		) | ||||
| 			m_errorReporter.typeError( | ||||
| 				_functionCall.location(), | ||||
| 				SecondarySourceLocation().append( | ||||
| 					"The function declaration is here:", | ||||
| 					functionType->declaration().scope()->location() | ||||
| 				), | ||||
| 				"Libraries cannot call their own functions externally." | ||||
| 			); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @ -21,6 +21,9 @@ | ||||
| #include <libsolidity/ast/ExperimentalFeatures.h> | ||||
| #include <libsolidity/interface/Version.h> | ||||
| 
 | ||||
| #include <libyul/optimiser/Semantics.h> | ||||
| #include <libyul/AsmData.h> | ||||
| 
 | ||||
| #include <liblangutil/ErrorReporter.h> | ||||
| #include <liblangutil/SemVerHandler.h> | ||||
| 
 | ||||
| @ -254,6 +257,23 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation) | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly) | ||||
| { | ||||
| 	if (!m_useYulOptimizer) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (yul::SideEffectsCollector( | ||||
| 		_inlineAssembly.dialect(), | ||||
| 		_inlineAssembly.operations() | ||||
| 	).containsMSize()) | ||||
| 		m_errorReporter.syntaxError( | ||||
| 			_inlineAssembly.location(), | ||||
| 			"The msize instruction cannot be used when the Yul optimizer is activated because " | ||||
| 			"it can change its semantics. Either disable the Yul optimizer or do not use the instruction." | ||||
| 		); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| bool SyntaxChecker::visit(PlaceholderStatement const&) | ||||
| { | ||||
| 	m_placeholderFound = true; | ||||
|  | ||||
| @ -39,12 +39,16 @@ namespace solidity | ||||
|  *  - whether a modifier contains at least one '_' | ||||
|  *  - issues deprecation warnings for unary '+' | ||||
|  *  - issues deprecation warning for throw | ||||
|  *  - whether the msize instruction is used and the Yul optimizer is enabled at the same time. | ||||
|  */ | ||||
| class SyntaxChecker: private ASTConstVisitor | ||||
| { | ||||
| public: | ||||
| 	/// @param _errorReporter provides the error logging functionality.
 | ||||
| 	SyntaxChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} | ||||
| 	SyntaxChecker(langutil::ErrorReporter& _errorReporter, bool _useYulOptimizer): | ||||
| 		m_errorReporter(_errorReporter), | ||||
| 		m_useYulOptimizer(_useYulOptimizer) | ||||
| 	{} | ||||
| 
 | ||||
| 	bool checkSyntax(ASTNode const& _astRoot); | ||||
| 
 | ||||
| @ -75,6 +79,8 @@ private: | ||||
| 
 | ||||
| 	bool visit(UnaryOperation const& _operation) override; | ||||
| 
 | ||||
| 	bool visit(InlineAssembly const& _inlineAssembly) override; | ||||
| 
 | ||||
| 	bool visit(PlaceholderStatement const& _placeholderStatement) override; | ||||
| 
 | ||||
| 	bool visit(ContractDefinition const& _contract) override; | ||||
| @ -88,6 +94,8 @@ private: | ||||
| 
 | ||||
| 	langutil::ErrorReporter& m_errorReporter; | ||||
| 
 | ||||
| 	bool m_useYulOptimizer = false; | ||||
| 
 | ||||
| 	/// Flag that indicates whether a function modifier actually contains '_'.
 | ||||
| 	bool m_placeholderFound = false; | ||||
| 
 | ||||
|  | ||||
| @ -27,7 +27,6 @@ | ||||
| #include <libyul/AsmAnalysis.h> | ||||
| #include <libyul/AsmAnalysisInfo.h> | ||||
| #include <libyul/AsmData.h> | ||||
| #include <libyul/backends/evm/EVMDialect.h> | ||||
| 
 | ||||
| #include <liblangutil/ErrorReporter.h> | ||||
| 
 | ||||
| @ -707,7 +706,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) | ||||
| 		*_inlineAssembly.annotation().analysisInfo, | ||||
| 		m_errorReporter, | ||||
| 		Error::Type::SyntaxError, | ||||
| 		yul::EVMDialect::looseAssemblyForEVM(m_evmVersion), | ||||
| 		_inlineAssembly.dialect(), | ||||
| 		identifierAccess | ||||
| 	); | ||||
| 	if (!analyzer.analyze(_inlineAssembly.operations())) | ||||
|  | ||||
| @ -18,6 +18,7 @@ | ||||
| #include <libsolidity/analysis/ViewPureChecker.h> | ||||
| #include <libsolidity/ast/ExperimentalFeatures.h> | ||||
| #include <libyul/AsmData.h> | ||||
| #include <libyul/backends/evm/EVMDialect.h> | ||||
| #include <liblangutil/ErrorReporter.h> | ||||
| #include <libevmasm/SemanticInformation.h> | ||||
| #include <functional> | ||||
| @ -33,7 +34,11 @@ namespace | ||||
| class AssemblyViewPureChecker: public boost::static_visitor<void> | ||||
| { | ||||
| public: | ||||
| 	explicit AssemblyViewPureChecker(std::function<void(StateMutability, SourceLocation const&)> _reportMutability): | ||||
| 	explicit AssemblyViewPureChecker( | ||||
| 		yul::Dialect const& _dialect, | ||||
| 		std::function<void(StateMutability, SourceLocation const&)> _reportMutability | ||||
| 	): | ||||
| 		m_dialect(_dialect), | ||||
| 		m_reportMutability(_reportMutability) {} | ||||
| 
 | ||||
| 	void operator()(yul::Label const&) { } | ||||
| @ -69,6 +74,11 @@ public: | ||||
| 	} | ||||
| 	void operator()(yul::FunctionCall const& _funCall) | ||||
| 	{ | ||||
| 		if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect)) | ||||
| 			if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) | ||||
| 				if (fun->instruction) | ||||
| 					checkInstruction(_funCall.location, *fun->instruction); | ||||
| 
 | ||||
| 		for (auto const& arg: _funCall.arguments) | ||||
| 			boost::apply_visitor(*this, arg); | ||||
| 	} | ||||
| @ -107,16 +117,16 @@ public: | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	std::function<void(StateMutability, SourceLocation const&)> m_reportMutability; | ||||
| 	void checkInstruction(SourceLocation _location, dev::eth::Instruction _instruction) | ||||
| 	{ | ||||
| 		if (eth::SemanticInformation::invalidInViewFunctions(_instruction)) | ||||
| 			m_reportMutability(StateMutability::NonPayable, _location); | ||||
| 		else if (_instruction == dev::eth::Instruction::CALLVALUE) | ||||
| 			m_reportMutability(StateMutability::Payable, _location); | ||||
| 		else if (eth::SemanticInformation::invalidInPureFunctions(_instruction)) | ||||
| 			m_reportMutability(StateMutability::View, _location); | ||||
| 	} | ||||
| 
 | ||||
| 	yul::Dialect const& m_dialect; | ||||
| 	std::function<void(StateMutability, SourceLocation const&)> m_reportMutability; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| @ -223,6 +233,7 @@ void ViewPureChecker::endVisit(Identifier const& _identifier) | ||||
| void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly) | ||||
| { | ||||
| 	AssemblyViewPureChecker{ | ||||
| 		_inlineAssembly.dialect(), | ||||
| 		[=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); } | ||||
| 	}(_inlineAssembly.operations()); | ||||
| } | ||||
|  | ||||
| @ -400,6 +400,21 @@ SourceUnit const& Scopable::sourceUnit() const | ||||
| 	return dynamic_cast<SourceUnit const&>(*s); | ||||
| } | ||||
| 
 | ||||
| CallableDeclaration const* Scopable::functionOrModifierDefinition() const | ||||
| { | ||||
| 	ASTNode const* s = scope(); | ||||
| 	solAssert(s, ""); | ||||
| 	while (dynamic_cast<Scopable const*>(s)) | ||||
| 	{ | ||||
| 		if (auto funDef = dynamic_cast<FunctionDefinition const*>(s)) | ||||
| 			return funDef; | ||||
| 		if (auto modDef = dynamic_cast<ModifierDefinition const*>(s)) | ||||
| 			return modDef; | ||||
| 		s = dynamic_cast<Scopable const*>(s)->scope(); | ||||
| 	} | ||||
| 	return nullptr; | ||||
| } | ||||
| 
 | ||||
| string Scopable::sourceUnitName() const | ||||
| { | ||||
| 	return sourceUnit().annotation().path; | ||||
|  | ||||
| @ -43,6 +43,7 @@ namespace yul | ||||
| { | ||||
| // Forward-declaration to <yul/AsmData.h>
 | ||||
| struct Block; | ||||
| struct Dialect; | ||||
| } | ||||
| 
 | ||||
| namespace dev | ||||
| @ -161,6 +162,9 @@ public: | ||||
| 	/// @returns the source unit this scopable is present in.
 | ||||
| 	SourceUnit const& sourceUnit() const; | ||||
| 
 | ||||
| 	/// @returns the function or modifier definition this scopable is present in or nullptr.
 | ||||
| 	CallableDeclaration const* functionOrModifierDefinition() const; | ||||
| 
 | ||||
| 	/// @returns the source name this scopable is present in.
 | ||||
| 	/// Can be combined with annotation().canonicalName (if present) to form a globally unique name.
 | ||||
| 	std::string sourceUnitName() const; | ||||
| @ -1046,17 +1050,20 @@ public: | ||||
| 	InlineAssembly( | ||||
| 		SourceLocation const& _location, | ||||
| 		ASTPointer<ASTString> const& _docString, | ||||
| 		yul::Dialect const& _dialect, | ||||
| 		std::shared_ptr<yul::Block> const& _operations | ||||
| 	): | ||||
| 		Statement(_location, _docString), m_operations(_operations) {} | ||||
| 		Statement(_location, _docString), m_dialect(_dialect), m_operations(_operations) {} | ||||
| 	void accept(ASTVisitor& _visitor) override; | ||||
| 	void accept(ASTConstVisitor& _visitor) const override; | ||||
| 
 | ||||
| 	yul::Dialect const& dialect() const { return m_dialect; } | ||||
| 	yul::Block const& operations() const { return *m_operations; } | ||||
| 
 | ||||
| 	InlineAssemblyAnnotation& annotation() const override; | ||||
| 
 | ||||
| private: | ||||
| 	yul::Dialect const& m_dialect; | ||||
| 	std::shared_ptr<yul::Block> m_operations; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -35,8 +35,9 @@ | ||||
| 
 | ||||
| namespace yul | ||||
| { | ||||
| 	struct AsmAnalysisInfo; | ||||
| 	struct Identifier; | ||||
| struct AsmAnalysisInfo; | ||||
| struct Identifier; | ||||
| struct Dialect; | ||||
| } | ||||
| 
 | ||||
| namespace dev | ||||
|  | ||||
| @ -38,6 +38,7 @@ class SourceUnit; | ||||
| class PragmaDirective; | ||||
| class ImportDirective; | ||||
| class Declaration; | ||||
| class CallableDeclaration; | ||||
| class ContractDefinition; | ||||
| class InheritanceSpecifier; | ||||
| class UsingForDirective; | ||||
|  | ||||
| @ -2068,7 +2068,8 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const | ||||
| 	{ | ||||
| 		TypePointer type = variable->annotation().type; | ||||
| 		solAssert(type, ""); | ||||
| 		// Skip all mapping members if we are not in storage.
 | ||||
| 		// If we are not in storage, skip all members that cannot live outside of storage,
 | ||||
| 		// ex. mappings and array of mappings
 | ||||
| 		if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) | ||||
| 			continue; | ||||
| 		members.emplace_back( | ||||
|  | ||||
| @ -255,39 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const | ||||
| 	return suffix; | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) | ||||
| { | ||||
| 	solAssert(_type.isValueType(), ""); | ||||
| 	solUnimplementedAssert(!_splitFunctionTypes, ""); | ||||
| 
 | ||||
| 	string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier(); | ||||
| 	return createFunction(functionName, [&] { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) -> cleaned { | ||||
| 				<body> | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 
 | ||||
| 		unsigned storageBytes = _type.storageBytes(); | ||||
| 		if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type)) | ||||
| 			if (type->isSigned() && storageBytes != 32) | ||||
| 			{ | ||||
| 				templ("body", "cleaned := signextend(" + to_string(storageBytes - 1) + ", value)"); | ||||
| 				return templ.render(); | ||||
| 			} | ||||
| 
 | ||||
| 		if (storageBytes == 32) | ||||
| 			templ("body", "cleaned := value"); | ||||
| 		else if (_type.leftAligned()) | ||||
| 			templ("body", "cleaned := " + m_utils.shiftLeftFunction(256 - 8 * storageBytes) + "(value)"); | ||||
| 		else | ||||
| 			templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")"); | ||||
| 
 | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::abiEncodingFunction( | ||||
| 	Type const& _from, | ||||
| 	Type const& _to, | ||||
| @ -609,7 +576,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( | ||||
| 				break; | ||||
| 			case DataLocation::Storage: | ||||
| 				if (_from.baseType()->isValueType()) | ||||
| 					templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); | ||||
| 					templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); | ||||
| 				else | ||||
| 					templ("arrayElementAccess", "srcPtr"); | ||||
| 				break; | ||||
| @ -806,7 +773,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( | ||||
| 					items[i]["inRange"] = "1"; | ||||
| 				else | ||||
| 					items[i]["inRange"] = "0"; | ||||
| 				items[i]["extractFromSlot"] = extractFromStorageValue(*_from.baseType(), i * storageBytes, false); | ||||
| 				items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes, false); | ||||
| 			} | ||||
| 			templ("items", items); | ||||
| 			return templ.render(); | ||||
| @ -893,7 +860,7 @@ string ABIFunctions::abiEncodingFunctionStruct( | ||||
| 							members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; | ||||
| 							previousSlotOffset = storageSlotOffset; | ||||
| 						} | ||||
| 						members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; | ||||
| 						members.back()["retrieveValue"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| @ -1400,51 +1367,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes) | ||||
| { | ||||
| 	solUnimplementedAssert(!_splitFunctionTypes, ""); | ||||
| 	string functionName = | ||||
| 		"read_from_storage_" + | ||||
| 		string(_splitFunctionTypes ? "split_" : "") + | ||||
| 		"offset_" + | ||||
| 		to_string(_offset) + | ||||
| 		_type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&] { | ||||
| 		solAssert(_type.sizeOnStack() == 1, ""); | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(slot) -> value { | ||||
| 				value := <extract>(sload(slot)) | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("extract", extractFromStorageValue(_type, _offset, false)) | ||||
| 		.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes) | ||||
| { | ||||
| 	solUnimplementedAssert(!_splitFunctionTypes, ""); | ||||
| 
 | ||||
| 	string functionName = | ||||
| 		"extract_from_storage_value_" + | ||||
| 		string(_splitFunctionTypes ? "split_" : "") + | ||||
| 		"offset_" + | ||||
| 		to_string(_offset) + | ||||
| 		_type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&] { | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(slot_value) -> value { | ||||
| 				value := <cleanupStorage>(<shr>(slot_value)) | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("shr", m_utils.shiftRightFunction(_offset * 8)) | ||||
| 		("cleanupStorage", cleanupFromStorageFunction(_type, false)) | ||||
| 		.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string ABIFunctions::calldataAccessFunction(Type const& _type) | ||||
| { | ||||
| 	solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), ""); | ||||
|  | ||||
| @ -128,15 +128,6 @@ private: | ||||
| 		std::string toFunctionNameSuffix() const; | ||||
| 	}; | ||||
| 
 | ||||
| 	/// Performs cleanup after reading from a potentially compressed storage slot.
 | ||||
| 	/// The function does not perform any validation, it just masks or sign-extends
 | ||||
| 	/// higher order bytes or left-aligns (in case of bytesNN).
 | ||||
| 	/// The storage cleanup expects the value to be right-aligned with potentially
 | ||||
| 	/// dirty higher order bytes.
 | ||||
| 	/// @param _splitFunctionTypes if false, returns the address and function signature in a
 | ||||
| 	/// single variable.
 | ||||
| 	std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes); | ||||
| 
 | ||||
| 	/// @returns the name of the ABI encoding function with the given type
 | ||||
| 	/// and queues the generation of the function to the requested functions.
 | ||||
| 	/// @param _fromStack if false, the input value was just loaded from storage
 | ||||
| @ -231,19 +222,6 @@ private: | ||||
| 	/// Part of @a abiDecodingFunction for array types.
 | ||||
| 	std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); | ||||
| 
 | ||||
| 	/// @returns a function that reads a value type from storage.
 | ||||
| 	/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
 | ||||
| 	/// @param _splitFunctionTypes if false, returns the address and function signature in a
 | ||||
| 	/// single variable.
 | ||||
| 	std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes); | ||||
| 
 | ||||
| 	/// @returns a function that extracts a value type from storage slot that has been
 | ||||
| 	/// retrieved already.
 | ||||
| 	/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
 | ||||
| 	/// @param _splitFunctionTypes if false, returns the address and function signature in a
 | ||||
| 	/// single variable.
 | ||||
| 	std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); | ||||
| 
 | ||||
| 	/// @returns the name of a function that retrieves an element from calldata.
 | ||||
| 	std::string calldataAccessFunction(Type const& _type); | ||||
| 
 | ||||
|  | ||||
| @ -33,6 +33,7 @@ | ||||
| #include <libyul/backends/evm/AsmCodeGen.h> | ||||
| #include <libyul/backends/evm/EVMDialect.h> | ||||
| #include <libyul/optimiser/Suite.h> | ||||
| #include <libyul/optimiser/Metrics.h> | ||||
| #include <libyul/YulString.h> | ||||
| 
 | ||||
| #include <liblangutil/ErrorReporter.h> | ||||
| @ -382,7 +383,8 @@ void CompilerContext::appendInlineAssembly( | ||||
| 	ErrorList errors; | ||||
| 	ErrorReporter errorReporter(errors); | ||||
| 	auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--")); | ||||
| 	auto parserResult = yul::Parser(errorReporter, yul::EVMDialect::strictAssemblyForEVM(m_evmVersion)).parse(scanner, false); | ||||
| 	yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion); | ||||
| 	auto parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false); | ||||
| #ifdef SOL_OUTPUT_ASM | ||||
| 	cout << yul::AsmPrinter()(*parserResult) << endl; | ||||
| #endif | ||||
| @ -409,7 +411,7 @@ void CompilerContext::appendInlineAssembly( | ||||
| 			analysisInfo, | ||||
| 			errorReporter, | ||||
| 			boost::none, | ||||
| 			yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), | ||||
| 			dialect, | ||||
| 			identifierAccess.resolve | ||||
| 		).analyze(*parserResult); | ||||
| 	if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) | ||||
| @ -419,8 +421,10 @@ void CompilerContext::appendInlineAssembly( | ||||
| 	// so we essentially only optimize the ABI functions.
 | ||||
| 	if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) | ||||
| 	{ | ||||
| 		bool const isCreation = m_runtimeContext != nullptr; | ||||
| 		yul::OptimiserSuite::run( | ||||
| 			yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), | ||||
| 			dialect, | ||||
| 			yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment), | ||||
| 			*parserResult, | ||||
| 			analysisInfo, | ||||
| 			_optimiserSettings.optimizeStackAllocation, | ||||
| @ -431,7 +435,7 @@ void CompilerContext::appendInlineAssembly( | ||||
| 			analysisInfo, | ||||
| 			errorReporter, | ||||
| 			boost::none, | ||||
| 			yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), | ||||
| 			dialect, | ||||
| 			identifierAccess.resolve | ||||
| 		).analyze(*parserResult)) | ||||
| 			reportError("Optimizer introduced error into inline assembly."); | ||||
|  | ||||
| @ -228,34 +228,21 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) | ||||
| 	// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
 | ||||
| 	if (!_constructor.parameters().empty()) | ||||
| 	{ | ||||
| 		unsigned argumentSize = 0; | ||||
| 		for (ASTPointer<VariableDeclaration> const& var: _constructor.parameters()) | ||||
| 			if (var->annotation().type->isDynamicallySized()) | ||||
| 			{ | ||||
| 				argumentSize = 0; | ||||
| 				break; | ||||
| 			} | ||||
| 			else | ||||
| 				argumentSize += var->annotation().type->calldataEncodedSize(); | ||||
| 
 | ||||
| 		CompilerUtils(m_context).fetchFreeMemoryPointer(); | ||||
| 		if (argumentSize == 0) | ||||
| 		{ | ||||
| 			// argument size is dynamic, use CODESIZE to determine it
 | ||||
| 			m_context.appendProgramSize(); // program itself
 | ||||
| 			// CODESIZE is program plus manually added arguments
 | ||||
| 			m_context << Instruction::CODESIZE << Instruction::SUB; | ||||
| 		} | ||||
| 		else | ||||
| 			m_context << u256(argumentSize); | ||||
| 		// CODESIZE returns the actual size of the code,
 | ||||
| 		// which is the size of the generated code (``programSize``)
 | ||||
| 		// plus the constructor arguments added to the transaction payload.
 | ||||
| 		m_context.appendProgramSize(); | ||||
| 		m_context << Instruction::CODESIZE << Instruction::SUB; | ||||
| 		// stack: <memptr> <argument size>
 | ||||
| 		m_context << Instruction::DUP1; | ||||
| 		m_context.appendProgramSize(); | ||||
| 		m_context << Instruction::DUP4 << Instruction::CODECOPY; | ||||
| 		m_context << Instruction::DUP2 << Instruction::ADD; | ||||
| 		m_context << Instruction::DUP1; | ||||
| 		// stack: <memptr> <argument size>
 | ||||
| 		m_context << Instruction::DUP2 << Instruction::DUP2 << Instruction::ADD; | ||||
| 		// stack: <memptr> <argument size> <mem end>
 | ||||
| 		CompilerUtils(m_context).storeFreeMemoryPointer(); | ||||
| 		// stack: <memptr>
 | ||||
| 		// stack: <memptr> <argument size>
 | ||||
| 		CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true); | ||||
| 	} | ||||
| 	_constructor.accept(*this); | ||||
|  | ||||
| @ -206,6 +206,12 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const | ||||
| 				CompilerUtils(m_context).splitExternalFunctionType(false); | ||||
| 				cleaned = true; | ||||
| 			} | ||||
| 			else if (fun->kind() == FunctionType::Kind::Internal) | ||||
| 			{ | ||||
| 				m_context << Instruction::DUP1 << Instruction::ISZERO; | ||||
| 				CompilerUtils(m_context).pushZeroValue(*fun); | ||||
| 				m_context << Instruction::MUL << Instruction::OR; | ||||
| 			} | ||||
| 		} | ||||
| 		if (!cleaned) | ||||
| 		{ | ||||
| @ -288,7 +294,8 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc | ||||
| 	{ | ||||
| 		solAssert( | ||||
| 			_sourceType.category() == m_dataType->category(), | ||||
| 			"Wrong type conversation for assignment."); | ||||
| 			"Wrong type conversation for assignment." | ||||
| 		); | ||||
| 		if (m_dataType->category() == Type::Category::Array) | ||||
| 		{ | ||||
| 			m_context << Instruction::POP; // remove byte offset
 | ||||
| @ -313,9 +320,9 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc | ||||
| 			solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported."); | ||||
| 			for (auto const& member: structType.members(nullptr)) | ||||
| 			{ | ||||
| 				// assign each member that is not a mapping
 | ||||
| 				// assign each member that can live outside of storage
 | ||||
| 				TypePointer const& memberType = member.type; | ||||
| 				if (memberType->category() == Type::Category::Mapping) | ||||
| 				if (!memberType->canLiveOutsideStorage()) | ||||
| 					continue; | ||||
| 				TypePointer sourceMemberType = sourceType.memberType(member.name); | ||||
| 				if (sourceType.location() == DataLocation::Storage) | ||||
|  | ||||
| @ -104,6 +104,61 @@ string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata) | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType) | ||||
| { | ||||
| 	string functionName = | ||||
| 		string(_assert ? "assert_helper" : "require_helper") + | ||||
| 		(_messageType ? ("_" + _messageType->identifier()) : ""); | ||||
| 
 | ||||
| 	solAssert(!_assert || !_messageType, "Asserts can't have messages!"); | ||||
| 
 | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		if (!_messageType) | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>(condition) { | ||||
| 					if iszero(condition) { <invalidOrRevert> } | ||||
| 				} | ||||
| 			)") | ||||
| 			("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)") | ||||
| 			("functionName", functionName) | ||||
| 			.render(); | ||||
| 
 | ||||
| 		int const hashHeaderSize = 4; | ||||
| 		int const byteSize = 8; | ||||
| 		u256 const errorHash = | ||||
| 			u256(FixedHash<hashHeaderSize>::Arith( | ||||
| 				FixedHash<hashHeaderSize>(dev::keccak256("Error(string)")) | ||||
| 			)) << (256 - hashHeaderSize * byteSize); | ||||
| 
 | ||||
| 		string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector) | ||||
| 			.tupleEncoder( | ||||
| 				{_messageType}, | ||||
| 				{TypeProvider::stringMemory()} | ||||
| 			); | ||||
| 
 | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(condition <messageVars>) { | ||||
| 				if iszero(condition) { | ||||
| 					let fmp := mload(<freeMemPointer>) | ||||
| 					mstore(fmp, <errorHash>) | ||||
| 					let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>) <messageVars>) | ||||
| 					revert(fmp, sub(end, fmp)) | ||||
| 				} | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer)) | ||||
| 		("errorHash", formatNumber(errorHash)) | ||||
| 		("abiEncodeFunc", encodeFunc) | ||||
| 		("hashHeaderSize", to_string(hashHeaderSize)) | ||||
| 		("messageVars", | ||||
| 			(_messageType->sizeOnStack() > 0 ? ", " : "") + | ||||
| 			suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack()) | ||||
| 		) | ||||
| 		.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::leftAlignFunction(Type const& _type) | ||||
| { | ||||
| 	string functionName = string("leftAlign_") + _type.identifier(); | ||||
| @ -206,35 +261,49 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) | ||||
| 	// Note that if this is extended with signed shifts,
 | ||||
| 	// the opcodes SAR and SDIV behave differently with regards to rounding!
 | ||||
| 
 | ||||
| 	string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; | ||||
| 	if (m_evmVersion.hasBitwiseShifting()) | ||||
| 	{ | ||||
| 		return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 			return | ||||
| 				Whiskers(R"( | ||||
| 				function <functionName>(value) -> newValue { | ||||
| 					newValue := shr(<numBits>, value) | ||||
| 				} | ||||
| 				)") | ||||
| 				("functionName", functionName) | ||||
| 				("numBits", to_string(_numBits)) | ||||
| 				.render(); | ||||
| 		}); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 			return | ||||
| 				Whiskers(R"( | ||||
| 				function <functionName>(value) -> newValue { | ||||
| 					newValue := div(value, <multiplier>) | ||||
| 				} | ||||
| 				)") | ||||
| 				("functionName", functionName) | ||||
| 				("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) | ||||
| 				.render(); | ||||
| 		}); | ||||
| 	} | ||||
| 	string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		return | ||||
| 			Whiskers(R"( | ||||
| 			function <functionName>(value) -> newValue { | ||||
| 				newValue := | ||||
| 				<?hasShifts> | ||||
| 					shr(<numBits>, value) | ||||
| 				<!hasShifts> | ||||
| 					div(value, <multiplier>) | ||||
| 				</hasShifts> | ||||
| 			} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("hasShifts", m_evmVersion.hasBitwiseShifting()) | ||||
| 			("numBits", to_string(_numBits)) | ||||
| 			("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) | ||||
| 			.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) | ||||
| { | ||||
| 	solAssert(_numBytes <= 32, ""); | ||||
| 	solAssert(_shiftBytes <= 32, ""); | ||||
| 	size_t numBits = _numBytes * 8; | ||||
| 	size_t shiftBits = _shiftBytes * 8; | ||||
| 	string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		return | ||||
| 			Whiskers(R"( | ||||
| 			function <functionName>(value, toInsert) -> result { | ||||
| 				let mask := <mask> | ||||
| 				toInsert := <shl>(toInsert) | ||||
| 				value := and(value, not(mask)) | ||||
| 				result := or(value, and(toInsert, mask)) | ||||
| 			} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits)) | ||||
| 			("shl", shiftLeftFunction(shiftBits)) | ||||
| 			.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::roundUpFunction() | ||||
| @ -257,74 +326,135 @@ string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits) | ||||
| 	solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); | ||||
| 	string functionName = "checked_add_uint_" + to_string(_bits); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		if (_bits < 256) | ||||
| 			return | ||||
| 				Whiskers(R"( | ||||
| 				function <functionName>(x, y) -> sum { | ||||
| 		return | ||||
| 			Whiskers(R"( | ||||
| 			function <functionName>(x, y) -> sum { | ||||
| 				<?shortType> | ||||
| 					let mask := <mask> | ||||
| 					sum := add(and(x, mask), and(y, mask)) | ||||
| 					if and(sum, not(mask)) { revert(0, 0) } | ||||
| 				} | ||||
| 				)") | ||||
| 				("functionName", functionName) | ||||
| 				("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) | ||||
| 				.render(); | ||||
| 		else | ||||
| 			return | ||||
| 				Whiskers(R"( | ||||
| 				function <functionName>(x, y) -> sum { | ||||
| 				<!shortType> | ||||
| 					sum := add(x, y) | ||||
| 					if lt(sum, x) { revert(0, 0) } | ||||
| 				} | ||||
| 				)") | ||||
| 				</shortType> | ||||
| 			} | ||||
| 			)") | ||||
| 			("shortType", _bits < 256) | ||||
| 			("functionName", functionName) | ||||
| 			("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) | ||||
| 			.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::overflowCheckedUIntMulFunction(size_t _bits) | ||||
| { | ||||
| 	solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); | ||||
| 	string functionName = "checked_mul_uint_" + to_string(_bits); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		return | ||||
| 			// - The current overflow check *before* the multiplication could
 | ||||
| 			//   be replaced by the following check *after* the multiplication:
 | ||||
| 			//   if and(iszero(iszero(x)), iszero(eq(div(product, x), y))) { revert(0, 0) }
 | ||||
| 			// - The case the x equals 0 could be treated separately and directly return zero.
 | ||||
| 			Whiskers(R"( | ||||
| 			function <functionName>(x, y) -> product { | ||||
| 				if and(iszero(iszero(x)), lt(div(<mask>, x), y)) { revert(0, 0) } | ||||
| 				<?shortType> | ||||
| 					product := mulmod(x, y, <powerOfTwo>) | ||||
| 				<!shortType> | ||||
| 					product := mul(x, y) | ||||
| 				</shortType> | ||||
| 			} | ||||
| 			)") | ||||
| 				("shortType", _bits < 256) | ||||
| 				("functionName", functionName) | ||||
| 				("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits)) | ||||
| 				("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) | ||||
| 				.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) | ||||
| { | ||||
| 	unsigned bits = _type.numBits(); | ||||
| 	solAssert(0 < bits && bits <= 256 && bits % 8 == 0, ""); | ||||
| 	string functionName = "checked_div_" + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		return | ||||
| 			Whiskers(R"( | ||||
| 			function <functionName>(x, y) -> r { | ||||
| 				if iszero(y) { revert(0, 0) } | ||||
| 				<?signed> | ||||
| 				// x / -1 == x
 | ||||
| 				if and( | ||||
| 					eq(x, <minVal>), | ||||
| 					eq(y, sub(0, 1)) | ||||
| 				) { revert(0, 0) } | ||||
| 				</signed> | ||||
| 				r := <?signed>s</signed>div(x, y) | ||||
| 			} | ||||
| 			)") | ||||
| 				("functionName", functionName) | ||||
| 				("signed", _type.isSigned()) | ||||
| 				("minVal", (0 - (u256(1) << (bits - 1))).str()) | ||||
| 				.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::overflowCheckedUIntSubFunction() | ||||
| { | ||||
| 	string functionName = "checked_sub_uint"; | ||||
| 	return m_functionCollector->createFunction(functionName, [&] { | ||||
| 		return | ||||
| 			Whiskers(R"( | ||||
| 			function <functionName>(x, y) -> diff { | ||||
| 				if lt(x, y) { revert(0, 0) } | ||||
| 				diff := sub(x, y) | ||||
| 			} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) | ||||
| { | ||||
| 	string functionName = "array_length_" + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		Whiskers w(R"( | ||||
| 			function <functionName>(value) -> length { | ||||
| 				<body> | ||||
| 				<?dynamic> | ||||
| 					<?memory> | ||||
| 						length := mload(value) | ||||
| 					</memory> | ||||
| 					<?storage> | ||||
| 						length := sload(value) | ||||
| 						<?byteArray> | ||||
| 							// Retrieve length both for in-place strings and off-place strings:
 | ||||
| 							// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
 | ||||
| 							// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
 | ||||
| 							// computes (x & (-1)) / 2, which is equivalent to just x / 2.
 | ||||
| 							let mask := sub(mul(0x100, iszero(and(length, 1))), 1) | ||||
| 							length := div(and(length, mask), 2) | ||||
| 						</byteArray> | ||||
| 					</storage> | ||||
| 				<!dynamic> | ||||
| 					length := <length> | ||||
| 				</dynamic> | ||||
| 			} | ||||
| 		)"); | ||||
| 		w("functionName", functionName); | ||||
| 		string body; | ||||
| 		w("dynamic", _type.isDynamicallySized()); | ||||
| 		if (!_type.isDynamicallySized()) | ||||
| 			body = "length := " + toCompactHexWithPrefix(_type.length()); | ||||
| 		else | ||||
| 		{ | ||||
| 			switch (_type.location()) | ||||
| 			{ | ||||
| 			case DataLocation::CallData: | ||||
| 				solAssert(false, "called regular array length function on calldata array"); | ||||
| 				break; | ||||
| 			case DataLocation::Memory: | ||||
| 				body = "length := mload(value)"; | ||||
| 				break; | ||||
| 			case DataLocation::Storage: | ||||
| 				if (_type.isByteArray()) | ||||
| 				{ | ||||
| 					// Retrieve length both for in-place strings and off-place strings:
 | ||||
| 					// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
 | ||||
| 					// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
 | ||||
| 					// computes (x & (-1)) / 2, which is equivalent to just x / 2.
 | ||||
| 					body = R"( | ||||
| 						length := sload(value) | ||||
| 						let mask := sub(mul(0x100, iszero(and(length, 1))), 1) | ||||
| 						length := div(and(length, mask), 2) | ||||
| 					)"; | ||||
| 				} | ||||
| 				else | ||||
| 					body = "length := sload(value)"; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		solAssert(!body.empty(), ""); | ||||
| 		w("body", body); | ||||
| 			w("length", toCompactHexWithPrefix(_type.length())); | ||||
| 		w("memory", _type.location() == DataLocation::Memory); | ||||
| 		w("storage", _type.location() == DataLocation::Storage); | ||||
| 		w("byteArray", _type.isByteArray()); | ||||
| 		if (_type.isDynamicallySized()) | ||||
| 			solAssert( | ||||
| 				_type.location() != DataLocation::CallData, | ||||
| 				"called regular array length function on calldata array" | ||||
| 			); | ||||
| 		return w.render(); | ||||
| 	}); | ||||
| } | ||||
| @ -338,20 +468,21 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) | ||||
| 			function <functionName>(length) -> size { | ||||
| 				// Make sure we can allocate memory without overflow
 | ||||
| 				if gt(length, 0xffffffffffffffff) { revert(0, 0) } | ||||
| 				size := <allocationSize> | ||||
| 				<addLengthSlot> | ||||
| 				<?byteArray> | ||||
| 					// round up
 | ||||
| 					size := and(add(length, 0x1f), not(0x1f)) | ||||
| 				<!byteArray> | ||||
| 					size := mul(length, 0x20) | ||||
| 				</byteArray> | ||||
| 				<?dynamic> | ||||
| 					// add length slot
 | ||||
| 					size := add(size, 0x20) | ||||
| 				</dynamic> | ||||
| 			} | ||||
| 		)"); | ||||
| 		w("functionName", functionName); | ||||
| 		if (_type.isByteArray()) | ||||
| 			// Round up
 | ||||
| 			w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); | ||||
| 		else | ||||
| 			w("allocationSize", "mul(length, 0x20)"); | ||||
| 		if (_type.isDynamicallySized()) | ||||
| 			w("addLengthSlot", "size := add(size, 0x20)"); | ||||
| 		else | ||||
| 			w("addLengthSlot", ""); | ||||
| 		w("byteArray", _type.isByteArray()); | ||||
| 		w("dynamic", _type.isDynamicallySized()); | ||||
| 		return w.render(); | ||||
| 	}); | ||||
| } | ||||
| @ -360,64 +491,30 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) | ||||
| { | ||||
| 	string functionName = "array_dataslot_" + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		switch (_type.location()) | ||||
| 		{ | ||||
| 			case DataLocation::Memory: | ||||
| 				if (_type.isDynamicallySized()) | ||||
| 					return Whiskers(R"( | ||||
| 						function <functionName>(memPtr) -> dataPtr { | ||||
| 							dataPtr := add(memPtr, 0x20) | ||||
| 						} | ||||
| 					)") | ||||
| 					("functionName", functionName) | ||||
| 					.render(); | ||||
| 				else | ||||
| 					return Whiskers(R"( | ||||
| 						function <functionName>(memPtr) -> dataPtr { | ||||
| 							dataPtr := memPtr | ||||
| 						} | ||||
| 					)") | ||||
| 					("functionName", functionName) | ||||
| 					.render(); | ||||
| 			case DataLocation::Storage: | ||||
| 				if (_type.isDynamicallySized()) | ||||
| 				{ | ||||
| 					Whiskers w(R"( | ||||
| 						function <functionName>(slot) -> dataSlot { | ||||
| 							mstore(0, slot) | ||||
| 							dataSlot := keccak256(0, 0x20) | ||||
| 						} | ||||
| 					)"); | ||||
| 					w("functionName", functionName); | ||||
| 					return w.render(); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					Whiskers w(R"( | ||||
| 						function <functionName>(slot) -> dataSlot { | ||||
| 							dataSlot := slot | ||||
| 						} | ||||
| 					)"); | ||||
| 					w("functionName", functionName); | ||||
| 					return w.render(); | ||||
| 				} | ||||
| 			case DataLocation::CallData: | ||||
| 			{ | ||||
| 				// Calldata arrays are stored as offset of the data area and length
 | ||||
| 				// on the stack, so the offset already points to the data area.
 | ||||
| 				// This might change, if calldata arrays are stored in a single
 | ||||
| 				// stack slot at some point.
 | ||||
| 				Whiskers w(R"( | ||||
| 					function <functionName>(slot) -> dataSlot { | ||||
| 						dataSlot := slot | ||||
| 					} | ||||
| 				)"); | ||||
| 				w("functionName", functionName); | ||||
| 				return w.render(); | ||||
| 		// No special processing for calldata arrays, because they are stored as
 | ||||
| 		// offset of the data area and length on the stack, so the offset already
 | ||||
| 		// points to the data area.
 | ||||
| 		// This might change, if calldata arrays are stored in a single
 | ||||
| 		// stack slot at some point.
 | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(ptr) -> data { | ||||
| 				data := ptr | ||||
| 				<?dynamic> | ||||
| 					<?memory> | ||||
| 						data := add(ptr, 0x20) | ||||
| 					</memory> | ||||
| 					<?storage> | ||||
| 						mstore(0, ptr) | ||||
| 						data := keccak256(0, 0x20) | ||||
| 					</storage> | ||||
| 				</dynamic> | ||||
| 			} | ||||
| 			default: | ||||
| 				solAssert(false, ""); | ||||
| 		} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("dynamic", _type.isDynamicallySized()) | ||||
| 		("memory", _type.location() == DataLocation::Memory) | ||||
| 		("storage", _type.location() == DataLocation::Storage) | ||||
| 		.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| @ -428,39 +525,170 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) | ||||
| 		solAssert(_type.baseType()->storageBytes() > 16, ""); | ||||
| 	string functionName = "array_nextElement_" + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		switch (_type.location()) | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(ptr) -> next { | ||||
| 				next := add(ptr, <advance>) | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		if (_type.location() == DataLocation::Memory) | ||||
| 			templ("advance", "0x20"); | ||||
| 		else if (_type.location() == DataLocation::Storage) | ||||
| 			templ("advance", "1"); | ||||
| 		else if (_type.location() == DataLocation::CallData) | ||||
| 			templ("advance", toCompactHexWithPrefix( | ||||
| 				_type.baseType()->isDynamicallyEncoded() ? | ||||
| 				32 : | ||||
| 				_type.baseType()->calldataEncodedSize() | ||||
| 			)); | ||||
| 		else | ||||
| 			solAssert(false, ""); | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType) | ||||
| { | ||||
| 	solAssert(_keyType.sizeOnStack() <= 1, ""); | ||||
| 
 | ||||
| 	string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		if (_mappingType.keyType()->isDynamicallySized()) | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>(slot <comma> <key>) -> dataSlot { | ||||
| 					dataSlot := <hash>(slot <comma> <key>) | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("key", _keyType.sizeOnStack() > 0 ? "key" : "") | ||||
| 			("comma", _keyType.sizeOnStack() > 0 ? "," : "") | ||||
| 			("hash", packedHashFunction( | ||||
| 				{&_keyType, TypeProvider::uint256()}, | ||||
| 				{_mappingType.keyType(), TypeProvider::uint256()} | ||||
| 			)) | ||||
| 			.render(); | ||||
| 		else | ||||
| 		{ | ||||
| 			case DataLocation::Memory: | ||||
| 				return Whiskers(R"( | ||||
| 					function <functionName>(memPtr) -> nextPtr { | ||||
| 						nextPtr := add(memPtr, 0x20) | ||||
| 					} | ||||
| 				)") | ||||
| 				("functionName", functionName) | ||||
| 				.render(); | ||||
| 			case DataLocation::Storage: | ||||
| 				return Whiskers(R"( | ||||
| 					function <functionName>(slot) -> nextSlot { | ||||
| 						nextSlot := add(slot, 1) | ||||
| 					} | ||||
| 				)") | ||||
| 				("functionName", functionName) | ||||
| 				.render(); | ||||
| 			case DataLocation::CallData: | ||||
| 				return Whiskers(R"( | ||||
| 					function <functionName>(calldataPtr) -> nextPtr { | ||||
| 						nextPtr := add(calldataPtr, <stride>) | ||||
| 					} | ||||
| 				)") | ||||
| 				("stride", toCompactHexWithPrefix(_type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize())) | ||||
| 				("functionName", functionName) | ||||
| 				.render(); | ||||
| 			default: | ||||
| 				solAssert(false, ""); | ||||
| 			solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); | ||||
| 			solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), ""); | ||||
| 			solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, ""); | ||||
| 			Whiskers templ(R"( | ||||
| 				function <functionName>(slot <key>) -> dataSlot { | ||||
| 					mstore(0, <convertedKey>) | ||||
| 					mstore(0x20, slot) | ||||
| 					dataSlot := keccak256(0, 0x40) | ||||
| 				} | ||||
| 			)"); | ||||
| 			templ("functionName", functionName); | ||||
| 			templ("key", _keyType.sizeOnStack() == 1 ? ", key" : ""); | ||||
| 			if (_keyType.sizeOnStack() == 0) | ||||
| 				templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()"); | ||||
| 			else | ||||
| 				templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)"); | ||||
| 			return templ.render(); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes) | ||||
| { | ||||
| 	solUnimplementedAssert(!_splitFunctionTypes, ""); | ||||
| 	string functionName = | ||||
| 		"read_from_storage_" + | ||||
| 		string(_splitFunctionTypes ? "split_" : "") + | ||||
| 		"offset_" + | ||||
| 		to_string(_offset) + | ||||
| 		"_" + | ||||
| 		_type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&] { | ||||
| 		solAssert(_type.sizeOnStack() == 1, ""); | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(slot) -> value { | ||||
| 				value := <extract>(sload(slot)) | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("extract", extractFromStorageValue(_type, _offset, false)) | ||||
| 		.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes) | ||||
| { | ||||
| 	solUnimplementedAssert(!_splitFunctionTypes, ""); | ||||
| 
 | ||||
| 	string functionName = | ||||
| 		"extract_from_storage_value_" + | ||||
| 		string(_splitFunctionTypes ? "split_" : "") + | ||||
| 		"offset_" + | ||||
| 		to_string(_offset) + | ||||
| 		_type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&] { | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(slot_value) -> value { | ||||
| 				value := <cleanupStorage>(<shr>(slot_value)) | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("shr", shiftRightFunction(_offset * 8)) | ||||
| 		("cleanupStorage", cleanupFromStorageFunction(_type, false)) | ||||
| 		.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) | ||||
| { | ||||
| 	solAssert(_type.isValueType(), ""); | ||||
| 	solUnimplementedAssert(!_splitFunctionTypes, ""); | ||||
| 
 | ||||
| 	string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&] { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) -> cleaned { | ||||
| 				cleaned := <cleaned> | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 
 | ||||
| 		unsigned storageBytes = _type.storageBytes(); | ||||
| 		if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type)) | ||||
| 			if (type->isSigned() && storageBytes != 32) | ||||
| 			{ | ||||
| 				templ("cleaned", "signextend(" + to_string(storageBytes - 1) + ", value)"); | ||||
| 				return templ.render(); | ||||
| 			} | ||||
| 
 | ||||
| 		if (storageBytes == 32) | ||||
| 			templ("cleaned", "value"); | ||||
| 		else if (_type.leftAligned()) | ||||
| 			templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)"); | ||||
| 		else | ||||
| 			templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")"); | ||||
| 
 | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::prepareStoreFunction(Type const& _type) | ||||
| { | ||||
| 	solUnimplementedAssert(_type.category() != Type::Category::Function, ""); | ||||
| 
 | ||||
| 	string functionName = "prepare_store_" + _type.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(value) -> ret { | ||||
| 				ret := <actualPrepare> | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		if (_type.category() == Type::Category::FixedBytes) | ||||
| 			templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)"); | ||||
| 		else | ||||
| 			templ("actualPrepare", "value"); | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::allocationFunction() | ||||
| { | ||||
| 	string functionName = "allocateMemory"; | ||||
| @ -482,6 +710,9 @@ string YulUtilFunctions::allocationFunction() | ||||
| 
 | ||||
| string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) | ||||
| { | ||||
| 	if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) | ||||
| 		return conversionFunctionSpecial(_from, _to); | ||||
| 
 | ||||
| 	string functionName = | ||||
| 		"convert_" + | ||||
| 		_from.identifier() + | ||||
| @ -782,6 +1013,62 @@ string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFail | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::packedHashFunction( | ||||
| 	vector<Type const*> const& _givenTypes, | ||||
| 	vector<Type const*> const& _targetTypes | ||||
| ) | ||||
| { | ||||
| 	string functionName = string("packed_hashed_"); | ||||
| 	for (auto const& t: _givenTypes) | ||||
| 		functionName += t->identifier() + "_"; | ||||
| 	functionName += "_to_"; | ||||
| 	for (auto const& t: _targetTypes) | ||||
| 		functionName += t->identifier() + "_"; | ||||
| 	size_t sizeOnStack = 0; | ||||
| 	for (Type const* t: _givenTypes) | ||||
| 		sizeOnStack += t->sizeOnStack(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(<variables>) -> hash { | ||||
| 				let pos := mload(<freeMemoryPointer>) | ||||
| 				let end := <packedEncode>(pos <comma> <variables>) | ||||
| 				hash := keccak256(pos, sub(end, pos)) | ||||
| 			} | ||||
| 		)"); | ||||
| 		templ("functionName", functionName); | ||||
| 		templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); | ||||
| 		templ("comma", sizeOnStack > 0 ? "," : ""); | ||||
| 		templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); | ||||
| 		templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::forwardingRevertFunction() | ||||
| { | ||||
| 	bool forward = m_evmVersion.supportsReturndata(); | ||||
| 	string functionName = "revert_forward_" + to_string(forward); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		if (forward) | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>() { | ||||
| 					returndatacopy(0, 0, returndatasize()) | ||||
| 					revert(0, returndatasize()) | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			.render(); | ||||
| 		else | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>() { | ||||
| 					revert(0, 0) | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) | ||||
| { | ||||
| 	string result; | ||||
| @ -799,3 +1086,166 @@ string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_ | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type) | ||||
| { | ||||
| 	IntegerType const& type = dynamic_cast<IntegerType const&>(_type); | ||||
| 
 | ||||
| 	string const functionName = "decrement_" + _type.identifier(); | ||||
| 
 | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		u256 minintval; | ||||
| 
 | ||||
| 		// Smallest admissible value to decrement
 | ||||
| 		if (type.isSigned()) | ||||
| 			minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; | ||||
| 		else | ||||
| 			minintval = 1; | ||||
| 
 | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(value) -> ret { | ||||
| 				if <lt>(value, <minval>) { revert(0,0) } | ||||
| 				ret := sub(value, 1) | ||||
| 			} | ||||
| 		)") | ||||
| 			("functionName", functionName) | ||||
| 			("minval", toCompactHexWithPrefix(minintval)) | ||||
| 			("lt", type.isSigned() ? "slt" : "lt") | ||||
| 			.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type) | ||||
| { | ||||
| 	IntegerType const& type = dynamic_cast<IntegerType const&>(_type); | ||||
| 
 | ||||
| 	string const functionName = "increment_" + _type.identifier(); | ||||
| 
 | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		u256 maxintval; | ||||
| 
 | ||||
| 		// Biggest admissible value to increment
 | ||||
| 		if (type.isSigned()) | ||||
| 			maxintval = (u256(1) << (type.numBits() - 1)) - 2; | ||||
| 		else | ||||
| 			maxintval = (u256(1) << type.numBits()) - 2; | ||||
| 
 | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(value) -> ret { | ||||
| 				if <gt>(value, <maxval>) { revert(0,0) } | ||||
| 				ret := add(value, 1) | ||||
| 			} | ||||
| 		)") | ||||
| 			("functionName", functionName) | ||||
| 			("maxval", toCompactHexWithPrefix(maxintval)) | ||||
| 			("gt", type.isSigned() ? "sgt" : "gt") | ||||
| 			.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type) | ||||
| { | ||||
| 	IntegerType const& type = dynamic_cast<IntegerType const&>(_type); | ||||
| 	solAssert(type.isSigned(), "Expected signed type!"); | ||||
| 
 | ||||
| 	string const functionName = "negate_" + _type.identifier(); | ||||
| 
 | ||||
| 	u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; | ||||
| 
 | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(_value) -> ret { | ||||
| 				if slt(_value, <minval>) { revert(0,0) } | ||||
| 				ret := sub(0, _value) | ||||
| 			} | ||||
| 		)") | ||||
| 			("functionName", functionName) | ||||
| 			("minval", toCompactHexWithPrefix(minintval)) | ||||
| 			.render(); | ||||
| 		}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::zeroValueFunction(Type const& _type) | ||||
| { | ||||
| 	solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!"); | ||||
| 	solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented"); | ||||
| 
 | ||||
| 	string const functionName = "zero_value_for_" + _type.identifier(); | ||||
| 
 | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>() -> ret { | ||||
| 				<body> | ||||
| 			} | ||||
| 		)") | ||||
| 			("functionName", functionName) | ||||
| 			("body", "ret := 0x0") | ||||
| 			.render(); | ||||
| 		}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to) | ||||
| { | ||||
| 	string functionName = | ||||
| 		"convert_" + | ||||
| 		_from.identifier() + | ||||
| 		"_to_" + | ||||
| 		_to.identifier(); | ||||
| 	return m_functionCollector->createFunction(functionName, [&]() { | ||||
| 		solUnimplementedAssert( | ||||
| 			_from.category() == Type::Category::StringLiteral, | ||||
| 			"Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented." | ||||
| 		); | ||||
| 		string const& data = dynamic_cast<StringLiteralType const&>(_from).value(); | ||||
| 		if (_to.category() == Type::Category::FixedBytes) | ||||
| 		{ | ||||
| 			unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_to).numBytes(); | ||||
| 			solAssert(data.size() <= 32, ""); | ||||
| 			Whiskers templ(R"( | ||||
| 				function <functionName>() -> converted { | ||||
| 					converted := <data> | ||||
| 				} | ||||
| 			)"); | ||||
| 			templ("functionName", functionName); | ||||
| 			templ("data", formatNumber( | ||||
| 				h256::Arith(h256(data, h256::AlignLeft)) & | ||||
| 				(~(u256(-1) >> (8 * numBytes))) | ||||
| 			)); | ||||
| 			return templ.render(); | ||||
| 		} | ||||
| 		else if (_to.category() == Type::Category::Array) | ||||
| 		{ | ||||
| 			auto const& arrayType = dynamic_cast<ArrayType const&>(_to); | ||||
| 			solAssert(arrayType.isByteArray(), ""); | ||||
| 			size_t words = (data.size() + 31) / 32; | ||||
| 			size_t storageSize = 32 + words * 32; | ||||
| 
 | ||||
| 			Whiskers templ(R"( | ||||
| 				function <functionName>() -> converted { | ||||
| 					converted := <allocate>(<storageSize>) | ||||
| 					mstore(converted, <size>) | ||||
| 					<#word> | ||||
| 						mstore(add(converted, <offset>), <wordValue>) | ||||
| 					</word> | ||||
| 				} | ||||
| 			)"); | ||||
| 			templ("functionName", functionName); | ||||
| 			templ("allocate", allocationFunction()); | ||||
| 			templ("storageSize", to_string(storageSize)); | ||||
| 			templ("size", to_string(data.size())); | ||||
| 			vector<map<string, string>> wordParams(words); | ||||
| 			for (size_t i = 0; i < words; ++i) | ||||
| 			{ | ||||
| 				wordParams[i]["offset"] = to_string(32 + i * 32); | ||||
| 				wordParams[i]["wordValue"] = "0x" + h256(data.substr(32 * i, 32), h256::AlignLeft).hex(); | ||||
| 			} | ||||
| 			templ("word", wordParams); | ||||
| 			return templ.render(); | ||||
| 		} | ||||
| 		else | ||||
| 			solAssert( | ||||
| 				false, | ||||
| 				"Invalid conversion from string literal to " + _to.toString() + " requested." | ||||
| 			); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @ -26,6 +26,7 @@ | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| @ -34,6 +35,8 @@ namespace solidity | ||||
| 
 | ||||
| class Type; | ||||
| class ArrayType; | ||||
| class MappingType; | ||||
| class IntegerType; | ||||
| 
 | ||||
| /**
 | ||||
|  * Component that can generate various useful Yul functions. | ||||
| @ -62,6 +65,10 @@ public: | ||||
| 	/// Pads with zeros and might write more than exactly length.
 | ||||
| 	std::string copyToMemoryFunction(bool _fromCalldata); | ||||
| 
 | ||||
| 	// @returns the name of a function that has the equivalent logic of an
 | ||||
| 	// `assert` or `require` call.
 | ||||
| 	std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr); | ||||
| 
 | ||||
| 	/// @returns the name of a function that takes a (cleaned) value of the given value type and
 | ||||
| 	/// left-aligns it, usually for use in non-padded encoding.
 | ||||
| 	std::string leftAlignFunction(Type const& _type); | ||||
| @ -69,12 +76,28 @@ public: | ||||
| 	std::string shiftLeftFunction(size_t _numBits); | ||||
| 	std::string shiftRightFunction(size_t _numBits); | ||||
| 
 | ||||
| 	/// @returns the name of a function f(value, toInsert) -> newValue which replaces the
 | ||||
| 	/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
 | ||||
| 	/// byte) by the _numBytes least significant bytes of `toInsert`.
 | ||||
| 	std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); | ||||
| 
 | ||||
| 	/// @returns the name of a function that rounds its input to the next multiple
 | ||||
| 	/// of 32 or the input if it is a multiple of 32.
 | ||||
| 	std::string roundUpFunction(); | ||||
| 
 | ||||
| 	std::string overflowCheckedUIntAddFunction(size_t _bits); | ||||
| 
 | ||||
| 	std::string overflowCheckedUIntMulFunction(size_t _bits); | ||||
| 
 | ||||
| 	/// @returns name of function to perform division on integers.
 | ||||
| 	/// Checks for division by zero and the special case of
 | ||||
| 	/// signed division of the smallest number by -1.
 | ||||
| 	std::string overflowCheckedIntDivFunction(IntegerType const& _type); | ||||
| 
 | ||||
| 	/// @returns computes the difference between two values.
 | ||||
| 	/// Assumes the input to be in range for the type.
 | ||||
| 	std::string overflowCheckedUIntSubFunction(); | ||||
| 
 | ||||
| 	std::string arrayLengthFunction(ArrayType const& _type); | ||||
| 	/// @returns the name of a function that computes the number of bytes required
 | ||||
| 	/// to store an array in memory given its length (internally encoded, not ABI encoded).
 | ||||
| @ -88,6 +111,39 @@ public: | ||||
| 	/// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot.
 | ||||
| 	std::string nextArrayElementFunction(ArrayType const& _type); | ||||
| 
 | ||||
| 	/// @returns the name of a function that performs index access for mappings.
 | ||||
| 	/// @param _mappingType the type of the mapping
 | ||||
| 	/// @param _keyType the type of the value provided
 | ||||
| 	std::string mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType); | ||||
| 
 | ||||
| 	/// @returns a function that reads a value type from storage.
 | ||||
| 	/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
 | ||||
| 	/// @param _splitFunctionTypes if false, returns the address and function signature in a
 | ||||
| 	/// single variable.
 | ||||
| 	std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes); | ||||
| 
 | ||||
| 	/// @returns a function that extracts a value type from storage slot that has been
 | ||||
| 	/// retrieved already.
 | ||||
| 	/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
 | ||||
| 	/// @param _splitFunctionTypes if false, returns the address and function signature in a
 | ||||
| 	/// single variable.
 | ||||
| 	std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); | ||||
| 
 | ||||
| 	/// Performs cleanup after reading from a potentially compressed storage slot.
 | ||||
| 	/// The function does not perform any validation, it just masks or sign-extends
 | ||||
| 	/// higher order bytes or left-aligns (in case of bytesNN).
 | ||||
| 	/// The storage cleanup expects the value to be right-aligned with potentially
 | ||||
| 	/// dirty higher order bytes.
 | ||||
| 	/// @param _splitFunctionTypes if false, returns the address and function signature in a
 | ||||
| 	/// single variable.
 | ||||
| 	std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes); | ||||
| 
 | ||||
| 	/// @returns the name of a function that prepares a value of the given type
 | ||||
| 	/// for being stored in storage. This usually includes cleanup and right-alignment
 | ||||
| 	/// to fit the number of bytes in storage.
 | ||||
| 	/// The resulting value might still have dirty higher order bits.
 | ||||
| 	std::string prepareStoreFunction(Type const& _type); | ||||
| 
 | ||||
| 	/// @returns the name of a function that allocates memory.
 | ||||
| 	/// Modifies the "free memory pointer"
 | ||||
| 	/// Arguments: size
 | ||||
| @ -115,13 +171,32 @@ public: | ||||
| 	/// This is used for data decoded from external sources.
 | ||||
| 	std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); | ||||
| 
 | ||||
| 	std::string packedHashFunction(std::vector<Type const*> const& _givenTypes, std::vector<Type const*> const& _targetTypes); | ||||
| 
 | ||||
| 	/// @returns the name of a function that reverts and uses returndata (if available)
 | ||||
| 	/// as reason string.
 | ||||
| 	std::string forwardingRevertFunction(); | ||||
| 
 | ||||
| 	/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
 | ||||
| 	/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
 | ||||
| 	/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.
 | ||||
| 	/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
 | ||||
| 	static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix); | ||||
| 
 | ||||
| 
 | ||||
| 	std::string incrementCheckedFunction(Type const& _type); | ||||
| 	std::string decrementCheckedFunction(Type const& _type); | ||||
| 
 | ||||
| 	std::string negateNumberCheckedFunction(Type const& _type); | ||||
| 
 | ||||
| 	/// @returns the name of a function that returns the zero value for the
 | ||||
| 	/// provided type
 | ||||
| 	std::string zeroValueFunction(Type const& _type); | ||||
| private: | ||||
| 	/// Special case of conversionFunction - handles everything that does not
 | ||||
| 	/// use exactly one variable to hold the value.
 | ||||
| 	std::string conversionFunctionSpecial(Type const& _from, Type const& _to); | ||||
| 
 | ||||
| 	langutil::EVMVersion m_evmVersion; | ||||
| 	std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector; | ||||
| }; | ||||
|  | ||||
| @ -39,7 +39,7 @@ string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl | ||||
| 	return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id()); | ||||
| } | ||||
| 
 | ||||
| string IRGenerationContext::variableName(VariableDeclaration const& _varDecl) | ||||
| string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl) | ||||
| { | ||||
| 	solAssert( | ||||
| 		m_localVariables.count(&_varDecl), | ||||
| @ -48,6 +48,15 @@ string IRGenerationContext::variableName(VariableDeclaration const& _varDecl) | ||||
| 	return m_localVariables[&_varDecl]; | ||||
| } | ||||
| 
 | ||||
| void IRGenerationContext::addStateVariable( | ||||
| 	VariableDeclaration const& _declaration, | ||||
| 	u256 _storageOffset, | ||||
| 	unsigned _byteOffset | ||||
| ) | ||||
| { | ||||
| 	m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset); | ||||
| } | ||||
| 
 | ||||
| string IRGenerationContext::functionName(FunctionDefinition const& _function) | ||||
| { | ||||
| 	// @TODO previously, we had to distinguish creation context and runtime context,
 | ||||
| @ -85,18 +94,27 @@ string IRGenerationContext::newYulVariable() | ||||
| string IRGenerationContext::variable(Expression const& _expression) | ||||
| { | ||||
| 	unsigned size = _expression.annotation().type->sizeOnStack(); | ||||
| 	solUnimplementedAssert(size == 1, ""); | ||||
| 	return "expr_" + to_string(_expression.id()); | ||||
| 	string var = "expr_" + to_string(_expression.id()); | ||||
| 	if (size == 1) | ||||
| 		return var; | ||||
| 	else | ||||
| 		return YulUtilFunctions::suffixedVariableNameList(move(var) + "_", 1, 1 + size); | ||||
| } | ||||
| 
 | ||||
| string IRGenerationContext::variablePart(Expression const& _expression, size_t _part) | ||||
| { | ||||
| 	size_t numVars = _expression.annotation().type->sizeOnStack(); | ||||
| 	solAssert(numVars > 1, ""); | ||||
| 	solAssert(1 <= _part && _part <= numVars, ""); | ||||
| 	return "expr_" + to_string(_expression.id()) + "_" + to_string(_part); | ||||
| } | ||||
| 
 | ||||
| string IRGenerationContext::internalDispatch(size_t _in, size_t _out) | ||||
| { | ||||
| 	// TODO can we limit the generated functions to only those visited
 | ||||
| 	// in the expression context? What about creation / runtime context?
 | ||||
| 	string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out); | ||||
| 	return m_functions->createFunction(funName, [&]() { | ||||
| 		Whiskers templ(R"( | ||||
| 			function <functionName>(fun <comma> <in>) -> <out> { | ||||
| 			function <functionName>(fun <comma> <in>) <arrow> <out> { | ||||
| 				switch fun | ||||
| 				<#cases> | ||||
| 				case <funID> | ||||
| @ -111,6 +129,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) | ||||
| 		templ("comma", _in > 0 ? "," : ""); | ||||
| 		YulUtilFunctions utils(m_evmVersion, m_functions); | ||||
| 		templ("in", utils.suffixedVariableNameList("in_", 0, _in)); | ||||
| 		templ("arrow", _out > 0 ? "->" : ""); | ||||
| 		templ("out", utils.suffixedVariableNameList("out_", 0, _out)); | ||||
| 		vector<map<string, string>> functions; | ||||
| 		for (auto const& contract: m_inheritanceHierarchy) | ||||
| @ -120,11 +139,21 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) | ||||
| 					function->parameters().size() == _in && | ||||
| 					function->returnParameters().size() == _out | ||||
| 				) | ||||
| 				{ | ||||
| 					// 0 is reserved for uninitialized function pointers
 | ||||
| 					solAssert(function->id() != 0, "Unexpected function ID: 0"); | ||||
| 
 | ||||
| 					functions.emplace_back(map<string, string> { | ||||
| 						{ "funID", to_string(function->id()) }, | ||||
| 						{ "name", functionName(*function)} | ||||
| 					}); | ||||
| 				} | ||||
| 		templ("cases", move(functions)); | ||||
| 		return templ.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| YulUtilFunctions IRGenerationContext::utils() | ||||
| { | ||||
| 	return YulUtilFunctions(m_evmVersion, m_functions); | ||||
| } | ||||
|  | ||||
| @ -26,6 +26,8 @@ | ||||
| 
 | ||||
| #include <liblangutil/EVMVersion.h> | ||||
| 
 | ||||
| #include <libdevcore/Common.h> | ||||
| 
 | ||||
| #include <string> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| @ -39,6 +41,7 @@ class ContractDefinition; | ||||
| class VariableDeclaration; | ||||
| class FunctionDefinition; | ||||
| class Expression; | ||||
| class YulUtilFunctions; | ||||
| 
 | ||||
| /**
 | ||||
|  * Class that contains contextual information during IR generation. | ||||
| @ -62,7 +65,16 @@ public: | ||||
| 
 | ||||
| 
 | ||||
| 	std::string addLocalVariable(VariableDeclaration const& _varDecl); | ||||
| 	std::string variableName(VariableDeclaration const& _varDecl); | ||||
| 	bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); } | ||||
| 	std::string localVariableName(VariableDeclaration const& _varDecl); | ||||
| 
 | ||||
| 	void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); | ||||
| 	bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } | ||||
| 	std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const | ||||
| 	{ | ||||
| 		return m_stateVariables.at(&_varDecl); | ||||
| 	} | ||||
| 
 | ||||
| 	std::string functionName(FunctionDefinition const& _function); | ||||
| 	FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration); | ||||
| 	std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration); | ||||
| @ -71,14 +83,24 @@ public: | ||||
| 	/// @returns the variable (or comma-separated list of variables) that contain
 | ||||
| 	/// the value of the given expression.
 | ||||
| 	std::string variable(Expression const& _expression); | ||||
| 	/// @returns the variable of a multi-variable expression. Variables are numbered
 | ||||
| 	/// starting from 1.
 | ||||
| 	std::string variablePart(Expression const& _expression, size_t _part); | ||||
| 
 | ||||
| 	std::string internalDispatch(size_t _in, size_t _out); | ||||
| 
 | ||||
| 	/// @returns a new copy of the utility function generator (but using the same function set).
 | ||||
| 	YulUtilFunctions utils(); | ||||
| 
 | ||||
| 	langutil::EVMVersion evmVersion() const { return m_evmVersion; }; | ||||
| 
 | ||||
| private: | ||||
| 	langutil::EVMVersion m_evmVersion; | ||||
| 	OptimiserSettings m_optimiserSettings; | ||||
| 	std::vector<ContractDefinition const*> m_inheritanceHierarchy; | ||||
| 	std::map<VariableDeclaration const*, std::string> m_localVariables; | ||||
| 	/// Storage offsets of state variables
 | ||||
| 	std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; | ||||
| 	std::shared_ptr<MultiUseYulFunctionCollector> m_functions; | ||||
| 	size_t m_varCounter = 0; | ||||
| }; | ||||
|  | ||||
| @ -71,6 +71,8 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract) | ||||
| 
 | ||||
| string IRGenerator::generate(ContractDefinition const& _contract) | ||||
| { | ||||
| 	solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented."); | ||||
| 
 | ||||
| 	Whiskers t(R"( | ||||
| 		object "<CreationObject>" { | ||||
| 			code { | ||||
| @ -89,18 +91,27 @@ string IRGenerator::generate(ContractDefinition const& _contract) | ||||
| 		} | ||||
| 	)"); | ||||
| 
 | ||||
| 	resetContext(); | ||||
| 	m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); | ||||
| 	resetContext(_contract); | ||||
| 
 | ||||
| 	t("CreationObject", creationObjectName(_contract)); | ||||
| 	t("memoryInit", memoryInit()); | ||||
| 	t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : ""); | ||||
| 	t("constructor", constructorCode(_contract)); | ||||
| 	t("deploy", deployCode(_contract)); | ||||
| 	// We generate code for all functions and rely on the optimizer to remove them again
 | ||||
| 	// TODO it would probably be better to only generate functions when internalDispatch or
 | ||||
| 	// virtualFunctionName is called - same below.
 | ||||
| 	for (auto const* contract: _contract.annotation().linearizedBaseContracts) | ||||
| 		for (auto const* fun: contract->definedFunctions()) | ||||
| 			generateFunction(*fun); | ||||
| 	t("functions", m_context.functionCollector()->requestedFunctions()); | ||||
| 
 | ||||
| 	resetContext(); | ||||
| 	resetContext(_contract); | ||||
| 	m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); | ||||
| 	t("RuntimeObject", runtimeObjectName(_contract)); | ||||
| 	t("dispatch", dispatchRoutine(_contract)); | ||||
| 	for (auto const* contract: _contract.annotation().linearizedBaseContracts) | ||||
| 		for (auto const* fun: contract->definedFunctions()) | ||||
| 			generateFunction(*fun); | ||||
| 	t("runtimeFunctions", m_context.functionCollector()->requestedFunctions()); | ||||
| 	return t.render(); | ||||
| } | ||||
| @ -116,7 +127,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function) | ||||
| { | ||||
| 	string functionName = m_context.functionName(_function); | ||||
| 	return m_context.functionCollector()->createFunction(functionName, [&]() { | ||||
| 		Whiskers t("\nfunction <functionName>(<params>) <returns> {\n<body>\n}\n"); | ||||
| 		Whiskers t(R"( | ||||
| 			function <functionName>(<params>) <returns> { | ||||
| 				for { let return_flag := 1 } return_flag {} { | ||||
| 					<body> | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		)"); | ||||
| 		t("functionName", functionName); | ||||
| 		string params; | ||||
| 		for (auto const& varDecl: _function.parameters()) | ||||
| @ -131,15 +149,21 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function) | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string IRGenerator::constructorCode(FunctionDefinition const& _constructor) | ||||
| string IRGenerator::constructorCode(ContractDefinition const& _contract) | ||||
| { | ||||
| 	string out; | ||||
| 	if (!_constructor.isPayable()) | ||||
| 		out = callValueCheck(); | ||||
| 	// TODO initialize state variables in base to derived order.
 | ||||
| 	// TODO base constructors
 | ||||
| 	// TODO callValueCheck if there is no constructor.
 | ||||
| 	if (FunctionDefinition const* constructor = _contract.constructor()) | ||||
| 	{ | ||||
| 		string out; | ||||
| 		if (!constructor->isPayable()) | ||||
| 			out = callValueCheck(); | ||||
| 		solUnimplementedAssert(constructor->parameters().empty(), ""); | ||||
| 		return move(out) + m_context.functionName(*constructor) + "()\n"; | ||||
| 	} | ||||
| 
 | ||||
| 	solUnimplemented("Constructors are not yet implemented."); | ||||
| 
 | ||||
| 	return out; | ||||
| 	return {}; | ||||
| } | ||||
| 
 | ||||
| string IRGenerator::deployCode(ContractDefinition const& _contract) | ||||
| @ -242,7 +266,7 @@ string IRGenerator::memoryInit() | ||||
| 		.render(); | ||||
| } | ||||
| 
 | ||||
| void IRGenerator::resetContext() | ||||
| void IRGenerator::resetContext(ContractDefinition const& _contract) | ||||
| { | ||||
| 	solAssert( | ||||
| 		m_context.functionCollector()->requestedFunctions().empty(), | ||||
| @ -250,4 +274,8 @@ void IRGenerator::resetContext() | ||||
| 	); | ||||
| 	m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); | ||||
| 	m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); | ||||
| 
 | ||||
| 	m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); | ||||
| 	for (auto const& var: ContractType(_contract).stateVariables()) | ||||
| 		m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); | ||||
| } | ||||
|  | ||||
| @ -57,7 +57,7 @@ private: | ||||
| 	/// Generates code for and returns the name of the function.
 | ||||
| 	std::string generateFunction(FunctionDefinition const& _function); | ||||
| 
 | ||||
| 	std::string constructorCode(FunctionDefinition const& _constructor); | ||||
| 	std::string constructorCode(ContractDefinition const& _contract); | ||||
| 	std::string deployCode(ContractDefinition const& _contract); | ||||
| 	std::string callValueCheck(); | ||||
| 
 | ||||
| @ -68,7 +68,7 @@ private: | ||||
| 
 | ||||
| 	std::string memoryInit(); | ||||
| 
 | ||||
| 	void resetContext(); | ||||
| 	void resetContext(ContractDefinition const& _contract); | ||||
| 
 | ||||
| 	langutil::EVMVersion const m_evmVersion; | ||||
| 	OptimiserSettings const m_optimiserSettings; | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -21,6 +21,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/ast/ASTVisitor.h> | ||||
| #include <libsolidity/codegen/ir/IRLValue.h> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| @ -42,25 +43,70 @@ public: | ||||
| 		m_utils(_utils) | ||||
| 	{} | ||||
| 
 | ||||
| 	std::string code() const { return m_code.str(); } | ||||
| 	std::string code() const; | ||||
| 
 | ||||
| 	bool visit(VariableDeclarationStatement const& _variableDeclaration) override; | ||||
| 	void endVisit(VariableDeclarationStatement const& _variableDeclaration) override; | ||||
| 	bool visit(Assignment const& _assignment) override; | ||||
| 	bool visit(Return const& _return) override; | ||||
| 	void endVisit(BinaryOperation const& _binOp) override; | ||||
| 	bool visit(FunctionCall const& _funCall) override; | ||||
| 	bool visit(TupleExpression const& _tuple) override; | ||||
| 	bool visit(IfStatement const& _ifStatement) override; | ||||
| 	bool visit(ForStatement const& _forStatement) override; | ||||
| 	bool visit(WhileStatement const& _whileStatement) override; | ||||
| 	bool visit(Continue const& _continueStatement) override; | ||||
| 	bool visit(Break const& _breakStatement) override; | ||||
| 	void endVisit(Return const& _return) override; | ||||
| 	void endVisit(UnaryOperation const& _unaryOperation) override; | ||||
| 	bool visit(BinaryOperation const& _binOp) override; | ||||
| 	void endVisit(FunctionCall const& _funCall) override; | ||||
| 	void endVisit(MemberAccess const& _memberAccess) override; | ||||
| 	bool visit(InlineAssembly const& _inlineAsm) override; | ||||
| 	bool visit(Identifier const& _identifier) override; | ||||
| 	void endVisit(IndexAccess const& _indexAccess) override; | ||||
| 	void endVisit(Identifier const& _identifier) override; | ||||
| 	bool visit(Literal const& _literal) override; | ||||
| 
 | ||||
| private: | ||||
| 	/// Appends code to call an external function with the given arguments.
 | ||||
| 	/// All involved expressions have already been visited.
 | ||||
| 	void appendExternalFunctionCall( | ||||
| 		FunctionCall const& _functionCall, | ||||
| 		std::vector<ASTPointer<Expression const>> const& _arguments | ||||
| 	); | ||||
| 
 | ||||
| 	std::string fetchFreeMem() const; | ||||
| 
 | ||||
| 	/// @returns a Yul expression representing the current value of @a _expression,
 | ||||
| 	/// converted to type @a _to if it does not yet have that type.
 | ||||
| 	std::string expressionAsType(Expression const& _expression, Type const& _to); | ||||
| 	std::ostream& defineExpression(Expression const& _expression); | ||||
| 	/// Defines only one of many variables corresponding to an expression.
 | ||||
| 	/// We start counting at 1 instead of 0.
 | ||||
| 	std::ostream& defineExpressionPart(Expression const& _expression, size_t _part); | ||||
| 
 | ||||
| 	void appendAndOrOperatorCode(BinaryOperation const& _binOp); | ||||
| 	void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr); | ||||
| 
 | ||||
| 	/// @returns code to perform the given binary operation in the given type on the two values.
 | ||||
| 	std::string binaryOperation( | ||||
| 		langutil::Token _op, | ||||
| 		Type const& _type, | ||||
| 		std::string const& _left, | ||||
| 		std::string const& _right | ||||
| 	); | ||||
| 
 | ||||
| 	void setLValue(Expression const& _expression, std::unique_ptr<IRLValue> _lvalue); | ||||
| 	void generateLoop( | ||||
| 		Statement const& _body, | ||||
| 		Expression const* _conditionExpression, | ||||
| 		Statement const*  _initExpression = nullptr, | ||||
| 		ExpressionStatement const* _loopExpression = nullptr, | ||||
| 		bool _isDoWhile = false | ||||
| 	); | ||||
| 
 | ||||
| 	static Type const& type(Expression const& _expression); | ||||
| 
 | ||||
| 	std::ostringstream m_code; | ||||
| 	IRGenerationContext& m_context; | ||||
| 	YulUtilFunctions& m_utils; | ||||
| 	std::unique_ptr<IRLValue> m_currentLValue; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										122
									
								
								libsolidity/codegen/ir/IRLValue.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								libsolidity/codegen/ir/IRLValue.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Generator for code that handles LValues. | ||||
|  */ | ||||
| 
 | ||||
| #include <libsolidity/codegen/ir/IRLValue.h> | ||||
| 
 | ||||
| #include <libsolidity/codegen/ir/IRGenerationContext.h> | ||||
| #include <libsolidity/codegen/YulUtilFunctions.h> | ||||
| #include <libsolidity/codegen/CompilerUtils.h> | ||||
| #include <libsolidity/ast/AST.h> | ||||
| 
 | ||||
| #include <libdevcore/Whiskers.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| 
 | ||||
| IRLocalVariable::IRLocalVariable( | ||||
| 	IRGenerationContext& _context, | ||||
| 	VariableDeclaration const& _varDecl | ||||
| ): | ||||
| 	IRLValue(_context, _varDecl.annotation().type), | ||||
| 	m_variableName(_context.localVariableName(_varDecl)) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| string IRLocalVariable::storeValue(string const& _value, Type const& _type) const | ||||
| { | ||||
| 	solAssert(_type == *m_type, "Storing different types - not necessarily a problem."); | ||||
| 	return m_variableName + " := " + _value + "\n"; | ||||
| } | ||||
| 
 | ||||
| string IRLocalVariable::setToZero() const | ||||
| { | ||||
| 	return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type); | ||||
| } | ||||
| 
 | ||||
| IRStorageItem::IRStorageItem( | ||||
| 	IRGenerationContext& _context, | ||||
| 	VariableDeclaration const& _varDecl | ||||
| ): | ||||
| 	IRLValue(_context, _varDecl.annotation().type) | ||||
| { | ||||
| 	u256 slot; | ||||
| 	unsigned offset; | ||||
| 	std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl); | ||||
| 	m_slot = toCompactHexWithPrefix(slot); | ||||
| 	m_offset = offset; | ||||
| } | ||||
| 
 | ||||
| IRStorageItem::IRStorageItem( | ||||
| 	IRGenerationContext& _context, | ||||
| 	string _slot, | ||||
| 	unsigned _offset, | ||||
| 	Type const& _type | ||||
| ): | ||||
| 	IRLValue(_context, &_type), | ||||
| 	m_slot(move(_slot)), | ||||
| 	m_offset(_offset) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| string IRStorageItem::retrieveValue() const | ||||
| { | ||||
| 	if (!m_type->isValueType()) | ||||
| 		return m_slot; | ||||
| 	solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); | ||||
| 	return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")"; | ||||
| } | ||||
| 
 | ||||
| string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const | ||||
| { | ||||
| 	if (m_type->isValueType()) | ||||
| 	{ | ||||
| 		solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size."); | ||||
| 		solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size."); | ||||
| 		solAssert(m_type->storageBytes() + m_offset <= 32, ""); | ||||
| 
 | ||||
| 		solAssert(_sourceType == *m_type, "Different type, but might not be an error."); | ||||
| 
 | ||||
| 		return Whiskers("sstore(<slot>, <update>(sload(<slot>), <prepare>(<value>)))\n") | ||||
| 			("slot", m_slot) | ||||
| 			("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset)) | ||||
| 			("prepare", m_context.utils().prepareStoreFunction(*m_type)) | ||||
| 			("value", _value) | ||||
| 			.render(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		solAssert( | ||||
| 			_sourceType.category() == m_type->category(), | ||||
| 			"Wrong type conversation for assignment." | ||||
| 		); | ||||
| 		if (m_type->category() == Type::Category::Array) | ||||
| 			solUnimplementedAssert(false, ""); | ||||
| 		else if (m_type->category() == Type::Category::Struct) | ||||
| 			solUnimplementedAssert(false, ""); | ||||
| 		else | ||||
| 			solAssert(false, "Invalid non-value type for assignment."); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| string IRStorageItem::setToZero() const | ||||
| { | ||||
| 	solUnimplemented("Delete for storage location not yet implemented"); | ||||
| } | ||||
							
								
								
									
										100
									
								
								libsolidity/codegen/ir/IRLValue.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								libsolidity/codegen/ir/IRLValue.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Generator for code that handles LValues. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| #include <ostream> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| 
 | ||||
| class VariableDeclaration; | ||||
| class IRGenerationContext; | ||||
| class Type; | ||||
| 
 | ||||
| /**
 | ||||
|  * Abstract class used to retrieve, delete and store data in LValues. | ||||
|  */ | ||||
| class IRLValue | ||||
| { | ||||
| protected: | ||||
| 	IRLValue(IRGenerationContext& _context, Type const* _type = nullptr): | ||||
| 		m_context(_context), | ||||
| 		m_type(_type) | ||||
| 	{} | ||||
| 
 | ||||
| public: | ||||
| 	virtual ~IRLValue() = default; | ||||
| 	/// @returns an expression to retrieve the value of the lvalue.
 | ||||
| 	virtual std::string retrieveValue() const = 0; | ||||
| 	/// Returns code that stores the value of @a _value (should be an identifier)
 | ||||
| 	/// of type @a _type in the lvalue. Might perform type conversion.
 | ||||
| 	virtual std::string storeValue(std::string const& _value, Type const& _type) const = 0; | ||||
| 
 | ||||
| 	/// Returns code that will reset the stored value to zero
 | ||||
| 	virtual std::string setToZero() const = 0; | ||||
| protected: | ||||
| 	IRGenerationContext& m_context; | ||||
| 	Type const* m_type; | ||||
| }; | ||||
| 
 | ||||
| class IRLocalVariable: public IRLValue | ||||
| { | ||||
| public: | ||||
| 	IRLocalVariable( | ||||
| 		IRGenerationContext& _context, | ||||
| 		VariableDeclaration const& _varDecl | ||||
| 	); | ||||
| 	std::string retrieveValue() const override { return m_variableName; } | ||||
| 	std::string storeValue(std::string const& _value, Type const& _type) const override; | ||||
| 
 | ||||
| 	std::string setToZero() const override; | ||||
| private: | ||||
| 	std::string m_variableName; | ||||
| }; | ||||
| 
 | ||||
| class IRStorageItem: public IRLValue | ||||
| { | ||||
| public: | ||||
| 	IRStorageItem( | ||||
| 		IRGenerationContext& _context, | ||||
| 		VariableDeclaration const& _varDecl | ||||
| 	); | ||||
| 	IRStorageItem( | ||||
| 		IRGenerationContext& _context, | ||||
| 		std::string _slot, | ||||
| 		unsigned _offset, | ||||
| 		Type const& _type | ||||
| 	); | ||||
| 	std::string retrieveValue() const override; | ||||
| 	std::string storeValue(std::string const& _value, Type const& _type) const override; | ||||
| 
 | ||||
| 	std::string setToZero() const override; | ||||
| private: | ||||
| 	std::string m_slot; | ||||
| 	unsigned m_offset; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| } | ||||
| @ -21,42 +21,179 @@ | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| using namespace dev::solidity::smt; | ||||
| 
 | ||||
| EncodingContext::EncodingContext(SolverInterface& _solver): | ||||
| 	m_solver(_solver), | ||||
| 	m_thisAddress(make_unique<SymbolicAddressVariable>("this", m_solver)) | ||||
| { | ||||
| 	auto sort = make_shared<smt::ArraySort>( | ||||
| 		make_shared<smt::Sort>(smt::Kind::Int), | ||||
| 		make_shared<smt::Sort>(smt::Kind::Int) | ||||
| 	auto sort = make_shared<ArraySort>( | ||||
| 		make_shared<Sort>(Kind::Int), | ||||
| 		make_shared<Sort>(Kind::Int) | ||||
| 	); | ||||
| 	m_balances = make_unique<SymbolicVariable>(sort, "balances", m_solver); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::reset() | ||||
| { | ||||
| 	resetAllVariables(); | ||||
| 	m_expressions.clear(); | ||||
| 	m_globalContext.clear(); | ||||
| 	m_thisAddress->increaseIndex(); | ||||
| 	m_balances->increaseIndex(); | ||||
| } | ||||
| 
 | ||||
| smt::Expression EncodingContext::thisAddress() | ||||
| /// Variables.
 | ||||
| 
 | ||||
| shared_ptr<SymbolicVariable> EncodingContext::variable(solidity::VariableDeclaration const& _varDecl) | ||||
| { | ||||
| 	solAssert(knownVariable(_varDecl), ""); | ||||
| 	return m_variables[&_varDecl]; | ||||
| } | ||||
| 
 | ||||
| bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDecl) | ||||
| { | ||||
| 	solAssert(!knownVariable(_varDecl), ""); | ||||
| 	auto const& type = _varDecl.type(); | ||||
| 	auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), m_solver); | ||||
| 	m_variables.emplace(&_varDecl, result.second); | ||||
| 	return result.first; | ||||
| } | ||||
| 
 | ||||
| bool EncodingContext::knownVariable(solidity::VariableDeclaration const& _varDecl) | ||||
| { | ||||
| 	return m_variables.count(&_varDecl); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::resetVariable(solidity::VariableDeclaration const& _variable) | ||||
| { | ||||
| 	newValue(_variable); | ||||
| 	setUnknownValue(_variable); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::resetVariables(set<solidity::VariableDeclaration const*> const& _variables) | ||||
| { | ||||
| 	for (auto const* decl: _variables) | ||||
| 		resetVariable(*decl); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::resetVariables(function<bool(solidity::VariableDeclaration const&)> const& _filter) | ||||
| { | ||||
| 	for_each(begin(m_variables), end(m_variables), [&](auto _variable) | ||||
| 	{ | ||||
| 		if (_filter(*_variable.first)) | ||||
| 			this->resetVariable(*_variable.first); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::resetAllVariables() | ||||
| { | ||||
| 	resetVariables([&](solidity::VariableDeclaration const&) { return true; }); | ||||
| } | ||||
| 
 | ||||
| Expression EncodingContext::newValue(solidity::VariableDeclaration const& _decl) | ||||
| { | ||||
| 	solAssert(knownVariable(_decl), ""); | ||||
| 	return m_variables.at(&_decl)->increaseIndex(); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl) | ||||
| { | ||||
| 	solAssert(knownVariable(_decl), ""); | ||||
| 	setZeroValue(*m_variables.at(&_decl)); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::setZeroValue(SymbolicVariable& _variable) | ||||
| { | ||||
| 	setSymbolicZeroValue(_variable, m_solver); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl) | ||||
| { | ||||
| 	solAssert(knownVariable(_decl), ""); | ||||
| 	setUnknownValue(*m_variables.at(&_decl)); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::setUnknownValue(SymbolicVariable& _variable) | ||||
| { | ||||
| 	setSymbolicUnknownValue(_variable, m_solver); | ||||
| } | ||||
| 
 | ||||
| /// Expressions
 | ||||
| 
 | ||||
| shared_ptr<SymbolicVariable> EncodingContext::expression(solidity::Expression const& _e) | ||||
| { | ||||
| 	if (!knownExpression(_e)) | ||||
| 		createExpression(_e); | ||||
| 	return m_expressions.at(&_e); | ||||
| } | ||||
| 
 | ||||
| bool EncodingContext::createExpression(solidity::Expression const& _e, shared_ptr<SymbolicVariable> _symbVar) | ||||
| { | ||||
| 	solAssert(_e.annotation().type, ""); | ||||
| 	if (knownExpression(_e)) | ||||
| 	{ | ||||
| 		expression(_e)->increaseIndex(); | ||||
| 		return false; | ||||
| 	} | ||||
| 	else if (_symbVar) | ||||
| 	{ | ||||
| 		m_expressions.emplace(&_e, _symbVar); | ||||
| 		return false; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), m_solver); | ||||
| 		m_expressions.emplace(&_e, result.second); | ||||
| 		return result.first; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool EncodingContext::knownExpression(solidity::Expression const& _e) const | ||||
| { | ||||
| 	return m_expressions.count(&_e); | ||||
| } | ||||
| 
 | ||||
| /// Global variables and functions.
 | ||||
| 
 | ||||
| shared_ptr<SymbolicVariable> EncodingContext::globalSymbol(string const& _name) | ||||
| { | ||||
| 	solAssert(knownGlobalSymbol(_name), ""); | ||||
| 	return m_globalContext.at(_name); | ||||
| } | ||||
| 
 | ||||
| bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr) | ||||
| { | ||||
| 	solAssert(!knownGlobalSymbol(_name), ""); | ||||
| 	auto result = newSymbolicVariable(*_expr.annotation().type, _name, m_solver); | ||||
| 	m_globalContext.emplace(_name, result.second); | ||||
| 	setUnknownValue(*result.second); | ||||
| 	return result.first; | ||||
| } | ||||
| 
 | ||||
| bool EncodingContext::knownGlobalSymbol(string const& _var) const | ||||
| { | ||||
| 	return m_globalContext.count(_var); | ||||
| } | ||||
| 
 | ||||
| // Blockchain
 | ||||
| 
 | ||||
| Expression EncodingContext::thisAddress() | ||||
| { | ||||
| 	return m_thisAddress->currentValue(); | ||||
| } | ||||
| 
 | ||||
| smt::Expression EncodingContext::balance() | ||||
| Expression EncodingContext::balance() | ||||
| { | ||||
| 	return balance(m_thisAddress->currentValue()); | ||||
| } | ||||
| 
 | ||||
| smt::Expression EncodingContext::balance(smt::Expression _address) | ||||
| Expression EncodingContext::balance(Expression _address) | ||||
| { | ||||
| 	return smt::Expression::select(m_balances->currentValue(), move(_address)); | ||||
| 	return Expression::select(m_balances->currentValue(), move(_address)); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value) | ||||
| void EncodingContext::transfer(Expression _from, Expression _to, Expression _value) | ||||
| { | ||||
| 	unsigned indexBefore = m_balances->index(); | ||||
| 	addBalance(_from, 0 - _value); | ||||
| @ -65,7 +202,7 @@ void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt:: | ||||
| 	solAssert(indexAfter > indexBefore, ""); | ||||
| 	m_balances->increaseIndex(); | ||||
| 	/// Do not apply the transfer operation if _from == _to.
 | ||||
| 	auto newBalances = smt::Expression::ite( | ||||
| 	auto newBalances = Expression::ite( | ||||
| 		move(_from) == move(_to), | ||||
| 		m_balances->valueAtIndex(indexBefore), | ||||
| 		m_balances->valueAtIndex(indexAfter) | ||||
| @ -73,9 +210,9 @@ void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt:: | ||||
| 	m_solver.addAssertion(m_balances->currentValue() == newBalances); | ||||
| } | ||||
| 
 | ||||
| void EncodingContext::addBalance(smt::Expression _address, smt::Expression _value) | ||||
| void EncodingContext::addBalance(Expression _address, Expression _value) | ||||
| { | ||||
| 	auto newBalances = smt::Expression::store( | ||||
| 	auto newBalances = Expression::store( | ||||
| 		m_balances->currentValue(), | ||||
| 		_address, | ||||
| 		balance(_address) + move(_value) | ||||
|  | ||||
| @ -20,6 +20,9 @@ | ||||
| #include <libsolidity/formal/SolverInterface.h> | ||||
| #include <libsolidity/formal/SymbolicVariables.h> | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| #include <set> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| @ -38,22 +41,94 @@ public: | ||||
| 	/// Resets the entire context.
 | ||||
| 	void reset(); | ||||
| 
 | ||||
| 	/// Value of `this` address.
 | ||||
| 	smt::Expression thisAddress(); | ||||
| 	/// Methods related to variables.
 | ||||
| 	//@{
 | ||||
| 	/// @returns the symbolic representation of a program variable.
 | ||||
| 	std::shared_ptr<SymbolicVariable> variable(solidity::VariableDeclaration const& _varDecl); | ||||
| 	/// @returns all symbolic variables.
 | ||||
| 	std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> const& variables() const { return m_variables; } | ||||
| 
 | ||||
| 	/// Creates a symbolic variable and
 | ||||
| 	/// @returns true if a variable's type is not supported and is therefore abstract.
 | ||||
| 	bool createVariable(solidity::VariableDeclaration const& _varDecl); | ||||
| 	/// @returns true if variable was created.
 | ||||
| 	bool knownVariable(solidity::VariableDeclaration const& _varDecl); | ||||
| 
 | ||||
| 	/// Resets a specific variable.
 | ||||
| 	void resetVariable(solidity::VariableDeclaration const& _variable); | ||||
| 	/// Resets a set of variables.
 | ||||
| 	void resetVariables(std::set<solidity::VariableDeclaration const*> const& _variables); | ||||
| 	/// Resets variables according to a predicate.
 | ||||
| 	void resetVariables(std::function<bool(solidity::VariableDeclaration const&)> const& _filter); | ||||
| 	///Resets all variables.
 | ||||
| 	void resetAllVariables(); | ||||
| 
 | ||||
| 	/// Allocates a new index for the declaration, updates the current
 | ||||
| 	/// index to this value and returns the expression.
 | ||||
| 	Expression newValue(solidity::VariableDeclaration const& _decl); | ||||
| 	/// Sets the value of the declaration to zero.
 | ||||
| 	void setZeroValue(solidity::VariableDeclaration const& _decl); | ||||
| 	void setZeroValue(SymbolicVariable& _variable); | ||||
| 	/// Resets the variable to an unknown value (in its range).
 | ||||
| 	void setUnknownValue(solidity::VariableDeclaration const& decl); | ||||
| 	void setUnknownValue(SymbolicVariable& _variable); | ||||
| 	//@}
 | ||||
| 
 | ||||
| 	/// Methods related to expressions.
 | ||||
| 	////@{
 | ||||
| 	/// @returns the symbolic representation of an AST node expression.
 | ||||
| 	std::shared_ptr<SymbolicVariable> expression(solidity::Expression const& _e); | ||||
| 	/// @returns all symbolic expressions.
 | ||||
| 	std::unordered_map<solidity::Expression const*, std::shared_ptr<SymbolicVariable>> const& expressions() const { return m_expressions; } | ||||
| 
 | ||||
| 	/// Creates the expression (value can be arbitrary).
 | ||||
| 	/// @returns true if type is not supported.
 | ||||
| 	bool createExpression(solidity::Expression const& _e, std::shared_ptr<SymbolicVariable> _symbExpr = nullptr); | ||||
| 	/// Checks if expression was created.
 | ||||
| 	bool knownExpression(solidity::Expression const& _e) const; | ||||
| 	//@}
 | ||||
| 
 | ||||
| 	/// Methods related to global variables and functions.
 | ||||
| 	//@{
 | ||||
| 	/// Global variables and functions.
 | ||||
| 	std::shared_ptr<SymbolicVariable> globalSymbol(std::string const& _name); | ||||
| 	/// @returns all symbolic variables.
 | ||||
| 	std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> const& globalSymbols() const { return m_globalContext; } | ||||
| 	/// Defines a new global variable or function
 | ||||
| 	/// and @returns true if type was abstracted.
 | ||||
| 	bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr); | ||||
| 	/// Checks if special variable or function was seen.
 | ||||
| 	bool knownGlobalSymbol(std::string const& _var) const; | ||||
| 	//@}
 | ||||
| 
 | ||||
| 	/// Blockchain related methods.
 | ||||
| 	//@{
 | ||||
| 	/// Value of `this` address.
 | ||||
| 	Expression thisAddress(); | ||||
| 	/// @returns the symbolic balance of address `this`.
 | ||||
| 	smt::Expression balance(); | ||||
| 	Expression balance(); | ||||
| 	/// @returns the symbolic balance of an address.
 | ||||
| 	smt::Expression balance(smt::Expression _address); | ||||
| 	Expression balance(Expression _address); | ||||
| 	/// Transfer _value from _from to _to.
 | ||||
| 	void transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value); | ||||
| 	void transfer(Expression _from, Expression _to, Expression _value); | ||||
| 	//@}
 | ||||
| 
 | ||||
| private: | ||||
| 	/// Adds _value to _account's balance.
 | ||||
| 	void addBalance(smt::Expression _account, smt::Expression _value); | ||||
| 	void addBalance(Expression _account, Expression _value); | ||||
| 
 | ||||
| 	SolverInterface& m_solver; | ||||
| 
 | ||||
| 	/// Symbolic variables.
 | ||||
| 	std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables; | ||||
| 
 | ||||
| 	/// Symbolic expressions.
 | ||||
| 	std::unordered_map<solidity::Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions; | ||||
| 
 | ||||
| 	/// Symbolic representation of global symbols including
 | ||||
| 	/// variables and functions.
 | ||||
| 	std::unordered_map<std::string, std::shared_ptr<smt::SymbolicVariable>> m_globalContext; | ||||
| 
 | ||||
| 	/// Symbolic `this` address.
 | ||||
| 	std::unique_ptr<SymbolicAddressVariable> m_thisAddress; | ||||
| 
 | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -55,9 +55,11 @@ public: | ||||
| 	/// the constructor.
 | ||||
| 	std::vector<std::string> unhandledQueries() { return m_interface->unhandledQueries(); } | ||||
| 
 | ||||
| 	/// @return the FunctionDefinition of a called function if possible and should inline,
 | ||||
| 	/// @returns the FunctionDefinition of a called function if possible and should inline,
 | ||||
| 	/// otherwise nullptr.
 | ||||
| 	static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall); | ||||
| 	/// @returns the leftmost identifier in a multi-d IndexAccess.
 | ||||
| 	static Expression const* leftmostBase(IndexAccess const& _indexAccess); | ||||
| 
 | ||||
| private: | ||||
| 	// TODO: Check that we do not have concurrent reads and writes to a variable,
 | ||||
| @ -115,14 +117,18 @@ private: | ||||
| 	void inlineFunctionCall(FunctionCall const& _funCall); | ||||
| 	/// Creates an uninterpreted function call.
 | ||||
| 	void abstractFunctionCall(FunctionCall const& _funCall); | ||||
| 	/// Inlines if the function call is internal or external to `this`.
 | ||||
| 	/// Erases knowledge about state variables if external.
 | ||||
| 	void internalOrExternalFunctionCall(FunctionCall const& _funCall); | ||||
| 	void visitFunctionIdentifier(Identifier const& _identifier); | ||||
| 
 | ||||
| 	/// Encodes a modifier or function body according to the modifier
 | ||||
| 	/// visit depth.
 | ||||
| 	void visitFunctionOrModifier(); | ||||
| 
 | ||||
| 	/// Defines a new global variable or function.
 | ||||
| 	void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); | ||||
| 	void defineGlobalFunction(std::string const& _name, Expression const& _expr); | ||||
| 
 | ||||
| 	/// Handles the side effects of assignment
 | ||||
| 	/// to variable of some SMT array type
 | ||||
| 	/// while aliasing is not supported.
 | ||||
| @ -135,7 +141,18 @@ private: | ||||
| 	smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type); | ||||
| 
 | ||||
| 	void assignment(VariableDeclaration const& _variable, Expression const& _value, langutil::SourceLocation const& _location); | ||||
| 	/// Handles assignments to variables of different types.
 | ||||
| 	void assignment(VariableDeclaration const& _variable, smt::Expression const& _value, langutil::SourceLocation const& _location); | ||||
| 	/// Handles assignments between generic expressions.
 | ||||
| 	/// Will also be used for assignments of tuple components.
 | ||||
| 	void assignment( | ||||
| 		Expression const& _left, | ||||
| 		std::vector<smt::Expression> const& _right, | ||||
| 		TypePointer const& _type, | ||||
| 		langutil::SourceLocation const& _location | ||||
| 	); | ||||
| 	/// Computes the right hand side of a compound assignment.
 | ||||
| 	smt::Expression compoundAssignment(Assignment const& _assignment); | ||||
| 
 | ||||
| 	/// Maps a variable to an SSA index.
 | ||||
| 	using VariableIndices = std::unordered_map<VariableDeclaration const*, int>; | ||||
| @ -162,6 +179,8 @@ private: | ||||
| 		std::string const& _description | ||||
| 	); | ||||
| 
 | ||||
| 	using CallStackEntry = std::pair<CallableDeclaration const*, ASTNode const*>; | ||||
| 
 | ||||
| 	struct OverflowTarget | ||||
| 	{ | ||||
| 		enum class Type { Underflow, Overflow, All } type; | ||||
| @ -169,9 +188,9 @@ private: | ||||
| 		smt::Expression value; | ||||
| 		smt::Expression path; | ||||
| 		langutil::SourceLocation const& location; | ||||
| 		std::vector<ASTNode const*> callStack; | ||||
| 		std::vector<CallStackEntry> callStack; | ||||
| 
 | ||||
| 		OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<ASTNode const*> _callStack): | ||||
| 		OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<CallStackEntry> _callStack): | ||||
| 			type(_type), | ||||
| 			intType(_intType), | ||||
| 			value(_value), | ||||
| @ -198,11 +217,8 @@ private: | ||||
| 
 | ||||
| 	void initializeLocalVariables(FunctionDefinition const& _function); | ||||
| 	void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smt::Expression> const& _callArgs); | ||||
| 	void resetVariable(VariableDeclaration const& _variable); | ||||
| 	void resetStateVariables(); | ||||
| 	void resetStorageReferences(); | ||||
| 	void resetVariables(std::set<VariableDeclaration const*> const& _variables); | ||||
| 	void resetVariables(std::function<bool(VariableDeclaration const&)> const& _filter); | ||||
| 	/// @returns the type without storage pointer information if it has it.
 | ||||
| 	TypePointer typeWithoutPointer(TypePointer const& _type); | ||||
| 
 | ||||
| @ -211,41 +227,21 @@ private: | ||||
| 	/// using the branch condition as guard.
 | ||||
| 	void mergeVariables(std::set<VariableDeclaration const*> const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse); | ||||
| 	/// Tries to create an uninitialized variable and returns true on success.
 | ||||
| 	/// This fails if the type is not supported.
 | ||||
| 	bool createVariable(VariableDeclaration const& _varDecl); | ||||
| 
 | ||||
| 	/// @returns true if _delc is a variable that is known at the current point, i.e.
 | ||||
| 	/// has a valid index
 | ||||
| 	bool knownVariable(VariableDeclaration const& _decl); | ||||
| 	/// @returns an expression denoting the value of the variable declared in @a _decl
 | ||||
| 	/// at the current point.
 | ||||
| 	smt::Expression currentValue(VariableDeclaration const& _decl); | ||||
| 	/// @returns an expression denoting the value of the variable declared in @a _decl
 | ||||
| 	/// at the given index. Does not ensure that this index exists.
 | ||||
| 	smt::Expression valueAtIndex(VariableDeclaration const& _decl, int _index); | ||||
| 	/// Allocates a new index for the declaration, updates the current
 | ||||
| 	/// index to this value and returns the expression.
 | ||||
| 	smt::Expression newValue(VariableDeclaration const& _decl); | ||||
| 
 | ||||
| 	/// Sets the value of the declaration to zero.
 | ||||
| 	void setZeroValue(VariableDeclaration const& _decl); | ||||
| 	void setZeroValue(SymbolicVariable& _variable); | ||||
| 	/// Resets the variable to an unknown value (in its range).
 | ||||
| 	void setUnknownValue(VariableDeclaration const& decl); | ||||
| 	void setUnknownValue(SymbolicVariable& _variable); | ||||
| 
 | ||||
| 	/// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
 | ||||
| 	smt::Expression expr(Expression const& _e); | ||||
| 	/// Creates the expression (value can be arbitrary)
 | ||||
| 	void createExpr(Expression const& _e); | ||||
| 	/// Checks if expression was created
 | ||||
| 	bool knownExpr(Expression const& _e) const; | ||||
| 	/// Creates the expression and sets its value.
 | ||||
| 	void defineExpr(Expression const& _e, smt::Expression _value); | ||||
| 
 | ||||
| 	/// Checks if special variable or function was seen.
 | ||||
| 	bool knownGlobalSymbol(std::string const& _var) const; | ||||
| 
 | ||||
| 	/// Adds a new path condition
 | ||||
| 	void pushPathCondition(smt::Expression const& _e); | ||||
| 	/// Remove the last path condition
 | ||||
| @ -255,17 +251,14 @@ private: | ||||
| 	/// Returns the current callstack. Used for models.
 | ||||
| 	langutil::SecondarySourceLocation currentCallStack(); | ||||
| 	/// Copies and pops the last called node.
 | ||||
| 	ASTNode const* popCallStack(); | ||||
| 	/// Adds @param _node to the callstack.
 | ||||
| 	void pushCallStack(ASTNode const* _node); | ||||
| 	CallStackEntry popCallStack(); | ||||
| 	/// Adds (_definition, _node) to the callstack.
 | ||||
| 	void pushCallStack(CallStackEntry _entry); | ||||
| 	/// Conjoin the current path conditions with the given parameter and add to the solver
 | ||||
| 	void addPathConjoinedExpression(smt::Expression const& _e); | ||||
| 	/// Add to the solver: the given expression implied by the current path conditions
 | ||||
| 	void addPathImpliedExpression(smt::Expression const& _e); | ||||
| 
 | ||||
| 	/// Removes local variables from the context.
 | ||||
| 	void removeLocalVariables(); | ||||
| 
 | ||||
| 	/// Copy the SSA indices of m_variables.
 | ||||
| 	VariableIndices copyVariableIndices(); | ||||
| 	/// Resets the variable indices.
 | ||||
| @ -274,18 +267,16 @@ private: | ||||
| 	/// @returns variables that are touched in _node's subtree.
 | ||||
| 	std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node); | ||||
| 
 | ||||
| 	std::shared_ptr<smt::SolverInterface> m_interface; | ||||
| 	VariableUsage m_variableUsage; | ||||
| 	/// @returns the VariableDeclaration referenced by an Identifier or nullptr.
 | ||||
| 	VariableDeclaration const* identifierToVariable(Expression const& _expr); | ||||
| 
 | ||||
| 	std::unique_ptr<smt::SolverInterface> m_interface; | ||||
| 	smt::VariableUsage m_variableUsage; | ||||
| 	bool m_loopExecutionHappened = false; | ||||
| 	bool m_arrayAssignmentHappened = false; | ||||
| 	bool m_externalFunctionCallHappened = false; | ||||
| 	// True if the "No SMT solver available" warning was already created.
 | ||||
| 	bool m_noSolverWarning = false; | ||||
| 	/// An Expression may have multiple smt::Expression due to
 | ||||
| 	/// repeated calls to the same function.
 | ||||
| 	std::unordered_map<Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions; | ||||
| 	std::unordered_map<VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables; | ||||
| 	std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> m_globalContext; | ||||
| 
 | ||||
| 	/// Stores the instances of an Uninterpreted Function applied to arguments.
 | ||||
| 	/// These may be direct application of UFs or Array index access.
 | ||||
| @ -301,10 +292,8 @@ private: | ||||
| 	langutil::ErrorList m_smtErrors; | ||||
| 	std::shared_ptr<langutil::Scanner> m_scanner; | ||||
| 
 | ||||
| 	/// Stores the current path of function calls.
 | ||||
| 	std::vector<FunctionDefinition const*> m_functionPath; | ||||
| 	/// Stores the current call/invocation path.
 | ||||
| 	std::vector<ASTNode const*> m_callStack; | ||||
| 	/// Stores the current function/modifier call/invocation path.
 | ||||
| 	std::vector<CallStackEntry> m_callStack; | ||||
| 	/// Returns true if the current function was not visited by
 | ||||
| 	/// a function call.
 | ||||
| 	bool isRootFunction(); | ||||
|  | ||||
| @ -32,42 +32,42 @@ using namespace dev::solidity::smt; | ||||
| 
 | ||||
| SMTPortfolio::SMTPortfolio(map<h256, string> const& _smtlib2Responses) | ||||
| { | ||||
| 	m_solvers.emplace_back(make_shared<smt::SMTLib2Interface>(_smtlib2Responses)); | ||||
| 	m_solvers.emplace_back(make_unique<smt::SMTLib2Interface>(_smtlib2Responses)); | ||||
| #ifdef HAVE_Z3 | ||||
| 	m_solvers.emplace_back(make_shared<smt::Z3Interface>()); | ||||
| 	m_solvers.emplace_back(make_unique<smt::Z3Interface>()); | ||||
| #endif | ||||
| #ifdef HAVE_CVC4 | ||||
| 	m_solvers.emplace_back(make_shared<smt::CVC4Interface>()); | ||||
| 	m_solvers.emplace_back(make_unique<smt::CVC4Interface>()); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void SMTPortfolio::reset() | ||||
| { | ||||
| 	for (auto s : m_solvers) | ||||
| 	for (auto const& s: m_solvers) | ||||
| 		s->reset(); | ||||
| } | ||||
| 
 | ||||
| void SMTPortfolio::push() | ||||
| { | ||||
| 	for (auto s : m_solvers) | ||||
| 	for (auto const& s: m_solvers) | ||||
| 		s->push(); | ||||
| } | ||||
| 
 | ||||
| void SMTPortfolio::pop() | ||||
| { | ||||
| 	for (auto s : m_solvers) | ||||
| 	for (auto const& s: m_solvers) | ||||
| 		s->pop(); | ||||
| } | ||||
| 
 | ||||
| void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort) | ||||
| { | ||||
| 	for (auto s : m_solvers) | ||||
| 	for (auto const& s: m_solvers) | ||||
| 		s->declareVariable(_name, _sort); | ||||
| } | ||||
| 
 | ||||
| void SMTPortfolio::addAssertion(Expression const& _expr) | ||||
| { | ||||
| 	for (auto s : m_solvers) | ||||
| 	for (auto const& s: m_solvers) | ||||
| 		s->addAssertion(_expr); | ||||
| } | ||||
| 
 | ||||
| @ -105,7 +105,7 @@ pair<CheckResult, vector<string>> SMTPortfolio::check(vector<Expression> const& | ||||
| { | ||||
| 	CheckResult lastResult = CheckResult::ERROR; | ||||
| 	vector<string> finalValues; | ||||
| 	for (auto s : m_solvers) | ||||
| 	for (auto const& s: m_solvers) | ||||
| 	{ | ||||
| 		CheckResult result; | ||||
| 		vector<string> values; | ||||
| @ -134,8 +134,8 @@ vector<string> SMTPortfolio::unhandledQueries() | ||||
| 	// This code assumes that the constructor guarantees that
 | ||||
| 	// SmtLib2Interface is in position 0.
 | ||||
| 	solAssert(!m_solvers.empty(), ""); | ||||
| 	solAssert(dynamic_cast<smt::SMTLib2Interface*>(m_solvers.at(0).get()), ""); | ||||
| 	return m_solvers.at(0)->unhandledQueries(); | ||||
| 	solAssert(dynamic_cast<smt::SMTLib2Interface*>(m_solvers.front().get()), ""); | ||||
| 	return m_solvers.front()->unhandledQueries(); | ||||
| } | ||||
| 
 | ||||
| bool SMTPortfolio::solverAnswered(CheckResult result) | ||||
|  | ||||
| @ -59,7 +59,7 @@ public: | ||||
| private: | ||||
| 	static bool solverAnswered(CheckResult result); | ||||
| 
 | ||||
| 	std::vector<std::shared_ptr<smt::SolverInterface>> m_solvers; | ||||
| 	std::vector<std::unique_ptr<smt::SolverInterface>> m_solvers; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -18,7 +18,7 @@ | ||||
| #include <libsolidity/formal/SSAVariable.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev::solidity; | ||||
| using namespace dev::solidity::smt; | ||||
| 
 | ||||
| SSAVariable::SSAVariable() | ||||
| { | ||||
| @ -28,6 +28,5 @@ SSAVariable::SSAVariable() | ||||
| void SSAVariable::resetIndex() | ||||
| { | ||||
| 	m_currentIndex = 0; | ||||
| 	m_nextFreeIndex.reset (new unsigned); | ||||
| 	*m_nextFreeIndex = 1; | ||||
| 	m_nextFreeIndex = make_unique<unsigned>(1); | ||||
| } | ||||
|  | ||||
| @ -23,6 +23,8 @@ namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace smt | ||||
| { | ||||
| 
 | ||||
| /**
 | ||||
|  * This class represents the SSA representation of a program variable. | ||||
| @ -44,10 +46,9 @@ public: | ||||
| 
 | ||||
| private: | ||||
| 	unsigned m_currentIndex; | ||||
| 	/// The next free index is a shared pointer because we want
 | ||||
| 	/// the copy and the copied to share it.
 | ||||
| 	std::shared_ptr<unsigned> m_nextFreeIndex; | ||||
| 	std::unique_ptr<unsigned> m_nextFreeIndex; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -22,105 +22,112 @@ | ||||
| #include <memory> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev::solidity; | ||||
| 
 | ||||
| smt::SortPointer dev::solidity::smtSort(Type const& _type) | ||||
| namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace smt | ||||
| { | ||||
| 
 | ||||
| SortPointer smtSort(solidity::Type const& _type) | ||||
| { | ||||
| 	switch (smtKind(_type.category())) | ||||
| 	{ | ||||
| 	case smt::Kind::Int: | ||||
| 		return make_shared<smt::Sort>(smt::Kind::Int); | ||||
| 	case smt::Kind::Bool: | ||||
| 		return make_shared<smt::Sort>(smt::Kind::Bool); | ||||
| 	case smt::Kind::Function: | ||||
| 	case Kind::Int: | ||||
| 		return make_shared<Sort>(Kind::Int); | ||||
| 	case Kind::Bool: | ||||
| 		return make_shared<Sort>(Kind::Bool); | ||||
| 	case Kind::Function: | ||||
| 	{ | ||||
| 		auto fType = dynamic_cast<FunctionType const*>(&_type); | ||||
| 		auto fType = dynamic_cast<solidity::FunctionType const*>(&_type); | ||||
| 		solAssert(fType, ""); | ||||
| 		vector<smt::SortPointer> parameterSorts = smtSort(fType->parameterTypes()); | ||||
| 		vector<SortPointer> parameterSorts = smtSort(fType->parameterTypes()); | ||||
| 		auto returnTypes = fType->returnParameterTypes(); | ||||
| 		smt::SortPointer returnSort; | ||||
| 		SortPointer returnSort; | ||||
| 		// TODO change this when we support tuples.
 | ||||
| 		if (returnTypes.size() == 0) | ||||
| 			// We cannot declare functions without a return sort, so we use the smallest.
 | ||||
| 			returnSort = make_shared<smt::Sort>(smt::Kind::Bool); | ||||
| 			returnSort = make_shared<Sort>(Kind::Bool); | ||||
| 		else if (returnTypes.size() > 1) | ||||
| 			// Abstract sort.
 | ||||
| 			returnSort = make_shared<smt::Sort>(smt::Kind::Int); | ||||
| 			returnSort = make_shared<Sort>(Kind::Int); | ||||
| 		else | ||||
| 			returnSort = smtSort(*returnTypes.at(0)); | ||||
| 		return make_shared<smt::FunctionSort>(parameterSorts, returnSort); | ||||
| 			returnSort = smtSort(*returnTypes.front()); | ||||
| 		return make_shared<FunctionSort>(parameterSorts, returnSort); | ||||
| 	} | ||||
| 	case smt::Kind::Array: | ||||
| 	case Kind::Array: | ||||
| 	{ | ||||
| 		if (isMapping(_type.category())) | ||||
| 		{ | ||||
| 			auto mapType = dynamic_cast<MappingType const*>(&_type); | ||||
| 			auto mapType = dynamic_cast<solidity::MappingType const*>(&_type); | ||||
| 			solAssert(mapType, ""); | ||||
| 			return make_shared<smt::ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType())); | ||||
| 			return make_shared<ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType())); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			solAssert(isArray(_type.category()), ""); | ||||
| 			auto arrayType = dynamic_cast<ArrayType const*>(&_type); | ||||
| 			auto arrayType = dynamic_cast<solidity::ArrayType const*>(&_type); | ||||
| 			solAssert(arrayType, ""); | ||||
| 			return make_shared<smt::ArraySort>(make_shared<smt::Sort>(smt::Kind::Int), smtSort(*arrayType->baseType())); | ||||
| 			return make_shared<ArraySort>(make_shared<Sort>(Kind::Int), smtSort(*arrayType->baseType())); | ||||
| 		} | ||||
| 	} | ||||
| 	default: | ||||
| 		// Abstract case.
 | ||||
| 		return make_shared<smt::Sort>(smt::Kind::Int); | ||||
| 		return make_shared<Sort>(Kind::Int); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| vector<smt::SortPointer> dev::solidity::smtSort(vector<TypePointer> const& _types) | ||||
| vector<SortPointer> smtSort(vector<solidity::TypePointer> const& _types) | ||||
| { | ||||
| 	vector<smt::SortPointer> sorts; | ||||
| 	vector<SortPointer> sorts; | ||||
| 	for (auto const& type: _types) | ||||
| 		sorts.push_back(smtSort(*type)); | ||||
| 	return sorts; | ||||
| } | ||||
| 
 | ||||
| smt::Kind dev::solidity::smtKind(Type::Category _category) | ||||
| Kind smtKind(solidity::Type::Category _category) | ||||
| { | ||||
| 	if (isNumber(_category)) | ||||
| 		return smt::Kind::Int; | ||||
| 		return Kind::Int; | ||||
| 	else if (isBool(_category)) | ||||
| 		return smt::Kind::Bool; | ||||
| 		return Kind::Bool; | ||||
| 	else if (isFunction(_category)) | ||||
| 		return smt::Kind::Function; | ||||
| 		return Kind::Function; | ||||
| 	else if (isMapping(_category) || isArray(_category)) | ||||
| 		return smt::Kind::Array; | ||||
| 		return Kind::Array; | ||||
| 	// Abstract case.
 | ||||
| 	return smt::Kind::Int; | ||||
| 	return Kind::Int; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isSupportedType(Type::Category _category) | ||||
| bool isSupportedType(solidity::Type::Category _category) | ||||
| { | ||||
| 	return isNumber(_category) || | ||||
| 		isBool(_category) || | ||||
| 		isMapping(_category) || | ||||
| 		isArray(_category); | ||||
| 		isArray(_category) || | ||||
| 		isTuple(_category); | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isSupportedTypeDeclaration(Type::Category _category) | ||||
| bool isSupportedTypeDeclaration(solidity::Type::Category _category) | ||||
| { | ||||
| 	return isSupportedType(_category) || | ||||
| 		isFunction(_category); | ||||
| } | ||||
| 
 | ||||
| pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( | ||||
| 	Type const& _type, | ||||
| pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable( | ||||
| 	solidity::Type const& _type, | ||||
| 	std::string const& _uniqueName, | ||||
| 	smt::SolverInterface& _solver | ||||
| 	SolverInterface& _solver | ||||
| ) | ||||
| { | ||||
| 	bool abstract = false; | ||||
| 	shared_ptr<SymbolicVariable> var; | ||||
| 	TypePointer type = &_type; | ||||
| 	solidity::TypePointer type = &_type; | ||||
| 	if (!isSupportedTypeDeclaration(_type)) | ||||
| 	{ | ||||
| 		abstract = true; | ||||
| 		var = make_shared<SymbolicIntVariable>(TypeProvider::uint256(), _uniqueName, _solver); | ||||
| 		var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver); | ||||
| 	} | ||||
| 	else if (isBool(_type.category())) | ||||
| 		var = make_shared<SymbolicBoolVariable>(type, _uniqueName, _solver); | ||||
| @ -130,7 +137,7 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( | ||||
| 		var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver); | ||||
| 	else if (isFixedBytes(_type.category())) | ||||
| 	{ | ||||
| 		auto fixedBytesType = dynamic_cast<FixedBytesType const*>(type); | ||||
| 		auto fixedBytesType = dynamic_cast<solidity::FixedBytesType const*>(type); | ||||
| 		solAssert(fixedBytesType, ""); | ||||
| 		var = make_shared<SymbolicFixedBytesVariable>(fixedBytesType->numBytes(), _uniqueName, _solver); | ||||
| 	} | ||||
| @ -140,10 +147,10 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( | ||||
| 		var = make_shared<SymbolicEnumVariable>(type, _uniqueName, _solver); | ||||
| 	else if (isRational(_type.category())) | ||||
| 	{ | ||||
| 		auto rational = dynamic_cast<RationalNumberType const*>(&_type); | ||||
| 		auto rational = dynamic_cast<solidity::RationalNumberType const*>(&_type); | ||||
| 		solAssert(rational, ""); | ||||
| 		if (rational->isFractional()) | ||||
| 			var = make_shared<SymbolicIntVariable>(TypeProvider::uint256(), _uniqueName, _solver); | ||||
| 			var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver); | ||||
| 		else | ||||
| 			var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver); | ||||
| 	} | ||||
| @ -151,52 +158,54 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( | ||||
| 		var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _solver); | ||||
| 	else if (isArray(_type.category())) | ||||
| 		var = make_shared<SymbolicArrayVariable>(type, _uniqueName, _solver); | ||||
| 	else if (isTuple(_type.category())) | ||||
| 		var = make_shared<SymbolicTupleVariable>(type, _uniqueName, _solver); | ||||
| 	else | ||||
| 		solAssert(false, ""); | ||||
| 	return make_pair(abstract, var); | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isSupportedType(Type const& _type) | ||||
| bool isSupportedType(solidity::Type const& _type) | ||||
| { | ||||
| 	return isSupportedType(_type.category()); | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isSupportedTypeDeclaration(Type const& _type) | ||||
| bool isSupportedTypeDeclaration(solidity::Type const& _type) | ||||
| { | ||||
| 	return isSupportedTypeDeclaration(_type.category()); | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isInteger(Type::Category _category) | ||||
| bool isInteger(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Integer; | ||||
| 	return _category == solidity::Type::Category::Integer; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isRational(Type::Category _category) | ||||
| bool isRational(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::RationalNumber; | ||||
| 	return _category == solidity::Type::Category::RationalNumber; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isFixedBytes(Type::Category _category) | ||||
| bool isFixedBytes(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::FixedBytes; | ||||
| 	return _category == solidity::Type::Category::FixedBytes; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isAddress(Type::Category _category) | ||||
| bool isAddress(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Address; | ||||
| 	return _category == solidity::Type::Category::Address; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isContract(Type::Category _category) | ||||
| bool isContract(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Contract; | ||||
| 	return _category == solidity::Type::Category::Contract; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isEnum(Type::Category _category) | ||||
| bool isEnum(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Enum; | ||||
| 	return _category == solidity::Type::Category::Enum; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isNumber(Type::Category _category) | ||||
| bool isNumber(solidity::Type::Category _category) | ||||
| { | ||||
| 	return isInteger(_category) || | ||||
| 		isRational(_category) || | ||||
| @ -206,70 +215,79 @@ bool dev::solidity::isNumber(Type::Category _category) | ||||
| 		isEnum(_category); | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isBool(Type::Category _category) | ||||
| bool isBool(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Bool; | ||||
| 	return _category == solidity::Type::Category::Bool; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isFunction(Type::Category _category) | ||||
| bool isFunction(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Function; | ||||
| 	return _category == solidity::Type::Category::Function; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isMapping(Type::Category _category) | ||||
| bool isMapping(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Mapping; | ||||
| 	return _category == solidity::Type::Category::Mapping; | ||||
| } | ||||
| 
 | ||||
| bool dev::solidity::isArray(Type::Category _category) | ||||
| bool isArray(solidity::Type::Category _category) | ||||
| { | ||||
| 	return _category == Type::Category::Array; | ||||
| 	return _category == solidity::Type::Category::Array; | ||||
| } | ||||
| 
 | ||||
| smt::Expression dev::solidity::minValue(IntegerType const& _type) | ||||
| bool isTuple(solidity::Type::Category _category) | ||||
| { | ||||
| 	return smt::Expression(_type.minValue()); | ||||
| 	return _category == solidity::Type::Category::Tuple; | ||||
| } | ||||
| 
 | ||||
| smt::Expression dev::solidity::maxValue(IntegerType const& _type) | ||||
| Expression minValue(solidity::IntegerType const& _type) | ||||
| { | ||||
| 	return smt::Expression(_type.maxValue()); | ||||
| 	return Expression(_type.minValue()); | ||||
| } | ||||
| 
 | ||||
| void dev::solidity::smt::setSymbolicZeroValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface) | ||||
| Expression maxValue(solidity::IntegerType const& _type) | ||||
| { | ||||
| 	return Expression(_type.maxValue()); | ||||
| } | ||||
| 
 | ||||
| void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface) | ||||
| { | ||||
| 	setSymbolicZeroValue(_variable.currentValue(), _variable.type(), _interface); | ||||
| } | ||||
| 
 | ||||
| void dev::solidity::smt::setSymbolicZeroValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface) | ||||
| void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface) | ||||
| { | ||||
| 	solAssert(_type, ""); | ||||
| 	if (isInteger(_type->category())) | ||||
| 		_interface.addAssertion(_expr == 0); | ||||
| 	else if (isBool(_type->category())) | ||||
| 		_interface.addAssertion(_expr == smt::Expression(false)); | ||||
| 		_interface.addAssertion(_expr == Expression(false)); | ||||
| } | ||||
| 
 | ||||
| void dev::solidity::smt::setSymbolicUnknownValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface) | ||||
| void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface) | ||||
| { | ||||
| 	setSymbolicUnknownValue(_variable.currentValue(), _variable.type(), _interface); | ||||
| } | ||||
| 
 | ||||
| void dev::solidity::smt::setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface) | ||||
| void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface) | ||||
| { | ||||
| 	solAssert(_type, ""); | ||||
| 	if (isEnum(_type->category())) | ||||
| 	{ | ||||
| 		auto enumType = dynamic_cast<EnumType const*>(_type); | ||||
| 		auto enumType = dynamic_cast<solidity::EnumType const*>(_type); | ||||
| 		solAssert(enumType, ""); | ||||
| 		_interface.addAssertion(_expr >= 0); | ||||
| 		_interface.addAssertion(_expr < enumType->numberOfMembers()); | ||||
| 	} | ||||
| 	else if (isInteger(_type->category())) | ||||
| 	{ | ||||
| 		auto intType = dynamic_cast<IntegerType const*>(_type); | ||||
| 		auto intType = dynamic_cast<solidity::IntegerType const*>(_type); | ||||
| 		solAssert(intType, ""); | ||||
| 		_interface.addAssertion(_expr >= minValue(*intType)); | ||||
| 		_interface.addAssertion(_expr <= maxValue(*intType)); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -26,49 +26,48 @@ namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace smt | ||||
| { | ||||
| 
 | ||||
| /// Returns the SMT sort that models the Solidity type _type.
 | ||||
| smt::SortPointer smtSort(Type const& _type); | ||||
| std::vector<smt::SortPointer> smtSort(std::vector<TypePointer> const& _types); | ||||
| SortPointer smtSort(solidity::Type const& _type); | ||||
| std::vector<SortPointer> smtSort(std::vector<solidity::TypePointer> const& _types); | ||||
| /// Returns the SMT kind that models the Solidity type type category _category.
 | ||||
| smt::Kind smtKind(Type::Category _category); | ||||
| Kind smtKind(solidity::Type::Category _category); | ||||
| 
 | ||||
| /// Returns true if type is fully supported (declaration and operations).
 | ||||
| bool isSupportedType(Type::Category _category); | ||||
| bool isSupportedType(Type const& _type); | ||||
| bool isSupportedType(solidity::Type::Category _category); | ||||
| bool isSupportedType(solidity::Type const& _type); | ||||
| /// Returns true if type is partially supported (declaration).
 | ||||
| bool isSupportedTypeDeclaration(Type::Category _category); | ||||
| bool isSupportedTypeDeclaration(Type const& _type); | ||||
| bool isSupportedTypeDeclaration(solidity::Type::Category _category); | ||||
| bool isSupportedTypeDeclaration(solidity::Type const& _type); | ||||
| 
 | ||||
| bool isInteger(Type::Category _category); | ||||
| bool isRational(Type::Category _category); | ||||
| bool isFixedBytes(Type::Category _category); | ||||
| bool isAddress(Type::Category _category); | ||||
| bool isContract(Type::Category _category); | ||||
| bool isEnum(Type::Category _category); | ||||
| bool isNumber(Type::Category _category); | ||||
| bool isBool(Type::Category _category); | ||||
| bool isFunction(Type::Category _category); | ||||
| bool isMapping(Type::Category _category); | ||||
| bool isArray(Type::Category _category); | ||||
| bool isInteger(solidity::Type::Category _category); | ||||
| bool isRational(solidity::Type::Category _category); | ||||
| bool isFixedBytes(solidity::Type::Category _category); | ||||
| bool isAddress(solidity::Type::Category _category); | ||||
| bool isContract(solidity::Type::Category _category); | ||||
| bool isEnum(solidity::Type::Category _category); | ||||
| bool isNumber(solidity::Type::Category _category); | ||||
| bool isBool(solidity::Type::Category _category); | ||||
| bool isFunction(solidity::Type::Category _category); | ||||
| bool isMapping(solidity::Type::Category _category); | ||||
| bool isArray(solidity::Type::Category _category); | ||||
| bool isTuple(solidity::Type::Category _category); | ||||
| 
 | ||||
| /// Returns a new symbolic variable, according to _type.
 | ||||
| /// Also returns whether the type is abstract or not,
 | ||||
| /// which is true for unsupported types.
 | ||||
| std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(Type const& _type, std::string const& _uniqueName, smt::SolverInterface& _solver); | ||||
| std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(solidity::Type const& _type, std::string const& _uniqueName, SolverInterface& _solver); | ||||
| 
 | ||||
| smt::Expression minValue(IntegerType const& _type); | ||||
| smt::Expression maxValue(IntegerType const& _type); | ||||
| Expression minValue(solidity::IntegerType const& _type); | ||||
| Expression maxValue(solidity::IntegerType const& _type); | ||||
| 
 | ||||
| namespace smt | ||||
| { | ||||
| 
 | ||||
| void setSymbolicZeroValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface); | ||||
| void setSymbolicZeroValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface); | ||||
| void setSymbolicUnknownValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface); | ||||
| void setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface); | ||||
| 
 | ||||
| } | ||||
| void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface); | ||||
| void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface); | ||||
| void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface); | ||||
| void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface); | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -23,17 +23,17 @@ | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| using namespace dev::solidity::smt; | ||||
| 
 | ||||
| SymbolicVariable::SymbolicVariable( | ||||
| 	TypePointer _type, | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	m_type(move(_type)), | ||||
| 	m_uniqueName(move(_uniqueName)), | ||||
| 	m_interface(_interface), | ||||
| 	m_ssa(make_shared<SSAVariable>()) | ||||
| 	m_ssa(make_unique<SSAVariable>()) | ||||
| { | ||||
| 	solAssert(m_type, ""); | ||||
| 	m_sort = smtSort(*m_type); | ||||
| @ -41,19 +41,19 @@ SymbolicVariable::SymbolicVariable( | ||||
| } | ||||
| 
 | ||||
| SymbolicVariable::SymbolicVariable( | ||||
| 	smt::SortPointer _sort, | ||||
| 	SortPointer _sort, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	m_sort(move(_sort)), | ||||
| 	m_uniqueName(move(_uniqueName)), | ||||
| 	m_interface(_interface), | ||||
| 	m_ssa(make_shared<SSAVariable>()) | ||||
| 	m_ssa(make_unique<SSAVariable>()) | ||||
| { | ||||
| 	solAssert(m_sort, ""); | ||||
| } | ||||
| 
 | ||||
| smt::Expression SymbolicVariable::currentValue() const | ||||
| Expression SymbolicVariable::currentValue() const | ||||
| { | ||||
| 	return valueAtIndex(m_ssa->index()); | ||||
| } | ||||
| @ -63,7 +63,7 @@ string SymbolicVariable::currentName() const | ||||
| 	return uniqueSymbol(m_ssa->index()); | ||||
| } | ||||
| 
 | ||||
| smt::Expression SymbolicVariable::valueAtIndex(int _index) const | ||||
| Expression SymbolicVariable::valueAtIndex(int _index) const | ||||
| { | ||||
| 	return m_interface.newVariable(uniqueSymbol(_index), m_sort); | ||||
| } | ||||
| @ -73,26 +73,26 @@ string SymbolicVariable::uniqueSymbol(unsigned _index) const | ||||
| 	return m_uniqueName + "_" + to_string(_index); | ||||
| } | ||||
| 
 | ||||
| smt::Expression SymbolicVariable::increaseIndex() | ||||
| Expression SymbolicVariable::increaseIndex() | ||||
| { | ||||
| 	++(*m_ssa); | ||||
| 	return currentValue(); | ||||
| } | ||||
| 
 | ||||
| SymbolicBoolVariable::SymbolicBoolVariable( | ||||
| 	TypePointer _type, | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicVariable(move(_type), move(_uniqueName), _interface) | ||||
| { | ||||
| 	solAssert(m_type->category() == Type::Category::Bool, ""); | ||||
| 	solAssert(m_type->category() == solidity::Type::Category::Bool, ""); | ||||
| } | ||||
| 
 | ||||
| SymbolicIntVariable::SymbolicIntVariable( | ||||
| 	TypePointer _type, | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicVariable(move(_type), move(_uniqueName), _interface) | ||||
| { | ||||
| @ -101,7 +101,7 @@ SymbolicIntVariable::SymbolicIntVariable( | ||||
| 
 | ||||
| SymbolicAddressVariable::SymbolicAddressVariable( | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicIntVariable(TypeProvider::uint(160), move(_uniqueName), _interface) | ||||
| { | ||||
| @ -110,21 +110,21 @@ SymbolicAddressVariable::SymbolicAddressVariable( | ||||
| SymbolicFixedBytesVariable::SymbolicFixedBytesVariable( | ||||
| 	unsigned _numBytes, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), move(_uniqueName), _interface) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| SymbolicFunctionVariable::SymbolicFunctionVariable( | ||||
| 	TypePointer _type, | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicVariable(move(_type), move(_uniqueName), _interface), | ||||
| 	m_declaration(m_interface.newVariable(currentName(), m_sort)) | ||||
| { | ||||
| 	solAssert(m_type->category() == Type::Category::Function, ""); | ||||
| 	solAssert(m_type->category() == solidity::Type::Category::Function, ""); | ||||
| } | ||||
| 
 | ||||
| void SymbolicFunctionVariable::resetDeclaration() | ||||
| @ -132,22 +132,22 @@ void SymbolicFunctionVariable::resetDeclaration() | ||||
| 	m_declaration = m_interface.newVariable(currentName(), m_sort); | ||||
| } | ||||
| 
 | ||||
| smt::Expression SymbolicFunctionVariable::increaseIndex() | ||||
| Expression SymbolicFunctionVariable::increaseIndex() | ||||
| { | ||||
| 	++(*m_ssa); | ||||
| 	resetDeclaration(); | ||||
| 	return currentValue(); | ||||
| } | ||||
| 
 | ||||
| smt::Expression SymbolicFunctionVariable::operator()(vector<smt::Expression> _arguments) const | ||||
| Expression SymbolicFunctionVariable::operator()(vector<Expression> _arguments) const | ||||
| { | ||||
| 	return m_declaration(_arguments); | ||||
| } | ||||
| 
 | ||||
| SymbolicMappingVariable::SymbolicMappingVariable( | ||||
| 	TypePointer _type, | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicVariable(move(_type), move(_uniqueName), _interface) | ||||
| { | ||||
| @ -155,9 +155,9 @@ SymbolicMappingVariable::SymbolicMappingVariable( | ||||
| } | ||||
| 
 | ||||
| SymbolicArrayVariable::SymbolicArrayVariable( | ||||
| 	TypePointer _type, | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicVariable(move(_type), move(_uniqueName), _interface) | ||||
| { | ||||
| @ -165,11 +165,29 @@ SymbolicArrayVariable::SymbolicArrayVariable( | ||||
| } | ||||
| 
 | ||||
| SymbolicEnumVariable::SymbolicEnumVariable( | ||||
| 	TypePointer _type, | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	smt::SolverInterface& _interface | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicVariable(move(_type), move(_uniqueName), _interface) | ||||
| { | ||||
| 	solAssert(isEnum(m_type->category()), ""); | ||||
| } | ||||
| 
 | ||||
| SymbolicTupleVariable::SymbolicTupleVariable( | ||||
| 	solidity::TypePointer _type, | ||||
| 	string _uniqueName, | ||||
| 	SolverInterface& _interface | ||||
| ): | ||||
| 	SymbolicVariable(move(_type), move(_uniqueName), _interface) | ||||
| { | ||||
| 	solAssert(isTuple(m_type->category()), ""); | ||||
| } | ||||
| 
 | ||||
| void SymbolicTupleVariable::setComponents(vector<shared_ptr<SymbolicVariable>> _components) | ||||
| { | ||||
| 	solAssert(m_components.empty(), ""); | ||||
| 	auto const& tupleType = dynamic_cast<solidity::TupleType const*>(m_type); | ||||
| 	solAssert(_components.size() == tupleType->components().size(), ""); | ||||
| 	m_components = move(_components); | ||||
| } | ||||
|  | ||||
| @ -26,6 +26,8 @@ namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace smt | ||||
| { | ||||
| 
 | ||||
| class Type; | ||||
| 
 | ||||
| @ -36,23 +38,23 @@ class SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicVariable( | ||||
| 		TypePointer _type, | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| 	SymbolicVariable( | ||||
| 		smt::SortPointer _sort, | ||||
| 		SortPointer _sort, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| 
 | ||||
| 	virtual ~SymbolicVariable() = default; | ||||
| 
 | ||||
| 	smt::Expression currentValue() const; | ||||
| 	Expression currentValue() const; | ||||
| 	std::string currentName() const; | ||||
| 	virtual smt::Expression valueAtIndex(int _index) const; | ||||
| 	virtual smt::Expression increaseIndex(); | ||||
| 	virtual smt::Expression operator()(std::vector<smt::Expression> /*_arguments*/) const | ||||
| 	virtual Expression valueAtIndex(int _index) const; | ||||
| 	virtual Expression increaseIndex(); | ||||
| 	virtual Expression operator()(std::vector<Expression> /*_arguments*/) const | ||||
| 	{ | ||||
| 		solAssert(false, "Function application to non-function."); | ||||
| 	} | ||||
| @ -60,18 +62,18 @@ public: | ||||
| 	unsigned index() const { return m_ssa->index(); } | ||||
| 	unsigned& index() { return m_ssa->index(); } | ||||
| 
 | ||||
| 	TypePointer const& type() const { return m_type; } | ||||
| 	solidity::TypePointer const& type() const { return m_type; } | ||||
| 
 | ||||
| protected: | ||||
| 	std::string uniqueSymbol(unsigned _index) const; | ||||
| 
 | ||||
| 	/// SMT sort.
 | ||||
| 	smt::SortPointer m_sort; | ||||
| 	SortPointer m_sort; | ||||
| 	/// Solidity type, used for size and range in number types.
 | ||||
| 	TypePointer m_type; | ||||
| 	solidity::TypePointer m_type; | ||||
| 	std::string m_uniqueName; | ||||
| 	smt::SolverInterface& m_interface; | ||||
| 	std::shared_ptr<SSAVariable> m_ssa; | ||||
| 	SolverInterface& m_interface; | ||||
| 	std::unique_ptr<SSAVariable> m_ssa; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
| @ -81,9 +83,9 @@ class SymbolicBoolVariable: public SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicBoolVariable( | ||||
| 		TypePointer _type, | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| @ -94,9 +96,9 @@ class SymbolicIntVariable: public SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicIntVariable( | ||||
| 		TypePointer _type, | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| @ -108,7 +110,7 @@ class SymbolicAddressVariable: public SymbolicIntVariable | ||||
| public: | ||||
| 	SymbolicAddressVariable( | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| @ -121,7 +123,7 @@ public: | ||||
| 	SymbolicFixedBytesVariable( | ||||
| 		unsigned _numBytes, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| @ -132,20 +134,20 @@ class SymbolicFunctionVariable: public SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicFunctionVariable( | ||||
| 		TypePointer _type, | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| 
 | ||||
| 	smt::Expression increaseIndex(); | ||||
| 	smt::Expression operator()(std::vector<smt::Expression> _arguments) const; | ||||
| 	Expression increaseIndex(); | ||||
| 	Expression operator()(std::vector<Expression> _arguments) const; | ||||
| 
 | ||||
| private: | ||||
| 	/// Creates a new function declaration.
 | ||||
| 	void resetDeclaration(); | ||||
| 
 | ||||
| 	/// Stores the current function declaration.
 | ||||
| 	smt::Expression m_declaration; | ||||
| 	Expression m_declaration; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
| @ -155,9 +157,9 @@ class SymbolicMappingVariable: public SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicMappingVariable( | ||||
| 		TypePointer _type, | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| @ -168,9 +170,9 @@ class SymbolicArrayVariable: public SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicArrayVariable( | ||||
| 		TypePointer _type, | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| @ -181,11 +183,35 @@ class SymbolicEnumVariable: public SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicEnumVariable( | ||||
| 		TypePointer _type, | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		smt::SolverInterface& _interface | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Specialization of SymbolicVariable for Tuple | ||||
|  */ | ||||
| class SymbolicTupleVariable: public SymbolicVariable | ||||
| { | ||||
| public: | ||||
| 	SymbolicTupleVariable( | ||||
| 		solidity::TypePointer _type, | ||||
| 		std::string _uniqueName, | ||||
| 		SolverInterface& _interface | ||||
| 	); | ||||
| 
 | ||||
| 	std::vector<std::shared_ptr<SymbolicVariable>> const& components() | ||||
| 	{ | ||||
| 		return m_components; | ||||
| 	} | ||||
| 
 | ||||
| 	void setComponents(std::vector<std::shared_ptr<SymbolicVariable>> _components); | ||||
| 
 | ||||
| private: | ||||
| 	std::vector<std::shared_ptr<SymbolicVariable>> m_components; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -24,33 +24,53 @@ | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| using namespace dev::solidity::smt; | ||||
| 
 | ||||
| set<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _node, vector<CallableDeclaration const*> const& _outerCallstack) | ||||
| { | ||||
| 	m_touchedVariables.clear(); | ||||
| 	m_callStack.clear(); | ||||
| 	m_callStack += _outerCallstack; | ||||
| 	m_lastCall = m_callStack.back(); | ||||
| 	_node.accept(*this); | ||||
| 	return m_touchedVariables; | ||||
| } | ||||
| 
 | ||||
| void VariableUsage::endVisit(Identifier const& _identifier) | ||||
| { | ||||
| 	Declaration const* declaration = _identifier.annotation().referencedDeclaration; | ||||
| 	solAssert(declaration, ""); | ||||
| 	if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) | ||||
| 		if (_identifier.annotation().lValueRequested) | ||||
| 			m_touchedVariables.insert(varDecl); | ||||
| 	if (_identifier.annotation().lValueRequested) | ||||
| 		checkIdentifier(_identifier); | ||||
| } | ||||
| 
 | ||||
| void VariableUsage::endVisit(IndexAccess const& _indexAccess) | ||||
| { | ||||
| 	if (_indexAccess.annotation().lValueRequested) | ||||
| 	{ | ||||
| 		/// identifier.annotation().lValueRequested == false, that's why we
 | ||||
| 		/// need to check that before.
 | ||||
| 		auto identifier = dynamic_cast<Identifier const*>(SMTChecker::leftmostBase(_indexAccess)); | ||||
| 		if (identifier) | ||||
| 			checkIdentifier(*identifier); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void VariableUsage::endVisit(FunctionCall const& _funCall) | ||||
| { | ||||
| 	if (auto const& funDef = SMTChecker::inlinedFunctionCallToDefinition(_funCall)) | ||||
| 		if (find(m_functionPath.begin(), m_functionPath.end(), funDef) == m_functionPath.end()) | ||||
| 		if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end()) | ||||
| 			funDef->accept(*this); | ||||
| } | ||||
| 
 | ||||
| bool VariableUsage::visit(FunctionDefinition const& _function) | ||||
| { | ||||
| 	m_functionPath.push_back(&_function); | ||||
| 	m_callStack.push_back(&_function); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| void VariableUsage::endVisit(FunctionDefinition const&) | ||||
| { | ||||
| 	solAssert(!m_functionPath.empty(), ""); | ||||
| 	m_functionPath.pop_back(); | ||||
| 	solAssert(!m_callStack.empty(), ""); | ||||
| 	m_callStack.pop_back(); | ||||
| } | ||||
| 
 | ||||
| void VariableUsage::endVisit(ModifierInvocation const& _modifierInv) | ||||
| @ -62,18 +82,23 @@ void VariableUsage::endVisit(ModifierInvocation const& _modifierInv) | ||||
| 
 | ||||
| void VariableUsage::endVisit(PlaceholderStatement const&) | ||||
| { | ||||
| 	solAssert(!m_functionPath.empty(), ""); | ||||
| 	FunctionDefinition const* function = m_functionPath.back(); | ||||
| 	solAssert(function, ""); | ||||
| 	if (function->isImplemented()) | ||||
| 		function->body().accept(*this); | ||||
| 	solAssert(!m_callStack.empty(), ""); | ||||
| 	FunctionDefinition const* funDef = nullptr; | ||||
| 	for (auto it = m_callStack.rbegin(); it != m_callStack.rend() && !funDef; ++it) | ||||
| 		funDef = dynamic_cast<FunctionDefinition const*>(*it); | ||||
| 	solAssert(funDef, ""); | ||||
| 	if (funDef->isImplemented()) | ||||
| 		funDef->body().accept(*this); | ||||
| } | ||||
| 
 | ||||
| set<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _node, vector<FunctionDefinition const*> const& _outerCallstack) | ||||
| void VariableUsage::checkIdentifier(Identifier const& _identifier) | ||||
| { | ||||
| 	m_touchedVariables.clear(); | ||||
| 	m_functionPath.clear(); | ||||
| 	m_functionPath += _outerCallstack; | ||||
| 	_node.accept(*this); | ||||
| 	return m_touchedVariables; | ||||
| 	Declaration const* declaration = _identifier.annotation().referencedDeclaration; | ||||
| 	solAssert(declaration, ""); | ||||
| 	if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) | ||||
| 	{ | ||||
| 		solAssert(m_lastCall, ""); | ||||
| 		if (!varDecl->isLocalVariable() || varDecl->functionOrModifierDefinition() == m_lastCall) | ||||
| 			m_touchedVariables.insert(varDecl); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -26,6 +26,8 @@ namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace smt | ||||
| { | ||||
| 
 | ||||
| /**
 | ||||
|  * This class computes information about which variables are modified in a certain subtree. | ||||
| @ -34,19 +36,25 @@ class VariableUsage: private ASTConstVisitor | ||||
| { | ||||
| public: | ||||
| 	/// @param _outerCallstack the current callstack in the callers context.
 | ||||
| 	std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<FunctionDefinition const*> const& _outerCallstack); | ||||
| 	std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<CallableDeclaration const*> const& _outerCallstack); | ||||
| 
 | ||||
| private: | ||||
| 	void endVisit(Identifier const& _node) override; | ||||
| 	void endVisit(IndexAccess const& _node) override; | ||||
| 	void endVisit(FunctionCall const& _node) override; | ||||
| 	bool visit(FunctionDefinition const& _node) override; | ||||
| 	void endVisit(FunctionDefinition const& _node) override; | ||||
| 	void endVisit(ModifierInvocation const& _node) override; | ||||
| 	void endVisit(PlaceholderStatement const& _node) override; | ||||
| 
 | ||||
| 	/// Checks whether an identifier should be added to touchedVariables.
 | ||||
| 	void checkIdentifier(Identifier const& _identifier); | ||||
| 
 | ||||
| 	std::set<VariableDeclaration const*> m_touchedVariables; | ||||
| 	std::vector<FunctionDefinition const*> m_functionPath; | ||||
| 	std::vector<CallableDeclaration const*> m_callStack; | ||||
| 	CallableDeclaration const* m_lastCall = nullptr; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -56,6 +56,7 @@ | ||||
| #include <libevmasm/Exceptions.h> | ||||
| 
 | ||||
| #include <libdevcore/SwarmHash.h> | ||||
| #include <libdevcore/IpfsHash.h> | ||||
| #include <libdevcore/JSON.h> | ||||
| 
 | ||||
| #include <json/json.h> | ||||
| @ -216,7 +217,7 @@ bool CompilerStack::parse() | ||||
| 		string const& path = sourcesToParse[i]; | ||||
| 		Source& source = m_sources[path]; | ||||
| 		source.scanner->reset(); | ||||
| 		source.ast = Parser(m_errorReporter).parse(source.scanner); | ||||
| 		source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner); | ||||
| 		if (!source.ast) | ||||
| 			solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); | ||||
| 		else | ||||
| @ -249,7 +250,7 @@ bool CompilerStack::analyze() | ||||
| 	bool noErrors = true; | ||||
| 
 | ||||
| 	try { | ||||
| 		SyntaxChecker syntaxChecker(m_errorReporter); | ||||
| 		SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser); | ||||
| 		for (Source const* source: m_sourceOrder) | ||||
| 			if (!syntaxChecker.checkSyntax(*source->ast)) | ||||
| 				noErrors = false; | ||||
| @ -260,7 +261,7 @@ bool CompilerStack::analyze() | ||||
| 				noErrors = false; | ||||
| 
 | ||||
| 		m_globalContext = make_shared<GlobalContext>(); | ||||
| 		NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter); | ||||
| 		NameAndTypeResolver resolver(*m_globalContext, m_scopes, m_errorReporter); | ||||
| 		for (Source const* source: m_sourceOrder) | ||||
| 			if (!resolver.registerDeclarations(*source->ast)) | ||||
| 				return false; | ||||
| @ -278,11 +279,8 @@ bool CompilerStack::analyze() | ||||
| 			for (ASTPointer<ASTNode> const& node: source->ast->nodes()) | ||||
| 				if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) | ||||
| 				{ | ||||
| 					m_globalContext->setCurrentContract(*contract); | ||||
| 					if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false; | ||||
| 					if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false; | ||||
| 					if (!resolver.resolveNamesAndTypes(*contract)) return false; | ||||
| 
 | ||||
| 					if (!resolver.resolveNamesAndTypes(*contract)) return false; | ||||
| 					// Note that we now reference contracts by their fully qualified names, and
 | ||||
| 					// thus contracts can only conflict if declared in the same source file.  This
 | ||||
| 					// already causes a double-declaration error elsewhere, so we do not report
 | ||||
| @ -397,7 +395,8 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con | ||||
| 	return | ||||
| 		m_requestedContractNames.empty() || | ||||
| 		m_requestedContractNames.count(_contract.fullyQualifiedName()) || | ||||
| 		m_requestedContractNames.count(_contract.name()); | ||||
| 		m_requestedContractNames.count(_contract.name()) || | ||||
| 		m_requestedContractNames.count(":" + _contract.name()); | ||||
| } | ||||
| 
 | ||||
| bool CompilerStack::compile() | ||||
| @ -771,6 +770,13 @@ h256 const& CompilerStack::Source::swarmHash() const | ||||
| 	return swarmHashCached; | ||||
| } | ||||
| 
 | ||||
| string const& CompilerStack::Source::ipfsUrl() const | ||||
| { | ||||
| 	if (ipfsUrlCached.empty()) | ||||
| 		if (scanner->source().size() < 1024 * 256) | ||||
| 			ipfsUrlCached = "dweb:/ipfs/" + dev::ipfsHashBase58(scanner->source()); | ||||
| 	return ipfsUrlCached; | ||||
| } | ||||
| 
 | ||||
| StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath) | ||||
| { | ||||
| @ -1032,6 +1038,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const | ||||
| 		{ | ||||
| 			meta["sources"][s.first]["urls"] = Json::arrayValue; | ||||
| 			meta["sources"][s.first]["urls"].append("bzzr://" + toHex(s.second.swarmHash().asBytes())); | ||||
| 			meta["sources"][s.first]["urls"].append(s.second.ipfsUrl()); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -1180,6 +1187,10 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen | ||||
| 	encoder.pushBytes("bzzr0", dev::swarmHash(_metadata).asBytes()); | ||||
| 	if (_experimentalMode) | ||||
| 		encoder.pushBool("experimental", true); | ||||
| 	if (m_release) | ||||
| 		encoder.pushBytes("solc", VersionCompactBytes); | ||||
| 	else | ||||
| 		encoder.pushString("solc", VersionStringStrict); | ||||
| 	return encoder.serialise(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -25,6 +25,7 @@ | ||||
| 
 | ||||
| #include <libsolidity/interface/ReadFile.h> | ||||
| #include <libsolidity/interface/OptimiserSettings.h> | ||||
| #include <libsolidity/interface/Version.h> | ||||
| 
 | ||||
| #include <liblangutil/ErrorReporter.h> | ||||
| #include <liblangutil/EVMVersion.h> | ||||
| @ -131,6 +132,14 @@ public: | ||||
| 	/// Must be set before parsing.
 | ||||
| 	void setOptimiserSettings(OptimiserSettings _settings); | ||||
| 
 | ||||
| 	/// Set whether or not parser error is desired.
 | ||||
| 	/// When called without an argument it will revert to the default.
 | ||||
| 	/// Must be set before parsing.
 | ||||
| 	void setParserErrorRecovery(bool _wantErrorRecovery = false) | ||||
| 	{ | ||||
| 		m_parserErrorRecovery = _wantErrorRecovery; | ||||
| 	} | ||||
| 
 | ||||
| 	/// Set the EVM version used before running compile.
 | ||||
| 	/// When called without an argument it will revert to the default version.
 | ||||
| 	/// Must be set before parsing.
 | ||||
| @ -261,6 +270,8 @@ public: | ||||
| 	/// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
 | ||||
| 	Json::Value gasEstimates(std::string const& _contractName) const; | ||||
| 
 | ||||
| 	/// Overwrites the release/prerelease flag. Should only be used for testing.
 | ||||
| 	void overwriteReleaseFlag(bool release) { m_release = release; } | ||||
| private: | ||||
| 	/// The state per source unit. Filled gradually during parsing.
 | ||||
| 	struct Source | ||||
| @ -269,9 +280,11 @@ private: | ||||
| 		std::shared_ptr<SourceUnit> ast; | ||||
| 		h256 mutable keccak256HashCached; | ||||
| 		h256 mutable swarmHashCached; | ||||
| 		std::string mutable ipfsUrlCached; | ||||
| 		void reset() { *this = Source(); } | ||||
| 		h256 const& keccak256() const; | ||||
| 		h256 const& swarmHash() const; | ||||
| 		std::string const& ipfsUrl() const; | ||||
| 	}; | ||||
| 
 | ||||
| 	/// The state per contract. Filled gradually during compilation.
 | ||||
| @ -333,7 +346,7 @@ private: | ||||
| 	std::string createMetadata(Contract const& _contract) const; | ||||
| 
 | ||||
| 	/// @returns the metadata CBOR for the given serialised metadata JSON.
 | ||||
| 	static bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode); | ||||
| 	bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode); | ||||
| 
 | ||||
| 	/// @returns the computer source mapping string.
 | ||||
| 	std::string computeSourceMapping(eth::AssemblyItems const& _items) const; | ||||
| @ -381,7 +394,9 @@ private: | ||||
| 	langutil::ErrorList m_errorList; | ||||
| 	langutil::ErrorReporter m_errorReporter; | ||||
| 	bool m_metadataLiteralSources = false; | ||||
| 	bool m_parserErrorRecovery = false; | ||||
| 	State m_stackState = Empty; | ||||
| 	bool m_release = VersionIsRelease; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -437,7 +437,7 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu | ||||
| 				return *error; | ||||
| 		} | ||||
| 	} | ||||
| 	return std::move(settings); | ||||
| 	return { std::move(settings) }; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| @ -663,7 +663,7 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile | ||||
| 
 | ||||
| 	ret.outputSelection = std::move(outputSelection); | ||||
| 
 | ||||
| 	return std::move(ret); | ||||
| 	return { std::move(ret) }; | ||||
| } | ||||
| 
 | ||||
| Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings) | ||||
| @ -965,6 +965,8 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) | ||||
| 
 | ||||
| Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept | ||||
| { | ||||
| 	YulStringRepository::reset(); | ||||
| 
 | ||||
| 	try | ||||
| 	{ | ||||
| 		auto parsed = parseInput(_input); | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user