mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6630 from ethereum/develop
Merge develop into release for 0.5.8
This commit is contained in:
commit
23d335f28e
@ -17,9 +17,20 @@ defaults:
|
||||
[ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON"
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS
|
||||
make -j4
|
||||
- run_build_ossfuzz: &run_build_ossfuzz
|
||||
name: Build_ossfuzz
|
||||
command: |
|
||||
mkdir -p build
|
||||
cd build
|
||||
/src/LPM/external.protobuf/bin/protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS
|
||||
make ossfuzz ossfuzz_proto -j4
|
||||
- run_tests: &run_tests
|
||||
name: Tests
|
||||
command: scripts/tests.sh --junit_report test_results
|
||||
- run_regressions: &run_regressions
|
||||
name: Regression tests
|
||||
command: scripts/regressions.py -o test_results
|
||||
- solc_artifact: &solc_artifact
|
||||
path: build/solc/solc
|
||||
destination: solc
|
||||
@ -29,6 +40,15 @@ defaults:
|
||||
- solc/solc
|
||||
- test/soltest
|
||||
- test/tools/solfuzzer
|
||||
- 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/strictasm_diff_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_ossfuzz
|
||||
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
@ -86,7 +106,7 @@ jobs:
|
||||
command: |
|
||||
test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt)
|
||||
|
||||
test_emscripten_external:
|
||||
test_emscripten_external_gnosis:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
environment:
|
||||
@ -96,14 +116,23 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
name: Install external tests deps
|
||||
name: External GnosisSafe tests
|
||||
command: |
|
||||
node --version
|
||||
npm --version
|
||||
test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js
|
||||
|
||||
test_emscripten_external_zeppelin:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
name: External tests
|
||||
name: External Zeppelin tests
|
||||
command: |
|
||||
test/externalTests.sh /tmp/workspace/soljson.js || test/externalTests.sh /tmp/workspace/soljson.js
|
||||
test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js
|
||||
|
||||
build_x86_linux:
|
||||
docker:
|
||||
@ -117,8 +146,7 @@ jobs:
|
||||
name: Install build dependencies
|
||||
command: |
|
||||
apt-get -qq update
|
||||
apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev
|
||||
./scripts/install_obsolete_jsoncpp_1_7_4.sh
|
||||
apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\*
|
||||
- run: *setup_prerelease_commit_hash
|
||||
- run: *run_build
|
||||
- store_artifacts: *solc_artifact
|
||||
@ -139,8 +167,7 @@ jobs:
|
||||
name: Install build dependencies
|
||||
command: |
|
||||
apt-get -qq update
|
||||
apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev
|
||||
./scripts/install_obsolete_jsoncpp_1_7_4.sh
|
||||
apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\*
|
||||
- run: *setup_prerelease_commit_hash
|
||||
- run: *run_build
|
||||
|
||||
@ -179,8 +206,7 @@ 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
|
||||
./scripts/install_obsolete_jsoncpp_1_7_4.sh
|
||||
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 libjsoncpp-dev=1.7.4-\*
|
||||
- run: *setup_prerelease_commit_hash
|
||||
- run: *run_build
|
||||
- store_artifacts: *solc_artifact
|
||||
@ -306,12 +332,19 @@ jobs:
|
||||
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1
|
||||
- run: mkdir -p test_results
|
||||
- run:
|
||||
name: Run tests with ASAN
|
||||
name: Run soltest with ASAN
|
||||
command: |
|
||||
ulimit -a
|
||||
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
|
||||
ulimit -s 16384
|
||||
build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test
|
||||
- run:
|
||||
name: Run commandline tests with ASAN
|
||||
command: |
|
||||
ulimit -a
|
||||
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
|
||||
ulimit -s 16384
|
||||
test/cmdlineTests.sh
|
||||
- store_test_results:
|
||||
path: test_results/
|
||||
- store_artifacts:
|
||||
@ -381,6 +414,51 @@ jobs:
|
||||
path: docs/_build/html/
|
||||
destination: docs-html
|
||||
|
||||
build_x86_linux_ossfuzz:
|
||||
docker:
|
||||
- image: buildpack-deps:cosmic
|
||||
environment:
|
||||
TERM: xterm
|
||||
CC: /usr/bin/clang-7
|
||||
CXX: /usr/bin/clang++-7
|
||||
CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
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-\*
|
||||
./scripts/install_lpm.sh
|
||||
./scripts/install_libfuzzer.sh
|
||||
- run: *setup_prerelease_commit_hash
|
||||
- run: *run_build_ossfuzz
|
||||
- persist_to_workspace: *ossfuzz_artifacts
|
||||
|
||||
test_x86_ossfuzz_regression:
|
||||
docker:
|
||||
- image: buildpack-deps:cosmic
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
apt-get -qq update
|
||||
apt-get -qy install libcvc4-dev llvm-7-dev
|
||||
./scripts/download_ossfuzz_corpus.sh
|
||||
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1
|
||||
- run: mkdir -p test_results
|
||||
- run: *run_regressions
|
||||
- store_test_results:
|
||||
path: test_results/
|
||||
- store_artifacts:
|
||||
path: test_results/
|
||||
destination: test_results/
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_all:
|
||||
@ -393,7 +471,11 @@ workflows:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- test_emscripten_external:
|
||||
- test_emscripten_external_zeppelin:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- test_emscripten_external_gnosis:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
@ -406,9 +488,7 @@ workflows:
|
||||
requires:
|
||||
- build_x86_linux
|
||||
- test_x86_clang7_asan:
|
||||
filters:
|
||||
branches:
|
||||
only: develop
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_x86_clang7_asan
|
||||
- test_x86_mac:
|
||||
@ -421,3 +501,28 @@ workflows:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_x86_archlinux
|
||||
- build_x86_linux_ossfuzz: *build_on_tags
|
||||
|
||||
test_nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
jobs:
|
||||
- build_emscripten: *build_on_tags
|
||||
- test_emscripten_external_zeppelin:
|
||||
<<: *build_on_tags
|
||||
requires:
|
||||
- build_emscripten
|
||||
- test_emscripten_external_gnosis:
|
||||
<<: *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
|
||||
|
@ -17,3 +17,7 @@ indent_size = 4
|
||||
[std/**.sol]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{txt,cmake}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -32,6 +32,7 @@ prerelease.txt
|
||||
|
||||
# Build directory
|
||||
build/
|
||||
build*/
|
||||
emscripten_build/
|
||||
docs/_build
|
||||
docs/utils/__pycache__
|
||||
@ -44,6 +45,9 @@ deps/cache
|
||||
[._]*.sw[a-p]
|
||||
[._]sw[a-p]
|
||||
|
||||
# emacs stuff
|
||||
*~
|
||||
|
||||
# IDE files
|
||||
.idea
|
||||
.vscode
|
||||
@ -53,3 +57,6 @@ CMakeLists.txt.user
|
||||
/.vs
|
||||
/.cproject
|
||||
/.project
|
||||
|
||||
# place to put local temporary files
|
||||
tmp
|
||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.5.7")
|
||||
set(PROJECT_VERSION "0.5.8")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
|
||||
|
||||
option(LLL "Build LLL" OFF)
|
||||
|
@ -46,6 +46,8 @@ foo->bar(someLongVariableName,
|
||||
cout << "some very long string that contains completely irrelevant text that talks about this and that and contains the words \"lorem\" and \"ipsum\"" << endl;
|
||||
```
|
||||
|
||||
To set indentation and tab width settings uniformly, the repository contains an [EditorConfig](https://editorconfig.org/) [`.editorconfig`](https://github.com/ethereum/solidity/blob/develop/.editorconfig) file, which describes some of the styles used and which is recognized by many IDE's and editors.
|
||||
|
||||
## 1. Namespaces
|
||||
|
||||
1. No `using namespace` declarations in header files.
|
||||
|
57
Changelog.md
57
Changelog.md
@ -1,3 +1,46 @@
|
||||
### 0.5.8 (2019-04-30)
|
||||
|
||||
Important Bugfixes:
|
||||
* Code Generator: Fix initialization routine of uninitialized internal function pointers in constructor context.
|
||||
* Yul Optimizer: Fix SSA transform for multi-assignments.
|
||||
|
||||
|
||||
Language Features:
|
||||
* ABIEncoderV2: Implement encoding of calldata arrays and structs.
|
||||
* Code Generation: Implement copying recursive structs from storage to memory.
|
||||
* Yul: Disallow function definitions inside for-loop init blocks.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* ABI Decoder: Raise a runtime error on dirty inputs when using the experimental decoder.
|
||||
* Optimizer: Add rule for shifts by constants larger than 255 for Constantinople.
|
||||
* Optimizer: Add rule to simplify certain ANDs and SHL combinations
|
||||
* SMTChecker: Support arithmetic compound assignment operators.
|
||||
* SMTChecker: Support unary increment and decrement for array and mapping access.
|
||||
* SMTChecker: Show unsupported warning for inline assembly blocks.
|
||||
* SMTChecker: Support mod.
|
||||
* SMTChecker: Support ``contract`` type.
|
||||
* SMTChecker: Support ``this`` as address.
|
||||
* SMTChecker: Support address members.
|
||||
* Standard JSON Interface: Metadata settings now re-produce the original ``"useLiteralContent"`` setting from the compilation input.
|
||||
* Yul: Adds break and continue keywords to for-loop syntax.
|
||||
* Yul: Support ``.`` as part of identifiers.
|
||||
* Yul Optimizer: Adds steps for detecting and removing of dead code.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* SMTChecker: Implement Boolean short-circuiting.
|
||||
* 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.
|
||||
* Yul: Properly register functions and disallow shadowing between function variables and variables in the outside scope.
|
||||
|
||||
|
||||
Build System:
|
||||
* Soltest: Add commandline option `--test` / `-t` to isoltest which takes a string that allows filtering unit tests.
|
||||
* soltest.sh: allow environment variable ``SOLIDITY_BUILD_DIR`` to specify build folder and add ``--help`` usage.
|
||||
|
||||
|
||||
### 0.5.7 (2019-03-26)
|
||||
|
||||
Important Bugfixes:
|
||||
@ -368,6 +411,20 @@ Bugfixes:
|
||||
* Parser: Fix incorrect source location for nameless parameters.
|
||||
* Command Line Interface: Fix internal error when compiling stdin with no content and --ast option.
|
||||
|
||||
|
||||
### 0.4.26 (2019-04-29)
|
||||
|
||||
Important Bugfixes:
|
||||
* Code Generator: Fix initialization routine of uninitialized internal function pointers in constructor context.
|
||||
* Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation.
|
||||
|
||||
Bugfixes:
|
||||
* ABIEncoderV2: Refuse to generate code that is known to be potentially buggy.
|
||||
* General: Split rule list such that JavaScript environments with small stacks can use the compiler.
|
||||
|
||||
Note: The above changes are not included in 0.5.0, because they were backported.
|
||||
|
||||
|
||||
### 0.4.25 (2018-09-12)
|
||||
|
||||
Important Bugfixes:
|
||||
|
@ -1,5 +1,6 @@
|
||||
# The Solidity Contract-Oriented Programming Language
|
||||
[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/ethereum/solidity.svg?branch=develop)](https://travis-ci.org/ethereum/solidity)
|
||||
[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform.
|
||||
|
||||
## Table of Contents
|
||||
|
@ -33,10 +33,10 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
|
||||
add_compile_options(-Werror)
|
||||
|
||||
# Configuration-specific compiler settings.
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DETH_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g3")
|
||||
|
||||
# Additional GCC-specific compiler settings.
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
||||
@ -54,6 +54,12 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
|
||||
# Set stack size to 32MB - by default Apple's clang defines a stack size of 8MB.
|
||||
# Normally 16MB is enough to run all tests, but it will exceed the stack, if -DSANITIZE=address is used.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-stack_size -Wl,0x2000000")
|
||||
|
||||
# Boost libraries use visibility=hidden to reduce unnecessary DWARF entries.
|
||||
# Unless we match visibility, ld will give a warning message like:
|
||||
# ld: warning: direct access in function 'boost::filesystem... from file ...
|
||||
# means the weak symbol cannot be overridden at runtime. This was likely caused by different translation units being compiled with different visibility settings.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
|
||||
endif()
|
||||
|
||||
# Some Linux-specific Clang settings. We don't want these for OS X.
|
||||
|
@ -1,3 +1,6 @@
|
||||
# CMAKE macros to set default CMAKE options and to show the
|
||||
# resulting configuration.
|
||||
|
||||
macro(configure_project)
|
||||
set(NAME ${PROJECT_NAME})
|
||||
|
||||
@ -22,7 +25,7 @@ endmacro()
|
||||
macro(print_config NAME)
|
||||
message("")
|
||||
message("------------------------------------------------------------------------")
|
||||
message("-- Configuring ${NAME}")
|
||||
message("-- Configuring ${NAME} ${PROJECT_VERSION}")
|
||||
message("------------------------------------------------------------------------")
|
||||
message("-- CMake Version ${CMAKE_VERSION}")
|
||||
message("-- CMAKE_BUILD_TYPE Build type ${CMAKE_BUILD_TYPE}")
|
||||
@ -36,6 +39,9 @@ endif()
|
||||
if (SUPPORT_TOOLS)
|
||||
message("-- TOOLS Build tools ${TOOLS}")
|
||||
endif()
|
||||
message("------------------------------------------------------------------ flags")
|
||||
message("-- OSSFUZZ ${OSSFUZZ}")
|
||||
message("-- LLL ${LLL}")
|
||||
message("------------------------------------------------------------------------")
|
||||
message("")
|
||||
endmacro()
|
||||
|
2
cmake/toolchains/libfuzzer.cmake
Normal file
2
cmake/toolchains/libfuzzer.cmake
Normal file
@ -0,0 +1,2 @@
|
||||
# Require libfuzzer specific flags
|
||||
set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++")
|
@ -42,7 +42,8 @@ Syntax
|
||||
------
|
||||
|
||||
Assembly parses comments, literals and identifiers in the same way as Solidity, so you can use the
|
||||
usual ``//`` and ``/* */`` comments. Inline assembly is marked by ``assembly { ... }`` and inside
|
||||
usual ``//`` and ``/* */`` comments. There is one exception: Identifiers in inline assembly can contain
|
||||
``.``. Inline assembly is marked by ``assembly { ... }`` and inside
|
||||
these curly braces, you can use the following (see the later sections for more details):
|
||||
|
||||
- literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters)
|
||||
@ -765,7 +766,7 @@ Grammar::
|
||||
SubAssembly
|
||||
AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral
|
||||
AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral
|
||||
Identifier = [a-zA-Z_$] [a-zA-Z_0-9]*
|
||||
Identifier = [a-zA-Z_$] [a-zA-Z_0-9.]*
|
||||
AssemblyCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
|
||||
AssemblyLocalDefinition = 'let' IdentifierOrList ( ':=' AssemblyExpression )?
|
||||
AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression
|
||||
|
@ -1,19 +1,65 @@
|
||||
[
|
||||
{
|
||||
"name": "UninitializedFunctionPointerInConstructor",
|
||||
"summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.",
|
||||
"description": "Uninitialized internal function pointers point to a special piece of code that causes a revert when called. Jump target positions are different during construction and after deployment, but the code for setting this special jump target only considered the situation after deployment.",
|
||||
"introduced": "0.5.0",
|
||||
"fixed": "0.5.8",
|
||||
"severity": "very low"
|
||||
},
|
||||
{
|
||||
"name": "UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.",
|
||||
"description": "Uninitialized internal function pointers point to a special piece of code that causes a revert when called. Jump target positions are different during construction and after deployment, but the code for setting this special jump target only considered the situation after deployment.",
|
||||
"introduced": "0.4.5",
|
||||
"fixed": "0.4.26",
|
||||
"severity": "very low"
|
||||
},
|
||||
{
|
||||
"name": "IncorrectEventSignatureInLibraries",
|
||||
"summary": "Contract types used in events in libraries cause an incorrect event signature hash",
|
||||
"description": "Instead of using the type `address` in the hashed signature, the actual contract name was used, leading to a wrong hash in the logs.",
|
||||
"introduced": "0.5.0",
|
||||
"fixed": "0.5.8",
|
||||
"severity": "very low"
|
||||
},
|
||||
{
|
||||
"name": "IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"summary": "Contract types used in events in libraries cause an incorrect event signature hash",
|
||||
"description": "Instead of using the type `address` in the hashed signature, the actual contract name was used, leading to a wrong hash in the logs.",
|
||||
"introduced": "0.3.0",
|
||||
"fixed": "0.4.26",
|
||||
"severity": "very low"
|
||||
},
|
||||
{
|
||||
"name": "ABIEncoderV2PackedStorage",
|
||||
"summary": "Storage structs and arrays with types shorter than 32 bytes can cause data corruption if encoded directly from storage using the experimental ABIEncoderV2.",
|
||||
"description": "Elements of structs and arrays that are shorter than 32 bytes are not properly decoded from storage when encoded directly (i.e. not via a memory type) using ABIEncoderV2. This can cause corruption in the values themselves but can also overwrite other parts of the encoded data.",
|
||||
"introduced": "0.4.19",
|
||||
"link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/",
|
||||
"introduced": "0.5.0",
|
||||
"fixed": "0.5.7",
|
||||
"severity": "low",
|
||||
"conditions": {
|
||||
"ABIEncoderV2": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ABIEncoderV2PackedStorage_0.4.x",
|
||||
"summary": "Storage structs and arrays with types shorter than 32 bytes can cause data corruption if encoded directly from storage using the experimental ABIEncoderV2.",
|
||||
"description": "Elements of structs and arrays that are shorter than 32 bytes are not properly decoded from storage when encoded directly (i.e. not via a memory type) using ABIEncoderV2. This can cause corruption in the values themselves but can also overwrite other parts of the encoded data.",
|
||||
"link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/",
|
||||
"introduced": "0.4.19",
|
||||
"fixed": "0.4.26",
|
||||
"severity": "low",
|
||||
"conditions": {
|
||||
"ABIEncoderV2": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "IncorrectByteInstructionOptimization",
|
||||
"summary": "The optimizer incorrectly handles byte opcodes whose second argument is 31 or a constant expression that evaluates to 31. This can result in unexpected values.",
|
||||
"summary": "The optimizer incorrectly handles byte opcodes whose second argument is 31 or a constant expression that evaluates to 31. This can result in unexpected values.",
|
||||
"description": "The optimizer incorrectly handles byte opcodes that use the constant 31 as second argument. This can happen when performing index access on bytesNN types with a compile-time constant value (not index) of 31 or when using the byte opcode in inline assembly.",
|
||||
"link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/",
|
||||
"introduced": "0.5.5",
|
||||
"fixed": "0.5.7",
|
||||
"severity": "very low",
|
||||
@ -25,6 +71,7 @@
|
||||
"name": "DoubleShiftSizeOverflow",
|
||||
"summary": "Double bitwise shifts by large constants whose sum overflows 256 bits can result in unexpected values.",
|
||||
"description": "Nested logical shift operations whose total shift size is 2**256 or more are incorrectly optimized. This only applies to shifts by numbers of bits that are compile-time constant expressions.",
|
||||
"link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/",
|
||||
"introduced": "0.5.5",
|
||||
"fixed": "0.5.6",
|
||||
"severity": "low",
|
||||
@ -37,6 +84,7 @@
|
||||
"name": "ExpExponentCleanup",
|
||||
"summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.",
|
||||
"description": "Higher order bits in the exponent are not properly cleaned before the EXP opcode is applied if the type of the exponent expression is smaller than 256 bits and not smaller than the type of the base. In that case, the result might be larger than expected if the exponent is assumed to lie within the value range of the type. Literal numbers as exponents are unaffected as are exponents or bases of type uint256.",
|
||||
"link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/",
|
||||
"fixed": "0.4.25",
|
||||
"severity": "medium/high",
|
||||
"check": {"regex-source": "[^/]\\*\\* *[^/0-9 ]"}
|
||||
@ -45,6 +93,7 @@
|
||||
"name": "EventStructWrongData",
|
||||
"summary": "Using structs in events logged wrong data.",
|
||||
"description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.",
|
||||
"link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/",
|
||||
"introduced": "0.4.17",
|
||||
"fixed": "0.4.25",
|
||||
"severity": "very low",
|
||||
@ -54,6 +103,7 @@
|
||||
"name": "NestedArrayFunctionCallDecoder",
|
||||
"summary": "Calling functions that return multi-dimensional fixed-size arrays can result in memory corruption.",
|
||||
"description": "If Solidity code calls a function that returns a multi-dimensional fixed-size array, array elements are incorrectly interpreted as memory pointers and thus can cause memory corruption if the return values are accessed. Calling functions with multi-dimensional fixed-size arrays is unaffected as is returning fixed-size arrays from function calls. The regular expression only checks if such functions are present, not if they are called, which is required for the contract to be affected.",
|
||||
"link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/",
|
||||
"introduced": "0.1.4",
|
||||
"fixed": "0.4.22",
|
||||
"severity": "medium",
|
||||
|
@ -211,6 +211,7 @@
|
||||
},
|
||||
"0.3.0": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -231,6 +232,7 @@
|
||||
},
|
||||
"0.3.1": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -250,6 +252,7 @@
|
||||
},
|
||||
"0.3.2": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -269,6 +272,7 @@
|
||||
},
|
||||
"0.3.3": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -287,6 +291,7 @@
|
||||
},
|
||||
"0.3.4": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -305,6 +310,7 @@
|
||||
},
|
||||
"0.3.5": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -323,6 +329,7 @@
|
||||
},
|
||||
"0.3.6": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -339,6 +346,7 @@
|
||||
},
|
||||
"0.4.0": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -355,6 +363,7 @@
|
||||
},
|
||||
"0.4.1": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -371,6 +380,8 @@
|
||||
},
|
||||
"0.4.10": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -383,6 +394,8 @@
|
||||
},
|
||||
"0.4.11": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -394,6 +407,8 @@
|
||||
},
|
||||
"0.4.12": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -404,6 +419,8 @@
|
||||
},
|
||||
"0.4.13": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -414,6 +431,8 @@
|
||||
},
|
||||
"0.4.14": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -423,6 +442,8 @@
|
||||
},
|
||||
"0.4.15": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector"
|
||||
@ -431,6 +452,8 @@
|
||||
},
|
||||
"0.4.16": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector"
|
||||
@ -439,6 +462,8 @@
|
||||
},
|
||||
"0.4.17": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
@ -448,6 +473,8 @@
|
||||
},
|
||||
"0.4.18": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder"
|
||||
@ -456,7 +483,9 @@
|
||||
},
|
||||
"0.4.19": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder"
|
||||
@ -465,6 +494,7 @@
|
||||
},
|
||||
"0.4.2": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -480,7 +510,9 @@
|
||||
},
|
||||
"0.4.20": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder"
|
||||
@ -489,7 +521,9 @@
|
||||
},
|
||||
"0.4.21": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"NestedArrayFunctionCallDecoder"
|
||||
@ -498,7 +532,9 @@
|
||||
},
|
||||
"0.4.22": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData",
|
||||
"OneOfTwoConstructorsSkipped"
|
||||
@ -507,7 +543,9 @@
|
||||
},
|
||||
"0.4.23": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData"
|
||||
],
|
||||
@ -515,7 +553,9 @@
|
||||
},
|
||||
"0.4.24": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"EventStructWrongData"
|
||||
],
|
||||
@ -523,12 +563,19 @@
|
||||
},
|
||||
"0.4.25": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2PackedStorage"
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ABIEncoderV2PackedStorage_0.4.x"
|
||||
],
|
||||
"released": "2018-09-12"
|
||||
},
|
||||
"0.4.26": {
|
||||
"bugs": [],
|
||||
"released": "2019-04-29"
|
||||
},
|
||||
"0.4.3": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -543,6 +590,7 @@
|
||||
},
|
||||
"0.4.4": {
|
||||
"bugs": [
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -556,6 +604,8 @@
|
||||
},
|
||||
"0.4.5": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -570,6 +620,8 @@
|
||||
},
|
||||
"0.4.6": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -583,6 +635,8 @@
|
||||
},
|
||||
"0.4.7": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -595,6 +649,8 @@
|
||||
},
|
||||
"0.4.8": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -607,6 +663,8 @@
|
||||
},
|
||||
"0.4.9": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -619,36 +677,48 @@
|
||||
},
|
||||
"0.5.0": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2018-11-13"
|
||||
},
|
||||
"0.5.1": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2018-12-03"
|
||||
},
|
||||
"0.5.2": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2018-12-19"
|
||||
},
|
||||
"0.5.3": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2019-01-22"
|
||||
},
|
||||
"0.5.4": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage"
|
||||
],
|
||||
"released": "2019-02-12"
|
||||
},
|
||||
"0.5.5": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"IncorrectByteInstructionOptimization",
|
||||
"DoubleShiftSizeOverflow"
|
||||
@ -657,13 +727,22 @@
|
||||
},
|
||||
"0.5.6": {
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries",
|
||||
"ABIEncoderV2PackedStorage",
|
||||
"IncorrectByteInstructionOptimization"
|
||||
],
|
||||
"released": "2019-03-13"
|
||||
},
|
||||
"0.5.7": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"UninitializedFunctionPointerInConstructor",
|
||||
"IncorrectEventSignatureInLibraries"
|
||||
],
|
||||
"released": "2019-03-26"
|
||||
},
|
||||
"0.5.8": {
|
||||
"bugs": [],
|
||||
"released": "2019-04-30"
|
||||
}
|
||||
}
|
@ -71,15 +71,21 @@ Running the compiler tests
|
||||
==========================
|
||||
|
||||
The ``./scripts/tests.sh`` script executes most Solidity tests and
|
||||
runs ``aleth`` automatically if it is in the path, but does not download it,
|
||||
runs ``aleth`` automatically if it is in the path. The script does not download it,
|
||||
so you need to install it first. Please read on for the details.
|
||||
|
||||
Solidity includes different types of tests, most of them bundled into the ``soltest``
|
||||
application. Some of them require the ``aleth`` client in testing mode, others require ``libz3``.
|
||||
Solidity includes different types of tests, most of them bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest``.
|
||||
Some of them require the ``aleth`` client in testing mode, others require ``libz3``.
|
||||
|
||||
To run a basic set of tests that require neither ``aleth`` nor ``libz3``, run
|
||||
``./scripts/soltest.sh --no-ipc --no-smt``. This script runs ``./build/test/soltest``
|
||||
internally.
|
||||
``./scripts/soltest.sh --no-ipc --no-smt``.
|
||||
|
||||
``./build/test/soltest --help`` has extensive help on all of the options available.
|
||||
See especially:
|
||||
|
||||
- `show_progress (-p) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
|
||||
- `run_test (-t) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/run_test.html>`_ to run specific tests cases, and
|
||||
- `report-level (-r) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/report_level.html>`_ give a more detailed report.
|
||||
|
||||
.. note ::
|
||||
|
||||
@ -90,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.5.2/aleth-1.5.2-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-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``.
|
||||
|
||||
To run the actual tests, use: ``./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc``.
|
||||
|
||||
@ -415,4 +421,4 @@ Running Documentation Tests
|
||||
---------------------------
|
||||
|
||||
Make sure your contributions pass our documentation tests by running ``./scripts/docs.sh`` that installs dependencies
|
||||
needed for documentation and checks for any problems such as broken links or syntax issues.
|
||||
needed for documentation and checks for any problems such as broken links or syntax issues.
|
||||
|
@ -72,7 +72,6 @@ all function arguments have to be copied to memory.
|
||||
|
||||
When calling functions of other contracts, you can specify the amount of Wei or gas sent with the call with the special options ``.value()`` and ``.gas()``, respectively. Any Wei you send to the contract is added to the total balance of the contract:
|
||||
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
@ -21,6 +21,10 @@ and multi-signature wallets.
|
||||
|
||||
When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number `to indicate this fast pace of change <https://semver.org/#spec-item-4>`_.
|
||||
|
||||
.. warning::
|
||||
|
||||
Solidity recently released the 0.5.x version that introduced a lot of breaking changes. Make sure you read :doc:`the full list <050-breaking-changes>`.
|
||||
|
||||
Language Documentation
|
||||
----------------------
|
||||
|
||||
@ -63,6 +67,7 @@ version stands as a reference.
|
||||
|
||||
* `Simplified Chinese <http://solidity-cn.readthedocs.io>`_ (in progress)
|
||||
* `Spanish <https://solidity-es.readthedocs.io>`_
|
||||
* `Turkish <https://github.com/denizozzgur/Solidity_TR/blob/master/README.md>`_ (partial)
|
||||
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
|
||||
* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress)
|
||||
* `French <http://solidity-fr.readthedocs.io>`_ (in progress)
|
||||
@ -79,6 +84,7 @@ Contents
|
||||
installing-solidity.rst
|
||||
solidity-by-example.rst
|
||||
solidity-in-depth.rst
|
||||
natspec-format.rst
|
||||
security-considerations.rst
|
||||
resources.rst
|
||||
using-the-compiler.rst
|
||||
|
@ -170,7 +170,7 @@ The following are dependencies for all builds of Solidity:
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `Git`_ | Command-line tool for retrieving source code. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `z3`_ (version 5.6+, Optional) | For use with SMT checker. |
|
||||
| `z3`_ (version 4.6+, Optional) | For use with SMT checker. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `cvc4`_ (Optional) | For use with SMT checker. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
|
@ -89,12 +89,12 @@ It is activated for the Ubuntu PPA releases in most versions,
|
||||
but not for solc-js, the Docker images, Windows binaries or the
|
||||
statically-built Linux binaries.
|
||||
|
||||
If you use
|
||||
``pragma experimental SMTChecker;``, then you get additional
|
||||
safety warnings which are obtained by querying an SMT solver.
|
||||
The component does not yet support all features of the Solidity language
|
||||
and likely outputs many warnings. In case it reports unsupported
|
||||
features, the analysis may not be fully sound.
|
||||
If you use ``pragma experimental SMTChecker;``, then you get additional
|
||||
:ref:`safety warnings<formal_verification>` which are obtained by querying an
|
||||
SMT solver.
|
||||
The component does not yet support all features of the Solidity language and
|
||||
likely outputs many warnings. In case it reports unsupported features, the
|
||||
analysis may not be fully sound.
|
||||
|
||||
.. index:: source file, ! import, module
|
||||
|
||||
@ -238,6 +238,7 @@ GitHub and automatically retrieves the file over the network. You can import
|
||||
the iterable mapping as above, e.g.
|
||||
|
||||
::
|
||||
|
||||
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;
|
||||
|
||||
Remix may add other source code providers in the future.
|
||||
|
@ -65,7 +65,7 @@ explanatory purposes.
|
||||
settings:
|
||||
{
|
||||
// Required for Solidity: Sorted list of remappings
|
||||
remappings: [ ":g/dir" ],
|
||||
remappings: [ ":g=/dir" ],
|
||||
// Optional: Optimizer settings. The fields "enabled" and "runs" are deprecated
|
||||
// and are only given for backwards-compatibility.
|
||||
optimizer: {
|
||||
@ -84,6 +84,10 @@ explanatory purposes.
|
||||
yulDetails: {}
|
||||
}
|
||||
},
|
||||
metadata: {
|
||||
// Reflects the setting used in the input json, defaults to false
|
||||
useLiteralContent: true
|
||||
}
|
||||
// Required for Solidity: File and name of the contract or library this
|
||||
// metadata is created for.
|
||||
compilationTarget: {
|
||||
@ -121,9 +125,10 @@ 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
|
||||
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the beginning of that
|
||||
`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 thus adds the following
|
||||
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
|
||||
@ -131,6 +136,14 @@ to the end of the deployed bytecode::
|
||||
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.
|
||||
|
||||
.. 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``.
|
||||
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
|
||||
@ -154,7 +167,7 @@ Furthermore, the wallet can use the NatSpec user documentation to display a conf
|
||||
whenever they interact with the contract, together with requesting
|
||||
authorization for the transaction signature.
|
||||
|
||||
Additional information about Ethereum Natural Specification (NatSpec) can be found `here <https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format>`_.
|
||||
For additional information, read :doc:`Ethereum Natural Language Specification (NatSpec) format <natspec-format>`.
|
||||
|
||||
Usage for Source Code Verification
|
||||
==================================
|
||||
@ -167,3 +180,7 @@ bytecode is compared to the data of the creation transaction or ``CREATE`` opcod
|
||||
This automatically verifies the metadata since its hash is part of the bytecode.
|
||||
Excess data corresponds to the constructor input data, which should be decoded
|
||||
according to the interface and presented to the user.
|
||||
|
||||
In the repository [source-verify](https://github.com/ethereum/source-verify)
|
||||
([npm package](https://www.npmjs.com/package/source-verify)) you can see
|
||||
example code that shows how to use this feature.
|
||||
|
@ -19,6 +19,8 @@ For contracts that use inheritance, the ordering of state variables is determine
|
||||
C3-linearized order of contracts starting with the most base-ward contract. If allowed
|
||||
by the above rules, state variables from different contracts do share the same storage slot.
|
||||
|
||||
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
|
||||
|
||||
.. warning::
|
||||
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher.
|
||||
This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller
|
||||
@ -36,7 +38,7 @@ by the above rules, state variables from different contracts do share the same s
|
||||
``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the
|
||||
latter will take up three.
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
The layout of state variables in storage is considered to be part of the external interface
|
||||
of Solidity due to the fact that storage pointers can be passed to libraries. This means that
|
||||
any change to the rules outlined in this section is considered a breaking change
|
||||
@ -44,8 +46,6 @@ by the above rules, state variables from different contracts do share the same s
|
||||
being executed.
|
||||
|
||||
|
||||
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
|
||||
|
||||
Mappings and Dynamic Arrays
|
||||
===========================
|
||||
|
||||
|
199
docs/natspec-format.rst
Normal file
199
docs/natspec-format.rst
Normal file
@ -0,0 +1,199 @@
|
||||
.. _natspec:
|
||||
|
||||
##############
|
||||
NatSpec Format
|
||||
##############
|
||||
|
||||
Solidity contracts can use a special form of comments to provide rich
|
||||
documentation for functions, return variables and more. This special form is
|
||||
named the Ethereum Natural Language Specification Format (NatSpec).
|
||||
|
||||
This documentation is segmented into developer-focused messages and end-user-facing
|
||||
messages. These messages may be shown to the end user (the human) at the
|
||||
time that they will interact with the contract (i.e. sign a transaction).
|
||||
|
||||
It is recommended that Solidity contracts are fully annontated using NatSpec for
|
||||
all public interfaces (everything in the ABI).
|
||||
|
||||
NatSpec includes the formatting for comments that the smart contract author will
|
||||
use, and which are understood by the Solidity compiler. Also detailed below is
|
||||
output of the Solidity compiler, which extracts these comments into a machine-readable
|
||||
format.
|
||||
|
||||
.. _header-doc-example:
|
||||
|
||||
Documentation Example
|
||||
=====================
|
||||
|
||||
Documentation is inserted above each ``class``, ``interface`` and
|
||||
``function`` using the doxygen notation format.
|
||||
|
||||
- For Solidity you may choose ``///`` for single or multi-line
|
||||
comments, or ``/**`` and ending with ``*/``.
|
||||
|
||||
- For Vyper, use ``"""`` indented to the inner contents with bare
|
||||
comments. See `Vyper
|
||||
documentation <https://vyper.readthedocs.io/en/latest/structure-of-a-contract.html#natspec-metadata>`__.
|
||||
|
||||
The following example shows a contract and a function using all available tags.
|
||||
Note: NatSpec currently does NOT apply to public state variables (see
|
||||
`solidity#3418 <https://github.com/ethereum/solidity/issues/3418>`__),
|
||||
even if they are declared public and therefore do affect the ABI. Note:
|
||||
The Solidity compiler only interprets tags if they are external or
|
||||
public. You are welcome to use similar comments for your internal and
|
||||
private functions, but those will not be parsed.
|
||||
|
||||
.. code:: solidity
|
||||
|
||||
pragma solidity ^0.5.6;
|
||||
|
||||
/// @title A simulator for trees
|
||||
/// @author Larry A. Gardner
|
||||
/// @notice You can use this contract for only the most basic simulation
|
||||
/// @dev All function calls are currently implemented without side effects
|
||||
contract Tree {
|
||||
/// @author Mary A. Botanist
|
||||
/// @notice Calculate tree age in years, rounded up, for live trees
|
||||
/// @dev The Alexandr N. Tetearing algorithm could increase precision
|
||||
/// @param rings The number of rings from dendrochronological sample
|
||||
/// @return age in years, rounded up for partial years
|
||||
function age(uint256 rings) external pure returns (uint256) {
|
||||
return rings + 1;
|
||||
}
|
||||
}
|
||||
|
||||
.. _header-tags:
|
||||
|
||||
Tags
|
||||
====
|
||||
|
||||
All tags are optional. The following table explains the purpose of each
|
||||
NatSpec tag and where it may be used. As a special case, if no tags are
|
||||
used then the Solidity compiler will interpret a `///` or `/**` comment
|
||||
in the same way as if it were tagged with `@notice`.
|
||||
|
||||
=========== =============================================================================== =============================
|
||||
Tag Context
|
||||
=========== =============================================================================== =============================
|
||||
``@title`` A title that should describe the contract/interface contract, interface
|
||||
``@author`` The name of the author contract, interface, function
|
||||
``@notice`` Explain to an end user what this does contract, interface, function
|
||||
``@dev`` Explain to a developer any extra details contract, interface, function
|
||||
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function
|
||||
``@return`` Documents the return type of a contract's function function
|
||||
=========== =============================================================================== =============================
|
||||
|
||||
If your function returns multiple values, like ``(int quotient, int remainder)``
|
||||
then use multiple ``@return`` statements in the same format as the
|
||||
``@param`` statements.
|
||||
|
||||
.. _header-dynamic:
|
||||
|
||||
Dynamic expressions
|
||||
-------------------
|
||||
|
||||
The Solidity compiler will pass through NatSpec documentation from your Solidity
|
||||
source code to the JSON output as described in this guide. The consumer of this
|
||||
JSON output, for example the end-user client software, may present this to the end-user directly or it may apply some pre-processing.
|
||||
|
||||
For example, some client software will render:
|
||||
|
||||
.. code:: solidity
|
||||
|
||||
/// @notice This function will multiply `a` by 7
|
||||
|
||||
to the end-user as:
|
||||
|
||||
.. code:: text
|
||||
|
||||
This function will multiply 10 by 7
|
||||
|
||||
if a function is being called and the input ``a`` is assigned a value of 7.
|
||||
|
||||
Specifying these dynamic expressions is outside the scope of the Solidity
|
||||
documentation and you may read more at
|
||||
`the radspec project <https://github.com/aragon/radspec>`__.
|
||||
|
||||
.. _header-inheritance:
|
||||
|
||||
Inheritance Notes
|
||||
-----------------
|
||||
|
||||
Currently it is undefined whether a contract with a function having no
|
||||
NatSpec will inherit the NatSpec of a parent contract/interface for that
|
||||
same function.
|
||||
|
||||
.. _header-output:
|
||||
|
||||
Documentation Output
|
||||
====================
|
||||
|
||||
When parsed by the compiler, documentation such as the one from the
|
||||
above example will produce two different JSON files. One is meant to be
|
||||
consumed by the end user as a notice when a function is executed and the
|
||||
other to be used by the developer.
|
||||
|
||||
If the above contract is saved as ``ex1.sol`` then you can generate the
|
||||
documentation using:
|
||||
|
||||
.. code::
|
||||
|
||||
solc --userdoc --devdoc ex1.sol
|
||||
|
||||
And the output is below.
|
||||
|
||||
.. _header-user-doc:
|
||||
|
||||
User Documentation
|
||||
------------------
|
||||
|
||||
The above documentation will produce the following user documentation
|
||||
JSON file as output:
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
"methods" :
|
||||
{
|
||||
"age(uint256)" :
|
||||
{
|
||||
"notice" : "Calculate tree age in years, rounded up, for live trees"
|
||||
}
|
||||
},
|
||||
"notice" : "You can use this contract for only the most basic simulation"
|
||||
}
|
||||
|
||||
Note that the key by which to find the methods is the function's
|
||||
canonical signature as defined in the `Contract
|
||||
ABI <Ethereum-Contract-ABI#signature>`__ and not simply the function's
|
||||
name.
|
||||
|
||||
.. _header-developer-doc:
|
||||
|
||||
Developer Documentation
|
||||
-----------------------
|
||||
|
||||
Apart from the user documentation file, a developer documentation JSON
|
||||
file should also be produced and should look like this:
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
"author" : "Larry A. Gardner",
|
||||
"details" : "All function calls are currently implemented without side effects",
|
||||
"methods" :
|
||||
{
|
||||
"age(uint256)" :
|
||||
{
|
||||
"author" : "Mary A. Botanist",
|
||||
"details" : "The Alexandr N. Tetearing algorithm could increase precision",
|
||||
"params" :
|
||||
{
|
||||
"rings" : "The number of rings from dendrochronological sample"
|
||||
},
|
||||
"return" : "age in years, rounded up for partial years"
|
||||
}
|
||||
},
|
||||
"title" : "A simulator for trees"
|
||||
}
|
||||
|
@ -149,6 +149,9 @@ Sending and Receiving Ether
|
||||
into the sending contract or other state changes you might not have thought of.
|
||||
So it allows for great flexibility for honest users but also for malicious actors.
|
||||
|
||||
- Use the most precise units to represent the wei amount as possible, as you lose
|
||||
any that is rounded due to a lack of precision.
|
||||
|
||||
- If you want to send Ether using ``address.transfer``, there are certain details to be aware of:
|
||||
|
||||
1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract.
|
||||
@ -331,6 +334,8 @@ The more people examine a piece of code, the more issues are found.
|
||||
Asking people to review your code also helps as a cross-check to find out whether your code
|
||||
is easy to understand - a very important criterion for good smart contracts.
|
||||
|
||||
.. _formal_verification:
|
||||
|
||||
*******************
|
||||
Formal Verification
|
||||
*******************
|
||||
@ -344,3 +349,185 @@ Note that formal verification itself can only help you understand the
|
||||
difference between what you did (the specification) and how you did it
|
||||
(the actual implementation). You still need to check whether the specification
|
||||
is what you wanted and that you did not miss any unintended effects of it.
|
||||
|
||||
Solidity implements a formal verification approach based on SMT solving. The
|
||||
SMTChecker module automatically tries to prove that the code satisfies the
|
||||
specification given by ``require/assert`` statements. That is, it considers
|
||||
``require`` statements as assumptions and tries to prove that the conditions
|
||||
inside ``assert`` statements are always true. If an assertion failure is
|
||||
found, a counterexample is given to the user, showing how the assertion can be
|
||||
violated.
|
||||
|
||||
The SMTChecker also checks automatically for arithmetic underflow/overflow,
|
||||
trivial conditions and unreachable code.
|
||||
It is currently an experimental feature, therefore in order to use it you need
|
||||
to enable it via :ref:`a pragma directive<smt_checker>`.
|
||||
|
||||
The SMTChecker traverses the Solidity AST creating and collecting program constraints.
|
||||
When it encounters a verification target, an SMT solver is invoked to determine the outcome.
|
||||
If a check fails, the SMTChecker provides specific input values that lead to the failure.
|
||||
|
||||
For more details on how the SMT encoding works internally, see the paper
|
||||
`SMT-based Verification of Solidity Smart Contracts <https://github.com/leonardoalt/text/blob/master/solidity_isola_2018/main.pdf>`_.
|
||||
|
||||
Abstraction and False Positives
|
||||
===============================
|
||||
|
||||
The SMTChecker implements abstractions in an incomplete and sound way: If a bug
|
||||
is reported, it might be a false positive introduced by abstractions (due to
|
||||
erasing knowledge or using a non-precise type). If it determines that a
|
||||
verification target is safe, it is indeed safe, that is, there are no false
|
||||
negatives (unless there is a bug in the SMTChecker).
|
||||
|
||||
The SMT encoding tries to be as precise as possible, mapping Solidity types
|
||||
and expressions to their closest `SMT-LIB <http://smtlib.cs.uiowa.edu/>`_
|
||||
representation, as shown in the table below.
|
||||
|
||||
+-----------------------+--------------+-----------------------------+
|
||||
|Solidity type |SMT sort |Theories (quantifier-free) |
|
||||
+=======================+==============+=============================+
|
||||
|Boolean |Bool |Bool |
|
||||
+-----------------------+--------------+-----------------------------+
|
||||
|intN, uintN, address, |Integer |LIA, NIA |
|
||||
|bytesN, enum | | |
|
||||
+-----------------------+--------------+-----------------------------+
|
||||
|array, mapping |Array |Arrays |
|
||||
+-----------------------+--------------+-----------------------------+
|
||||
|other types |Integer |LIA |
|
||||
+-----------------------+--------------+-----------------------------+
|
||||
|
||||
Types that are not yet supported are abstracted by a single 256-bit unsigned integer,
|
||||
where their unsupported operations are ignored.
|
||||
|
||||
Function calls to the same contract (or base contracts) are inlined when
|
||||
possible, that is, when their implementation is available.
|
||||
Calls to functions in other contracts are not inlined even if their code is
|
||||
available, since we cannot guarantee that the actual deployed code is the same.
|
||||
Complex pure functions are abstracted by an uninterpreted function (UF) over
|
||||
the arguments.
|
||||
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|Functions |SMT behavior |
|
||||
+===================================+======================================+
|
||||
|``assert`` |Verification target |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|``require`` |Assumption |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|internal |Inline function call |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|external |Inline function call |
|
||||
| |Erase knowledge about state variables |
|
||||
| |and local storage references |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|``gasleft``, ``blockhash``, |Abstracted with UF |
|
||||
|``keccak256``, ``ecrecover`` | |
|
||||
|``ripemd160``, ``addmod``, | |
|
||||
|``mulmod`` | |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|pure functions without |Abstracted with UF |
|
||||
|implementation (external or | |
|
||||
|complex) | |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|external functions without |Unsupported |
|
||||
|implementation | |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|others |Currently unsupported |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|
||||
Using abstraction means loss of precise knowledge, but in many cases it does
|
||||
not mean loss of proving power.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.5.0;
|
||||
pragma experimental SMTChecker;
|
||||
|
||||
contract Recover
|
||||
{
|
||||
function f(
|
||||
bytes32 hash,
|
||||
uint8 _v1, uint8 _v2,
|
||||
bytes32 _r1, bytes32 _r2,
|
||||
bytes32 _s1, bytes32 _s2
|
||||
) public pure returns (address) {
|
||||
address a1 = ecrecover(hash, _v1, _r1, _s1);
|
||||
require(_v1 == _v2);
|
||||
require(_r1 == _r2);
|
||||
require(_s1 == _s2);
|
||||
address a2 = ecrecover(hash, _v2, _r2, _s2);
|
||||
assert(a1 == a2);
|
||||
return a1;
|
||||
}
|
||||
}
|
||||
|
||||
In the example above, the SMTChecker is not expressive enough to actually
|
||||
compute ``ecrecover``, but by modelling the function calls as uninterpreted
|
||||
functions we know that the return value is the same when called on equivalent
|
||||
parameters. This is enough to prove that the assertion above is always true.
|
||||
|
||||
Abstracting a function call with an UF can be done for functions known to be
|
||||
deterministic, and can be easily done for pure functions. It is however
|
||||
difficult to do this with general external functions, since they might depend
|
||||
on state variables.
|
||||
|
||||
External function calls also imply that any current knowledge that the
|
||||
SMTChecker might have regarding mutable state variables needs to be erased to
|
||||
guarantee no false negatives, since the called external function might direct
|
||||
or indirectly call a function in the analyzed contract that changes state
|
||||
variables.
|
||||
|
||||
Reference Types and Aliasing
|
||||
=============================
|
||||
|
||||
Solidity implements aliasing for reference types with the same :ref:`data
|
||||
location<data-location>`.
|
||||
That means one variable may be modified through a reference to the same data
|
||||
area.
|
||||
The SMTChecker does not keep track of which references refer to the same data.
|
||||
This implies that whenever a local reference or state variable of reference
|
||||
type is assigned, all knowledge regarding variables of the same type and data
|
||||
location is erased.
|
||||
If the type is nested, the knowledge removal also includes all the prefix base
|
||||
types.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.5.0;
|
||||
pragma experimental SMTChecker;
|
||||
// This will not compile
|
||||
contract Aliasing
|
||||
{
|
||||
uint[] array;
|
||||
function f(
|
||||
uint[] memory a,
|
||||
uint[] memory b,
|
||||
uint[][] memory c,
|
||||
uint[] storage d
|
||||
) internal view {
|
||||
require(array[0] == 42);
|
||||
require(a[0] == 2);
|
||||
require(c[0][0] == 2);
|
||||
require(d[0] == 2);
|
||||
b[0] = 1;
|
||||
// Erasing knowledge about memory references should not
|
||||
// erase knowledge about state variables.
|
||||
assert(array[0] == 42);
|
||||
// Fails because `a == b` is possible.
|
||||
assert(a[0] == 2);
|
||||
// Fails because `c[i] == b` is possible.
|
||||
assert(c[0][0] == 2);
|
||||
assert(d[0] == 2);
|
||||
assert(b[0] == 1);
|
||||
}
|
||||
}
|
||||
|
||||
After the assignment to ``b[0]``, we need to clear knowledge about ``a`` since
|
||||
it has the same type (``uint[]``) and data location (memory). We also need to
|
||||
clear knowledge about ``c``, since its base type is also a ``uint[]`` located
|
||||
in memory. This implies that some ``c[i]`` could refer to the same data as
|
||||
``b`` or ``a``.
|
||||
|
||||
Notice that we do not clear knowledge about ``array`` and ``d`` because they
|
||||
are located in storage, even though they also have type ``uint[]``. However,
|
||||
if ``d`` was assigned, we would need to clear knowledge about ``array`` and
|
||||
vice-versa.
|
||||
|
@ -1088,8 +1088,6 @@ Avoiding Naming Collisions
|
||||
This convention is suggested when the desired name collides with that of a
|
||||
built-in or otherwise reserved name.
|
||||
|
||||
.. _natspec:
|
||||
|
||||
*******
|
||||
NatSpec
|
||||
*******
|
||||
@ -1126,25 +1124,6 @@ added looks like the one below::
|
||||
}
|
||||
}
|
||||
|
||||
Natspec uses doxygen style tags with some special meaning.
|
||||
If no tag is used, then the comment applies to ``@notice``.
|
||||
The ``@notice`` tag is the main NatSpec tag and its audience is
|
||||
users of the contract who have never seen the source code, so it should make
|
||||
as little assumptions about the inner details as possible.
|
||||
All tags are optional.
|
||||
It is recommended that Solidity contracts are fully annontated using `NatSpec <natspec>`_ for all public interfaces (everything in the ABI).
|
||||
|
||||
+-------------+-------------------------------------------+-------------------------------+
|
||||
| Tag | Description | Context |
|
||||
+=============+===========================================+===============================+
|
||||
| ``@title`` | A title that describes the contract | contract, interface |
|
||||
+-------------+-------------------------------------------+-------------------------------+
|
||||
| ``@author`` | The name of the author | contract, interface, function |
|
||||
+-------------+-------------------------------------------+-------------------------------+
|
||||
| ``@notice`` | Explanation of functionality | contract, interface, function |
|
||||
+-------------+-------------------------------------------+-------------------------------+
|
||||
| ``@dev`` | Any extra details | contract, interface, function |
|
||||
+-------------+-------------------------------------------+-------------------------------+
|
||||
| ``@param`` | Parameter type followed by parameter name | function |
|
||||
+-------------+-------------------------------------------+-------------------------------+
|
||||
| ``@return`` | The return value of a contract's function | function |
|
||||
+-------------+-------------------------------------------+-------------------------------+
|
||||
Please see the sectian about `NatSpec <natspec>`_ for a detailed explanation.
|
@ -188,8 +188,12 @@ Mathematical and Cryptographic Functions
|
||||
|
||||
.. warning::
|
||||
|
||||
If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without requiring
|
||||
knowledge of the corresponding private key. This is usually not a problem unless you require signatures to be unique or
|
||||
If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without
|
||||
requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed
|
||||
for _transaction_ signatures (see `EIP-2 <http://eips.ethereum.org/EIPS/eip-2#specification>`_), but
|
||||
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.
|
||||
|
||||
.. note::
|
||||
|
@ -187,7 +187,7 @@ Input Description
|
||||
"settings":
|
||||
{
|
||||
// Optional: Sorted list of remappings
|
||||
"remappings": [ ":g/dir" ],
|
||||
"remappings": [ ":g=/dir" ],
|
||||
// Optional: Optimizer settings
|
||||
"optimizer": {
|
||||
// disabled by default
|
||||
|
30
docs/yul.rst
30
docs/yul.rst
@ -6,16 +6,14 @@ Yul
|
||||
|
||||
.. index:: ! assembly, ! asm, ! evmasm, ! yul, julia, iulia
|
||||
|
||||
Yul (previously also called JULIA or IULIA) is an intermediate language that can
|
||||
compile to various different backends
|
||||
(EVM 1.0, EVM 1.5 and eWASM are planned).
|
||||
Because of that, it is designed to be a usable common denominator of all three
|
||||
platforms.
|
||||
It can already be used for "inline assembly" inside Solidity and
|
||||
future versions of the Solidity compiler will even use Yul as intermediate
|
||||
language. It should also be easy to build high-level optimizer stages for Yul.
|
||||
Yul (previously also called JULIA or IULIA) is an intermediate language that can be
|
||||
compiled to bytecode for different backends.
|
||||
|
||||
In its flavour of inline-assembly, Yul can be used as a language setting
|
||||
Support for EVM 1.0, EVM 1.5 and eWASM is planned, and it is designed to be a usable common denominator of all three
|
||||
platforms. It can already be used for "inline assembly" inside Solidity and future versions of the Solidity compiler
|
||||
will use Yul as an intermediate language. Yul is a good target for high-level optimisation stages that can benefit all target platforms equally.
|
||||
|
||||
With the "inline assembly" flavour, Yul can be used as a language setting
|
||||
for the :ref:`standard-json interface <compiler-api>`:
|
||||
|
||||
::
|
||||
@ -29,15 +27,12 @@ for the :ref:`standard-json interface <compiler-api>`:
|
||||
}
|
||||
}
|
||||
|
||||
Furthermore, the commandline interface can be switched to Yul mode
|
||||
using ``solc --strict-assembly``.
|
||||
And on the command line interface with the ``--strict-assembly`` parameter.
|
||||
|
||||
.. note::
|
||||
.. warning::
|
||||
|
||||
Note that the flavour used for "inline assembly" does not have types
|
||||
(everything is ``u256``) and the built-in functions are identical
|
||||
to the EVM opcodes. Please resort to the inline assembly documentation
|
||||
for details.
|
||||
Yul is in active development and bytecode generation is fully implemented only for untyped Yul (everything is ``u256``)
|
||||
and with EVM 1.0 as target, :ref:`EVM opcodes <opcodes>` are used as built-in functions.
|
||||
|
||||
The core components of Yul are functions, blocks, variables, literals,
|
||||
for-loops, if-statements, switch-statements, expressions and assignments to variables.
|
||||
@ -128,7 +123,7 @@ Grammar::
|
||||
'break' | 'continue'
|
||||
FunctionCall =
|
||||
Identifier '(' ( Expression ( ',' Expression )* )? ')'
|
||||
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
|
||||
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]*
|
||||
IdentifierList = Identifier ( ',' Identifier)*
|
||||
TypeName = Identifier | BuiltinTypeName
|
||||
BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' )
|
||||
@ -172,6 +167,7 @@ The ``continue`` and ``break`` statements can only be used inside loop bodies
|
||||
and have to be in the same function as the loop (or both have to be at the
|
||||
top level).
|
||||
The condition part of the for-loop has to evaluate to exactly one value.
|
||||
Functions cannot be defined inside for loop init blocks.
|
||||
|
||||
Literals cannot be larger than the their type. The largest type defined is 256-bit wide.
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Exceptions.h"
|
||||
#include <libdevcore/Exceptions.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
@ -19,7 +19,11 @@
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "CommonIO.h"
|
||||
#include <libdevcore/CommonIO.h>
|
||||
#include <libdevcore/Assertions.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
@ -29,8 +33,6 @@
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#endif
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "Assertions.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
|
@ -23,10 +23,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include "Common.h"
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
@ -19,9 +19,9 @@
|
||||
* @date 2018
|
||||
*/
|
||||
|
||||
#include "JSON.h"
|
||||
#include <libdevcore/JSON.h>
|
||||
|
||||
#include "CommonIO.h"
|
||||
#include <libdevcore/CommonIO.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
|
@ -21,7 +21,7 @@
|
||||
* String routines
|
||||
*/
|
||||
|
||||
#include "StringUtils.h"
|
||||
#include <libdevcore/StringUtils.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
@ -21,12 +21,10 @@
|
||||
* UTF-8 related helpers
|
||||
*/
|
||||
|
||||
#include "UTF8.h"
|
||||
|
||||
#include <libdevcore/UTF8.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@ -77,6 +75,8 @@ bool isWellFormed(unsigned char byte1, unsigned char byte2)
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool validateUTF8(unsigned char const* _input, size_t _length, size_t& _invalidPosition)
|
||||
{
|
||||
bool valid = true;
|
||||
@ -133,8 +133,6 @@ bool validateUTF8(unsigned char const* _input, size_t _length, size_t& _invalidP
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
|
||||
{
|
||||
return validateUTF8(reinterpret_cast<unsigned char const*>(_input.c_str()), _input.length(), _invalidPosition);
|
||||
|
@ -19,7 +19,7 @@
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "Assembly.h"
|
||||
#include <libevmasm/Assembly.h>
|
||||
|
||||
#include <libevmasm/CommonSubexpressionEliminator.h>
|
||||
#include <libevmasm/ControlFlowGraph.h>
|
||||
|
@ -68,10 +68,10 @@ public:
|
||||
void appendProgramSize() { append(AssemblyItem(PushProgramSize)); }
|
||||
void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); }
|
||||
|
||||
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(solidity::Instruction::JUMP); return ret; }
|
||||
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(solidity::Instruction::JUMPI); return ret; }
|
||||
AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMP); return ret; }
|
||||
AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMPI); return ret; }
|
||||
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
|
||||
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
|
||||
AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; }
|
||||
AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMPI); return ret; }
|
||||
|
||||
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
|
||||
/// on the stack. @returns the pushsub assembly item.
|
||||
|
@ -231,7 +231,7 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item)
|
||||
{
|
||||
case Operation:
|
||||
_out << " " << instructionInfo(_item.instruction()).name;
|
||||
if (_item.instruction() == solidity::Instruction::JUMP || _item.instruction() == solidity::Instruction::JUMPI)
|
||||
if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI)
|
||||
_out << "\t" << _item.getJumpTypeAsString();
|
||||
break;
|
||||
case Push:
|
||||
|
@ -21,14 +21,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/Exceptions.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <libdevcore/Assertions.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include "Exceptions.h"
|
||||
using namespace dev::solidity;
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
@ -59,7 +58,7 @@ public:
|
||||
|
||||
AssemblyItem(u256 _push, langutil::SourceLocation _location = langutil::SourceLocation()):
|
||||
AssemblyItem(Push, std::move(_push), std::move(_location)) { }
|
||||
AssemblyItem(solidity::Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()):
|
||||
AssemblyItem(Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()):
|
||||
m_type(Operation),
|
||||
m_instruction(_i),
|
||||
m_location(std::move(_location))
|
||||
|
@ -19,17 +19,18 @@
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "./Instruction.h"
|
||||
#include <libevmasm/Instruction.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <libdevcore/CommonIO.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
using namespace dev::eth;
|
||||
|
||||
std::map<std::string, Instruction> const dev::solidity::c_instructions =
|
||||
std::map<std::string, Instruction> const dev::eth::c_instructions =
|
||||
{
|
||||
{ "STOP", Instruction::STOP },
|
||||
{ "ADD", Instruction::ADD },
|
||||
@ -317,7 +318,7 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
|
||||
{ Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Special } }
|
||||
};
|
||||
|
||||
void dev::solidity::eachInstruction(
|
||||
void dev::eth::eachInstruction(
|
||||
bytes const& _mem,
|
||||
function<void(Instruction,u256 const&)> const& _onInstruction
|
||||
)
|
||||
@ -346,7 +347,7 @@ void dev::solidity::eachInstruction(
|
||||
}
|
||||
}
|
||||
|
||||
string dev::solidity::disassemble(bytes const& _mem)
|
||||
string dev::eth::disassemble(bytes const& _mem)
|
||||
{
|
||||
stringstream ret;
|
||||
eachInstruction(_mem, [&](Instruction _instr, u256 const& _data) {
|
||||
@ -363,7 +364,7 @@ string dev::solidity::disassemble(bytes const& _mem)
|
||||
return ret.str();
|
||||
}
|
||||
|
||||
InstructionInfo dev::solidity::instructionInfo(Instruction _inst)
|
||||
InstructionInfo dev::eth::instructionInfo(Instruction _inst)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -375,7 +376,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst)
|
||||
}
|
||||
}
|
||||
|
||||
bool dev::solidity::isValidInstruction(Instruction _inst)
|
||||
bool dev::eth::isValidInstruction(Instruction _inst)
|
||||
{
|
||||
return !!c_instructionInfo.count(_inst);
|
||||
}
|
||||
|
@ -21,14 +21,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <libevmasm/Exceptions.h>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <libdevcore/Assertions.h>
|
||||
#include "Exceptions.h"
|
||||
#include <functional>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
namespace eth
|
||||
{
|
||||
|
||||
DEV_SIMPLE_EXCEPTION(InvalidDeposit);
|
||||
|
@ -19,7 +19,7 @@
|
||||
* Removes unused JUMPDESTs.
|
||||
*/
|
||||
|
||||
#include "JumpdestRemover.h"
|
||||
#include <libevmasm/JumpdestRemover.h>
|
||||
|
||||
#include <libevmasm/AssemblyItem.h>
|
||||
|
||||
|
@ -21,10 +21,11 @@
|
||||
* Contains knowledge about the state of the virtual machine at a specific instruction.
|
||||
*/
|
||||
|
||||
#include "KnownState.h"
|
||||
#include <functional>
|
||||
#include <libdevcore/Keccak256.h>
|
||||
#include <libevmasm/KnownState.h>
|
||||
#include <libevmasm/AssemblyItem.h>
|
||||
#include <libdevcore/Keccak256.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
|
@ -19,7 +19,7 @@
|
||||
* @date 2015
|
||||
*/
|
||||
|
||||
#include "PathGasMeter.h"
|
||||
#include <libevmasm/PathGasMeter.h>
|
||||
#include <libevmasm/KnownState.h>
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
* Performs local optimising code changes to assembly.
|
||||
*/
|
||||
|
||||
#include "PeepholeOptimiser.h"
|
||||
#include <libevmasm/PeepholeOptimiser.h>
|
||||
|
||||
#include <libevmasm/AssemblyItem.h>
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
|
@ -21,19 +21,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <boost/multiprecision/detail/min_max.hpp>
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/SimplificationRule.h>
|
||||
|
||||
#include <libdevcore/CommonData.h>
|
||||
|
||||
#include <boost/multiprecision/detail/min_max.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
namespace eth
|
||||
{
|
||||
|
||||
template <class S> S divWorkaround(S const& _a, S const& _b)
|
||||
@ -215,7 +216,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4(
|
||||
|
||||
template <class Pattern>
|
||||
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
Pattern,
|
||||
Pattern A,
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern X,
|
||||
@ -235,6 +236,22 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
});
|
||||
}
|
||||
|
||||
// Replace SHL >=256, X with 0
|
||||
rules.push_back({
|
||||
{Instruction::SHL, {A, X}},
|
||||
[=]() -> Pattern { return u256(0); },
|
||||
true,
|
||||
[=]() { return A.d() >= 256; }
|
||||
});
|
||||
|
||||
// Replace SHR >=256, X with 0
|
||||
rules.push_back({
|
||||
{Instruction::SHR, {A, X}},
|
||||
[=]() -> Pattern { return u256(0); },
|
||||
true,
|
||||
[=]() { return A.d() >= 256; }
|
||||
});
|
||||
|
||||
for (auto const& op: std::vector<Instruction>{
|
||||
Instruction::ADDRESS,
|
||||
Instruction::CALLER,
|
||||
@ -374,6 +391,30 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
false
|
||||
});
|
||||
|
||||
|
||||
std::function<bool()> feasibilityFunction = [=]() {
|
||||
if (B.d() > 256)
|
||||
return false;
|
||||
unsigned bAsUint = static_cast<unsigned>(B.d());
|
||||
return (A.d() & (u256(-1) >> bAsUint)) == (u256(-1) >> bAsUint);
|
||||
};
|
||||
|
||||
rules.push_back({
|
||||
// AND(A, SHR(B, X)) -> A & ((2^256-1) >> B) == ((2^256-1) >> B)
|
||||
{Instruction::AND, {A, {Instruction::SHR, {B, X}}}},
|
||||
[=]() -> Pattern { return {Instruction::SHR, {B, X}}; },
|
||||
false,
|
||||
feasibilityFunction
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
// AND(SHR(B, X), A) -> ((2^256-1) >> B) & A == ((2^256-1) >> B)
|
||||
{Instruction::AND, {{Instruction::SHR, {B, X}}, A}},
|
||||
[=]() -> Pattern { return {Instruction::SHR, {B, X}}; },
|
||||
false,
|
||||
feasibilityFunction
|
||||
});
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
|
@ -96,14 +96,14 @@ bool SemanticInformation::isDupInstruction(AssemblyItem const& _item)
|
||||
{
|
||||
if (_item.type() != Operation)
|
||||
return false;
|
||||
return solidity::isDupInstruction(_item.instruction());
|
||||
return dev::eth::isDupInstruction(_item.instruction());
|
||||
}
|
||||
|
||||
bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item)
|
||||
{
|
||||
if (_item.type() != Operation)
|
||||
return false;
|
||||
return solidity::isSwapInstruction(_item.instruction());
|
||||
return dev::eth::isSwapInstruction(_item.instruction());
|
||||
}
|
||||
|
||||
bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item)
|
||||
@ -132,6 +132,22 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
|
||||
}
|
||||
}
|
||||
|
||||
bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item)
|
||||
{
|
||||
if (_item.type() != Operation)
|
||||
return false;
|
||||
switch (_item.instruction())
|
||||
{
|
||||
case Instruction::RETURN:
|
||||
case Instruction::SELFDESTRUCT:
|
||||
case Instruction::STOP:
|
||||
case Instruction::INVALID:
|
||||
case Instruction::REVERT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
||||
{
|
||||
|
@ -47,19 +47,20 @@ struct SemanticInformation
|
||||
static bool isSwapInstruction(AssemblyItem const& _item);
|
||||
static bool isJumpInstruction(AssemblyItem const& _item);
|
||||
static bool altersControlFlow(AssemblyItem const& _item);
|
||||
static bool terminatesControlFlow(AssemblyItem const& _item);
|
||||
/// @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);
|
||||
/// @returns true if the instruction can be moved or copied (together with its arguments)
|
||||
/// 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(solidity::Instruction _instruction);
|
||||
static bool movable(Instruction _instruction);
|
||||
/// @returns true if the given instruction modifies memory.
|
||||
static bool invalidatesMemory(solidity::Instruction _instruction);
|
||||
static bool invalidatesMemory(Instruction _instruction);
|
||||
/// @returns true if the given instruction modifies storage (even indirectly).
|
||||
static bool invalidatesStorage(solidity::Instruction _instruction);
|
||||
static bool invalidInPureFunctions(solidity::Instruction _instruction);
|
||||
static bool invalidInViewFunctions(solidity::Instruction _instruction);
|
||||
static bool invalidatesStorage(Instruction _instruction);
|
||||
static bool invalidInPureFunctions(Instruction _instruction);
|
||||
static bool invalidInViewFunctions(Instruction _instruction);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
namespace eth
|
||||
{
|
||||
|
||||
/**
|
||||
|
@ -12,6 +12,8 @@ set(sources
|
||||
ParserBase.h
|
||||
Scanner.cpp
|
||||
Scanner.h
|
||||
SemVerHandler.cpp
|
||||
SemVerHandler.h
|
||||
SourceLocation.h
|
||||
SourceReferenceExtractor.cpp
|
||||
SourceReferenceExtractor.h
|
||||
|
@ -53,16 +53,15 @@
|
||||
#include <liblangutil/Common.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
using namespace langutil;
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
|
||||
std::string to_string(ScannerError _errorCode)
|
||||
string langutil::to_string(ScannerError _errorCode)
|
||||
{
|
||||
switch (_errorCode)
|
||||
{
|
||||
@ -83,15 +82,19 @@ std::string to_string(ScannerError _errorCode)
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ScannerError _errorCode)
|
||||
|
||||
ostream& langutil::operator<<(ostream& os, ScannerError _errorCode)
|
||||
{
|
||||
os << to_string(_errorCode);
|
||||
return os;
|
||||
return os << to_string(_errorCode);
|
||||
}
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
|
||||
/// Scoped helper for literal recording. Automatically drops the literal
|
||||
/// if aborting the scanning before it's complete.
|
||||
enum LiteralType {
|
||||
enum LiteralType
|
||||
{
|
||||
LITERAL_TYPE_STRING,
|
||||
LITERAL_TYPE_NUMBER, // not really different from string type in behaviour
|
||||
LITERAL_TYPE_COMMENT
|
||||
@ -100,9 +103,10 @@ enum LiteralType {
|
||||
class LiteralScope
|
||||
{
|
||||
public:
|
||||
explicit LiteralScope(Scanner* _self, enum LiteralType _type): m_type(_type)
|
||||
, m_scanner(_self)
|
||||
, m_complete(false)
|
||||
explicit LiteralScope(Scanner* _self, enum LiteralType _type):
|
||||
m_type(_type),
|
||||
m_scanner(_self),
|
||||
m_complete(false)
|
||||
{
|
||||
if (_type == LITERAL_TYPE_COMMENT)
|
||||
m_scanner->m_nextSkippedComment.literal.clear();
|
||||
@ -125,8 +129,9 @@ private:
|
||||
enum LiteralType m_type;
|
||||
Scanner* m_scanner;
|
||||
bool m_complete;
|
||||
}; // end of LiteralScope class
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void Scanner::reset(CharStream _source)
|
||||
{
|
||||
@ -134,20 +139,27 @@ void Scanner::reset(CharStream _source)
|
||||
reset();
|
||||
}
|
||||
|
||||
void Scanner::reset(std::shared_ptr<CharStream> _source)
|
||||
void Scanner::reset(shared_ptr<CharStream> _source)
|
||||
{
|
||||
solAssert(_source.get() != nullptr, "You MUST provide a CharStream when resetting.");
|
||||
m_source = _source;
|
||||
m_source = std::move(_source);
|
||||
reset();
|
||||
}
|
||||
|
||||
void Scanner::reset()
|
||||
{
|
||||
m_source->reset();
|
||||
m_supportPeriodInIdentifier = false;
|
||||
m_char = m_source->get();
|
||||
skipWhitespace();
|
||||
scanToken();
|
||||
next();
|
||||
next();
|
||||
}
|
||||
|
||||
void Scanner::supportPeriodInIdentifier(bool _value)
|
||||
{
|
||||
m_supportPeriodInIdentifier = _value;
|
||||
rescan();
|
||||
}
|
||||
|
||||
bool Scanner::scanHexByte(char& o_scannedByte)
|
||||
@ -168,7 +180,7 @@ bool Scanner::scanHexByte(char& o_scannedByte)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Scanner::scanUnicode(unsigned & o_codepoint)
|
||||
boost::optional<unsigned> Scanner::scanUnicode()
|
||||
{
|
||||
unsigned x = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
@ -177,13 +189,12 @@ bool Scanner::scanUnicode(unsigned & o_codepoint)
|
||||
if (d < 0)
|
||||
{
|
||||
rollback(i);
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
x = x * 16 + d;
|
||||
advance();
|
||||
}
|
||||
o_codepoint = x;
|
||||
return true;
|
||||
return x;
|
||||
}
|
||||
|
||||
// This supports codepoints between 0000 and FFFF.
|
||||
@ -204,6 +215,18 @@ void Scanner::addUnicodeAsUTF8(unsigned codepoint)
|
||||
}
|
||||
}
|
||||
|
||||
void Scanner::rescan()
|
||||
{
|
||||
size_t rollbackTo = 0;
|
||||
if (m_skippedComment.literal.empty())
|
||||
rollbackTo = m_currentToken.location.start;
|
||||
else
|
||||
rollbackTo = m_skippedComment.location.start;
|
||||
m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo);
|
||||
next();
|
||||
next();
|
||||
}
|
||||
|
||||
// Ensure that tokens can be stored in a byte.
|
||||
BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100);
|
||||
|
||||
@ -664,10 +687,10 @@ bool Scanner::scanEscape()
|
||||
break;
|
||||
case 'u':
|
||||
{
|
||||
unsigned codepoint;
|
||||
if (!scanUnicode(codepoint))
|
||||
if (boost::optional<unsigned> codepoint = scanUnicode())
|
||||
addUnicodeAsUTF8(*codepoint);
|
||||
else
|
||||
return false;
|
||||
addUnicodeAsUTF8(codepoint);
|
||||
return true;
|
||||
}
|
||||
case 'x':
|
||||
@ -754,7 +777,8 @@ void Scanner::scanDecimalDigits()
|
||||
return;
|
||||
|
||||
// May continue with decimal digit or underscore for grouping.
|
||||
do addLiteralCharAndAdvance();
|
||||
do
|
||||
addLiteralCharAndAdvance();
|
||||
while (!m_source->isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_'));
|
||||
|
||||
// Defer further validation of underscore to SyntaxChecker.
|
||||
@ -860,11 +884,8 @@ tuple<Token, unsigned, unsigned> Scanner::scanIdentifierOrKeyword()
|
||||
LiteralScope literal(this, LITERAL_TYPE_STRING);
|
||||
addLiteralCharAndAdvance();
|
||||
// Scan the rest of the identifier characters.
|
||||
while (isIdentifierPart(m_char)) //get full literal
|
||||
while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier))
|
||||
addLiteralCharAndAdvance();
|
||||
literal.complete();
|
||||
return TokenTraits::fromIdentifierOrKeyword(m_nextToken.literal);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -103,6 +103,10 @@ public:
|
||||
/// Resets scanner to the start of input.
|
||||
void reset();
|
||||
|
||||
/// Enables or disables support for period in identifier.
|
||||
/// This re-scans the current token and comment literal and thus invalidates it.
|
||||
void supportPeriodInIdentifier(bool _value);
|
||||
|
||||
/// @returns the next token and advances input
|
||||
Token next();
|
||||
|
||||
@ -191,6 +195,8 @@ private:
|
||||
|
||||
bool advance() { m_char = m_source->advanceAndGet(); return !m_source->isPastEndOfInput(); }
|
||||
void rollback(int _amount) { m_char = m_source->rollback(_amount); }
|
||||
/// Rolls back to the start of the current token and re-runs the scanner.
|
||||
void rescan();
|
||||
|
||||
inline Token selectErrorToken(ScannerError _err) { advance(); return setError(_err); }
|
||||
inline Token selectToken(Token _tok) { advance(); return _tok; }
|
||||
@ -198,7 +204,7 @@ private:
|
||||
inline Token selectToken(char _next, Token _then, Token _else);
|
||||
|
||||
bool scanHexByte(char& o_scannedByte);
|
||||
bool scanUnicode(unsigned& o_codepoint);
|
||||
boost::optional<unsigned> scanUnicode();
|
||||
|
||||
/// Scans a single Solidity token.
|
||||
void scanToken();
|
||||
@ -233,6 +239,8 @@ private:
|
||||
int sourcePos() const { return m_source->position(); }
|
||||
bool isSourcePastEndOfInput() const { return m_source->isPastEndOfInput(); }
|
||||
|
||||
bool m_supportPeriodInIdentifier = false;
|
||||
|
||||
TokenDesc m_skippedComment; // desc for current skipped comment
|
||||
TokenDesc m_nextSkippedComment; // desc for next skipped comment
|
||||
|
||||
|
@ -20,13 +20,13 @@
|
||||
* Utilities to handle semantic versioning.
|
||||
*/
|
||||
|
||||
#include <libsolidity/analysis/SemVerHandler.h>
|
||||
#include <liblangutil/SemVerHandler.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
using namespace langutil;
|
||||
|
||||
SemVerVersion::SemVerVersion(string const& _versionString)
|
||||
{
|
@ -22,13 +22,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/parsing/Token.h>
|
||||
#include <liblangutil/Token.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
namespace langutil
|
||||
{
|
||||
|
||||
class SemVerError: dev::Exception
|
||||
@ -109,4 +107,3 @@ private:
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -70,9 +70,17 @@ void SourceReferenceFormatter::printSourceName(SourceReference const& _ref)
|
||||
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": ";
|
||||
}
|
||||
|
||||
void SourceReferenceFormatter::printExceptionInformation(dev::Exception const& _error, std::string const& _category)
|
||||
void SourceReferenceFormatter::printExceptionInformation(dev::Exception const& _exception, std::string const& _category)
|
||||
{
|
||||
printExceptionInformation(SourceReferenceExtractor::extract(_error, _category));
|
||||
printExceptionInformation(SourceReferenceExtractor::extract(_exception, _category));
|
||||
}
|
||||
|
||||
void SourceReferenceFormatter::printErrorInformation(Error const& _error)
|
||||
{
|
||||
printExceptionInformation(
|
||||
_error,
|
||||
(_error.type() == Error::Type::Warning) ? "Warning" : "Error"
|
||||
);
|
||||
}
|
||||
|
||||
void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg)
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <liblangutil/SourceReferenceExtractor.h>
|
||||
|
||||
namespace dev
|
||||
@ -51,7 +52,16 @@ public:
|
||||
virtual void printExceptionInformation(SourceReferenceExtractor::Message const& _msg);
|
||||
|
||||
virtual void printSourceLocation(SourceLocation const* _location);
|
||||
virtual void printExceptionInformation(dev::Exception const& _error, std::string const& _category);
|
||||
virtual void printExceptionInformation(dev::Exception const& _exception, std::string const& _category);
|
||||
virtual void printErrorInformation(Error const& _error);
|
||||
|
||||
static std::string formatErrorInformation(Error const& _error)
|
||||
{
|
||||
return formatExceptionInformation(
|
||||
_error,
|
||||
(_error.type() == Error::Type::Warning) ? "Warning" : "Error"
|
||||
);
|
||||
}
|
||||
|
||||
static std::string formatExceptionInformation(
|
||||
dev::Exception const& _exception,
|
||||
|
@ -19,7 +19,11 @@
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "CodeFragment.h"
|
||||
#include <liblll/CodeFragment.h>
|
||||
#include <liblll/CompilerState.h>
|
||||
#include <liblll/Parser.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libdevcore/CommonIO.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
@ -34,13 +38,10 @@
|
||||
#pragma GCC diagnostic pop
|
||||
#endif // defined(__GNUC__)
|
||||
|
||||
#include <libdevcore/CommonIO.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include "CompilerState.h"
|
||||
#include "Parser.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
using namespace dev::lll;
|
||||
|
||||
void CodeFragment::finalise(CompilerState const& _cs)
|
||||
@ -66,7 +67,7 @@ bool validAssemblyInstruction(string us)
|
||||
auto it = c_instructions.find(us);
|
||||
return !(
|
||||
it == c_instructions.end() ||
|
||||
solidity::isPushInstruction(it->second)
|
||||
isPushInstruction(it->second)
|
||||
);
|
||||
}
|
||||
|
||||
@ -76,10 +77,10 @@ bool validFunctionalInstruction(string us)
|
||||
auto it = c_instructions.find(us);
|
||||
return !(
|
||||
it == c_instructions.end() ||
|
||||
solidity::isPushInstruction(it->second) ||
|
||||
solidity::isDupInstruction(it->second) ||
|
||||
solidity::isSwapInstruction(it->second) ||
|
||||
it->second == solidity::Instruction::JUMPDEST
|
||||
isPushInstruction(it->second) ||
|
||||
isDupInstruction(it->second) ||
|
||||
isSwapInstruction(it->second) ||
|
||||
it->second == Instruction::JUMPDEST
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -255,7 +256,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
|
||||
string contents = m_readFile(fileName);
|
||||
if (contents.empty())
|
||||
error<InvalidName>(std::string("File not found (or empty): ") + fileName);
|
||||
m_asm.append(CodeFragment::compile(contents, _s, m_readFile).m_asm);
|
||||
m_asm.append(CodeFragment::compile(std::move(contents), _s, m_readFile).m_asm);
|
||||
}
|
||||
else if (us == "SET")
|
||||
{
|
||||
@ -744,11 +745,11 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
|
||||
}
|
||||
}
|
||||
|
||||
CodeFragment CodeFragment::compile(string const& _src, CompilerState& _s, ReadCallback const& _readFile)
|
||||
CodeFragment CodeFragment::compile(string _src, CompilerState& _s, ReadCallback const& _readFile)
|
||||
{
|
||||
CodeFragment ret;
|
||||
sp::utree o;
|
||||
parseTreeLLL(_src, o);
|
||||
parseTreeLLL(std::move(_src), o);
|
||||
if (!o.empty())
|
||||
ret = CodeFragment(o, _s, _readFile);
|
||||
_s.treesToKill.push_back(o);
|
||||
|
@ -21,10 +21,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
#include <liblll/Exceptions.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include "Exceptions.h"
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
namespace boost { namespace spirit { class utree; } }
|
||||
namespace sp = boost::spirit;
|
||||
@ -44,7 +44,7 @@ public:
|
||||
CodeFragment() = default;
|
||||
CodeFragment(sp::utree const& _t, CompilerState& _s, ReadCallback const& _readFile, bool _allowASM = false);
|
||||
|
||||
static CodeFragment compile(std::string const& _src, CompilerState& _s, ReadCallback const& _readFile);
|
||||
static CodeFragment compile(std::string _src, CompilerState& _s, ReadCallback const& _readFile);
|
||||
|
||||
/// Consolidates data and compiles code.
|
||||
eth::Assembly& assembly(CompilerState const& _cs) { finalise(_cs); return m_asm; }
|
||||
@ -69,4 +69,3 @@ static CodeFragment const NullCodeFragment;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,13 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::lll;
|
||||
|
||||
bytes dev::lll::compileLLL(string const& _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector<std::string>* _errors, ReadCallback const& _readFile)
|
||||
bytes dev::lll::compileLLL(string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector<std::string>* _errors, ReadCallback const& _readFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
CompilerState cs;
|
||||
cs.populateStandard();
|
||||
auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs);
|
||||
auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs);
|
||||
if (_opt)
|
||||
assembly = assembly.optimise(true, _evmVersion, true, 200);
|
||||
bytes ret = assembly.assemble().bytecode;
|
||||
@ -66,13 +66,13 @@ bytes dev::lll::compileLLL(string const& _src, langutil::EVMVersion _evmVersion,
|
||||
return bytes();
|
||||
}
|
||||
|
||||
std::string dev::lll::compileLLLToAsm(std::string const& _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector<std::string>* _errors, ReadCallback const& _readFile)
|
||||
std::string dev::lll::compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector<std::string>* _errors, ReadCallback const& _readFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
CompilerState cs;
|
||||
cs.populateStandard();
|
||||
auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs);
|
||||
auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs);
|
||||
if (_opt)
|
||||
assembly = assembly.optimise(true, _evmVersion, true, 200);
|
||||
string ret = assembly.assemblyString();
|
||||
@ -104,13 +104,13 @@ std::string dev::lll::compileLLLToAsm(std::string const& _src, langutil::EVMVers
|
||||
return string();
|
||||
}
|
||||
|
||||
string dev::lll::parseLLL(string const& _src)
|
||||
string dev::lll::parseLLL(string _src)
|
||||
{
|
||||
sp::utree o;
|
||||
|
||||
try
|
||||
{
|
||||
parseTreeLLL(_src, o);
|
||||
parseTreeLLL(std::move(_src), o);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
@ -35,9 +35,9 @@ namespace lll
|
||||
|
||||
using ReadCallback = std::function<std::string(std::string const&)>;
|
||||
|
||||
std::string parseLLL(std::string const& _src);
|
||||
std::string compileLLLToAsm(std::string const& _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback());
|
||||
bytes compileLLL(std::string const& _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback());
|
||||
std::string parseLLL(std::string _src);
|
||||
std::string compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback());
|
||||
bytes compileLLL(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "CompilerState.h"
|
||||
#include "CodeFragment.h"
|
||||
#include <liblll/CompilerState.h>
|
||||
#include <liblll/CodeFragment.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
|
@ -21,8 +21,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <liblll/CodeFragment.h>
|
||||
#include <boost/spirit/include/support_utree.hpp>
|
||||
#include "CodeFragment.h"
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
@ -19,7 +19,7 @@
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "Parser.h"
|
||||
#include <liblll/Parser.h>
|
||||
|
||||
#if _MSC_VER
|
||||
#pragma warning(disable:4348)
|
||||
|
@ -21,10 +21,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <liblll/Exceptions.h>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <libdevcore/Common.h>
|
||||
#include "Exceptions.h"
|
||||
|
||||
namespace boost { namespace spirit { class utree; } }
|
||||
namespace sp = boost::spirit;
|
||||
|
@ -72,10 +72,10 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = n
|
||||
return readCallback;
|
||||
}
|
||||
|
||||
string compile(string const& _input, CStyleReadFileCallback _readCallback = nullptr)
|
||||
string compile(string _input, CStyleReadFileCallback _readCallback = nullptr)
|
||||
{
|
||||
StandardCompiler compiler(wrapReadCallback(_readCallback));
|
||||
return compiler.compile(_input);
|
||||
return compiler.compile(std::move(_input));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,8 +22,6 @@ set(sources
|
||||
analysis/PostTypeChecker.h
|
||||
analysis/ReferencesResolver.cpp
|
||||
analysis/ReferencesResolver.h
|
||||
analysis/SemVerHandler.cpp
|
||||
analysis/SemVerHandler.h
|
||||
analysis/StaticAnalyzer.cpp
|
||||
analysis/StaticAnalyzer.h
|
||||
analysis/SyntaxChecker.cpp
|
||||
@ -47,6 +45,8 @@ set(sources
|
||||
ast/ExperimentalFeatures.h
|
||||
ast/Types.cpp
|
||||
ast/Types.h
|
||||
ast/TypeProvider.cpp
|
||||
ast/TypeProvider.h
|
||||
codegen/ABIFunctions.cpp
|
||||
codegen/ABIFunctions.h
|
||||
codegen/ArrayUtils.cpp
|
||||
@ -67,6 +67,14 @@ set(sources
|
||||
codegen/MultiUseYulFunctionCollector.cpp
|
||||
codegen/YulUtilFunctions.h
|
||||
codegen/YulUtilFunctions.cpp
|
||||
codegen/ir/IRGenerator.cpp
|
||||
codegen/ir/IRGenerator.h
|
||||
codegen/ir/IRGeneratorForStatements.cpp
|
||||
codegen/ir/IRGeneratorForStatements.h
|
||||
codegen/ir/IRGenerationContext.cpp
|
||||
codegen/ir/IRGenerationContext.h
|
||||
formal/EncodingContext.cpp
|
||||
formal/EncodingContext.h
|
||||
formal/SMTChecker.cpp
|
||||
formal/SMTChecker.h
|
||||
formal/SMTLib2Interface.cpp
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libsolidity/analysis/ConstantEvaluator.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
using namespace std;
|
||||
@ -56,7 +57,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
|
||||
setType(
|
||||
_operation,
|
||||
TokenTraits::isCompareOp(_operation.getOperator()) ?
|
||||
make_shared<BoolType>() :
|
||||
TypeProvider::boolean() :
|
||||
commonType
|
||||
);
|
||||
}
|
||||
@ -64,7 +65,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
|
||||
|
||||
void ConstantEvaluator::endVisit(Literal const& _literal)
|
||||
{
|
||||
setType(_literal, Type::forLiteral(_literal));
|
||||
setType(_literal, TypeProvider::forLiteral(_literal));
|
||||
}
|
||||
|
||||
void ConstantEvaluator::endVisit(Identifier const& _identifier)
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/analysis/TypeChecker.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
@ -244,13 +245,13 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
|
||||
{
|
||||
for (VariableDeclaration const* v: contract->stateVariables())
|
||||
if (v->isPartOfExternalInterface())
|
||||
registerFunction(*v, make_shared<FunctionType>(*v), true);
|
||||
registerFunction(*v, TypeProvider::function(*v), true);
|
||||
|
||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||
if (!function->isConstructor())
|
||||
registerFunction(
|
||||
*function,
|
||||
make_shared<FunctionType>(*function)->asCallableFunction(false),
|
||||
TypeProvider::function(*function)->asCallableFunction(false),
|
||||
function->isImplemented()
|
||||
);
|
||||
}
|
||||
@ -407,7 +408,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c
|
||||
for (FunctionDefinition const* f: contract->definedFunctions())
|
||||
if (f->isPartOfExternalInterface())
|
||||
{
|
||||
auto functionType = make_shared<FunctionType>(*f);
|
||||
auto functionType = TypeProvider::function(*f);
|
||||
// under non error circumstances this should be true
|
||||
if (functionType->interfaceFunctionType())
|
||||
externalDeclarations[functionType->externalSignature()].emplace_back(
|
||||
@ -417,7 +418,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c
|
||||
for (VariableDeclaration const* v: contract->stateVariables())
|
||||
if (v->isPartOfExternalInterface())
|
||||
{
|
||||
auto functionType = make_shared<FunctionType>(*v);
|
||||
auto functionType = TypeProvider::function(*v);
|
||||
// under non error circumstances this should be true
|
||||
if (functionType->interfaceFunctionType())
|
||||
externalDeclarations[functionType->externalSignature()].emplace_back(
|
||||
|
@ -235,7 +235,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
|
||||
solAssert(!!m_currentNode, "");
|
||||
solAssert(!!_functionCall.expression().annotation().type, "");
|
||||
|
||||
if (auto functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type))
|
||||
if (auto functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type))
|
||||
switch (functionType->kind())
|
||||
{
|
||||
case FunctionType::Kind::Revert:
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <libsolidity/analysis/GlobalContext.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <memory>
|
||||
|
||||
@ -34,42 +35,50 @@ namespace dev
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
GlobalContext::GlobalContext():
|
||||
m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
|
||||
make_shared<MagicVariableDeclaration>("abi", make_shared<MagicType>(MagicType::Kind::ABI)),
|
||||
make_shared<MagicVariableDeclaration>("addmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("assert", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)),
|
||||
make_shared<MagicVariableDeclaration>("blockhash", make_shared<FunctionType>(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)),
|
||||
make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)),
|
||||
make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("log0", make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)),
|
||||
make_shared<MagicVariableDeclaration>("log1", make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)),
|
||||
make_shared<MagicVariableDeclaration>("log2", make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)),
|
||||
make_shared<MagicVariableDeclaration>("log3", make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)),
|
||||
make_shared<MagicVariableDeclaration>("log4", make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)),
|
||||
make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message)),
|
||||
make_shared<MagicVariableDeclaration>("mulmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
|
||||
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("sha3", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
|
||||
make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)),
|
||||
make_shared<MagicVariableDeclaration>("type", make_shared<FunctionType>(
|
||||
strings{"address"} /* accepts any contract type, handled by the type checker */,
|
||||
strings{} /* returns a MagicType, handled by the type checker */,
|
||||
FunctionType::Kind::MetaType,
|
||||
false,
|
||||
StateMutability::Pure
|
||||
)),
|
||||
})
|
||||
inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariables()
|
||||
{
|
||||
static auto const magicVarDecl = [](string const& _name, Type const* _type) {
|
||||
return make_shared<MagicVariableDeclaration>(_name, _type);
|
||||
};
|
||||
|
||||
return {
|
||||
magicVarDecl("abi", TypeProvider::magic(MagicType::Kind::ABI)),
|
||||
magicVarDecl("addmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)),
|
||||
magicVarDecl("assert", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)),
|
||||
magicVarDecl("block", TypeProvider::magic(MagicType::Kind::Block)),
|
||||
magicVarDecl("blockhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)),
|
||||
magicVarDecl("ecrecover", TypeProvider::function(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)),
|
||||
magicVarDecl("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)),
|
||||
magicVarDecl("keccak256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
|
||||
magicVarDecl("log0", TypeProvider::function(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)),
|
||||
magicVarDecl("log1", TypeProvider::function(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)),
|
||||
magicVarDecl("log2", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)),
|
||||
magicVarDecl("log3", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)),
|
||||
magicVarDecl("log4", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)),
|
||||
magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)),
|
||||
magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
|
||||
magicVarDecl("now", TypeProvider::uint256()),
|
||||
magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
|
||||
magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
|
||||
magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
|
||||
magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)),
|
||||
magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)),
|
||||
magicVarDecl("sha3", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
|
||||
magicVarDecl("suicide", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||
magicVarDecl("tx", TypeProvider::magic(MagicType::Kind::Transaction)),
|
||||
magicVarDecl("type", TypeProvider::function(
|
||||
strings{"address"} /* accepts any contract type, handled by the type checker */,
|
||||
strings{} /* returns a MagicType, handled by the type checker */,
|
||||
FunctionType::Kind::MetaType,
|
||||
false,
|
||||
StateMutability::Pure
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
GlobalContext::GlobalContext(): m_magicVariables{constructMagicVariables()}
|
||||
{
|
||||
}
|
||||
|
||||
@ -90,7 +99,7 @@ vector<Declaration const*> GlobalContext::declarations() const
|
||||
MagicVariableDeclaration const* GlobalContext::currentThis() const
|
||||
{
|
||||
if (!m_thisPointer[m_currentContract])
|
||||
m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>("this", make_shared<ContractType>(*m_currentContract));
|
||||
m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>("this", TypeProvider::contract(*m_currentContract));
|
||||
return m_thisPointer[m_currentContract].get();
|
||||
|
||||
}
|
||||
@ -98,7 +107,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const
|
||||
MagicVariableDeclaration const* GlobalContext::currentSuper() const
|
||||
{
|
||||
if (!m_superPointer[m_currentContract])
|
||||
m_superPointer[m_currentContract] = make_shared<MagicVariableDeclaration>("super", make_shared<ContractType>(*m_currentContract, true));
|
||||
m_superPointer[m_currentContract] = make_shared<MagicVariableDeclaration>("super", TypeProvider::contract(*m_currentContract, true));
|
||||
return m_superPointer[m_currentContract].get();
|
||||
}
|
||||
|
||||
|
@ -228,7 +228,7 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
|
||||
uniqueFunctions.end(),
|
||||
[&](Declaration const* d)
|
||||
{
|
||||
shared_ptr<FunctionType const> newFunctionType { d->functionType(false) };
|
||||
FunctionType const* newFunctionType = d->functionType(false);
|
||||
if (!newFunctionType)
|
||||
newFunctionType = d->functionType(true);
|
||||
return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
|
||||
@ -241,7 +241,7 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
|
||||
|
||||
void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
|
||||
{
|
||||
for (auto const& instruction: c_instructions)
|
||||
for (auto const& instruction: dev::eth::c_instructions)
|
||||
{
|
||||
string const instructionName{boost::algorithm::to_lower_copy(instruction.first)};
|
||||
auto declarations = nameFromCurrentScope(instructionName, true);
|
||||
|
@ -17,10 +17,10 @@
|
||||
|
||||
#include <libsolidity/analysis/PostTypeChecker.h>
|
||||
|
||||
#include <libsolidity/analysis/SemVerHandler.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/SemVerHandler.h>
|
||||
#include <libdevcore/Algorithms.h>
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/analysis/ConstantEvaluator.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
@ -120,7 +121,7 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
|
||||
{
|
||||
if (!_typeName.annotation().type)
|
||||
{
|
||||
_typeName.annotation().type = Type::fromElementaryTypeName(_typeName.typeName());
|
||||
_typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName());
|
||||
if (_typeName.stateMutability().is_initialized())
|
||||
{
|
||||
// for non-address types this was already caught by the parser
|
||||
@ -128,8 +129,10 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
|
||||
switch(*_typeName.stateMutability())
|
||||
{
|
||||
case StateMutability::Payable:
|
||||
_typeName.annotation().type = TypeProvider::payableAddress();
|
||||
break;
|
||||
case StateMutability::NonPayable:
|
||||
_typeName.annotation().type = make_shared<AddressType>(*_typeName.stateMutability());
|
||||
_typeName.annotation().type = TypeProvider::address();
|
||||
break;
|
||||
default:
|
||||
m_errorReporter.typeError(
|
||||
@ -179,14 +182,14 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
|
||||
_typeName.annotation().referencedDeclaration = declaration;
|
||||
|
||||
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
|
||||
_typeName.annotation().type = make_shared<StructType>(*structDef);
|
||||
_typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage);
|
||||
else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
|
||||
_typeName.annotation().type = make_shared<EnumType>(*enumDef);
|
||||
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
|
||||
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||
_typeName.annotation().type = make_shared<ContractType>(*contract);
|
||||
_typeName.annotation().type = TypeProvider::contract(*contract);
|
||||
else
|
||||
{
|
||||
_typeName.annotation().type = make_shared<TupleType>();
|
||||
_typeName.annotation().type = TypeProvider::emptyTuple();
|
||||
typeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
|
||||
}
|
||||
}
|
||||
@ -220,7 +223,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
|
||||
}
|
||||
}
|
||||
|
||||
_typeName.annotation().type = make_shared<FunctionType>(_typeName);
|
||||
_typeName.annotation().type = TypeProvider::function(_typeName);
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(Mapping const& _typeName)
|
||||
@ -228,10 +231,10 @@ void ReferencesResolver::endVisit(Mapping const& _typeName)
|
||||
TypePointer keyType = _typeName.keyType().annotation().type;
|
||||
TypePointer valueType = _typeName.valueType().annotation().type;
|
||||
// Convert key type to memory.
|
||||
keyType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, keyType);
|
||||
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
||||
// Convert value type to storage reference.
|
||||
valueType = ReferenceType::copyForLocationIfReference(DataLocation::Storage, valueType);
|
||||
_typeName.annotation().type = make_shared<MappingType>(keyType, valueType);
|
||||
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
|
||||
_typeName.annotation().type = TypeProvider::mapping(keyType, valueType);
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
|
||||
@ -249,7 +252,7 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
|
||||
TypePointer& lengthTypeGeneric = length->annotation().type;
|
||||
if (!lengthTypeGeneric)
|
||||
lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
|
||||
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric.get());
|
||||
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
|
||||
if (!lengthType || !lengthType->mobileType())
|
||||
fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
|
||||
else if (lengthType->isZero())
|
||||
@ -259,10 +262,10 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
|
||||
else if (lengthType->isNegative())
|
||||
fatalTypeError(length->location(), "Array with negative length specified.");
|
||||
else
|
||||
_typeName.annotation().type = make_shared<ArrayType>(DataLocation::Storage, baseType, lengthType->literalValue(nullptr));
|
||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthType->literalValue(nullptr));
|
||||
}
|
||||
else
|
||||
_typeName.annotation().type = make_shared<ArrayType>(DataLocation::Storage, baseType);
|
||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
|
||||
@ -436,10 +439,10 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
|
||||
}
|
||||
|
||||
TypePointer type = _variable.typeName()->annotation().type;
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type))
|
||||
{
|
||||
bool isPointer = !_variable.isStateVariable();
|
||||
type = ref->copyForLocation(typeLoc, isPointer);
|
||||
type = TypeProvider::withLocation(ref, typeLoc, isPointer);
|
||||
}
|
||||
|
||||
_variable.annotation().type = type;
|
||||
|
@ -190,7 +190,7 @@ bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
|
||||
|
||||
bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
|
||||
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type))
|
||||
{
|
||||
if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas")
|
||||
m_errorReporter.typeError(
|
||||
@ -217,7 +217,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
|
||||
}
|
||||
|
||||
if (_memberAccess.memberName() == "callcode")
|
||||
if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
|
||||
if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
|
||||
if (type->kind() == FunctionType::Kind::BareCallCode)
|
||||
m_errorReporter.typeError(
|
||||
_memberAccess.location(),
|
||||
@ -278,7 +278,7 @@ bool StaticAnalyzer::visit(BinaryOperation const& _operation)
|
||||
_operation.rightExpression().annotation().isPure &&
|
||||
(_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod)
|
||||
)
|
||||
if (auto rhs = dynamic_pointer_cast<RationalNumberType const>(
|
||||
if (auto rhs = dynamic_cast<RationalNumberType const*>(
|
||||
ConstantEvaluator(m_errorReporter).evaluate(_operation.rightExpression())
|
||||
))
|
||||
if (rhs->isZero())
|
||||
@ -294,13 +294,13 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
if (_functionCall.annotation().kind == FunctionCallKind::FunctionCall)
|
||||
{
|
||||
auto functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type);
|
||||
auto functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
|
||||
solAssert(functionType, "");
|
||||
if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod)
|
||||
{
|
||||
solAssert(_functionCall.arguments().size() == 3, "");
|
||||
if (_functionCall.arguments()[2]->annotation().isPure)
|
||||
if (auto lastArg = dynamic_pointer_cast<RationalNumberType const>(
|
||||
if (auto lastArg = dynamic_cast<RationalNumberType const*>(
|
||||
ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2])
|
||||
))
|
||||
if (lastArg->isZero())
|
||||
|
@ -17,12 +17,12 @@
|
||||
|
||||
#include <libsolidity/analysis/SyntaxChecker.h>
|
||||
|
||||
#include <libsolidity/analysis/SemVerHandler.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ExperimentalFeatures.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/SemVerHandler.h>
|
||||
|
||||
#include <boost/algorithm/cxx11/all_of.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <libsolidity/analysis/TypeChecker.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
@ -107,7 +108,7 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
|
||||
size_t toStorageCopies = 0;
|
||||
for (size_t i = 0; i < lhs.components().size(); ++i)
|
||||
{
|
||||
ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i].get());
|
||||
ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i]);
|
||||
if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer())
|
||||
continue;
|
||||
toStorageCopies++;
|
||||
@ -137,7 +138,7 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
|
||||
|
||||
if (arguments.size() >= 1)
|
||||
{
|
||||
BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType::bytesMemory());
|
||||
BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory());
|
||||
|
||||
if (!result)
|
||||
m_errorReporter.typeErrorConcatenateDescriptions(
|
||||
@ -169,17 +170,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
|
||||
for (auto const& typeArgument: tupleExpression->components())
|
||||
{
|
||||
solAssert(typeArgument, "");
|
||||
if (TypeType const* argTypeType = dynamic_cast<TypeType const*>(type(*typeArgument).get()))
|
||||
if (TypeType const* argTypeType = dynamic_cast<TypeType const*>(type(*typeArgument)))
|
||||
{
|
||||
TypePointer actualType = argTypeType->actualType();
|
||||
solAssert(actualType, "");
|
||||
// We force memory because the parser currently cannot handle
|
||||
// data locations. Furthermore, storage can be a little dangerous and
|
||||
// calldata is not really implemented anyway.
|
||||
actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType);
|
||||
actualType = TypeProvider::withLocationIfReference(DataLocation::Memory, actualType);
|
||||
// We force address payable for address types.
|
||||
if (actualType->category() == Type::Category::Address)
|
||||
actualType = make_shared<AddressType>(StateMutability::Payable);
|
||||
actualType = TypeProvider::payableAddress();
|
||||
solAssert(
|
||||
!actualType->dataStoredIn(DataLocation::CallData) &&
|
||||
!actualType->dataStoredIn(DataLocation::Storage),
|
||||
@ -195,7 +196,7 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
|
||||
else
|
||||
{
|
||||
m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name.");
|
||||
components.push_back(make_shared<TupleType>());
|
||||
components.push_back(TypeProvider::emptyTuple());
|
||||
}
|
||||
}
|
||||
return components;
|
||||
@ -230,7 +231,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
|
||||
return {};
|
||||
}
|
||||
|
||||
return {MagicType::metaType(dynamic_cast<TypeType const&>(*firstArgType).actualType())};
|
||||
return {TypeProvider::meta(dynamic_cast<TypeType const&>(*firstArgType).actualType())};
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
|
||||
@ -303,12 +304,12 @@ bool TypeChecker::visit(StructDefinition const& _struct)
|
||||
|
||||
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
||||
{
|
||||
Type const* memberType = type(*member).get();
|
||||
Type const* memberType = type(*member);
|
||||
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
|
||||
{
|
||||
if (arrayType->isDynamicallySized())
|
||||
break;
|
||||
memberType = arrayType->baseType().get();
|
||||
memberType = arrayType->baseType();
|
||||
}
|
||||
if (auto structType = dynamic_cast<StructType const*>(memberType))
|
||||
if (_cycleDetector.run(structType->structDefinition()))
|
||||
@ -359,7 +360,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
auto iType = type(var)->interfaceType(isLibraryFunction);
|
||||
|
||||
if (!iType.get())
|
||||
if (!iType)
|
||||
{
|
||||
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
||||
m_errorReporter.fatalTypeError(var.location(), iType.message());
|
||||
@ -455,7 +456,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
if (!_variable.type()->isValueType())
|
||||
{
|
||||
bool allowed = false;
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type().get()))
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type()))
|
||||
allowed = arrayType->isByteArray();
|
||||
if (!allowed)
|
||||
m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented.");
|
||||
@ -498,7 +499,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
switch (varType->category())
|
||||
{
|
||||
case Type::Category::Array:
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get()))
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(varType))
|
||||
if (
|
||||
((arrayType->location() == DataLocation::Memory) ||
|
||||
(arrayType->location() == DataLocation::CallData)) &&
|
||||
@ -584,7 +585,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
|
||||
numIndexed++;
|
||||
if (!type(*var)->canLiveOutsideStorage())
|
||||
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
||||
if (!type(*var)->interfaceType(false).get())
|
||||
if (!type(*var)->interfaceType(false))
|
||||
m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
||||
if (
|
||||
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
||||
@ -607,7 +608,7 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
||||
{
|
||||
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
|
||||
if (fun.kind() == FunctionType::Kind::External)
|
||||
solAssert(fun.interfaceType(false).get(), "External function type uses internal types.");
|
||||
solAssert(fun.interfaceType(false), "External function type uses internal types.");
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
@ -716,7 +717,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
|
||||
bool TypeChecker::visit(IfStatement const& _ifStatement)
|
||||
{
|
||||
expectType(_ifStatement.condition(), BoolType());
|
||||
expectType(_ifStatement.condition(), *TypeProvider::boolean());
|
||||
_ifStatement.trueStatement().accept(*this);
|
||||
if (_ifStatement.falseStatement())
|
||||
_ifStatement.falseStatement()->accept(*this);
|
||||
@ -725,7 +726,7 @@ bool TypeChecker::visit(IfStatement const& _ifStatement)
|
||||
|
||||
bool TypeChecker::visit(WhileStatement const& _whileStatement)
|
||||
{
|
||||
expectType(_whileStatement.condition(), BoolType());
|
||||
expectType(_whileStatement.condition(), *TypeProvider::boolean());
|
||||
_whileStatement.body().accept(*this);
|
||||
return false;
|
||||
}
|
||||
@ -735,7 +736,7 @@ bool TypeChecker::visit(ForStatement const& _forStatement)
|
||||
if (_forStatement.initializationExpression())
|
||||
_forStatement.initializationExpression()->accept(*this);
|
||||
if (_forStatement.condition())
|
||||
expectType(*_forStatement.condition(), BoolType());
|
||||
expectType(*_forStatement.condition(), *TypeProvider::boolean());
|
||||
if (_forStatement.loopExpression())
|
||||
_forStatement.loopExpression()->accept(*this);
|
||||
_forStatement.body().accept(*this);
|
||||
@ -759,7 +760,7 @@ void TypeChecker::endVisit(Return const& _return)
|
||||
TypePointers returnTypes;
|
||||
for (auto const& var: params->parameters())
|
||||
returnTypes.push_back(type(*var));
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(type(*_return.expression()).get()))
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(type(*_return.expression())))
|
||||
{
|
||||
if (tupleType->components().size() != params->parameters().size())
|
||||
m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
|
||||
@ -841,7 +842,7 @@ bool typeCanBeExpressed(vector<ASTPointer<VariableDeclaration>> const& decls)
|
||||
if (!decl->annotation().type)
|
||||
return false;
|
||||
|
||||
if (auto functionType = dynamic_cast<FunctionType const*>(decl->annotation().type.get()))
|
||||
if (auto functionType = dynamic_cast<FunctionType const*>(decl->annotation().type))
|
||||
if (
|
||||
functionType->kind() != FunctionType::Kind::Internal &&
|
||||
functionType->kind() != FunctionType::Kind::External
|
||||
@ -878,7 +879,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
||||
if (!varDecl.annotation().type)
|
||||
m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed.");
|
||||
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl).get()))
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl)))
|
||||
{
|
||||
if (ref->dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
@ -888,7 +889,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
||||
m_errorReporter.declarationError(varDecl.location(), errorText);
|
||||
}
|
||||
}
|
||||
else if (dynamic_cast<MappingType const*>(type(varDecl).get()))
|
||||
else if (dynamic_cast<MappingType const*>(type(varDecl)))
|
||||
m_errorReporter.typeError(
|
||||
varDecl.location(),
|
||||
"Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
|
||||
@ -902,7 +903,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
||||
|
||||
_statement.initialValue()->accept(*this);
|
||||
TypePointers valueTypes;
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(type(*_statement.initialValue()).get()))
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(type(*_statement.initialValue())))
|
||||
valueTypes = tupleType->components();
|
||||
else
|
||||
valueTypes = TypePointers{type(*_statement.initialValue())};
|
||||
@ -950,13 +951,13 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
||||
else
|
||||
solAssert(false, "");
|
||||
}
|
||||
else if (*var.annotation().type == TupleType())
|
||||
else if (*var.annotation().type == *TypeProvider::emptyTuple())
|
||||
solAssert(false, "Cannot declare variable with void (empty tuple) type.");
|
||||
else if (valueComponentType->category() == Type::Category::RationalNumber)
|
||||
{
|
||||
string typeName = var.annotation().type->toString(true);
|
||||
string extension;
|
||||
if (auto type = dynamic_cast<IntegerType const*>(var.annotation().type.get()))
|
||||
if (auto type = dynamic_cast<IntegerType const*>(var.annotation().type))
|
||||
{
|
||||
unsigned numBits = type->numBits();
|
||||
bool isSigned = type->isSigned();
|
||||
@ -974,7 +975,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
||||
extension = ", which can hold values between " + minValue + " and " + maxValue;
|
||||
}
|
||||
else
|
||||
solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type.get()), "Unknown type.");
|
||||
solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type), "Unknown type.");
|
||||
}
|
||||
|
||||
var.accept(*this);
|
||||
@ -1054,7 +1055,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
|
||||
|
||||
if (auto call = dynamic_cast<FunctionCall const*>(&_statement.expression()))
|
||||
{
|
||||
if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression()).get()))
|
||||
if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression())))
|
||||
{
|
||||
auto kind = callType->kind();
|
||||
if (
|
||||
@ -1072,7 +1073,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
|
||||
|
||||
bool TypeChecker::visit(Conditional const& _conditional)
|
||||
{
|
||||
expectType(_conditional.condition(), BoolType());
|
||||
expectType(_conditional.condition(), *TypeProvider::boolean());
|
||||
|
||||
_conditional.trueExpression().accept(*this);
|
||||
_conditional.falseExpression().accept(*this);
|
||||
@ -1080,7 +1081,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
|
||||
TypePointer trueType = type(_conditional.trueExpression())->mobileType();
|
||||
TypePointer falseType = type(_conditional.falseExpression())->mobileType();
|
||||
|
||||
TypePointer commonType;
|
||||
TypePointer commonType = nullptr;
|
||||
|
||||
if (!trueType)
|
||||
m_errorReporter.typeError(_conditional.trueExpression().location(), "Invalid mobile type in true expression.");
|
||||
@ -1134,7 +1135,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
|
||||
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
|
||||
{
|
||||
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
|
||||
auto const& types = tupleType ? tupleType->components() : vector<TypePointer> { _type.shared_from_this() };
|
||||
auto const& types = tupleType ? tupleType->components() : vector<TypePointer> { &_type };
|
||||
|
||||
solAssert(
|
||||
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
|
||||
@ -1168,7 +1169,7 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
||||
|
||||
checkExpressionAssignment(*t, _assignment.leftHandSide());
|
||||
|
||||
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get()))
|
||||
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t))
|
||||
{
|
||||
if (_assignment.assignmentOperator() != Token::Assign)
|
||||
m_errorReporter.typeError(
|
||||
@ -1176,12 +1177,12 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
||||
"Compound assignment is not allowed for tuple types."
|
||||
);
|
||||
// Sequenced assignments of tuples is not valid, make the result a "void" type.
|
||||
_assignment.annotation().type = make_shared<TupleType>();
|
||||
_assignment.annotation().type = TypeProvider::emptyTuple();
|
||||
|
||||
expectType(_assignment.rightHandSide(), *tupleType);
|
||||
|
||||
// expectType does not cause fatal errors, so we have to check again here.
|
||||
if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get()))
|
||||
if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide())))
|
||||
checkDoubleStorageAssignment(_assignment);
|
||||
}
|
||||
else if (_assignment.assignmentOperator() == Token::Assign)
|
||||
@ -1228,14 +1229,14 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
if (components.size() == 1)
|
||||
_tuple.annotation().type = type(*components[0]);
|
||||
else
|
||||
_tuple.annotation().type = make_shared<TupleType>(types);
|
||||
_tuple.annotation().type = TypeProvider::tuple(move(types));
|
||||
// If some of the components are not LValues, the error is reported above.
|
||||
_tuple.annotation().isLValue = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isPure = true;
|
||||
TypePointer inlineArrayType;
|
||||
TypePointer inlineArrayType = nullptr;
|
||||
|
||||
for (size_t i = 0; i < components.size(); ++i)
|
||||
{
|
||||
@ -1285,14 +1286,14 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
else if (!inlineArrayType->canLiveOutsideStorage())
|
||||
m_errorReporter.fatalTypeError(_tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage.");
|
||||
|
||||
_tuple.annotation().type = make_shared<ArrayType>(DataLocation::Memory, inlineArrayType, types.size());
|
||||
_tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (components.size() == 1)
|
||||
_tuple.annotation().type = type(*components[0]);
|
||||
else
|
||||
_tuple.annotation().type = make_shared<TupleType>(types);
|
||||
_tuple.annotation().type = TypeProvider::tuple(move(types));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1349,7 +1350,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
|
||||
_operation.annotation().commonType = commonType;
|
||||
_operation.annotation().type =
|
||||
TokenTraits::isCompareOp(_operation.getOperator()) ?
|
||||
make_shared<BoolType>() :
|
||||
TypeProvider::boolean() :
|
||||
commonType;
|
||||
_operation.annotation().isPure =
|
||||
_operation.leftExpression().annotation().isPure &&
|
||||
@ -1401,20 +1402,20 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
||||
);
|
||||
else
|
||||
{
|
||||
TypePointer const& argType = type(*arguments.front());
|
||||
Type const* argType = type(*arguments.front());
|
||||
// Resulting data location is memory unless we are converting from a reference
|
||||
// type with a different data location.
|
||||
// (data location cannot yet be specified for type conversions)
|
||||
DataLocation dataLoc = DataLocation::Memory;
|
||||
if (auto argRefType = dynamic_cast<ReferenceType const*>(argType.get()))
|
||||
if (auto argRefType = dynamic_cast<ReferenceType const*>(argType))
|
||||
dataLoc = argRefType->location();
|
||||
if (auto type = dynamic_cast<ReferenceType const*>(resultType.get()))
|
||||
resultType = type->copyForLocation(dataLoc, type->isPointer());
|
||||
if (auto type = dynamic_cast<ReferenceType const*>(resultType))
|
||||
resultType = TypeProvider::withLocation(type, dataLoc, type->isPointer());
|
||||
if (argType->isExplicitlyConvertibleTo(*resultType))
|
||||
{
|
||||
if (auto argArrayType = dynamic_cast<ArrayType const*>(argType.get()))
|
||||
if (auto argArrayType = dynamic_cast<ArrayType const*>(argType))
|
||||
{
|
||||
auto resultArrayType = dynamic_cast<ArrayType const*>(resultType.get());
|
||||
auto resultArrayType = dynamic_cast<ArrayType const*>(resultType);
|
||||
solAssert(!!resultArrayType, "");
|
||||
solAssert(
|
||||
argArrayType->location() != DataLocation::Storage ||
|
||||
@ -1436,9 +1437,9 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
||||
argType->category() == Type::Category::Address
|
||||
)
|
||||
{
|
||||
solAssert(dynamic_cast<ContractType const*>(resultType.get())->isPayable(), "");
|
||||
solAssert(dynamic_cast<ContractType const*>(resultType)->isPayable(), "");
|
||||
solAssert(
|
||||
dynamic_cast<AddressType const*>(argType.get())->stateMutability() <
|
||||
dynamic_cast<AddressType const*>(argType)->stateMutability() <
|
||||
StateMutability::Payable,
|
||||
""
|
||||
);
|
||||
@ -1474,10 +1475,8 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
||||
}
|
||||
if (resultType->category() == Type::Category::Address)
|
||||
{
|
||||
bool const payable = argType->isExplicitlyConvertibleTo(AddressType::addressPayable());
|
||||
resultType = make_shared<AddressType>(
|
||||
payable ? StateMutability::Payable : StateMutability::NonPayable
|
||||
);
|
||||
bool const payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress());
|
||||
resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address();
|
||||
}
|
||||
}
|
||||
return resultType;
|
||||
@ -1829,17 +1828,17 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
|
||||
_functionCall.expression().accept(*this);
|
||||
|
||||
TypePointer const& expressionType = type(_functionCall.expression());
|
||||
Type const* expressionType = type(_functionCall.expression());
|
||||
|
||||
// Determine function call kind and function type for this FunctionCall node
|
||||
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
|
||||
FunctionTypePointer functionType;
|
||||
FunctionTypePointer functionType = nullptr;
|
||||
|
||||
// Determine and assign function call kind, purity and function type for this FunctionCall node
|
||||
switch (expressionType->category())
|
||||
{
|
||||
case Type::Category::Function:
|
||||
functionType = dynamic_pointer_cast<FunctionType const>(expressionType);
|
||||
functionType = dynamic_cast<FunctionType const*>(expressionType);
|
||||
funcCallAnno.kind = FunctionCallKind::FunctionCall;
|
||||
|
||||
// Purity for function calls also depends upon the callee and its FunctionType
|
||||
@ -1926,7 +1925,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
|
||||
funcCallAnno.type = returnTypes.size() == 1 ?
|
||||
move(returnTypes.front()) :
|
||||
make_shared<TupleType>(move(returnTypes));
|
||||
TypeProvider::tuple(move(returnTypes));
|
||||
|
||||
break;
|
||||
}
|
||||
@ -1936,7 +1935,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
// for non-callables, ensure error reported and annotate node to void function
|
||||
solAssert(m_errorReporter.hasErrors(), "");
|
||||
funcCallAnno.kind = FunctionCallKind::FunctionCall;
|
||||
funcCallAnno.type = make_shared<TupleType>();
|
||||
funcCallAnno.type = TypeProvider::emptyTuple();
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1998,9 +1997,9 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||
_newExpression.typeName().location(),
|
||||
"Length has to be placed in parentheses after the array type for new expression."
|
||||
);
|
||||
type = ReferenceType::copyForLocationIfReference(DataLocation::Memory, type);
|
||||
_newExpression.annotation().type = make_shared<FunctionType>(
|
||||
TypePointers{make_shared<IntegerType>(256)},
|
||||
type = TypeProvider::withLocationIfReference(DataLocation::Memory, type);
|
||||
_newExpression.annotation().type = TypeProvider::function(
|
||||
TypePointers{TypeProvider::uint256()},
|
||||
TypePointers{type},
|
||||
strings(1, ""),
|
||||
strings(1, ""),
|
||||
@ -2044,7 +2043,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
if (initialMemberCount == 0)
|
||||
{
|
||||
// Try to see if the member was removed because it is only available for storage types.
|
||||
auto storageType = ReferenceType::copyForLocationIfReference(
|
||||
auto storageType = TypeProvider::withLocationIfReference(
|
||||
DataLocation::Storage,
|
||||
exprType
|
||||
);
|
||||
@ -2059,7 +2058,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
string errorMsg = "Member \"" + memberName + "\" not found or not visible "
|
||||
"after argument-dependent lookup in " + exprType->toString() + ".";
|
||||
|
||||
if (auto const& funType = dynamic_pointer_cast<FunctionType const>(exprType))
|
||||
if (auto const& funType = dynamic_cast<FunctionType const*>(exprType))
|
||||
{
|
||||
auto const& t = funType->returnParameterTypes();
|
||||
|
||||
@ -2079,7 +2078,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
}
|
||||
else if (exprType->category() == Type::Category::Contract)
|
||||
{
|
||||
for (auto const& addressMember: AddressType::addressPayable().nativeMembers(nullptr))
|
||||
for (auto const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr))
|
||||
if (addressMember.name == memberName)
|
||||
{
|
||||
Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
|
||||
@ -2088,7 +2087,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (auto addressType = dynamic_cast<AddressType const*>(exprType.get()))
|
||||
else if (auto addressType = dynamic_cast<AddressType const*>(exprType))
|
||||
{
|
||||
// Trigger error when using send or transfer with a non-payable fallback function.
|
||||
if (memberName == "send" || memberName == "transfer")
|
||||
@ -2118,14 +2117,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
annotation.referencedDeclaration = possibleMembers.front().declaration;
|
||||
annotation.type = possibleMembers.front().type;
|
||||
|
||||
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type.get()))
|
||||
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
|
||||
solAssert(
|
||||
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
|
||||
"Function \"" + memberName + "\" cannot be called on an object of type " +
|
||||
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
|
||||
);
|
||||
|
||||
if (auto const* structType = dynamic_cast<StructType const*>(exprType.get()))
|
||||
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
|
||||
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
|
||||
else if (exprType->category() == Type::Category::Array)
|
||||
{
|
||||
@ -2138,18 +2137,18 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
}
|
||||
else if (exprType->category() == Type::Category::FixedBytes)
|
||||
annotation.isLValue = false;
|
||||
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType.get()))
|
||||
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
|
||||
{
|
||||
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType().get()))
|
||||
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
|
||||
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
||||
}
|
||||
|
||||
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
|
||||
// although every subexpression is, so leaving this limited for now.
|
||||
if (auto tt = dynamic_cast<TypeType const*>(exprType.get()))
|
||||
if (auto tt = dynamic_cast<TypeType const*>(exprType))
|
||||
if (tt->actualType()->category() == Type::Category::Enum)
|
||||
annotation.isPure = true;
|
||||
if (auto magicType = dynamic_cast<MagicType const*>(exprType.get()))
|
||||
if (auto magicType = dynamic_cast<MagicType const*>(exprType))
|
||||
{
|
||||
if (magicType->kind() == MagicType::Kind::ABI)
|
||||
annotation.isPure = true;
|
||||
@ -2178,7 +2177,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
{
|
||||
_access.baseExpression().accept(*this);
|
||||
TypePointer baseType = type(_access.baseExpression());
|
||||
TypePointer resultType;
|
||||
TypePointer resultType = nullptr;
|
||||
bool isLValue = false;
|
||||
bool isPure = _access.baseExpression().annotation().isPure;
|
||||
Expression const* index = _access.indexExpression();
|
||||
@ -2196,9 +2195,9 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
}
|
||||
else
|
||||
{
|
||||
expectType(*index, IntegerType::uint256());
|
||||
expectType(*index, *TypeProvider::uint256());
|
||||
if (!m_errorReporter.hasErrors())
|
||||
if (auto numberType = dynamic_cast<RationalNumberType const*>(type(*index).get()))
|
||||
if (auto numberType = dynamic_cast<RationalNumberType const*>(type(*index)))
|
||||
{
|
||||
solAssert(!numberType->isFractional(), "");
|
||||
if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr))
|
||||
@ -2223,16 +2222,16 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
case Type::Category::TypeType:
|
||||
{
|
||||
TypeType const& typeType = dynamic_cast<TypeType const&>(*baseType);
|
||||
if (dynamic_cast<ContractType const*>(typeType.actualType().get()))
|
||||
if (dynamic_cast<ContractType const*>(typeType.actualType()))
|
||||
m_errorReporter.typeError(_access.location(), "Index access for contracts or libraries is not possible.");
|
||||
if (!index)
|
||||
resultType = make_shared<TypeType>(make_shared<ArrayType>(DataLocation::Memory, typeType.actualType()));
|
||||
resultType = TypeProvider::typeType(TypeProvider::array(DataLocation::Memory, typeType.actualType()));
|
||||
else
|
||||
{
|
||||
u256 length = 1;
|
||||
if (expectType(*index, IntegerType::uint256()))
|
||||
if (expectType(*index, *TypeProvider::uint256()))
|
||||
{
|
||||
if (auto indexValue = dynamic_cast<RationalNumberType const*>(type(*index).get()))
|
||||
if (auto indexValue = dynamic_cast<RationalNumberType const*>(type(*index)))
|
||||
length = indexValue->literalValue(nullptr);
|
||||
else
|
||||
m_errorReporter.fatalTypeError(index->location(), "Integer constant expected.");
|
||||
@ -2240,7 +2239,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
else
|
||||
solAssert(m_errorReporter.hasErrors(), "Expected errors as expectType returned false");
|
||||
|
||||
resultType = make_shared<TypeType>(make_shared<ArrayType>(
|
||||
resultType = TypeProvider::typeType(TypeProvider::array(
|
||||
DataLocation::Memory,
|
||||
typeType.actualType(),
|
||||
length
|
||||
@ -2255,13 +2254,13 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted.");
|
||||
else
|
||||
{
|
||||
if (!expectType(*index, IntegerType::uint256()))
|
||||
if (!expectType(*index, *TypeProvider::uint256()))
|
||||
m_errorReporter.fatalTypeError(_access.location(), "Index expression cannot be represented as an unsigned integer.");
|
||||
if (auto integerType = dynamic_cast<RationalNumberType const*>(type(*index).get()))
|
||||
if (auto integerType = dynamic_cast<RationalNumberType const*>(type(*index)))
|
||||
if (bytesType.numBytes() <= integerType->literalValue(nullptr))
|
||||
m_errorReporter.typeError(_access.location(), "Out of bounds array access.");
|
||||
}
|
||||
resultType = make_shared<FixedBytesType>(1);
|
||||
resultType = TypeProvider::fixedBytes(1);
|
||||
isLValue = false; // @todo this heavily depends on how it is embedded
|
||||
break;
|
||||
}
|
||||
@ -2271,7 +2270,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
"Indexed expression has to be a type, mapping or array (is " + baseType->toString() + ")"
|
||||
);
|
||||
}
|
||||
_access.annotation().type = move(resultType);
|
||||
_access.annotation().type = resultType;
|
||||
_access.annotation().isLValue = isLValue;
|
||||
if (index && !index->annotation().isPure)
|
||||
isPure = false;
|
||||
@ -2337,16 +2336,16 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
|
||||
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
|
||||
{
|
||||
if (dynamic_cast<FunctionType const*>(annotation.type.get()))
|
||||
if (dynamic_cast<FunctionType const*>(annotation.type))
|
||||
annotation.isPure = true;
|
||||
}
|
||||
else if (dynamic_cast<TypeType const*>(annotation.type.get()))
|
||||
else if (dynamic_cast<TypeType const*>(annotation.type))
|
||||
annotation.isPure = true;
|
||||
|
||||
|
||||
// Check for deprecated function names.
|
||||
// The check is done here for the case without an actual function call.
|
||||
if (FunctionType const* fType = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
|
||||
if (FunctionType const* fType = dynamic_cast<FunctionType const*>(_identifier.annotation().type))
|
||||
{
|
||||
if (_identifier.name() == "sha3" && fType->kind() == FunctionType::Kind::KECCAK256)
|
||||
m_errorReporter.typeError(
|
||||
@ -2365,7 +2364,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
|
||||
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
|
||||
{
|
||||
_expr.annotation().type = make_shared<TypeType>(Type::fromElementaryTypeName(_expr.typeName()));
|
||||
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.typeName()));
|
||||
_expr.annotation().isPure = true;
|
||||
}
|
||||
|
||||
@ -2374,7 +2373,7 @@ void TypeChecker::endVisit(Literal const& _literal)
|
||||
if (_literal.looksLikeAddress())
|
||||
{
|
||||
// Assign type here if it even looks like an address. This prevents double errors for invalid addresses
|
||||
_literal.annotation().type = make_shared<AddressType>(StateMutability::Payable);
|
||||
_literal.annotation().type = TypeProvider::payableAddress();
|
||||
|
||||
string msg;
|
||||
if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits
|
||||
@ -2413,7 +2412,7 @@ void TypeChecker::endVisit(Literal const& _literal)
|
||||
);
|
||||
|
||||
if (!_literal.annotation().type)
|
||||
_literal.annotation().type = Type::forLiteral(_literal);
|
||||
_literal.annotation().type = TypeProvider::forLiteral(_literal);
|
||||
|
||||
if (!_literal.annotation().type)
|
||||
m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value.");
|
||||
@ -2460,7 +2459,7 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
|
||||
_expectedType.toString();
|
||||
if (
|
||||
type(_expression)->category() == Type::Category::RationalNumber &&
|
||||
dynamic_pointer_cast<RationalNumberType const>(type(_expression))->isFractional() &&
|
||||
dynamic_cast<RationalNumberType const*>(type(_expression))->isFractional() &&
|
||||
type(_expression)->mobileType()
|
||||
)
|
||||
{
|
||||
|
@ -108,11 +108,11 @@ public:
|
||||
|
||||
private:
|
||||
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
|
||||
void checkInstruction(SourceLocation _location, solidity::Instruction _instruction)
|
||||
void checkInstruction(SourceLocation _location, dev::eth::Instruction _instruction)
|
||||
{
|
||||
if (eth::SemanticInformation::invalidInViewFunctions(_instruction))
|
||||
m_reportMutability(StateMutability::NonPayable, _location);
|
||||
else if (_instruction == Instruction::CALLVALUE)
|
||||
else if (_instruction == dev::eth::Instruction::CALLVALUE)
|
||||
m_reportMutability(StateMutability::Payable, _location);
|
||||
else if (eth::SemanticInformation::invalidInPureFunctions(_instruction))
|
||||
m_reportMutability(StateMutability::View, _location);
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/ast/AST_accept.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libdevcore/Keccak256.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@ -105,7 +106,7 @@ ImportAnnotation& ImportDirective::annotation() const
|
||||
TypePointer ImportDirective::type() const
|
||||
{
|
||||
solAssert(!!annotation().sourceUnit, "");
|
||||
return make_shared<ModuleType>(*annotation().sourceUnit);
|
||||
return TypeProvider::module(*annotation().sourceUnit);
|
||||
}
|
||||
|
||||
map<FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
|
||||
@ -188,10 +189,10 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::inter
|
||||
vector<FunctionTypePointer> functions;
|
||||
for (FunctionDefinition const* f: contract->definedFunctions())
|
||||
if (f->isPartOfExternalInterface())
|
||||
functions.push_back(make_shared<FunctionType>(*f, false));
|
||||
functions.push_back(TypeProvider::function(*f, false));
|
||||
for (VariableDeclaration const* v: contract->stateVariables())
|
||||
if (v->isPartOfExternalInterface())
|
||||
functions.push_back(make_shared<FunctionType>(*v));
|
||||
functions.push_back(TypeProvider::function(*v));
|
||||
for (FunctionTypePointer const& fun: functions)
|
||||
{
|
||||
if (!fun->interfaceFunctionType())
|
||||
@ -214,16 +215,12 @@ vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
|
||||
{
|
||||
if (!m_inheritableMembers)
|
||||
{
|
||||
set<string> memberSeen;
|
||||
m_inheritableMembers.reset(new vector<Declaration const*>());
|
||||
auto addInheritableMember = [&](Declaration const* _decl)
|
||||
{
|
||||
solAssert(_decl, "addInheritableMember got a nullpointer.");
|
||||
if (memberSeen.count(_decl->name()) == 0 && _decl->isVisibleInDerivedContracts())
|
||||
{
|
||||
memberSeen.insert(_decl->name());
|
||||
if (_decl->isVisibleInDerivedContracts())
|
||||
m_inheritableMembers->push_back(_decl);
|
||||
}
|
||||
};
|
||||
|
||||
for (FunctionDefinition const* f: definedFunctions())
|
||||
@ -246,7 +243,7 @@ vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
|
||||
|
||||
TypePointer ContractDefinition::type() const
|
||||
{
|
||||
return make_shared<TypeType>(make_shared<ContractType>(*this));
|
||||
return TypeProvider::typeType(TypeProvider::contract(*this));
|
||||
}
|
||||
|
||||
ContractDefinitionAnnotation& ContractDefinition::annotation() const
|
||||
@ -265,7 +262,7 @@ TypeNameAnnotation& TypeName::annotation() const
|
||||
|
||||
TypePointer StructDefinition::type() const
|
||||
{
|
||||
return make_shared<TypeType>(make_shared<StructType>(*this));
|
||||
return TypeProvider::typeType(TypeProvider::structType(*this, DataLocation::Storage));
|
||||
}
|
||||
|
||||
TypeDeclarationAnnotation& StructDefinition::annotation() const
|
||||
@ -279,12 +276,12 @@ TypePointer EnumValue::type() const
|
||||
{
|
||||
auto parentDef = dynamic_cast<EnumDefinition const*>(scope());
|
||||
solAssert(parentDef, "Enclosing Scope of EnumValue was not set");
|
||||
return make_shared<EnumType>(*parentDef);
|
||||
return TypeProvider::enumType(*parentDef);
|
||||
}
|
||||
|
||||
TypePointer EnumDefinition::type() const
|
||||
{
|
||||
return make_shared<TypeType>(make_shared<EnumType>(*this));
|
||||
return TypeProvider::typeType(TypeProvider::enumType(*this));
|
||||
}
|
||||
|
||||
TypeDeclarationAnnotation& EnumDefinition::annotation() const
|
||||
@ -312,7 +309,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
|
||||
case Declaration::Visibility::Private:
|
||||
case Declaration::Visibility::Internal:
|
||||
case Declaration::Visibility::Public:
|
||||
return make_shared<FunctionType>(*this, _internal);
|
||||
return TypeProvider::function(*this, _internal);
|
||||
case Declaration::Visibility::External:
|
||||
return {};
|
||||
}
|
||||
@ -328,7 +325,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
|
||||
return {};
|
||||
case Declaration::Visibility::Public:
|
||||
case Declaration::Visibility::External:
|
||||
return make_shared<FunctionType>(*this, _internal);
|
||||
return TypeProvider::function(*this, _internal);
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,12 +336,12 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
|
||||
TypePointer FunctionDefinition::type() const
|
||||
{
|
||||
solAssert(visibility() != Declaration::Visibility::External, "");
|
||||
return make_shared<FunctionType>(*this);
|
||||
return TypeProvider::function(*this);
|
||||
}
|
||||
|
||||
string FunctionDefinition::externalSignature() const
|
||||
{
|
||||
return FunctionType(*this).externalSignature();
|
||||
return TypeProvider::function(*this)->externalSignature();
|
||||
}
|
||||
|
||||
FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
|
||||
@ -356,7 +353,7 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
|
||||
|
||||
TypePointer ModifierDefinition::type() const
|
||||
{
|
||||
return make_shared<ModifierType>(*this);
|
||||
return TypeProvider::modifier(*this);
|
||||
}
|
||||
|
||||
ModifierDefinitionAnnotation& ModifierDefinition::annotation() const
|
||||
@ -368,15 +365,15 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const
|
||||
|
||||
TypePointer EventDefinition::type() const
|
||||
{
|
||||
return make_shared<FunctionType>(*this);
|
||||
return TypeProvider::function(*this);
|
||||
}
|
||||
|
||||
FunctionTypePointer EventDefinition::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
return make_shared<FunctionType>(*this);
|
||||
return TypeProvider::function(*this);
|
||||
else
|
||||
return {};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EventDefinitionAnnotation& EventDefinition::annotation() const
|
||||
@ -508,8 +505,8 @@ bool VariableDeclaration::hasReferenceOrMappingType() const
|
||||
{
|
||||
solAssert(typeName(), "");
|
||||
solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
|
||||
TypePointer const& type = typeName()->annotation().type;
|
||||
return type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type.get());
|
||||
Type const* type = typeName()->annotation().type;
|
||||
return type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type);
|
||||
}
|
||||
|
||||
set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const
|
||||
@ -557,21 +554,21 @@ TypePointer VariableDeclaration::type() const
|
||||
FunctionTypePointer VariableDeclaration::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
return {};
|
||||
return nullptr;
|
||||
switch (visibility())
|
||||
{
|
||||
case Declaration::Visibility::Default:
|
||||
solAssert(false, "visibility() should not return Default");
|
||||
case Declaration::Visibility::Private:
|
||||
case Declaration::Visibility::Internal:
|
||||
return {};
|
||||
return nullptr;
|
||||
case Declaration::Visibility::Public:
|
||||
case Declaration::Visibility::External:
|
||||
return make_shared<FunctionType>(*this);
|
||||
return TypeProvider::function(*this);
|
||||
}
|
||||
|
||||
// To make the compiler happy
|
||||
return {};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VariableDeclarationAnnotation& VariableDeclaration::annotation() const
|
||||
|
@ -848,8 +848,9 @@ private:
|
||||
class MagicVariableDeclaration: public Declaration
|
||||
{
|
||||
public:
|
||||
MagicVariableDeclaration(ASTString const& _name, std::shared_ptr<Type const> const& _type):
|
||||
MagicVariableDeclaration(ASTString const& _name, Type const* _type):
|
||||
Declaration(SourceLocation(), std::make_shared<ASTString>(_name)), m_type(_type) {}
|
||||
|
||||
void accept(ASTVisitor&) override
|
||||
{
|
||||
solAssert(false, "MagicVariableDeclaration used inside real AST.");
|
||||
@ -859,15 +860,15 @@ public:
|
||||
solAssert(false, "MagicVariableDeclaration used inside real AST.");
|
||||
}
|
||||
|
||||
FunctionTypePointer functionType(bool) const override
|
||||
FunctionType const* functionType(bool) const override
|
||||
{
|
||||
solAssert(m_type->category() == Type::Category::Function, "");
|
||||
return std::dynamic_pointer_cast<FunctionType const>(m_type);
|
||||
return dynamic_cast<FunctionType const*>(m_type);
|
||||
}
|
||||
TypePointer type() const override { return m_type; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Type const> m_type;
|
||||
Type const* m_type;
|
||||
};
|
||||
|
||||
/// Types
|
||||
|
@ -45,7 +45,7 @@ namespace solidity
|
||||
{
|
||||
|
||||
class Type;
|
||||
using TypePointer = std::shared_ptr<Type const>;
|
||||
using TypePointer = Type const*;
|
||||
|
||||
struct ASTAnnotation
|
||||
{
|
||||
@ -122,7 +122,7 @@ struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||
struct VariableDeclarationAnnotation: ASTAnnotation
|
||||
{
|
||||
/// Type of variable (type of identifier referencing this variable).
|
||||
TypePointer type;
|
||||
TypePointer type = nullptr;
|
||||
};
|
||||
|
||||
struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||
@ -155,7 +155,7 @@ struct TypeNameAnnotation: ASTAnnotation
|
||||
{
|
||||
/// Type declared by this type name, i.e. type of a variable where this type name is used.
|
||||
/// Set during reference resolution stage.
|
||||
TypePointer type;
|
||||
TypePointer type = nullptr;
|
||||
};
|
||||
|
||||
struct UserDefinedTypeNameAnnotation: TypeNameAnnotation
|
||||
@ -170,7 +170,7 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation
|
||||
struct ExpressionAnnotation: ASTAnnotation
|
||||
{
|
||||
/// Inferred type of the expression.
|
||||
TypePointer type;
|
||||
TypePointer type = nullptr;
|
||||
/// Whether the expression is a constant variable
|
||||
bool isConstant = false;
|
||||
/// Whether the expression is pure, i.e. compile-time constant.
|
||||
@ -203,7 +203,7 @@ struct BinaryOperationAnnotation: ExpressionAnnotation
|
||||
{
|
||||
/// The common type that is used for the operation, not necessarily the result type (which
|
||||
/// e.g. for comparisons is bool).
|
||||
TypePointer commonType;
|
||||
TypePointer commonType = nullptr;
|
||||
};
|
||||
|
||||
enum class FunctionCallKind
|
||||
|
@ -57,7 +57,7 @@ class Type;
|
||||
struct FuncCallArguments
|
||||
{
|
||||
/// Types of arguments
|
||||
std::vector<std::shared_ptr<Type const>> types;
|
||||
std::vector<Type const*> types;
|
||||
/// Names of the arguments if given, otherwise unset
|
||||
std::vector<ASTPointer<ASTString>> names;
|
||||
|
||||
|
541
libsolidity/ast/TypeProvider.cpp
Normal file
541
libsolidity/ast/TypeProvider.cpp
Normal file
@ -0,0 +1,541 @@
|
||||
/*
|
||||
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 <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace solidity;
|
||||
|
||||
BoolType const TypeProvider::m_boolean{};
|
||||
InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{};
|
||||
|
||||
/// The string and bytes unique_ptrs are initialized when they are first used because
|
||||
/// they rely on `byte` being available which we cannot guarantee in the static init context.
|
||||
unique_ptr<ArrayType> TypeProvider::m_bytesStorage;
|
||||
unique_ptr<ArrayType> TypeProvider::m_bytesMemory;
|
||||
unique_ptr<ArrayType> TypeProvider::m_stringStorage;
|
||||
unique_ptr<ArrayType> TypeProvider::m_stringMemory;
|
||||
|
||||
TupleType const TypeProvider::m_emptyTuple{};
|
||||
AddressType const TypeProvider::m_payableAddress{StateMutability::Payable};
|
||||
AddressType const TypeProvider::m_address{StateMutability::NonPayable};
|
||||
|
||||
array<unique_ptr<IntegerType>, 32> const TypeProvider::m_intM{{
|
||||
{make_unique<IntegerType>(8 * 1, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 2, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 3, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 4, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 5, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 6, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 7, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 8, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 9, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 10, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 11, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 12, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 13, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 14, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 15, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 16, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 17, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 18, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 19, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 20, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 21, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 22, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 23, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 24, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 25, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 26, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 27, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 28, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 29, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 30, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 31, IntegerType::Modifier::Signed)},
|
||||
{make_unique<IntegerType>(8 * 32, IntegerType::Modifier::Signed)}
|
||||
}};
|
||||
|
||||
array<unique_ptr<IntegerType>, 32> const TypeProvider::m_uintM{{
|
||||
{make_unique<IntegerType>(8 * 1, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 2, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 3, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 4, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 5, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 6, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 7, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 8, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 9, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 10, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 11, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 12, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 13, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 14, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 15, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 16, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 17, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 18, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 19, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 20, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 21, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 22, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 23, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 24, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 25, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 26, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 27, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 28, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 29, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 30, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 31, IntegerType::Modifier::Unsigned)},
|
||||
{make_unique<IntegerType>(8 * 32, IntegerType::Modifier::Unsigned)}
|
||||
}};
|
||||
|
||||
array<unique_ptr<FixedBytesType>, 32> const TypeProvider::m_bytesM{{
|
||||
{make_unique<FixedBytesType>(1)},
|
||||
{make_unique<FixedBytesType>(2)},
|
||||
{make_unique<FixedBytesType>(3)},
|
||||
{make_unique<FixedBytesType>(4)},
|
||||
{make_unique<FixedBytesType>(5)},
|
||||
{make_unique<FixedBytesType>(6)},
|
||||
{make_unique<FixedBytesType>(7)},
|
||||
{make_unique<FixedBytesType>(8)},
|
||||
{make_unique<FixedBytesType>(9)},
|
||||
{make_unique<FixedBytesType>(10)},
|
||||
{make_unique<FixedBytesType>(11)},
|
||||
{make_unique<FixedBytesType>(12)},
|
||||
{make_unique<FixedBytesType>(13)},
|
||||
{make_unique<FixedBytesType>(14)},
|
||||
{make_unique<FixedBytesType>(15)},
|
||||
{make_unique<FixedBytesType>(16)},
|
||||
{make_unique<FixedBytesType>(17)},
|
||||
{make_unique<FixedBytesType>(18)},
|
||||
{make_unique<FixedBytesType>(19)},
|
||||
{make_unique<FixedBytesType>(20)},
|
||||
{make_unique<FixedBytesType>(21)},
|
||||
{make_unique<FixedBytesType>(22)},
|
||||
{make_unique<FixedBytesType>(23)},
|
||||
{make_unique<FixedBytesType>(24)},
|
||||
{make_unique<FixedBytesType>(25)},
|
||||
{make_unique<FixedBytesType>(26)},
|
||||
{make_unique<FixedBytesType>(27)},
|
||||
{make_unique<FixedBytesType>(28)},
|
||||
{make_unique<FixedBytesType>(29)},
|
||||
{make_unique<FixedBytesType>(30)},
|
||||
{make_unique<FixedBytesType>(31)},
|
||||
{make_unique<FixedBytesType>(32)}
|
||||
}};
|
||||
|
||||
array<unique_ptr<MagicType>, 4> const TypeProvider::m_magics{{
|
||||
{make_unique<MagicType>(MagicType::Kind::Block)},
|
||||
{make_unique<MagicType>(MagicType::Kind::Message)},
|
||||
{make_unique<MagicType>(MagicType::Kind::Transaction)},
|
||||
{make_unique<MagicType>(MagicType::Kind::ABI)}
|
||||
// MetaType is stored separately
|
||||
}};
|
||||
|
||||
inline void clearCache(Type const& type)
|
||||
{
|
||||
type.clearCache();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void clearCache(unique_ptr<T> const& type)
|
||||
{
|
||||
// Some lazy-initialized types might not exist yet.
|
||||
if (type)
|
||||
type->clearCache();
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
inline void clearCaches(Container& container)
|
||||
{
|
||||
for (auto const& e: container)
|
||||
clearCache(e);
|
||||
}
|
||||
|
||||
void TypeProvider::reset()
|
||||
{
|
||||
clearCache(m_boolean);
|
||||
clearCache(m_inaccessibleDynamic);
|
||||
clearCache(m_bytesStorage);
|
||||
clearCache(m_bytesMemory);
|
||||
clearCache(m_stringStorage);
|
||||
clearCache(m_stringMemory);
|
||||
clearCache(m_emptyTuple);
|
||||
clearCache(m_payableAddress);
|
||||
clearCache(m_address);
|
||||
clearCaches(instance().m_intM);
|
||||
clearCaches(instance().m_uintM);
|
||||
clearCaches(instance().m_bytesM);
|
||||
clearCaches(instance().m_magics);
|
||||
|
||||
instance().m_generalTypes.clear();
|
||||
instance().m_stringLiteralTypes.clear();
|
||||
instance().m_ufixedMxN.clear();
|
||||
instance().m_fixedMxN.clear();
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline T const* TypeProvider::createAndGet(Args&& ... _args)
|
||||
{
|
||||
instance().m_generalTypes.emplace_back(make_unique<T>(std::forward<Args>(_args)...));
|
||||
return static_cast<T const*>(instance().m_generalTypes.back().get());
|
||||
}
|
||||
|
||||
Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
|
||||
{
|
||||
solAssert(
|
||||
TokenTraits::isElementaryTypeName(_type.token()),
|
||||
"Expected an elementary type name but got " + _type.toString()
|
||||
);
|
||||
|
||||
unsigned const m = _type.firstNumber();
|
||||
unsigned const n = _type.secondNumber();
|
||||
|
||||
switch (_type.token())
|
||||
{
|
||||
case Token::IntM:
|
||||
return integer(m, IntegerType::Modifier::Signed);
|
||||
case Token::UIntM:
|
||||
return integer(m, IntegerType::Modifier::Unsigned);
|
||||
case Token::Byte:
|
||||
return byte();
|
||||
case Token::BytesM:
|
||||
return fixedBytes(m);
|
||||
case Token::FixedMxN:
|
||||
return fixedPoint(m, n, FixedPointType::Modifier::Signed);
|
||||
case Token::UFixedMxN:
|
||||
return fixedPoint(m, n, FixedPointType::Modifier::Unsigned);
|
||||
case Token::Int:
|
||||
return integer(256, IntegerType::Modifier::Signed);
|
||||
case Token::UInt:
|
||||
return integer(256, IntegerType::Modifier::Unsigned);
|
||||
case Token::Fixed:
|
||||
return fixedPoint(128, 18, FixedPointType::Modifier::Signed);
|
||||
case Token::UFixed:
|
||||
return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned);
|
||||
case Token::Address:
|
||||
return address();
|
||||
case Token::Bool:
|
||||
return boolean();
|
||||
case Token::Bytes:
|
||||
return bytesStorage();
|
||||
case Token::String:
|
||||
return stringStorage();
|
||||
default:
|
||||
solAssert(
|
||||
false,
|
||||
"Unable to convert elementary typename " + _type.toString() + " to type."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TypePointer TypeProvider::fromElementaryTypeName(string const& _name)
|
||||
{
|
||||
vector<string> nameParts;
|
||||
boost::split(nameParts, _name, boost::is_any_of(" "));
|
||||
solAssert(nameParts.size() == 1 || nameParts.size() == 2, "Cannot parse elementary type: " + _name);
|
||||
|
||||
Token token;
|
||||
unsigned short firstNum, secondNum;
|
||||
tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(nameParts[0]);
|
||||
|
||||
auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum));
|
||||
if (auto* ref = dynamic_cast<ReferenceType const*>(t))
|
||||
{
|
||||
DataLocation location = DataLocation::Storage;
|
||||
if (nameParts.size() == 2)
|
||||
{
|
||||
if (nameParts[1] == "storage")
|
||||
location = DataLocation::Storage;
|
||||
else if (nameParts[1] == "calldata")
|
||||
location = DataLocation::CallData;
|
||||
else if (nameParts[1] == "memory")
|
||||
location = DataLocation::Memory;
|
||||
else
|
||||
solAssert(false, "Unknown data location: " + nameParts[1]);
|
||||
}
|
||||
return withLocation(ref, location, true);
|
||||
}
|
||||
else if (t->category() == Type::Category::Address)
|
||||
{
|
||||
if (nameParts.size() == 2)
|
||||
{
|
||||
if (nameParts[1] == "payable")
|
||||
return payableAddress();
|
||||
else
|
||||
solAssert(false, "Invalid state mutability for address type: " + nameParts[1]);
|
||||
}
|
||||
return address();
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(nameParts.size() == 1, "Storage location suffix only allowed for reference types");
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayType const* TypeProvider::bytesStorage()
|
||||
{
|
||||
if (!m_bytesStorage)
|
||||
m_bytesStorage = make_unique<ArrayType>(DataLocation::Storage, false);
|
||||
return m_bytesStorage.get();
|
||||
}
|
||||
|
||||
ArrayType const* TypeProvider::bytesMemory()
|
||||
{
|
||||
if (!m_bytesMemory)
|
||||
m_bytesMemory = make_unique<ArrayType>(DataLocation::Memory, false);
|
||||
return m_bytesMemory.get();
|
||||
}
|
||||
|
||||
ArrayType const* TypeProvider::stringStorage()
|
||||
{
|
||||
if (!m_stringStorage)
|
||||
m_stringStorage = make_unique<ArrayType>(DataLocation::Storage, true);
|
||||
return m_stringStorage.get();
|
||||
}
|
||||
|
||||
ArrayType const* TypeProvider::stringMemory()
|
||||
{
|
||||
if (!m_stringMemory)
|
||||
m_stringMemory = make_unique<ArrayType>(DataLocation::Memory, true);
|
||||
return m_stringMemory.get();
|
||||
}
|
||||
|
||||
TypePointer TypeProvider::forLiteral(Literal const& _literal)
|
||||
{
|
||||
switch (_literal.token())
|
||||
{
|
||||
case Token::TrueLiteral:
|
||||
case Token::FalseLiteral:
|
||||
return boolean();
|
||||
case Token::Number:
|
||||
return rationalNumber(_literal);
|
||||
case Token::StringLiteral:
|
||||
return stringLiteral(_literal.value());
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
RationalNumberType const* TypeProvider::rationalNumber(Literal const& _literal)
|
||||
{
|
||||
solAssert(_literal.token() == Token::Number, "");
|
||||
std::tuple<bool, rational> validLiteral = RationalNumberType::isValidLiteral(_literal);
|
||||
if (std::get<0>(validLiteral))
|
||||
{
|
||||
TypePointer compatibleBytesType = nullptr;
|
||||
if (_literal.isHexNumber())
|
||||
{
|
||||
size_t const digitCount = _literal.valueWithoutUnderscores().length() - 2;
|
||||
if (digitCount % 2 == 0 && (digitCount / 2) <= 32)
|
||||
compatibleBytesType = fixedBytes(digitCount / 2);
|
||||
}
|
||||
|
||||
return rationalNumber(std::get<1>(validLiteral), compatibleBytesType);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StringLiteralType const* TypeProvider::stringLiteral(string const& literal)
|
||||
{
|
||||
auto i = instance().m_stringLiteralTypes.find(literal);
|
||||
if (i != instance().m_stringLiteralTypes.end())
|
||||
return i->second.get();
|
||||
else
|
||||
return instance().m_stringLiteralTypes.emplace(literal, make_unique<StringLiteralType>(literal)).first->second.get();
|
||||
}
|
||||
|
||||
FixedPointType const* TypeProvider::fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier)
|
||||
{
|
||||
auto& map = _modifier == FixedPointType::Modifier::Unsigned ? instance().m_ufixedMxN : instance().m_fixedMxN;
|
||||
|
||||
auto i = map.find(make_pair(m, n));
|
||||
if (i != map.end())
|
||||
return i->second.get();
|
||||
|
||||
return map.emplace(
|
||||
make_pair(m, n),
|
||||
make_unique<FixedPointType>(m, n, _modifier)
|
||||
).first->second.get();
|
||||
}
|
||||
|
||||
TupleType const* TypeProvider::tuple(vector<Type const*> members)
|
||||
{
|
||||
if (members.empty())
|
||||
return &m_emptyTuple;
|
||||
|
||||
return createAndGet<TupleType>(move(members));
|
||||
}
|
||||
|
||||
ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer)
|
||||
{
|
||||
if (_type->location() == _location && _type->isPointer() == _isPointer)
|
||||
return _type;
|
||||
|
||||
instance().m_generalTypes.emplace_back(_type->copyForLocation(_location, _isPointer));
|
||||
return static_cast<ReferenceType const*>(instance().m_generalTypes.back().get());
|
||||
}
|
||||
|
||||
FunctionType const* TypeProvider::function(FunctionDefinition const& _function, bool _isInternal)
|
||||
{
|
||||
return createAndGet<FunctionType>(_function, _isInternal);
|
||||
}
|
||||
|
||||
FunctionType const* TypeProvider::function(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
return createAndGet<FunctionType>(_varDecl);
|
||||
}
|
||||
|
||||
FunctionType const* TypeProvider::function(EventDefinition const& _def)
|
||||
{
|
||||
return createAndGet<FunctionType>(_def);
|
||||
}
|
||||
|
||||
FunctionType const* TypeProvider::function(FunctionTypeName const& _typeName)
|
||||
{
|
||||
return createAndGet<FunctionType>(_typeName);
|
||||
}
|
||||
|
||||
FunctionType const* TypeProvider::function(
|
||||
strings const& _parameterTypes,
|
||||
strings const& _returnParameterTypes,
|
||||
FunctionType::Kind _kind,
|
||||
bool _arbitraryParameters,
|
||||
StateMutability _stateMutability
|
||||
)
|
||||
{
|
||||
return createAndGet<FunctionType>(
|
||||
_parameterTypes, _returnParameterTypes,
|
||||
_kind, _arbitraryParameters, _stateMutability
|
||||
);
|
||||
}
|
||||
|
||||
FunctionType const* TypeProvider::function(
|
||||
TypePointers const& _parameterTypes,
|
||||
TypePointers const& _returnParameterTypes,
|
||||
strings _parameterNames,
|
||||
strings _returnParameterNames,
|
||||
FunctionType::Kind _kind,
|
||||
bool _arbitraryParameters,
|
||||
StateMutability _stateMutability,
|
||||
Declaration const* _declaration,
|
||||
bool _gasSet,
|
||||
bool _valueSet,
|
||||
bool _bound
|
||||
)
|
||||
{
|
||||
return createAndGet<FunctionType>(
|
||||
_parameterTypes,
|
||||
_returnParameterTypes,
|
||||
_parameterNames,
|
||||
_returnParameterNames,
|
||||
_kind,
|
||||
_arbitraryParameters,
|
||||
_stateMutability,
|
||||
_declaration,
|
||||
_gasSet,
|
||||
_valueSet,
|
||||
_bound
|
||||
);
|
||||
}
|
||||
|
||||
RationalNumberType const* TypeProvider::rationalNumber(rational const& _value, Type const* _compatibleBytesType)
|
||||
{
|
||||
return createAndGet<RationalNumberType>(_value, _compatibleBytesType);
|
||||
}
|
||||
|
||||
ArrayType const* TypeProvider::array(DataLocation _location, bool _isString)
|
||||
{
|
||||
if (_isString)
|
||||
{
|
||||
if (_location == DataLocation::Storage)
|
||||
return stringStorage();
|
||||
if (_location == DataLocation::Memory)
|
||||
return stringMemory();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_location == DataLocation::Storage)
|
||||
return bytesStorage();
|
||||
if (_location == DataLocation::Memory)
|
||||
return bytesMemory();
|
||||
}
|
||||
return createAndGet<ArrayType>(_location, _isString);
|
||||
}
|
||||
|
||||
ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseType)
|
||||
{
|
||||
return createAndGet<ArrayType>(_location, _baseType);
|
||||
}
|
||||
|
||||
ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseType, u256 const& _length)
|
||||
{
|
||||
return createAndGet<ArrayType>(_location, _baseType, _length);
|
||||
}
|
||||
|
||||
ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper)
|
||||
{
|
||||
return createAndGet<ContractType>(_contractDef, _isSuper);
|
||||
}
|
||||
|
||||
EnumType const* TypeProvider::enumType(EnumDefinition const& _enumDef)
|
||||
{
|
||||
return createAndGet<EnumType>(_enumDef);
|
||||
}
|
||||
|
||||
ModuleType const* TypeProvider::module(SourceUnit const& _source)
|
||||
{
|
||||
return createAndGet<ModuleType>(_source);
|
||||
}
|
||||
|
||||
TypeType const* TypeProvider::typeType(Type const* _actualType)
|
||||
{
|
||||
return createAndGet<TypeType>(_actualType);
|
||||
}
|
||||
|
||||
StructType const* TypeProvider::structType(StructDefinition const& _struct, DataLocation _location)
|
||||
{
|
||||
return createAndGet<StructType>(_struct, _location);
|
||||
}
|
||||
|
||||
ModifierType const* TypeProvider::modifier(ModifierDefinition const& _def)
|
||||
{
|
||||
return createAndGet<ModifierType>(_def);
|
||||
}
|
||||
|
||||
MagicType const* TypeProvider::magic(MagicType::Kind _kind)
|
||||
{
|
||||
solAssert(_kind != MagicType::Kind::MetaType, "MetaType is handled separately");
|
||||
return m_magics.at(static_cast<size_t>(_kind)).get();
|
||||
}
|
||||
|
||||
MagicType const* TypeProvider::meta(Type const* _type)
|
||||
{
|
||||
solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now.");
|
||||
return createAndGet<MagicType>(_type);
|
||||
}
|
||||
|
||||
MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valueType)
|
||||
{
|
||||
return createAndGet<MappingType>(_keyType, _valueType);
|
||||
}
|
224
libsolidity/ast/TypeProvider.h
Normal file
224
libsolidity/ast/TypeProvider.h
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
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 <libsolidity/ast/Types.h>
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
/**
|
||||
* API for accessing the Solidity Type System.
|
||||
*
|
||||
* This is the Solidity Compiler's type provider. Use it to request for types. The caller does
|
||||
* <b>not</b> own the types.
|
||||
*
|
||||
* It is not recommended to explicitly instantiate types unless you really know what and why
|
||||
* you are doing it.
|
||||
*/
|
||||
class TypeProvider
|
||||
{
|
||||
public:
|
||||
TypeProvider() = default;
|
||||
TypeProvider(TypeProvider&&) = default;
|
||||
TypeProvider(TypeProvider const&) = delete;
|
||||
TypeProvider& operator=(TypeProvider&&) = default;
|
||||
TypeProvider& operator=(TypeProvider const&) = delete;
|
||||
~TypeProvider() = default;
|
||||
|
||||
/// Resets state of this TypeProvider to initial state, wiping all mutable types.
|
||||
/// This invalidates all dangling pointers to types provided by this TypeProvider.
|
||||
static void reset();
|
||||
|
||||
/// @name Factory functions
|
||||
/// Factory functions that convert an AST @ref TypeName to a Type.
|
||||
static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type);
|
||||
|
||||
/// Converts a given elementary type name with optional data location
|
||||
/// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage".
|
||||
static TypePointer fromElementaryTypeName(std::string const& _name);
|
||||
|
||||
/// @returns boolean type.
|
||||
static BoolType const* boolean() noexcept { return &m_boolean; }
|
||||
|
||||
static FixedBytesType const* byte() { return fixedBytes(1); }
|
||||
static FixedBytesType const* fixedBytes(unsigned m) { return m_bytesM.at(m - 1).get(); }
|
||||
|
||||
static ArrayType const* bytesStorage();
|
||||
static ArrayType const* bytesMemory();
|
||||
static ArrayType const* stringStorage();
|
||||
static ArrayType const* stringMemory();
|
||||
|
||||
/// Constructor for a byte array ("bytes") and string.
|
||||
static ArrayType const* array(DataLocation _location, bool _isString = false);
|
||||
|
||||
/// Constructor for a dynamically sized array type ("type[]")
|
||||
static ArrayType const* array(DataLocation _location, Type const* _baseType);
|
||||
|
||||
/// Constructor for a fixed-size array type ("type[20]")
|
||||
static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length);
|
||||
|
||||
static AddressType const* payableAddress() noexcept { return &m_payableAddress; }
|
||||
static AddressType const* address() noexcept { return &m_address; }
|
||||
|
||||
static IntegerType const* integer(unsigned _bits, IntegerType::Modifier _modifier)
|
||||
{
|
||||
solAssert((_bits % 8) == 0, "");
|
||||
if (_modifier == IntegerType::Modifier::Unsigned)
|
||||
return m_uintM.at(_bits / 8 - 1).get();
|
||||
else
|
||||
return m_intM.at(_bits / 8 - 1).get();
|
||||
}
|
||||
static IntegerType const* uint(unsigned _bits) { return integer(_bits, IntegerType::Modifier::Unsigned); }
|
||||
|
||||
static IntegerType const* uint256() { return uint(256); }
|
||||
|
||||
static FixedPointType const* fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier);
|
||||
|
||||
static StringLiteralType const* stringLiteral(std::string const& literal);
|
||||
|
||||
/// @param members the member types the tuple type must contain. This is passed by value on purspose.
|
||||
/// @returns a tuple type with the given members.
|
||||
static TupleType const* tuple(std::vector<Type const*> members);
|
||||
|
||||
static TupleType const* emptyTuple() noexcept { return &m_emptyTuple; }
|
||||
|
||||
static ReferenceType const* withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer);
|
||||
|
||||
/// @returns a copy of @a _type having the same location as this (and is not a pointer type)
|
||||
/// if _type is a reference type and an unmodified copy of _type otherwise.
|
||||
/// This function is mostly useful to modify inner types appropriately.
|
||||
static Type const* withLocationIfReference(DataLocation _location, Type const* _type)
|
||||
{
|
||||
if (auto refType = dynamic_cast<ReferenceType const*>(_type))
|
||||
return withLocation(refType, _location, false);
|
||||
|
||||
return _type;
|
||||
}
|
||||
|
||||
/// @returns the internally-facing or externally-facing type of a function.
|
||||
static FunctionType const* function(FunctionDefinition const& _function, bool _isInternal = true);
|
||||
|
||||
/// @returns the accessor function type of a state variable.
|
||||
static FunctionType const* function(VariableDeclaration const& _varDecl);
|
||||
|
||||
/// @returns the function type of an event.
|
||||
static FunctionType const* function(EventDefinition const& _event);
|
||||
|
||||
/// @returns the type of a function type name.
|
||||
static FunctionType const* function(FunctionTypeName const& _typeName);
|
||||
|
||||
/// @returns the function type to be used for a plain type (not derived from a declaration).
|
||||
static FunctionType const* function(
|
||||
strings const& _parameterTypes,
|
||||
strings const& _returnParameterTypes,
|
||||
FunctionType::Kind _kind = FunctionType::Kind::Internal,
|
||||
bool _arbitraryParameters = false,
|
||||
StateMutability _stateMutability = StateMutability::NonPayable
|
||||
);
|
||||
|
||||
/// @returns a highly customized FunctionType, use with care.
|
||||
static FunctionType const* function(
|
||||
TypePointers const& _parameterTypes,
|
||||
TypePointers const& _returnParameterTypes,
|
||||
strings _parameterNames = strings{},
|
||||
strings _returnParameterNames = strings{},
|
||||
FunctionType::Kind _kind = FunctionType::Kind::Internal,
|
||||
bool _arbitraryParameters = false,
|
||||
StateMutability _stateMutability = StateMutability::NonPayable,
|
||||
Declaration const* _declaration = nullptr,
|
||||
bool _gasSet = false,
|
||||
bool _valueSet = false,
|
||||
bool _bound = false
|
||||
);
|
||||
|
||||
/// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does
|
||||
/// not fit any type.
|
||||
static TypePointer forLiteral(Literal const& _literal);
|
||||
static RationalNumberType const* rationalNumber(Literal const& _literal);
|
||||
|
||||
static RationalNumberType const* rationalNumber(
|
||||
rational const& _value,
|
||||
Type const* _compatibleBytesType = nullptr
|
||||
);
|
||||
|
||||
static ContractType const* contract(ContractDefinition const& _contract, bool _isSuper = false);
|
||||
|
||||
static InaccessibleDynamicType const* inaccessibleDynamic() noexcept { return &m_inaccessibleDynamic; }
|
||||
|
||||
/// @returns the type of an enum instance for given definition, there is one distinct type per enum definition.
|
||||
static EnumType const* enumType(EnumDefinition const& _enum);
|
||||
|
||||
/// @returns special type for imported modules. These mainly give access to their scope via members.
|
||||
static ModuleType const* module(SourceUnit const& _source);
|
||||
|
||||
static TypeType const* typeType(Type const* _actualType);
|
||||
|
||||
static StructType const* structType(StructDefinition const& _struct, DataLocation _location);
|
||||
|
||||
static ModifierType const* modifier(ModifierDefinition const& _modifierDef);
|
||||
|
||||
static MagicType const* magic(MagicType::Kind _kind);
|
||||
|
||||
static MagicType const* meta(Type const* _type);
|
||||
|
||||
static MappingType const* mapping(Type const* _keyType, Type const* _valueType);
|
||||
|
||||
private:
|
||||
/// Global TypeProvider instance.
|
||||
static TypeProvider& instance()
|
||||
{
|
||||
static TypeProvider _provider;
|
||||
return _provider;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static inline T const* createAndGet(Args&& ... _args);
|
||||
|
||||
static BoolType const m_boolean;
|
||||
static InaccessibleDynamicType const m_inaccessibleDynamic;
|
||||
|
||||
/// These are lazy-initialized because they depend on `byte` being available.
|
||||
static std::unique_ptr<ArrayType> m_bytesStorage;
|
||||
static std::unique_ptr<ArrayType> m_bytesMemory;
|
||||
static std::unique_ptr<ArrayType> m_stringStorage;
|
||||
static std::unique_ptr<ArrayType> m_stringMemory;
|
||||
|
||||
static TupleType const m_emptyTuple;
|
||||
static AddressType const m_payableAddress;
|
||||
static AddressType const m_address;
|
||||
static std::array<std::unique_ptr<IntegerType>, 32> const m_intM;
|
||||
static std::array<std::unique_ptr<IntegerType>, 32> const m_uintM;
|
||||
static std::array<std::unique_ptr<FixedBytesType>, 32> const m_bytesM;
|
||||
static std::array<std::unique_ptr<MagicType>, 4> const m_magics; ///< MagicType's except MetaType
|
||||
|
||||
std::map<std::pair<unsigned, unsigned>, std::unique_ptr<FixedPointType>> m_ufixedMxN{};
|
||||
std::map<std::pair<unsigned, unsigned>, std::unique_ptr<FixedPointType>> m_fixedMxN{};
|
||||
std::map<std::string, std::unique_ptr<StringLiteralType>> m_stringLiteralTypes{};
|
||||
std::vector<std::unique_ptr<Type>> m_generalTypes{};
|
||||
};
|
||||
|
||||
} // namespace solidity
|
||||
} // namespace dev
|
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,6 @@
|
||||
#include <libdevcore/Result.h>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
|
||||
#include <map>
|
||||
@ -45,10 +44,11 @@ namespace dev
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class TypeProvider;
|
||||
class Type; // forward
|
||||
class FunctionType; // forward
|
||||
using TypePointer = std::shared_ptr<Type const>;
|
||||
using FunctionTypePointer = std::shared_ptr<FunctionType const>;
|
||||
using TypePointer = Type const*;
|
||||
using FunctionTypePointer = FunctionType const*;
|
||||
using TypePointers = std::vector<TypePointer>;
|
||||
using rational = boost::rational<dev::bigint>;
|
||||
using TypeResult = Result<TypePointer>;
|
||||
@ -94,7 +94,7 @@ class MemberList
|
||||
public:
|
||||
struct Member
|
||||
{
|
||||
Member(std::string const& _name, TypePointer const& _type, Declaration const* _declaration = nullptr):
|
||||
Member(std::string const& _name, Type const* _type, Declaration const* _declaration = nullptr):
|
||||
name(_name),
|
||||
type(_type),
|
||||
declaration(_declaration)
|
||||
@ -102,17 +102,18 @@ public:
|
||||
}
|
||||
|
||||
std::string name;
|
||||
TypePointer type;
|
||||
Type const* type;
|
||||
Declaration const* declaration = nullptr;
|
||||
};
|
||||
|
||||
using MemberMap = std::vector<Member>;
|
||||
|
||||
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {}
|
||||
|
||||
void combine(MemberList const& _other);
|
||||
TypePointer memberType(std::string const& _name) const
|
||||
{
|
||||
TypePointer type;
|
||||
TypePointer type = nullptr;
|
||||
for (auto const& it: m_memberTypes)
|
||||
if (it.name == _name)
|
||||
{
|
||||
@ -148,10 +149,16 @@ static_assert(std::is_nothrow_move_constructible<MemberList>::value, "MemberList
|
||||
/**
|
||||
* Abstract base class that forms the root of the type hierarchy.
|
||||
*/
|
||||
class Type: private boost::noncopyable, public std::enable_shared_from_this<Type>
|
||||
class Type
|
||||
{
|
||||
public:
|
||||
Type() = default;
|
||||
Type(Type const&) = delete;
|
||||
Type(Type&&) = delete;
|
||||
Type& operator=(Type const&) = delete;
|
||||
Type& operator=(Type&&) = delete;
|
||||
virtual ~Type() = default;
|
||||
|
||||
enum class Category
|
||||
{
|
||||
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array,
|
||||
@ -160,20 +167,8 @@ public:
|
||||
InaccessibleDynamic
|
||||
};
|
||||
|
||||
/// @{
|
||||
/// @name Factory functions
|
||||
/// Factory functions that convert an AST @ref TypeName to a Type.
|
||||
static TypePointer fromElementaryTypeName(ElementaryTypeNameToken const& _type);
|
||||
/// Converts a given elementary type name with optional data location
|
||||
/// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage".
|
||||
static TypePointer fromElementaryTypeName(std::string const& _name);
|
||||
/// @}
|
||||
|
||||
/// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does
|
||||
/// not fit any type.
|
||||
static TypePointer forLiteral(Literal const& _literal);
|
||||
/// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise
|
||||
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b);
|
||||
static TypePointer commonType(Type const* _a, Type const* _b);
|
||||
|
||||
virtual Category category() const = 0;
|
||||
/// @returns a valid solidity identifier such that two types should compare equal if and
|
||||
@ -201,13 +196,13 @@ public:
|
||||
/// @returns the resulting type of applying the given unary operator or an empty pointer if
|
||||
/// this is not possible.
|
||||
/// The default implementation does not allow any unary operator.
|
||||
virtual TypeResult unaryOperatorResult(Token) const { return TypePointer(); }
|
||||
virtual TypeResult unaryOperatorResult(Token) const { return nullptr; }
|
||||
/// @returns the resulting type of applying the given binary operator or an empty pointer if
|
||||
/// this is not possible.
|
||||
/// The default implementation allows comparison operators if a common type exists
|
||||
virtual TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const
|
||||
virtual TypeResult binaryOperatorResult(Token _operator, Type const* _other) const
|
||||
{
|
||||
return TokenTraits::isCompareOp(_operator) ? commonType(shared_from_this(), _other) : TypePointer();
|
||||
return TokenTraits::isCompareOp(_operator) ? commonType(this, _other) : nullptr;
|
||||
}
|
||||
|
||||
virtual bool operator==(Type const& _other) const { return category() == _other.category(); }
|
||||
@ -258,14 +253,14 @@ public:
|
||||
/// This returns the corresponding IntegerType or FixedPointType for RationalNumberType
|
||||
/// and the pointer type for storage reference types.
|
||||
/// Might return a null pointer if there is no fitting type.
|
||||
virtual TypePointer mobileType() const { return shared_from_this(); }
|
||||
virtual TypePointer mobileType() const { return this; }
|
||||
/// @returns true if this is a non-value type and the data of this type is stored at the
|
||||
/// given location.
|
||||
virtual bool dataStoredIn(DataLocation) const { return false; }
|
||||
/// @returns the type of a temporary during assignment to a variable of the given type.
|
||||
/// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type)
|
||||
/// and the mobile type otherwise.
|
||||
virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const
|
||||
virtual TypePointer closestTemporaryType(Type const* _targetType) const
|
||||
{
|
||||
return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType;
|
||||
}
|
||||
@ -298,7 +293,7 @@ public:
|
||||
/// @returns a (simpler) type that is encoded in the same way for external function calls.
|
||||
/// This for example returns address for contract types.
|
||||
/// If there is no such type, returns an empty shared pointer.
|
||||
virtual TypePointer encodingType() const { return TypePointer(); }
|
||||
virtual TypePointer encodingType() const { return nullptr; }
|
||||
/// @returns the encoding type used under the given circumstances for the type of an expression
|
||||
/// when used for e.g. abi.encode(...) or the empty pointer if the object
|
||||
/// cannot be encoded.
|
||||
@ -311,7 +306,10 @@ public:
|
||||
/// If there is no such type, returns an empty shared pointer.
|
||||
/// @param _inLibrary if set, returns types as used in a library, e.g. struct and contract types
|
||||
/// are returned without modification.
|
||||
virtual TypeResult interfaceType(bool /*_inLibrary*/) const { return TypePointer(); }
|
||||
virtual TypeResult interfaceType(bool /*_inLibrary*/) const { return nullptr; }
|
||||
|
||||
/// Clears all internally cached values (if any).
|
||||
virtual void clearCache() const;
|
||||
|
||||
private:
|
||||
/// @returns a member list containing all members added to this type by `using for` directives.
|
||||
@ -335,18 +333,15 @@ protected:
|
||||
class AddressType: public Type
|
||||
{
|
||||
public:
|
||||
static AddressType& address() { static std::shared_ptr<AddressType> addr(std::make_shared<AddressType>(StateMutability::NonPayable)); return *addr; }
|
||||
static AddressType& addressPayable() { static std::shared_ptr<AddressType> addr(std::make_shared<AddressType>(StateMutability::Payable)); return *addr; }
|
||||
explicit AddressType(StateMutability _stateMutability);
|
||||
|
||||
Category category() const override { return Category::Address; }
|
||||
|
||||
explicit AddressType(StateMutability _stateMutability);
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _other) const override;
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override;
|
||||
|
||||
bool operator==(Type const& _other) const override;
|
||||
|
||||
@ -362,8 +357,8 @@ public:
|
||||
|
||||
u256 literalValue(Literal const* _literal) const override;
|
||||
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypePointer encodingType() const override { return this; }
|
||||
TypeResult interfaceType(bool) const override { return this; }
|
||||
|
||||
StateMutability stateMutability(void) const { return m_stateMutability; }
|
||||
|
||||
@ -382,17 +377,15 @@ public:
|
||||
Unsigned, Signed
|
||||
};
|
||||
|
||||
static IntegerType& uint256() { static std::shared_ptr<IntegerType> uint256(std::make_shared<IntegerType>(256)); return *uint256; }
|
||||
explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned);
|
||||
|
||||
Category category() const override { return Category::Integer; }
|
||||
|
||||
explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned);
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override;
|
||||
|
||||
bool operator==(Type const& _other) const override;
|
||||
|
||||
@ -403,8 +396,8 @@ public:
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypePointer encodingType() const override { return this; }
|
||||
TypeResult interfaceType(bool) const override { return this; }
|
||||
|
||||
unsigned numBits() const { return m_bits; }
|
||||
bool isSigned() const { return m_modifier == Modifier::Signed; }
|
||||
@ -413,8 +406,8 @@ public:
|
||||
bigint maxValue() const;
|
||||
|
||||
private:
|
||||
unsigned m_bits;
|
||||
Modifier m_modifier;
|
||||
unsigned const m_bits;
|
||||
Modifier const m_modifier;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -427,15 +420,15 @@ public:
|
||||
{
|
||||
Unsigned, Signed
|
||||
};
|
||||
Category category() const override { return Category::FixedPoint; }
|
||||
|
||||
explicit FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, Modifier _modifier = Modifier::Unsigned);
|
||||
Category category() const override { return Category::FixedPoint; }
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override;
|
||||
|
||||
bool operator==(Type const& _other) const override;
|
||||
|
||||
@ -446,8 +439,8 @@ public:
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypePointer encodingType() const override { return this; }
|
||||
TypeResult interfaceType(bool) const override { return this; }
|
||||
|
||||
/// Number of bits used for this type in total.
|
||||
unsigned numBits() const { return m_totalBits; }
|
||||
@ -462,7 +455,7 @@ public:
|
||||
bigint minIntegerValue() const;
|
||||
|
||||
/// @returns the smallest integer type that can hold this type with fractional parts shifted to integers.
|
||||
std::shared_ptr<IntegerType> asIntegerType() const;
|
||||
IntegerType const* asIntegerType() const;
|
||||
|
||||
private:
|
||||
unsigned m_totalBits;
|
||||
@ -478,18 +471,16 @@ private:
|
||||
class RationalNumberType: public Type
|
||||
{
|
||||
public:
|
||||
explicit RationalNumberType(rational const& _value, Type const* _compatibleBytesType = nullptr):
|
||||
m_value(_value), m_compatibleBytesType(_compatibleBytesType)
|
||||
{}
|
||||
|
||||
Category category() const override { return Category::RationalNumber; }
|
||||
|
||||
static TypePointer forLiteral(Literal const& _literal);
|
||||
|
||||
explicit RationalNumberType(rational const& _value, TypePointer const& _compatibleBytesType = TypePointer()):
|
||||
m_value(_value), m_compatibleBytesType(_compatibleBytesType)
|
||||
{}
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override;
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
@ -502,11 +493,11 @@ public:
|
||||
TypePointer mobileType() const override;
|
||||
|
||||
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
|
||||
std::shared_ptr<IntegerType const> integerType() const;
|
||||
IntegerType const* integerType() const;
|
||||
/// @returns the smallest fixed type that can hold the value or incurs the least precision loss,
|
||||
/// unless the value was truncated, then a suitable type will be chosen to indicate such event.
|
||||
/// If the integer part does not fit, returns an empty pointer.
|
||||
std::shared_ptr<FixedPointType const> fixedPointType() const;
|
||||
FixedPointType const* fixedPointType() const;
|
||||
|
||||
/// @returns true if the value is not an integer.
|
||||
bool isFractional() const { return m_value.denominator() != 1; }
|
||||
@ -517,6 +508,9 @@ public:
|
||||
/// @returns true if the value is zero.
|
||||
bool isZero() const { return m_value == 0; }
|
||||
|
||||
/// @returns true if the literal is a valid integer.
|
||||
static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
|
||||
|
||||
private:
|
||||
rational m_value;
|
||||
|
||||
@ -524,9 +518,6 @@ private:
|
||||
/// Empty for all rationals that are not directly parsed from hex literals.
|
||||
TypePointer m_compatibleBytesType;
|
||||
|
||||
/// @returns true if the literal is a valid integer.
|
||||
static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
|
||||
|
||||
/// @returns true if the literal is a valid rational number.
|
||||
static std::tuple<bool, rational> parseRational(std::string const& _value);
|
||||
|
||||
@ -541,14 +532,15 @@ private:
|
||||
class StringLiteralType: public Type
|
||||
{
|
||||
public:
|
||||
explicit StringLiteralType(Literal const& _literal);
|
||||
explicit StringLiteralType(std::string const& _value);
|
||||
|
||||
Category category() const override { return Category::StringLiteral; }
|
||||
|
||||
explicit StringLiteralType(Literal const& _literal);
|
||||
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override
|
||||
{
|
||||
return TypePointer();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
@ -575,16 +567,16 @@ private:
|
||||
class FixedBytesType: public Type
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::FixedBytes; }
|
||||
|
||||
explicit FixedBytesType(unsigned _bytes);
|
||||
|
||||
Category category() const override { return Category::FixedBytes; }
|
||||
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override;
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; }
|
||||
unsigned storageBytes() const override { return m_bytes; }
|
||||
@ -593,8 +585,8 @@ public:
|
||||
|
||||
std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypePointer encodingType() const override { return this; }
|
||||
TypeResult interfaceType(bool) const override { return this; }
|
||||
|
||||
unsigned numBytes() const { return m_bytes; }
|
||||
|
||||
@ -611,7 +603,7 @@ public:
|
||||
Category category() const override { return Category::Bool; }
|
||||
std::string richIdentifier() const override { return "t_bool"; }
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, TypePointer const& _other) const override;
|
||||
TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override;
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; }
|
||||
unsigned storageBytes() const override { return 1; }
|
||||
@ -620,8 +612,8 @@ public:
|
||||
|
||||
std::string toString(bool) const override { return "bool"; }
|
||||
u256 literalValue(Literal const* _literal) const override;
|
||||
TypePointer encodingType() const override { return shared_from_this(); }
|
||||
TypeResult interfaceType(bool) const override { return shared_from_this(); }
|
||||
TypePointer encodingType() const override { return this; }
|
||||
TypeResult interfaceType(bool) const override { return this; }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -630,22 +622,24 @@ public:
|
||||
*/
|
||||
class ReferenceType: public Type
|
||||
{
|
||||
public:
|
||||
protected:
|
||||
explicit ReferenceType(DataLocation _location): m_location(_location) {}
|
||||
|
||||
public:
|
||||
DataLocation location() const { return m_location; }
|
||||
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override
|
||||
{
|
||||
return TypePointer();
|
||||
return nullptr;
|
||||
}
|
||||
unsigned memoryHeadSize() const override { return 32; }
|
||||
|
||||
/// @returns a copy of this type with location (recursively) changed to @a _location,
|
||||
/// whereas isPointer is only shallowly changed - the deep copy is always a bound reference.
|
||||
virtual TypePointer copyForLocation(DataLocation _location, bool _isPointer) const = 0;
|
||||
virtual std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const = 0;
|
||||
|
||||
TypePointer mobileType() const override { return copyForLocation(m_location, true); }
|
||||
TypePointer mobileType() const override { return withLocation(m_location, true); }
|
||||
bool dataStoredIn(DataLocation _location) const override { return m_location == _location; }
|
||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||
|
||||
@ -660,13 +654,10 @@ public:
|
||||
return location() == _other.location() && isPointer() == _other.isPointer();
|
||||
}
|
||||
|
||||
/// @returns a copy of @a _type having the same location as this (and is not a pointer type)
|
||||
/// if _type is a reference type and an unmodified copy of _type otherwise.
|
||||
/// This function is mostly useful to modify inner types appropriately.
|
||||
static TypePointer copyForLocationIfReference(DataLocation _location, TypePointer const& _type);
|
||||
Type const* withLocation(DataLocation _location, bool _isPointer) const;
|
||||
|
||||
protected:
|
||||
TypePointer copyForLocationIfReference(TypePointer const& _type) const;
|
||||
Type const* copyForLocationIfReference(Type const* _type) const;
|
||||
/// @returns a human-readable description of the reference part of the type.
|
||||
std::string stringForReferencePart() const;
|
||||
/// @returns the suffix computed from the reference part to be used by identifier();
|
||||
@ -686,32 +677,26 @@ protected:
|
||||
class ArrayType: public ReferenceType
|
||||
{
|
||||
public:
|
||||
static ArrayType& bytesMemory() { static std::shared_ptr<ArrayType> addr(std::make_shared<ArrayType>(DataLocation::Memory)); return *addr; }
|
||||
static ArrayType& stringMemory() { static std::shared_ptr<ArrayType> addr(std::make_shared<ArrayType>(DataLocation::Memory, true)); return *addr; }
|
||||
|
||||
Category category() const override { return Category::Array; }
|
||||
|
||||
/// Constructor for a byte array ("bytes") and string.
|
||||
explicit ArrayType(DataLocation _location, bool _isString = false):
|
||||
ReferenceType(_location),
|
||||
m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes),
|
||||
m_baseType(std::make_shared<FixedBytesType>(1))
|
||||
{
|
||||
}
|
||||
explicit ArrayType(DataLocation _location, bool _isString = false);
|
||||
|
||||
/// Constructor for a dynamically sized array type ("type[]")
|
||||
ArrayType(DataLocation _location, TypePointer const& _baseType):
|
||||
ArrayType(DataLocation _location, Type const* _baseType):
|
||||
ReferenceType(_location),
|
||||
m_baseType(copyForLocationIfReference(_baseType))
|
||||
{
|
||||
}
|
||||
|
||||
/// Constructor for a fixed-size array type ("type[20]")
|
||||
ArrayType(DataLocation _location, TypePointer const& _baseType, u256 const& _length):
|
||||
ArrayType(DataLocation _location, Type const* _baseType, u256 const& _length):
|
||||
ReferenceType(_location),
|
||||
m_baseType(copyForLocationIfReference(_baseType)),
|
||||
m_hasDynamicLength(false),
|
||||
m_length(_length)
|
||||
{}
|
||||
|
||||
Category category() const override { return Category::Array; }
|
||||
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
std::string richIdentifier() const override;
|
||||
@ -737,11 +722,11 @@ public:
|
||||
bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; }
|
||||
/// @returns true if this is a string
|
||||
bool isString() const { return m_arrayKind == ArrayKind::String; }
|
||||
TypePointer const& baseType() const { solAssert(!!m_baseType, ""); return m_baseType;}
|
||||
Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; }
|
||||
u256 const& length() const { return m_length; }
|
||||
u256 memorySize() const;
|
||||
|
||||
TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
|
||||
/// The offset to advance in calldata to move from one array element to the next.
|
||||
unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataEncodedSize(); }
|
||||
@ -750,6 +735,8 @@ public:
|
||||
/// The offset to advance in storage to move from one array element to the next.
|
||||
unsigned storageStride() const { return isByteArray() ? 1 : m_baseType->storageBytes(); }
|
||||
|
||||
void clearCache() const override;
|
||||
|
||||
private:
|
||||
/// String is interpreted as a subtype of Bytes.
|
||||
enum class ArrayKind { Ordinary, Bytes, String };
|
||||
@ -758,7 +745,7 @@ private:
|
||||
|
||||
///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays.
|
||||
ArrayKind m_arrayKind = ArrayKind::Ordinary;
|
||||
TypePointer m_baseType;
|
||||
Type const* m_baseType;
|
||||
bool m_hasDynamicLength = true;
|
||||
u256 m_length;
|
||||
mutable boost::optional<TypeResult> m_interfaceType;
|
||||
@ -771,9 +758,10 @@ private:
|
||||
class ContractType: public Type
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::Contract; }
|
||||
explicit ContractType(ContractDefinition const& _contract, bool _super = false):
|
||||
m_contract(_contract), m_super(_super) {}
|
||||
|
||||
Category category() const override { return Category::Contract; }
|
||||
/// Contracts can be implicitly converted only to base contracts.
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
/// Contracts can only be explicitly converted to address types and base contracts.
|
||||
@ -795,17 +783,14 @@ public:
|
||||
std::string canonicalName() const override;
|
||||
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
TypePointer encodingType() const override
|
||||
{
|
||||
if (isSuper())
|
||||
return TypePointer{};
|
||||
return std::make_shared<AddressType>(isPayable() ? StateMutability::Payable : StateMutability::NonPayable);
|
||||
}
|
||||
|
||||
Type const* encodingType() const override;
|
||||
|
||||
TypeResult interfaceType(bool _inLibrary) const override
|
||||
{
|
||||
if (isSuper())
|
||||
return TypePointer{};
|
||||
return _inLibrary ? shared_from_this() : encodingType();
|
||||
return nullptr;
|
||||
return _inLibrary ? this : encodingType();
|
||||
}
|
||||
|
||||
/// See documentation of m_super
|
||||
@ -817,7 +802,7 @@ public:
|
||||
ContractDefinition const& contractDefinition() const { return m_contract; }
|
||||
|
||||
/// Returns the function type of the constructor modified to return an object of the contract's type.
|
||||
FunctionTypePointer const& newExpressionType() const;
|
||||
FunctionType const* newExpressionType() const;
|
||||
|
||||
/// @returns a list of all state variables (including inherited) of the contract and their
|
||||
/// offsets in storage.
|
||||
@ -828,7 +813,7 @@ private:
|
||||
/// If true, this is a special "super" type of m_contract containing only members that m_contract inherited
|
||||
bool m_super = false;
|
||||
/// Type of the constructor, @see constructorType. Lazily initialized.
|
||||
mutable FunctionTypePointer m_constructorType;
|
||||
mutable FunctionType const* m_constructorType = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -837,9 +822,10 @@ private:
|
||||
class StructType: public ReferenceType
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::Struct; }
|
||||
explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage):
|
||||
ReferenceType(_location), m_struct(_struct) {}
|
||||
|
||||
Category category() const override { return Category::Struct; }
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
@ -851,10 +837,8 @@ public:
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
TypePointer encodingType() const override
|
||||
{
|
||||
return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : shared_from_this();
|
||||
}
|
||||
|
||||
Type const* encodingType() const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
|
||||
bool recursive() const
|
||||
@ -867,14 +851,14 @@ public:
|
||||
return m_recursive.get();
|
||||
}
|
||||
|
||||
TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
|
||||
std::string canonicalName() const override;
|
||||
std::string signatureInExternalFunction(bool _structsByName) const override;
|
||||
|
||||
/// @returns a function that performs the type conversion between a list of struct members
|
||||
/// and a memory struct of this type.
|
||||
FunctionTypePointer constructorType() const;
|
||||
FunctionType const* constructorType() const;
|
||||
|
||||
std::pair<u256, unsigned> const& storageOffsetsOfMember(std::string const& _name) const;
|
||||
u256 memoryOffsetOfMember(std::string const& _name) const;
|
||||
@ -886,6 +870,9 @@ public:
|
||||
TypePointers memoryMemberTypes() const;
|
||||
/// @returns the set of all members that are removed in the memory version (typically mappings).
|
||||
std::set<std::string> membersMissingInMemory() const;
|
||||
|
||||
void clearCache() const override;
|
||||
|
||||
private:
|
||||
StructDefinition const& m_struct;
|
||||
// Caches for interfaceType(bool)
|
||||
@ -900,8 +887,9 @@ private:
|
||||
class EnumType: public Type
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::Enum; }
|
||||
explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {}
|
||||
|
||||
Category category() const override { return Category::Enum; }
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
@ -917,13 +905,10 @@ public:
|
||||
bool isValueType() const override { return true; }
|
||||
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
TypePointer encodingType() const override
|
||||
{
|
||||
return std::make_shared<IntegerType>(8 * int(storageBytes()));
|
||||
}
|
||||
TypePointer encodingType() const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override
|
||||
{
|
||||
return _inLibrary ? shared_from_this() : encodingType();
|
||||
return _inLibrary ? this : encodingType();
|
||||
}
|
||||
|
||||
EnumDefinition const& enumDefinition() const { return m_enum; }
|
||||
@ -942,12 +927,14 @@ private:
|
||||
class TupleType: public Type
|
||||
{
|
||||
public:
|
||||
explicit TupleType(std::vector<TypePointer> _types = {}): m_components(std::move(_types)) {}
|
||||
|
||||
Category category() const override { return Category::Tuple; }
|
||||
explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {}
|
||||
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _other) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); }
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
|
||||
std::string toString(bool) const override;
|
||||
bool canBeStored() const override { return false; }
|
||||
u256 storageSize() const override;
|
||||
@ -956,7 +943,7 @@ public:
|
||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||
TypePointer mobileType() const override;
|
||||
/// Converts components to their temporary types and performs some wildcard matching.
|
||||
TypePointer closestTemporaryType(TypePointer const& _targetType) const override;
|
||||
TypePointer closestTemporaryType(Type const* _targetType) const override;
|
||||
|
||||
std::vector<TypePointer> const& components() const { return m_components; }
|
||||
|
||||
@ -1017,8 +1004,6 @@ public:
|
||||
MetaType ///< type(...)
|
||||
};
|
||||
|
||||
Category category() const override { return Category::Function; }
|
||||
|
||||
/// Creates the type of a function.
|
||||
explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true);
|
||||
/// Creates the accessor function type of a state variable.
|
||||
@ -1046,9 +1031,6 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/// @returns the type of the "new Contract" function, i.e. basically the constructor.
|
||||
static FunctionTypePointer newExpressionType(ContractDefinition const& _contract);
|
||||
|
||||
/// Detailed constructor, use with care.
|
||||
FunctionType(
|
||||
TypePointers const& _parameterTypes,
|
||||
@ -1089,6 +1071,11 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
Category category() const override { return Category::Function; }
|
||||
|
||||
/// @returns the type of the "new Contract" function, i.e. basically the constructor.
|
||||
static FunctionTypePointer newExpressionType(ContractDefinition const& _contract);
|
||||
|
||||
TypePointers parameterTypes() const;
|
||||
std::vector<std::string> parameterNames() const;
|
||||
TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; }
|
||||
@ -1097,14 +1084,14 @@ public:
|
||||
TypePointers returnParameterTypesWithoutDynamicTypes() const;
|
||||
std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; }
|
||||
/// @returns the "self" parameter type for a bound function
|
||||
TypePointer const& selfType() const;
|
||||
Type const* selfType() const;
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
TypeResult unaryOperatorResult(Token _operator) const override;
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override;
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override;
|
||||
std::string canonicalName() const override;
|
||||
std::string toString(bool _short) const override;
|
||||
unsigned calldataEncodedSize(bool _padded) const override;
|
||||
@ -1133,7 +1120,7 @@ public:
|
||||
/// expression the function is called on.
|
||||
bool canTakeArguments(
|
||||
FuncCallArguments const& _arguments,
|
||||
TypePointer const& _selfType = TypePointer()
|
||||
Type const* _selfType = nullptr
|
||||
) const;
|
||||
|
||||
/// @returns true if the types of parameters are equal (does not check return parameter types)
|
||||
@ -1229,27 +1216,25 @@ private:
|
||||
class MappingType: public Type
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::Mapping; }
|
||||
MappingType(TypePointer const& _keyType, TypePointer const& _valueType):
|
||||
MappingType(Type const* _keyType, Type const* _valueType):
|
||||
m_keyType(_keyType), m_valueType(_valueType) {}
|
||||
|
||||
Category category() const override { return Category::Mapping; }
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
std::string toString(bool _short) const override;
|
||||
std::string canonicalName() const override;
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); }
|
||||
TypePointer encodingType() const override
|
||||
{
|
||||
return std::make_shared<IntegerType>(256);
|
||||
}
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
|
||||
Type const* encodingType() const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
|
||||
/// Cannot be stored in memory, but just in case.
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
|
||||
TypePointer const& keyType() const { return m_keyType; }
|
||||
TypePointer const& valueType() const { return m_valueType; }
|
||||
Type const* keyType() const { return m_keyType; }
|
||||
Type const* valueType() const { return m_valueType; }
|
||||
|
||||
private:
|
||||
TypePointer m_keyType;
|
||||
@ -1264,11 +1249,12 @@ private:
|
||||
class TypeType: public Type
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::TypeType; }
|
||||
explicit TypeType(TypePointer const& _actualType): m_actualType(_actualType) {}
|
||||
TypePointer const& actualType() const { return m_actualType; }
|
||||
explicit TypeType(Type const* _actualType): m_actualType(_actualType) {}
|
||||
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); }
|
||||
Category category() const override { return Category::TypeType; }
|
||||
Type const* actualType() const { return m_actualType; }
|
||||
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
bool canBeStored() const override { return false; }
|
||||
@ -1290,10 +1276,11 @@ private:
|
||||
class ModifierType: public Type
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::Modifier; }
|
||||
explicit ModifierType(ModifierDefinition const& _modifier);
|
||||
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); }
|
||||
Category category() const override { return Category::Modifier; }
|
||||
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
|
||||
bool canBeStored() const override { return false; }
|
||||
u256 storageSize() const override;
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
@ -1315,11 +1302,11 @@ private:
|
||||
class ModuleType: public Type
|
||||
{
|
||||
public:
|
||||
Category category() const override { return Category::Module; }
|
||||
|
||||
explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {}
|
||||
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); }
|
||||
Category category() const override { return Category::Module; }
|
||||
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
bool canBeStored() const override { return false; }
|
||||
@ -1347,15 +1334,16 @@ public:
|
||||
ABI, ///< "abi"
|
||||
MetaType ///< "type(...)"
|
||||
};
|
||||
|
||||
public:
|
||||
explicit MagicType(Kind _kind): m_kind(_kind) {}
|
||||
explicit MagicType(Type const* _metaTypeArg): m_kind{Kind::MetaType}, m_typeArgument{_metaTypeArg} {}
|
||||
|
||||
Category category() const override { return Category::Magic; }
|
||||
|
||||
explicit MagicType(Kind _kind): m_kind(_kind) {}
|
||||
/// Factory function for meta type
|
||||
static std::shared_ptr<MagicType> metaType(TypePointer _type);
|
||||
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override
|
||||
{
|
||||
return TypePointer();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string richIdentifier() const override;
|
||||
@ -1376,7 +1364,6 @@ private:
|
||||
Kind m_kind;
|
||||
/// Contract type used for contract metadata magic.
|
||||
TypePointer m_typeArgument;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1391,7 +1378,7 @@ public:
|
||||
std::string richIdentifier() const override { return "t_inaccessible"; }
|
||||
BoolResult isImplicitlyConvertibleTo(Type const&) const override { return false; }
|
||||
BoolResult isExplicitlyConvertibleTo(Type const&) const override { return false; }
|
||||
TypeResult binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); }
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
|
||||
unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; }
|
||||
bool canBeStored() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
@ -1399,7 +1386,7 @@ public:
|
||||
unsigned sizeOnStack() const override { return 1; }
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
std::string toString(bool) const override { return "inaccessible dynamic type"; }
|
||||
TypePointer decodingType() const override { return std::make_shared<IntegerType>(256); }
|
||||
TypePointer decodingType() const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ string ABIFunctions::tupleEncoder(
|
||||
<abiEncode>(<values> add(headStart, <pos>))
|
||||
)")
|
||||
);
|
||||
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
string values = m_utils.suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
elementTempl("values", values.empty() ? "" : values + ", ");
|
||||
elementTempl("pos", to_string(headPos));
|
||||
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
|
||||
@ -91,7 +91,7 @@ string ABIFunctions::tupleEncoder(
|
||||
stackPos += sizeOnStack;
|
||||
}
|
||||
solAssert(headPos == headSize_, "");
|
||||
string valueParams = suffixedVariableNameList("value", stackPos, 0);
|
||||
string valueParams = m_utils.suffixedVariableNameList("value", stackPos, 0);
|
||||
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
|
||||
templ("encodeElements", encodeElements);
|
||||
|
||||
@ -147,7 +147,7 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
pos := add(pos, <calldataEncodedSize>)
|
||||
)")
|
||||
);
|
||||
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
string values = m_utils.suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
elementTempl("values", values.empty() ? "" : values + ", ");
|
||||
if (!dynamic)
|
||||
elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
|
||||
@ -155,7 +155,7 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
encodeElements += elementTempl.render();
|
||||
stackPos += sizeOnStack;
|
||||
}
|
||||
string valueParams = suffixedVariableNameList("value", stackPos, 0);
|
||||
string valueParams = m_utils.suffixedVariableNameList("value", stackPos, 0);
|
||||
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
|
||||
templ("encodeElements", encodeElements);
|
||||
|
||||
@ -255,97 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||
return suffix;
|
||||
}
|
||||
|
||||
|
||||
string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
|
||||
{
|
||||
string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Address:
|
||||
templ("body", "cleaned := " + cleanupFunction(IntegerType(160), _revertOnFailure) + "(value)");
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (type.numBits() == 256)
|
||||
templ("body", "cleaned := value");
|
||||
else if (type.isSigned())
|
||||
templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)");
|
||||
else
|
||||
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")");
|
||||
break;
|
||||
}
|
||||
case Type::Category::RationalNumber:
|
||||
templ("body", "cleaned := value");
|
||||
break;
|
||||
case Type::Category::Bool:
|
||||
templ("body", "cleaned := iszero(iszero(value))");
|
||||
break;
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
case Type::Category::Struct:
|
||||
case Type::Category::Mapping:
|
||||
solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type.");
|
||||
templ("body", "cleaned := value");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type);
|
||||
if (type.numBytes() == 32)
|
||||
templ("body", "cleaned := value");
|
||||
else if (type.numBytes() == 0)
|
||||
// This is disallowed in the type system.
|
||||
solAssert(false, "");
|
||||
else
|
||||
{
|
||||
size_t numBits = type.numBytes() * 8;
|
||||
u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits);
|
||||
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::Contract:
|
||||
{
|
||||
AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ?
|
||||
StateMutability::Payable :
|
||||
StateMutability::NonPayable
|
||||
);
|
||||
templ("body", "cleaned := " + cleanupFunction(addressType, _revertOnFailure) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
|
||||
solAssert(members > 0, "empty enum should have caused a parser error.");
|
||||
Whiskers w("if iszero(lt(value, <members>)) { <failure> } cleaned := value");
|
||||
w("members", to_string(members));
|
||||
if (_revertOnFailure)
|
||||
w("failure", "revert(0, 0)");
|
||||
else
|
||||
w("failure", "invalid()");
|
||||
templ("body", w.render());
|
||||
break;
|
||||
}
|
||||
case Type::Category::InaccessibleDynamic:
|
||||
templ("body", "cleaned := 0");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
|
||||
}
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
@ -379,186 +288,6 @@ string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFu
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
{
|
||||
string functionName =
|
||||
"convert_" +
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> converted {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
string body;
|
||||
auto toCategory = _to.category();
|
||||
auto fromCategory = _from.category();
|
||||
switch (fromCategory)
|
||||
{
|
||||
case Type::Category::Address:
|
||||
body =
|
||||
Whiskers("converted := <convert>(value)")
|
||||
("convert", conversionFunction(IntegerType(160), _to))
|
||||
.render();
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
case Type::Category::RationalNumber:
|
||||
case Type::Category::Contract:
|
||||
{
|
||||
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from))
|
||||
solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType.");
|
||||
if (toCategory == Type::Category::FixedBytes)
|
||||
{
|
||||
solAssert(
|
||||
fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber,
|
||||
"Invalid conversion to FixedBytesType requested."
|
||||
);
|
||||
FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to);
|
||||
body =
|
||||
Whiskers("converted := <shiftLeft>(<clean>(value))")
|
||||
("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
||||
("clean", cleanupFunction(_from))
|
||||
.render();
|
||||
}
|
||||
else if (toCategory == Type::Category::Enum)
|
||||
{
|
||||
solAssert(_from.mobileType(), "");
|
||||
body =
|
||||
Whiskers("converted := <cleanEnum>(<cleanInt>(value))")
|
||||
("cleanEnum", cleanupFunction(_to))
|
||||
// "mobileType()" returns integer type for rational
|
||||
("cleanInt", cleanupFunction(*_from.mobileType()))
|
||||
.render();
|
||||
}
|
||||
else if (toCategory == Type::Category::FixedPoint)
|
||||
solUnimplemented("Not yet implemented - FixedPointType.");
|
||||
else if (toCategory == Type::Category::Address)
|
||||
body =
|
||||
Whiskers("converted := <convert>(value)")
|
||||
("convert", conversionFunction(_from, IntegerType(160)))
|
||||
.render();
|
||||
else
|
||||
{
|
||||
solAssert(
|
||||
toCategory == Type::Category::Integer ||
|
||||
toCategory == Type::Category::Contract,
|
||||
"");
|
||||
IntegerType const addressType(160);
|
||||
IntegerType const& to =
|
||||
toCategory == Type::Category::Integer ?
|
||||
dynamic_cast<IntegerType const&>(_to) :
|
||||
addressType;
|
||||
|
||||
// Clean according to the "to" type, except if this is
|
||||
// a widening conversion.
|
||||
IntegerType const* cleanupType = &to;
|
||||
if (fromCategory != Type::Category::RationalNumber)
|
||||
{
|
||||
IntegerType const& from =
|
||||
fromCategory == Type::Category::Integer ?
|
||||
dynamic_cast<IntegerType const&>(_from) :
|
||||
addressType;
|
||||
if (to.numBits() > from.numBits())
|
||||
cleanupType = &from;
|
||||
}
|
||||
body =
|
||||
Whiskers("converted := <cleanInt>(value)")
|
||||
("cleanInt", cleanupFunction(*cleanupType))
|
||||
.render();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::Bool:
|
||||
{
|
||||
solAssert(_from == _to, "Invalid conversion for bool.");
|
||||
body =
|
||||
Whiskers("converted := <clean>(value)")
|
||||
("clean", cleanupFunction(_from))
|
||||
.render();
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
||||
break;
|
||||
case Type::Category::Struct:
|
||||
solUnimplementedAssert(false, "Struct conversion not implemented.");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from);
|
||||
if (toCategory == Type::Category::Integer)
|
||||
body =
|
||||
Whiskers("converted := <convert>(<shift>(value))")
|
||||
("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8))
|
||||
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
|
||||
.render();
|
||||
else if (toCategory == Type::Category::Address)
|
||||
body =
|
||||
Whiskers("converted := <convert>(value)")
|
||||
("convert", conversionFunction(_from, IntegerType(160)))
|
||||
.render();
|
||||
else
|
||||
{
|
||||
// clear for conversion to longer bytes
|
||||
solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
|
||||
body =
|
||||
Whiskers("converted := <clean>(value)")
|
||||
("clean", cleanupFunction(from))
|
||||
.render();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::Function:
|
||||
{
|
||||
solAssert(false, "Conversion should not be called for function types.");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
solAssert(toCategory == Type::Category::Integer || _from == _to, "");
|
||||
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from);
|
||||
body =
|
||||
Whiskers("converted := <clean>(value)")
|
||||
("clean", cleanupFunction(enumType))
|
||||
.render();
|
||||
break;
|
||||
}
|
||||
case Type::Category::Tuple:
|
||||
{
|
||||
solUnimplementedAssert(false, "Tuple conversion not implemented.");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName());
|
||||
templ("body", body);
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "cleanup_combined_external_function_id";
|
||||
return createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(addr_and_selector) -> cleaned {
|
||||
cleaned := <clean>(addr_and_selector)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("clean", cleanupFunction(FixedBytesType(24)))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunction(
|
||||
Type const& _from,
|
||||
Type const& _to,
|
||||
@ -576,19 +305,31 @@ string ABIFunctions::abiEncodingFunction(
|
||||
solAssert(_from.category() == Type::Category::Array, "");
|
||||
solAssert(to.dataStoredIn(DataLocation::Memory), "");
|
||||
ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from);
|
||||
if (fromArray.location() == DataLocation::CallData)
|
||||
return abiEncodingFunctionCalldataArray(fromArray, *toArray, _options);
|
||||
else if (!fromArray.isByteArray() && (
|
||||
fromArray.location() == DataLocation::Memory ||
|
||||
fromArray.baseType()->storageBytes() > 16
|
||||
))
|
||||
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
|
||||
else if (fromArray.location() == DataLocation::Memory)
|
||||
return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
|
||||
else if (fromArray.location() == DataLocation::Storage)
|
||||
return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
|
||||
else
|
||||
solAssert(false, "");
|
||||
|
||||
switch (fromArray.location())
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
if (
|
||||
fromArray.isByteArray() ||
|
||||
*fromArray.baseType() == *TypeProvider::uint256() ||
|
||||
*fromArray.baseType() == FixedBytesType(32)
|
||||
)
|
||||
return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options);
|
||||
else
|
||||
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
|
||||
case DataLocation::Memory:
|
||||
if (fromArray.isByteArray())
|
||||
return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options);
|
||||
else
|
||||
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
|
||||
case DataLocation::Storage:
|
||||
if (fromArray.baseType()->storageBytes() <= 16)
|
||||
return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options);
|
||||
else
|
||||
return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options);
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
}
|
||||
else if (auto const* toStruct = dynamic_cast<StructType const*>(&to))
|
||||
{
|
||||
@ -628,16 +369,16 @@ string ABIFunctions::abiEncodingFunction(
|
||||
// possible for library calls where we just forward the storage reference
|
||||
solAssert(_options.encodeAsLibraryTypes, "");
|
||||
solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested.");
|
||||
solAssert(to == IntegerType::uint256(), "");
|
||||
solAssert(to == *TypeProvider::uint256(), "");
|
||||
templ("cleanupConvert", "value");
|
||||
}
|
||||
else
|
||||
{
|
||||
string cleanupConvert;
|
||||
if (_from == to)
|
||||
cleanupConvert = cleanupFunction(_from) + "(value)";
|
||||
cleanupConvert = m_utils.cleanupFunction(_from) + "(value)";
|
||||
else
|
||||
cleanupConvert = conversionFunction(_from, to) + "(value)";
|
||||
cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)";
|
||||
if (!_options.padded)
|
||||
cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")";
|
||||
templ("cleanupConvert", cleanupConvert);
|
||||
@ -659,7 +400,7 @@ string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
|
||||
_targetType.identifier() +
|
||||
_options.toFunctionNameSuffix();
|
||||
return createFunction(functionName, [&]() {
|
||||
string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options));
|
||||
string values = m_utils.suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options));
|
||||
string encoder = abiEncodingFunction(_givenType, _targetType, _options);
|
||||
if (_targetType.isDynamicallyEncoded())
|
||||
return Whiskers(R"(
|
||||
@ -690,19 +431,24 @@ string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunctionCalldataArray(
|
||||
string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
|
||||
Type const& _from,
|
||||
Type const& _to,
|
||||
EncodingOptions const& _options
|
||||
)
|
||||
{
|
||||
solAssert(_to.isDynamicallySized(), "");
|
||||
solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type.");
|
||||
solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type.");
|
||||
auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from);
|
||||
auto const& toArrayType = dynamic_cast<ArrayType const&>(_to);
|
||||
|
||||
solAssert(fromArrayType.location() == DataLocation::CallData, "");
|
||||
solAssert(
|
||||
fromArrayType.isByteArray() ||
|
||||
*fromArrayType.baseType() == *TypeProvider::uint256() ||
|
||||
*fromArrayType.baseType() == FixedBytesType(32),
|
||||
"");
|
||||
solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), "");
|
||||
|
||||
solAssert(
|
||||
*fromArrayType.copyForLocation(DataLocation::Memory, true) ==
|
||||
@ -717,24 +463,54 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
|
||||
_to.identifier() +
|
||||
_options.toFunctionNameSuffix();
|
||||
return createFunction(functionName, [&]() {
|
||||
solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently.");
|
||||
// TODO if this is not a byte array, we might just copy byte-by-byte anyway,
|
||||
// because the encoding is position-independent, but we have to check that.
|
||||
Whiskers templ(R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
function <functionName>(start, length, pos) -> end {
|
||||
pos := <storeLength>(pos, length)
|
||||
<copyFun>(start, pos, length)
|
||||
end := add(pos, <lengthPadded>)
|
||||
}
|
||||
)");
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeNameFrom", _from.toString(true));
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("copyFun", m_utils.copyToMemoryFunction(true));
|
||||
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
|
||||
return templ.render();
|
||||
bool needsPadding = _options.padded && fromArrayType.isByteArray();
|
||||
if (fromArrayType.isDynamicallySized())
|
||||
{
|
||||
Whiskers templ(R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
function <functionName>(start, length, pos) -> end {
|
||||
pos := <storeLength>(pos, length)
|
||||
<scaleLengthByStride>
|
||||
<copyFun>(start, pos, length)
|
||||
end := add(pos, <lengthPadded>)
|
||||
}
|
||||
)");
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options));
|
||||
templ("functionName", functionName);
|
||||
if (fromArrayType.isByteArray() || fromArrayType.calldataStride() == 1)
|
||||
templ("scaleLengthByStride", "");
|
||||
else
|
||||
templ("scaleLengthByStride",
|
||||
Whiskers(R"(
|
||||
if gt(length, <maxLength>) { revert(0, 0) }
|
||||
length := mul(length, <stride>)
|
||||
)")
|
||||
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
|
||||
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
|
||||
.render()
|
||||
);
|
||||
templ("readableTypeNameFrom", _from.toString(true));
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("copyFun", m_utils.copyToMemoryFunction(true));
|
||||
templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length");
|
||||
return templ.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(fromArrayType.calldataStride() == 32, "");
|
||||
Whiskers templ(R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
function <functionName>(start, pos) {
|
||||
<copyFun>(start, pos, <byteLength>)
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeNameFrom", _from.toString(true));
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("copyFun", m_utils.copyToMemoryFunction(true));
|
||||
templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride()));
|
||||
return templ.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -753,29 +529,34 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
|
||||
solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
|
||||
solAssert(_from.length() == _to.length(), "");
|
||||
solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), "");
|
||||
solAssert(!_from.isByteArray(), "");
|
||||
solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, "");
|
||||
if (_from.dataStoredIn(DataLocation::Storage))
|
||||
solAssert(_from.baseType()->storageBytes() > 16, "");
|
||||
|
||||
return createFunction(functionName, [&]() {
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
|
||||
bool inMemory = _from.dataStoredIn(DataLocation::Memory);
|
||||
bool const usesTail = dynamicBase && !_options.dynamicInplace;
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
subOptions.padded = true;
|
||||
string elementValues = m_utils.suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions));
|
||||
Whiskers templ(
|
||||
usesTail ?
|
||||
R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
function <functionName>(value, pos) <return> {
|
||||
let length := <lengthFun>(value)
|
||||
function <functionName>(value,<maybeLength> pos) <return> {
|
||||
<declareLength>
|
||||
pos := <storeLength>(pos, length)
|
||||
let headStart := pos
|
||||
let tail := add(pos, mul(length, 0x20))
|
||||
let srcPtr := <dataAreaFun>(value)
|
||||
let baseRef := <dataAreaFun>(value)
|
||||
let srcPtr := baseRef
|
||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||
{
|
||||
mstore(pos, sub(tail, headStart))
|
||||
tail := <encodeToMemoryFun>(<arrayElementAccess>, tail)
|
||||
let <elementValues> := <arrayElementAccess>
|
||||
tail := <encodeToMemoryFun>(<elementValues>, tail)
|
||||
srcPtr := <nextArrayElement>(srcPtr)
|
||||
pos := add(pos, 0x20)
|
||||
}
|
||||
@ -785,13 +566,15 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
)" :
|
||||
R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
function <functionName>(value, pos) <return> {
|
||||
let length := <lengthFun>(value)
|
||||
function <functionName>(value,<maybeLength> pos) <return> {
|
||||
<declareLength>
|
||||
pos := <storeLength>(pos, length)
|
||||
let srcPtr := <dataAreaFun>(value)
|
||||
let baseRef := <dataAreaFun>(value)
|
||||
let srcPtr := baseRef
|
||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||
{
|
||||
pos := <encodeToMemoryFun>(<arrayElementAccess>, pos)
|
||||
let <elementValues> := <arrayElementAccess>
|
||||
pos := <encodeToMemoryFun>(<elementValues>, pos)
|
||||
srcPtr := <nextArrayElement>(srcPtr)
|
||||
}
|
||||
<assignEnd>
|
||||
@ -799,27 +582,43 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
)"
|
||||
);
|
||||
templ("functionName", functionName);
|
||||
templ("elementValues", elementValues);
|
||||
bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized();
|
||||
if (lengthAsArgument)
|
||||
{
|
||||
templ("maybeLength", " length,");
|
||||
templ("declareLength", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
templ("maybeLength", "");
|
||||
templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)");
|
||||
}
|
||||
templ("readableTypeNameFrom", _from.toString(true));
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("return", dynamic ? " -> end " : "");
|
||||
templ("assignEnd", dynamic ? "end := pos" : "");
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
|
||||
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
subOptions.padded = true;
|
||||
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
|
||||
if (inMemory)
|
||||
templ("arrayElementAccess", "mload(srcPtr)");
|
||||
else if (_from.baseType()->isValueType())
|
||||
switch (_from.location())
|
||||
{
|
||||
solAssert(_from.dataStoredIn(DataLocation::Storage), "");
|
||||
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
|
||||
case DataLocation::Memory:
|
||||
templ("arrayElementAccess", "mload(srcPtr)");
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
if (_from.baseType()->isValueType())
|
||||
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
|
||||
else
|
||||
templ("arrayElementAccess", "srcPtr");
|
||||
break;
|
||||
case DataLocation::CallData:
|
||||
templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
else
|
||||
templ("arrayElementAccess", "srcPtr");
|
||||
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
|
||||
return templ.render();
|
||||
});
|
||||
@ -1028,11 +827,9 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
_to.identifier() +
|
||||
_options.toFunctionNameSuffix();
|
||||
|
||||
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
|
||||
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
|
||||
|
||||
return createFunction(functionName, [&]() {
|
||||
bool fromStorage = _from.location() == DataLocation::Storage;
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
Whiskers templ(R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
@ -1043,7 +840,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
{
|
||||
// <memberName>
|
||||
<preprocess>
|
||||
let memberValue := <retrieveValue>
|
||||
let <memberValues> := <retrieveValue>
|
||||
<encode>
|
||||
}
|
||||
</members>
|
||||
@ -1061,7 +858,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
else
|
||||
templ("assignEnd", "");
|
||||
// to avoid multiple loads from the same slot for subsequent members
|
||||
templ("init", fromStorage ? "let slotValue := 0" : "");
|
||||
templ("init", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : "");
|
||||
u256 previousSlotOffset(-1);
|
||||
u256 encodingOffset = 0;
|
||||
vector<map<string, string>> members;
|
||||
@ -1081,32 +878,45 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
members.push_back({});
|
||||
members.back()["preprocess"] = "";
|
||||
|
||||
if (fromStorage)
|
||||
switch (_from.location())
|
||||
{
|
||||
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
|
||||
u256 storageSlotOffset;
|
||||
size_t intraSlotOffset;
|
||||
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
|
||||
if (memberTypeFrom->isValueType())
|
||||
case DataLocation::Storage:
|
||||
{
|
||||
if (storageSlotOffset != previousSlotOffset)
|
||||
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
|
||||
u256 storageSlotOffset;
|
||||
size_t intraSlotOffset;
|
||||
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
|
||||
if (memberTypeFrom->isValueType())
|
||||
{
|
||||
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
if (storageSlotOffset != previousSlotOffset)
|
||||
{
|
||||
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
|
||||
}
|
||||
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
|
||||
else
|
||||
{
|
||||
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
|
||||
solAssert(intraSlotOffset == 0, "");
|
||||
members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")";
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
case DataLocation::Memory:
|
||||
{
|
||||
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
|
||||
solAssert(intraSlotOffset == 0, "");
|
||||
members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")";
|
||||
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
|
||||
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
|
||||
members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))";
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name));
|
||||
members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
EncodingOptions subOptions(_options);
|
||||
@ -1114,10 +924,14 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
// Like with arrays, struct members are always padded.
|
||||
subOptions.padded = true;
|
||||
|
||||
string memberValues = m_utils.suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions));
|
||||
members.back()["memberValues"] = memberValues;
|
||||
|
||||
string encode;
|
||||
if (_options.dynamicInplace)
|
||||
encode = Whiskers{"pos := <encode>(memberValue, pos)"}
|
||||
encode = Whiskers{"pos := <encode>(<memberValues>, pos)"}
|
||||
("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions))
|
||||
("memberValues", memberValues)
|
||||
.render();
|
||||
else
|
||||
{
|
||||
@ -1125,10 +939,11 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
dynamicMember ?
|
||||
string(R"(
|
||||
mstore(add(pos, <encodingOffset>), sub(tail, pos))
|
||||
tail := <abiEncode>(memberValue, tail)
|
||||
tail := <abiEncode>(<memberValues>, tail)
|
||||
)") :
|
||||
"<abiEncode>(memberValue, add(pos, <encodingOffset>))"
|
||||
"<abiEncode>(<memberValues>, add(pos, <encodingOffset>))"
|
||||
);
|
||||
encodeTempl("memberValues", memberValues);
|
||||
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
|
||||
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
|
||||
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
|
||||
@ -1248,7 +1063,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
|
||||
("cleanExtFun", m_utils.cleanupFunction(_to))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -1262,7 +1077,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
|
||||
TypePointer decodingType = _type.decodingType();
|
||||
solAssert(decodingType, "");
|
||||
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType.get()))
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType))
|
||||
{
|
||||
if (arrayType->dataStoredIn(DataLocation::CallData))
|
||||
{
|
||||
@ -1274,7 +1089,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
|
||||
else
|
||||
return abiDecodingFunctionArray(*arrayType, _fromMemory);
|
||||
}
|
||||
else if (auto const* structType = dynamic_cast<StructType const*>(decodingType.get()))
|
||||
else if (auto const* structType = dynamic_cast<StructType const*>(decodingType))
|
||||
{
|
||||
if (structType->dataStoredIn(DataLocation::CallData))
|
||||
{
|
||||
@ -1284,7 +1099,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
|
||||
else
|
||||
return abiDecodingFunctionStruct(*structType, _fromMemory);
|
||||
}
|
||||
else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType.get()))
|
||||
else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType))
|
||||
return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack);
|
||||
else
|
||||
return abiDecodingFunctionValueType(_type, _fromMemory);
|
||||
@ -1306,14 +1121,15 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(offset, end) -> value {
|
||||
value := <cleanup>(<load>(offset))
|
||||
value := <load>(offset)
|
||||
<validator>(value)
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("load", _fromMemory ? "mload" : "calldataload");
|
||||
// Cleanup itself should use the type and not decodingType, because e.g.
|
||||
// Validation should use the type and not decodingType, because e.g.
|
||||
// the decoding type of an enum is a plain int.
|
||||
templ("cleanup", cleanupFunction(_type, true));
|
||||
templ("validator", m_utils.validatorFunction(_type, true));
|
||||
return templ.render();
|
||||
});
|
||||
|
||||
@ -1560,11 +1376,11 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(offset, end) -> addr, function_selector {
|
||||
addr, function_selector := <splitExtFun>(<load>(offset))
|
||||
addr, function_selector := <splitExtFun>(<decodeFun>(offset, end))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromMemory ? "mload" : "calldataload")
|
||||
("decodeFun", abiDecodingFunctionFunctionType(_type, _fromMemory, false))
|
||||
("splitExtFun", m_utils.splitExternalFunctionIdFunction())
|
||||
.render();
|
||||
}
|
||||
@ -1572,18 +1388,18 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(offset, end) -> fun {
|
||||
fun := <cleanExtFun>(<load>(offset))
|
||||
fun := <load>(offset)
|
||||
<validateExtFun>(fun)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromMemory ? "mload" : "calldataload")
|
||||
("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
|
||||
("validateExtFun", m_utils.validatorFunction(_type, true))
|
||||
.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
@ -1629,6 +1445,80 @@ string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset,
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
{
|
||||
solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), "");
|
||||
string functionName = "calldata_access_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_type.isDynamicallyEncoded())
|
||||
{
|
||||
unsigned int baseEncodedSize = _type.calldataEncodedSize();
|
||||
solAssert(baseEncodedSize > 1, "");
|
||||
Whiskers w(R"(
|
||||
function <functionName>(base_ref, ptr) -> <return> {
|
||||
let rel_offset_of_tail := calldataload(ptr)
|
||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
||||
value := add(rel_offset_of_tail, base_ref)
|
||||
<handleLength>
|
||||
}
|
||||
)");
|
||||
if (_type.isDynamicallySized())
|
||||
{
|
||||
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
|
||||
solAssert(!!arrayType, "");
|
||||
unsigned int calldataStride = arrayType->calldataStride();
|
||||
w("handleLength", Whiskers(R"(
|
||||
length := calldataload(value)
|
||||
value := add(value, 0x20)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||
)")("calldataStride", toCompactHexWithPrefix(calldataStride)).render());
|
||||
w("return", "value, length");
|
||||
}
|
||||
else
|
||||
{
|
||||
w("handleLength", "");
|
||||
w("return", "value");
|
||||
}
|
||||
w("neededLength", toCompactHexWithPrefix(baseEncodedSize));
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
else if (_type.isValueType())
|
||||
{
|
||||
string decodingFunction;
|
||||
if (auto const* functionType = dynamic_cast<FunctionType const*>(&_type))
|
||||
decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false);
|
||||
else
|
||||
decodingFunction = abiDecodingFunctionValueType(_type, false);
|
||||
// Note that the second argument to the decoding function should be discarded after inlining.
|
||||
return Whiskers(R"(
|
||||
function <functionName>(baseRef, ptr) -> value {
|
||||
value := <decodingFunction>(ptr, add(ptr, 32))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("decodingFunction", decodingFunction)
|
||||
.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(
|
||||
_type.category() == Type::Category::Array ||
|
||||
_type.category() == Type::Category::Struct,
|
||||
""
|
||||
);
|
||||
return Whiskers(R"(
|
||||
function <functionName>(baseRef, ptr) -> value {
|
||||
value := ptr
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
|
||||
{
|
||||
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
|
||||
@ -1679,24 +1569,6 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
||||
return headSize;
|
||||
}
|
||||
|
||||
string ABIFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
|
||||
{
|
||||
string result;
|
||||
if (_startSuffix < _endSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_startSuffix++);
|
||||
while (_startSuffix < _endSuffix)
|
||||
result += ", " + _baseName + to_string(_startSuffix++);
|
||||
}
|
||||
else if (_endSuffix < _startSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_endSuffix++);
|
||||
while (_endSuffix < _startSuffix)
|
||||
result = _baseName + to_string(_endSuffix++) + ", " + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions const& _options)
|
||||
{
|
||||
if (_type.category() == Type::Category::Function && !_options.encodeFunctionFromStack)
|
||||
|
@ -41,7 +41,7 @@ class Type;
|
||||
class ArrayType;
|
||||
class StructType;
|
||||
class FunctionType;
|
||||
using TypePointer = std::shared_ptr<Type const>;
|
||||
using TypePointer = Type const*;
|
||||
using TypePointers = std::vector<TypePointer>;
|
||||
|
||||
/**
|
||||
@ -128,12 +128,6 @@ private:
|
||||
std::string toFunctionNameSuffix() const;
|
||||
};
|
||||
|
||||
/// @returns the name of the cleanup function for the given type and
|
||||
/// adds its implementation to the requested functions.
|
||||
/// @param _revertOnFailure if true, causes revert on invalid data,
|
||||
/// otherwise an assertion failure.
|
||||
std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false);
|
||||
|
||||
/// 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).
|
||||
@ -143,13 +137,6 @@ private:
|
||||
/// single variable.
|
||||
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns the name of the function that converts a value of type @a _from
|
||||
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
|
||||
/// (i.e. "clean"). Asserts on failure.
|
||||
std::string conversionFunction(Type const& _from, Type const& _to);
|
||||
|
||||
std::string cleanupCombinedExternalFunctionIdFunction();
|
||||
|
||||
/// @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
|
||||
@ -168,7 +155,9 @@ private:
|
||||
EncodingOptions const& _options
|
||||
);
|
||||
/// Part of @a abiEncodingFunction for array target type and given calldata array.
|
||||
std::string abiEncodingFunctionCalldataArray(
|
||||
/// Uses calldatacopy and does not perform cleanup or validation and can therefore only
|
||||
/// be used for byte arrays and arrays with the base type uint256 or bytes32.
|
||||
std::string abiEncodingFunctionCalldataArrayWithoutCleanup(
|
||||
Type const& _givenType,
|
||||
Type const& _targetType,
|
||||
EncodingOptions const& _options
|
||||
@ -255,6 +244,9 @@ private:
|
||||
/// 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);
|
||||
|
||||
/// @returns the name of a function used during encoding that stores the length
|
||||
/// if the array is dynamically sized (and the options do not request in-place encoding).
|
||||
/// It returns the new encoding position.
|
||||
@ -275,12 +267,6 @@ private:
|
||||
/// @returns the size of the static part of the encoding of the given types.
|
||||
static size_t headSize(TypePointers const& _targetTypes);
|
||||
|
||||
/// @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);
|
||||
|
||||
/// @returns the number of variables needed to store a type.
|
||||
/// This is one for almost all types. The exception being dynamically sized calldata arrays or
|
||||
/// external function types (if we are encoding from stack, i.e. _options.encodeFunctionFromStack
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libsolidity/codegen/ArrayUtils.h>
|
||||
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/codegen/LValue.h>
|
||||
@ -32,8 +33,9 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
using namespace langutil;
|
||||
using namespace solidity;
|
||||
using namespace dev::solidity;
|
||||
|
||||
void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const
|
||||
{
|
||||
@ -43,7 +45,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
// stack layout: [source_ref] [source length] target_ref (top)
|
||||
solAssert(_targetType.location() == DataLocation::Storage, "");
|
||||
|
||||
TypePointer uint256 = make_shared<IntegerType>(256);
|
||||
TypePointer uint256 = TypeProvider::uint256();
|
||||
TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType();
|
||||
TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType();
|
||||
|
||||
@ -73,8 +75,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
}
|
||||
|
||||
// stack: target_ref source_ref source_length
|
||||
TypePointer targetType = _targetType.shared_from_this();
|
||||
TypePointer sourceType = _sourceType.shared_from_this();
|
||||
TypePointer targetType = &_targetType;
|
||||
TypePointer sourceType = &_sourceType;
|
||||
m_context.callLowLevelFunction(
|
||||
"$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(),
|
||||
3,
|
||||
@ -335,7 +337,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
m_context << Instruction::DUP3 << Instruction::DUP5;
|
||||
accessIndex(_sourceType, false);
|
||||
MemoryItem(m_context, *_sourceType.baseType(), true).retrieveValue(SourceLocation(), true);
|
||||
if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType().get()))
|
||||
if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType()))
|
||||
copyArrayToMemory(*baseArray, _padToWordBoundaries);
|
||||
else
|
||||
utils.storeInMemoryDynamic(*_sourceType.baseType());
|
||||
@ -492,7 +494,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
else
|
||||
m_context << Instruction::DUP2 << u256(0);
|
||||
StorageItem(m_context, *_sourceType.baseType()).retrieveValue(SourceLocation(), true);
|
||||
if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType().get()))
|
||||
if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType()))
|
||||
copyArrayToMemory(*baseArray, _padToWordBoundaries);
|
||||
else
|
||||
utils.storeInMemoryDynamic(*_sourceType.baseType());
|
||||
@ -529,7 +531,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
|
||||
void ArrayUtils::clearArray(ArrayType const& _typeIn) const
|
||||
{
|
||||
TypePointer type = _typeIn.shared_from_this();
|
||||
TypePointer type = &_typeIn;
|
||||
m_context.callLowLevelFunction(
|
||||
"$clearArray_" + _typeIn.identifier(),
|
||||
2,
|
||||
@ -583,7 +585,7 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const
|
||||
ArrayUtils(_context).convertLengthToSize(_type);
|
||||
_context << Instruction::ADD << Instruction::SWAP1;
|
||||
if (_type.baseType()->storageBytes() < 32)
|
||||
ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
|
||||
ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256());
|
||||
else
|
||||
ArrayUtils(_context).clearStorageLoop(_type.baseType());
|
||||
_context << Instruction::POP;
|
||||
@ -624,7 +626,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
|
||||
<< Instruction::SWAP1;
|
||||
// stack: data_pos_end data_pos
|
||||
if (_type.storageStride() < 32)
|
||||
clearStorageLoop(make_shared<IntegerType>(256));
|
||||
clearStorageLoop(TypeProvider::uint256());
|
||||
else
|
||||
clearStorageLoop(_type.baseType());
|
||||
// cleanup
|
||||
@ -634,7 +636,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
|
||||
|
||||
void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
|
||||
{
|
||||
TypePointer type = _typeIn.shared_from_this();
|
||||
TypePointer type = &_typeIn;
|
||||
m_context.callLowLevelFunction(
|
||||
"$resizeDynamicArray_" + _typeIn.identifier(),
|
||||
2,
|
||||
@ -731,7 +733,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
|
||||
ArrayUtils(_context).convertLengthToSize(_type);
|
||||
_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
|
||||
// stack: ref new_length current_length first_word data_location_end data_location
|
||||
ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
|
||||
ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256());
|
||||
_context << Instruction::POP;
|
||||
// stack: ref new_length current_length first_word
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
@ -770,7 +772,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
|
||||
_context << Instruction::SWAP2 << Instruction::ADD;
|
||||
// stack: ref new_length delete_end delete_start
|
||||
if (_type.storageStride() < 32)
|
||||
ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
|
||||
ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256());
|
||||
else
|
||||
ArrayUtils(_context).clearStorageLoop(_type.baseType());
|
||||
|
||||
@ -910,7 +912,7 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
|
||||
void ArrayUtils::clearStorageLoop(TypePointer _type) const
|
||||
{
|
||||
m_context.callLowLevelFunction(
|
||||
"$clearStorageLoop_" + _type->identifier(),
|
||||
|
@ -32,7 +32,7 @@ namespace solidity
|
||||
class CompilerContext;
|
||||
class Type;
|
||||
class ArrayType;
|
||||
using TypePointer = std::shared_ptr<Type const>;
|
||||
using TypePointer = Type const*;
|
||||
|
||||
/**
|
||||
* Class that provides code generation for handling arrays.
|
||||
@ -81,7 +81,7 @@ public:
|
||||
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
|
||||
/// Stack pre: end_ref start_ref
|
||||
/// Stack post: end_ref
|
||||
void clearStorageLoop(TypePointer const& _type) const;
|
||||
void clearStorageLoop(TypePointer _type) const;
|
||||
/// Converts length to size (number of storage slots or calldata/memory bytes).
|
||||
/// if @a _pad then add padding to multiples of 32 bytes for calldata/memory.
|
||||
/// Stack pre: length
|
||||
|
@ -53,11 +53,9 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace langutil;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
using namespace dev::eth;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
void CompilerContext::addStateVariable(
|
||||
VariableDeclaration const& _declaration,
|
||||
@ -398,10 +396,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
_assembly + "\n"
|
||||
"------------------ Errors: ----------------\n";
|
||||
for (auto const& error: errorReporter.errors())
|
||||
message += SourceReferenceFormatter::formatExceptionInformation(
|
||||
*error,
|
||||
(error->type() == Error::Type::Warning) ? "Warning" : "Error"
|
||||
);
|
||||
message += SourceReferenceFormatter::formatErrorInformation(*error);
|
||||
message += "-------------------------------------------\n";
|
||||
|
||||
solAssert(false, message);
|
||||
@ -554,6 +549,3 @@ void CompilerContext::FunctionCompilationQueue::startFunction(Declaration const&
|
||||
m_functionsToCompile.pop();
|
||||
m_alreadyCompiledFunctions.insert(&_function);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +203,7 @@ public:
|
||||
|
||||
/// Append elements to the current instruction list and adjust @a m_stackOffset.
|
||||
CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm->append(_item); return *this; }
|
||||
CompilerContext& operator<<(Instruction _instruction) { m_asm->append(_instruction); return *this; }
|
||||
CompilerContext& operator<<(dev::eth::Instruction _instruction) { m_asm->append(_instruction); return *this; }
|
||||
CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; }
|
||||
CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; }
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
#include <libsolidity/codegen/ArrayUtils.h>
|
||||
#include <libsolidity/codegen/LValue.h>
|
||||
@ -31,11 +32,9 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace langutil;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
using namespace dev::solidity;
|
||||
|
||||
unsigned const CompilerUtils::dataStartOffset = 4;
|
||||
size_t const CompilerUtils::freeMemoryPointer = 64;
|
||||
@ -85,13 +84,13 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer()
|
||||
|
||||
void CompilerUtils::revertWithStringData(Type const& _argumentType)
|
||||
{
|
||||
solAssert(_argumentType.isImplicitlyConvertibleTo(*Type::fromElementaryTypeName("string memory")), "");
|
||||
solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory")), "");
|
||||
fetchFreeMemoryPointer();
|
||||
m_context << (u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256("Error(string)")))) << (256 - 32));
|
||||
m_context << Instruction::DUP2 << Instruction::MSTORE;
|
||||
m_context << u256(4) << Instruction::ADD;
|
||||
// Stack: <string data> <mem pos of encoding start>
|
||||
abiEncode({_argumentType.shared_from_this()}, {make_shared<ArrayType>(DataLocation::Memory, true)});
|
||||
abiEncode({&_argumentType}, {TypeProvider::array(DataLocation::Memory, true)});
|
||||
toSizeAfterFreeMemoryPointer();
|
||||
m_context << Instruction::REVERT;
|
||||
}
|
||||
@ -187,7 +186,7 @@ void CompilerUtils::loadFromMemoryDynamic(
|
||||
|
||||
void CompilerUtils::storeInMemory(unsigned _offset)
|
||||
{
|
||||
unsigned numBytes = prepareMemoryStore(IntegerType::uint256(), true);
|
||||
unsigned numBytes = prepareMemoryStore(*TypeProvider::uint256(), true);
|
||||
if (numBytes > 0)
|
||||
m_context << u256(_offset) << Instruction::MSTORE;
|
||||
}
|
||||
@ -201,7 +200,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
|
||||
ref->location() == DataLocation::Memory,
|
||||
"Only in-memory reference type can be stored."
|
||||
);
|
||||
storeInMemoryDynamic(IntegerType::uint256(), _padToWordBoundaries);
|
||||
storeInMemoryDynamic(*TypeProvider::uint256(), _padToWordBoundaries);
|
||||
}
|
||||
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
|
||||
{
|
||||
@ -313,11 +312,11 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
else
|
||||
{
|
||||
// first load from calldata and potentially convert to memory if arrayType is memory
|
||||
TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false);
|
||||
TypePointer calldataType = TypeProvider::withLocation(&arrayType, DataLocation::CallData, false);
|
||||
if (calldataType->isDynamicallySized())
|
||||
{
|
||||
// put on stack: data_pointer length
|
||||
loadFromMemoryDynamic(IntegerType::uint256(), !_fromMemory);
|
||||
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory);
|
||||
m_context << Instruction::SWAP1;
|
||||
// stack: input_end base_offset next_pointer data_offset
|
||||
m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
|
||||
@ -328,7 +327,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
{"input_end", "base_offset", "next_ptr", "array_head_ptr"}
|
||||
);
|
||||
// retrieve length
|
||||
loadFromMemoryDynamic(IntegerType::uint256(), !_fromMemory, true);
|
||||
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true);
|
||||
// stack: input_end base_offset next_pointer array_length data_pointer
|
||||
m_context << Instruction::SWAP2;
|
||||
// stack: input_end base_offset data_pointer array_length next_pointer
|
||||
@ -456,7 +455,7 @@ void CompilerUtils::encodeToMemory(
|
||||
type = _givenTypes[i]; // delay conversion
|
||||
else
|
||||
convertType(*_givenTypes[i], *targetType, true);
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(type))
|
||||
ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries);
|
||||
else
|
||||
storeInMemoryDynamic(*type, _padToWordBoundaries);
|
||||
@ -484,7 +483,7 @@ void CompilerUtils::encodeToMemory(
|
||||
{
|
||||
auto const& strType = dynamic_cast<StringLiteralType const&>(*_givenTypes[i]);
|
||||
m_context << u256(strType.value().size());
|
||||
storeInMemoryDynamic(IntegerType::uint256(), true);
|
||||
storeInMemoryDynamic(*TypeProvider::uint256(), true);
|
||||
// stack: ... <end_of_mem'>
|
||||
storeInMemoryDynamic(strType, _padToWordBoundaries);
|
||||
}
|
||||
@ -499,7 +498,7 @@ void CompilerUtils::encodeToMemory(
|
||||
m_context << dupInstruction(1 + arrayType.sizeOnStack());
|
||||
ArrayUtils(m_context).retrieveLength(arrayType, 1);
|
||||
// stack: ... <end_of_mem> <value...> <end_of_mem'> <length>
|
||||
storeInMemoryDynamic(IntegerType::uint256(), true);
|
||||
storeInMemoryDynamic(*TypeProvider::uint256(), true);
|
||||
// stack: ... <end_of_mem> <value...> <end_of_mem''>
|
||||
// copy the new memory pointer
|
||||
m_context << swapInstruction(arrayType.sizeOnStack() + 1) << Instruction::POP;
|
||||
@ -870,7 +869,7 @@ void CompilerUtils::convertType(
|
||||
allocateMemory(storageSize);
|
||||
// stack: mempos
|
||||
m_context << Instruction::DUP1 << u256(data.size());
|
||||
storeInMemoryDynamic(IntegerType::uint256());
|
||||
storeInMemoryDynamic(*TypeProvider::uint256());
|
||||
// stack: mempos datapos
|
||||
storeStringData(data);
|
||||
}
|
||||
@ -919,7 +918,7 @@ void CompilerUtils::convertType(
|
||||
if (targetType.isDynamicallySized())
|
||||
{
|
||||
m_context << Instruction::DUP2;
|
||||
storeInMemoryDynamic(IntegerType::uint256());
|
||||
storeInMemoryDynamic(*TypeProvider::uint256());
|
||||
}
|
||||
// stack: <mem start> <source ref> (variably sized) <length> <mem data pos>
|
||||
if (targetType.baseType()->isValueType())
|
||||
@ -989,32 +988,48 @@ void CompilerUtils::convertType(
|
||||
switch (typeOnStack.location())
|
||||
{
|
||||
case DataLocation::Storage:
|
||||
// stack: <source ref>
|
||||
allocateMemory(typeOnStack.memorySize());
|
||||
m_context << Instruction::SWAP1 << Instruction::DUP2;
|
||||
// stack: <memory ptr> <source ref> <memory ptr>
|
||||
for (auto const& member: typeOnStack.members(nullptr))
|
||||
{
|
||||
auto conversionImpl =
|
||||
[typeOnStack = &typeOnStack, targetType = &targetType](CompilerContext& _context)
|
||||
{
|
||||
if (!member.type->canLiveOutsideStorage())
|
||||
continue;
|
||||
pair<u256, unsigned> const& offsets = typeOnStack.storageOffsetsOfMember(member.name);
|
||||
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
StorageItem(m_context, *member.type).retrieveValue(SourceLocation(), true);
|
||||
TypePointer targetMemberType = targetType.memberType(member.name);
|
||||
solAssert(!!targetMemberType, "Member not found in target type.");
|
||||
convertType(*member.type, *targetMemberType, true);
|
||||
storeInMemoryDynamic(*targetMemberType, true);
|
||||
}
|
||||
m_context << Instruction::POP << Instruction::POP;
|
||||
CompilerUtils utils(_context);
|
||||
// stack: <source ref>
|
||||
utils.allocateMemory(typeOnStack->memorySize());
|
||||
_context << Instruction::SWAP1 << Instruction::DUP2;
|
||||
// stack: <memory ptr> <source ref> <memory ptr>
|
||||
for (auto const& member: typeOnStack->members(nullptr))
|
||||
{
|
||||
if (!member.type->canLiveOutsideStorage())
|
||||
continue;
|
||||
pair<u256, unsigned> const& offsets = typeOnStack->storageOffsetsOfMember(member.name);
|
||||
_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
|
||||
_context << u256(offsets.second);
|
||||
StorageItem(_context, *member.type).retrieveValue(SourceLocation(), true);
|
||||
TypePointer targetMemberType = targetType->memberType(member.name);
|
||||
solAssert(!!targetMemberType, "Member not found in target type.");
|
||||
utils.convertType(*member.type, *targetMemberType, true);
|
||||
utils.storeInMemoryDynamic(*targetMemberType, true);
|
||||
}
|
||||
_context << Instruction::POP << Instruction::POP;
|
||||
};
|
||||
if (typeOnStack.recursive())
|
||||
m_context.callLowLevelFunction(
|
||||
"$convertRecursiveArrayStorageToMemory_" + typeOnStack.identifier() + "_to_" + targetType.identifier(),
|
||||
1,
|
||||
1,
|
||||
conversionImpl
|
||||
);
|
||||
else
|
||||
conversionImpl(m_context);
|
||||
break;
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
solUnimplementedAssert(!typeOnStack.isDynamicallyEncoded(), "");
|
||||
m_context << Instruction::DUP1;
|
||||
m_context << Instruction::CALLDATASIZE;
|
||||
m_context << Instruction::SUB;
|
||||
abiDecode({targetType.shared_from_this()}, false);
|
||||
abiDecode({&targetType}, false);
|
||||
break;
|
||||
}
|
||||
case DataLocation::Memory:
|
||||
@ -1128,6 +1143,14 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
|
||||
_context.appendInvalid();
|
||||
});
|
||||
if (CompilerContext* runCon = m_context.runtimeContext())
|
||||
{
|
||||
leftShiftNumberOnStack(32);
|
||||
m_context << runCon->lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
|
||||
_context.appendInvalid();
|
||||
}).toSubAssemblyTag(m_context.runtimeSub());
|
||||
m_context << Instruction::OR;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1147,7 +1170,7 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
return;
|
||||
}
|
||||
|
||||
TypePointer type = _type.shared_from_this();
|
||||
TypePointer type = &_type;
|
||||
m_context.callLowLevelFunction(
|
||||
"$pushZeroValue_" + referenceType->identifier(),
|
||||
0,
|
||||
@ -1157,13 +1180,13 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
utils.allocateMemory(max(32u, type->calldataEncodedSize()));
|
||||
_context << Instruction::DUP1;
|
||||
|
||||
if (auto structType = dynamic_cast<StructType const*>(type.get()))
|
||||
if (auto structType = dynamic_cast<StructType const*>(type))
|
||||
for (auto const& member: structType->members(nullptr))
|
||||
{
|
||||
utils.pushZeroValue(*member.type);
|
||||
utils.storeInMemoryDynamic(*member.type);
|
||||
}
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(type))
|
||||
{
|
||||
solAssert(!arrayType->isDynamicallySized(), "");
|
||||
if (arrayType->length() > 0)
|
||||
@ -1260,10 +1283,10 @@ void CompilerUtils::popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jum
|
||||
m_context.adjustStackOffset(amount);
|
||||
}
|
||||
|
||||
unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _variableTypes)
|
||||
unsigned CompilerUtils::sizeOnStack(vector<Type const*> const& _variableTypes)
|
||||
{
|
||||
unsigned size = 0;
|
||||
for (shared_ptr<Type const> const& type: _variableTypes)
|
||||
for (Type const* const& type: _variableTypes)
|
||||
size += type->sizeOnStack();
|
||||
return size;
|
||||
}
|
||||
@ -1306,7 +1329,7 @@ void CompilerUtils::storeStringData(bytesConstRef _data)
|
||||
for (unsigned i = 0; i < _data.size(); i += 32)
|
||||
{
|
||||
m_context << h256::Arith(h256(_data.cropped(i), h256::AlignLeft));
|
||||
storeInMemoryDynamic(IntegerType::uint256());
|
||||
storeInMemoryDynamic(*TypeProvider::uint256());
|
||||
}
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
@ -1414,6 +1437,3 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
|
||||
namespace dev {
|
||||
@ -80,7 +82,7 @@ public:
|
||||
/// @returns the number of bytes consumed in memory.
|
||||
unsigned loadFromMemory(
|
||||
unsigned _offset,
|
||||
Type const& _type = IntegerType::uint256(),
|
||||
Type const& _type = *TypeProvider::uint256(),
|
||||
bool _fromCalldata = false,
|
||||
bool _padToWords = false
|
||||
);
|
||||
@ -264,7 +266,7 @@ public:
|
||||
|
||||
template <class T>
|
||||
static unsigned sizeOnStack(std::vector<T> const& _variables);
|
||||
static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes);
|
||||
static unsigned sizeOnStack(std::vector<Type const*> const& _variableTypes);
|
||||
|
||||
/// Helper function to shift top value on the stack to the left.
|
||||
/// Stack pre: <value> <shift_by_bits>
|
||||
|
@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/codegen/ContractCompiler.h>
|
||||
#include <libsolidity/codegen/ExpressionCompiler.h>
|
||||
@ -39,6 +40,7 @@
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace langutil;
|
||||
using namespace dev::eth;
|
||||
using namespace dev::solidity;
|
||||
|
||||
namespace
|
||||
@ -870,7 +872,7 @@ bool ContractCompiler::visit(Return const& _return)
|
||||
|
||||
TypePointer expectedType;
|
||||
if (expression->annotation().type->category() == Type::Category::Tuple || types.size() != 1)
|
||||
expectedType = make_shared<TupleType>(types);
|
||||
expectedType = TypeProvider::tuple(move(types));
|
||||
else
|
||||
expectedType = types.front();
|
||||
compileExpression(*expression, expectedType);
|
||||
@ -914,7 +916,7 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar
|
||||
CompilerUtils utils(m_context);
|
||||
compileExpression(*expression);
|
||||
TypePointers valueTypes;
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(expression->annotation().type.get()))
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(expression->annotation().type))
|
||||
valueTypes = tupleType->components();
|
||||
else
|
||||
valueTypes = TypePointers{expression->annotation().type};
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libsolidity/codegen/ExpressionCompiler.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/codegen/LValue.h>
|
||||
@ -39,11 +40,10 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace langutil;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
using namespace dev::solidity;
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
void ExpressionCompiler::compile(Expression const& _expression)
|
||||
{
|
||||
@ -103,7 +103,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
||||
|
||||
for (size_t i = 0; i < paramTypes.size(); ++i)
|
||||
{
|
||||
if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get()))
|
||||
if (auto mappingType = dynamic_cast<MappingType const*>(returnType))
|
||||
{
|
||||
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
|
||||
|
||||
@ -153,7 +153,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
||||
m_context << u256(0);
|
||||
returnType = mappingType->valueType();
|
||||
}
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType.get()))
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType))
|
||||
{
|
||||
// pop offset
|
||||
m_context << Instruction::POP;
|
||||
@ -177,7 +177,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
||||
unsigned retSizeOnStack = 0;
|
||||
auto returnTypes = accessorType.returnParameterTypes();
|
||||
solAssert(returnTypes.size() >= 1, "");
|
||||
if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get()))
|
||||
if (StructType const* structType = dynamic_cast<StructType const*>(returnType))
|
||||
{
|
||||
// remove offset
|
||||
m_context << Instruction::POP;
|
||||
@ -187,7 +187,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
||||
{
|
||||
if (returnTypes[i]->category() == Type::Category::Mapping)
|
||||
continue;
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(returnTypes[i].get()))
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(returnTypes[i]))
|
||||
if (!arrayType->isByteArray())
|
||||
continue;
|
||||
pair<u256, unsigned> const& offsets = structType->storageOffsetsOfMember(names[i]);
|
||||
@ -497,7 +497,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
functionType = structType.constructorType();
|
||||
}
|
||||
else
|
||||
functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type);
|
||||
functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
|
||||
|
||||
TypePointers parameterTypes = functionType->parameterTypes();
|
||||
vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments();
|
||||
@ -648,7 +648,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
_functionCall.expression().accept(*this);
|
||||
|
||||
arguments.front()->accept(*this);
|
||||
utils().convertType(*arguments.front()->annotation().type, IntegerType::uint256(), true);
|
||||
utils().convertType(*arguments.front()->annotation().type, *TypeProvider::uint256(), true);
|
||||
// Note that function is not the original function, but the ".gas" function.
|
||||
// Its values of gasSet and valueSet is equal to the original function's though.
|
||||
unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0);
|
||||
@ -732,9 +732,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
arguments.front()->accept(*this);
|
||||
// Optimization: If type is bytes or string, then do not encode,
|
||||
// but directly compute keccak256 on memory.
|
||||
if (*argType == ArrayType::bytesMemory() || *argType == ArrayType::stringMemory())
|
||||
if (*argType == *TypeProvider::bytesMemory() || *argType == *TypeProvider::stringMemory())
|
||||
{
|
||||
ArrayUtils(m_context).retrieveLength(ArrayType::bytesMemory());
|
||||
ArrayUtils(m_context).retrieveLength(*TypeProvider::bytesMemory());
|
||||
m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD;
|
||||
}
|
||||
else
|
||||
@ -781,7 +781,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
++numIndexed;
|
||||
arguments[arg - 1]->accept(*this);
|
||||
if (auto const& referenceType = dynamic_pointer_cast<ReferenceType const>(paramTypes[arg - 1]))
|
||||
if (auto const& referenceType = dynamic_cast<ReferenceType const*>(paramTypes[arg - 1]))
|
||||
{
|
||||
utils().fetchFreeMemoryPointer();
|
||||
utils().packedEncode(
|
||||
@ -836,13 +836,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
case FunctionType::Kind::MulMod:
|
||||
{
|
||||
arguments[2]->accept(*this);
|
||||
utils().convertType(*arguments[2]->annotation().type, IntegerType::uint256());
|
||||
utils().convertType(*arguments[2]->annotation().type, *TypeProvider::uint256());
|
||||
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
||||
m_context.appendConditionalInvalid();
|
||||
for (unsigned i = 1; i < 3; i ++)
|
||||
{
|
||||
arguments[2 - i]->accept(*this);
|
||||
utils().convertType(*arguments[2 - i]->annotation().type, IntegerType::uint256());
|
||||
utils().convertType(*arguments[2 - i]->annotation().type, *TypeProvider::uint256());
|
||||
}
|
||||
if (function.kind() == FunctionType::Kind::AddMod)
|
||||
m_context << Instruction::ADDMOD;
|
||||
@ -873,10 +873,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
solAssert(function.parameterTypes().size() == 1, "");
|
||||
solAssert(!!function.parameterTypes()[0], "");
|
||||
TypePointer paramType = function.parameterTypes()[0];
|
||||
shared_ptr<ArrayType> arrayType =
|
||||
ArrayType const* arrayType =
|
||||
function.kind() == FunctionType::Kind::ArrayPush ?
|
||||
make_shared<ArrayType>(DataLocation::Storage, paramType) :
|
||||
make_shared<ArrayType>(DataLocation::Storage);
|
||||
TypeProvider::array(DataLocation::Storage, paramType) :
|
||||
TypeProvider::array(DataLocation::Storage);
|
||||
|
||||
// stack: ArrayReference
|
||||
arguments[0]->accept(*this);
|
||||
@ -928,7 +928,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
|
||||
// Fetch requested length.
|
||||
arguments[0]->accept(*this);
|
||||
utils().convertType(*arguments[0]->annotation().type, IntegerType::uint256());
|
||||
utils().convertType(*arguments[0]->annotation().type, *TypeProvider::uint256());
|
||||
|
||||
// Stack: requested_length
|
||||
utils().fetchFreeMemoryPointer();
|
||||
@ -1058,11 +1058,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature)
|
||||
{
|
||||
// hash the signature
|
||||
if (auto const* stringType = dynamic_cast<StringLiteralType const*>(selectorType.get()))
|
||||
if (auto const* stringType = dynamic_cast<StringLiteralType const*>(selectorType))
|
||||
{
|
||||
FixedHash<4> hash(dev::keccak256(stringType->value()));
|
||||
m_context << (u256(FixedHash<4>::Arith(hash)) << (256 - 32));
|
||||
dataOnStack = make_shared<FixedBytesType>(4);
|
||||
dataOnStack = TypeProvider::fixedBytes(4);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1073,7 +1073,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
m_context << Instruction::KECCAK256;
|
||||
// stack: <memory pointer> <hash>
|
||||
|
||||
dataOnStack = make_shared<FixedBytesType>(32);
|
||||
dataOnStack = TypeProvider::fixedBytes(32);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1104,7 +1104,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
arguments.front()->accept(*this);
|
||||
TypePointer firstArgType = arguments.front()->annotation().type;
|
||||
TypePointers targetTypes;
|
||||
if (TupleType const* targetTupleType = dynamic_cast<TupleType const*>(_functionCall.annotation().type.get()))
|
||||
if (TupleType const* targetTupleType = dynamic_cast<TupleType const*>(_functionCall.annotation().type))
|
||||
targetTypes = targetTupleType->components();
|
||||
else
|
||||
targetTypes = TypePointers{_functionCall.annotation().type};
|
||||
@ -1115,7 +1115,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
utils().abiDecode(targetTypes, false);
|
||||
else
|
||||
{
|
||||
utils().convertType(*firstArgType, ArrayType::bytesMemory());
|
||||
utils().convertType(*firstArgType, *TypeProvider::bytesMemory());
|
||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||
m_context << Instruction::SWAP1 << Instruction::MLOAD;
|
||||
// stack now: <mem_pos> <length>
|
||||
@ -1146,7 +1146,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _memberAccess);
|
||||
// Check whether the member is a bound function.
|
||||
ASTString const& member = _memberAccess.memberName();
|
||||
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
|
||||
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
|
||||
if (funType->bound())
|
||||
{
|
||||
_memberAccess.expression().accept(*this);
|
||||
@ -1175,14 +1175,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
|
||||
// Special processing for TypeType because we do not want to visit the library itself
|
||||
// for internal functions, or enum/struct definitions.
|
||||
if (TypeType const* type = dynamic_cast<TypeType const*>(_memberAccess.expression().annotation().type.get()))
|
||||
if (TypeType const* type = dynamic_cast<TypeType const*>(_memberAccess.expression().annotation().type))
|
||||
{
|
||||
if (dynamic_cast<ContractType const*>(type->actualType().get()))
|
||||
if (dynamic_cast<ContractType const*>(type->actualType()))
|
||||
{
|
||||
solAssert(_memberAccess.annotation().type, "_memberAccess has no type");
|
||||
if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
|
||||
appendVariable(*variable, static_cast<Expression const&>(_memberAccess));
|
||||
else if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
|
||||
else if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
|
||||
{
|
||||
switch (funType->kind())
|
||||
{
|
||||
@ -1224,14 +1224,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
solAssert(false, "unsupported member function");
|
||||
}
|
||||
}
|
||||
else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type.get()))
|
||||
else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else
|
||||
_memberAccess.expression().accept(*this);
|
||||
}
|
||||
else if (auto enumType = dynamic_cast<EnumType const*>(type->actualType().get()))
|
||||
else if (auto enumType = dynamic_cast<EnumType const*>(type->actualType()))
|
||||
{
|
||||
_memberAccess.expression().accept(*this);
|
||||
m_context << enumType->memberValue(_memberAccess.memberName());
|
||||
@ -1289,7 +1289,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
identifier = FunctionType(*function).externalIdentifier();
|
||||
else
|
||||
solAssert(false, "Contract member is neither variable nor function.");
|
||||
utils().convertType(type, type.isPayable() ? AddressType::addressPayable() : AddressType::address(), true);
|
||||
utils().convertType(type, type.isPayable() ? *TypeProvider::payableAddress() : *TypeProvider::address(), true);
|
||||
m_context << identifier;
|
||||
}
|
||||
else
|
||||
@ -1307,7 +1307,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
utils().convertType(
|
||||
*_memberAccess.expression().annotation().type,
|
||||
AddressType::address(),
|
||||
*TypeProvider::address(),
|
||||
true
|
||||
);
|
||||
m_context << Instruction::BALANCE;
|
||||
@ -1324,7 +1324,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
else if ((set<string>{"call", "callcode", "delegatecall", "staticcall"}).count(member))
|
||||
utils().convertType(
|
||||
*_memberAccess.expression().annotation().type,
|
||||
AddressType::address(),
|
||||
*TypeProvider::address(),
|
||||
true
|
||||
);
|
||||
else
|
||||
@ -1401,12 +1401,17 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||
utils().storeStringData(contract.name());
|
||||
}
|
||||
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else
|
||||
solAssert(false, "Unknown magic member.");
|
||||
break;
|
||||
case Type::Category::Struct:
|
||||
{
|
||||
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.expression().annotation().type);
|
||||
TypePointer const& memberType = _memberAccess.annotation().type;
|
||||
switch (type.location())
|
||||
{
|
||||
case DataLocation::Storage:
|
||||
@ -1419,7 +1424,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
case DataLocation::Memory:
|
||||
{
|
||||
m_context << type.memoryOffsetOfMember(member) << Instruction::ADD;
|
||||
setLValue<MemoryItem>(_memberAccess, *_memberAccess.annotation().type);
|
||||
setLValue<MemoryItem>(_memberAccess, *memberType);
|
||||
break;
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
@ -1428,21 +1433,28 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
m_context << Instruction::DUP1;
|
||||
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
||||
CompilerUtils(m_context).accessCalldataTail(*_memberAccess.annotation().type);
|
||||
CompilerUtils(m_context).accessCalldataTail(*memberType);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
||||
// For non-value types the calldata offset is returned directly.
|
||||
if (_memberAccess.annotation().type->isValueType())
|
||||
if (memberType->isValueType())
|
||||
{
|
||||
solAssert(_memberAccess.annotation().type->calldataEncodedSize() > 0, "");
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false);
|
||||
solAssert(memberType->calldataEncodedSize() > 0, "");
|
||||
solAssert(memberType->storageBytes() <= 32, "");
|
||||
if (memberType->storageBytes() < 32 && m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
|
||||
{
|
||||
m_context << u256(32);
|
||||
CompilerUtils(m_context).abiDecodeV2({memberType}, false);
|
||||
}
|
||||
else
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(*memberType, true, true, false);
|
||||
}
|
||||
else
|
||||
solAssert(
|
||||
_memberAccess.annotation().type->category() == Type::Category::Array ||
|
||||
_memberAccess.annotation().type->category() == Type::Category::Struct,
|
||||
memberType->category() == Type::Category::Array ||
|
||||
memberType->category() == Type::Category::Struct,
|
||||
""
|
||||
);
|
||||
}
|
||||
@ -1535,7 +1547,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
TypePointers{keyType}
|
||||
);
|
||||
m_context << Instruction::SWAP1;
|
||||
utils().storeInMemoryDynamic(IntegerType::uint256());
|
||||
utils().storeInMemoryDynamic(*TypeProvider::uint256());
|
||||
utils().toSizeAfterFreeMemoryPointer();
|
||||
}
|
||||
else
|
||||
@ -1544,7 +1556,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression());
|
||||
m_context << Instruction::SWAP1;
|
||||
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
|
||||
utils().storeInMemoryDynamic(IntegerType::uint256());
|
||||
utils().storeInMemoryDynamic(*TypeProvider::uint256());
|
||||
m_context << u256(0);
|
||||
}
|
||||
m_context << Instruction::KECCAK256;
|
||||
@ -1557,7 +1569,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
||||
|
||||
_indexAccess.indexExpression()->accept(*this);
|
||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true);
|
||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true);
|
||||
// stack layout: <base_ref> [<length>] <index>
|
||||
switch (arrayType.location())
|
||||
{
|
||||
@ -1589,12 +1601,25 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
{
|
||||
ArrayUtils(m_context).accessIndex(arrayType, true);
|
||||
if (arrayType.baseType()->isValueType())
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(
|
||||
*arrayType.baseType(),
|
||||
true,
|
||||
!arrayType.isByteArray(),
|
||||
false
|
||||
);
|
||||
{
|
||||
solAssert(arrayType.baseType()->storageBytes() <= 32, "");
|
||||
if (
|
||||
!arrayType.isByteArray() &&
|
||||
arrayType.baseType()->storageBytes() < 32 &&
|
||||
m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
|
||||
)
|
||||
{
|
||||
m_context << u256(32);
|
||||
CompilerUtils(m_context).abiDecodeV2({arrayType.baseType()}, false);
|
||||
}
|
||||
else
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(
|
||||
*arrayType.baseType(),
|
||||
true,
|
||||
!arrayType.isByteArray(),
|
||||
false
|
||||
);
|
||||
}
|
||||
else
|
||||
solAssert(
|
||||
arrayType.baseType()->category() == Type::Category::Struct ||
|
||||
@ -1611,7 +1636,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
||||
|
||||
_indexAccess.indexExpression()->accept(*this);
|
||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true);
|
||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true);
|
||||
// stack layout: <value> <index>
|
||||
// check out-of-bounds access
|
||||
m_context << u256(fixedBytesType.numBytes());
|
||||
@ -2191,7 +2216,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
needToUpdateFreeMemoryPtr = true;
|
||||
else
|
||||
for (auto const& retType: returnTypes)
|
||||
if (dynamic_cast<ReferenceType const*>(retType.get()))
|
||||
if (dynamic_cast<ReferenceType const*>(retType))
|
||||
needToUpdateFreeMemoryPtr = true;
|
||||
|
||||
// Stack: return_data_start
|
||||
@ -2269,6 +2294,3 @@ CompilerUtils ExpressionCompiler::utils()
|
||||
{
|
||||
return CompilerUtils(m_context);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -29,12 +29,13 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
using namespace dev::solidity;
|
||||
using namespace langutil;
|
||||
using namespace solidity;
|
||||
|
||||
|
||||
StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
|
||||
LValue(_compilerContext, _declaration.annotation().type.get()),
|
||||
LValue(_compilerContext, _declaration.annotation().type),
|
||||
m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)),
|
||||
m_size(m_dataType->sizeOnStack())
|
||||
{
|
||||
@ -418,11 +419,8 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in StorageByteArrayElement
|
||||
static FixedBytesType byteType(1);
|
||||
|
||||
StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext):
|
||||
LValue(_compilerContext, &byteType)
|
||||
LValue(_compilerContext, TypeProvider::byte())
|
||||
{
|
||||
}
|
||||
|
||||
@ -474,7 +472,7 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer
|
||||
}
|
||||
|
||||
StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType):
|
||||
LValue(_compilerContext, _arrayType.memberType("length").get()),
|
||||
LValue(_compilerContext, _arrayType.memberType("length")),
|
||||
m_arrayType(_arrayType)
|
||||
{
|
||||
solAssert(m_arrayType.isDynamicallySized(), "");
|
||||
|
@ -145,7 +145,7 @@ string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
||||
templ("body", "aligned := value");
|
||||
break;
|
||||
case Type::Category::Contract:
|
||||
templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)");
|
||||
templ("body", "aligned := " + leftAlignFunction(*TypeProvider::address()) + "(value)");
|
||||
break;
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
@ -252,6 +252,36 @@ string YulUtilFunctions::roundUpFunction()
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
sum := add(x, y)
|
||||
if lt(sum, x) { revert(0, 0) }
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_length_" + _type.identifier();
|
||||
@ -330,40 +360,53 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_dataslot_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_type.dataStoredIn(DataLocation::Memory))
|
||||
switch (_type.location())
|
||||
{
|
||||
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();
|
||||
}
|
||||
else if (_type.dataStoredIn(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
|
||||
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
|
||||
@ -372,11 +415,8 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not used for calldata
|
||||
solAssert(false, "");
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -384,36 +424,40 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.location() == DataLocation::Storage,
|
||||
""
|
||||
);
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.baseType()->storageBytes() > 16,
|
||||
""
|
||||
);
|
||||
if (_type.dataStoredIn(DataLocation::Storage))
|
||||
solAssert(_type.baseType()->storageBytes() > 16, "");
|
||||
string functionName = "array_nextElement_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_type.location() == DataLocation::Memory)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> nextPtr {
|
||||
nextPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else if (_type.location() == DataLocation::Storage)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> nextSlot {
|
||||
nextSlot := add(slot, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
solAssert(false, "");
|
||||
switch (_type.location())
|
||||
{
|
||||
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, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -436,3 +480,322 @@ string YulUtilFunctions::allocationFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
{
|
||||
string functionName =
|
||||
"convert_" +
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> converted {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
string body;
|
||||
auto toCategory = _to.category();
|
||||
auto fromCategory = _from.category();
|
||||
switch (fromCategory)
|
||||
{
|
||||
case Type::Category::Address:
|
||||
body =
|
||||
Whiskers("converted := <convert>(value)")
|
||||
("convert", conversionFunction(IntegerType(160), _to))
|
||||
.render();
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
case Type::Category::RationalNumber:
|
||||
case Type::Category::Contract:
|
||||
{
|
||||
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from))
|
||||
solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType.");
|
||||
if (toCategory == Type::Category::FixedBytes)
|
||||
{
|
||||
solAssert(
|
||||
fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber,
|
||||
"Invalid conversion to FixedBytesType requested."
|
||||
);
|
||||
FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to);
|
||||
body =
|
||||
Whiskers("converted := <shiftLeft>(<clean>(value))")
|
||||
("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
||||
("clean", cleanupFunction(_from))
|
||||
.render();
|
||||
}
|
||||
else if (toCategory == Type::Category::Enum)
|
||||
{
|
||||
solAssert(_from.mobileType(), "");
|
||||
body =
|
||||
Whiskers("converted := <cleanEnum>(<cleanInt>(value))")
|
||||
("cleanEnum", cleanupFunction(_to))
|
||||
// "mobileType()" returns integer type for rational
|
||||
("cleanInt", cleanupFunction(*_from.mobileType()))
|
||||
.render();
|
||||
}
|
||||
else if (toCategory == Type::Category::FixedPoint)
|
||||
solUnimplemented("Not yet implemented - FixedPointType.");
|
||||
else if (toCategory == Type::Category::Address)
|
||||
body =
|
||||
Whiskers("converted := <convert>(value)")
|
||||
("convert", conversionFunction(_from, IntegerType(160)))
|
||||
.render();
|
||||
else
|
||||
{
|
||||
solAssert(
|
||||
toCategory == Type::Category::Integer ||
|
||||
toCategory == Type::Category::Contract,
|
||||
"");
|
||||
IntegerType const addressType(160);
|
||||
IntegerType const& to =
|
||||
toCategory == Type::Category::Integer ?
|
||||
dynamic_cast<IntegerType const&>(_to) :
|
||||
addressType;
|
||||
|
||||
// Clean according to the "to" type, except if this is
|
||||
// a widening conversion.
|
||||
IntegerType const* cleanupType = &to;
|
||||
if (fromCategory != Type::Category::RationalNumber)
|
||||
{
|
||||
IntegerType const& from =
|
||||
fromCategory == Type::Category::Integer ?
|
||||
dynamic_cast<IntegerType const&>(_from) :
|
||||
addressType;
|
||||
if (to.numBits() > from.numBits())
|
||||
cleanupType = &from;
|
||||
}
|
||||
body =
|
||||
Whiskers("converted := <cleanInt>(value)")
|
||||
("cleanInt", cleanupFunction(*cleanupType))
|
||||
.render();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::Bool:
|
||||
{
|
||||
solAssert(_from == _to, "Invalid conversion for bool.");
|
||||
body =
|
||||
Whiskers("converted := <clean>(value)")
|
||||
("clean", cleanupFunction(_from))
|
||||
.render();
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
||||
break;
|
||||
case Type::Category::Struct:
|
||||
solUnimplementedAssert(false, "Struct conversion not implemented.");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from);
|
||||
if (toCategory == Type::Category::Integer)
|
||||
body =
|
||||
Whiskers("converted := <convert>(<shift>(value))")
|
||||
("shift", shiftRightFunction(256 - from.numBytes() * 8))
|
||||
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
|
||||
.render();
|
||||
else if (toCategory == Type::Category::Address)
|
||||
body =
|
||||
Whiskers("converted := <convert>(value)")
|
||||
("convert", conversionFunction(_from, IntegerType(160)))
|
||||
.render();
|
||||
else
|
||||
{
|
||||
// clear for conversion to longer bytes
|
||||
solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
|
||||
body =
|
||||
Whiskers("converted := <clean>(value)")
|
||||
("clean", cleanupFunction(from))
|
||||
.render();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::Function:
|
||||
{
|
||||
solAssert(false, "Conversion should not be called for function types.");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
solAssert(toCategory == Type::Category::Integer || _from == _to, "");
|
||||
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from);
|
||||
body =
|
||||
Whiskers("converted := <clean>(value)")
|
||||
("clean", cleanupFunction(enumType))
|
||||
.render();
|
||||
break;
|
||||
}
|
||||
case Type::Category::Tuple:
|
||||
{
|
||||
solUnimplementedAssert(false, "Tuple conversion not implemented.");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName());
|
||||
templ("body", body);
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::cleanupFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("cleanup_") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> cleaned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Address:
|
||||
templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)");
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (type.numBits() == 256)
|
||||
templ("body", "cleaned := value");
|
||||
else if (type.isSigned())
|
||||
templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)");
|
||||
else
|
||||
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")");
|
||||
break;
|
||||
}
|
||||
case Type::Category::RationalNumber:
|
||||
templ("body", "cleaned := value");
|
||||
break;
|
||||
case Type::Category::Bool:
|
||||
templ("body", "cleaned := iszero(iszero(value))");
|
||||
break;
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Function:
|
||||
solAssert(dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External, "");
|
||||
templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
case Type::Category::Struct:
|
||||
case Type::Category::Mapping:
|
||||
solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type.");
|
||||
templ("body", "cleaned := value");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type);
|
||||
if (type.numBytes() == 32)
|
||||
templ("body", "cleaned := value");
|
||||
else if (type.numBytes() == 0)
|
||||
// This is disallowed in the type system.
|
||||
solAssert(false, "");
|
||||
else
|
||||
{
|
||||
size_t numBits = type.numBytes() * 8;
|
||||
u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits);
|
||||
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::Contract:
|
||||
{
|
||||
AddressType addressType(dynamic_cast<ContractType const&>(_type).isPayable() ?
|
||||
StateMutability::Payable :
|
||||
StateMutability::NonPayable
|
||||
);
|
||||
templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
// Out of range enums cannot be truncated unambigiously and therefore it should be an error.
|
||||
templ("body", "cleaned := value " + validatorFunction(_type) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::InaccessibleDynamic:
|
||||
templ("body", "cleaned := 0");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
|
||||
}
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
|
||||
{
|
||||
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) {
|
||||
if iszero(<condition>) { <failure> }
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
if (_revertOnFailure)
|
||||
templ("failure", "revert(0, 0)");
|
||||
else
|
||||
templ("failure", "invalid()");
|
||||
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Address:
|
||||
case Type::Category::Integer:
|
||||
case Type::Category::RationalNumber:
|
||||
case Type::Category::Bool:
|
||||
case Type::Category::FixedPoint:
|
||||
case Type::Category::Function:
|
||||
case Type::Category::Array:
|
||||
case Type::Category::Struct:
|
||||
case Type::Category::Mapping:
|
||||
case Type::Category::FixedBytes:
|
||||
case Type::Category::Contract:
|
||||
{
|
||||
templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
|
||||
solAssert(members > 0, "empty enum should have caused a parser error.");
|
||||
templ("condition", "lt(value, " + to_string(members) + ")");
|
||||
break;
|
||||
}
|
||||
case Type::Category::InaccessibleDynamic:
|
||||
templ("condition", "1");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Validation of type " + _type.identifier() + " requested.");
|
||||
}
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
|
||||
{
|
||||
string result;
|
||||
if (_startSuffix < _endSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_startSuffix++);
|
||||
while (_startSuffix < _endSuffix)
|
||||
result += ", " + _baseName + to_string(_startSuffix++);
|
||||
}
|
||||
else if (_endSuffix < _startSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_endSuffix++);
|
||||
while (_endSuffix < _startSuffix)
|
||||
result = _baseName + to_string(_endSuffix++) + ", " + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -73,17 +73,19 @@ public:
|
||||
/// of 32 or the input if it is a multiple of 32.
|
||||
std::string roundUpFunction();
|
||||
|
||||
std::string overflowCheckedUIntAddFunction(size_t _bits);
|
||||
|
||||
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).
|
||||
/// The function reverts for too large lengths.
|
||||
std::string arrayAllocationSizeFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that converts a storage slot number
|
||||
/// or a memory pointer to the slot number / memory pointer for the data position of an array
|
||||
/// which is stored in that slot / memory area.
|
||||
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
|
||||
/// for the data position of an array which is stored in that slot / memory area / calldata area.
|
||||
std::string arrayDataAreaFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that advances an array data pointer to the next element.
|
||||
/// Only works for memory arrays and storage arrays that store one item per slot.
|
||||
/// 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 allocates memory.
|
||||
@ -92,6 +94,33 @@ public:
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
/// @returns the name of the function that converts a value of type @a _from
|
||||
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
|
||||
/// (i.e. "clean"). Asserts on failure.
|
||||
///
|
||||
/// This is used for data being encoded or general type conversions in the code.
|
||||
std::string conversionFunction(Type const& _from, Type const& _to);
|
||||
|
||||
/// @returns the name of the cleanup function for the given type and
|
||||
/// adds its implementation to the requested functions.
|
||||
/// The cleanup function defers to the validator function with "assert"
|
||||
/// if there is no reasonable way to clean a value.
|
||||
std::string cleanupFunction(Type const& _type);
|
||||
|
||||
/// @returns the name of the validator function for the given type and
|
||||
/// adds its implementation to the requested functions.
|
||||
/// @param _revertOnFailure if true, causes revert on invalid data,
|
||||
/// otherwise an assertion failure.
|
||||
///
|
||||
/// This is used for data decoded from external sources.
|
||||
std::string validatorFunction(Type const& _type, bool _revertOnFailure = false);
|
||||
|
||||
/// @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);
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
|
130
libsolidity/codegen/ir/IRGenerationContext.cpp
Normal file
130
libsolidity/codegen/ir/IRGenerationContext.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Class that contains contextual information during IR generation.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/ir/IRGenerationContext.h>
|
||||
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <libdevcore/Whiskers.h>
|
||||
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
using namespace std;
|
||||
|
||||
string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solUnimplementedAssert(
|
||||
_varDecl.annotation().type->sizeOnStack() == 1,
|
||||
"Multi-slot types not yet implemented."
|
||||
);
|
||||
|
||||
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
||||
}
|
||||
|
||||
string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(
|
||||
m_localVariables.count(&_varDecl),
|
||||
"Unknown variable: " + _varDecl.name()
|
||||
);
|
||||
return m_localVariables[&_varDecl];
|
||||
}
|
||||
|
||||
string IRGenerationContext::functionName(FunctionDefinition const& _function)
|
||||
{
|
||||
// @TODO previously, we had to distinguish creation context and runtime context,
|
||||
// but since we do not work with jump positions anymore, this should not be a problem, right?
|
||||
return "fun_" + _function.name() + "_" + to_string(_function.id());
|
||||
}
|
||||
|
||||
FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
// @TODO previously, we had to distinguish creation context and runtime context,
|
||||
// but since we do not work with jump positions anymore, this should not be a problem, right?
|
||||
string name = _function.name();
|
||||
FunctionType functionType(_function);
|
||||
for (auto const& contract: m_inheritanceHierarchy)
|
||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||
if (
|
||||
function->name() == name &&
|
||||
!function->isConstructor() &&
|
||||
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
|
||||
)
|
||||
return *function;
|
||||
solAssert(false, "Super function " + name + " not found.");
|
||||
}
|
||||
|
||||
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
|
||||
{
|
||||
return functionName(virtualFunction(_functionDeclaration));
|
||||
}
|
||||
|
||||
string IRGenerationContext::newYulVariable()
|
||||
{
|
||||
return "_" + to_string(++m_varCounter);
|
||||
}
|
||||
|
||||
string IRGenerationContext::variable(Expression const& _expression)
|
||||
{
|
||||
unsigned size = _expression.annotation().type->sizeOnStack();
|
||||
solUnimplementedAssert(size == 1, "");
|
||||
return "expr_" + to_string(_expression.id());
|
||||
}
|
||||
|
||||
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> {
|
||||
switch fun
|
||||
<#cases>
|
||||
case <funID>
|
||||
{
|
||||
<out> := <name>(<in>)
|
||||
}
|
||||
</cases>
|
||||
default { invalid() }
|
||||
}
|
||||
)");
|
||||
templ("functionName", funName);
|
||||
templ("comma", _in > 0 ? "," : "");
|
||||
YulUtilFunctions utils(m_evmVersion, m_functions);
|
||||
templ("in", utils.suffixedVariableNameList("in_", 0, _in));
|
||||
templ("out", utils.suffixedVariableNameList("out_", 0, _out));
|
||||
vector<map<string, string>> functions;
|
||||
for (auto const& contract: m_inheritanceHierarchy)
|
||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||
if (
|
||||
!function->isConstructor() &&
|
||||
function->parameters().size() == _in &&
|
||||
function->returnParameters().size() == _out
|
||||
)
|
||||
functions.emplace_back(map<string, string> {
|
||||
{ "funID", to_string(function->id()) },
|
||||
{ "name", functionName(*function)}
|
||||
});
|
||||
templ("cases", move(functions));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
87
libsolidity/codegen/ir/IRGenerationContext.h
Normal file
87
libsolidity/codegen/ir/IRGenerationContext.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Class that contains contextual information during IR generation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class ContractDefinition;
|
||||
class VariableDeclaration;
|
||||
class FunctionDefinition;
|
||||
class Expression;
|
||||
|
||||
/**
|
||||
* Class that contains contextual information during IR generation.
|
||||
*/
|
||||
class IRGenerationContext
|
||||
{
|
||||
public:
|
||||
IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||
m_functions(std::make_shared<MultiUseYulFunctionCollector>())
|
||||
{}
|
||||
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; }
|
||||
|
||||
/// Sets the current inheritance hierarchy from derived to base.
|
||||
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
|
||||
{
|
||||
m_inheritanceHierarchy = std::move(_hierarchy);
|
||||
}
|
||||
|
||||
|
||||
std::string addLocalVariable(VariableDeclaration const& _varDecl);
|
||||
std::string variableName(VariableDeclaration const& _varDecl);
|
||||
std::string functionName(FunctionDefinition const& _function);
|
||||
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
|
||||
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
|
||||
|
||||
std::string newYulVariable();
|
||||
/// @returns the variable (or comma-separated list of variables) that contain
|
||||
/// the value of the given expression.
|
||||
std::string variable(Expression const& _expression);
|
||||
|
||||
std::string internalDispatch(size_t _in, size_t _out);
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
OptimiserSettings m_optimiserSettings;
|
||||
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
|
||||
std::map<VariableDeclaration const*, std::string> m_localVariables;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
|
||||
size_t m_varCounter = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
253
libsolidity/codegen/ir/IRGenerator.cpp
Normal file
253
libsolidity/codegen/ir/IRGenerator.cpp
Normal file
@ -0,0 +1,253 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* @author Alex Beregszaszi
|
||||
* @date 2017
|
||||
* Component that translates Solidity code into Yul.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/ir/IRGenerator.h>
|
||||
|
||||
#include <libsolidity/codegen/ir/IRGeneratorForStatements.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
|
||||
#include <libyul/AssemblyStack.h>
|
||||
|
||||
#include <libdevcore/CommonData.h>
|
||||
#include <libdevcore/Whiskers.h>
|
||||
#include <libdevcore/StringUtils.h>
|
||||
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
|
||||
{
|
||||
// TODO Would be nice to pretty-print this while retaining comments.
|
||||
string ir = generate(_contract);
|
||||
|
||||
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||
if (!asmStack.parseAndAnalyze("", ir))
|
||||
{
|
||||
string errorMessage;
|
||||
for (auto const& error: asmStack.errors())
|
||||
errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error);
|
||||
solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir);
|
||||
}
|
||||
asmStack.optimize();
|
||||
|
||||
string warning =
|
||||
"/*******************************************************\n"
|
||||
" * WARNING *\n"
|
||||
" * Solidity to Yul compilation is still EXPERIMENTAL *\n"
|
||||
" * It can result in LOSS OF FUNDS or worse *\n"
|
||||
" * !USE AT YOUR OWN RISK! *\n"
|
||||
" *******************************************************/\n\n";
|
||||
|
||||
return {warning + ir, warning + asmStack.print()};
|
||||
}
|
||||
|
||||
string IRGenerator::generate(ContractDefinition const& _contract)
|
||||
{
|
||||
Whiskers t(R"(
|
||||
object "<CreationObject>" {
|
||||
code {
|
||||
<memoryInit>
|
||||
<constructor>
|
||||
<deploy>
|
||||
<functions>
|
||||
}
|
||||
object "<RuntimeObject>" {
|
||||
code {
|
||||
<memoryInit>
|
||||
<dispatch>
|
||||
<runtimeFunctions>
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
resetContext();
|
||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||
t("CreationObject", creationObjectName(_contract));
|
||||
t("memoryInit", memoryInit());
|
||||
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : "");
|
||||
t("deploy", deployCode(_contract));
|
||||
t("functions", m_context.functionCollector()->requestedFunctions());
|
||||
|
||||
resetContext();
|
||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||
t("RuntimeObject", runtimeObjectName(_contract));
|
||||
t("dispatch", dispatchRoutine(_contract));
|
||||
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
|
||||
return t.render();
|
||||
}
|
||||
|
||||
string IRGenerator::generate(Block const& _block)
|
||||
{
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
_block.accept(generator);
|
||||
return generator.code();
|
||||
}
|
||||
|
||||
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");
|
||||
t("functionName", functionName);
|
||||
string params;
|
||||
for (auto const& varDecl: _function.parameters())
|
||||
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl);
|
||||
t("params", params);
|
||||
string retParams;
|
||||
for (auto const& varDecl: _function.returnParameters())
|
||||
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl);
|
||||
t("returns", retParams.empty() ? "" : " -> " + retParams);
|
||||
t("body", generate(_function.body()));
|
||||
return t.render();
|
||||
});
|
||||
}
|
||||
|
||||
string IRGenerator::constructorCode(FunctionDefinition const& _constructor)
|
||||
{
|
||||
string out;
|
||||
if (!_constructor.isPayable())
|
||||
out = callValueCheck();
|
||||
|
||||
solUnimplemented("Constructors are not yet implemented.");
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
string IRGenerator::deployCode(ContractDefinition const& _contract)
|
||||
{
|
||||
Whiskers t(R"X(
|
||||
codecopy(0, dataoffset("<object>"), datasize("<object>"))
|
||||
return(0, datasize("<object>"))
|
||||
)X");
|
||||
t("object", runtimeObjectName(_contract));
|
||||
return t.render();
|
||||
}
|
||||
|
||||
string IRGenerator::callValueCheck()
|
||||
{
|
||||
return "if callvalue() { revert(0, 0) }";
|
||||
}
|
||||
|
||||
string IRGenerator::creationObjectName(ContractDefinition const& _contract)
|
||||
{
|
||||
return _contract.name() + "_" + to_string(_contract.id());
|
||||
}
|
||||
|
||||
string IRGenerator::runtimeObjectName(ContractDefinition const& _contract)
|
||||
{
|
||||
return _contract.name() + "_" + to_string(_contract.id()) + "_deployed";
|
||||
}
|
||||
|
||||
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
{
|
||||
Whiskers t(R"X(
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
{
|
||||
let selector := <shr224>(calldataload(0))
|
||||
switch selector
|
||||
<#cases>
|
||||
case <functionSelector>
|
||||
{
|
||||
// <functionName>
|
||||
<callValueCheck>
|
||||
<assignToParams> <abiDecode>(4, calldatasize())
|
||||
<assignToRetParams> <function>(<params>)
|
||||
let memPos := <allocate>(0)
|
||||
let memEnd := <abiEncode>(memPos <comma> <retParams>)
|
||||
return(memPos, sub(memEnd, memPos))
|
||||
}
|
||||
</cases>
|
||||
default {}
|
||||
}
|
||||
<fallback>
|
||||
)X");
|
||||
t("shr224", m_utils.shiftRightFunction(224));
|
||||
vector<map<string, string>> functions;
|
||||
for (auto const& function: _contract.interfaceFunctions())
|
||||
{
|
||||
functions.push_back({});
|
||||
map<string, string>& templ = functions.back();
|
||||
templ["functionSelector"] = "0x" + function.first.hex();
|
||||
FunctionTypePointer const& type = function.second;
|
||||
templ["functionName"] = type->externalSignature();
|
||||
templ["callValueCheck"] = type->isPayable() ? "" : callValueCheck();
|
||||
|
||||
unsigned paramVars = make_shared<TupleType>(type->parameterTypes())->sizeOnStack();
|
||||
unsigned retVars = make_shared<TupleType>(type->returnParameterTypes())->sizeOnStack();
|
||||
templ["assignToParams"] = paramVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("param_", 0, paramVars) + " := ";
|
||||
templ["assignToRetParams"] = retVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("ret_", 0, retVars) + " := ";
|
||||
|
||||
ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector());
|
||||
templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes());
|
||||
templ["params"] = m_utils.suffixedVariableNameList("param_", 0, paramVars);
|
||||
templ["retParams"] = m_utils.suffixedVariableNameList("ret_", retVars, 0);
|
||||
templ["function"] = generateFunction(dynamic_cast<FunctionDefinition const&>(type->declaration()));
|
||||
templ["allocate"] = m_utils.allocationFunction();
|
||||
templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), false);
|
||||
templ["comma"] = retVars == 0 ? "" : ", ";
|
||||
}
|
||||
t("cases", functions);
|
||||
if (FunctionDefinition const* fallback = _contract.fallbackFunction())
|
||||
{
|
||||
string fallbackCode;
|
||||
if (!fallback->isPayable())
|
||||
fallbackCode += callValueCheck();
|
||||
fallbackCode += generateFunction(*fallback) + "() stop()";
|
||||
|
||||
t("fallback", fallbackCode);
|
||||
}
|
||||
else
|
||||
t("fallback", "revert(0, 0)");
|
||||
return t.render();
|
||||
}
|
||||
|
||||
string IRGenerator::memoryInit()
|
||||
{
|
||||
// This function should be called at the beginning of the EVM call frame
|
||||
// and thus can assume all memory to be zero, including the contents of
|
||||
// the "zero memory area" (the position CompilerUtils::zeroPointer points to).
|
||||
return
|
||||
Whiskers{"mstore(<memPtr>, <generalPurposeStart>)"}
|
||||
("memPtr", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("generalPurposeStart", to_string(CompilerUtils::generalPurposeMemoryStart))
|
||||
.render();
|
||||
}
|
||||
|
||||
void IRGenerator::resetContext()
|
||||
{
|
||||
solAssert(
|
||||
m_context.functionCollector()->requestedFunctions().empty(),
|
||||
"Reset context while it still had functions."
|
||||
);
|
||||
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings);
|
||||
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector());
|
||||
}
|
81
libsolidity/codegen/ir/IRGenerator.h
Normal file
81
libsolidity/codegen/ir/IRGenerator.h
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* @author Alex Beregszaszi
|
||||
* @date 2017
|
||||
* Component that translates Solidity code into Yul.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/codegen/ir/IRGenerationContext.h>
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <string>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class SourceUnit;
|
||||
|
||||
class IRGenerator
|
||||
{
|
||||
public:
|
||||
IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_optimiserSettings(_optimiserSettings),
|
||||
m_context(_evmVersion, std::move(_optimiserSettings)),
|
||||
m_utils(_evmVersion, m_context.functionCollector())
|
||||
{}
|
||||
|
||||
/// Generates and returns the IR code, in unoptimized and optimized form
|
||||
/// (or just pretty-printed, depending on the optimizer settings).
|
||||
std::pair<std::string, std::string> run(ContractDefinition const& _contract);
|
||||
|
||||
private:
|
||||
std::string generate(ContractDefinition const& _contract);
|
||||
std::string generate(Block const& _block);
|
||||
|
||||
/// Generates code for and returns the name of the function.
|
||||
std::string generateFunction(FunctionDefinition const& _function);
|
||||
|
||||
std::string constructorCode(FunctionDefinition const& _constructor);
|
||||
std::string deployCode(ContractDefinition const& _contract);
|
||||
std::string callValueCheck();
|
||||
|
||||
std::string creationObjectName(ContractDefinition const& _contract);
|
||||
std::string runtimeObjectName(ContractDefinition const& _contract);
|
||||
|
||||
std::string dispatchRoutine(ContractDefinition const& _contract);
|
||||
|
||||
std::string memoryInit();
|
||||
|
||||
void resetContext();
|
||||
|
||||
langutil::EVMVersion const m_evmVersion;
|
||||
OptimiserSettings const m_optimiserSettings;
|
||||
|
||||
IRGenerationContext m_context;
|
||||
YulUtilFunctions m_utils;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
315
libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Normal file
315
libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Normal file
@ -0,0 +1,315 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Component that translates Solidity code into Yul at statement level and below.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/ir/IRGeneratorForStatements.h>
|
||||
|
||||
#include <libsolidity/codegen/ir/IRGenerationContext.h>
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <libyul/AsmPrinter.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/optimiser/ASTCopier.h>
|
||||
|
||||
#include <libdevcore/StringUtils.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct CopyTranslate: public yul::ASTCopier
|
||||
{
|
||||
using ExternalRefsMap = std::map<yul::Identifier const*, InlineAssemblyAnnotation::ExternalIdentifierInfo>;
|
||||
|
||||
CopyTranslate(IRGenerationContext& _context, ExternalRefsMap const& _references):
|
||||
m_context(_context), m_references(_references) {}
|
||||
|
||||
using ASTCopier::operator();
|
||||
|
||||
yul::YulString translateIdentifier(yul::YulString _name) override
|
||||
{
|
||||
return yul::YulString{"usr$" + _name.str()};
|
||||
}
|
||||
|
||||
yul::Identifier translate(yul::Identifier const& _identifier) override
|
||||
{
|
||||
if (!m_references.count(&_identifier))
|
||||
return ASTCopier::translate(_identifier);
|
||||
|
||||
auto const& reference = m_references.at(&_identifier);
|
||||
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
|
||||
solUnimplementedAssert(varDecl, "");
|
||||
solUnimplementedAssert(
|
||||
reference.isOffset == false && reference.isSlot == false,
|
||||
""
|
||||
);
|
||||
|
||||
return yul::Identifier{
|
||||
_identifier.location,
|
||||
yul::YulString{m_context.variableName(*varDecl)}
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
IRGenerationContext& m_context;
|
||||
ExternalRefsMap const& m_references;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDeclStatement)
|
||||
{
|
||||
for (auto const& decl: _varDeclStatement.declarations())
|
||||
if (decl)
|
||||
m_context.addLocalVariable(*decl);
|
||||
|
||||
if (Expression const* expression = _varDeclStatement.initialValue())
|
||||
{
|
||||
solUnimplementedAssert(_varDeclStatement.declarations().size() == 1, "");
|
||||
|
||||
expression->accept(*this);
|
||||
|
||||
VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front();
|
||||
m_code <<
|
||||
"let " <<
|
||||
m_context.variableName(varDecl) <<
|
||||
" := " <<
|
||||
expressionAsType(*expression, *varDecl.type()) <<
|
||||
"\n";
|
||||
}
|
||||
else
|
||||
for (auto const& decl: _varDeclStatement.declarations())
|
||||
if (decl)
|
||||
m_code << "let " << m_context.variableName(*decl) << "\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
{
|
||||
solUnimplementedAssert(_assignment.assignmentOperator() == Token::Assign, "");
|
||||
|
||||
_assignment.rightHandSide().accept(*this);
|
||||
|
||||
// TODO proper lvalue handling
|
||||
auto const& lvalue = dynamic_cast<Identifier const&>(_assignment.leftHandSide());
|
||||
string varName = m_context.variableName(dynamic_cast<VariableDeclaration const&>(*lvalue.annotation().referencedDeclaration));
|
||||
|
||||
m_code <<
|
||||
varName <<
|
||||
" := " <<
|
||||
expressionAsType(_assignment.rightHandSide(), *lvalue.annotation().type) <<
|
||||
"\n";
|
||||
m_code << "let " << m_context.variable(_assignment) << " := " << varName << "\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Return const&)
|
||||
{
|
||||
solUnimplemented("Return not yet implemented in yul code generation");
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::endVisit(BinaryOperation const& _binOp)
|
||||
{
|
||||
solUnimplementedAssert(_binOp.getOperator() == Token::Add, "");
|
||||
solUnimplementedAssert(*_binOp.leftExpression().annotation().type == *_binOp.rightExpression().annotation().type, "");
|
||||
if (IntegerType const* type = dynamic_cast<IntegerType const*>(_binOp.annotation().commonType))
|
||||
{
|
||||
solUnimplementedAssert(!type->isSigned(), "");
|
||||
m_code <<
|
||||
"let " <<
|
||||
m_context.variable(_binOp) <<
|
||||
" := " <<
|
||||
m_utils.overflowCheckedUIntAddFunction(type->numBits()) <<
|
||||
"(" <<
|
||||
m_context.variable(_binOp.leftExpression()) <<
|
||||
", " <<
|
||||
m_context.variable(_binOp.rightExpression()) <<
|
||||
")\n";
|
||||
}
|
||||
else
|
||||
solUnimplementedAssert(false, "");
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
solUnimplementedAssert(
|
||||
_functionCall.annotation().kind == FunctionCallKind::FunctionCall ||
|
||||
_functionCall.annotation().kind == FunctionCallKind::TypeConversion,
|
||||
"This type of function call is not yet implemented"
|
||||
);
|
||||
|
||||
TypePointer const funcType = _functionCall.expression().annotation().type;
|
||||
|
||||
if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
|
||||
{
|
||||
solAssert(funcType->category() == Type::Category::TypeType, "Expected category to be TypeType");
|
||||
solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
|
||||
_functionCall.arguments().front()->accept(*this);
|
||||
|
||||
m_code <<
|
||||
"let " <<
|
||||
m_context.variable(_functionCall) <<
|
||||
" := " <<
|
||||
expressionAsType(*_functionCall.arguments().front(), *_functionCall.annotation().type) <<
|
||||
"\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FunctionTypePointer functionType = dynamic_cast<FunctionType const*>(funcType);
|
||||
|
||||
TypePointers parameterTypes = functionType->parameterTypes();
|
||||
vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments();
|
||||
vector<ASTPointer<ASTString>> const& callArgumentNames = _functionCall.names();
|
||||
if (!functionType->takesArbitraryParameters())
|
||||
solAssert(callArguments.size() == parameterTypes.size(), "");
|
||||
|
||||
vector<ASTPointer<Expression const>> arguments;
|
||||
if (callArgumentNames.empty())
|
||||
// normal arguments
|
||||
arguments = callArguments;
|
||||
else
|
||||
// named arguments
|
||||
for (auto const& parameterName: functionType->parameterNames())
|
||||
{
|
||||
auto const it = std::find_if(callArgumentNames.cbegin(), callArgumentNames.cend(), [&](ASTPointer<ASTString> const& _argName) {
|
||||
return *_argName == parameterName;
|
||||
});
|
||||
|
||||
solAssert(it != callArgumentNames.cend(), "");
|
||||
arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
|
||||
}
|
||||
|
||||
solUnimplementedAssert(!functionType->bound(), "");
|
||||
switch (functionType->kind())
|
||||
{
|
||||
case FunctionType::Kind::Internal:
|
||||
{
|
||||
vector<string> args;
|
||||
for (unsigned i = 0; i < arguments.size(); ++i)
|
||||
{
|
||||
arguments[i]->accept(*this);
|
||||
|
||||
if (functionType->takesArbitraryParameters())
|
||||
args.emplace_back(m_context.variable(*arguments[i]));
|
||||
else
|
||||
args.emplace_back(expressionAsType(*arguments[i], *parameterTypes[i]));
|
||||
}
|
||||
|
||||
if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
|
||||
{
|
||||
solAssert(!functionType->bound(), "");
|
||||
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
|
||||
{
|
||||
// @TODO The function can very well return multiple vars.
|
||||
m_code <<
|
||||
"let " <<
|
||||
m_context.variable(_functionCall) <<
|
||||
" := " <<
|
||||
m_context.virtualFunctionName(*functionDef) <<
|
||||
"(" <<
|
||||
joinHumanReadable(args) <<
|
||||
")\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_functionCall.expression().accept(*this);
|
||||
|
||||
// @TODO The function can very well return multiple vars.
|
||||
args = vector<string>{m_context.variable(_functionCall.expression())} + args;
|
||||
m_code <<
|
||||
"let " <<
|
||||
m_context.variable(_functionCall) <<
|
||||
" := " <<
|
||||
m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) <<
|
||||
"(" <<
|
||||
joinHumanReadable(args) <<
|
||||
")\n";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solUnimplemented("");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
{
|
||||
CopyTranslate bodyCopier{m_context, _inlineAsm.annotation().externalReferences};
|
||||
|
||||
yul::Statement modified = bodyCopier(_inlineAsm.operations());
|
||||
|
||||
solAssert(modified.type() == typeid(yul::Block), "");
|
||||
|
||||
m_code << yul::AsmPrinter()(boost::get<yul::Block>(std::move(modified))) << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Identifier const& _identifier)
|
||||
{
|
||||
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
|
||||
string value;
|
||||
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
|
||||
value = to_string(m_context.virtualFunction(*functionDef).id());
|
||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
value = m_context.variableName(*varDecl);
|
||||
else
|
||||
solUnimplemented("");
|
||||
m_code << "let " << m_context.variable(_identifier) << " := " << value << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Literal const& _literal)
|
||||
{
|
||||
TypePointer type = _literal.annotation().type;
|
||||
|
||||
switch (type->category())
|
||||
{
|
||||
case Type::Category::RationalNumber:
|
||||
case Type::Category::Bool:
|
||||
case Type::Category::Address:
|
||||
m_code << "let " << m_context.variable(_literal) << " := " << toCompactHexWithPrefix(type->literalValue(&_literal)) << "\n";
|
||||
break;
|
||||
case Type::Category::StringLiteral:
|
||||
solUnimplemented("");
|
||||
break; // will be done during conversion
|
||||
default:
|
||||
solUnimplemented("Only integer, boolean and string literals implemented for now.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to)
|
||||
{
|
||||
Type const& from = *_expression.annotation().type;
|
||||
string varName = m_context.variable(_expression);
|
||||
|
||||
if (from == _to)
|
||||
return varName;
|
||||
else
|
||||
return m_utils.conversionFunction(from, _to) + "(" + std::move(varName) + ")";
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user