aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.editorconfig18
-rw-r--r--.gitignore5
-rw-r--r--.travis.yml169
-rw-r--r--CMakeLists.txt11
-rw-r--r--Changelog.md155
-rw-r--r--README.md6
-rw-r--r--ReleaseChecklist.md18
-rw-r--r--appveyor.yml19
-rw-r--r--cmake/EthCompilerSettings.cmake10
-rw-r--r--cmake/UseDev.cmake1
-rw-r--r--cmake/scripts/buildinfo.cmake2
-rw-r--r--cmake/templates/BuildInfo.h.in2
-rw-r--r--cmake/templates/license.h.in75
m---------deps0
-rw-r--r--docs/abi-spec.rst345
-rw-r--r--docs/assembly.rst1022
-rw-r--r--docs/bugs.json110
-rw-r--r--docs/bugs.rst61
-rw-r--r--docs/bugs_by_version.json373
-rw-r--r--docs/common-patterns.rst57
-rw-r--r--docs/conf.py12
-rw-r--r--docs/contracts.rst162
-rw-r--r--docs/contributing.rst21
-rw-r--r--docs/control-structures.rst565
-rw-r--r--docs/frequently-asked-questions.rst17
-rw-r--r--docs/grammar.txt81
-rw-r--r--docs/index.rst46
-rw-r--r--docs/installing-solidity.rst84
-rw-r--r--docs/introduction-to-smart-contracts.rst37
-rw-r--r--docs/layout-of-source-files.rst21
-rw-r--r--docs/logo.svg27
-rw-r--r--docs/miscellaneous.rst150
-rw-r--r--docs/security-considerations.rst37
-rw-r--r--docs/solidity-by-example.rst145
-rw-r--r--docs/solidity-in-depth.rst1
-rw-r--r--docs/structure-of-a-contract.rst8
-rw-r--r--docs/style-guide.rst81
-rw-r--r--docs/types.rst110
-rw-r--r--docs/units-and-global-variables.rst34
-rw-r--r--docs/using-the-compiler.rst275
-rw-r--r--docs/utils/SolidityLexer.py27
-rw-r--r--libdevcore/ABI.h8
-rw-r--r--libdevcore/Assertions.h74
-rw-r--r--libdevcore/Common.h10
-rw-r--r--libdevcore/CommonData.cpp48
-rw-r--r--libdevcore/CommonData.h13
-rw-r--r--libdevcore/CommonIO.cpp31
-rw-r--r--libdevcore/CommonIO.h8
-rw-r--r--libdevcore/Exceptions.h15
-rw-r--r--libdevcore/FixedHash.h8
-rw-r--r--libdevcore/JSON.h12
-rw-r--r--libdevcore/SHA3.cpp8
-rw-r--r--libdevcore/SHA3.h24
-rw-r--r--libdevcore/SwarmHash.cpp8
-rw-r--r--libdevcore/SwarmHash.h8
-rw-r--r--libdevcore/UTF8.cpp92
-rw-r--r--libdevcore/UTF8.h8
-rw-r--r--libdevcore/UndefMacros.h8
-rw-r--r--libdevcore/Whiskers.cpp127
-rw-r--r--libdevcore/Whiskers.h87
-rw-r--r--libdevcore/debugbreak.h128
-rw-r--r--libevmasm/Assembly.cpp170
-rw-r--r--libevmasm/Assembly.h25
-rw-r--r--libevmasm/AssemblyItem.cpp109
-rw-r--r--libevmasm/AssemblyItem.h62
-rw-r--r--libevmasm/CommonSubexpressionEliminator.cpp6
-rw-r--r--libevmasm/ConstantOptimiser.cpp144
-rw-r--r--libevmasm/ConstantOptimiser.h34
-rw-r--r--libevmasm/EVMSchedule.h8
-rw-r--r--libevmasm/ExpressionClasses.cpp343
-rw-r--r--libevmasm/ExpressionClasses.h65
-rw-r--r--libevmasm/GasMeter.cpp38
-rw-r--r--libevmasm/GasMeter.h15
-rw-r--r--libevmasm/Instruction.cpp278
-rw-r--r--libevmasm/Instruction.h52
-rw-r--r--libevmasm/KnownState.cpp20
-rw-r--r--libevmasm/KnownState.h8
-rw-r--r--libevmasm/LinkerObject.cpp29
-rw-r--r--libevmasm/LinkerObject.h6
-rw-r--r--libevmasm/PeepholeOptimiser.cpp29
-rw-r--r--libevmasm/SemanticInformation.cpp11
-rw-r--r--libevmasm/SimplificationRules.cpp379
-rw-r--r--libevmasm/SimplificationRules.h140
-rw-r--r--libjulia/backends/evm/AbstractAssembly.h117
-rw-r--r--libjulia/backends/evm/EVMAssembly.cpp194
-rw-r--r--libjulia/backends/evm/EVMAssembly.h94
-rw-r--r--libjulia/backends/evm/EVMCodeTransform.cpp500
-rw-r--r--libjulia/backends/evm/EVMCodeTransform.h149
-rw-r--r--liblll/All.h6
-rw-r--r--liblll/CodeFragment.cpp98
-rw-r--r--liblll/CodeFragment.h8
-rw-r--r--liblll/Compiler.cpp13
-rw-r--r--liblll/Compiler.h8
-rw-r--r--liblll/CompilerState.cpp26
-rw-r--r--liblll/CompilerState.h8
-rw-r--r--liblll/Exceptions.h8
-rw-r--r--liblll/Parser.cpp10
-rw-r--r--liblll/Parser.h8
-rw-r--r--libsolidity/CMakeLists.txt4
-rw-r--r--libsolidity/analysis/DeclarationContainer.cpp27
-rw-r--r--libsolidity/analysis/DocStringAnalyser.cpp19
-rw-r--r--libsolidity/analysis/DocStringAnalyser.h8
-rw-r--r--libsolidity/analysis/GlobalContext.cpp34
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp365
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.h75
-rw-r--r--libsolidity/analysis/PostTypeChecker.cpp106
-rw-r--r--libsolidity/analysis/PostTypeChecker.h71
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp128
-rw-r--r--libsolidity/analysis/ReferencesResolver.h26
-rw-r--r--libsolidity/analysis/SemVerHandler.h3
-rw-r--r--libsolidity/analysis/StaticAnalyzer.cpp153
-rw-r--r--libsolidity/analysis/StaticAnalyzer.h83
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp62
-rw-r--r--libsolidity/analysis/SyntaxChecker.h16
-rw-r--r--libsolidity/analysis/TypeChecker.cpp742
-rw-r--r--libsolidity/analysis/TypeChecker.h20
-rw-r--r--libsolidity/ast/AST.cpp189
-rw-r--r--libsolidity/ast/AST.h65
-rw-r--r--libsolidity/ast/ASTAnnotations.h37
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp890
-rw-r--r--libsolidity/ast/ASTJsonConverter.h117
-rw-r--r--libsolidity/ast/Types.cpp601
-rw-r--r--libsolidity/ast/Types.h106
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp893
-rw-r--r--libsolidity/codegen/ArrayUtils.h5
-rw-r--r--libsolidity/codegen/CompilerContext.cpp157
-rw-r--r--libsolidity/codegen/CompilerContext.h61
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp221
-rw-r--r--libsolidity/codegen/CompilerUtils.h44
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp249
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp380
-rw-r--r--libsolidity/codegen/ExpressionCompiler.h8
-rw-r--r--libsolidity/codegen/LValue.cpp11
-rw-r--r--libsolidity/formal/Why3Translator.cpp902
-rw-r--r--libsolidity/formal/Why3Translator.h149
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp496
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.h103
-rw-r--r--libsolidity/inlineasm/AsmAnalysisInfo.cpp26
-rw-r--r--libsolidity/inlineasm/AsmAnalysisInfo.h52
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.cpp326
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.h29
-rw-r--r--libsolidity/inlineasm/AsmData.h40
-rw-r--r--libsolidity/inlineasm/AsmDataForward.h (renamed from libsolidity/interface/Utils.h)39
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp380
-rw-r--r--libsolidity/inlineasm/AsmParser.h14
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp214
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h63
-rw-r--r--libsolidity/inlineasm/AsmScope.cpp98
-rw-r--r--libsolidity/inlineasm/AsmScope.h126
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.cpp168
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.h82
-rw-r--r--libsolidity/inlineasm/AsmStack.cpp71
-rw-r--r--libsolidity/inlineasm/AsmStack.h67
-rw-r--r--libsolidity/interface/ABI.cpp116
-rw-r--r--libsolidity/interface/ABI.h56
-rw-r--r--libsolidity/interface/AssemblyStack.cpp119
-rw-r--r--libsolidity/interface/AssemblyStack.h96
-rw-r--r--libsolidity/interface/CompilerStack.cpp472
-rw-r--r--libsolidity/interface/CompilerStack.h118
-rw-r--r--libsolidity/interface/ErrorReporter.cpp167
-rw-r--r--libsolidity/interface/ErrorReporter.h102
-rw-r--r--libsolidity/interface/Exceptions.cpp44
-rw-r--r--libsolidity/interface/Exceptions.h20
-rw-r--r--libsolidity/interface/InterfaceHandler.cpp189
-rw-r--r--libsolidity/interface/Natspec.cpp130
-rw-r--r--libsolidity/interface/Natspec.h (renamed from libsolidity/interface/InterfaceHandler.h)19
-rw-r--r--libsolidity/interface/ReadFile.h45
-rw-r--r--libsolidity/interface/SourceReferenceFormatter.h11
-rw-r--r--libsolidity/interface/StandardCompiler.cpp476
-rw-r--r--libsolidity/interface/StandardCompiler.h63
-rw-r--r--libsolidity/interface/Version.cpp6
-rw-r--r--libsolidity/interface/Version.h1
-rw-r--r--libsolidity/parsing/DocStringParser.cpp79
-rw-r--r--libsolidity/parsing/DocStringParser.h7
-rw-r--r--libsolidity/parsing/Parser.cpp143
-rw-r--r--libsolidity/parsing/Parser.h10
-rw-r--r--libsolidity/parsing/ParserBase.cpp87
-rw-r--r--libsolidity/parsing/ParserBase.h15
-rw-r--r--libsolidity/parsing/Scanner.cpp5
-rw-r--r--libsolidity/parsing/Token.h7
-rw-r--r--lllc/main.cpp8
-rw-r--r--scripts/Dockerfile24
-rwxr-xr-xscripts/build.sh23
-rwxr-xr-xscripts/build_emscripten.sh6
-rw-r--r--scripts/bytecodecompare/deploy_key.encbin0 -> 1680 bytes
-rwxr-xr-xscripts/bytecodecompare/prepare_report.py24
-rw-r--r--scripts/bytecodecompare/storebytecode.bat42
-rwxr-xr-xscripts/bytecodecompare/storebytecode.sh110
-rwxr-xr-xscripts/create_source_tarball.sh39
-rwxr-xr-xscripts/docker_build.sh8
-rwxr-xr-xscripts/docker_deploy.sh21
-rwxr-xr-xscripts/get_version.sh31
-rwxr-xr-xscripts/install_cmake.sh37
-rw-r--r--scripts/install_deps.bat1
-rwxr-xr-xscripts/install_deps.sh45
-rw-r--r--scripts/install_eth.cmake76
-rwxr-xr-xscripts/isolateTests.py24
-rwxr-xr-xscripts/isolate_tests.py48
-rwxr-xr-xscripts/release.sh4
-rwxr-xr-xscripts/release_ppa.sh34
-rwxr-xr-xscripts/test_emscripten.sh53
-rwxr-xr-xscripts/tests.sh39
-rwxr-xr-xscripts/travis-emscripten/build_emscripten.sh4
-rwxr-xr-xscripts/uniqueErrors.sh14
-rwxr-xr-xscripts/update_bugs_by_version.py43
-rw-r--r--snap/snapcraft.yaml29
-rw-r--r--solc/CMakeLists.txt2
-rw-r--r--solc/CommandLineInterface.cpp835
-rw-r--r--solc/CommandLineInterface.h23
-rw-r--r--solc/jsonCompiler.cpp346
-rw-r--r--solc/main.cpp7
-rw-r--r--std/StandardToken.sol44
-rw-r--r--test/CMakeLists.txt26
-rw-r--r--test/ExecutionFramework.cpp27
-rw-r--r--test/ExecutionFramework.h3
-rw-r--r--test/Metadata.cpp80
-rw-r--r--test/Metadata.h37
-rw-r--r--test/RPCSession.cpp125
-rw-r--r--test/RPCSession.h24
-rw-r--r--test/TestHelper.cpp6
-rw-r--r--test/TestHelper.h2
-rw-r--r--test/boostTest.cpp31
-rwxr-xr-xtest/cmdlineTests.sh93
-rw-r--r--test/contracts/FixedFeeRegistrar.cpp4
-rw-r--r--test/contracts/Wallet.cpp16
-rw-r--r--test/fuzzer.cpp223
-rw-r--r--test/libdevcore/Checksum.cpp83
-rw-r--r--test/libdevcore/MiniMoustache.cpp127
-rw-r--r--test/libdevcore/SwarmHash.cpp8
-rw-r--r--test/libdevcore/UTF8.cpp216
-rw-r--r--test/libjulia/Parser.cpp231
-rw-r--r--test/liblll/EndToEndTest.cpp746
-rw-r--r--test/liblll/Parser.cpp8
-rw-r--r--test/libsolidity/ASTJSON.cpp82
-rw-r--r--test/libsolidity/Assembly.cpp19
-rw-r--r--test/libsolidity/ErrorCheck.cpp11
-rw-r--r--test/libsolidity/GasMeter.cpp51
-rw-r--r--test/libsolidity/Imports.cpp38
-rw-r--r--test/libsolidity/InlineAssembly.cpp542
-rw-r--r--test/libsolidity/JSONCompiler.cpp111
-rw-r--r--test/libsolidity/Metadata.cpp63
-rw-r--r--test/libsolidity/SolidityABIJSON.cpp138
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp1412
-rw-r--r--test/libsolidity/SolidityExpressionCompiler.cpp147
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp2568
-rw-r--r--test/libsolidity/SolidityNatspecJSON.cpp415
-rw-r--r--test/libsolidity/SolidityOptimizer.cpp269
-rw-r--r--test/libsolidity/SolidityParser.cpp128
-rw-r--r--test/libsolidity/SolidityScanner.cpp35
-rw-r--r--test/libsolidity/SolidityTypes.cpp67
-rw-r--r--test/libsolidity/StandardCompiler.cpp235
251 files changed, 22855 insertions, 8003 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..86a837c1
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{cpp,h}]
+indent_style = tab
+
+[*.{py,rst,sh,yml}]
+indent_style = space
+indent_size = 4
+
+[std/**.sol]
+indent_style = space
+indent_size = 4
diff --git a/.gitignore b/.gitignore
index e3e12421..114420c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
-.commit_hash.txt
-.prerelease.txt
+commit_hash.txt
+prerelease.txt
# Compiled Object files
*.slo
@@ -41,3 +41,4 @@ docs/utils/__pycache__
# IDE files
.idea
browse.VC.db
+CMakeLists.txt.user
diff --git a/.travis.yml b/.travis.yml
index 2160d175..315d29bf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,16 +21,32 @@
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
-# (c) 2016 solidity contributors.
+# (c) 2016-2017 solidity contributors.
#------------------------------------------------------------------------------
language: cpp
+
branches:
- # We need to whitelist the branches which we want to have "push" automation.
+ # We need to whitelist the branches which we want to have "push" automation,
+ # this includes tags (which are treated as branches by travis).
# Pull request automation is not constrained to this set of branches.
only:
- develop
- release
+ - /^v[0-9]/
+
+env:
+ global:
+ - ENCRYPTION_LABEL="6d4541b72666"
+ - SOLC_BUILD_TYPE=RelWithDebInfo
+ - SOLC_DOCS=Off
+ - SOLC_EMSCRIPTEN=Off
+ - SOLC_INSTALL_DEPS_TRAVIS=On
+ - SOLC_RELEASE=On
+ - SOLC_TESTS=On
+ - SOLC_STOREBYTECODE=Off
+ - SOLC_DOCKER=Off
+
matrix:
include:
# Ubuntu 14.04 LTS "Trusty Tahr"
@@ -46,6 +62,7 @@ matrix:
compiler: gcc
env:
- ZIP_SUFFIX=ubuntu-trusty
+ - SOLC_STOREBYTECODE=On
- os: linux
dist: trusty
@@ -53,16 +70,31 @@ matrix:
compiler: clang
env:
- ZIP_SUFFIX=ubuntu-trusty-clang
+ - SOLC_STOREBYTECODE=On
# Documentation target, which generates documentation using Phoenix / ReadTheDocs.
- os: linux
dist: trusty
sudo: required
compiler: gcc
+ before_install:
+ - sudo apt-get -y install python-sphinx
env:
- - TRAVIS_DOCS=On
- - TRAVIS_RELEASE=Off
- - TRAVIS_TESTS=Off
+ - SOLC_DOCS=On
+ - SOLC_RELEASE=Off
+ - SOLC_TESTS=Off
+
+ # Docker target, which generates a statically linked alpine image
+ - os: linux
+ dist: trusty
+ sudo: required
+ services:
+ - docker
+ env:
+ - SOLC_DOCKER=On
+ - SOLC_INSTALL_DEPS_TRAVIS=Off
+ - SOLC_RELEASE=Off
+ - SOLC_TESTS=Off
# Emscripten target, which compiles 'solc' to javascript and uploads the resulting .js
# files to https://github.com/ethereum/solc-bin. These binaries are used in Browser-Solidity
@@ -71,15 +103,21 @@ matrix:
dist: trusty
sudo: required
compiler: gcc
+ node_js:
+ - "6"
services:
- docker
before_install:
+ - nvm install 6
+ - nvm use 6
- docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit
env:
- - TRAVIS_EMSCRIPTEN=On
- - TRAVIS_INSTALL_DEPS=Off
- - TRAVIS_RELEASE=Off
- - TRAVIS_TESTS=Off
+ - SOLC_EMSCRIPTEN=On
+ - SOLC_INSTALL_DEPS_TRAVIS=Off
+ - SOLC_RELEASE=Off
+ - SOLC_TESTS=Off
+ - ZIP_SUFFIX=emscripten
+ - SOLC_STOREBYTECODE=On
# OS X Mavericks (10.9)
# https://en.wikipedia.org/wiki/OS_X_Mavericks
@@ -98,7 +136,7 @@ matrix:
# env:
# # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?"
# # https://github.com/ethereum/solidity/issues/894
-# - TRAVIS_TESTS=Off
+# - SOLC_TESTS=Off
# - ZIP_SUFFIX=osx-yosemite
# OS X El Capitan (10.11)
@@ -109,10 +147,10 @@ matrix:
# env:
# # The use of Debug config here ONLY for El Capitan is a workaround for "The Heisenbug"
# # See https://github.com/ethereum/webthree-umbrella/issues/565
-# - TRAVIS_BUILD_TYPE=Debug
+# - SOLC_BUILD_TYPE=Debug
# # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?"
# # https://github.com/ethereum/solidity/issues/894
-# - TRAVIS_TESTS=Off
+# - SOLC_TESTS=Off
# - ZIP_SUFFIX=osx-elcapitan
# macOS Sierra (10.12)
@@ -123,10 +161,10 @@ matrix:
# env:
# # Look like "The Heisenbug" is occurring here too, so we'll do the same workaround.
# # See https://travis-ci.org/ethereum/solidity/jobs/150240930
-# - TRAVIS_BUILD_TYPE=Debug
+# - SOLC_BUILD_TYPE=Debug
# # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?"
# # https://github.com/ethereum/solidity/issues/894
-# - TRAVIS_TESTS=Off
+# - SOLC_TESTS=Off
# - ZIP_SUFFIX=macos-sierra
git:
@@ -137,42 +175,26 @@ cache:
directories:
- boost_1_57_0
- build
+ - $HOME/.local
install:
- - test $TRAVIS_INSTALL_DEPS != On || ./scripts/install_deps.sh
+ - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh)
+ - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh)
+ - if [ "$TRAVIS_BRANCH" = release -o -n "$TRAVIS_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
- echo -n "$TRAVIS_COMMIT" > commit_hash.txt
- - test "$TRAVIS_PULL_REQUESTS" != "false" || test "$TRAVIS_BRANCH" != release || echo -n > prerelease.txt # this is a proper release
+
before_script:
- - test $TRAVIS_EMSCRIPTEN != On || ./scripts/build_emscripten.sh
- - test $TRAVIS_RELEASE != On || (mkdir -p build
- && cd build
- && cmake .. -DCMAKE_BUILD_TYPE=$TRAVIS_BUILD_TYPE
- && make -j2
- && cd ..
- && ./scripts/release.sh $ZIP_SUFFIX )
-script:
- - test $TRAVIS_DOCS != On || ./scripts/docs.sh
+ - test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh)
+ - test $SOLC_DOCKER != On || (scripts/docker_build.sh)
+ - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE
+ && scripts/release.sh $ZIP_SUFFIX
+ && scripts/create_source_tarball.sh)
- # There are a variety of reliability issues with the Solidity unit-tests at the time of
- # writing (especially on macOS), so within TravisCI we will try to run the unit-tests
- # up to 3 times before giving up and declaring the tests as broken.
- #
- # We should aim to remove this "retry logic" as soon as we can, because it is a
- # band-aid for issues which need solving at their root. Some of those issues will be
- # in Solidity's RPC setup and some will be in 'eth'. It seems unlikely that Solidity
- # itself is broken from the failure messages which we are seeing.
- #
- # More details on known issues at https://github.com/ethereum/solidity/issues/769
- - test $TRAVIS_TESTS != On || (cd $TRAVIS_BUILD_DIR && (./scripts/tests.sh || ./scripts/tests.sh || ./scripts/tests.sh) )
-env:
- global:
- - ENCRYPTION_LABEL="6d4541b72666"
- - TRAVIS_BUILD_TYPE=RelWithDebInfo
- - TRAVIS_DOCS=Off
- - TRAVIS_EMSCRIPTEN=Off
- - TRAVIS_INSTALL_DEPS=On
- - TRAVIS_RELEASE=On
- - TRAVIS_TESTS=On
+script:
+ - test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh)
+ - test $SOLC_DOCS != On || (scripts/docs.sh)
+ - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh)
+ - test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh)
deploy:
# This is the deploy target for the Emscripten build.
@@ -181,52 +203,37 @@ deploy:
# Both the build and deploy steps for Emscripten are only run within the Ubuntu
# configurations (not for macOS). That is controlled by conditionals within the bash
# scripts because TravisCI doesn't provide much in the way of conditional logic.
+
- provider: script
- script: test $TRAVIS_EMSCRIPTEN != On || scripts/release_emscripten.sh
+ script: test $SOLC_EMSCRIPTEN != On || (scripts/release_emscripten.sh)
skip_cleanup: true
on:
branch:
- develop
- release
-
+ # This is the deploy target for the dockerfile. If we are pushing into a develop branch, it will be tagged
+ # as a nightly and appended the commit of the branch it was pushed in. If we are pushing to master it will
+ # be tagged as "stable" and given the version tag as well.
+ - provider: script
+ script: test $SOLC_DOCKER != On || (scripts/docker_deploy.sh)
+ skip_cleanup: true
+ on:
+ branch:
+ - develop
+ - release
+ - /^v[0-9]/
# This is the deploy target for the native build (Linux and macOS)
- # which generates ZIPs per commit. We are in agreement that
- # generating ZIPs per commit for the develop branch is probably
- # just noise, so we only run this deployment target on 'release'.
- #
- # Unlike the Appveyor GitHub Releases target, the support in TravisCI
- # seemingly doesn't provide a means for passing a description, tag, etc.
- # In practice, we are letting the Appveyor CI do all that stuff, and
- # then this deployment flow just seems to find that most recent tag,
- # and just add our Linux and macOS ZIPs into the same tag, which is
- # what we want to happen. But is very accidental and brittle-looking.
- #
- # The 'skip_cleanup' stops the workspace being cleaned out prior to
- # generation of the artifacts. Strange that we should explicitly
- # need to do that, but we do.
- #
- # Tokens in TravisCI can be generated a few different ways. Bob had
- # success using the 'travis' gem, and then using that gem to
- # create/edit this .travis.yml file, and then cut-and-pasting the
- # good bits back out of what it generated. The gem changes all the
- # whitespace and deletes comments, so cannot be used as-is. But
- # it does generate an appropriate auth token.
- #
- # TODO - I do not know if the api_key below which work correctly
- # for ethereum/solidity. I suspect not, for the same reason as
- # my auth token does not work for Appveyor. I don't have enough
- # permissions to enable this myself. Christian should be able to.
- #
- # See https://docs.travis-ci.com/user/deployment/releases
- # See https://blog.travis-ci.com/2013-01-28-token-token-token/
- # See https://github.com/ethereum/webthree-umbrella/issues/658
+ # which generates ZIPs per commit and the source tarball.
#
+ # This runs for each tag that is created and adds the corresponding files.
- provider: releases
api_key:
secure: PWH37xVBCF0XiSjl+eH7XIdkrfxZXjzvqF4PiBOnD3VnFz+odrdnIwBmCeBYTHTWF8efpp8fmzWJk2UVq1JcpyZiC+SVxO8dx91W2ia1a+wKrEQuDgkUrZBkl5IQNCv0QS81DDQhliyZEaYh8wHO/7RReyMpGpw2U2u85WkFiZ+LdlHEZPfzUeh9lxQ9n8qwFL8Rja+Q05d4cQ8zaVEtofJJT4T6DUWhc3TzuxDYxOmjwg37rC9CkGSLn6VadSh8b3j5R0SZupFsAEvBL/imBLP9r9ewoo7o4p6By3jwiIgH9yNg7LM618xbffcNaYF/KtLBi9uPHfqF7hRD4PlECz+D0PR78nQItOX5HKm1QMg5kCnghRVCA0IVjpV5fiYQnMLM7dCRv34I5b3zLpa69wQ/GLYB2FViqNUfvPeiZTEeIJ2OmATlFx8AH2JoqpY1XJknWb35+vMfa8LSiJJW++SLWeV+ncC92hrvyZ1cy3trepRRZIfyYepxHifnfdWMkddQUJk5b2WS5Fy/TJLZNPeombnpvRhUC38dsYItarKeXTc6k4oADCEDZ2rgGIcEiqRxXV11Y5xHJekLDWzUs+YJNcCuL4pnAP//LOnbnH2w9rLpwhQYSl0anCd097NivAXQJXO2JI/byIYz1kiCVQWnW6EM8+72mLOklf/Qr8k=
- file: $TRAVIS_BUILD_DIR/solidity-$ZIP_SUFFIX.zip
+
+ overwrite: true
+ file_glob: true
+ file: $TRAVIS_BUILD_DIR/upload/*
skip_cleanup: true
on:
- repo: ethereum/solidity
- branch: release
- condition: $TRAVIS_RELEASE == On
+ all_branches: true
+ tags: true
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ee66eebd..b29bc414 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
-set(PROJECT_VERSION "0.4.7")
+set(PROJECT_VERSION "0.4.14")
project(solidity VERSION ${PROJECT_VERSION})
# Let's find our dependencies
@@ -24,6 +24,15 @@ include(EthExecutableHelper)
# Include utils
include(EthUtils)
+# Create license.h from LICENSE.txt and template
+# Converting to char array is required due to MSVC's string size limit.
+file(READ ${CMAKE_SOURCE_DIR}/LICENSE.txt LICENSE_TEXT HEX)
+string(REGEX MATCHALL ".." LICENSE_TEXT "${LICENSE_TEXT}")
+string(REGEX REPLACE ";" ",\n\t0x" LICENSE_TEXT "${LICENSE_TEXT}")
+set(LICENSE_TEXT "0x${LICENSE_TEXT}")
+
+configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" "license.h")
+
include(EthOptions)
configure_project(TESTS)
diff --git a/Changelog.md b/Changelog.md
index 1058fdf5..9afb0679 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,11 +1,164 @@
-### 0.4.7 (unreleased)
+### 0.4.14 (unreleased)
Features:
+
+Bugfixes:
+ * Type Checker: Fix invalid "specify storage keyword" warning for reference members of structs.
+
+
+### 0.4.13 (2017-07-06)
+
+Features:
+ * Syntax Checker: Deprecated "throw" in favour of require(), assert() and revert().
+ * Type Checker: Warn if a local storage reference variable does not explicitly use the keyword ``storage``.
+
+Bugfixes:
+ * Code Generator: Correctly unregister modifier variables.
+ * Compiler Interface: Only output AST if analysis was successful.
+ * Error Output: Do not omit the error type.
+
+### 0.4.12 (2017-07-03)
+
+Features:
+ * Assembly: Add ``CREATE2`` (EIP86), ``STATICCALL`` (EIP214), ``RETURNDATASIZE`` and ``RETURNDATACOPY`` (EIP211) instructions.
+ * Assembly: Display auxiliary data in the assembly output.
+ * Assembly: Renamed ``SHA3`` to ``KECCAK256``.
+ * AST: export all attributes to JSON format.
+ * C API (``jsonCompiler``): Use the Standard JSON I/O internally.
+ * Code Generator: Added the Whiskers template system.
+ * Inline Assembly: ``for`` and ``switch`` statements.
+ * Inline Assembly: Function definitions and function calls.
+ * Inline Assembly: Introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias.
+ * Inline Assembly: Present proper error message when not supplying enough arguments to a functional
+ instruction.
+ * Inline Assembly: Warn when instructions shadow Solidity variables.
+ * Inline Assembly: Warn when using ``jump``s.
+ * Remove obsolete Why3 output.
+ * Type Checker: Enforce strict UTF-8 validation.
+ * Type Checker: Warn about copies in storage that might overwrite unexpectedly.
+ * Type Checker: Warn about type inference from literal numbers.
+ * Static Analyzer: Warn about deprecation of ``callcode``.
+
+Bugfixes:
+ * Assembly: mark ``MLOAD`` to have side effects in the optimiser.
+ * Code Generator: Fix ABI encoding of empty literal string.
+ * Code Generator: Fix negative stack size checks.
+ * Code generator: Use ``REVERT`` instead of ``INVALID`` for generated input validation routines.
+ * Inline Assembly: Enforce function arguments when parsing functional instructions.
+ * Optimizer: Disallow optimizations involving ``MLOAD`` because it changes ``MSIZE``.
+ * Static Analyzer: Unused variable warnings no longer issued for variables used inside inline assembly.
+ * Type Checker: Fix address literals not being treated as compile-time constants.
+ * Type Checker: Fixed crash concerning non-callable types.
+ * Type Checker: Fixed segfault with constant function parameters
+ * Type Checker: Disallow comparisons between mapping and non-internal function types.
+ * Type Checker: Disallow invoking the same modifier multiple times.
+ * Type Checker: Do not treat strings that look like addresses as addresses.
+ * Type Checker: Support valid, but incorrectly rejected UTF-8 sequences.
+
+### 0.4.11 (2017-05-03)
+
+Features:
+ * Implement the Standard JSON Input / Output API
+ * Support ``interface`` contracts.
+ * C API (``jsonCompiler``): Add the ``compileStandard()`` method to process a Standard JSON I/O.
+ * Commandline interface: Add the ``--standard-json`` parameter to process a Standard JSON I/O.
+ * Commandline interface: Support ``--allow-paths`` to define trusted import paths. Note: the
+ path(s) of the supplied source file(s) is always trusted.
+ * Inline Assembly: Storage variable access using ``_slot`` and ``_offset`` suffixes.
+ * Inline Assembly: Disallow blocks with unbalanced stack.
+ * Static analyzer: Warn about statements without effects.
+ * Static analyzer: Warn about unused local variables, parameters, and return parameters.
+ * Syntax checker: issue deprecation warning for unary '+'
+
+Bugfixes:
+ * Assembly output: Implement missing AssemblyItem types.
+ * Compiler interface: Fix a bug where source indexes could be inconsistent between Solidity compiled
+ with different compilers (clang vs. gcc) or compiler settings. The bug was visible in AST
+ and source mappings.
+ * Gas Estimator: Reflect the most recent fee schedule.
+ * Type system: Contract inheriting from base with unimplemented constructor should be abstract.
+ * Optimizer: Number representation bug in the constant optimizer fixed.
+
+### 0.4.10 (2017-03-15)
+
+Features:
+ * Add ``assert(condition)``, which throws if condition is false (meant for internal errors).
+ * Add ``require(condition)``, which throws if condition is false (meant for invalid input).
+ * Commandline interface: Do not overwrite files unless forced.
+ * Introduce ``.transfer(value)`` for sending Ether.
+ * Code generator: Support ``revert()`` to abort with rolling back, but not consuming all gas.
+ * Inline assembly: Support ``revert`` (EIP140) as an opcode.
+ * Parser: Support scientific notation in numbers (e.g. ``2e8`` and ``200e-2``).
+ * Type system: Support explicit conversion of external function to address.
+ * Type system: Warn if base of exponentiation is literal (result type might be unexpected).
+ * Type system: Warn if constant state variables are not compile-time constants.
+
+Bugfixes:
+ * Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``).
+ * Commandline interface: Do not try creating paths ``.`` and ``..``.
+ * Commandline interface: Allow long library names.
+ * Parser: Disallow octal literals.
+ * Type system: Fix a crash caused by continuing on fatal errors in the code.
+ * Type system: Disallow compound assignment for tuples.
+ * Type system: Detect cyclic dependencies between constants.
+ * Type system: Disallow arrays with negative length.
+ * Type system: Fix a crash related to invalid binary operators.
+ * Type system: Disallow ``var`` declaration with empty tuple type.
+ * Type system: Correctly convert function argument types to pointers for member functions.
+ * Type system: Move privateness of constructor into AST itself.
+ * Inline assembly: Charge one stack slot for non-value types during analysis.
+ * Assembly output: Print source location before the operation it refers to instead of after.
+ * Optimizer: Stop trying to optimize tricky constants after a while.
+
+### 0.4.9 (2017-01-31)
+
+Features:
+ * Compiler interface: Contracts and libraries can be referenced with a ``file:`` prefix to make them unique.
+ * Compiler interface: Report source location for "stack too deep" errors.
+ * AST: Use deterministic node identifiers.
+ * Inline assembly: introduce ``invalid`` (EIP141) as an opcode.
+ * Type system: Introduce type identifier strings.
+ * Type checker: Warn about invalid checksum for addresses and deduce type from valid ones.
+ * Metadata: Do not include platform in the version number.
+ * Metadata: Add option to store sources as literal content.
+ * Code generator: Extract array utils into low-level functions.
+ * Code generator: Internal errors (array out of bounds, etc.) now cause a reversion by using an invalid
+ instruction (0xfe - EIP141) instead of an invalid jump. Invalid jump is still kept for explicit throws.
+
+Bugfixes:
+ * Code generator: Allow recursive structs.
+ * Inline assembly: Disallow variables named like opcodes.
+ * Type checker: Allow multiple events of the same name (but with different arities or argument types)
+ * Natspec parser: Fix error with ``@param`` parsing and whitespace.
+
+### 0.4.8 (2017-01-13)
+
+Features:
+ * Optimiser: Performance improvements.
+ * Output: Print assembly in new standardized Solidity assembly format.
+
+Bugfixes:
+ * Remappings: Prefer longer context over longer prefix.
+ * Type checker, code generator: enable access to events of base contracts' names.
+ * Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``.
+ * Type checker, disallow inheritances of different kinds (e.g. a function and a modifier) of members of the same name
+
+### 0.4.7 (2016-12-15)
+
+Features:
+ * Bitshift operators.
+ * Type checker: Warn when ``msg.value`` is used in non-payable function.
* Code generator: Inject the Swarm hash of a metadata file into the bytecode.
+ * Code generator: Replace expensive memcpy precompile by simple assembly loop.
* Optimizer: Some dead code elimination.
Bugfixes:
+ * Code generator: throw if calling the identity precompile failed during memory (array) copying.
* Type checker: string literals that are not valid UTF-8 cannot be converted to string type
+ * Code generator: any non-zero value given as a boolean argument is now converted into 1.
+ * AST Json Converter: replace ``VariableDefinitionStatement`` nodes with ``VariableDeclarationStatement``
+ * AST Json Converter: fix the camel case in ``ElementaryTypeNameExpression``
+ * AST Json Converter: replace ``public`` field with ``visibility`` in the function definition nodes
### 0.4.6 (2016-11-22)
diff --git a/README.md b/README.md
index bda66996..1fed49fb 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[![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)
## Useful links
-To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](http://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](http://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts.
+To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts.
You can start using [Solidity in your browser](https://ethereum.github.io/browser-solidity/) with no need to download or compile anything.
@@ -11,9 +11,9 @@ The changelog for this project can be found [here](https://github.com/ethereum/s
Solidity is still under development. So please do not hesitate and open an [issue in GitHub](https://github.com/ethereum/solidity/issues) if you encounter anything strange.
## Building
-See the [Solidity documentation](http://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source) for build instructions.
+See the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source) for build instructions.
## How to Contribute
-Please see our contribution guidelines in [the Solidity documentation](http://solidity.readthedocs.io/en/latest/contributing.html).
+Please see our contribution guidelines in [the Solidity documentation](https://solidity.readthedocs.io/en/latest/contributing.html).
Any contributions are welcome!
diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md
new file mode 100644
index 00000000..76e3bfb1
--- /dev/null
+++ b/ReleaseChecklist.md
@@ -0,0 +1,18 @@
+Checklist for making a release:
+
+ - [ ] Check that all "nextrelease" issues and pull requests are merged to ``develop``.
+ - [ ] Create a commit in ``develop`` that updates the ``Changelog`` to include a release date (run the tests locally to update the bug list).
+ - [ ] Create a pull request and wait for the tests, merge it.
+ - [ ] Create a pull request from ``develop`` to ``release``, wait for the tests, then merge it.
+ - [ ] Make a final check that there are no platform-dependency issues in the ``solc-test-bytecode`` repository.
+ - [ ] Wait for the tests for the commit on ``release``, create a release in Github, creating the tag.
+ - [ ] Thank voluntary contributors in the Github release page (use ``git shortlog -s -n -e origin/release..origin/develop``).
+ - [ ] Wait for the CI runs on the tag itself (they should push artefacts onto the Github release page).
+ - [ ] Run ``scripts/release_ppa.sh release`` to create the PPA release (you need the relevant openssl key).
+ - [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems).
+ - [ ] Make a release of ``solc-js``: Increment the version number, create a pull request for that, merge it after tests succeeded.
+ - [ ] Run ``npm publish`` in the updated ``solc-js`` repository.
+ - [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry.
+ - [ ] Merge ``release`` back into ``develop``.
+ - [ ] Announce on Twitter and Reddit.
+ - [ ] Lay back, wait for bug reports and repeat from step 1 :)
diff --git a/appveyor.yml b/appveyor.yml
index 85fb36f2..3d4d65bb 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -34,6 +34,10 @@ branches:
os: Visual Studio 2015
configuration:
- RelWithDebInfo
+environment:
+ # This is used for pushing to solidity-test-bytecodes
+ priv_key:
+ secure: yYGwg4rhCdHfwuv2mFjaNEDwAx3IKUbp0D5fMGpaKefnfk+BiMS5bqSHRiOj91PZ91P9pUk2Vu+eNuS4hTFCf1zFGfrOhlJ4Ij0xSyU5m/LQr590Mo+f7W94Xc8ubgo6j2hp9qH/szTqTzmAkmxKO5TLlWjVzVny2t/s5o5UprLS1/MdzDNLjpVNXR03oKfdWUV9a2l6+PejXCbqyUCagh6BByZqeAPbDcil6eAfxu4EPX83Fuurof+KqFzIWycBG5qK1pTipn2pxiA0QKuUrD8y8VNL0S23NTgxoxSp7nPVMd3K0qRSzPM5lrqS7Z8i3evkVwPbuhu0gSiV08jGVahH2snQ3JGYsH2D4KmVn/xiVBeJ0lRplYlfZF0GUu7iJ+DDxi6wBPhW9A25/NyD/mx7Ub2dLheyWi8AjdSCzhfRD+4We8FQQeHRo3Q0kAohFmlCXdXhrcwOOloId8r6xYwg+hWxHTt2Oe9CKwXfmiPjgl/Gd6lYgLpyyfJ8drQ6tjO/pybLEa10v74qYNdVW5LaLIsRUM9Jm/FDVTrOGYtPndi87mF+/tBJIaXXNz0EMl5xvsKW0SBfUMV49zoDDKZZgWyO9U/cfViEUi7Sdn9QLsBWLZfSgBQNkq3WGZVKPq58OxEWT9dUghQHlSVh2qWF/NUx0TRBjiJl9JM56ENTMD00y18eDcXNCeLLVYB+R1axabUPdXivrO+BrWQK94IWxKEJ+YYN8WVJWAO5T/EBDKwgiXGneePwJ75WP7XCLtuYxqjC+CeW3xBVCzCEeZB/VVBvt7fhmtcoeZZ6tAS10h0yY5WWZ/EUVorj+c/FrMm7Nlpcrd1p4hciffePSLVg+yvy9/xTuM9trYWMgj4xcDQbYsaeItHO2Z3EiUoCgNdUw6rONiNwS/XBApWhCcklWm0/g62h2gOa7/hnKG6p2omQzYw+cOzWbF9+DBzoTSXXZXqbUshVee+CD+iYJKleGYSdbMdM89HW4HyskHk6HgM1ggE8CsgD1pMhXtqLTYZBlvsZCBkHPkD9NhGD2DtrNOmJOW8xwkL2/Il6roDF4n856XNdsjvd++rvQoKr58SkyApCJeCo3sfVres0W22g+7If2b2kWC4/DphrFkeaceFzJOctBUrwstvQBXIVOcadU978A3E7jvTaMR4JL9kC/iPOUVNjNRNM/gNvTlf3CIyMMszFeftjEBGnCZaSpht2RtNapRQQb6QPkOP88nufQVZq/TP1ECmvdTUWJ7kSnAupu6u8oH2x2IIm/KKeIwSYU5rGxjRb36DwgXCHcwfRYo3VNorwTeZGj4q1TSM9PuvgzNg//gKZW6VRa+HdNm/40ZGpDsOrr55tOBqfpq9k5RmevqW/OMZS3xUuArKdYLQY75t9eWcbHSgFN2ZY1KEdyEEvVKgs6Q4lEnSSulGxroRxTU5BOoA0V4tCeCUoSPD3FB93WsO9fBPzNsqOuBtDdIkApefzc1pT38uKpmVfggKUsoWUdqMXAWqCDWr2uw9EE900RJpEY6mIEWhkcro5LAMwaqByOGpqFFUkH+UWTC102eVHEmjxKpC6c6cSzoKKU6Ckd+jVRFO7TvmVe1MKCwjXj8lcAfAM2gQ+XehtrQdIBhAmCrnzurfz2u9tKVdpiADC1ig+kMs1/HX2713LYVXzDKdk+duQ94SVtGv9F2Iv+KN5oq4UFgll6VGt7GHsJOrYYf/wrOfB09IkpmjNygvcpmmSdcXXF8ulDD6KHTGEGUlFwLOpEwKx+zX2ZvviStHhN8KsoTKSVSueDmSSI63HdTS7FxfrHJc1yAzsdqEN5g5eV/z2Fn34qy64mdFSAZMF5zsbWZYFpc9ef3llF5aRcuD90JWT2VC7rB2jeGEtiwGkDlqKzxqRvJk06wTK6+n5RncN66bDaksulOPJMAR/bRW7dinV8T6yIvybuhqDetxJQP6eyAnW4xr1YxIAG4BXGZV6XAPTgOG2oGvMdncxkcLQHXVu07x39ySqP/m2MBxn0zF3DmaqrSPIRMhS8gG3d/23Jux3YHDEOBHjdJSdwqs5F5+QBFPV2rmJnpcSoW4d3M119XI20L914c62R7wY4e6+qmi3ydQU9g6p8psZgaE3TuMsyzX3k4C30nC/3gWT+zl253NjZwfbzIdHu5LWNDY9kEHtKzLP
# NB: Appveyor cache is disabled, because it is proving very unreliable.
# We can re-enable it when we find a way to mitigate the unreliability
# issues. Have automated builds be reliable is the more important thing.
@@ -43,7 +47,14 @@ configuration:
#init:
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
install:
+ - ps: $fileContent = "-----BEGIN RSA PRIVATE KEY-----`n"
+ - ps: $fileContent += $env:priv_key.Replace(' ', "`n")
+ - ps: $fileContent += "`n-----END RSA PRIVATE KEY-----`n"
+ - ps: Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent
- git submodule update --init --recursive
+ - ps: $prerelease = "nightly."
+ - ps: $prerelease += Get-Date -format "yyyy.M.d"
+ - ps: if($env:appveyor_repo_branch -eq 'release') { Set-Content prerelease.txt $null } else { Set-Content prerelease.txt $prerelease }
- scripts/install_deps.bat
- set ETHEREUM_DEPS_PATH=%APPVEYOR_BUILD_FOLDER%\deps\install
before_build:
@@ -54,15 +65,13 @@ build_script:
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
- cd %APPVEYOR_BUILD_FOLDER%
- scripts\release.bat %CONFIGURATION%
+ - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short
+ - ps: scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir
test_script:
- cd %APPVEYOR_BUILD_FOLDER%
- - cd deps\install\x64\eth
- - ps: $ethProc = Start-Process eth.exe --test
- - ps: Start-Sleep -s 100
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
- - copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" .
- - soltest.exe -- --ipcpath \\.\pipe\geth.ipc
+ - soltest.exe --show-progress -- --no-ipc
artifacts:
- path: solidity-windows.zip
diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake
index c734423b..ea3b185a 100644
--- a/cmake/EthCompilerSettings.cmake
+++ b/cmake/EthCompilerSettings.cmake
@@ -65,13 +65,13 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
# Build everything as shared libraries (.so files)
add_definitions(-DSHAREDLIB)
-
+
# If supported for the target machine, emit position-independent code, suitable for dynamic
# linking and avoiding any limit on the size of the global offset table.
add_compile_options(-fPIC)
# Configuration-specific compiler settings.
- set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DETH_DEBUG")
+ set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG")
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
@@ -94,6 +94,12 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
add_compile_options(-fstack-protector)
endif()
+ # Until https://github.com/ethereum/solidity/issues/2479 is handled
+ # disable all implicit fallthrough warnings in the codebase for GCC > 7.0
+ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
+ add_compile_options(-Wno-implicit-fallthrough)
+ endif()
+
# Additional Clang-specific compiler settings.
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
diff --git a/cmake/UseDev.cmake b/cmake/UseDev.cmake
index 4461a8a0..68df691a 100644
--- a/cmake/UseDev.cmake
+++ b/cmake/UseDev.cmake
@@ -10,6 +10,7 @@ function(eth_apply TARGET REQUIRED SUBMODULE)
target_link_libraries(${TARGET} ${Boost_RANDOM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_FILESYSTEM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_SYSTEM_LIBRARIES})
+ target_link_libraries(${TARGET} ${Boost_REGEX_LIBRARIES})
if (DEFINED MSVC)
target_link_libraries(${TARGET} ${Boost_CHRONO_LIBRARIES})
diff --git a/cmake/scripts/buildinfo.cmake b/cmake/scripts/buildinfo.cmake
index 8e1615f6..efbfb8fb 100644
--- a/cmake/scripts/buildinfo.cmake
+++ b/cmake/scripts/buildinfo.cmake
@@ -60,6 +60,8 @@ if (SOL_COMMIT_HASH AND SOL_LOCAL_CHANGES)
set(SOL_COMMIT_HASH "${SOL_COMMIT_HASH}.mod")
endif()
+set(SOL_VERSION_COMMIT "commit.${SOL_COMMIT_HASH}")
+set(SOl_VERSION_PLATFORM ETH_BUILD_PLATFORM)
set(SOL_VERSION_BUILDINFO "commit.${SOL_COMMIT_HASH}.${ETH_BUILD_PLATFORM}")
set(TMPFILE "${ETH_DST_DIR}/BuildInfo.h.tmp")
diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in
index 6c16e4ac..4b35df98 100644
--- a/cmake/templates/BuildInfo.h.in
+++ b/cmake/templates/BuildInfo.h.in
@@ -8,3 +8,5 @@
#define ETH_BUILD_PLATFORM "@ETH_BUILD_PLATFORM@"
#define SOL_VERSION_PRERELEASE "@SOL_VERSION_PRERELEASE@"
#define SOL_VERSION_BUILDINFO "@SOL_VERSION_BUILDINFO@"
+#define SOL_VERSION_COMMIT "@SOL_VERSION_COMMIT@"
+#define SOL_VERSION_PLATFORM "@SOL_VERSION_PLATFORM@"
diff --git a/cmake/templates/license.h.in b/cmake/templates/license.h.in
new file mode 100644
index 00000000..84524a52
--- /dev/null
+++ b/cmake/templates/license.h.in
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <string>
+
+static std::string const otherLicenses{R"(Most of the code is licensed under GPLv3 (see below), the license for individual
+parts are as follows:
+
+libkeccak-tiny:
+ A single-file implementation of SHA-3 and SHAKE implemented by David Leon Gil
+ License: CC0, attribution kindly requested. Blame taken too, but not liability.
+
+jsoncpp:
+ The JsonCpp library's source code, including accompanying documentation,
+ tests and demonstration applications, are licensed under the following
+ conditions...
+
+ The JsonCpp Authors explicitly disclaim copyright in all
+ jurisdictions which recognize such a disclaimer. In such jurisdictions,
+ this software is released into the Public Domain.
+
+ In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
+ 2010), this software is Copyright (c) 2007-2010 by The JsonCpp Authors, and is
+ released under the terms of the MIT License (see below).
+
+ In jurisdictions which recognize Public Domain property, the user of this
+ software may choose to accept it either as 1) Public Domain, 2) under the
+ conditions of the MIT License (see below), or 3) under the terms of dual
+ Public Domain/MIT License conditions described here, as they choose.
+
+ The MIT License is about as close to Public Domain as a license can get, and is
+ described in clear, concise terms at:
+
+ http://en.wikipedia.org/wiki/MIT_License
+
+ The full text of the MIT License follows:
+
+ ========================================================================
+ Copyright (c) 2007-2010 The JsonCpp Authors
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use, copy,
+ modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ ========================================================================
+ (END LICENSE TEXT)
+
+ The MIT license is compatible with both the GPL and commercial
+ software, affording one all of the rights of Public Domain with the
+ minor nuisance of being required to keep the above copyright notice
+ and license text in the source code. Note also that by accepting the
+ Public Domain "license" you can re-license your copy using whatever
+ license you like.
+
+All other code is licensed under GPL version 3:
+
+)"};
+
+static char const licenseText[] = {
+ @LICENSE_TEXT@, 0
+};
diff --git a/deps b/deps
-Subproject b3db8905894eafb74a436b702de78ba235f3a3b
+Subproject e5c8316db8d3daa0abc3b5af8545ce330057608
diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst
new file mode 100644
index 00000000..e39c8861
--- /dev/null
+++ b/docs/abi-spec.rst
@@ -0,0 +1,345 @@
+.. index:: abi, application binary interface
+
+.. _ABI:
+
+******************************************
+Application Binary Interface Specification
+******************************************
+
+Basic design
+============
+
+The Application Binary Interface is the standard way to interact with contracts in the Ethereum ecosystem, both
+from outside the blockchain and for contract-to-contract interaction. Data is encoded following its type,
+according to this specification.
+
+We assume the Application Binary Interface (ABI) is strongly typed, known at compilation time and static. No introspection mechanism will be provided. We assert that all contracts will have the interface definitions of any contracts they call available at compile-time.
+
+This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem.
+
+Function Selector
+=================
+
+The first four bytes of the call data for a function call specifies the function to be called. It is the
+first (left, high-order in big-endian) four bytes of the Keccak (SHA-3) hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype, i.e.
+the function name with the parenthesised list of parameter types. Parameter types are split by a single comma - no spaces are used.
+
+Argument Encoding
+=================
+
+Starting from the fifth byte, the encoded arguments follow. This encoding is also used in other places, e.g. the return values and also event arguments are encoded in the same way, without the four bytes specifying the function.
+
+Types
+=====
+
+The following elementary types exist:
+
+- `uint<M>`: unsigned integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. e.g. `uint32`, `uint8`, `uint256`.
+
+- `int<M>`: two's complement signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`.
+
+- `address`: equivalent to `uint160`, except for the assumed interpretation and language typing.
+
+- `uint`, `int`: synonyms for `uint256`, `int256` respectively (not to be used for computing the function selector).
+
+- `bool`: equivalent to `uint8` restricted to the values 0 and 1
+
+- `fixed<M>x<N>`: signed fixed-point decimal number of `M` bits, `0 < M <= 256`, `M % 8 ==0`, and `0 < N <= 80`, which denotes the value `v` as `v / (10 ** N)`.
+
+- `ufixed<M>x<N>`: unsigned variant of `fixed<M>x<N>`.
+
+- `fixed`, `ufixed`: synonyms for `fixed128x19`, `ufixed128x19` respectively (not to be used for computing the function selector).
+
+- `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`.
+
+- `function`: equivalent to `bytes24`: an address, followed by a function selector
+
+The following (fixed-size) array type exists:
+
+- `<type>[M]`: a fixed-length array of the given fixed-length type.
+
+The following non-fixed-size types exist:
+
+- `bytes`: dynamic sized byte sequence.
+
+- `string`: dynamic sized unicode string assumed to be UTF-8 encoded.
+
+- `<type>[]`: a variable-length array of the given fixed-length type.
+
+Types can be combined to anonymous structs by enclosing a finite non-negative number
+of them inside parentheses, separated by commas:
+
+- `(T1,T2,...,Tn)`: anonymous struct (ordered tuple) consisting of the types `T1`, ..., `Tn`, `n >= 0`
+
+It is possible to form structs of structs, arrays of structs and so on.
+
+
+Formal Specification of the Encoding
+====================================
+
+We will now formally specify the encoding, such that it will have the following
+properties, which are especially useful if some arguments are nested arrays:
+
+Properties:
+
+ 1. The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve `a_i[k][l][r]`. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case.
+
+ 2. The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative "addresses"
+
+We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block.
+
+**Definition:** The following types are called "dynamic":
+* `bytes`
+* `string`
+* `T[]` for any `T`
+* `T[k]` for any dynamic `T` and any `k > 0`
+
+All other types are called "static".
+
+**Definition:** `len(a)` is the number of bytes in a binary string `a`.
+The type of `len(a)` is assumed to be `uint256`.
+
+We define `enc`, the actual encoding, as a mapping of values of the ABI types to binary strings such
+that `len(enc(X))` depends on the value of `X` if and only if the type of `X` is dynamic.
+
+**Definition:** For any ABI value `X`, we recursively define `enc(X)`, depending
+on the type of `X` being
+
+- `(T1,...,Tk)` for `k >= 0` and any types `T1`, ..., `Tk`
+
+ `enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))`
+
+ where `X(i)` is the `ith` component of the value, and
+ `head` and `tail` are defined for `Ti` being a static type as
+
+ `head(X(i)) = enc(X(i))` and `tail(X(i)) = ""` (the empty string)
+
+ and as
+
+ `head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1))))`
+ `tail(X(i)) = enc(X(i))`
+
+ otherwise, i.e. if `Ti` is a dynamic type.
+
+ Note that in the dynamic case, `head(X(i))` is well-defined since the lengths of
+ the head parts only depend on the types and not the values. Its value is the offset
+ of the beginning of `tail(X(i))` relative to the start of `enc(X)`.
+
+- `T[k]` for any `T` and `k`:
+
+ `enc(X) = enc((X[0], ..., X[k-1]))`
+
+ i.e. it is encoded as if it were an anonymous struct with `k` elements
+ of the same type.
+
+- `T[]` where `X` has `k` elements (`k` is assumed to be of type `uint256`):
+
+ `enc(X) = enc(k) enc([X[1], ..., X[k]])`
+
+ i.e. it is encoded as if it were an array of static size `k`, prefixed with
+ the number of elements.
+
+- `bytes`, of length `k` (which is assumed to be of type `uint256`):
+
+ `enc(X) = enc(k) pad_right(X)`, i.e. the number of bytes is encoded as a
+ `uint256` followed by the actual value of `X` as a byte sequence, followed by
+ the minimum number of zero-bytes such that `len(enc(X))` is a multiple of 32.
+
+- `string`:
+
+ `enc(X) = enc(enc_utf8(X))`, i.e. `X` is utf-8 encoded and this value is interpreted as of `bytes` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters.
+
+- `uint<M>`: `enc(X)` is the big-endian encoding of `X`, padded on the higher-order (left) side with zero-bytes such that the length is a multiple of 32 bytes.
+- `address`: as in the `uint160` case
+- `int<M>`: `enc(X)` is the big-endian two's complement encoding of `X`, padded on the higher-oder (left) side with `0xff` for negative `X` and with zero bytes for positive `X` such that the length is a multiple of 32 bytes.
+- `bool`: as in the `uint8` case, where `1` is used for `true` and `0` for `false`
+- `fixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `int256`.
+- `fixed`: as in the `fixed128x19` case
+- `ufixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `uint256`.
+- `ufixed`: as in the `ufixed128x19` case
+- `bytes<M>`: `enc(X)` is the sequence of bytes in `X` padded with zero-bytes to a length of 32.
+
+Note that for any `X`, `len(enc(X))` is a multiple of 32.
+
+Function Selector and Argument Encoding
+=======================================
+
+All in all, a call to the function `f` with parameters `a_1, ..., a_n` is encoded as
+
+ `function_selector(f) enc((a_1, ..., a_n))`
+
+and the return values `v_1, ..., v_k` of `f` are encoded as
+
+ `enc((v_1, ..., v_k))`
+
+i.e. the values are combined into an anonymous struct and encoded.
+
+Examples
+========
+
+Given the contract:
+
+::
+
+ contract Foo {
+ function bar(bytes3[2] xy) {}
+ function baz(uint32 x, bool y) returns (bool r) { r = x > 32 || y; }
+ function sam(bytes name, bool z, uint[] data) {}
+ }
+
+
+Thus for our `Foo` example if we wanted to call `baz` with the parameters `69` and `true`, we would pass 68 bytes total, which can be broken down into:
+
+- `0xcdcd77c0`: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature `baz(uint32,bool)`.
+- `0x0000000000000000000000000000000000000000000000000000000000000045`: the first parameter, a uint32 value `69` padded to 32 bytes
+- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter - boolean `true`, padded to 32 bytes
+
+In total::
+
+ 0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
+
+It returns a single `bool`. If, for example, it were to return `false`, its output would be the single byte array `0x0000000000000000000000000000000000000000000000000000000000000000`, a single bool.
+
+If we wanted to call `bar` with the argument `["abc", "def"]`, we would pass 68 bytes total, broken down into:
+
+- `0xfce353f6`: the Method ID. This is derived from the signature `bar(bytes3[2])`.
+- `0x6162630000000000000000000000000000000000000000000000000000000000`: the first part of the first parameter, a `bytes3` value `"abc"` (left-aligned).
+- `0x6465660000000000000000000000000000000000000000000000000000000000`: the second part of the first parameter, a `bytes3` value `"def"` (left-aligned).
+
+In total::
+
+ 0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
+
+If we wanted to call `sam` with the arguments `"dave"`, `true` and `[1,2,3]`, we would pass 292 bytes total, broken down into:
+- `0xa5643bf2`: the Method ID. This is derived from the signature `sam(bytes,bool,uint256[])`. Note that `uint` is replaced with its canonical representation `uint256`.
+- `0x0000000000000000000000000000000000000000000000000000000000000060`: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, `0x60`.
+- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter: boolean true.
+- `0x00000000000000000000000000000000000000000000000000000000000000a0`: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case, `0xa0`.
+- `0x0000000000000000000000000000000000000000000000000000000000000004`: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4.
+- `0x6461766500000000000000000000000000000000000000000000000000000000`: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of `"dave"`, padded on the right to 32 bytes.
+- `0x0000000000000000000000000000000000000000000000000000000000000003`: the data part of the third argument, it starts with the length of the array in elements, in this case, 3.
+- `0x0000000000000000000000000000000000000000000000000000000000000001`: the first entry of the third parameter.
+- `0x0000000000000000000000000000000000000000000000000000000000000002`: the second entry of the third parameter.
+- `0x0000000000000000000000000000000000000000000000000000000000000003`: the third entry of the third parameter.
+
+In total::
+
+ 0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
+
+Use of Dynamic Types
+====================
+
+A call to a function with the signature `f(uint,uint32[],bytes10,bytes)` with values `(0x123, [0x456, 0x789], "1234567890", "Hello, world!")` is encoded in the following way:
+
+We take the first four bytes of `sha3("f(uint256,uint32[],bytes10,bytes)")`, i.e. `0x8be65246`.
+Then we encode the head parts of all four arguments. For the static types `uint256` and `bytes10`, these are directly the values we want to pass, whereas for the dynamic types `uint32[]` and `bytes`, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
+
+ - `0x0000000000000000000000000000000000000000000000000000000000000123` (`0x123` padded to 32 bytes)
+ - `0x0000000000000000000000000000000000000000000000000000000000000080` (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part)
+ - `0x3132333435363738393000000000000000000000000000000000000000000000` (`"1234567890"` padded to 32 bytes on the right)
+ - `0x00000000000000000000000000000000000000000000000000000000000000e0` (offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4\*32 + 3\*32 (see below))
+
+After this, the data part of the first dynamic argument, `[0x456, 0x789]` follows:
+
+ - `0x0000000000000000000000000000000000000000000000000000000000000002` (number of elements of the array, 2)
+ - `0x0000000000000000000000000000000000000000000000000000000000000456` (first element)
+ - `0x0000000000000000000000000000000000000000000000000000000000000789` (second element)
+
+Finally, we encode the data part of the second dynamic argument, `"Hello, world!"`:
+
+ - `0x000000000000000000000000000000000000000000000000000000000000000d` (number of elements (bytes in this case): 13)
+ - `0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000` (`"Hello, world!"` padded to 32 bytes on the right)
+
+All together, the encoding is (newline after function selector and each 32-bytes for clarity):
+
+::
+
+ 0x8be65246
+ 0000000000000000000000000000000000000000000000000000000000000123
+ 0000000000000000000000000000000000000000000000000000000000000080
+ 3132333435363738393000000000000000000000000000000000000000000000
+ 00000000000000000000000000000000000000000000000000000000000000e0
+ 0000000000000000000000000000000000000000000000000000000000000002
+ 0000000000000000000000000000000000000000000000000000000000000456
+ 0000000000000000000000000000000000000000000000000000000000000789
+ 000000000000000000000000000000000000000000000000000000000000000d
+ 48656c6c6f2c20776f726c642100000000000000000000000000000000000000
+
+Events
+======
+
+Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract's address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure.
+
+Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the event signature to form the topics of the log entry. Those which as not indexed form the byte array of the event.
+
+In effect, a log entry using this ABI is described as:
+
+- `address`: the address of the contract (intrinsically provided by Ethereum);
+- `topics[0]`: `keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")` (`canonical_type_of` is a function that simply returns the canonical type of a given argument, e.g. for `uint indexed foo`, it would return `uint256`). If the event is declared as `anonymous` the `topics[0]` is not generated;
+- `topics[n]`: `EVENT_INDEXED_ARGS[n - 1]` (`EVENT_INDEXED_ARGS` is the series of `EVENT_ARGS` that are indexed);
+- `data`: `abi_serialise(EVENT_NON_INDEXED_ARGS)` (`EVENT_NON_INDEXED_ARGS` is the series of `EVENT_ARGS` that are not indexed, `abi_serialise` is the ABI serialisation function used for returning a series of typed values from a function, as described above).
+
+JSON
+====
+
+The JSON format for a contract's interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields:
+
+- `type`: `"function"`, `"constructor"`, or `"fallback"` (the :ref:`unnamed "default" function <fallback-function>`);
+- `name`: the name of the function;
+- `inputs`: an array of objects, each of which contains:
+ * `name`: the name of the parameter;
+ * `type`: the canonical type of the parameter.
+- `outputs`: an array of objects similar to `inputs`, can be omitted if function doesn't return anything;
+- `constant`: `true` if function is :ref:`specified to not modify blockchain state <constant-functions>`);
+- `payable`: `true` if function accepts ether, defaults to `false`.
+
+`type` can be omitted, defaulting to `"function"`.
+
+Constructor and fallback function never have `name` or `outputs`. Fallback function doesn't have `inputs` either.
+
+Sending non-zero ether to non-payable function will throw. Don't do it.
+
+An event description is a JSON object with fairly similar fields:
+
+- `type`: always `"event"`
+- `name`: the name of the event;
+- `inputs`: an array of objects, each of which contains:
+ * `name`: the name of the parameter;
+ * `type`: the canonical type of the parameter.
+ * `indexed`: `true` if the field is part of the log's topics, `false` if it one of the log's data segment.
+- `anonymous`: `true` if the event was declared as `anonymous`.
+
+For example,
+
+::
+
+ contract Test {
+ function Test(){ b = 0x12345678901234567890123456789012; }
+ event Event(uint indexed a, bytes32 b)
+ event Event2(uint indexed a, bytes32 b)
+ function foo(uint a) { Event(a, b); }
+ bytes32 b;
+ }
+
+would result in the JSON:
+
+.. code:: json
+
+ [{
+ "type":"event",
+ "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
+ "name":"Event"
+ }, {
+ "type":"event",
+ "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
+ "name":"Event2"
+ }, {
+ "type":"event",
+ "inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
+ "name":"Event2"
+ }, {
+ "type":"function",
+ "inputs": [{"name":"a","type":"uint256"}],
+ "name":"foo",
+ "outputs": []
+ }]
diff --git a/docs/assembly.rst b/docs/assembly.rst
new file mode 100644
index 00000000..00601371
--- /dev/null
+++ b/docs/assembly.rst
@@ -0,0 +1,1022 @@
+#################
+Solidity Assembly
+#################
+
+.. index:: ! assembly, ! asm, ! evmasm
+
+Solidity defines an assembly language that can also be used without Solidity.
+This assembly language can also be used as "inline assembly" inside Solidity
+source code. We start with describing how to use inline assembly and how it
+differs from standalone assembly and then specify assembly itself.
+
+TODO: Write about how scoping rules of inline assembly are a bit different
+and the complications that arise when for example using internal functions
+of libraries. Furthermore, write about the symbols defined by the compiler.
+
+.. _inline-assembly:
+
+Inline Assembly
+===============
+
+For more fine-grained control especially in order to enhance the language by writing libraries,
+it is possible to interleave Solidity statements with inline assembly in a language close
+to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is
+often hard to address the correct stack slot and provide arguments to opcodes at the correct
+point on the stack. Solidity's inline assembly tries to facilitate that and other issues
+arising when writing manual assembly by the following features:
+
+* functional-style opcodes: ``mul(1, add(2, 3))`` instead of ``push1 3 push1 2 add push1 1 mul``
+* assembly-local variables: ``let x := add(2, 3) let y := mload(0x40) x := add(x, y)``
+* access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }``
+* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))``
+* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }``
+* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }``
+* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }``
+
+We now want to describe the inline assembly language in detail.
+
+.. warning::
+ Inline assembly is a way to access the Ethereum Virtual Machine
+ at a low level. This discards several important safety
+ features of Solidity.
+
+Example
+-------
+
+The following example provides library code to access the code of another contract and
+load it into a ``bytes`` variable. This is not possible at all with "plain Solidity" and the
+idea is that assembly libraries will be used to enhance the language in such ways.
+
+.. code::
+
+ pragma solidity ^0.4.0;
+
+ library GetCode {
+ function at(address _addr) returns (bytes o_code) {
+ assembly {
+ // retrieve the size of the code, this needs assembly
+ let size := extcodesize(_addr)
+ // allocate output byte array - this could also be done without assembly
+ // by using o_code = new bytes(size)
+ o_code := mload(0x40)
+ // new "memory end" including padding
+ mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
+ // store length in memory
+ mstore(o_code, size)
+ // actually retrieve the code, this needs assembly
+ extcodecopy(_addr, add(o_code, 0x20), 0, size)
+ }
+ }
+ }
+
+Inline assembly could also be beneficial in cases where the optimizer fails to produce
+efficient code. Please be aware that assembly is much more difficult to write because
+the compiler does not perform checks, so you should use it for complex things only if
+you really know what you are doing.
+
+.. code::
+
+ pragma solidity ^0.4.0;
+
+ library VectorSum {
+ // This function is less efficient because the optimizer currently fails to
+ // remove the bounds checks in array access.
+ function sumSolidity(uint[] _data) returns (uint o_sum) {
+ for (uint i = 0; i < _data.length; ++i)
+ o_sum += _data[i];
+ }
+
+ // We know that we only access the array in bounds, so we can avoid the check.
+ // 0x20 needs to be added to an array because the first slot contains the
+ // array length.
+ function sumAsm(uint[] _data) returns (uint o_sum) {
+ for (uint i = 0; i < _data.length; ++i) {
+ assembly {
+ o_sum := mload(add(add(_data, 0x20), mul(i, 0x20)))
+ }
+ }
+ }
+ }
+
+
+Syntax
+------
+
+Assembly parses comments, literals and identifiers exactly as Solidity, so you can use the
+usual ``//`` and ``/* */`` comments. Inline assembly is marked by ``assembly { ... }`` and inside
+these curly braces, the following can be used (see the later sections for more details)
+
+ - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters)
+ - opcodes (in "instruction style"), e.g. ``mload sload dup1 sstore``, for a list see below
+ - opcodes in functional style, e.g. ``add(1, mlod(0))``
+ - labels, e.g. ``name:``
+ - variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)``
+ - identifiers (labels or assembly-local variables and externals if used as inline assembly), e.g. ``jump(name)``, ``3 x add``
+ - assignments (in "instruction style"), e.g. ``3 =: x``
+ - assignments in functional style, e.g. ``x := add(y, 3)``
+ - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }``
+
+Opcodes
+-------
+
+This document does not want to be a full description of the Ethereum virtual machine, but the
+following list can be used as a reference of its opcodes.
+
+If an opcode takes arguments (always from the top of the stack), they are given in parentheses.
+Note that the order of arguments can be seen to be reversed in non-functional style (explained below).
+Opcodes marked with ``-`` do not push an item onto the stack, those marked with ``*`` are
+special and all others push exactly one item onte the stack.
+
+In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to
+(excluding) position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``.
+
+The opcodes ``pushi`` and ``jumpdest`` cannot be used directly.
+
+In the grammar, opcodes are represented as pre-defined identifiers.
+
++-------------------------+------+-----------------------------------------------------------------+
+| stop + `-` | stop execution, identical to return(0,0) |
++-------------------------+------+-----------------------------------------------------------------+
+| add(x, y) | | x + y |
++-------------------------+------+-----------------------------------------------------------------+
+| sub(x, y) | | x - y |
++-------------------------+------+-----------------------------------------------------------------+
+| mul(x, y) | | x * y |
++-------------------------+------+-----------------------------------------------------------------+
+| div(x, y) | | x / y |
++-------------------------+------+-----------------------------------------------------------------+
+| sdiv(x, y) | | x / y, for signed numbers in two's complement |
++-------------------------+------+-----------------------------------------------------------------+
+| mod(x, y) | | x % y |
++-------------------------+------+-----------------------------------------------------------------+
+| smod(x, y) | | x % y, for signed numbers in two's complement |
++-------------------------+------+-----------------------------------------------------------------+
+| exp(x, y) | | x to the power of y |
++-------------------------+------+-----------------------------------------------------------------+
+| not(x) | | ~x, every bit of x is negated |
++-------------------------+------+-----------------------------------------------------------------+
+| lt(x, y) | | 1 if x < y, 0 otherwise |
++-------------------------+------+-----------------------------------------------------------------+
+| gt(x, y) | | 1 if x > y, 0 otherwise |
++-------------------------+------+-----------------------------------------------------------------+
+| slt(x, y) | | 1 if x < y, 0 otherwise, for signed numbers in two's complement |
++-------------------------+------+-----------------------------------------------------------------+
+| sgt(x, y) | | 1 if x > y, 0 otherwise, for signed numbers in two's complement |
++-------------------------+------+-----------------------------------------------------------------+
+| eq(x, y) | | 1 if x == y, 0 otherwise |
++-------------------------+------+-----------------------------------------------------------------+
+| iszero(x) | | 1 if x == 0, 0 otherwise |
++-------------------------+------+-----------------------------------------------------------------+
+| and(x, y) | | bitwise and of x and y |
++-------------------------+------+-----------------------------------------------------------------+
+| or(x, y) | | bitwise or of x and y |
++-------------------------+------+-----------------------------------------------------------------+
+| xor(x, y) | | bitwise xor of x and y |
++-------------------------+------+-----------------------------------------------------------------+
+| byte(n, x) | | nth byte of x, where the most significant byte is the 0th byte |
++-------------------------+------+-----------------------------------------------------------------+
+| addmod(x, y, m) | | (x + y) % m with arbitrary precision arithmetics |
++-------------------------+------+-----------------------------------------------------------------+
+| mulmod(x, y, m) | | (x * y) % m with arbitrary precision arithmetics |
++-------------------------+------+-----------------------------------------------------------------+
+| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant |
++-------------------------+------+-----------------------------------------------------------------+
+| keccak256(p, n) | | keccak(mem[p...(p+n))) |
++-------------------------+------+-----------------------------------------------------------------+
+| sha3(p, n) | | keccak(mem[p...(p+n))) |
++-------------------------+------+-----------------------------------------------------------------+
+| jump(label) | `-` | jump to label / code position |
++-------------------------+------+-----------------------------------------------------------------+
+| jumpi(label, cond) | `-` | jump to label if cond is nonzero |
++-------------------------+------+-----------------------------------------------------------------+
+| pc | | current position in code |
++-------------------------+------+-----------------------------------------------------------------+
+| pop(x) | `-` | remove the element pushed by x |
++-------------------------+------+-----------------------------------------------------------------+
+| dup1 ... dup16 | | copy ith stack slot to the top (counting from top) |
++-------------------------+------+-----------------------------------------------------------------+
+| swap1 ... swap16 | `*` | swap topmost and ith stack slot below it |
++-------------------------+------+-----------------------------------------------------------------+
+| mload(p) | | mem[p..(p+32)) |
++-------------------------+------+-----------------------------------------------------------------+
+| mstore(p, v) | `-` | mem[p..(p+32)) := v |
++-------------------------+------+-----------------------------------------------------------------+
+| mstore8(p, v) | `-` | mem[p] := v & 0xff - only modifies a single byte |
++-------------------------+------+-----------------------------------------------------------------+
+| sload(p) | | storage[p] |
++-------------------------+------+-----------------------------------------------------------------+
+| sstore(p, v) | `-` | storage[p] := v |
++-------------------------+------+-----------------------------------------------------------------+
+| msize | | size of memory, i.e. largest accessed memory index |
++-------------------------+------+-----------------------------------------------------------------+
+| gas | | gas still available to execution |
++-------------------------+------+-----------------------------------------------------------------+
+| address | | address of the current contract / execution context |
++-------------------------+------+-----------------------------------------------------------------+
+| balance(a) | | wei balance at address a |
++-------------------------+------+-----------------------------------------------------------------+
+| caller | | call sender (excluding delegatecall) |
++-------------------------+------+-----------------------------------------------------------------+
+| callvalue | | wei sent together with the current call |
++-------------------------+------+-----------------------------------------------------------------+
+| calldataload(p) | | call data starting from position p (32 bytes) |
++-------------------------+------+-----------------------------------------------------------------+
+| calldatasize | | size of call data in bytes |
++-------------------------+------+-----------------------------------------------------------------+
+| calldatacopy(t, f, s) | `-` | copy s bytes from calldata at position f to mem at position t |
++-------------------------+------+-----------------------------------------------------------------+
+| codesize | | size of the code of the current contract / execution context |
++-------------------------+------+-----------------------------------------------------------------+
+| codecopy(t, f, s) | `-` | copy s bytes from code at position f to mem at position t |
++-------------------------+------+-----------------------------------------------------------------+
+| extcodesize(a) | | size of the code at address a |
++-------------------------+------+-----------------------------------------------------------------+
+| extcodecopy(a, t, f, s) | `-` | like codecopy(t, f, s) but take code at address a |
++-------------------------+------+-----------------------------------------------------------------+
+| returndatasize | | size of the last returndata |
++-------------------------+------+-----------------------------------------------------------------+
+| returndatacopy(t, f, s) | `-` | copy s bytes from returndata at position f to mem at position t |
++-------------------------+------+-----------------------------------------------------------------+
+| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei |
+| | | and return the new address |
++-------------------------+------+-----------------------------------------------------------------+
+| create2(v, n, p, s) | | create new contract with code mem[p..(p+s)) at address |
+| | | keccak256(<address> . n . keccak256(mem[p..(p+s))) and send v |
+| | | wei and return the new address |
++-------------------------+------+-----------------------------------------------------------------+
+| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)) |
+| insize, out, outsize) | | providing g gas and v wei and output area |
+| | | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) |
+| | | and 1 on success |
++-------------------------+------+-----------------------------------------------------------------+
+| callcode(g, a, v, in, | | identical to `call` but only use the code from a and stay |
+| insize, out, outsize) | | in the context of the current contract otherwise |
++-------------------------+------+-----------------------------------------------------------------+
+| delegatecall(g, a, in, | | identical to `callcode` but also keep ``caller`` |
+| insize, out, outsize) | | and ``callvalue`` |
++-------------------------+------+-----------------------------------------------------------------+
+| staticcall(g, a, in, | | identical to `call(g, a, 0, in, insize, out, outsize)` but do |
+| insize, out, outsize) | | not allow state modifications |
++-------------------------+------+-----------------------------------------------------------------+
+| return(p, s) | `-` | end execution, return data mem[p..(p+s)) |
++-------------------------+------+-----------------------------------------------------------------+
+| revert(p, s) | `-` | end execution, revert state changes, return data mem[p..(p+s)) |
++-------------------------+------+-----------------------------------------------------------------+
+| selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a |
++-------------------------+------+-----------------------------------------------------------------+
+| invalid | `-` | end execution with invalid instruction |
++-------------------------+------+-----------------------------------------------------------------+
+| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) |
++-------------------------+------+-----------------------------------------------------------------+
+| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) |
++-------------------------+------+-----------------------------------------------------------------+
+| log2(p, s, t1, t2) | `-` | log with topics t1, t2 and data mem[p..(p+s)) |
++-------------------------+------+-----------------------------------------------------------------+
+| log3(p, s, t1, t2, t3) | `-` | log with topics t1, t2, t3 and data mem[p..(p+s)) |
++-------------------------+------+-----------------------------------------------------------------+
+| log4(p, s, t1, t2, t3, | `-` | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) |
+| t4) | | |
++-------------------------+------+-----------------------------------------------------------------+
+| origin | | transaction sender |
++-------------------------+------+-----------------------------------------------------------------+
+| gasprice | | gas price of the transaction |
++-------------------------+------+-----------------------------------------------------------------+
+| blockhash(b) | | hash of block nr b - only for last 256 blocks excluding current |
++-------------------------+------+-----------------------------------------------------------------+
+| coinbase | | current mining beneficiary |
++-------------------------+------+-----------------------------------------------------------------+
+| timestamp | | timestamp of the current block in seconds since the epoch |
++-------------------------+------+-----------------------------------------------------------------+
+| number | | current block number |
++-------------------------+------+-----------------------------------------------------------------+
+| difficulty | | difficulty of the current block |
++-------------------------+------+-----------------------------------------------------------------+
+| gaslimit | | block gas limit of the current block |
++-------------------------+------+-----------------------------------------------------------------+
+
+Literals
+--------
+
+You can use integer constants by typing them in decimal or hexadecimal notation and an
+appropriate ``PUSHi`` instruction will automatically be generated. The following creates code
+to add 2 and 3 resulting in 5 and then computes the bitwise and with the string "abc".
+Strings are stored left-aligned and cannot be longer than 32 bytes.
+
+.. code::
+
+ assembly { 2 3 add "abc" and }
+
+Functional Style
+-----------------
+
+You can type opcode after opcode in the same way they will end up in bytecode. For example
+adding ``3`` to the contents in memory at position ``0x80`` would be
+
+.. code::
+
+ 3 0x80 mload add 0x80 mstore
+
+As it is often hard to see what the actual arguments for certain opcodes are,
+Solidity inline assembly also provides a "functional style" notation where the same code
+would be written as follows
+
+.. code::
+
+ mstore(0x80, add(mload(0x80), 3))
+
+Functional style expressions cannot use instructional style internally, i.e.
+``1 2 mstore(0x80, add)`` is not valid assembly, it has to be written as
+``mstore(0x80, add(2, 1))``. For opcodes that do not take arguments, the
+parentheses can be omitted.
+
+Note that the order of arguments is reversed in functional-style as opposed to the instruction-style
+way. If you use functional-style, the first argument will end up on the stack top.
+
+
+Access to External Variables and Functions
+------------------------------------------
+
+Solidity variables and other identifiers can be accessed by simply using their name.
+For memory variables, this will push the address and not the value onto the
+stack. Storage variables are different: Values in storage might not occupy a
+full storage slot, so their "address" is composed of a slot and a byte-offset
+inside that slot. To retrieve the slot pointed to by the variable ``x``, you
+used ``x_slot`` and to retrieve the byte-offset you used ``x_offset``.
+
+In assignments (see below), we can even use local Solidity variables to assign to.
+
+Functions external to inline assembly can also be accessed: The assembly will
+push their entry label (with virtual function resolution applied). The calling semantics
+in solidity are:
+
+ - the caller pushes return label, arg1, arg2, ..., argn
+ - the call returns with ret1, ret2, ..., retm
+
+This feature is still a bit cumbersome to use, because the stack offset essentially
+changes during the call, and thus references to local variables will be wrong.
+
+.. code::
+
+ pragma solidity ^0.4.11;
+
+ contract C {
+ uint b;
+ function f(uint x) returns (uint r) {
+ assembly {
+ r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero
+ }
+ }
+ }
+
+Labels
+------
+
+Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses
+which can change easily. Solidity inline assembly provides labels to make the use of
+jumps easier. Note that labels are a low-level feature and it is possible to write
+efficient assembly without labels, just using assembly functions, loops and switch instructions
+(see below). The following code computes an element in the Fibonacci series.
+
+.. code::
+
+ {
+ let n := calldataload(4)
+ let a := 1
+ let b := a
+ loop:
+ jumpi(loopend, eq(n, 0))
+ a add swap1
+ n := sub(n, 1)
+ jump(loop)
+ loopend:
+ mstore(0, a)
+ return(0, 0x20)
+ }
+
+Please note that automatically accessing stack variables can only work if the
+assembler knows the current stack height. This fails to work if the jump source
+and target have different stack heights. It is still fine to use such jumps, but
+you should just not access any stack variables (even assembly variables) in that case.
+
+Furthermore, the stack height analyser goes through the code opcode by opcode
+(and not according to control flow), so in the following case, the assembler
+will have a wrong impression about the stack height at label ``two``:
+
+.. code::
+
+ {
+ let x := 8
+ jump(two)
+ one:
+ // Here the stack height is 2 (because we pushed x and 7),
+ // but the assembler thinks it is 1 because it reads
+ // from top to bottom.
+ // Accessing the stack variable x here will lead to errors.
+ x := 9
+ jump(three)
+ two:
+ 7 // push something onto the stack
+ jump(one)
+ three:
+ }
+
+This problem can be fixed by manually adjusting the stack height for the
+assembler - you can provide a stack height delta that is added
+to the stack height just prior to the label.
+Note that you will not have to care about these things if you just use
+loops and assembly-level functions.
+
+As an example how this can be done in extreme cases, please see the following.
+
+.. code::
+
+ {
+ let x := 8
+ jump(two)
+ 0 // This code is unreachable but will adjust the stack height correctly
+ one:
+ x := 9 // Now x can be accessed properly.
+ jump(three)
+ pop // Similar negative correction.
+ two:
+ 7 // push something onto the stack
+ jump(one)
+ three:
+ pop // We have to pop the manually pushed value here again.
+ }
+
+Declaring Assembly-Local Variables
+----------------------------------
+
+You can use the ``let`` keyword to declare variables that are only visible in
+inline assembly and actually only in the current ``{...}``-block. What happens
+is that the ``let`` instruction will create a new stack slot that is reserved
+for the variable and automatically removed again when the end of the block
+is reached. You need to provide an initial value for the variable which can
+be just ``0``, but it can also be a complex functional-style expression.
+
+.. code::
+
+ pragma solidity ^0.4.0;
+
+ contract C {
+ function f(uint x) returns (uint b) {
+ assembly {
+ let v := add(x, 1)
+ mstore(0x80, v)
+ {
+ let y := add(sload(v), 1)
+ b := y
+ } // y is "deallocated" here
+ b := add(b, v)
+ } // v is "deallocated" here
+ }
+ }
+
+
+Assignments
+-----------
+
+Assignments are possible to assembly-local variables and to function-local
+variables. Take care that when you assign to variables that point to
+memory or storage, you will only change the pointer and not the data.
+
+There are two kinds of assignments: functional-style and instruction-style.
+For functional-style assignments (``variable := value``), you need to provide a value in a
+functional-style expression that results in exactly one stack value
+and for instruction-style (``=: variable``), the value is just taken from the stack top.
+For both ways, the colon points to the name of the variable. The assignment
+is performed by replacing the variable's value on the stack by the new value.
+
+.. code::
+
+ assembly {
+ let v := 0 // functional-style assignment as part of variable declaration
+ let g := add(v, 2)
+ sload(10)
+ =: v // instruction style assignment, puts the result of sload(10) into v
+ }
+
+Switch
+------
+
+You can use a switch statement as a very basic version of "if/else".
+It takes the value of an expression and compares it to several constants.
+The branch corresponding to the matching constant is taken. Contrary to the
+error-prone behaviour of some programming languages, control flow does
+not continue from one case to the next. There can be a fallback or default
+case called ``default``.
+
+.. code::
+
+ assembly {
+ let x := 0
+ switch calldataload(4)
+ case 0 {
+ x := calldataload(0x24)
+ }
+ default {
+ x := calldataload(0x44)
+ }
+ sstore(0, div(x, 2))
+ }
+
+The list of cases does not require curly braces, but the body of a
+case does require them.
+
+Loops
+-----
+
+Assembly supports a simple for-style loop. For-style loops have
+a header containing an initializing part, a condition and a post-iteration
+part. The condition has to be a functional-style expression, while
+the other two are blocks. If the initializing part
+declares any variables, the scope of these variables is extended into the
+body (including the condition and the post-iteration part).
+
+The following example computes the sum of an area in memory.
+
+.. code::
+
+ assembly {
+ let x := 0
+ for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } {
+ x := add(x, mload(i))
+ }
+ }
+
+Functions
+---------
+
+Assembly allows the definition of low-level functions. These take their
+arguments (and a return PC) from the stack and also put the results onto the
+stack. Calling a function looks the same way as executing a functional-style
+opcode.
+
+Functions can be defined anywhere and are visible in the block they are
+declared in. Inside a function, you cannot access local variables
+defined outside of that function. There is no explicit ``return``
+statement.
+
+If you call a function that returns multiple values, you have to assign
+them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``.
+
+The following example implements the power function by square-and-multiply.
+
+.. code::
+
+ assembly {
+ function power(base, exponent) -> result {
+ switch exponent
+ case 0 { result := 1 }
+ case 1 { result := base }
+ default {
+ result := power(mul(base, base), div(exponent, 2))
+ switch mod(exponent, 2)
+ case 1 { result := mul(base, result) }
+ }
+ }
+ }
+
+Things to Avoid
+---------------
+
+Inline assembly might have a quite high-level look, but it actually is extremely
+low-level. Function calls, loops and switches are converted by simple
+rewriting rules and after that, the only thing the assembler does for you is re-arranging
+functional-style opcodes, managing jump labels, counting stack height for
+variable access and removing stack slots for assembly-local variables when the end
+of their block is reached. Especially for those two last cases, it is important
+to know that the assembler only counts stack height from top to bottom, not
+necessarily following control flow. Furthermore, operations like swap will only
+swap the contents of the stack but not the location of variables.
+
+Conventions in Solidity
+-----------------------
+
+In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits,
+e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just
+treat them as 256-bit numbers and the higher-order bits are only cleaned at the
+point where it is necessary, i.e. just shortly before they are written to memory
+or before comparisons are performed. This means that if you access such a variable
+from within inline assembly, you might have to manually clean the higher order bits
+first.
+
+Solidity manages memory in a very simple way: There is a "free memory pointer"
+at position ``0x40`` in memory. If you want to allocate memory, just use the memory
+from that point on and update the pointer accordingly.
+
+Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is
+even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory
+arrays are pointers to memory arrays. The length of a dynamic array is stored at the
+first slot of the array and then only the array elements follow.
+
+.. warning::
+ Statically-sized memory arrays do not have a length field, but it will be added soon
+ to allow better convertibility between statically- and dynamically-sized arrays, so
+ please do not rely on that.
+
+
+Standalone Assembly
+===================
+
+The assembly language described as inline assembly above can also be used
+standalone and in fact, the plan is to use it as an intermediate language
+for the Solidity compiler. In this form, it tries to achieve several goals:
+
+1. Programs written in it should be readable, even if the code is generated by a compiler from Solidity.
+2. The translation from assembly to bytecode should contain as few "surprises" as possible.
+3. Control flow should be easy to detect to help in formal verification and optimization.
+
+In order to achieve the first and last goal, assembly provides high-level constructs
+like ``for`` loops, ``switch`` statements and function calls. It should be possible
+to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``,
+``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow
+and the last two obfuscate control flow. Furthermore, functional statements of
+the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like
+``7 y x add mul`` because in the first form, it is much easier to see which
+operand is used for which opcode.
+
+The second goal is achieved by introducing a desugaring phase that only removes
+the higher level constructs in a very regular way and still allows inspecting
+the generated low-level assembly code. The only non-local operation performed
+by the assembler is name lookup of user-defined identifiers (functions, variables, ...),
+which follow very simple and regular scoping rules and cleanup of local variables from the stack.
+
+Scoping: An identifier that is declared (label, variable, function, assembly)
+is only visible in the block where it was declared (including nested blocks
+inside the current block). It is not legal to access local variables across
+function borders, even if they would be in scope. Shadowing is not allowed.
+Local variables cannot be accessed before they were declared, but labels,
+functions and assemblies can. Assemblies are special blocks that are used
+for e.g. returning runtime code or creating contracts. No identifier from an
+outer assembly is visible in a sub-assembly.
+
+If control flow passes over the end of a block, pop instructions are inserted
+that match the number of local variables declared in that block.
+Whenever a local variable is referenced, the code generator needs
+to know its current relative position in the stack and thus it needs to
+keep track of the current so-called stack height. Since all local variables
+are removed at the end of a block, the stack height before and after the block
+should be the same. If this is not the case, a warning is issued.
+
+Why do we use higher-level constructs like ``switch``, ``for`` and functions:
+
+Using ``switch``, ``for`` and functions, it should be possible to write
+complex code without using ``jump`` or ``jumpi`` manually. This makes it much
+easier to analyze the control flow, which allows for improved formal
+verification and optimization.
+
+Furthermore, if manual jumps are allowed, computing the stack height is rather complicated.
+The position of all local variables on the stack needs to be known, otherwise
+neither references to local variables nor removing local variables automatically
+from the stack at the end of a block will work properly. The desugaring
+mechanism correctly inserts operations at unreachable blocks that adjust the
+stack height properly in case of jumps that do not have a continuing control flow.
+
+Example:
+
+We will follow an example compilation from Solidity to desugared assembly.
+We consider the runtime bytecode of the following Solidity program::
+
+ contract C {
+ function f(uint x) returns (uint y) {
+ y = 1;
+ for (uint i = 0; i < x; i++)
+ y = 2 * y;
+ }
+ }
+
+The following assembly will be generated::
+
+ {
+ mstore(0x40, 0x60) // store the "free memory pointer"
+ // function dispatcher
+ switch div(calldataload(0), exp(2, 226))
+ case 0xb3de648b {
+ let (r) = f(calldataload(4))
+ let ret := $allocate(0x20)
+ mstore(ret, r)
+ return(ret, 0x20)
+ }
+ default { revert(0, 0) }
+ // memory allocator
+ function $allocate(size) -> pos {
+ pos := mload(0x40)
+ mstore(0x40, add(pos, size))
+ }
+ // the contract function
+ function f(x) -> y {
+ y := 1
+ for { let i := 0 } lt(i, x) { i := add(i, 1) } {
+ y := mul(2, y)
+ }
+ }
+ }
+
+After the desugaring phase it looks as follows::
+
+ {
+ mstore(0x40, 0x60)
+ {
+ let $0 := div(calldataload(0), exp(2, 226))
+ jumpi($case1, eq($0, 0xb3de648b))
+ jump($caseDefault)
+ $case1:
+ {
+ // the function call - we put return label and arguments on the stack
+ $ret1 calldataload(4) jump(f)
+ // This is unreachable code. Opcodes are added that mirror the
+ // effect of the function on the stack height: Arguments are
+ // removed and return values are introduced.
+ pop pop
+ let r := 0
+ $ret1: // the actual return point
+ $ret2 0x20 jump($allocate)
+ pop pop let ret := 0
+ $ret2:
+ mstore(ret, r)
+ return(ret, 0x20)
+ // although it is useless, the jump is automatically inserted,
+ // since the desugaring process is a purely syntactic operation that
+ // does not analyze control-flow
+ jump($endswitch)
+ }
+ $caseDefault:
+ {
+ revert(0, 0)
+ jump($endswitch)
+ }
+ $endswitch:
+ }
+ jump($afterFunction)
+ allocate:
+ {
+ // we jump over the unreachable code that introduces the function arguments
+ jump($start)
+ let $retpos := 0 let size := 0
+ $start:
+ // output variables live in the same scope as the arguments and is
+ // actually allocated.
+ let pos := 0
+ {
+ pos := mload(0x40)
+ mstore(0x40, add(pos, size))
+ }
+ // This code replaces the arguments by the return values and jumps back.
+ swap1 pop swap1 jump
+ // Again unreachable code that corrects stack height.
+ 0 0
+ }
+ f:
+ {
+ jump($start)
+ let $retpos := 0 let x := 0
+ $start:
+ let y := 0
+ {
+ let i := 0
+ $for_begin:
+ jumpi($for_end, iszero(lt(i, x)))
+ {
+ y := mul(2, y)
+ }
+ $for_continue:
+ { i := add(i, 1) }
+ jump($for_begin)
+ $for_end:
+ } // Here, a pop instruction will be inserted for i
+ swap1 pop swap1 jump
+ 0 0
+ }
+ $afterFunction:
+ stop
+ }
+
+
+Assembly happens in four stages:
+
+1. Parsing
+2. Desugaring (removes switch, for and functions)
+3. Opcode stream generation
+4. Bytecode generation
+
+We will specify steps one to three in a pseudo-formal way. More formal
+specifications will follow.
+
+
+Parsing / Grammar
+-----------------
+
+The tasks of the parser are the following:
+
+- Turn the byte stream into a token stream, discarding C++-style comments
+ (a special comment exists for source references, but we will not explain it here).
+- Turn the token stream into an AST according to the grammar below
+- Register identifiers with the block they are defined in (annotation to the
+ AST node) and note from which point on, variables can be accessed.
+
+The assembly lexer follows the one defined by Solidity itself.
+
+Whitespace is used to delimit tokens and it consists of the characters
+Space, Tab and Linefeed. Comments are regular JavaScript/C++ comments and
+are interpreted in the same way as Whitespace.
+
+Grammar::
+
+ AssemblyBlock = '{' AssemblyItem* '}'
+ AssemblyItem =
+ Identifier |
+ AssemblyBlock |
+ FunctionalAssemblyExpression |
+ AssemblyLocalDefinition |
+ FunctionalAssemblyAssignment |
+ AssemblyAssignment |
+ LabelDefinition |
+ AssemblySwitch |
+ AssemblyFunctionDefinition |
+ AssemblyFor |
+ 'break' | 'continue' |
+ SubAssembly | 'dataSize' '(' Identifier ')' |
+ LinkerSymbol |
+ 'errorLabel' | 'bytecodeSize' |
+ NumberLiteral | StringLiteral | HexLiteral
+ Identifier = [a-zA-Z_$] [a-zA-Z_0-9]*
+ FunctionalAssemblyExpression = Identifier '(' ( AssemblyItem ( ',' AssemblyItem )* )? ')'
+ AssemblyLocalDefinition = 'let' IdentifierOrList ':=' FunctionalAssemblyExpression
+ FunctionalAssemblyAssignment = IdentifierOrList ':=' FunctionalAssemblyExpression
+ IdentifierOrList = Identifier | '(' IdentifierList ')'
+ IdentifierList = Identifier ( ',' Identifier)*
+ AssemblyAssignment = '=:' Identifier
+ LabelDefinition = Identifier ':'
+ AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
+ ( 'default' AssemblyBlock )?
+ AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock
+ AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
+ ( '->' '(' IdentifierList ')' )? AssemblyBlock
+ AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression)
+ FunctionalAssemblyExpression ( AssemblyBlock | FunctionalAssemblyExpression) AssemblyBlock
+ SubAssembly = 'assembly' Identifier AssemblyBlock
+ LinkerSymbol = 'linkerSymbol' '(' StringLiteral ')'
+ NumberLiteral = HexNumber | DecimalNumber
+ HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
+ StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
+ HexNumber = '0x' [0-9a-fA-F]+
+ DecimalNumber = [0-9]+
+
+
+Desugaring
+----------
+
+An AST transformation removes for, switch and function constructs. The result
+is still parseable by the same parser, but it will not use certain constructs.
+If jumpdests are added that are only jumped to and not continued at, information
+about the stack content is added, unless no local variables of outer scopes are
+accessed or the stack height is the same as for the previous instruction.
+
+Pseudocode::
+
+ desugar item: AST -> AST =
+ match item {
+ AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) ->
+ <name>:
+ {
+ jump($<name>_start)
+ let $retPC := 0 let argn := 0 ... let arg1 := 0
+ $<name>_start:
+ let ret1 := 0 ... let retm := 0
+ { desugar(body) }
+ swap and pop items so that only ret1, ... retm, $retPC are left on the stack
+ jump
+ 0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC
+ }
+ AssemblyFor('for' { init } condition post body) ->
+ {
+ init // cannot be its own block because we want variable scope to extend into the body
+ // find I such that there are no labels $forI_*
+ $forI_begin:
+ jumpi($forI_end, iszero(condition))
+ { body }
+ $forI_continue:
+ { post }
+ jump($forI_begin)
+ $forI_end:
+ }
+ 'break' ->
+ {
+ // find nearest enclosing scope with label $forI_end
+ pop all local variables that are defined at the current point
+ but not at $forI_end
+ jump($forI_end)
+ 0 (as many as variables were removed above)
+ }
+ 'continue' ->
+ {
+ // find nearest enclosing scope with label $forI_continue
+ pop all local variables that are defined at the current point
+ but not at $forI_continue
+ jump($forI_continue)
+ 0 (as many as variables were removed above)
+ }
+ AssemblySwitch(switch condition cases ( default: defaultBlock )? ) ->
+ {
+ // find I such that there is no $switchI* label or variable
+ let $switchI_value := condition
+ for each of cases match {
+ case val: -> jumpi($switchI_caseJ, eq($switchI_value, val))
+ }
+ if default block present: ->
+ { defaultBlock jump($switchI_end) }
+ for each of cases match {
+ case val: { body } -> $switchI_caseJ: { body jump($switchI_end) }
+ }
+ $switchI_end:
+ }
+ FunctionalAssemblyExpression( identifier(arg1, arg2, ..., argn) ) ->
+ {
+ if identifier is function <name> with n args and m ret values ->
+ {
+ // find I such that $funcallI_* does not exist
+ $funcallI_return argn ... arg2 arg1 jump(<name>)
+ pop (n + 1 times)
+ if the current context is `let (id1, ..., idm) := f(...)` ->
+ let id1 := 0 ... let idm := 0
+ $funcallI_return:
+ else ->
+ 0 (m times)
+ $funcallI_return:
+ turn the functional expression that leads to the function call
+ into a statement stream
+ }
+ else -> desugar(children of node)
+ }
+ default node ->
+ desugar(children of node)
+ }
+
+Opcode Stream Generation
+------------------------
+
+During opcode stream generation, we keep track of the current stack height
+in a counter,
+so that accessing stack variables by name is possible. The stack height is modified with every opcode
+that modifies the stack and with every label that is annotated with a stack
+adjustment. Every time a new
+local variable is introduced, it is registered together with the current
+stack height. If a variable is accessed (either for copying its value or for
+assignment), the appropriate DUP or SWAP instruction is selected depending
+on the difference between the current stack height and the
+stack height at the point the variable was introduced.
+
+Pseudocode::
+
+ codegen item: AST -> opcode_stream =
+ match item {
+ AssemblyBlock({ items }) ->
+ join(codegen(item) for item in items)
+ if last generated opcode has continuing control flow:
+ POP for all local variables registered at the block (including variables
+ introduced by labels)
+ warn if the stack height at this point is not the same as at the start of the block
+ Identifier(id) ->
+ lookup id in the syntactic stack of blocks
+ match type of id
+ Local Variable ->
+ DUPi where i = 1 + stack_height - stack_height_of_identifier(id)
+ Label ->
+ // reference to be resolved during bytecode generation
+ PUSH<bytecode position of label>
+ SubAssembly ->
+ PUSH<bytecode position of subassembly data>
+ FunctionalAssemblyExpression(id ( arguments ) ) ->
+ join(codegen(arg) for arg in arguments.reversed())
+ id (which has to be an opcode, might be a function name later)
+ AssemblyLocalDefinition(let (id1, ..., idn) := expr) ->
+ register identifiers id1, ..., idn as locals in current block at current stack height
+ codegen(expr) - assert that expr returns n items to the stack
+ FunctionalAssemblyAssignment((id1, ..., idn) := expr) ->
+ lookup id1, ..., idn in the syntactic stack of blocks, assert that they are variables
+ codegen(expr)
+ for j = n, ..., i:
+ SWAPi where i = 1 + stack_height - stack_height_of_identifier(idj)
+ POP
+ AssemblyAssignment(=: id) ->
+ look up id in the syntactic stack of blocks, assert that it is a variable
+ SWAPi where i = 1 + stack_height - stack_height_of_identifier(id)
+ POP
+ LabelDefinition(name:) ->
+ JUMPDEST
+ NumberLiteral(num) ->
+ PUSH<num interpreted as decimal and right-aligned>
+ HexLiteral(lit) ->
+ PUSH32<lit interpreted as hex and left-aligned>
+ StringLiteral(lit) ->
+ PUSH32<lit utf-8 encoded and left-aligned>
+ SubAssembly(assembly <name> block) ->
+ append codegen(block) at the end of the code
+ dataSize(<name>) ->
+ assert that <name> is a subassembly ->
+ PUSH32<size of code generated from subassembly <name>>
+ linkerSymbol(<lit>) ->
+ PUSH32<zeros> and append position to linker table
+ }
diff --git a/docs/bugs.json b/docs/bugs.json
new file mode 100644
index 00000000..a0c0e7c4
--- /dev/null
+++ b/docs/bugs.json
@@ -0,0 +1,110 @@
+[
+ {
+ "name": "SkipEmptyStringLiteral",
+ "summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.",
+ "description": "If the empty string literal \"\" is used as an argument in a function call, it is skipped by the encoder. This has the effect that the encoding of all arguments following this is shifted left by 32 bytes and thus the function call data is corrupted.",
+ "fixed": "0.4.12",
+ "severity": "low"
+ },
+ {
+ "name": "ConstantOptimizerSubtraction",
+ "summary": "In some situations, the optimizer replaces certain numbers in the code with routines that compute different numbers.",
+ "description": "The optimizer tries to represent any number in the bytecode by routines that compute them with less gas. For some special numbers, an incorrect routine is generated. This could allow an attacker to e.g. trick victims about a specific amount of ether, or function calls to call different functions (or none at all).",
+ "link": "https://blog.ethereum.org/2017/05/03/solidity-optimizer-bug/",
+ "fixed": "0.4.11",
+ "severity": "low",
+ "conditions": {
+ "optimizer": true
+ }
+ },
+ {
+ "name": "IdentityPrecompileReturnIgnored",
+ "summary": "Failure of the identity precompile was ignored.",
+ "description": "Calls to the identity contract, which is used for copying memory, ignored its return value. On the public chain, calls to the identity precompile can be made in a way that they never fail, but this might be different on private chains.",
+ "severity": "low",
+ "fixed": "0.4.7"
+ },
+ {
+ "name": "OptimizerStateKnowledgeNotResetForJumpdest",
+ "summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
+ "description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was simplified to just use the empty state, but this implementation was not done properly. This bug can cause data corruption.",
+ "severity": "medium",
+ "introduced": "0.4.5",
+ "fixed": "0.4.6",
+ "conditions": {
+ "optimizer": true
+ }
+ },
+ {
+ "name": "HighOrderByteCleanStorage",
+ "summary": "For short types, the high order bytes were not cleaned properly and could overwrite existing data.",
+ "description": "Types shorter than 32 bytes are packed together into the same 32 byte storage slot, but storage writes always write 32 bytes. For some types, the higher order bytes were not cleaned properly, which made it sometimes possible to overwrite a variable in storage when writing to another one.",
+ "link": "https://blog.ethereum.org/2016/11/01/security-alert-solidity-variables-can-overwritten-storage/",
+ "severity": "high",
+ "introduced": "0.1.6",
+ "fixed": "0.4.4"
+ },
+ {
+ "name": "OptimizerStaleKnowledgeAboutSHA3",
+ "summary": "The optimizer did not properly reset its knowledge about SHA3 operations resulting in some hashes (also used for storage variable positions) not being calculated correctly.",
+ "description": "The optimizer performs symbolic execution in order to save re-evaluating expressions whose value is already known. This knowledge was not properly reset across control flow paths and thus the optimizer sometimes thought that the result of a SHA3 operation is already present on the stack. This could result in data corruption by accessing the wrong storage slot.",
+ "severity": "medium",
+ "fixed": "0.4.3",
+ "conditions": {
+ "optimizer": true
+ }
+ },
+ {
+ "name": "LibrariesNotCallableFromPayableFunctions",
+ "summary": "Library functions threw an exception when called from a call that received Ether.",
+ "description": "Library functions are protected against sending them Ether through a call. Since the DELEGATECALL opcode forwards the information about how much Ether was sent with a call, the library function incorrectly assumed that Ether was sent to the library and threw an exception.",
+ "severity": "low",
+ "introduced": "0.4.0",
+ "fixed": "0.4.2"
+ },
+ {
+ "name": "SendFailsForZeroEther",
+ "summary": "The send function did not provide enough gas to the recipient if no Ether was sent with it.",
+ "description": "The recipient of an Ether transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.",
+ "severity": "low",
+ "fixed": "0.4.0"
+ },
+ {
+ "name": "DynamicAllocationInfiniteLoop",
+ "summary": "Dynamic allocation of an empty memory array caused an infinite loop and thus an exception.",
+ "description": "Memory arrays can be created provided a length. If this length is zero, code was generated that did not terminate and thus consumed all gas.",
+ "severity": "low",
+ "fixed": "0.3.6"
+ },
+ {
+ "name": "OptimizerClearStateOnCodePathJoin",
+ "summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
+ "description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was not done correctly. This bug can cause data corruption, but it is probably quite hard to use for targeted attacks.",
+ "severity": "low",
+ "fixed": "0.3.6",
+ "conditions": {
+ "optimizer": true
+ }
+ },
+ {
+ "name": "CleanBytesHigherOrderBits",
+ "summary": "The higher order bits of short bytesNN types were not cleaned before comparison.",
+ "description": "Two variables of type bytesNN were considered different if their higher order bits, which are not part of the actual value, were different. An attacker might use this to reach seemingly unreachable code paths by providing incorrectly formatted input data.",
+ "severity": "medium/high",
+ "fixed": "0.3.3"
+ },
+ {
+ "name": "ArrayAccessCleanHigherOrderBits",
+ "summary": "Access to array elements for arrays of types with less than 32 bytes did not correctly clean the higher order bits, causing corruption in other array elements.",
+ "description": "Multiple elements of an array of values that are shorter than 17 bytes are packed into the same storage slot. Writing to a single element of such an array did not properly clean the higher order bytes and thus could lead to data corruption.",
+ "severity": "medium/high",
+ "fixed": "0.3.1"
+ },
+ {
+ "name": "AncientCompiler",
+ "summary": "This compiler version is ancient and might contain several undocumented or undiscovered bugs.",
+ "description": "The list of bugs is only kept for compiler versions starting from 0.3.0, so older versions might contain undocumented bugs.",
+ "severity": "high",
+ "fixed": "0.3.0"
+ }
+] \ No newline at end of file
diff --git a/docs/bugs.rst b/docs/bugs.rst
new file mode 100644
index 00000000..55771a35
--- /dev/null
+++ b/docs/bugs.rst
@@ -0,0 +1,61 @@
+.. index:: Bugs
+
+.. _known_bugs:
+
+##################
+List of Known Bugs
+##################
+
+Below, you can find a JSON-formatted list of some of the known security-relevant bugs in the
+Solidity compiler. The file itself is hosted in the `Github repository
+<https://github.com/ethereum/solidity/blob/develop/docs/bugs.json>`_.
+The list stretches back as far as version 0.3.0, bugs known to be present only
+in versions preceding that are not listed.
+
+There is another file called `bugs_by_version.json
+<https://github.com/ethereum/solidity/blob/develop/docs/bugs_by_version.json>`_,
+which can be used to check which bugs affect a specific version of the compiler.
+
+Contract source verification tools and also other tools interacting with
+contracts should consult this list according to the following criteria:
+
+ - It is mildly suspicious if a contract was compiled with a nightly
+ compiler version instead of a released version. This list does not keep
+ track of unreleased or nightly versions.
+ - It is also mildly suspicious if a contract was compiled with a version that was
+ not the most recent at the time the contract was created. For contracts
+ created from other contracts, you have to follow the creation chain
+ back to a transaction and use the date of that transaction as creation date.
+ - It is highly suspicious if a contract was compiled with a compiler that
+ contains a known bug and the contract was created at a time where a newer
+ compiler version containing a fix was already released.
+
+The JSON file of known bugs below is an array of objects, one for each bug,
+with the following keys:
+
+name
+ Unique name given to the bug
+summary
+ Short description of the bug
+description
+ Detailed description of the bug
+link
+ URL of a website with more detailed information, optional
+introduced
+ The first published compiler version that contained the bug, optional
+fixed
+ The first published compiler version that did not contain the bug anymore
+publish
+ The date at which the bug became known publicly, optional
+severity
+ Severity of the bug: low, medium, high. Takes into account
+ discoverability in contract tests, likelihood of occurrence and
+ potential damage by exploits.
+conditions
+ Conditions that have to be met to trigger the bug. Currently, this
+ is an object that can contain a boolean value ``optimizer``, which
+ means that the optimizer has to be switched on to enable the bug.
+ If no conditions are given, assume that the bug is present.
+
+.. literalinclude:: bugs.json
+ :language: js
diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json
new file mode 100644
index 00000000..d6802eec
--- /dev/null
+++ b/docs/bugs_by_version.json
@@ -0,0 +1,373 @@
+{
+ "0.1.0": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-07-10"
+ },
+ "0.1.1": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-08-04"
+ },
+ "0.1.2": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-08-20"
+ },
+ "0.1.3": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-09-25"
+ },
+ "0.1.4": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-09-30"
+ },
+ "0.1.5": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-10-07"
+ },
+ "0.1.6": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-10-16"
+ },
+ "0.1.7": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-11-17"
+ },
+ "0.2.0": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2015-12-02"
+ },
+ "0.2.1": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2016-01-30"
+ },
+ "0.2.2": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits",
+ "AncientCompiler"
+ ],
+ "released": "2016-02-17"
+ },
+ "0.3.0": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits",
+ "ArrayAccessCleanHigherOrderBits"
+ ],
+ "released": "2016-03-11"
+ },
+ "0.3.1": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits"
+ ],
+ "released": "2016-03-31"
+ },
+ "0.3.2": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin",
+ "CleanBytesHigherOrderBits"
+ ],
+ "released": "2016-04-18"
+ },
+ "0.3.3": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin"
+ ],
+ "released": "2016-05-27"
+ },
+ "0.3.4": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin"
+ ],
+ "released": "2016-05-31"
+ },
+ "0.3.5": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther",
+ "DynamicAllocationInfiniteLoop",
+ "OptimizerClearStateOnCodePathJoin"
+ ],
+ "released": "2016-06-10"
+ },
+ "0.3.6": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "SendFailsForZeroEther"
+ ],
+ "released": "2016-08-10"
+ },
+ "0.4.0": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "LibrariesNotCallableFromPayableFunctions"
+ ],
+ "released": "2016-09-08"
+ },
+ "0.4.1": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3",
+ "LibrariesNotCallableFromPayableFunctions"
+ ],
+ "released": "2016-09-09"
+ },
+ "0.4.10": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction"
+ ],
+ "released": "2017-03-15"
+ },
+ "0.4.11": {
+ "bugs": [
+ "SkipEmptyStringLiteral"
+ ],
+ "released": "2017-05-03"
+ },
+ "0.4.12": {
+ "bugs": [],
+ "released": "2017-07-03"
+ },
+ "0.4.13": {
+ "bugs": [],
+ "released": "2017-07-06"
+ },
+ "0.4.2": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage",
+ "OptimizerStaleKnowledgeAboutSHA3"
+ ],
+ "released": "2016-09-17"
+ },
+ "0.4.3": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "HighOrderByteCleanStorage"
+ ],
+ "released": "2016-10-25"
+ },
+ "0.4.4": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored"
+ ],
+ "released": "2016-10-31"
+ },
+ "0.4.5": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored",
+ "OptimizerStateKnowledgeNotResetForJumpdest"
+ ],
+ "released": "2016-11-21"
+ },
+ "0.4.6": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction",
+ "IdentityPrecompileReturnIgnored"
+ ],
+ "released": "2016-11-22"
+ },
+ "0.4.7": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction"
+ ],
+ "released": "2016-12-15"
+ },
+ "0.4.8": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction"
+ ],
+ "released": "2017-01-13"
+ },
+ "0.4.9": {
+ "bugs": [
+ "SkipEmptyStringLiteral",
+ "ConstantOptimizerSubtraction"
+ ],
+ "released": "2017-01-31"
+ }
+} \ No newline at end of file
diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst
index fa5e68a6..acef13b7 100644
--- a/docs/common-patterns.rst
+++ b/docs/common-patterns.rst
@@ -23,12 +23,12 @@ contract in order to become the "richest", inspired by
`King of the Ether <https://www.kingoftheether.com/>`_.
In the following contract, if you are usurped as the richest,
-you will recieve the funds of the person who has gone on to
+you will receive the funds of the person who has gone on to
become the new richest.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract WithdrawalContract {
address public richest;
@@ -52,17 +52,12 @@ become the new richest.
}
}
- function withdraw() returns (bool) {
+ function withdraw() {
uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0;
- if (msg.sender.send(amount)) {
- return true;
- } else {
- pendingWithdrawals[msg.sender] = amount;
- return false;
- }
+ msg.sender.transfer(amount);
}
}
@@ -70,7 +65,7 @@ This is as opposed to the more intuitive sending pattern:
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract SendContract {
address public richest;
@@ -81,14 +76,10 @@ This is as opposed to the more intuitive sending pattern:
mostSent = msg.value;
}
- function becomeRichest() returns (bool) {
+ function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
- // Check if call succeeds to prevent an attacker
- // from trapping the previous person's funds in
- // this contract through a callstack attack
- if (!richest.send(msg.value)) {
- throw;
- }
+ // This line can cause problems (explained below).
+ richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
@@ -100,12 +91,16 @@ This is as opposed to the more intuitive sending pattern:
Notice that, in this example, an attacker could trap the
contract into an unusable state by causing ``richest`` to be
-the address of a contract that has a fallback function
-which consumes more than the 2300 gas stipend. That way,
-whenever ``send`` is called to deliver funds to the
-"poisoned" contract, it will cause execution to always fail
-because there will not be enough gas to finish the execution
-of the fallback function.
+the address of a contract that has a fallback function
+which fails (e.g. by using ``revert()`` or by just
+conssuming more than the 2300 gas stipend). That way,
+whenever ``transfer`` is called to deliver funds to the
+"poisoned" contract, it will fail and thus also ``becomeRichest``
+will fail, with the contract being stuck forever.
+
+In contrast, if you use the "withdraw" pattern from the first example,
+the attacker can only cause his or her own withdraw to fail and not the
+rest of the contract's workings.
.. index:: access;restricting
@@ -135,7 +130,7 @@ restrictions highly readable.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract AccessRestriction {
// These will be assigned at the construction
@@ -152,8 +147,7 @@ restrictions highly readable.
// a certain address.
modifier onlyBy(address _account)
{
- if (msg.sender != _account)
- throw;
+ require(msg.sender == _account);
// Do not forget the "_;"! It will
// be replaced by the actual function
// body when the modifier is used.
@@ -169,7 +163,7 @@ restrictions highly readable.
}
modifier onlyAfter(uint _time) {
- if (now < _time) throw;
+ require(now >= _time);
_;
}
@@ -190,8 +184,7 @@ restrictions highly readable.
// This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`.
modifier costs(uint _amount) {
- if (msg.value < _amount)
- throw;
+ require(msg.value >= _amount);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);
@@ -232,7 +225,7 @@ reached at a certain point in **time**.
An example for this is a blind auction contract which
starts in the stage "accepting blinded bids", then
transitions to "revealing bids" which is ended by
-"determine auction autcome".
+"determine auction outcome".
.. index:: function;modifier
@@ -276,7 +269,7 @@ function finishes.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract StateMachine {
enum Stages {
@@ -293,7 +286,7 @@ function finishes.
uint public creationTime = now;
modifier atStage(Stages _stage) {
- if (stage != _stage) throw;
+ require(stage == _stage);
_;
}
diff --git a/docs/conf.py b/docs/conf.py
index 2bc79fd9..ca8c0fec 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -15,6 +15,7 @@
import sys
import os
+import re
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -49,16 +50,21 @@ master_doc = 'index'
# General information about the project.
project = 'Solidity'
-copyright = '2016, Ethereum'
+copyright = '2016-2017, Ethereum'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '0.4.7'
+with open('../CMakeLists.txt', 'r') as f:
+ version = re.search('PROJECT_VERSION "([^"]+)"', f.read()).group(1)
# The full version, including alpha/beta/rc tags.
-release = '0.4.7-develop'
+if os.path.isfile('../prerelease.txt') != True or os.path.getsize('../prerelease.txt') == 0:
+ release = version
+else:
+ # This is a prerelease version
+ release = version + '-develop'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/contracts.rst b/docs/contracts.rst
index e82b7495..e9ea1b3b 100644
--- a/docs/contracts.rst
+++ b/docs/contracts.rst
@@ -20,14 +20,14 @@ Contracts can be created "from outside" or from Solidity contracts.
When a contract is created, its constructor (a function with the same
name as the contract) is executed once.
-A constructor is optional. Only one constructor is allowed and this means
+A constructor is optional. Only one constructor is allowed, and this means
overloading is not supported.
From ``web3.js``, i.e. the JavaScript
API, this is done as follows::
// Need to specify some source including contract name for the data param below
- var source = "contract CONTRACT_NAME { function CONTRACT_NAME(unit a, uint b) {} }";
+ var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }";
// The json abi array generated by the compiler
var abiArray = [
@@ -145,11 +145,11 @@ This means that cyclic creation dependencies are impossible.
.. index:: ! visibility, external, public, private, internal
-.. _visibility-and-accessors:
+.. _visibility-and-getters:
-************************
-Visibility and Accessors
-************************
+**********************
+Visibility and Getters
+**********************
Since Solidity knows two kinds of function calls (internal
ones that do not create an actual EVM call (also called
@@ -173,7 +173,7 @@ and the default is ``internal``.
``public``:
Public functions are part of the contract
interface and can be either called internally or via
- messages. For public state variables, an automatic accessor
+ messages. For public state variables, an automatic getter
function (see below) is generated.
``internal``:
@@ -243,12 +243,13 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
}
}
-.. index:: ! accessor;function, ! function;accessor
+.. index:: ! getter;function, ! function;getter
+.. _getter_functions:
-Accessor Functions
-==================
+Getter Functions
+================
-The compiler automatically creates accessor functions for
+The compiler automatically creates getter functions for
all **public** state variables. For the contract given below, the compiler will
generate a function called ``data`` that does not take any
arguments and returns a ``uint``, the value of the state
@@ -271,9 +272,9 @@ be done at declaration.
}
}
-The accessor functions have external visibility. If the
+The getter functions have external visibility. If the
symbol is accessed internally (i.e. without ``this.``),
-it is evaluated as a state variable and if it is accessed externally
+it is evaluated as a state variable. If it is accessed externally
(i.e. with ``this.``), it is evaluated as a function.
::
@@ -321,13 +322,13 @@ is no good way to provide the key for the mapping.
Function Modifiers
******************
-Modifiers can be used to easily change the behaviour of functions, for example
-to automatically check a condition prior to executing the function. They are
+Modifiers can be used to easily change the behaviour of functions. For example,
+they can automatically check a condition prior to executing the function. Modifiers are
inheritable properties of contracts and may be overridden by derived contracts.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract owned {
function owned() { owner = msg.sender; }
@@ -341,8 +342,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
// function is executed and otherwise, an exception is
// thrown.
modifier onlyOwner {
- if (msg.sender != owner)
- throw;
+ require(msg.sender == owner);
_;
}
}
@@ -390,7 +390,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
contract Mutex {
bool locked;
modifier noReentrancy() {
- if (locked) throw;
+ require(!locked);
locked = true;
_;
locked = false;
@@ -401,13 +401,13 @@ inheritable properties of contracts and may be overridden by derived contracts.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
function f() noReentrancy returns (uint) {
- if (!msg.sender.call()) throw;
+ require(msg.sender.call());
return 7;
}
}
-Multiple modifiers can be applied to a function by specifying them in a
-whitespace-separated list and will be evaluated in order.
+Multiple modifiers are applied to a function by specifying them in a
+whitespace-separated list and are evaluated in the order presented.
.. warning::
In an earlier version of Solidity, ``return`` statements in functions
@@ -428,8 +428,25 @@ change by overriding).
Constant State Variables
************************
-State variables can be declared as constant (this is not yet implemented
-for array and struct types and not possible for mapping types).
+State variables can be declared as ``constant``. In this case, they have to be
+assigned from an expression which is a constant at compile time. Any expression
+that accesses storage, blockchain data (e.g. ``now``, ``this.balance`` or
+``block.number``) or
+execution data (``msg.gas``) or make calls to external contracts are disallowed. Expressions
+that might have a side-effect on memory allocation are allowed, but those that
+might have a side-effect on other memory objects are not. The built-in functions
+``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod``
+are allowed (even though they do call external contracts).
+
+The reason behind allowing side-effects on the memory allocator is that it
+should be possible to construct complex objects like e.g. lookup-tables.
+This feature is not yet fully usable.
+
+The compiler does not reserve a storage slot for these variables, and every occurrence is
+replaced by the respective constant expression (which might be computed to a single value by the optimizer).
+
+Not all types for constants are implemented at this time. The only supported types are
+value types and strings.
::
@@ -438,18 +455,17 @@ for array and struct types and not possible for mapping types).
contract C {
uint constant x = 32**22 + 8;
string constant text = "abc";
+ bytes32 constant myHash = keccak256("abc");
}
-This has the effect that the compiler does not reserve a storage slot
-for these variables and every occurrence is replaced by their constant value.
-The value expression can only contain integer arithmetics.
+.. _constant-functions:
******************
Constant Functions
******************
-Functions can be declared constant. These functions promise not to modify the state.
+Functions can be declared constant in which case they promise not to modify the state.
::
@@ -462,7 +478,7 @@ Functions can be declared constant. These functions promise not to modify the st
}
.. note::
- Accessor methods are marked constant.
+ Getter methods are marked constant.
.. warning::
The compiler does not enforce yet that a constant method is not modifying state.
@@ -478,7 +494,7 @@ Fallback Function
A contract can have exactly one unnamed function. This function cannot have
arguments and cannot return anything.
It is executed on a call to the contract if none of the other
-functions matches the given function identifier (or if no data was supplied at
+functions match the given function identifier (or if no data was supplied at
all).
Furthermore, this function is executed whenever the contract receives plain
@@ -496,7 +512,8 @@ In particular, the following operations will consume more gas than the stipend p
Please ensure you test your fallback function thoroughly to ensure the execution cost is less than 2300 gas before deploying a contract.
.. warning::
- Contracts that receive Ether but do not define a fallback function
+ Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
+ but do not define a fallback function
throw an exception, sending back the Ether (this was different
before Solidity v0.4.0). So if you want your contract to receive Ether,
you have to implement a fallback function.
@@ -554,13 +571,12 @@ the contract and will be incorporated into the blockchain
and stay there as long as a block is accessible (forever as of
Frontier and Homestead, but this might change with Serenity). Log and
event data is not accessible from within contracts (not even from
-the contract that created a log).
+the contract that created them).
SPV proofs for logs are possible, so if an external entity supplies
a contract with such a proof, it can check that the log actually
-exists inside the blockchain (but be aware of the fact that
-ultimately, also the block headers have to be supplied because
-the contract can only see the last 256 block hashes).
+exists inside the blockchain. But be aware that block headers have to be supplied because
+the contract can only see the last 256 block hashes.
Up to three parameters can
receive the attribute ``indexed`` which will cause the respective arguments
@@ -577,7 +593,7 @@ not possible to filter for specific anonymous events by name.
All non-indexed arguments will be stored in the data part of the log.
.. note::
- Indexed arguments will not be stored themselves, you can only
+ Indexed arguments will not be stored themselves. You can only
search for the values, but it is impossible to retrieve the
values themselves.
@@ -592,7 +608,7 @@ All non-indexed arguments will be stored in the data part of the log.
uint _value
);
- function deposit(bytes32 _id) {
+ function deposit(bytes32 _id) payable {
// Any call to this function (even deeply nested) can
// be detected from the JavaScript API by filtering
// for `Deposit` to be called.
@@ -666,9 +682,9 @@ Solidity supports multiple inheritance by copying code including polymorphism.
All function calls are virtual, which means that the most derived function
is called, except when the contract name is explicitly given.
-Even if a contract inherits from multiple other contracts, only a single
-contract is created on the blockchain, the code from the base contracts
-is always copied into the final contract.
+When a contract inherits from multiple contracts, only a single
+contract is created on the blockchain, and the code from all the base contracts
+is copied into the created contract.
The general inheritance system is very similar to
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_,
@@ -805,7 +821,7 @@ derived override, but this function will bypass
}
If ``Base1`` calls a function of ``super``, it does not simply
-call this function on one of its base contracts, it rather
+call this function on one of its base contracts. Rather, it
calls this function on the next base contract in the final
inheritance graph, so it will call ``Base2.kill()`` (note that
the final inheritance sequence is -- starting with the most
@@ -821,7 +837,7 @@ Arguments for Base Constructors
===============================
Derived contracts need to provide all arguments needed for
-the base constructors. This can be done at two places::
+the base constructors. This can be done in two ways::
pragma solidity ^0.4.0;
@@ -836,7 +852,7 @@ the base constructors. This can be done at two places::
}
}
-Either directly in the inheritance list (``is Base(7)``) or in
+One way is directly in the inheritance list (``is Base(7)``). The other is in
the way a modifier would be invoked as part of the header of
the derived constructor (``Base(_y * _y)``). The first way to
do it is more convenient if the constructor argument is a
@@ -852,7 +868,7 @@ Multiple Inheritance and Linearization
======================================
Languages that allow multiple inheritance have to deal with
-several problems, one of them being the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_.
+several problems. One is the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_.
Solidity follows the path of Python and uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_"
to force a specific order in the DAG of base classes. This
results in the desirable property of monotonicity but
@@ -877,6 +893,13 @@ cannot be resolved.
A simple rule to remember is to specify the base classes in
the order from "most base-like" to "most derived".
+Inheriting Different Kinds of Members of the Same Name
+======================================================
+
+When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error.
+This error is produced also by an event and a modifier of the same name, and a function and an event of the same name.
+As an exception, a state variable getter can override a public function.
+
.. index:: ! contract;abstract, ! abstract contract
******************
@@ -901,6 +924,35 @@ Such contracts cannot be compiled (even if they contain implemented functions al
If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract.
+.. index:: ! contract;interface, ! interface contract
+
+**********
+Interfaces
+**********
+
+Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions:
+
+#. Cannot inherit other contracts or interfaces.
+#. Cannot define constructor.
+#. Cannot define variables.
+#. Cannot define structs.
+#. Cannot define enums.
+
+Some of these restrictions might be lifted in the future.
+
+Interfaces are basically limited to what the Contract ABI can represent, and the conversion between the ABI and
+an Interface should be possible without any information loss.
+
+Interfaces are denoted by their own keyword:
+
+::
+
+ interface Token {
+ function transfer(address recipient, uint amount);
+ }
+
+Contracts can inherit interfaces as they would inherit other contracts.
+
.. index:: ! library, callcode, delegatecall
.. _libraries:
@@ -927,9 +979,9 @@ contracts (``L.f()`` if ``L`` is the name of the library). Furthermore,
if the library were a base contract. Of course, calls to internal functions
use the internal calling convention, which means that all internal types
can be passed and memory types will be passed by reference and not copied.
-In order to realise this in the EVM, code of internal library functions
-(and all functions called from therein) will be pulled into the calling
-contract and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``.
+To realize this in the EVM, code of internal library functions
+and all functions called from therein will be pulled into the calling
+contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``.
.. index:: using for, set
@@ -939,7 +991,7 @@ more advanced example to implement a set).
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
library Set {
// We define a new struct datatype that will be used to
@@ -985,16 +1037,15 @@ more advanced example to implement a set).
// The library functions can be called without a
// specific instance of the library, since the
// "instance" will be the current contract.
- if (!Set.insert(knownValues, value))
- throw;
+ require(Set.insert(knownValues, value));
}
// In this contract, we can also directly access knownValues.flags, if we want.
}
Of course, you do not have to follow this way to use
libraries - they can also be used without defining struct
-data types, functions also work without any storage
-reference parameters, can have multiple storage reference
+data types. Functions also work without any storage
+reference parameters, and they can have multiple storage reference
parameters and in any position.
The calls to ``Set.contains``, ``Set.insert`` and ``Set.remove``
@@ -1080,7 +1131,7 @@ Restrictions for libraries in comparison to contracts:
- No state variables
- Cannot inherit nor be inherited
-- Cannot recieve Ether
+- Cannot receive Ether
(These might be lifted at a later point.)
@@ -1116,7 +1167,7 @@ available without having to add further code.
Let us rewrite the set example from the
:ref:`libraries` in this way::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
// This is the same code as before, just without comments
library Set {
@@ -1157,8 +1208,7 @@ Let us rewrite the set example from the
// corresponding member functions.
// The following function call is identical to
// Set.insert(knownValues, value)
- if (!knownValues.insert(value))
- throw;
+ require(knownValues.insert(value));
}
}
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 42204d5c..9d1b2ce3 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -12,7 +12,7 @@ In particular, we need help in the following areas:
* Improving the documentation
* Responding to questions from other users on `StackExchange
- <http://ethereum.stackexchange.com/>`_ and the `Solidity Gitter
+ <https://ethereum.stackexchange.com>`_ and the `Solidity Gitter
<https://gitter.im/ethereum/solidity>`_
* Fixing and responding to `Solidity's GitHub issues
<https://github.com/ethereum/solidity/issues>`_, especially those tagged as
@@ -74,3 +74,22 @@ To run a subset of tests, filters can be used:
``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``.
Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests.
+
+Whiskers
+========
+
+*Whiskers* is a templating system similar to `Mustache <https://mustache.github.io>`_. It is used by the
+compiler in various places to aid readability, and thus maintainability and verifiability, of the code.
+
+The syntax comes with a substantial difference to Mustache: the template markers ``{{`` and ``}}`` are
+replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`inline-assembly`
+(The symbols ``<`` and ``>`` are invalid in inline assembly, while ``{`` and ``}`` are used to delimit blocks).
+Another limitation is that lists are only resolved one depth and they will not recurse. This may change in the future.
+
+A rough specification is the following:
+
+Any occurrence of ``<name>`` is replaced by the string-value of the supplied variable ``name`` without any
+escaping and without iterated replacements. An area can be delimited by ``<#name>...</name>``. It is replaced
+by as many concatenations of its contents as there were sets of variables supplied to the template system,
+each time replacing any ``<inner>`` items by their respective value. Top-level variables can also be used
+inside such areas.
diff --git a/docs/control-structures.rst b/docs/control-structures.rst
index 974a093f..03787c20 100644
--- a/docs/control-structures.rst
+++ b/docs/control-structures.rst
@@ -104,7 +104,7 @@ contract can be called internally.
External Function Calls
-----------------------
-The expressions ``this.g(8);`` and ``c.g(2);`` (where ``g`` is a contract
+The expressions ``this.g(8);`` and ``c.g(2);`` (where ``c`` is a contract
instance) are also valid function calls, but this time, the function
will be called "externally", via a message call and not directly via jumps.
Please note that function calls on ``this`` cannot be used in the constructor, as the
@@ -113,8 +113,8 @@ actual contract has not been created yet.
Functions of other contracts have to be called externally. For an external call,
all function arguments have to be copied to memory.
-When calling functions
-of other contracts, the amount of Wei sent with the call and the gas can be specified::
+When calling functions of other contracts, the amount of Wei sent with the call and
+the gas can be specified with special options ``.value()`` and ``.gas()``, respectively::
contract InfoFeed {
function info() payable returns (uint ret) { return 42; }
@@ -127,8 +127,8 @@ of other contracts, the amount of Wei sent with the call and the gas can be spec
function callFeed() { feed.info.value(10).gas(800)(); }
}
-The modifier ``payable`` has to be used for ``info``, because otherwise,
-we would not be able to send Ether to it in the call ``feed.info.value(10).gas(800)()``.
+The modifier ``payable`` has to be used for ``info``, because otherwise, the `.value()`
+option would not be available.
Note that the expression ``InfoFeed(addr)`` performs an explicit type conversion stating
that "we know that the type of the contract at the given address is ``InfoFeed``" and
@@ -207,8 +207,8 @@ Creating Contracts via ``new``
==============================
A contract can create a new contract using the ``new`` keyword. The full
-code of the contract being created has to be known and, thus, recursive
-creation-dependencies are now possible.
+code of the contract being created has to be known in advance, so recursive
+creation-dependencies are not possible.
::
@@ -235,7 +235,7 @@ creation-dependencies are now possible.
}
}
-As seen in the example, it is possible to forward Ether to the creation,
+As seen in the example, it is possible to forward Ether to the creation using the ``.value()`` option,
but it is not possible to limit the amount of gas. If the creation fails
(due to out-of-stack, not enough balance or other problems), an exception
is thrown.
@@ -361,511 +361,72 @@ As a result, the following code is legal, despite being poorly written::
return bar;// returns 5
}
-.. index:: ! exception, ! throw
+.. index:: ! exception, ! throw, ! assert, ! require, ! revert
-Exceptions
-==========
+Error handling: Assert, Require, Revert and Exceptions
+======================================================
+
+Solidity uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the
+state in the current call (and all its sub-calls) and also flag an error to the caller.
+The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception
+if the condition is not met. The difference between the two is that ``assert`` should only be used for internal errors
+and ``require`` should be used to check external conditions (invalid inputs or errors in external components).
+The idea behind that is that analysis tools can check your contract and try to come up with situations and
+series of function calls that will reach a failing assertion. If this is possible, this means there is a bug
+in your contract you should fix.
+
+There are two other ways to trigger execptions: The ``revert`` function can be used to flag an error and
+revert the current call. In the future it might be possible to also include details about the error
+in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``.
-There are some cases where exceptions are thrown automatically (see below). You can use the ``throw`` instruction to throw an exception manually. The effect of an exception is that the currently executing call is stopped and reverted (i.e. all changes to the state and balances are undone) and the exception is also "bubbled up" through Solidity function calls (exceptions are ``send`` and the low-level functions ``call``, ``delegatecall`` and ``callcode``, those return ``false`` in case of an exception).
+When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send``
+and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case
+of an exception instead of "bubbling up".
Catching exceptions is not yet possible.
-In the following example, we show how ``throw`` can be used to easily revert an Ether transfer and also how to check the return value of ``send``::
+In the following example, you can see how ``require`` can be used to easily check conditions on inputs
+and how ``assert`` can be used for internal error checking::
pragma solidity ^0.4.0;
contract Sharer {
function sendHalf(address addr) payable returns (uint balance) {
- if (!addr.send(msg.value / 2))
- throw; // also reverts the transfer to Sharer
+ require(msg.value % 2 == 0); // Only allow even numbers
+ uint balanceBeforeTransfer = this.balance;
+ addr.transfer(msg.value / 2);
+ // Since transfer throws an exception on failure and
+ // cannot call back here, there should be no way for us to
+ // still have half of the money.
+ assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
}
-Currently, there are situations, where exceptions happen automatically in Solidity:
-
-1. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
-2. If you access a fixed-length ``bytesN`` at a too large or negative index.
-3. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
-4. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly").
-5. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
-6. If you convert a value too big or negative into an enum type.
-7. If you perform an external function call targeting a contract that contains no code.
-8. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
-9. If your contract receives Ether via a public accessor function.
-
-Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect.
-
-.. index:: ! assembly, ! asm, ! evmasm
-
-Inline Assembly
-===============
-
-For more fine-grained control especially in order to enhance the language by writing libraries,
-it is possible to interleave Solidity statements with inline assembly in a language close
-to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is
-often hard to address the correct stack slot and provide arguments to opcodes at the correct
-point on the stack. Solidity's inline assembly tries to facilitate that and other issues
-arising when writing manual assembly by the following features:
-
-* functional-style opcodes: ``mul(1, add(2, 3))`` instead of ``push1 3 push1 2 add push1 1 mul``
-* assembly-local variables: ``let x := add(2, 3) let y := mload(0x40) x := add(x, y)``
-* access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }``
-* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))``
-
-We now want to describe the inline assembly language in detail.
-
-.. warning::
- Inline assembly is a way to access the Ethereum Virtual Machine
- at a low level. This discards several important safety
- features of Solidity.
-
-Example
--------
-
-The following example provides library code to access the code of another contract and
-load it into a ``bytes`` variable. This is not possible at all with "plain Solidity" and the
-idea is that assembly libraries will be used to enhance the language in such ways.
-
-.. code::
-
- pragma solidity ^0.4.0;
-
- library GetCode {
- function at(address _addr) returns (bytes o_code) {
- assembly {
- // retrieve the size of the code, this needs assembly
- let size := extcodesize(_addr)
- // allocate output byte array - this could also be done without assembly
- // by using o_code = new bytes(size)
- o_code := mload(0x40)
- // new "memory end" including padding
- mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
- // store length in memory
- mstore(o_code, size)
- // actually retrieve the code, this needs assembly
- extcodecopy(_addr, add(o_code, 0x20), 0, size)
- }
- }
- }
-
-Inline assembly could also be beneficial in cases where the optimizer fails to produce
-efficient code. Please be aware that assembly is much more difficult to write because
-the compiler does not perform checks, so you should use it for complex things only if
-you really know what you are doing.
-
-.. code::
-
- pragma solidity ^0.4.0;
-
- library VectorSum {
- // This function is less efficient because the optimizer currently fails to
- // remove the bounds checks in array access.
- function sumSolidity(uint[] _data) returns (uint o_sum) {
- for (uint i = 0; i < _data.length; ++i)
- o_sum += _data[i];
- }
-
- // We know that we only access the array in bounds, so we can avoid the check.
- // 0x20 needs to be added to an array because the first slot contains the
- // array length.
- function sumAsm(uint[] _data) returns (uint o_sum) {
- for (uint i = 0; i < _data.length; ++i) {
- assembly {
- o_sum := mload(add(add(_data, 0x20), i))
- }
- }
- }
- }
-
-Syntax
-------
-
-Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the
-usual ``//`` and ``/* */`` comments. Inline assembly is initiated by ``assembly { ... }`` and inside
-these curly braces, the following can be used (see the later sections for more details)
-
- - literals, e.g. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters)
- - opcodes (in "instruction style"), e.g. ``mload sload dup1 sstore``, for a list see below
- - opcodes in functional style, e.g. ``add(1, mload(0))``
- - labels, e.g. ``name:``
- - variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)``
- - identifiers (externals, labels or assembly-local variables), e.g. ``jump(name)``, ``3 x add``
- - assignments (in "instruction style"), e.g. ``3 =: x``
- - assignments in functional style, e.g. ``x := add(y, 3)``
- - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }``
-
-Opcodes
--------
-
-This document does not want to be a full description of the Ethereum virtual machine, but the
-following list can be used as a reference of its opcodes.
-
-If an opcode takes arguments (always from the top of the stack), they are given in parentheses.
-Note that the order of arguments can be seen as being reversed compared to the instructional style (explained below).
-Opcodes marked with ``-`` do not push an item onto the stack, those marked with ``*`` are
-special and all others push exactly one item onte the stack.
-
-In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to
-(excluding) position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``.
-
-The opcodes ``pushi`` and ``jumpdest`` cannot be used directly.
-
-+-------------------------+------+-----------------------------------------------------------------+
-| stop + `-` | stop execution, identical to return(0,0) |
-+-------------------------+------+-----------------------------------------------------------------+
-| add(x, y) | | x + y |
-+-------------------------+------+-----------------------------------------------------------------+
-| sub(x, y) | | x - y |
-+-------------------------+------+-----------------------------------------------------------------+
-| mul(x, y) | | x * y |
-+-------------------------+------+-----------------------------------------------------------------+
-| div(x, y) | | x / y |
-+-------------------------+------+-----------------------------------------------------------------+
-| sdiv(x, y) | | x / y, for signed numbers in two's complement |
-+-------------------------+------+-----------------------------------------------------------------+
-| mod(x, y) | | x % y |
-+-------------------------+------+-----------------------------------------------------------------+
-| smod(x, y) | | x % y, for signed numbers in two's complement |
-+-------------------------+------+-----------------------------------------------------------------+
-| exp(x, y) | | x to the power of y |
-+-------------------------+------+-----------------------------------------------------------------+
-| not(x) | | ~x, every bit of x is negated |
-+-------------------------+------+-----------------------------------------------------------------+
-| lt(x, y) | | 1 if x < y, 0 otherwise |
-+-------------------------+------+-----------------------------------------------------------------+
-| gt(x, y) | | 1 if x > y, 0 otherwise |
-+-------------------------+------+-----------------------------------------------------------------+
-| slt(x, y) | | 1 if x < y, 0 otherwise, for signed numbers in two's complement |
-+-------------------------+------+-----------------------------------------------------------------+
-| sgt(x, y) | | 1 if x > y, 0 otherwise, for signed numbers in two's complement |
-+-------------------------+------+-----------------------------------------------------------------+
-| eq(x, y) | | 1 if x == y, 0 otherwise |
-+-------------------------+------+-----------------------------------------------------------------+
-| iszero(x) | | 1 if x == 0, 0 otherwise |
-+-------------------------+------+-----------------------------------------------------------------+
-| and(x, y) | | bitwise and of x and y |
-+-------------------------+------+-----------------------------------------------------------------+
-| or(x, y) | | bitwise or of x and y |
-+-------------------------+------+-----------------------------------------------------------------+
-| xor(x, y) | | bitwise xor of x and y |
-+-------------------------+------+-----------------------------------------------------------------+
-| byte(n, x) | | nth byte of x, where the most significant byte is the 0th byte |
-+-------------------------+------+-----------------------------------------------------------------+
-| addmod(x, y, m) | | (x + y) % m with arbitrary precision arithmetics |
-+-------------------------+------+-----------------------------------------------------------------+
-| mulmod(x, y, m) | | (x * y) % m with arbitrary precision arithmetics |
-+-------------------------+------+-----------------------------------------------------------------+
-| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant |
-+-------------------------+------+-----------------------------------------------------------------+
-| sha3(p, n) | | keccak(mem[p...(p+n))) |
-+-------------------------+------+-----------------------------------------------------------------+
-| jump(label) | `-` | jump to label / code position |
-+-------------------------+------+-----------------------------------------------------------------+
-| jumpi(label, cond) | `-` | jump to label if cond is nonzero |
-+-------------------------+------+-----------------------------------------------------------------+
-| pc | | current position in code |
-+-------------------------+------+-----------------------------------------------------------------+
-| pop(x) | `-` | remove the element pushed by x |
-+-------------------------+------+-----------------------------------------------------------------+
-| dup1 ... dup16 | | copy ith stack slot to the top (counting from top) |
-+-------------------------+------+-----------------------------------------------------------------+
-| swap1 ... swap16 | `*` | swap topmost and ith stack slot below it |
-+-------------------------+------+-----------------------------------------------------------------+
-| mload(p) | | mem[p..(p+32)) |
-+-------------------------+------+-----------------------------------------------------------------+
-| mstore(p, v) | `-` | mem[p..(p+32)) := v |
-+-------------------------+------+-----------------------------------------------------------------+
-| mstore8(p, v) | `-` | mem[p] := v & 0xff - only modifies a single byte |
-+-------------------------+------+-----------------------------------------------------------------+
-| sload(p) | | storage[p] |
-+-------------------------+------+-----------------------------------------------------------------+
-| sstore(p, v) | `-` | storage[p] := v |
-+-------------------------+------+-----------------------------------------------------------------+
-| msize | | size of memory, i.e. largest accessed memory index |
-+-------------------------+------+-----------------------------------------------------------------+
-| gas | | gas still available to execution |
-+-------------------------+------+-----------------------------------------------------------------+
-| address | | address of the current contract / execution context |
-+-------------------------+------+-----------------------------------------------------------------+
-| balance(a) | | wei balance at address a |
-+-------------------------+------+-----------------------------------------------------------------+
-| caller | | call sender (excluding delegatecall) |
-+-------------------------+------+-----------------------------------------------------------------+
-| callvalue | | wei sent together with the current call |
-+-------------------------+------+-----------------------------------------------------------------+
-| calldataload(p) | | calldata starting from position p (32 bytes) |
-+-------------------------+------+-----------------------------------------------------------------+
-| calldatasize | | size of calldata in bytes |
-+-------------------------+------+-----------------------------------------------------------------+
-| calldatacopy(t, f, s) | `-` | copy s bytes from calldata at position f to mem at position t |
-+-------------------------+------+-----------------------------------------------------------------+
-| codesize | | size of the code of the current contract / execution context |
-+-------------------------+------+-----------------------------------------------------------------+
-| codecopy(t, f, s) | `-` | copy s bytes from code at position f to mem at position t |
-+-------------------------+------+-----------------------------------------------------------------+
-| extcodesize(a) | | size of the code at address a |
-+-------------------------+------+-----------------------------------------------------------------+
-| extcodecopy(a, t, f, s) | `-` | like codecopy(t, f, s) but take code at address a |
-+-------------------------+------+-----------------------------------------------------------------+
-| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei |
-| | | and return the new address |
-+-------------------------+------+-----------------------------------------------------------------+
-| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)) |
-| insize, out, outsize) | | providing g gas and v wei and output area |
-| | | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) |
-| | | and 1 on success |
-+-------------------------+------+-----------------------------------------------------------------+
-| callcode(g, a, v, in, | | identical to `call` but only use the code from a and stay |
-| insize, out, outsize) | | in the context of the current contract otherwise |
-+-------------------------+------+-----------------------------------------------------------------+
-| delegatecall(g, a, in, | | identical to `callcode` but also keep ``caller`` |
-| insize, out, outsize) | | and ``callvalue`` |
-+-------------------------+------+-----------------------------------------------------------------+
-| return(p, s) | `-` | end execution, return data mem[p..(p+s)) |
-+-------------------------+------+-----------------------------------------------------------------+
-| selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a |
-+-------------------------+------+-----------------------------------------------------------------+
-| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) |
-+-------------------------+------+-----------------------------------------------------------------+
-| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) |
-+-------------------------+------+-----------------------------------------------------------------+
-| log2(p, s, t1, t2) | `-` | log with topics t1, t2 and data mem[p..(p+s)) |
-+-------------------------+------+-----------------------------------------------------------------+
-| log3(p, s, t1, t2, t3) | `-` | log with topics t1, t2, t3 and data mem[p..(p+s)) |
-+-------------------------+------+-----------------------------------------------------------------+
-| log4(p, s, t1, t2, t3, | `-` | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) |
-| t4) | | |
-+-------------------------+------+-----------------------------------------------------------------+
-| origin | | transaction sender |
-+-------------------------+------+-----------------------------------------------------------------+
-| gasprice | | gas price of the transaction |
-+-------------------------+------+-----------------------------------------------------------------+
-| blockhash(b) | | hash of block nr b - only for last 256 blocks excluding current |
-+-------------------------+------+-----------------------------------------------------------------+
-| coinbase | | current mining beneficiary |
-+-------------------------+------+-----------------------------------------------------------------+
-| timestamp | | timestamp of the current block in seconds since the epoch |
-+-------------------------+------+-----------------------------------------------------------------+
-| number | | current block number |
-+-------------------------+------+-----------------------------------------------------------------+
-| difficulty | | difficulty of the current block |
-+-------------------------+------+-----------------------------------------------------------------+
-| gaslimit | | block gas limit of the current block |
-+-------------------------+------+-----------------------------------------------------------------+
-
-Literals
---------
-
-You can use integer constants by typing them in decimal or hexadecimal notation and an
-appropriate ``PUSHi`` instruction will automatically be generated. The following creates code
-to add 2 and 3 resulting in 5 and then computes the bitwise and with the string "abc".
-Strings are stored left-aligned and cannot be longer than 32 bytes.
-
-.. code::
-
- assembly { 2 3 add "abc" and }
-
-Functional Style
------------------
-
-You can type opcode after opcode in the same way they will end up in bytecode. For example
-adding ``3`` to the contents in memory at position ``0x80`` would be
-
-.. code::
-
- 3 0x80 mload add 0x80 mstore
-
-As it is often hard to see what the actual arguments for certain opcodes are,
-Solidity inline assembly also provides a "functional style" notation where the same code
-would be written as follows
-
-.. code::
-
- mstore(0x80, add(mload(0x80), 3))
-
-Functional style and instructional style can be mixed, but any opcode inside a
-functional style expression has to return exactly one stack slot (most of the opcodes do).
-
-Note that the order of arguments is reversed in functional-style as opposed to the instruction-style
-way. If you use functional-style, the first argument will end up on the stack top.
-
-
-Access to External Variables and Functions
-------------------------------------------
-
-Solidity variables and other identifiers can be accessed by simply using their name.
-For storage and memory variables, this will push the address and not the value onto the
-stack. Also note that non-struct and non-array storage variable addresses occupy two slots
-on the stack: One for the address and one for the byte offset inside the storage slot.
-In assignments (see below), we can even use local Solidity variables to assign to.
-
-Functions external to inline assembly can also be accessed: The assembly will
-push their entry label (with virtual function resolution applied). The calling semantics
-in solidity are:
-
- - the caller pushes return label, arg1, arg2, ..., argn
- - the call returns with ret1, ret2, ..., retn
-
-This feature is still a bit cumbersome to use, because the stack offset essentially
-changes during the call, and thus references to local variables will be wrong.
-It is planned that the stack height changes can be specified in inline assembly.
-
-.. code::
-
- pragma solidity ^0.4.0;
-
- contract C {
- uint b;
- function f(uint x) returns (uint r) {
- assembly {
- b pop // remove the offset, we know it is zero
- sload
- x
- mul
- =: r // assign to return variable r
- }
- }
- }
-
-Labels
-------
-
-Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses
-which can change easily. Solidity inline assembly provides labels to make the use of
-jumps easier. The following code computes an element in the Fibonacci series.
-
-.. code::
-
- {
- let n := calldataload(4)
- let a := 1
- let b := a
- loop:
- jumpi(loopend, eq(n, 0))
- a add swap1
- n := sub(n, 1)
- jump(loop)
- loopend:
- mstore(0, a)
- return(0, 0x20)
- }
-
-Please note that automatically accessing stack variables can only work if the
-assembler knows the current stack height. This fails to work if the jump source
-and target have different stack heights. It is still fine to use such jumps,
-you should just not access any stack variables (even assembly variables) in that case.
-
-Furthermore, the stack height analyser goes through the code opcode by opcode
-(and not according to control flow), so in the following case, the assembler
-will have a wrong impression about the stack height at label ``two``:
-
-.. code::
-
- {
- jump(two)
- one:
- // Here the stack height is 1 (because we pushed 7),
- // but the assembler thinks it is 0 because it reads
- // from top to bottom.
- // Accessing stack variables here will lead to errors.
- jump(three)
- two:
- 7 // push something onto the stack
- jump(one)
- three:
- }
-
-.. note::
-
- ``invalidJumpLabel`` is a pre-defined label. Jumping to this location will always
- result in an invalid jump, effectively aborting execution of the code.
-
-Declaring Assembly-Local Variables
-----------------------------------
-
-You can use the ``let`` keyword to declare variables that are only visible in
-inline assembly and actually only in the current ``{...}``-block. What happens
-is that the ``let`` instruction will create a new stack slot that is reserved
-for the variable and automatically removed again when the end of the block
-is reached. You need to provide an initial value for the variable which can
-be just ``0``, but it can also be a complex functional-style expression.
-
-.. code::
-
- pragma solidity ^0.4.0;
-
- contract C {
- function f(uint x) returns (uint b) {
- assembly {
- let v := add(x, 1)
- mstore(0x80, v)
- {
- let y := add(sload(v), 1)
- b := y
- } // y is "deallocated" here
- b := add(b, v)
- } // v is "deallocated" here
- }
- }
-
-
-Assignments
------------
-
-Assignments are possible to assembly-local variables and to function-local
-variables. Take care that when you assign to variables that point to
-memory or storage, you will only change the pointer and not the data.
-
-There are two kinds of assignments: Functional-style and instruction-style.
-For functional-style assignments (``variable := value``), you need to provide a value in a
-functional-style expression that results in exactly one stack value
-and for instruction-style (``=: variable``), the value is just taken from the stack top.
-For both ways, the colon points to the name of the variable.
-
-.. code::
-
- assembly {
- let v := 0 // functional-style assignment as part of variable declaration
- let g := add(v, 2)
- sload(10)
- =: v // instruction style assignment, puts the result of sload(10) into v
- }
-
-
-Things to Avoid
----------------
-
-Inline assembly might have a quite high-level look, but it actually is extremely
-low-level. The only thing the assembler does for you is re-arranging
-functional-style opcodes, managing jump labels, counting stack height for
-variable access and removing stack slots for assembly-local variables when the end
-of their block is reached. Especially for those two last cases, it is important
-to know that the assembler only counts stack height from top to bottom, not
-necessarily following control flow. Furthermore, operations like swap will only
-swap the contents of the stack but not the location of variables.
-
-Conventions in Solidity
------------------------
-
-In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits,
-e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just
-treat them as 256-bit numbers and the higher-order bits are only cleaned at the
-point where it is necessary, i.e. just shortly before they are written to memory
-or before comparisons are performed. This means that if you access such a variable
-from within inline assembly, you might have to manually clean the higher order bits
-first.
-
-Solidity manages memory in a very simple way: There is a "free memory pointer"
-at position ``0x40`` in memory. If you want to allocate memory, just use the memory
-from that point on and update the pointer accordingly.
-
-Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is
-even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory
-arrays are pointers to memory arrays. The length of a dynamic array is stored at the
-first slot of the array and then only the array elements follow.
-
-.. warning::
- Statically-sized memory arrays do not have a length field, but it will be added soon
- to allow better convertibility between statically- and dynamically-sized arrays, so
- please do not rely on that.
+An ``assert``-style exception is generated in the following situations:
+
+#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
+#. If you access a fixed-length ``bytesN`` at a too large or negative index.
+#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
+#. If you shift by a negative amount.
+#. If you convert a value too big or negative into an enum type.
+#. If you call a zero-initialized variable of internal function type.
+#. If you call ``assert`` with an argument that evaluates to false.
+
+A ``require``-style exception is generated in the following situations:
+
+#. Calling ``throw``.
+#. Calling ``require`` with an argument that evaluates to ``false``.
+#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
+#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly").
+#. If you perform an external function call targeting a contract that contains no code.
+#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
+#. If your contract receives Ether via a public getter function.
+#. If a ``.transfer()`` fails.
+
+Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation
+(instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes
+the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect
+did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction
+(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while
+``revert``-style exceptions will not consume any gas starting from the Metropolis release. \ No newline at end of file
diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst
index 43fba332..03ee8388 100644
--- a/docs/frequently-asked-questions.rst
+++ b/docs/frequently-asked-questions.rst
@@ -68,7 +68,7 @@ creator. Save it. Then ``selfdestruct(creator);`` to kill and return funds.
Note that if you ``import "mortal"`` at the top of your contracts and declare
``contract SomeContract is mortal { ...`` and compile with a compiler that already
-has it (which includes `browser-solidity <https://ethereum.github.io/browser-solidity/>`_), then
+has it (which includes `Remix <https://remix.ethereum.org/>`_), then
``kill()`` is taken care of for you. Once a contract is "mortal", then you can
``contractname.kill.sendTransaction({from:eth.coinbase})``, just the same as my
examples.
@@ -641,7 +641,7 @@ Not yet, as this requires two levels of dynamic arrays (``string`` is a dynamic
If you issue a call for an array, it is possible to retrieve the whole array? Or must you write a helper function for that?
===========================================================================================================================
-The automatic accessor function for a public state variable of array type only returns
+The automatic getter function for a public state variable of array type only returns
individual elements. If you want to return the complete array, you have to
manually write a function to do that.
@@ -660,23 +660,12 @@ https://github.com/ethereum/wiki/wiki/Subtleties
After a successful CREATE operation's sub-execution, if the operation returns x, 5 * len(x) gas is subtracted from the remaining gas before the contract is created. If the remaining gas is less than 5 * len(x), then no gas is subtracted, the code of the created contract becomes the empty string, but this is not treated as an exceptional condition - no reverts happen.
-How do I use ``.send()``?
-=========================
-
-If you want to send 20 Ether from a contract to the address ``x``, you use ``x.send(20 ether);``.
-Here, ``x`` can be a plain address or a contract. If the contract already explicitly defines
-a function ``send`` (and thus overwrites the special function), you can use ``address(x).send(20 ether);``.
-
-Note that the call to ``send`` may fail in certain conditions, such as if you have insufficient funds, so you should always check the return value.
-``send`` returns ``true`` if the send was successful and ``false`` otherwise.
-
What does the following strange check do in the Custom Token contract?
======================================================================
::
- if (balanceOf[_to] + _value < balanceOf[_to])
- throw;
+ require((balanceOf[_to] + _value) >= balanceOf[_to]);
Integers in Solidity (and most other machine-related programming languages) are restricted to a certain range.
For ``uint256``, this is ``0`` up to ``2**256 - 1``. If the result of some operation on those numbers
diff --git a/docs/grammar.txt b/docs/grammar.txt
index 0aa6690b..6c041460 100644
--- a/docs/grammar.txt
+++ b/docs/grammar.txt
@@ -7,23 +7,26 @@ ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
-ContractDefinition = ( 'contract' | 'library' ) Identifier
+ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
ContractPart = StateVariableDeclaration | UsingForDeclaration
| StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
-InheritanceSpecifier = Identifier ( '(' Expression ( ',' Expression )* ')' )?
+InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' )? Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* )? '}'
+
ModifierDefinition = 'modifier' Identifier ParameterList? Block
+ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
+
FunctionDefinition = 'function' Identifier? ParameterList
- ( FunctionCall | Identifier | 'constant' |' payable' | 'external' | 'public' | 'internal' | 'private' )*
- ( 'returns' ParameterList )? Block
+ ( ModifierInvocation | 'constant' | 'payable' | 'external' | 'public' | 'internal' | 'private' )*
+ ( 'returns' ParameterList )? ( ';' | Block )
EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';'
EnumValue = Identifier
@@ -35,16 +38,24 @@ TypeNameList = '(' ( TypeName (',' TypeName )* )? ')'
// semantic restriction: mappings and structs (recursively) containing mappings
// are not allowed in argument lists
-VariableDeclaration = TypeName Identifier
-TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName | FunctionTypeName
+VariableDeclaration = TypeName StorageLocation? Identifier
+
+TypeName = ElementaryTypeName
+ | UserDefinedTypeName
+ | Mapping
+ | ArrayTypeName
+ | FunctionTypeName
+
+UserDefinedTypeName = Identifier ( '.' Identifier )*
+
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
-ArrayTypeName = TypeName StorageLocation? '[' Expression? ']'
+ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' | 'payable' )*
( 'returns' TypeNameList )?
StorageLocation = 'memory' | 'storage'
Block = '{' Statement* '}'
-Statement = IfStatement | WhileStatement | ForStatement | Block |
+Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | SimpleStatement ) ';'
@@ -54,21 +65,28 @@ WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
+InlineAssemblyStatement = 'assembly' StringLiteral? InlineAssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
-VariableDefinition = VariableDeclaration ( '=' Expression )?
+VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )?
+IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
// Precedence by order (see github.com/ethereum/solidity/pull/732)
-Expression =
- ( Expression ('++' | '--') | FunctionCall | IndexAccess | MemberAccess | '(' Expression ')' )
+Expression
+ = Expression ('++' | '--')
+ | NewExpression
+ | IndexAccess
+ | MemberAccess
+ | FunctionCall
+ | '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression
| Expression ('*' | '/' | '%') Expression
| Expression ('+' | '-') Expression
- | Expression ('<<' | '>>')
+ | Expression ('<<' | '>>') Expression
| Expression '&' Expression
| Expression '^' Expression
| Expression '|' Expression
@@ -78,23 +96,42 @@ Expression =
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
- | Expression? (',' Expression)
| PrimaryExpression
-PrimaryExpression = Identifier | BooleanLiteral | NumberLiteral | HexLiteral | StringLiteral
+PrimaryExpression = BooleanLiteral
+ | NumberLiteral
+ | HexLiteral
+ | StringLiteral
+ | TupleExpression
+ | Identifier
+ | ElementaryTypeNameExpression
+
+ExpressionList = Expression ( ',' Expression )*
+NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
-FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' Expression? ( ',' Expression )* ')'
-NewExpression = 'new' Identifier
+FunctionCall = Expression '(' FunctionCallArguments ')'
+FunctionCallArguments = '{' NameValueList? '}'
+ | ExpressionList?
+
+NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
BooleanLiteral = 'true' | 'false'
-NumberLiteral = '0x'? [0-9]+ (' ' NumberUnit)?
+NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
-Identifier = [a-zA-Z_] [a-zA-Z_0-9]*
+Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
+
+HexNumber = '0x' [0-9a-fA-F]+
+DecimalNumber = [0-9]+
+
+TupleExpression = '(' ( Expression ( ',' Expression )* )? ')'
+ | '[' ( Expression ( ',' Expression )* )? ']'
+
+ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | 'var'
| Int | Uint | Byte | Fixed | Ufixed
@@ -108,3 +145,11 @@ Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' |
Fixed = 'fixed' | 'fixed0x8' | 'fixed0x16' | 'fixed0x24' | 'fixed0x32' | 'fixed0x40' | 'fixed0x48' | 'fixed0x56' | 'fixed0x64' | 'fixed0x72' | 'fixed0x80' | 'fixed0x88' | 'fixed0x96' | 'fixed0x104' | 'fixed0x112' | 'fixed0x120' | 'fixed0x128' | 'fixed0x136' | 'fixed0x144' | 'fixed0x152' | 'fixed0x160' | 'fixed0x168' | 'fixed0x176' | 'fixed0x184' | 'fixed0x192' | 'fixed0x200' | 'fixed0x208' | 'fixed0x216' | 'fixed0x224' | 'fixed0x232' | 'fixed0x240' | 'fixed0x248' | 'fixed0x256' | 'fixed8x8' | 'fixed8x16' | 'fixed8x24' | 'fixed8x32' | 'fixed8x40' | 'fixed8x48' | 'fixed8x56' | 'fixed8x64' | 'fixed8x72' | 'fixed8x80' | 'fixed8x88' | 'fixed8x96' | 'fixed8x104' | 'fixed8x112' | 'fixed8x120' | 'fixed8x128' | 'fixed8x136' | 'fixed8x144' | 'fixed8x152' | 'fixed8x160' | 'fixed8x168' | 'fixed8x176' | 'fixed8x184' | 'fixed8x192' | 'fixed8x200' | 'fixed8x208' | 'fixed8x216' | 'fixed8x224' | 'fixed8x232' | 'fixed8x240' | 'fixed8x248' | 'fixed16x8' | 'fixed16x16' | 'fixed16x24' | 'fixed16x32' | 'fixed16x40' | 'fixed16x48' | 'fixed16x56' | 'fixed16x64' | 'fixed16x72' | 'fixed16x80' | 'fixed16x88' | 'fixed16x96' | 'fixed16x104' | 'fixed16x112' | 'fixed16x120' | 'fixed16x128' | 'fixed16x136' | 'fixed16x144' | 'fixed16x152' | 'fixed16x160' | 'fixed16x168' | 'fixed16x176' | 'fixed16x184' | 'fixed16x192' | 'fixed16x200' | 'fixed16x208' | 'fixed16x216' | 'fixed16x224' | 'fixed16x232' | 'fixed16x240' | 'fixed24x8' | 'fixed24x16' | 'fixed24x24' | 'fixed24x32' | 'fixed24x40' | 'fixed24x48' | 'fixed24x56' | 'fixed24x64' | 'fixed24x72' | 'fixed24x80' | 'fixed24x88' | 'fixed24x96' | 'fixed24x104' | 'fixed24x112' | 'fixed24x120' | 'fixed24x128' | 'fixed24x136' | 'fixed24x144' | 'fixed24x152' | 'fixed24x160' | 'fixed24x168' | 'fixed24x176' | 'fixed24x184' | 'fixed24x192' | 'fixed24x200' | 'fixed24x208' | 'fixed24x216' | 'fixed24x224' | 'fixed24x232' | 'fixed32x8' | 'fixed32x16' | 'fixed32x24' | 'fixed32x32' | 'fixed32x40' | 'fixed32x48' | 'fixed32x56' | 'fixed32x64' | 'fixed32x72' | 'fixed32x80' | 'fixed32x88' | 'fixed32x96' | 'fixed32x104' | 'fixed32x112' | 'fixed32x120' | 'fixed32x128' | 'fixed32x136' | 'fixed32x144' | 'fixed32x152' | 'fixed32x160' | 'fixed32x168' | 'fixed32x176' | 'fixed32x184' | 'fixed32x192' | 'fixed32x200' | 'fixed32x208' | 'fixed32x216' | 'fixed32x224' | 'fixed40x8' | 'fixed40x16' | 'fixed40x24' | 'fixed40x32' | 'fixed40x40' | 'fixed40x48' | 'fixed40x56' | 'fixed40x64' | 'fixed40x72' | 'fixed40x80' | 'fixed40x88' | 'fixed40x96' | 'fixed40x104' | 'fixed40x112' | 'fixed40x120' | 'fixed40x128' | 'fixed40x136' | 'fixed40x144' | 'fixed40x152' | 'fixed40x160' | 'fixed40x168' | 'fixed40x176' | 'fixed40x184' | 'fixed40x192' | 'fixed40x200' | 'fixed40x208' | 'fixed40x216' | 'fixed48x8' | 'fixed48x16' | 'fixed48x24' | 'fixed48x32' | 'fixed48x40' | 'fixed48x48' | 'fixed48x56' | 'fixed48x64' | 'fixed48x72' | 'fixed48x80' | 'fixed48x88' | 'fixed48x96' | 'fixed48x104' | 'fixed48x112' | 'fixed48x120' | 'fixed48x128' | 'fixed48x136' | 'fixed48x144' | 'fixed48x152' | 'fixed48x160' | 'fixed48x168' | 'fixed48x176' | 'fixed48x184' | 'fixed48x192' | 'fixed48x200' | 'fixed48x208' | 'fixed56x8' | 'fixed56x16' | 'fixed56x24' | 'fixed56x32' | 'fixed56x40' | 'fixed56x48' | 'fixed56x56' | 'fixed56x64' | 'fixed56x72' | 'fixed56x80' | 'fixed56x88' | 'fixed56x96' | 'fixed56x104' | 'fixed56x112' | 'fixed56x120' | 'fixed56x128' | 'fixed56x136' | 'fixed56x144' | 'fixed56x152' | 'fixed56x160' | 'fixed56x168' | 'fixed56x176' | 'fixed56x184' | 'fixed56x192' | 'fixed56x200' | 'fixed64x8' | 'fixed64x16' | 'fixed64x24' | 'fixed64x32' | 'fixed64x40' | 'fixed64x48' | 'fixed64x56' | 'fixed64x64' | 'fixed64x72' | 'fixed64x80' | 'fixed64x88' | 'fixed64x96' | 'fixed64x104' | 'fixed64x112' | 'fixed64x120' | 'fixed64x128' | 'fixed64x136' | 'fixed64x144' | 'fixed64x152' | 'fixed64x160' | 'fixed64x168' | 'fixed64x176' | 'fixed64x184' | 'fixed64x192' | 'fixed72x8' | 'fixed72x16' | 'fixed72x24' | 'fixed72x32' | 'fixed72x40' | 'fixed72x48' | 'fixed72x56' | 'fixed72x64' | 'fixed72x72' | 'fixed72x80' | 'fixed72x88' | 'fixed72x96' | 'fixed72x104' | 'fixed72x112' | 'fixed72x120' | 'fixed72x128' | 'fixed72x136' | 'fixed72x144' | 'fixed72x152' | 'fixed72x160' | 'fixed72x168' | 'fixed72x176' | 'fixed72x184' | 'fixed80x8' | 'fixed80x16' | 'fixed80x24' | 'fixed80x32' | 'fixed80x40' | 'fixed80x48' | 'fixed80x56' | 'fixed80x64' | 'fixed80x72' | 'fixed80x80' | 'fixed80x88' | 'fixed80x96' | 'fixed80x104' | 'fixed80x112' | 'fixed80x120' | 'fixed80x128' | 'fixed80x136' | 'fixed80x144' | 'fixed80x152' | 'fixed80x160' | 'fixed80x168' | 'fixed80x176' | 'fixed88x8' | 'fixed88x16' | 'fixed88x24' | 'fixed88x32' | 'fixed88x40' | 'fixed88x48' | 'fixed88x56' | 'fixed88x64' | 'fixed88x72' | 'fixed88x80' | 'fixed88x88' | 'fixed88x96' | 'fixed88x104' | 'fixed88x112' | 'fixed88x120' | 'fixed88x128' | 'fixed88x136' | 'fixed88x144' | 'fixed88x152' | 'fixed88x160' | 'fixed88x168' | 'fixed96x8' | 'fixed96x16' | 'fixed96x24' | 'fixed96x32' | 'fixed96x40' | 'fixed96x48' | 'fixed96x56' | 'fixed96x64' | 'fixed96x72' | 'fixed96x80' | 'fixed96x88' | 'fixed96x96' | 'fixed96x104' | 'fixed96x112' | 'fixed96x120' | 'fixed96x128' | 'fixed96x136' | 'fixed96x144' | 'fixed96x152' | 'fixed96x160' | 'fixed104x8' | 'fixed104x16' | 'fixed104x24' | 'fixed104x32' | 'fixed104x40' | 'fixed104x48' | 'fixed104x56' | 'fixed104x64' | 'fixed104x72' | 'fixed104x80' | 'fixed104x88' | 'fixed104x96' | 'fixed104x104' | 'fixed104x112' | 'fixed104x120' | 'fixed104x128' | 'fixed104x136' | 'fixed104x144' | 'fixed104x152' | 'fixed112x8' | 'fixed112x16' | 'fixed112x24' | 'fixed112x32' | 'fixed112x40' | 'fixed112x48' | 'fixed112x56' | 'fixed112x64' | 'fixed112x72' | 'fixed112x80' | 'fixed112x88' | 'fixed112x96' | 'fixed112x104' | 'fixed112x112' | 'fixed112x120' | 'fixed112x128' | 'fixed112x136' | 'fixed112x144' | 'fixed120x8' | 'fixed120x16' | 'fixed120x24' | 'fixed120x32' | 'fixed120x40' | 'fixed120x48' | 'fixed120x56' | 'fixed120x64' | 'fixed120x72' | 'fixed120x80' | 'fixed120x88' | 'fixed120x96' | 'fixed120x104' | 'fixed120x112' | 'fixed120x120' | 'fixed120x128' | 'fixed120x136' | 'fixed128x8' | 'fixed128x16' | 'fixed128x24' | 'fixed128x32' | 'fixed128x40' | 'fixed128x48' | 'fixed128x56' | 'fixed128x64' | 'fixed128x72' | 'fixed128x80' | 'fixed128x88' | 'fixed128x96' | 'fixed128x104' | 'fixed128x112' | 'fixed128x120' | 'fixed128x128' | 'fixed136x8' | 'fixed136x16' | 'fixed136x24' | 'fixed136x32' | 'fixed136x40' | 'fixed136x48' | 'fixed136x56' | 'fixed136x64' | 'fixed136x72' | 'fixed136x80' | 'fixed136x88' | 'fixed136x96' | 'fixed136x104' | 'fixed136x112' | 'fixed136x120' | 'fixed144x8' | 'fixed144x16' | 'fixed144x24' | 'fixed144x32' | 'fixed144x40' | 'fixed144x48' | 'fixed144x56' | 'fixed144x64' | 'fixed144x72' | 'fixed144x80' | 'fixed144x88' | 'fixed144x96' | 'fixed144x104' | 'fixed144x112' | 'fixed152x8' | 'fixed152x16' | 'fixed152x24' | 'fixed152x32' | 'fixed152x40' | 'fixed152x48' | 'fixed152x56' | 'fixed152x64' | 'fixed152x72' | 'fixed152x80' | 'fixed152x88' | 'fixed152x96' | 'fixed152x104' | 'fixed160x8' | 'fixed160x16' | 'fixed160x24' | 'fixed160x32' | 'fixed160x40' | 'fixed160x48' | 'fixed160x56' | 'fixed160x64' | 'fixed160x72' | 'fixed160x80' | 'fixed160x88' | 'fixed160x96' | 'fixed168x8' | 'fixed168x16' | 'fixed168x24' | 'fixed168x32' | 'fixed168x40' | 'fixed168x48' | 'fixed168x56' | 'fixed168x64' | 'fixed168x72' | 'fixed168x80' | 'fixed168x88' | 'fixed176x8' | 'fixed176x16' | 'fixed176x24' | 'fixed176x32' | 'fixed176x40' | 'fixed176x48' | 'fixed176x56' | 'fixed176x64' | 'fixed176x72' | 'fixed176x80' | 'fixed184x8' | 'fixed184x16' | 'fixed184x24' | 'fixed184x32' | 'fixed184x40' | 'fixed184x48' | 'fixed184x56' | 'fixed184x64' | 'fixed184x72' | 'fixed192x8' | 'fixed192x16' | 'fixed192x24' | 'fixed192x32' | 'fixed192x40' | 'fixed192x48' | 'fixed192x56' | 'fixed192x64' | 'fixed200x8' | 'fixed200x16' | 'fixed200x24' | 'fixed200x32' | 'fixed200x40' | 'fixed200x48' | 'fixed200x56' | 'fixed208x8' | 'fixed208x16' | 'fixed208x24' | 'fixed208x32' | 'fixed208x40' | 'fixed208x48' | 'fixed216x8' | 'fixed216x16' | 'fixed216x24' | 'fixed216x32' | 'fixed216x40' | 'fixed224x8' | 'fixed224x16' | 'fixed224x24' | 'fixed224x32' | 'fixed232x8' | 'fixed232x16' | 'fixed232x24' | 'fixed240x8' | 'fixed240x16' | 'fixed248x8'
Ufixed = 'ufixed' | 'ufixed0x8' | 'ufixed0x16' | 'ufixed0x24' | 'ufixed0x32' | 'ufixed0x40' | 'ufixed0x48' | 'ufixed0x56' | 'ufixed0x64' | 'ufixed0x72' | 'ufixed0x80' | 'ufixed0x88' | 'ufixed0x96' | 'ufixed0x104' | 'ufixed0x112' | 'ufixed0x120' | 'ufixed0x128' | 'ufixed0x136' | 'ufixed0x144' | 'ufixed0x152' | 'ufixed0x160' | 'ufixed0x168' | 'ufixed0x176' | 'ufixed0x184' | 'ufixed0x192' | 'ufixed0x200' | 'ufixed0x208' | 'ufixed0x216' | 'ufixed0x224' | 'ufixed0x232' | 'ufixed0x240' | 'ufixed0x248' | 'ufixed0x256' | 'ufixed8x8' | 'ufixed8x16' | 'ufixed8x24' | 'ufixed8x32' | 'ufixed8x40' | 'ufixed8x48' | 'ufixed8x56' | 'ufixed8x64' | 'ufixed8x72' | 'ufixed8x80' | 'ufixed8x88' | 'ufixed8x96' | 'ufixed8x104' | 'ufixed8x112' | 'ufixed8x120' | 'ufixed8x128' | 'ufixed8x136' | 'ufixed8x144' | 'ufixed8x152' | 'ufixed8x160' | 'ufixed8x168' | 'ufixed8x176' | 'ufixed8x184' | 'ufixed8x192' | 'ufixed8x200' | 'ufixed8x208' | 'ufixed8x216' | 'ufixed8x224' | 'ufixed8x232' | 'ufixed8x240' | 'ufixed8x248' | 'ufixed16x8' | 'ufixed16x16' | 'ufixed16x24' | 'ufixed16x32' | 'ufixed16x40' | 'ufixed16x48' | 'ufixed16x56' | 'ufixed16x64' | 'ufixed16x72' | 'ufixed16x80' | 'ufixed16x88' | 'ufixed16x96' | 'ufixed16x104' | 'ufixed16x112' | 'ufixed16x120' | 'ufixed16x128' | 'ufixed16x136' | 'ufixed16x144' | 'ufixed16x152' | 'ufixed16x160' | 'ufixed16x168' | 'ufixed16x176' | 'ufixed16x184' | 'ufixed16x192' | 'ufixed16x200' | 'ufixed16x208' | 'ufixed16x216' | 'ufixed16x224' | 'ufixed16x232' | 'ufixed16x240' | 'ufixed24x8' | 'ufixed24x16' | 'ufixed24x24' | 'ufixed24x32' | 'ufixed24x40' | 'ufixed24x48' | 'ufixed24x56' | 'ufixed24x64' | 'ufixed24x72' | 'ufixed24x80' | 'ufixed24x88' | 'ufixed24x96' | 'ufixed24x104' | 'ufixed24x112' | 'ufixed24x120' | 'ufixed24x128' | 'ufixed24x136' | 'ufixed24x144' | 'ufixed24x152' | 'ufixed24x160' | 'ufixed24x168' | 'ufixed24x176' | 'ufixed24x184' | 'ufixed24x192' | 'ufixed24x200' | 'ufixed24x208' | 'ufixed24x216' | 'ufixed24x224' | 'ufixed24x232' | 'ufixed32x8' | 'ufixed32x16' | 'ufixed32x24' | 'ufixed32x32' | 'ufixed32x40' | 'ufixed32x48' | 'ufixed32x56' | 'ufixed32x64' | 'ufixed32x72' | 'ufixed32x80' | 'ufixed32x88' | 'ufixed32x96' | 'ufixed32x104' | 'ufixed32x112' | 'ufixed32x120' | 'ufixed32x128' | 'ufixed32x136' | 'ufixed32x144' | 'ufixed32x152' | 'ufixed32x160' | 'ufixed32x168' | 'ufixed32x176' | 'ufixed32x184' | 'ufixed32x192' | 'ufixed32x200' | 'ufixed32x208' | 'ufixed32x216' | 'ufixed32x224' | 'ufixed40x8' | 'ufixed40x16' | 'ufixed40x24' | 'ufixed40x32' | 'ufixed40x40' | 'ufixed40x48' | 'ufixed40x56' | 'ufixed40x64' | 'ufixed40x72' | 'ufixed40x80' | 'ufixed40x88' | 'ufixed40x96' | 'ufixed40x104' | 'ufixed40x112' | 'ufixed40x120' | 'ufixed40x128' | 'ufixed40x136' | 'ufixed40x144' | 'ufixed40x152' | 'ufixed40x160' | 'ufixed40x168' | 'ufixed40x176' | 'ufixed40x184' | 'ufixed40x192' | 'ufixed40x200' | 'ufixed40x208' | 'ufixed40x216' | 'ufixed48x8' | 'ufixed48x16' | 'ufixed48x24' | 'ufixed48x32' | 'ufixed48x40' | 'ufixed48x48' | 'ufixed48x56' | 'ufixed48x64' | 'ufixed48x72' | 'ufixed48x80' | 'ufixed48x88' | 'ufixed48x96' | 'ufixed48x104' | 'ufixed48x112' | 'ufixed48x120' | 'ufixed48x128' | 'ufixed48x136' | 'ufixed48x144' | 'ufixed48x152' | 'ufixed48x160' | 'ufixed48x168' | 'ufixed48x176' | 'ufixed48x184' | 'ufixed48x192' | 'ufixed48x200' | 'ufixed48x208' | 'ufixed56x8' | 'ufixed56x16' | 'ufixed56x24' | 'ufixed56x32' | 'ufixed56x40' | 'ufixed56x48' | 'ufixed56x56' | 'ufixed56x64' | 'ufixed56x72' | 'ufixed56x80' | 'ufixed56x88' | 'ufixed56x96' | 'ufixed56x104' | 'ufixed56x112' | 'ufixed56x120' | 'ufixed56x128' | 'ufixed56x136' | 'ufixed56x144' | 'ufixed56x152' | 'ufixed56x160' | 'ufixed56x168' | 'ufixed56x176' | 'ufixed56x184' | 'ufixed56x192' | 'ufixed56x200' | 'ufixed64x8' | 'ufixed64x16' | 'ufixed64x24' | 'ufixed64x32' | 'ufixed64x40' | 'ufixed64x48' | 'ufixed64x56' | 'ufixed64x64' | 'ufixed64x72' | 'ufixed64x80' | 'ufixed64x88' | 'ufixed64x96' | 'ufixed64x104' | 'ufixed64x112' | 'ufixed64x120' | 'ufixed64x128' | 'ufixed64x136' | 'ufixed64x144' | 'ufixed64x152' | 'ufixed64x160' | 'ufixed64x168' | 'ufixed64x176' | 'ufixed64x184' | 'ufixed64x192' | 'ufixed72x8' | 'ufixed72x16' | 'ufixed72x24' | 'ufixed72x32' | 'ufixed72x40' | 'ufixed72x48' | 'ufixed72x56' | 'ufixed72x64' | 'ufixed72x72' | 'ufixed72x80' | 'ufixed72x88' | 'ufixed72x96' | 'ufixed72x104' | 'ufixed72x112' | 'ufixed72x120' | 'ufixed72x128' | 'ufixed72x136' | 'ufixed72x144' | 'ufixed72x152' | 'ufixed72x160' | 'ufixed72x168' | 'ufixed72x176' | 'ufixed72x184' | 'ufixed80x8' | 'ufixed80x16' | 'ufixed80x24' | 'ufixed80x32' | 'ufixed80x40' | 'ufixed80x48' | 'ufixed80x56' | 'ufixed80x64' | 'ufixed80x72' | 'ufixed80x80' | 'ufixed80x88' | 'ufixed80x96' | 'ufixed80x104' | 'ufixed80x112' | 'ufixed80x120' | 'ufixed80x128' | 'ufixed80x136' | 'ufixed80x144' | 'ufixed80x152' | 'ufixed80x160' | 'ufixed80x168' | 'ufixed80x176' | 'ufixed88x8' | 'ufixed88x16' | 'ufixed88x24' | 'ufixed88x32' | 'ufixed88x40' | 'ufixed88x48' | 'ufixed88x56' | 'ufixed88x64' | 'ufixed88x72' | 'ufixed88x80' | 'ufixed88x88' | 'ufixed88x96' | 'ufixed88x104' | 'ufixed88x112' | 'ufixed88x120' | 'ufixed88x128' | 'ufixed88x136' | 'ufixed88x144' | 'ufixed88x152' | 'ufixed88x160' | 'ufixed88x168' | 'ufixed96x8' | 'ufixed96x16' | 'ufixed96x24' | 'ufixed96x32' | 'ufixed96x40' | 'ufixed96x48' | 'ufixed96x56' | 'ufixed96x64' | 'ufixed96x72' | 'ufixed96x80' | 'ufixed96x88' | 'ufixed96x96' | 'ufixed96x104' | 'ufixed96x112' | 'ufixed96x120' | 'ufixed96x128' | 'ufixed96x136' | 'ufixed96x144' | 'ufixed96x152' | 'ufixed96x160' | 'ufixed104x8' | 'ufixed104x16' | 'ufixed104x24' | 'ufixed104x32' | 'ufixed104x40' | 'ufixed104x48' | 'ufixed104x56' | 'ufixed104x64' | 'ufixed104x72' | 'ufixed104x80' | 'ufixed104x88' | 'ufixed104x96' | 'ufixed104x104' | 'ufixed104x112' | 'ufixed104x120' | 'ufixed104x128' | 'ufixed104x136' | 'ufixed104x144' | 'ufixed104x152' | 'ufixed112x8' | 'ufixed112x16' | 'ufixed112x24' | 'ufixed112x32' | 'ufixed112x40' | 'ufixed112x48' | 'ufixed112x56' | 'ufixed112x64' | 'ufixed112x72' | 'ufixed112x80' | 'ufixed112x88' | 'ufixed112x96' | 'ufixed112x104' | 'ufixed112x112' | 'ufixed112x120' | 'ufixed112x128' | 'ufixed112x136' | 'ufixed112x144' | 'ufixed120x8' | 'ufixed120x16' | 'ufixed120x24' | 'ufixed120x32' | 'ufixed120x40' | 'ufixed120x48' | 'ufixed120x56' | 'ufixed120x64' | 'ufixed120x72' | 'ufixed120x80' | 'ufixed120x88' | 'ufixed120x96' | 'ufixed120x104' | 'ufixed120x112' | 'ufixed120x120' | 'ufixed120x128' | 'ufixed120x136' | 'ufixed128x8' | 'ufixed128x16' | 'ufixed128x24' | 'ufixed128x32' | 'ufixed128x40' | 'ufixed128x48' | 'ufixed128x56' | 'ufixed128x64' | 'ufixed128x72' | 'ufixed128x80' | 'ufixed128x88' | 'ufixed128x96' | 'ufixed128x104' | 'ufixed128x112' | 'ufixed128x120' | 'ufixed128x128' | 'ufixed136x8' | 'ufixed136x16' | 'ufixed136x24' | 'ufixed136x32' | 'ufixed136x40' | 'ufixed136x48' | 'ufixed136x56' | 'ufixed136x64' | 'ufixed136x72' | 'ufixed136x80' | 'ufixed136x88' | 'ufixed136x96' | 'ufixed136x104' | 'ufixed136x112' | 'ufixed136x120' | 'ufixed144x8' | 'ufixed144x16' | 'ufixed144x24' | 'ufixed144x32' | 'ufixed144x40' | 'ufixed144x48' | 'ufixed144x56' | 'ufixed144x64' | 'ufixed144x72' | 'ufixed144x80' | 'ufixed144x88' | 'ufixed144x96' | 'ufixed144x104' | 'ufixed144x112' | 'ufixed152x8' | 'ufixed152x16' | 'ufixed152x24' | 'ufixed152x32' | 'ufixed152x40' | 'ufixed152x48' | 'ufixed152x56' | 'ufixed152x64' | 'ufixed152x72' | 'ufixed152x80' | 'ufixed152x88' | 'ufixed152x96' | 'ufixed152x104' | 'ufixed160x8' | 'ufixed160x16' | 'ufixed160x24' | 'ufixed160x32' | 'ufixed160x40' | 'ufixed160x48' | 'ufixed160x56' | 'ufixed160x64' | 'ufixed160x72' | 'ufixed160x80' | 'ufixed160x88' | 'ufixed160x96' | 'ufixed168x8' | 'ufixed168x16' | 'ufixed168x24' | 'ufixed168x32' | 'ufixed168x40' | 'ufixed168x48' | 'ufixed168x56' | 'ufixed168x64' | 'ufixed168x72' | 'ufixed168x80' | 'ufixed168x88' | 'ufixed176x8' | 'ufixed176x16' | 'ufixed176x24' | 'ufixed176x32' | 'ufixed176x40' | 'ufixed176x48' | 'ufixed176x56' | 'ufixed176x64' | 'ufixed176x72' | 'ufixed176x80' | 'ufixed184x8' | 'ufixed184x16' | 'ufixed184x24' | 'ufixed184x32' | 'ufixed184x40' | 'ufixed184x48' | 'ufixed184x56' | 'ufixed184x64' | 'ufixed184x72' | 'ufixed192x8' | 'ufixed192x16' | 'ufixed192x24' | 'ufixed192x32' | 'ufixed192x40' | 'ufixed192x48' | 'ufixed192x56' | 'ufixed192x64' | 'ufixed200x8' | 'ufixed200x16' | 'ufixed200x24' | 'ufixed200x32' | 'ufixed200x40' | 'ufixed200x48' | 'ufixed200x56' | 'ufixed208x8' | 'ufixed208x16' | 'ufixed208x24' | 'ufixed208x32' | 'ufixed208x40' | 'ufixed208x48' | 'ufixed216x8' | 'ufixed216x16' | 'ufixed216x24' | 'ufixed216x32' | 'ufixed216x40' | 'ufixed224x8' | 'ufixed224x16' | 'ufixed224x24' | 'ufixed224x32' | 'ufixed232x8' | 'ufixed232x16' | 'ufixed232x24' | 'ufixed240x8' | 'ufixed240x16' | 'ufixed248x8'
+
+InlineAssemblyBlock = '{' AssemblyItem* '}'
+
+AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
+AssemblyLocalBinding = 'let' Identifier ':=' FunctionalAssemblyExpression
+AssemblyAssignment = ( Identifier ':=' FunctionalAssemblyExpression ) | ( '=:' Identifier )
+AssemblyLabel = Identifier ':'
+FunctionalAssemblyExpression = Identifier '(' AssemblyItem? ( ',' AssemblyItem )* ')'
diff --git a/docs/index.rst b/docs/index.rst
index 9bee1515..3cdda62d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,8 +1,13 @@
Solidity
========
+.. image:: logo.svg
+ :width: 120px
+ :alt: Solidity logo
+ :align: center
+
Solidity is a contract-oriented, high-level language whose syntax is similar to that of JavaScript
-and it is designed to target the Ethereum Virtual Machine.
+and it is designed to target the Ethereum Virtual Machine (EVM).
Solidity is statically typed, supports inheritance, libraries and complex
user-defined types among other features.
@@ -11,8 +16,8 @@ As you will see, it is possible to create contracts for voting,
crowdfunding, blind auctions, multi-signature wallets and more.
.. note::
- The best way to try out Solidity right now is using the
- `Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_
+ The best way to try out Solidity right now is using
+ `Remix <https://remix.ethereum.org/>`_
(it can take a while to load, please be patient).
Useful links
@@ -33,24 +38,30 @@ Useful links
Available Solidity Integrations
-------------------------------
-* `Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_
+* `Remix <https://remix.ethereum.org/>`_
Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components.
* `Ethereum Studio <https://live.ether.camp/>`_
Specialized web IDE that also provides shell access to a complete Ethereum environment.
+* `IntelliJ IDEA plugin <https://plugins.jetbrains.com/plugin/9475-intellij-solidity>`_
+ Solidity plugin for IntelliJ IDEA (and all other JetBrains IDEs)
+
* `Visual Studio Extension <https://visualstudiogallery.msdn.microsoft.com/96221853-33c4-4531-bdd5-d2ea5acc4799/>`_
Solidity plugin for Microsoft Visual Studio that includes the Solidity compiler.
* `Package for SublimeText — Solidity language syntax <https://packagecontrol.io/packages/Ethereum/>`_
Solidity syntax highlighting for SublimeText editor.
-* `Atom Ethereum interface <https://github.com/gmtDevs/atom-ethereum-interface>`_
- Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (requires backend node).
+* `Etheratom <https://github.com/0mkara/etheratom>`_
+ Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (Backend node & VM compatible).
* `Atom Solidity Linter <https://atom.io/packages/linter-solidity>`_
Plugin for the Atom editor that provides Solidity linting.
-
+
+* `Atom Solium Linter <https://atom.io/packages/linter-solium>`_
+ Configurable Solidty linter for Atom using Solium as a base.
+
* `Solium <https://github.com/duaraghav8/Solium/>`_
A commandline linter for Solidity which strictly follows the rules prescribed by the `Solidity Style Guide <http://solidity.readthedocs.io/en/latest/style-guide.html>`_.
@@ -75,8 +86,8 @@ Discontinued:
Solidity Tools
--------------
-* `Dapple <https://github.com/nexusdev/dapple>`_
- Package and deployment manager for Solidity.
+* `Dapp <https://dapp.readthedocs.io>`_
+ Build tool, package manager, and deployment assistant for Solidity.
* `Solidity REPL <https://github.com/raineorshine/solidity-repl>`_
Try Solidity instantly with a command-line Solidity console.
@@ -87,6 +98,18 @@ Solidity Tools
* `evmdis <https://github.com/Arachnid/evmdis>`_
EVM Disassembler that performs static analysis on the bytecode to provide a higher level of abstraction than raw EVM operations.
+* `Doxity <https://github.com/DigixGlobal/doxity>`_
+ Documentation Generator for Solidity.
+
+Third-Party Solidity Parsers and Grammars
+-----------------------------------------
+
+* `solidity-parser <https://github.com/ConsenSys/solidity-parser>`_
+ Solidity parser for JavaScript
+
+* `Solidity Grammar for ANTLR 4 <https://github.com/federicobond/solidity-antlr4>`_
+ Solidity grammar for the ANTLR 4 parser generator
+
Language Documentation
----------------------
@@ -97,7 +120,7 @@ and the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`.
The next section will explain several *features* of Solidity by giving
useful :ref:`example contracts <voting>`
Remember that you can always try out the contracts
-`in your browser <https://ethereum.github.io/browser-solidity>`_!
+`in your browser <https://remix.ethereum.org>`_!
The last and most extensive section will cover all aspects of Solidity in depth.
@@ -121,7 +144,10 @@ Contents
solidity-by-example.rst
solidity-in-depth.rst
security-considerations.rst
+ using-the-compiler.rst
+ abi-spec.rst
style-guide.rst
common-patterns.rst
+ bugs.rst
contributing.rst
frequently-asked-questions.rst
diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst
index ef38705c..9b5ba9f2 100644
--- a/docs/installing-solidity.rst
+++ b/docs/installing-solidity.rst
@@ -15,34 +15,35 @@ are not guaranteed to be working and despite best efforts they might contain und
and/or broken changes. We recommend using the latest release. Package installers below
will use the latest release.
-Browser-Solidity
-================
+Remix
+=====
If you just want to try Solidity for small contracts, you
-can try `browser-solidity <https://ethereum.github.io/browser-solidity>`_
+can try `Remix <https://remix.ethereum.org/>`_
which does not need any installation. If you want to use it
without connection to the Internet, you can go to
https://github.com/ethereum/browser-solidity/tree/gh-pages and
download the .ZIP file as explained on that page.
-
npm / Node.js
=============
This is probably the most portable and most convenient way to install Solidity locally.
A platform-independent JavaScript library is provided by compiling the C++ source
-into JavaScript using Emscripten for browser-solidity and there is also an npm
-package available.
+into JavaScript using Emscripten. It can be used in projects directly (such as Remix).
+Please refer to the `solc-js <https://github.com/ethereum/solc-js>`_ repository for instructions.
-To install it, simply use
+It also contains a commandline tool called `solcjs`, which can be installed via npm:
.. code:: bash
- npm install solc
+ npm install -g solc
+
+.. note::
-Details about the usage of the Node.js package can be found in the
-`solc-js repository <https://github.com/ethereum/solc-js>`_.
+ The comandline options of `solcjs` are not compatible with `solc` and tools (such as `geth`)
+ expecting the behaviour of `solc` will not work with `solcjs`.
Docker
======
@@ -82,6 +83,12 @@ If you want to use the cutting edge developer version:
sudo apt-get update
sudo apt-get install solc
+Arch Linux also has packages, albeit limited to the latest development version:
+
+.. code:: bash
+
+ pacman -S solidity-git
+
Homebrew is missing pre-built bottles at the time of writing,
following a Jenkins to TravisCI migration, but Homebrew
should still work just fine as a means to build-from-source.
@@ -95,6 +102,28 @@ We will re-add the pre-built bottles soon.
brew install solidity
brew linkapps solidity
+If you need a specific version of Solidity you can install a
+Homebrew formula directly from Github.
+
+View
+`solidity.rb commits on Github <https://github.com/ethereum/homebrew-ethereum/commits/master/solidity.rb>`_.
+
+Follow the history links until you have a raw file link of a
+specific commit of ``solidity.rb``.
+
+Install it using ``brew``:
+
+.. code:: bash
+
+ brew unlink solidity
+ # Install 0.4.8
+ brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
+
+Gentoo Linux also provides a solidity package that can be installed using ``emerge``:
+
+.. code:: bash
+
+ demerge ev-lang/solidity
.. _building-from-source:
@@ -119,6 +148,11 @@ you should fork Solidity and add your personal fork as a second remote:
cd solidity
git remote add personal git@github.com:[username]/solidity.git
+Solidity has git submodules. Ensure they are properly loaded:
+
+.. code:: bash
+
+ git submodule update --init --recursive
Prerequisites - macOS
---------------------
@@ -192,7 +226,14 @@ Building Solidity is quite similar on Linux, macOS and other Unices:
cd build
cmake .. && make
-And even on Windows:
+or even easier:
+
+.. code:: bash
+
+ #note: this will install binaries solc and soltest at usr/local/bin
+ ./scripts/build.sh
+
+And even for Windows:
.. code:: bash
@@ -211,6 +252,25 @@ Alternatively, you can build for Windows on the command-line, like so:
cmake --build . --config RelWithDebInfo
+The version string in detail
+============================
+
+The Solidity version string contains four parts:
+
+- the version number
+- pre-release tag, usually set to ``develop.YYYY.MM.DD`` or ``nightly.YYYY.MM.DD``
+- commit in the format of ``commit.GITHASH``
+- platform has arbitrary number of items, containing details about the platform and compiler
+
+If there are local modifications, the commit will be postfixed with ``.mod``.
+
+These parts are combined as required by Semver, where the Solidity pre-release tag equals to the Semver pre-release
+and the Solidity commit and platform combined make up the Semver build metadata.
+
+A release example: ``0.4.8+commit.60cc1668.Emscripten.clang``.
+
+A pre-release example: ``0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang``
+
Important information about versioning
======================================
@@ -227,4 +287,4 @@ Example:
3. a breaking change is introduced - version is bumped to 0.5.0
4. the 0.5.0 release is made
-This behaviour works well with the version pragma.
+This behaviour works well with the :ref:`version pragma <version_pragma>`.
diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst
index aee1e03b..dc7c6cc9 100644
--- a/docs/introduction-to-smart-contracts.rst
+++ b/docs/introduction-to-smart-contracts.rst
@@ -33,9 +33,11 @@ Storage
The first line simply tells that the source code is written for
Solidity version 0.4.0 or anything newer that does not break functionality
(up to, but not including, version 0.5.0). This is to ensure that the
-contract does not suddenly behave differently with a new compiler version.
+contract does not suddenly behave differently with a new compiler version. The keyword ``pragma`` is called that way because, in general,
+pragmas are instructions for the compiler about how to treat the
+source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_). .
-A contract in the sense of Solidity is a collection of code (its functions) and
+A contract in the sense of Solidity is a collection of code (its *functions*) and
data (its *state*) that resides at a specific address on the Ethereum
blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of
type ``uint`` (unsigned integer of 256 bits). You can think of it as a single slot
@@ -47,9 +49,9 @@ or retrieve the value of the variable.
To access a state variable, you do not need the prefix ``this.`` as is common in
other languages.
-This contract does not yet do much apart from (due to the infrastructure
-built by Ethereum) allowing anyone to store a single number that is accessible by
-anyone in the world without (feasible) a way to prevent you from publishing
+This contract does not do much yet (due to the infrastructure
+built by Ethereum) apart from allowing anyone to store a single number that is accessible by
+anyone in the world without a (feasible) way to prevent you from publishing
this number. Of course, anyone could just call ``set`` again with a different value
and overwrite your number, but the number will still be stored in the history
of the blockchain. Later, we will see how you can impose access restrictions
@@ -109,8 +111,7 @@ that does not allow any arithmetic operations. It is suitable for
storing addresses of contracts or keypairs belonging to external
persons. The keyword ``public`` automatically generates a function that
allows you to access the current value of the state variable.
-Without this keyword, other contracts have no way to access the variable
-and only the code of this contract can write to it.
+Without this keyword, other contracts have no way to access the variable.
The function will look something like this::
function minter() returns (address) { return minter; }
@@ -125,14 +126,14 @@ get the idea - the compiler figures that out for you.
The next line, ``mapping (address => uint) public balances;`` also
creates a public state variable, but it is a more complex datatype.
The type maps addresses to unsigned integers.
-Mappings can be seen as hashtables which are
+Mappings can be seen as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_ which are
virtually initialized such that every possible key exists and is mapped to a
value whose byte-representation is all zeros. This analogy does not go
too far, though, as it is neither possible to obtain a list of all keys of
a mapping, nor a list of all values. So either keep in mind (or
better, keep a list or use a more advanced data type) what you
added to the mapping or use it in a context where this is not needed,
-like this one. The accessor function created by the ``public`` keyword
+like this one. The getter function created by the ``public`` keyword
is a bit more complex in this case. It roughly looks like the
following::
@@ -147,7 +148,7 @@ single account.
The line ``event Sent(address from, address to, uint amount);`` declares
a so-called "event" which is fired in the last line of the function
-``send``. User interfaces (as well as server appliances of course) can
+``send``. User interfaces (as well as server applications of course) can
listen for those events being fired on the blockchain without much
cost. As soon as it is fired, the listener will also receive the
arguments ``from``, ``to`` and ``amount``, which makes it easy to track
@@ -162,7 +163,7 @@ transactions. In order to listen for this event, you would use ::
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
}
- }
+ })
Note how the automatically generated function ``balances`` is called from
the user interface.
@@ -194,7 +195,7 @@ Blockchain Basics
*****************
Blockchains as a concept are not too hard to understand for programmers. The reason is that
-most of the complications (mining, hashing, elliptic-curve cryptography, peer-to-peer networks, ...)
+most of the complications (mining, `hashing <https://en.wikipedia.org/wiki/Cryptographic_hash_function>`_, `elliptic-curve cryptography <https://en.wikipedia.org/wiki/Elliptic_curve_cryptography>`_, `peer-to-peer networks <https://en.wikipedia.org/wiki/Peer-to-peer>`_, etc.)
are just there to provide a certain set of features and promises. Once you accept these
features as given, you do not have to worry about the underlying technology - or do you have
to know how Amazon's AWS works internally in order to use it?
@@ -283,8 +284,8 @@ determined at the time the contract is created
(it is derived from the creator address and the number
of transactions sent from that address, the so-called "nonce").
-Apart from the fact whether an account stores code or not,
-the EVM treats the two types equally, though.
+Regardless of whether or not the account stores code, the two types are
+treated equally by the EVM.
Every account has a persistent key-value store mapping 256-bit words to 256-bit
words called **storage**.
@@ -403,7 +404,7 @@ such situations, so that exceptions "bubble up" the call stack.
As already said, the called contract (which can be the same as the caller)
will receive a freshly cleared instance of memory and has access to the
call payload - which will be provided in a separate area called the **calldata**.
-After it finished execution, it can return data which will be stored at
+After it has finished execution, it can return data which will be stored at
a location in the caller's memory preallocated by the caller.
Calls are **limited** to a depth of 1024, which means that for more complex
@@ -424,8 +425,8 @@ address at runtime. Storage, current address and balance still
refer to the calling contract, only the code is taken from the called address.
This makes it possible to implement the "library" feature in Solidity:
-Reusable library code that can be applied to a contract's storage in
-order to e.g. implement a complex data structure.
+Reusable library code that can be applied to a contract's storage, e.g. in
+order to implement a complex data structure.
.. index:: log
@@ -437,7 +438,7 @@ that maps all the way up to the block level. This feature called **logs**
is used by Solidity in order to implement **events**.
Contracts cannot access log data after it has been created, but they
can be efficiently accessed from outside the blockchain.
-Since some part of the log data is stored in bloom filters, it is
+Since some part of the log data is stored in `bloom filters <https://en.wikipedia.org/wiki/Bloom_filter>`_, it is
possible to search for this data in an efficient and cryptographically
secure way, so network peers that do not download the whole blockchain
("light clients") can still find these logs.
diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst
index 17ac8c6f..e4b403f6 100644
--- a/docs/layout-of-source-files.rst
+++ b/docs/layout-of-source-files.rst
@@ -7,6 +7,8 @@ and pragma directives.
.. index:: ! pragma, version
+.. _version_pragma:
+
Version Pragma
==============
@@ -24,14 +26,14 @@ The version pragma is used as follows::
pragma solidity ^0.4.0;
Such a source file will not compile with a compiler earlier than version 0.4.0
-and it will also not work on a compiler starting form version 0.5.0 (this
+and it will also not work on a compiler starting from version 0.5.0 (this
second condition is added by using ``^``). The idea behind this is that
there will be no breaking changes until version ``0.5.0``, so we can always
be sure that our code will compile the way we intended it to. We do not fix
the exact version of the compiler, so that bugfix releases are still possible.
It is possible to specify much more complex rules for the compiler version,
-the expression follows those used by npm.
+the expression follows those used by `npm <https://docs.npmjs.com/misc/semver>`_.
.. index:: source file, ! import
@@ -79,8 +81,9 @@ Paths
-----
In the above, ``filename`` is always treated as a path with ``/`` as directory separator,
-``.`` as the current and ``..`` as the parent directory. Path names that do not start
-with ``.`` are treated as absolute paths.
+``.`` as the current and ``..`` as the parent directory. When ``.`` or ``..`` is followed by a character except ``/``,
+it is not considered as the current or the parent directory.
+All path names are treated as absolute paths unless they start with the current ``.`` or the parent directory ``..``.
To import a file ``x`` from the same directory as the current file, use ``import "./x" as x;``.
If you use ``import "x" as x;`` instead, a different file could be referenced
@@ -96,8 +99,8 @@ Use in Actual Compilers
When the compiler is invoked, it is not only possible to specify how to
discover the first element of a path, but it is possible to specify path prefix
remappings so that e.g. ``github.com/ethereum/dapp-bin/library`` is remapped to
-``/usr/local/dapp-bin/library`` and the compiler will read the files from there. If
-remapping keys are prefixes of each other, the longest is tried first. This
+``/usr/local/dapp-bin/library`` and the compiler will read the files from there.
+If multiple remappings can be applied, the one with the longest key is tried first. This
allows for a "fallback-remapping" with e.g. ``""`` maps to
``"/usr/local/include/solidity"``. Furthermore, these remappings can
depend on the context, which allows you to configure packages to
@@ -150,9 +153,9 @@ remapping ``=/``.
If there are multiple remappings that lead to a valid file, the remapping
with the longest common prefix is chosen.
-**browser-solidity**:
+**Remix**:
-The `browser-based compiler <https://ethereum.github.io/browser-solidity>`_
+`Remix <https://remix.ethereum.org/>`_
provides an automatic remapping for github and will also automatically retrieve
the file over the network:
You can import the iterable mapping by e.g.
@@ -182,7 +185,7 @@ Additionally, there is another type of comment called a natspec comment,
for which the documentation is not yet written. They are written with a
triple slash (``///``) or a double asterisk block(``/** ... */``) and
they should be used directly above function declarations or statements.
-You can use Doxygen-style tags inside these comments to document
+You can use `Doxygen <https://en.wikipedia.org/wiki/Doxygen>`_-style tags inside these comments to document
functions, annotate conditions for formal verification, and provide a
**confirmation text** which is shown to users when they attempt to invoke a
function.
diff --git a/docs/logo.svg b/docs/logo.svg
new file mode 100644
index 00000000..86b9f499
--- /dev/null
+++ b/docs/logo.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1300px" height="1300px"
+ viewBox="0 0 1300 1300" enable-background="new 0 0 1300 1300" xml:space="preserve">
+<title>Vector 1</title>
+<desc>Created with Sketch.</desc>
+<g id="Page-1" sketch:type="MSPage">
+ <g id="solidity" transform="translate(402.000000, 118.000000)" sketch:type="MSLayerGroup">
+ <g id="Group" sketch:type="MSShapeGroup">
+ <path id="Shape" opacity="0.45" enable-background="new " d="M371.772,135.308L241.068,367.61H-20.158l130.614-232.302
+ H371.772"/>
+ <path id="Shape_1_" opacity="0.6" enable-background="new " d="M241.068,367.61h261.318L371.772,135.308H110.456
+ L241.068,367.61z"/>
+ <path id="Shape_2_" opacity="0.8" enable-background="new " d="M110.456,599.822L241.068,367.61L110.456,135.308
+ L-20.158,367.61L110.456,599.822z"/>
+ <path id="Shape_3_" opacity="0.45" enable-background="new " d="M111.721,948.275l130.704-232.303h261.318L373.038,948.275
+ H111.721"/>
+ <path id="Shape_4_" opacity="0.6" enable-background="new " d="M242.424,715.973H-18.893l130.613,232.303h261.317
+ L242.424,715.973z"/>
+ <path id="Shape_5_" opacity="0.8" enable-background="new " d="M373.038,483.761L242.424,715.973l130.614,232.303
+ l130.704-232.303L373.038,483.761z"/>
+ </g>
+ </g>
+</g>
+</svg>
diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst
index 1754752d..182de33a 100644
--- a/docs/miscellaneous.rst
+++ b/docs/miscellaneous.rst
@@ -56,6 +56,8 @@ So for the following contract snippet::
The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``.
+.. index: memory layout
+
****************
Layout in Memory
****************
@@ -72,7 +74,8 @@ Solidity always places new objects at the free memory pointer and memory is neve
.. warning::
There are some operations in Solidity that need a temporary memory area larger than 64 bytes and therefore will not fit into the scratch space. They will be placed where the free memory points to, but given their short lifecycle, the pointer is not updated. The memory may or may not be zeroed out. Because of this, one shouldn't expect the free memory to be zeroed out.
-.. index: memory layout
+
+.. index: calldata layout
*******************
Layout of Call Data
@@ -85,29 +88,54 @@ specification
ABI specification requires arguments to be padded to multiples of 32
bytes. The internal function calls use a different convention.
-*****************
-Esoteric Features
-*****************
-
-There are some types in Solidity's type system that have no counterpart in the syntax. One of these types are the types of functions. But still, using ``var`` it is possible to have local variables of these types::
- contract FunctionSelector {
- function select(bool useB, uint x) returns (uint z) {
- var f = a;
- if (useB) f = b;
- return f(x);
- }
-
- function a(uint x) returns (uint z) {
- return x * x;
- }
-
- function b(uint x) returns (uint z) {
- return 2 * x;
- }
- }
-
-Calling ``select(false, x)`` will compute ``x * x`` and ``select(true, x)`` will compute ``2 * x``.
+.. index: variable cleanup
+
+*********************************
+Internals - Cleaning Up Variables
+*********************************
+
+When a value is shorter than 256-bit, in some cases the remaining bits
+must be cleaned.
+The Solidity compiler is designed to clean such remaining bits before any operations
+that might be adversely affected by the potential garbage in the remaining bits.
+For example, before writing a value to the memory, the remaining bits need
+to be cleared because the memory contents can be used for computing
+hashes or sent as the data of a message call. Similarly, before
+storing a value in the storage, the remaining bits need to be cleaned
+because otherwise the garbled value can be observed.
+
+On the other hand, we do not clean the bits if the immediately
+following operation is not affected. For instance, since any non-zero
+value is considered ``true`` by ``JUMPI`` instruction, we do not clean
+the boolean values before they are used as the condition for
+``JUMPI``.
+
+In addition to the design principle above, the Solidity compiler
+cleans input data when it is loaded onto the stack.
+
+Different types have different rules for cleaning up invalid values:
+
++---------------+---------------+-------------------+
+|Type |Valid Values |Invalid Values Mean|
++===============+===============+===================+
+|enum of n |0 until n - 1 |exception |
+|members | | |
++---------------+---------------+-------------------+
+|bool |0 or 1 |1 |
++---------------+---------------+-------------------+
+|signed integers|sign-extended |currently silently |
+| |word |wraps; in the |
+| | |future exceptions |
+| | |will be thrown |
+| | | |
+| | | |
++---------------+---------------+-------------------+
+|unsigned |higher bits |currently silently |
+|integers |zeroed |wraps; in the |
+| | |future exceptions |
+| | |will be thrown |
++---------------+---------------+-------------------+
.. index:: optimizer, common subexpression elimination, constant propagation
@@ -193,45 +221,6 @@ This means the following source mappings represent the same information:
``1:2:1;:9;2::2;;``
-
-.. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker
-
-.. _commandline-compiler:
-
-******************************
-Using the Commandline Compiler
-******************************
-
-One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler.
-Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
-If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
-
-The commandline compiler will automatically read imported files from the filesystem, but
-it is also possible to provide path redirects using ``context:prefix=path`` in the following way:
-
-::
-
- solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol
-
-This essentially instructs the compiler to search for anything starting with
-``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin`` and if it does not
-find the file there, it will look at ``/usr/local/lib/fallback`` (the empty prefix
-always matches). ``solc`` will not read files from the filesystem that lie outside of
-the remapping targets and outside of the directories where explicitly specified source
-files reside, so things like ``import "/etc/passwd";`` only work if you add ``=/`` as a remapping.
-
-You can restrict remappings to only certain source files by prefixing a context.
-
-The section on :ref:`import` provides more details on remappings.
-
-If there are multiple matches due to remappings, the one with the longest common prefix is selected.
-
-If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points:
-
-Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``.
-
-If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case.
-
*****************
Contract Metadata
*****************
@@ -281,10 +270,10 @@ Comments are of course also not permitted and used here only for explanatory pur
"myFile.sol": {
// Required: keccak256 hash of the source file
"keccak256": "0x123...",
- // Required (unless "content" is used, see below): URL to the
- // source file, protocol is more or less arbitrary, but a Swarm
- // URL is recommended
- "url": "bzzr://56ab..."
+ // Required (unless "content" is used, see below): Sorted URL(s)
+ // to the source file, protocol is more or less arbitrary, but a
+ // Swarm URL is recommended
+ "urls": [ "bzzr://56ab..." ]
},
"mortal": {
// Required: keccak256 hash of the source file
@@ -325,6 +314,14 @@ Comments are of course also not permitted and used here only for explanatory pur
}
}
+.. note::
+ Note the ABI definition above has no fixed order. It can change with compiler versions.
+
+.. note::
+ Since the bytecode of the resulting contract contains the metadata hash, any change to
+ the metadata will result in a change of the bytecode. Furthermore, since the metadata
+ includes a hash of all the sources used, a single whitespace change in any of the source
+ codes will result in a different metadata, and subsequently a different bytecode.
Encoding of the Metadata Hash in the Bytecode
=============================================
@@ -374,7 +371,7 @@ Tips and Tricks
* Use ``delete`` on arrays to delete all its elements.
* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple SSTORE operations might be combined into a single (SSTORE costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
-* Make your state variables public - the compiler will create :ref:`getters <visibility-and-accessors>` for you for free.
+* Make your state variables public - the compiler will create :ref:`getters <visibility-and-getters>` for you automatically.
* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`.
* If your contract has a function called ``send`` but you want to use the built-in send-function, use ``address(contractVariable).send(amount)``.
* Initialise storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});``
@@ -397,12 +394,14 @@ The following is the order of precedence for operators, listed in order of evalu
+============+=====================================+============================================+
| *1* | Postfix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
-| | Function-like call | ``<func>(<args...>)`` |
+| | New expression | ``new <typename>`` |
+ +-------------------------------------+--------------------------------------------+
| | Array subscripting | ``<array>[<index>]`` |
+ +-------------------------------------+--------------------------------------------+
| | Member access | ``<object>.<member>`` |
+ +-------------------------------------+--------------------------------------------+
+| | Function-like call | ``<func>(<args...>)`` |
++ +-------------------------------------+--------------------------------------------+
| | Parentheses | ``(<statement>)`` |
+------------+-------------------------------------+--------------------------------------------+
| *2* | Prefix increment and decrement | ``++``, ``--`` |
@@ -446,7 +445,7 @@ The following is the order of precedence for operators, listed in order of evalu
| *16* | Comma operator | ``,`` |
+------------+-------------------------------------+--------------------------------------------+
-.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
+.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
Global Variables
================
@@ -464,6 +463,9 @@ Global Variables
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address``): sender of the transaction (full call chain)
+- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
+- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
+- ``revert()``: abort execution and revert state changes
- ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
- ``sha3(...) returns (bytes32)``: an alias to `keccak256()`
- ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments
@@ -474,8 +476,9 @@ Global Variables
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address``
- ``super``: the contract one level higher in the inheritance hierarchy
- ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address
-- ``<address>.balance`` (``uint256``): balance of the address in Wei
-- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to address, returns ``false`` on failure
+- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
+- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
+- ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
.. index:: visibility, public, private, external, internal
@@ -488,7 +491,7 @@ Function Visibility Specifiers
return true;
}
-- ``public``: visible externally and internally (creates accessor function for storage/state variables)
+- ``public``: visible externally and internally (creates getter function for storage/state variables)
- ``private``: only visible in the current contract
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
- ``internal``: only visible internally
@@ -510,10 +513,11 @@ Reserved Keywords
These keywords are reserved in Solidity. They might become part of the syntax in the future:
-``abstract``, ``after``, ``case``, ``catch``, ``final``, ``in``, ``inline``, ``interface``, ``let``, ``match``,
+``abstract``, ``after``, ``case``, ``catch``, ``default``, ``final``, ``in``, ``inline``, ``interface``, ``let``, ``match``, ``null``,
``of``, ``pure``, ``relocatable``, ``static``, ``switch``, ``try``, ``type``, ``typeof``, ``view``.
Language Grammar
================
.. literalinclude:: grammar.txt
+ :language: none
diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst
index 77e1bf08..33c613d8 100644
--- a/docs/security-considerations.rst
+++ b/docs/security-considerations.rst
@@ -22,7 +22,11 @@ you should be more careful.
This section will list some pitfalls and general security recommendations but
can, of course, never be complete. Also, keep in mind that even if your
smart contract code is bug-free, the compiler or the platform itself might
-have a bug.
+have a bug. A list of some publicly known security-relevant bugs of the compiler
+can be found in the
+:ref:`list of known bugs<known_bugs>`, which is also machine-readable. Note
+that there is a bug bounty program that covers the code generator of the
+Solidity compiler.
As always, with open source documentation, please help us extend this section
(especially, some examples would not hurt)!
@@ -75,7 +79,7 @@ outlined further below:
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract Fund {
/// Mapping of ether shares of the contract.
@@ -84,8 +88,7 @@ outlined further below:
function withdraw() {
var share = shares[msg.sender];
shares[msg.sender] = 0;
- if (!msg.sender.send(share))
- throw;
+ msg.sender.transfer(share);
}
}
@@ -117,25 +120,27 @@ Sending and Receiving Ether
During the execution of the fallback function, the contract can only rely
on the "gas stipend" (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way.
To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function
- (for example in the "details" section in browser-solidity).
+ (for example in the "details" section in Remix).
- There is a way to forward more gas to the receiving contract using
- ``addr.call.value(x)()``. This is essentially the same as ``addr.send(x)``,
+ ``addr.call.value(x)()``. This is essentially the same as ``addr.transfer(x)``,
only that it forwards all remaining gas and opens up the ability for the
- recipient to perform more expensive actions. This might include calling back
+ recipient to perform more expensive actions (and it only returns a failure code
+ and does not automatically propagate the error). This might include calling back
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.
-- If you want to send Ether using ``address.send``, there are certain details to be aware of:
+- 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.
2. Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call
- depth, they can force the transfer to fail; make sure to always check the return value of ``send``. Better yet,
+ depth, they can force the transfer to fail; take this possibility into account or use ``send`` and make sure to always check its return value. Better yet,
write your contract using a pattern where the recipient can withdraw Ether instead.
3. Sending Ether can also fail because the execution of the recipient contract
- requires more than the allotted amount of gas (explicitly by using ``throw`` or
+ requires more than the allotted amount of gas (explicitly by using ``require``,
+ ``assert``, ``revert``, ``throw`` or
because the operation is just too expensive) - it "runs out of gas" (OOG).
- If the return value of ``send`` is checked, this might provide a
+ If you use ``transfer`` or ``send`` with a return value check, this might provide a
means for the recipient to block progress in the sending contract. Again, the best practice here is to use
a :ref:`"withdraw" pattern instead of a "send" pattern <withdrawal_pattern>`.
@@ -158,7 +163,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
@@ -168,9 +173,9 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
owner = msg.sender;
}
- function transfer(address dest, uint amount) {
- if (tx.origin != owner) { throw; }
- if (!dest.call.value(amount)()) throw;
+ function transferTo(address dest, uint amount) {
+ require(tx.origin == owner);
+ dest.transfer(amount);
}
}
@@ -188,7 +193,7 @@ Now someone tricks you into sending ether to the address of this attack wallet:
}
function() {
- TxUserWallet(msg.sender).transfer(owner, msg.sender.balance);
+ TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
}
}
diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst
index 915cfa76..450b0286 100644
--- a/docs/solidity-by-example.rst
+++ b/docs/solidity-by-example.rst
@@ -36,7 +36,7 @@ of votes.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
/// @title Voting with delegation.
contract Ballot {
@@ -51,8 +51,7 @@ of votes.
}
// This is a type for a single proposal.
- struct Proposal
- {
+ struct Proposal {
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
@@ -88,14 +87,14 @@ of votes.
// Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`.
function giveRightToVote(address voter) {
- if (msg.sender != chairperson || voters[voter].voted) {
- // `throw` terminates and reverts all changes to
- // the state and to Ether balances. It is often
- // a good idea to use this if functions are
- // called incorrectly. But watch out, this
- // will also consume all provided gas.
- throw;
- }
+ // If the argument of `require` evaluates to `false`,
+ // it terminates and reverts all changes to
+ // the state and to Ether balances. It is often
+ // a good idea to use this if functions are
+ // called incorrectly. But watch out, this
+ // will currently also consume all provided gas
+ // (this is planned to change in the future).
+ require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0));
voters[voter].weight = 1;
}
@@ -103,8 +102,10 @@ of votes.
function delegate(address to) {
// assigns reference
Voter sender = voters[msg.sender];
- if (sender.voted)
- throw;
+ require(!sender.voted);
+
+ // Self-delegation is not allowed.
+ require(to != msg.sender);
// Forward the delegation as long as
// `to` also delegated.
@@ -114,16 +115,11 @@ of votes.
// In this case, the delegation will not be executed,
// but in other situations, such loops might
// cause a contract to get "stuck" completely.
- while (
- voters[to].delegate != address(0) &&
- voters[to].delegate != msg.sender
- ) {
+ while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
- }
- // We found a loop in the delegation, not allowed.
- if (to == msg.sender) {
- throw;
+ // We found a loop in the delegation, not allowed.
+ require(to != msg.sender);
}
// Since `sender` is a reference, this
@@ -146,8 +142,7 @@ of votes.
/// to proposal `proposals[proposal].name`.
function vote(uint proposal) {
Voter sender = voters[msg.sender];
- if (sender.voted)
- throw;
+ require(!sender.voted);
sender.voted = true;
sender.vote = proposal;
@@ -170,7 +165,7 @@ of votes.
}
}
}
-
+
// Calls winningProposal() function to get the index
// of the winner contained in the proposals array and then
// returns the name of the winner
@@ -219,7 +214,7 @@ activate themselves.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract SimpleAuction {
// Parameters of the auction. Times are either
@@ -270,22 +265,21 @@ activate themselves.
// the transaction. The keyword payable
// is required for the function to
// be able to receive Ether.
- if (now > auctionStart + biddingTime) {
- // Revert the call if the bidding
- // period is over.
- throw;
- }
- if (msg.value <= highestBid) {
- // If the bid is not higher, send the
- // money back.
- throw;
- }
+
+ // Revert the call if the bidding
+ // period is over.
+ require(now <= (auctionStart + biddingTime));
+
+ // If the bid is not higher, send the
+ // money back.
+ require(msg.value > highestBid);
+
if (highestBidder != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it can be prevented by the caller by e.g.
// raising the call stack to 1023. It is always safer
- // to let the recipient withdraw their money themselves.
+ // to let the recipients withdraw their money themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
@@ -302,7 +296,7 @@ activate themselves.
// before `send` returns.
pendingReturns[msg.sender] = 0;
- if (!msg.sender.send(amount)) {
+ if (!msg.sender.send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
@@ -322,24 +316,21 @@ activate themselves.
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
- // effects (ether payout) to be perfromed multiple times.
+ // effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
// 1. Conditions
- if (now <= auctionStart + biddingTime)
- throw; // auction did not yet end
- if (ended)
- throw; // this function has already been called
+ require(now >= (auctionStart + biddingTime)); // auction did not yet end
+ require(!ended); // this function has already been called
// 2. Effects
ended = true;
AuctionEnded(highestBidder, highestBid);
// 3. Interaction
- if (!beneficiary.send(highestBid))
- throw;
+ beneficiary.transfer(highestBid);
}
}
@@ -382,7 +373,7 @@ high or low invalid bids.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract BlindAuction {
struct Bid {
@@ -410,8 +401,8 @@ high or low invalid bids.
/// functions. `onlyBefore` is applied to `bid` below:
/// The new function body is the modifier's body where
/// `_` is replaced by the old function body.
- modifier onlyBefore(uint _time) { if (now >= _time) throw; _; }
- modifier onlyAfter(uint _time) { if (now <= _time) throw; _; }
+ modifier onlyBefore(uint _time) { require(now < _time); _; }
+ modifier onlyAfter(uint _time) { require(now > _time); _; }
function BlindAuction(
uint _biddingTime,
@@ -455,13 +446,9 @@ high or low invalid bids.
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
- if (
- _values.length != length ||
- _fake.length != length ||
- _secret.length != length
- ) {
- throw;
- }
+ require(_values.length == length);
+ require(_fake.length == length);
+ require(_secret.length == length);
uint refund;
for (uint i = 0; i < length; i++) {
@@ -482,8 +469,7 @@ high or low invalid bids.
// the same deposit.
bid.blindedBid = 0;
}
- if (!msg.sender.send(refund))
- throw;
+ msg.sender.transfer(refund);
}
// This is an "internal" function which means that it
@@ -528,14 +514,12 @@ high or low invalid bids.
function auctionEnd()
onlyAfter(revealEnd)
{
- if (ended)
- throw;
+ require(!ended);
AuctionEnded(highestBidder, highestBid);
ended = true;
// We send all the money we have, because some
// of the refunds might have failed.
- if (!beneficiary.send(this.balance))
- throw;
+ beneficiary.transfer(this.balance);
}
}
@@ -547,7 +531,7 @@ Safe Remote Purchase
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract Purchase {
uint public value;
@@ -559,32 +543,32 @@ Safe Remote Purchase
function Purchase() payable {
seller = msg.sender;
value = msg.value / 2;
- if (2 * value != msg.value) throw;
+ require((2 * value) == msg.value);
}
- modifier require(bool _condition) {
- if (!_condition) throw;
+ modifier condition(bool _condition) {
+ require(_condition);
_;
}
modifier onlyBuyer() {
- if (msg.sender != buyer) throw;
+ require(msg.sender == buyer);
_;
}
modifier onlySeller() {
- if (msg.sender != seller) throw;
+ require(msg.sender == seller);
_;
}
modifier inState(State _state) {
- if (state != _state) throw;
+ require(state == _state);
_;
}
- event aborted();
- event purchaseConfirmed();
- event itemReceived();
+ event Aborted();
+ event PurchaseConfirmed();
+ event ItemReceived();
/// Abort the purchase and reclaim the ether.
/// Can only be called by the seller before
@@ -593,10 +577,9 @@ Safe Remote Purchase
onlySeller
inState(State.Created)
{
- aborted();
+ Aborted();
state = State.Inactive;
- if (!seller.send(this.balance))
- throw;
+ seller.transfer(this.balance);
}
/// Confirm the purchase as buyer.
@@ -605,10 +588,10 @@ Safe Remote Purchase
/// is called.
function confirmPurchase()
inState(State.Created)
- require(msg.value == 2 * value)
+ condition(msg.value == (2 * value))
payable
{
- purchaseConfirmed();
+ PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
@@ -619,15 +602,17 @@ Safe Remote Purchase
onlyBuyer
inState(State.Locked)
{
- itemReceived();
+ ItemReceived();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.
state = State.Inactive;
- // This actually allows both the buyer and the seller to
- // block the refund.
- if (!buyer.send(value) || !seller.send(this.balance))
- throw;
+
+ // NOTE: This actually allows both the buyer and the seller to
+ // block the refund - the withdraw pattern should be used.
+
+ buyer.transfer(value);
+ seller.transfer(this.balance);
}
}
diff --git a/docs/solidity-in-depth.rst b/docs/solidity-in-depth.rst
index 40704698..b6217b47 100644
--- a/docs/solidity-in-depth.rst
+++ b/docs/solidity-in-depth.rst
@@ -16,4 +16,5 @@ If something is missing here, please contact us on
units-and-global-variables.rst
control-structures.rst
contracts.rst
+ assembly.rst
miscellaneous.rst
diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst
index c7af0c8c..224eb368 100644
--- a/docs/structure-of-a-contract.rst
+++ b/docs/structure-of-a-contract.rst
@@ -28,7 +28,7 @@ State variables are values which are permanently stored in contract storage.
}
See the :ref:`types` section for valid state variable types and
-:ref:`visibility-and-accessors` for possible choices for
+:ref:`visibility-and-getters` for possible choices for
visibility.
.. _structure-functions:
@@ -49,7 +49,7 @@ Functions are the executable units of code within a contract.
}
:ref:`function-calls` can happen internally or externally
-and have different levels of visibility (:ref:`visibility-and-accessors`)
+and have different levels of visibility (:ref:`visibility-and-getters`)
towards other contracts.
.. _structure-function-modifiers:
@@ -62,13 +62,13 @@ Function modifiers can be used to amend the semantics of functions in a declarat
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract Purchase {
address public seller;
modifier onlySeller() { // Modifier
- if (msg.sender != seller) throw;
+ require(msg.sender == seller);
_;
}
diff --git a/docs/style-guide.rst b/docs/style-guide.rst
index 272a1b31..0742d2e9 100644
--- a/docs/style-guide.rst
+++ b/docs/style-guide.rst
@@ -150,6 +150,74 @@ No::
...
}
+Order of Functions
+==================
+
+Ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier.
+
+Functions should be grouped according to their visibility and ordered:
+
+- constructor
+- fallback function (if exists)
+- external
+- public
+- internal
+- private
+
+Within a grouping, place the ``constant`` functions last.
+
+Yes::
+
+ contract A {
+ function A() {
+ ...
+ }
+
+ function() {
+ ...
+ }
+
+ // External functions
+ // ...
+
+ // External functions that are constant
+ // ...
+
+ // Public functions
+ // ...
+
+ // Internal functions
+ // ...
+
+ // Private functions
+ // ...
+ }
+
+No::
+
+ contract A {
+
+ // External functions
+ // ...
+
+ // Private functions
+ // ...
+
+ // Public functions
+ // ...
+
+ function A() {
+ ...
+ }
+
+ function() {
+ ...
+ }
+
+ // Internal functions
+ // ...
+ }
+
Whitespace in Expressions
=========================
@@ -194,6 +262,19 @@ No::
y = 2;
long_variable = 3;
+Don't include a whitespace in the fallback function:
+
+Yes::
+
+ function() {
+ ...
+ }
+
+No::
+
+ function () {
+ ...
+ }
Control Structures
==================
diff --git a/docs/types.rst b/docs/types.rst
index b17a2771..67549499 100644
--- a/docs/types.rst
+++ b/docs/types.rst
@@ -52,21 +52,32 @@ Operators:
* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``)
* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation)
-* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder), ``**`` (exponentiation)
+* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder), ``**`` (exponentiation), ``<<`` (left shift), ``>>`` (right shift)
-Division always truncates (it just maps to the DIV opcode of the EVM), but it does not truncate if both
+Division always truncates (it is just compiled to the DIV opcode of the EVM), but it does not truncate if both
operators are :ref:`literals<rational_literals>` (or literal expressions).
-Division by zero and modulus with zero throws an exception.
+Division by zero and modulus with zero throws a runtime exception.
-.. index:: address, balance, send, call, callcode, delegatecall
+The result of a shift operation is the type of the left operand. The
+expression ``x << y`` is equivalent to ``x * 2**y``, and ``x >> y`` is
+equivalent to ``x / 2**y``. This means that shifting negative numbers
+sign extends. Shifting by a negative amount throws a runtime exception.
+
+.. warning::
+ The results produced by shift right of negative values of signed integer types is different from those produced
+ by other programming languages. In Solidity, shift right maps to division so the shifted negative values
+ are going to be rounded towards zero (truncated). In other programming languages the shift right of negative values
+ works like division with rounding down (towards negative infinity).
+
+.. index:: address, balance, send, call, callcode, delegatecall, transfer
.. _address:
Address
-------
-``address``: Holds a 20 byte value (size of an Ethereum address). Address types also have members and serve as base for all contracts.
+``address``: Holds a 20 byte value (size of an Ethereum address). Address types also have members and serve as a base for all contracts.
Operators:
@@ -75,27 +86,31 @@ Operators:
Members of Addresses
^^^^^^^^^^^^^^^^^^^^
-* ``balance`` and ``send``
+* ``balance`` and ``transfer``
For a quick reference, see :ref:`address_related`.
It is possible to query the balance of an address using the property ``balance``
-and to send Ether (in units of wei) to an address using the ``send`` function:
+and to send Ether (in units of wei) to an address using the ``transfer`` function:
::
address x = 0x123;
address myAddress = this;
- if (x.balance < 10 && myAddress.balance >= 10) x.send(10);
+ if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
.. note::
- If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``send`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted. In this case, ``send`` returns ``false``.
+ If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception.
+
+* ``send``
+
+Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``.
.. warning::
There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024
(this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order
- to make safe Ether transfers, always check the return value of ``send`` or even better:
- Use a pattern where the recipient withdraws the money.
+ to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better:
+ use a pattern where the recipient withdraws the money.
* ``call``, ``callcode`` and ``delegatecall``
@@ -110,10 +125,12 @@ the function ``call`` is provided which takes an arbitrary number of arguments o
``call`` returns a boolean indicating whether the invoked function terminated (``true``) or caused an EVM exception (``false``). It is not possible to access the actual data returned (for this we would need to know the encoding and size in advance).
-In a similar way, the function ``delegatecall`` can be used: The difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values.
+In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values.
All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
+The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``.
+
.. note::
All contracts inherit the members of address, so it is possible to query the balance of the
current contract using ``this.balance``.
@@ -136,9 +153,13 @@ Fixed-size byte arrays
Operators:
* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``)
-* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation)
+* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation), ``<<`` (left shift), ``>>`` (right shift)
* Index access: If ``x`` is of type ``bytesI``, then ``x[k]`` for ``0 <= k < I`` returns the ``k`` th byte (read-only).
+The shifting operator works with any integer type as right operand (but will
+return the type of the left operand), which denotes the number of bits to shift by.
+Shifting by a negative amount will cause a runtime exception.
+
Members:
* ``.length`` yields the fixed length of the byte array (read-only).
@@ -162,6 +183,19 @@ Fixed Point Numbers
**COMING SOON...**
+.. index:: address, literal;address
+
+.. _address_literals:
+
+Address Literals
+----------------
+
+Hexadecimal literals that pass the address checksum test, for example
+``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address`` type.
+Hexadecimal literals that are between 39 and 41 digits
+long and do not pass the checksum test produce
+a warning and are treated as regular rational number literals.
+
.. index:: literal, literal;rational
.. _rational_literals:
@@ -171,12 +205,14 @@ Rational and Integer Literals
Integer literals are formed from a sequence of numbers in the range 0-9.
They are interpreted as decimals. For example, ``69`` means sixty nine.
-Octal literals do not exist in Solidity and leading zeros are ignored.
-For example, ``0100`` means one hundred.
+Octal literals do not exist in Solidity and leading zeros are invalid.
-Decimal literals are formed by a ``.`` with at least one number on
+Decimal fraction literals are formed by a ``.`` with at least one number on
one side. Examples include ``1.``, ``.1`` and ``1.3``.
+Scientific notation is also supported, where the base can have fractions, while the exponent cannot.
+Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``.
+
Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by
using them together with a non-literal expression).
This means that computations do not overflow and divisions do not truncate
@@ -196,7 +232,7 @@ a non-rational number).
Integer literals and rational number literals belong to number literal types.
Moreover, all number literal expressions (i.e. the expressions that
contain only number literals and operators) belong to number literal
- types. So the number literal expressions `1 + 2` and `2 + 1` both
+ types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both
belong to the same number literal type for the rational number three.
.. warning::
@@ -219,7 +255,7 @@ a non-rational number).
String Literals
---------------
-String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; `"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``.
+String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``.
String literals support escape characters, such as ``\n``, ``\xNN`` and ``\uNNNN``. ``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence.
@@ -337,10 +373,10 @@ Example that shows how to use internal function types::
}
function reduce(
uint[] memory self,
- function (uint x, uint y) returns (uint) f
+ function (uint, uint) returns (uint) f
)
internal
- returns (uint r)
+ returns (uint)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
@@ -370,7 +406,7 @@ Example that shows how to use internal function types::
Another example that uses external function types::
- pragma solidity ^0.4.5;
+ pragma solidity ^0.4.11;
contract Oracle {
struct Request {
@@ -395,7 +431,7 @@ Another example that uses external function types::
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) {
- if (msg.sender != address(oracle)) throw;
+ require(msg.sender == address(oracle));
// Use the data
}
}
@@ -422,19 +458,19 @@ context, there is always a default, but it can be overridden by appending
either ``storage`` or ``memory`` to the type. The default for function parameters (including return parameters) is ``memory``, the default for local variables is ``storage`` and the location is forced
to ``storage`` for state variables (obviously).
-There is also a third data location, "calldata", which is a non-modifyable
+There is also a third data location, "calldata", which is a non-modifiable,
non-persistent area where function arguments are stored. Function parameters
(not return parameters) of external functions are forced to "calldata" and
-it behaves mostly like memory.
+behave mostly like memory.
Data locations are important because they change how assignments behave:
-Assignments between storage and memory and also to a state variable (even from other state variables)
+assignments between storage and memory and also to a state variable (even from other state variables)
always create an independent copy.
Assignments to local storage variables only assign a reference though, and
this reference always points to the state variable even if the latter is changed
in the meantime.
On the other hand, assignments from a memory stored reference type to another
-memory-stored reference type does not create a copy.
+memory-stored reference type do not create a copy.
::
@@ -507,8 +543,8 @@ So ``bytes`` should always be preferred over ``byte[]`` because it is cheaper.
that you are accessing the low-level bytes of the UTF-8 representation,
and not the individual characters!
-It is possible to mark arrays ``public`` and have Solidity create an accessor.
-The numeric index will become a required parameter for the accessor.
+It is possible to mark arrays ``public`` and have Solidity create a getter.
+The numeric index will become a required parameter for the getter.
.. index:: ! array;allocating, new
@@ -605,7 +641,8 @@ Members
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
- // Note that the following is not a pair of arrays but an array of pairs.
+ // Note that the following is not a pair of dynamic arrays but a
+ // dynamic array of pairs (i.e. of fixed size arrays of length two).
bool[2][] m_pairsOfFlags;
// newPairs is stored in memory - the default for function arguments
@@ -672,7 +709,7 @@ shown in the following example:
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.11;
contract CrowdFunding {
// Defines a new type with two fields.
@@ -713,8 +750,7 @@ shown in the following example:
return false;
uint amount = c.amount;
c.amount = 0;
- if (!c.beneficiary.send(amount))
- throw;
+ c.beneficiary.transfer(amount);
return true;
}
}
@@ -742,11 +778,11 @@ assigning it to a local variable, as in
Mappings
========
-Mapping types are declared as ``mapping _KeyType => _ValueType``.
+Mapping types are declared as ``mapping(_KeyType => _ValueType)``.
Here ``_KeyType`` can be almost any type except for a mapping, a dynamically sized array, a contract, an enum and a struct.
``_ValueType`` can actually be any type, including mappings.
-Mappings can be seen as hashtables which are virtually initialized such that
+Mappings can be seen as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_ which are virtually initialized such that
every possible key exists and is mapped to a value whose byte-representation is
all zeros: a type's :ref:`default value <default-value>`. The similarity ends here, though: The key data is not actually stored
in a mapping, only its ``keccak256`` hash used to look up the value.
@@ -756,11 +792,11 @@ Because of this, mappings do not have a length or a concept of a key or value be
Mappings are only allowed for state variables (or as storage reference types
in internal functions).
-It is possible to mark mappings ``public`` and have Solidity create an accessor.
-The ``_KeyType`` will become a required parameter for the accessor and it will
+It is possible to mark mappings ``public`` and have Solidity create a getter.
+The ``_KeyType`` will become a required parameter for the getter and it will
return ``_ValueType``.
-The ``_ValueType`` can be a mapping too. The accessor will have one parameter
+The ``_ValueType`` can be a mapping too. The getter will have one parameter
for each ``_KeyType``, recursively.
::
diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst
index dd3d4be8..7d21f065 100644
--- a/docs/units-and-global-variables.rst
+++ b/docs/units-and-global-variables.rst
@@ -22,8 +22,8 @@ unit and units are considered naively in the following way:
* ``1 minutes == 60 seconds``
* ``1 hours == 60 minutes``
* ``1 days == 24 hours``
- * ``1 weeks = 7 days``
- * ``1 years = 365 days``
+ * ``1 weeks == 7 days``
+ * ``1 years == 365 days``
Take care if you perform calendar calculations using these units, because
not every year equals 365 days and not even every day has 24 hours
@@ -55,7 +55,7 @@ Block and Transaction Properties
- ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
- ``block.number`` (``uint``): current block number
-- ``block.timestamp`` (``uint``): current block timestamp
+- ``block.timestamp`` (``uint``): current block timestamp as seconds since unix epoch
- ``msg.data`` (``bytes``): complete calldata
- ``msg.gas`` (``uint``): remaining gas
- ``msg.sender`` (``address``): sender of the message (current call)
@@ -79,7 +79,19 @@ Block and Transaction Properties
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
-.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
+.. index:: assert, revert, require
+
+Error Handling
+--------------
+
+``assert(bool condition)``:
+ throws if the condition is not met - to be used for internal errors.
+``require(bool condition)``:
+ throws if the condition is not met - to be used for errors in inputs or external components.
+``revert()``:
+ abort execution and revert state changes
+
+.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
Mathematical and Cryptographic Functions
----------------------------------------
@@ -91,13 +103,14 @@ Mathematical and Cryptographic Functions
``keccak256(...) returns (bytes32)``:
compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
``sha3(...) returns (bytes32)``:
- alias to `keccak256()`
+ alias to ``keccak256()``
``sha256(...) returns (bytes32)``:
compute the SHA-256 hash of the (tightly packed) arguments
``ripemd160(...) returns (bytes20)``:
compute RIPEMD-160 hash of the (tightly packed) arguments
``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``:
recover the address associated with the public key from elliptic curve signature or return zero on error
+ (`example usage <https://ethereum.stackexchange.com/q/1777/222>`_)
In the above, "tightly packed" means that the arguments are concatenated without padding.
This means that the following are all identical::
@@ -117,6 +130,7 @@ This means that, for example, ``keccak256(0) == keccak256(uint8(0))`` and
It might be that you run into Out-of-Gas for ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*. The reason for this is that those are implemented as so-called precompiled contracts and these contracts only really exist after they received the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution runs into an Out-of-Gas error. A workaround for this problem is to first send e.g. 1 Wei to each of the contracts before you use them in your actual contracts. This is not an issue on the official or test net.
+.. index:: balance, send, transfer, call, callcode, delegatecall
.. _address_related:
Address Related
@@ -124,15 +138,23 @@ Address Related
``<address>.balance`` (``uint256``):
balance of the :ref:`address` in Wei
+``<address>.transfer(uint256 amount)``:
+ send given amount of Wei to :ref:`address`, throws on failure
``<address>.send(uint256 amount) returns (bool)``:
send given amount of Wei to :ref:`address`, returns ``false`` on failure
+``<address>.call(...) returns (bool)``:
+ issue low-level ``CALL``, returns ``false`` on failure
+``<address>.callcode(...) returns (bool)``:
+ issue low-level ``CALLCODE``, returns ``false`` on failure
+``<address>.delegatecall(...) returns (bool)``:
+ issue low-level ``DELEGATECALL``, returns ``false`` on failure
For more information, see the section on :ref:`address`.
.. warning::
There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024
(this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order
- to make safe Ether transfers, always check the return value of ``send`` or even better:
+ to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better:
Use a pattern where the recipient withdraws the money.
.. index:: this, selfdestruct
diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst
new file mode 100644
index 00000000..7f82df70
--- /dev/null
+++ b/docs/using-the-compiler.rst
@@ -0,0 +1,275 @@
+******************
+Using the compiler
+******************
+
+.. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker
+
+.. _commandline-compiler:
+
+Using the Commandline Compiler
+******************************
+
+One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler.
+Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
+If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
+
+The commandline compiler will automatically read imported files from the filesystem, but
+it is also possible to provide path redirects using ``prefix=path`` in the following way:
+
+::
+
+ solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol
+
+This essentially instructs the compiler to search for anything starting with
+``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin`` and if it does not
+find the file there, it will look at ``/usr/local/lib/fallback`` (the empty prefix
+always matches). ``solc`` will not read files from the filesystem that lie outside of
+the remapping targets and outside of the directories where explicitly specified source
+files reside, so things like ``import "/etc/passwd";`` only work if you add ``=/`` as a remapping.
+
+If there are multiple matches due to remappings, the one with the longest common prefix is selected.
+
+For security reasons the compiler has restrictions what directories it can access. Paths (and their subdirectories) of source files specified on the commandline and paths defined by remappings are allowed for import statements, but everything else is rejected. Additional paths (and their subdirectories) can be allowed via the ``--allow-paths /sample/path,/another/sample/path`` switch.
+
+If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points:
+
+Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``.
+
+If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case.
+
+If ``solc`` is called with the option ``--standard-json``, it will expect a JSON input (as explained below) on the standard input, and return a JSON output on the standard output.
+
+.. _compiler-api:
+
+Compiler Input and Output JSON Description
+******************************************
+
+These JSON formats are used by the compiler API as well as are available through ``solc``. These are subject to change,
+some fields are optional (as noted), but it is aimed at to only make backwards compatible changes.
+
+The compiler API expects a JSON formatted input and outputs the compilation result in a JSON formatted output.
+
+Comments are of course not permitted and used here only for explanatory purposes.
+
+Input Description
+-----------------
+
+.. code-block:: none
+
+ {
+ // Required: Source code language, such as "Solidity", "serpent", "lll", "assembly", etc.
+ language: "Solidity",
+ // Required
+ sources:
+ {
+ // The keys here are the "global" names of the source files,
+ // imports can use other files via remappings (see below).
+ "myFile.sol":
+ {
+ // Optional: keccak256 hash of the source file
+ // It is used to verify the retrieved content if imported via URLs.
+ "keccak256": "0x123...",
+ // Required (unless "content" is used, see below): URL(s) to the source file.
+ // URL(s) should be imported in this order and the result checked against the
+ // keccak256 hash (if available). If the hash doesn't match or none of the
+ // URL(s) result in success, an error should be raised.
+ "urls":
+ [
+ "bzzr://56ab...",
+ "ipfs://Qma...",
+ "file:///tmp/path/to/file.sol"
+ ]
+ },
+ "mortal":
+ {
+ // Optional: keccak256 hash of the source file
+ "keccak256": "0x234...",
+ // Required (unless "urls" is used): literal contents of the source file
+ "content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
+ }
+ },
+ // Optional
+ settings:
+ {
+ // Optional: Sorted list of remappings
+ remappings: [ ":g/dir" ],
+ // Optional: Optimizer settings (enabled defaults to false)
+ optimizer: {
+ enabled: true,
+ runs: 500
+ },
+ // Metadata settings (optional)
+ metadata: {
+ // Use only literal content and not URLs (false by default)
+ useLiteralContent: true
+ },
+ // Addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different.
+ libraries: {
+ // The top level key is the the name of the source file where the library is used.
+ // If remappings are used, this source file should match the global path after remappings were applied.
+ // If this key is an empty string, that refers to a global level.
+ "myFile.sol": {
+ "MyLib": "0x123123..."
+ }
+ }
+ // The following can be used to select desired outputs.
+ // If this field is omitted, then the compiler loads and does type checking, but will not generate any outputs apart from errors.
+ // The first level key is the file name and the second is the contract name, where empty contract name refers to the file itself,
+ // while the star refers to all of the contracts.
+ //
+ // The available output types are as follows:
+ // abi - ABI
+ // ast - AST of all source files
+ // legacyAST - legacy AST of all source files
+ // devdoc - Developer documentation (natspec)
+ // userdoc - User documentation (natspec)
+ // metadata - Metadata
+ // ir - New assembly format before desugaring
+ // evm.assembly - New assembly format after desugaring
+ // evm.legacyAssembly - Old-style assembly format in JSON
+ // evm.bytecode.object - Bytecode object
+ // evm.bytecode.opcodes - Opcodes list
+ // evm.bytecode.sourceMap - Source mapping (useful for debugging)
+ // evm.bytecode.linkReferences - Link references (if unlinked object)
+ // evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode)
+ // evm.methodIdentifiers - The list of function hashes
+ // evm.gasEstimates - Function gas estimates
+ // ewasm.wast - eWASM S-expressions format (not supported atm)
+ // ewasm.wasm - eWASM binary format (not supported atm)
+ //
+ // Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
+ // target part of that output.
+ //
+ outputSelection: {
+ // Enable the metadata and bytecode outputs of every single contract.
+ "*": {
+ "*": [ "metadata", "evm.bytecode" ]
+ },
+ // Enable the abi and opcodes output of MyContract defined in file def.
+ "def": {
+ "MyContract": [ "abi", "evm.opcodes" ]
+ },
+ // Enable the source map output of every single contract.
+ "*": {
+ "*": [ "evm.sourceMap" ]
+ },
+ // Enable the legacy AST output of every single file.
+ "*": {
+ "": [ "legacyAST" ]
+ }
+ }
+ }
+ }
+
+
+Output Description
+------------------
+
+.. code-block:: none
+
+ {
+ // Optional: not present if no errors/warnings were encountered
+ errors: [
+ {
+ // Optional: Location within the source file.
+ sourceLocation: {
+ file: "sourceFile.sol",
+ start: 0,
+ end: 100
+ ],
+ // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc
+ type: "TypeError",
+ // Mandatory: Component where the error originated, such as "general", "ewasm", etc.
+ component: "general",
+ // Mandatory ("error" or "warning")
+ severity: "error",
+ // Mandatory
+ message: "Invalid keyword"
+ // Optional: the message formatted with source location
+ formattedMessage: "sourceFile.sol:100: Invalid keyword"
+ }
+ ],
+ // This contains the file-level outputs. In can be limited/filtered by the outputSelection settings.
+ sources: {
+ "sourceFile.sol": {
+ // Identifier (used in source maps)
+ id: 1,
+ // The AST object
+ ast: {},
+ // The legacy AST object
+ legacyAST: {}
+ }
+ },
+ // This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings.
+ contracts: {
+ "sourceFile.sol": {
+ // If the language used has no contract names, this field should equal to an empty string.
+ "ContractName": {
+ // The Ethereum Contract ABI. If empty, it is represented as an empty array.
+ // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
+ abi: [],
+ // See the Metadata Output documentation (serialised JSON string)
+ metadata: "{...}",
+ // User documentation (natspec)
+ userdoc: {},
+ // Developer documentation (natspec)
+ devdoc: {},
+ // Intermediate representation (string)
+ ir: "",
+ // EVM-related outputs
+ evm: {
+ // Assembly (string)
+ assembly: "",
+ // Old-style assembly (object)
+ legacyAssembly: {},
+ // Bytecode and related details.
+ bytecode: {
+ // The bytecode as a hex string.
+ object: "00fe",
+ // Opcodes list (string)
+ opcodes: "",
+ // The source mapping as a string. See the source mapping definition.
+ sourceMap: "",
+ // If given, this is an unlinked object.
+ linkReferences: {
+ "libraryFile.sol": {
+ // Byte offsets into the bytecode. Linking replaces the 20 bytes located there.
+ "Library1": [
+ { start: 0, length: 20 },
+ { start: 200, length: 20 }
+ ]
+ }
+ }
+ },
+ // The same layout as above.
+ deployedBytecode: { },
+ // The list of function hashes
+ methodIdentifiers: {
+ "delegate(address)": "5c19a95c"
+ },
+ // Function gas estimates
+ gasEstimates: {
+ creation: {
+ codeDepositCost: "420000",
+ executionCost: "infinite",
+ totalCost: "infinite"
+ },
+ external: {
+ "delegate(address)": "25000"
+ },
+ internal: {
+ "heavyLifting()": "infinite"
+ }
+ }
+ },
+ // eWASM related outputs
+ ewasm: {
+ // S-expressions format
+ wast: "",
+ // Binary format (hex string)
+ wasm: ""
+ }
+ }
+ }
+ }
+ }
diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py
index 779147f4..ef55c6a2 100644
--- a/docs/utils/SolidityLexer.py
+++ b/docs/utils/SolidityLexer.py
@@ -26,7 +26,7 @@ class SolidityLexer(RegexLexer):
(r'/\*.*?\*/', Comment.Multiline)
],
'natspec': [
- (r'@author|@dev|@notice|@return|@param|@why3|@title', Keyword),
+ (r'@author|@dev|@notice|@return|@param|@title', Keyword),
(r'.[^@*\n]*?', Comment.Special)
],
'docstringsingle': [
@@ -54,11 +54,10 @@ class SolidityLexer(RegexLexer):
r'(<<|>>>?|==?|!=?|[-<>+*%&\|\^/])=?', Operator, 'slashstartsregex'),
(r'[{(\[;,]', Punctuation, 'slashstartsregex'),
(r'[})\].]', Punctuation),
- (r'(for|in|while|do|break|return|continue|switch|case|default|if|else|'
- r'throw|try|catch|finally|new|delete|typeof|instanceof|void|'
- r'this|import|mapping|returns|private|public|external|internal|'
- r'constant|memory|storage)\b', Keyword, 'slashstartsregex'),
- (r'(var|let|with|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'),
+ (r'(anonymous|as|assembly|break|constant|continue|do|delete|else|external|for|hex|if|'
+ r'indexed|internal|import|is|mapping|memory|new|payable|public|pragma|'
+ r'private|return|returns|storage|super|this|throw|using|while)\b', Keyword, 'slashstartsregex'),
+ (r'(var|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'),
(r'(bytes|string|address|uint|int|bool|byte|' +
'|'.join(
['uint%d' % (i + 8) for i in range(0, 256, 8)] +
@@ -67,16 +66,12 @@ class SolidityLexer(RegexLexer):
['ufixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)] +
['fixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)]
) + r')\b', Keyword.Type, 'slashstartsregex'),
- (r'(abstract|boolean|byte|char|class|const|debugger|double|enum|export|'
- r'extends|final|float|goto|implements|int|interface|long|native|'
- r'package|private|protected|public|short|static|super|synchronized|throws|'
- r'transient|volatile)\b', Keyword.Reserved),
- (r'(true|false|null|NaN|Infinity|undefined)\b', Keyword.Constant),
- (r'(Array|Boolean|Date|Error|Function|Math|netscape|'
- r'Number|Object|Packages|RegExp|String|sun|decodeURI|'
- r'decodeURIComponent|encodeURI|encodeURIComponent|'
- r'Error|eval|isFinite|isNaN|parseFloat|parseInt|document|this|'
- r'window)\b', Name.Builtin),
+ (r'(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b', Keyword.Type, 'slashstartsregex'),
+ (r'(abstract|after|case|catch|default|final|in|inline|interface|let|match|'
+ r'null|of|pure|relocatable|static|switch|try|type|typeof|view)\b', Keyword.Reserved),
+ (r'(true|false)\b', Keyword.Constant),
+ (r'(block|msg|tx|now|suicide|selfdestruct|addmod|mulmod|sha3|keccak256|log[0-4]|'
+ r'sha256|ecrecover|ripemd160|assert|revert)', Name.Builtin),
(r'[$a-zA-Z_][a-zA-Z0-9_]*', Name.Other),
(r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float),
(r'0x[0-9a-fA-F]+', Number.Hex),
diff --git a/libdevcore/ABI.h b/libdevcore/ABI.h
index 423cfda8..8b9e5c98 100644
--- a/libdevcore/ABI.h
+++ b/libdevcore/ABI.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file ABI.h
* @author Gav Wood <i@gavwood.com>
diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h
index 05e0b0e5..729ffb05 100644
--- a/libdevcore/Assertions.h
+++ b/libdevcore/Assertions.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file Assertions.h
@@ -25,7 +25,6 @@
#pragma once
#include "Exceptions.h"
-#include "debugbreak.h"
namespace dev
{
@@ -38,62 +37,21 @@ namespace dev
#define ETH_FUNC __func__
#endif
-#define asserts(A) ::dev::assertAux(A, #A, __LINE__, __FILE__, ETH_FUNC)
-#define assertsEqual(A, B) ::dev::assertEqualAux(A, B, #A, #B, __LINE__, __FILE__, ETH_FUNC)
-
-inline bool assertAux(bool _a, char const* _aStr, unsigned _line, char const* _file, char const* _func)
-{
- bool ret = _a;
- if (!ret)
- {
- std::cerr << "Assertion failed:" << _aStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl;
-#if ETH_DEBUG
- debug_break();
-#endif
- }
- return !ret;
-}
-
-template<class A, class B>
-inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char const* _bStr, unsigned _line, char const* _file, char const* _func)
-{
- bool ret = _a == _b;
- if (!ret)
- {
- std::cerr << "Assertion failed: " << _aStr << " == " << _bStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl;
- std::cerr << " Fail equality: " << _a << "==" << _b << std::endl;
-#if ETH_DEBUG
- debug_break();
-#endif
- }
- return !ret;
-}
-
/// Assertion that throws an exception containing the given description if it is not met.
/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong.");
/// Do NOT supply an exception object as the second parameter.
#define assertThrow(_condition, _ExceptionType, _description) \
- ::dev::assertThrowAux<_ExceptionType>(!!(_condition), _description, __LINE__, __FILE__, ETH_FUNC)
-
-using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
-
-template <class _ExceptionType>
-inline void assertThrowAux(
- bool _condition,
- ::std::string const& _errorDescription,
- unsigned _line,
- char const* _file,
- char const* _function
-)
-{
- if (!_condition)
- ::boost::throw_exception(
- _ExceptionType() <<
- ::dev::errinfo_comment(_errorDescription) <<
- ::boost::throw_function(_function) <<
- ::boost::throw_file(_file) <<
- ::boost::throw_line(_line)
- );
-}
+ do \
+ { \
+ if (!(_condition)) \
+ ::boost::throw_exception( \
+ _ExceptionType() << \
+ ::dev::errinfo_comment(_description) << \
+ ::boost::throw_function(ETH_FUNC) << \
+ ::boost::throw_file(__FILE__) << \
+ ::boost::throw_line(__LINE__) \
+ ); \
+ } \
+ while (false)
}
diff --git a/libdevcore/Common.h b/libdevcore/Common.h
index 225f38ac..c5b09a80 100644
--- a/libdevcore/Common.h
+++ b/libdevcore/Common.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Common.h
* @author Gav Wood <i@gavwood.com>
@@ -76,8 +76,6 @@ using byte = uint8_t;
#define DEV_QUOTED_HELPER(s) #s
#define DEV_QUOTED(s) DEV_QUOTED_HELPER(s)
-#define DEV_IGNORE_EXCEPTIONS(X) try { X; } catch (...) {}
-
namespace dev
{
diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp
index 062d1b29..14caf494 100644
--- a/libdevcore/CommonData.cpp
+++ b/libdevcore/CommonData.cpp
@@ -1,26 +1,30 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CommonData.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
-#include "CommonData.h"
-#include "Exceptions.h"
+#include <libdevcore/CommonData.h>
+#include <libdevcore/Exceptions.h>
+#include <libdevcore/SHA3.h>
+
+#include <boost/algorithm/string.hpp>
+
using namespace std;
using namespace dev;
@@ -95,3 +99,35 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw)
}
return ret;
}
+
+
+bool dev::passesAddressChecksum(string const& _str, bool _strict)
+{
+ string s = _str.substr(0, 2) == "0x" ? _str.substr(2) : _str;
+
+ if (s.length() != 40)
+ return false;
+
+ if (!_strict && (
+ _str.find_first_of("abcdef") == string::npos ||
+ _str.find_first_of("ABCDEF") == string::npos
+ ))
+ return true;
+
+ h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic()));
+ for (size_t i = 0; i < 40; ++i)
+ {
+ char addressCharacter = s[i];
+ bool lowerCase;
+ if ('a' <= addressCharacter && addressCharacter <= 'f')
+ lowerCase = true;
+ else if ('A' <= addressCharacter && addressCharacter <= 'F')
+ lowerCase = false;
+ else
+ continue;
+ unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf;
+ if ((nibble >= 8) == lowerCase)
+ return false;
+ }
+ return true;
+}
diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h
index 5ffcdcca..98ad548d 100644
--- a/libdevcore/CommonData.h
+++ b/libdevcore/CommonData.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CommonData.h
* @author Gav Wood <i@gavwood.com>
@@ -179,4 +179,9 @@ bool contains(T const& _t, V const& _v)
return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v);
}
+/// @returns true iff @a _str passess the hex address checksum test.
+/// @param _strict if false, hex strings with only uppercase or only lowercase letters
+/// are considered valid.
+bool passesAddressChecksum(std::string const& _str, bool _strict);
+
}
diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp
index 60ac518d..52829455 100644
--- a/libdevcore/CommonIO.cpp
+++ b/libdevcore/CommonIO.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CommonIO.cpp
* @author Gav Wood <i@gavwood.com>
@@ -30,11 +30,11 @@
#include <termios.h>
#endif
#include <boost/filesystem.hpp>
-#include "Exceptions.h"
+#include "Assertions.h"
+
using namespace std;
using namespace dev;
-
template <typename _T>
inline _T contentsGeneric(std::string const& _file)
{
@@ -78,13 +78,24 @@ void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDe
if (!fs::exists(p.parent_path()))
{
fs::create_directories(p.parent_path());
- DEV_IGNORE_EXCEPTIONS(fs::permissions(p.parent_path(), fs::owner_all));
+ try
+ {
+ fs::permissions(p.parent_path(), fs::owner_all);
+ }
+ catch (...)
+ {
+ }
}
ofstream s(_file, ios::trunc | ios::binary);
s.write(reinterpret_cast<char const*>(_data.data()), _data.size());
- if (!s)
- BOOST_THROW_EXCEPTION(FileError() << errinfo_comment("Could not write to file: " + _file));
- DEV_IGNORE_EXCEPTIONS(fs::permissions(_file, fs::owner_read|fs::owner_write));
+ assertThrow(s, FileError, "Could not write to file: " + _file);
+ try
+ {
+ fs::permissions(_file, fs::owner_read|fs::owner_write);
+ }
+ catch (...)
+ {
+ }
}
}
diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h
index 8238fe0f..d84362cf 100644
--- a/libdevcore/CommonIO.h
+++ b/libdevcore/CommonIO.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CommonIO.h
* @author Gav Wood <i@gavwood.com>
diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h
index 667ec31c..4817e9e3 100644
--- a/libdevcore/Exceptions.h
+++ b/libdevcore/Exceptions.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Exceptions.h
* @author Gav Wood <i@gavwood.com>
@@ -41,6 +41,9 @@ struct Exception: virtual std::exception, virtual boost::exception
Exception(std::string _message = std::string()): m_message(std::move(_message)) {}
const char* what() const noexcept override { return m_message.empty() ? std::exception::what() : m_message.c_str(); }
+ /// @returns "FileName:LineNumber" referring to the point where the exception was thrown.
+ std::string lineInfo() const;
+
private:
std::string m_message;
};
@@ -53,9 +56,5 @@ DEV_SIMPLE_EXCEPTION(FileError);
// error information to be added to exceptions
using errinfo_invalidSymbol = boost::error_info<struct tag_invalidSymbol, char>;
using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
-using errinfo_required = boost::error_info<struct tag_required, bigint>;
-using errinfo_got = boost::error_info<struct tag_got, bigint>;
-using errinfo_required_h256 = boost::error_info<struct tag_required_h256, h256>;
-using errinfo_got_h256 = boost::error_info<struct tag_get_h256, h256>;
}
diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h
index a23aecc6..5b1c7acf 100644
--- a/libdevcore/FixedHash.h
+++ b/libdevcore/FixedHash.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file FixedHash.h
* @author Gav Wood <i@gavwood.com>
diff --git a/libdevcore/JSON.h b/libdevcore/JSON.h
index 0d6e0d2e..8499d623 100644
--- a/libdevcore/JSON.h
+++ b/libdevcore/JSON.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file JSON.h
* @date 2016
@@ -27,13 +27,13 @@
namespace dev
{
-/// Serialise the JSON object (@a _input) with identation
+/// Serialise the JSON object (@a _input) with indentation
inline std::string jsonPrettyPrint(Json::Value const& _input)
{
return Json::StyledWriter().write(_input);
}
-/// Serialise theJ SON object (@a _input) without identation
+/// Serialise the JSON object (@a _input) without indentation
inline std::string jsonCompactPrint(Json::Value const& _input)
{
Json::FastWriter writer;
diff --git a/libdevcore/SHA3.cpp b/libdevcore/SHA3.cpp
index 3b12f39f..4d82ec85 100644
--- a/libdevcore/SHA3.cpp
+++ b/libdevcore/SHA3.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file SHA3.cpp
* @author Gav Wood <i@gavwood.com>
diff --git a/libdevcore/SHA3.h b/libdevcore/SHA3.h
index c481bfc9..1a561066 100644
--- a/libdevcore/SHA3.h
+++ b/libdevcore/SHA3.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file SHA3.h
* @author Gav Wood <i@gavwood.com>
@@ -29,28 +29,28 @@
namespace dev
{
-// SHA-3 convenience routines.
+// Keccak-256 convenience routines.
-/// Calculate SHA3-256 hash of the given input and load it into the given output.
+/// Calculate Keccak-256 hash of the given input and load it into the given output.
/// @returns false if o_output.size() != 32.
bool keccak256(bytesConstRef _input, bytesRef o_output);
-/// Calculate SHA3-256 hash of the given input, returning as a 256-bit hash.
+/// Calculate Keccak-256 hash of the given input, returning as a 256-bit hash.
inline h256 keccak256(bytesConstRef _input) { h256 ret; keccak256(_input, ret.ref()); return ret; }
-/// Calculate SHA3-256 hash of the given input, returning as a 256-bit hash.
+/// Calculate Keccak-256 hash of the given input, returning as a 256-bit hash.
inline h256 keccak256(bytes const& _input) { return keccak256(bytesConstRef(&_input)); }
-/// Calculate SHA3-256 hash of the given input (presented as a binary-filled string), returning as a 256-bit hash.
+/// Calculate Keccak-256 hash of the given input (presented as a binary-filled string), returning as a 256-bit hash.
inline h256 keccak256(std::string const& _input) { return keccak256(bytesConstRef(_input)); }
-/// Calculate SHA3-256 hash of the given input (presented as a FixedHash), returns a 256-bit hash.
+/// Calculate Keccak-256 hash of the given input (presented as a FixedHash), returns a 256-bit hash.
template<unsigned N> inline h256 keccak256(FixedHash<N> const& _input) { return keccak256(_input.ref()); }
-/// Calculate SHA3-256 hash of the given input, possibly interpreting it as nibbles, and return the hash as a string filled with binary data.
+/// Calculate Keccak-256 hash of the given input, possibly interpreting it as nibbles, and return the hash as a string filled with binary data.
inline std::string keccak256(std::string const& _input, bool _isNibbles) { return asString((_isNibbles ? keccak256(fromHex(_input)) : keccak256(bytesConstRef(&_input))).asBytes()); }
-/// Calculate SHA3-256 MAC
+/// Calculate Keccak-256 MAC
inline void keccak256mac(bytesConstRef _secret, bytesConstRef _plain, bytesRef _output) { keccak256(_secret.toBytes() + _plain.toBytes()).ref().populate(_output); }
}
diff --git a/libdevcore/SwarmHash.cpp b/libdevcore/SwarmHash.cpp
index aa98eafd..78188668 100644
--- a/libdevcore/SwarmHash.cpp
+++ b/libdevcore/SwarmHash.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file SwarmHash.cpp
*/
diff --git a/libdevcore/SwarmHash.h b/libdevcore/SwarmHash.h
index f474ce11..a5da96f5 100644
--- a/libdevcore/SwarmHash.h
+++ b/libdevcore/SwarmHash.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file SwarmHash.h
*/
diff --git a/libdevcore/UTF8.cpp b/libdevcore/UTF8.cpp
index 1c7ed17c..2ae720ec 100644
--- a/libdevcore/UTF8.cpp
+++ b/libdevcore/UTF8.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file UTF8.cpp
* @author Alex Beregszaszi
@@ -27,25 +27,74 @@
namespace dev
{
+namespace
+{
-bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
+/// Validate byte sequence against Unicode chapter 3 Table 3-7.
+bool isWellFormed(unsigned char byte1, unsigned char byte2)
+{
+ if (byte1 == 0xc0 || byte1 == 0xc1)
+ return false;
+ else if (byte1 >= 0xc2 && byte1 <= 0xdf)
+ return true;
+ else if (byte1 == 0xe0)
+ {
+ if (byte2 < 0xa0)
+ return false;
+ else
+ return true;
+ }
+ else if (byte1 >= 0xe1 && byte1 <= 0xec)
+ return true;
+ else if (byte1 == 0xed)
+ {
+ if (byte2 > 0x9f)
+ return false;
+ else
+ return true;
+ }
+ else if (byte1 == 0xee || byte1 == 0xef)
+ return true;
+ else if (byte1 == 0xf0)
+ {
+ if (byte2 < 0x90)
+ return false;
+ else
+ return true;
+ }
+ else if (byte1 >= 0xf1 && byte1 <= 0xf3)
+ return true;
+ else if (byte1 == 0xf4)
+ {
+ if (byte2 > 0x8f)
+ return false;
+ else
+ return true;
+ }
+ /// 0xf5 .. 0xf7 is disallowed
+ /// Technically anything below 0xc0 or above 0xf7 is
+ /// not possible to encode using Table 3-6 anyway.
+ return false;
+}
+
+bool validateUTF8(const unsigned char *_input, size_t _length, size_t& _invalidPosition)
{
- const size_t length = _input.length();
bool valid = true;
size_t i = 0;
- for (; i < length; i++)
+ for (; i < _length; i++)
{
- if ((unsigned char)_input[i] < 0x80)
+ // Check for Unicode Chapter 3 Table 3-6 conformity.
+ if (_input[i] < 0x80)
continue;
size_t count = 0;
- switch(_input[i] & 0xe0) {
- case 0xc0: count = 1; break;
- case 0xe0: count = 2; break;
- case 0xf0: count = 3; break;
- default: break;
- }
+ if (_input[i] >= 0xc0 && _input[i] <= 0xdf)
+ count = 1;
+ else if (_input[i] >= 0xe0 && _input[i] <= 0xef)
+ count = 2;
+ else if (_input[i] >= 0xf0 && _input[i] <= 0xf7)
+ count = 3;
if (count == 0)
{
@@ -53,7 +102,7 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
break;
}
- if ((i + count) >= length)
+ if ((i + count) >= _length)
{
valid = false;
break;
@@ -67,6 +116,13 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
valid = false;
break;
}
+
+ // Check for Unicode Chapter 3 Table 3-7 conformity.
+ if ((j == 0) && !isWellFormed(_input[i - 1], _input[i]))
+ {
+ valid = false;
+ break;
+ }
}
}
@@ -77,5 +133,11 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
return false;
}
+}
+
+bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
+{
+ return validateUTF8(reinterpret_cast<unsigned char const*>(_input.c_str()), _input.length(), _invalidPosition);
+}
}
diff --git a/libdevcore/UTF8.h b/libdevcore/UTF8.h
index 753914e3..1f755e70 100644
--- a/libdevcore/UTF8.h
+++ b/libdevcore/UTF8.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file UTF8.h
* @author Alex Beregszaszi
diff --git a/libdevcore/UndefMacros.h b/libdevcore/UndefMacros.h
index 91249523..d2da3323 100644
--- a/libdevcore/UndefMacros.h
+++ b/libdevcore/UndefMacros.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file UndefMacros.h
* @author Lefteris <lefteris@ethdev.com>
diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp
new file mode 100644
index 00000000..4bad8476
--- /dev/null
+++ b/libdevcore/Whiskers.cpp
@@ -0,0 +1,127 @@
+/*
+ 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/>.
+*/
+/** @file Whiskers.cpp
+ * @author Chris <chis@ethereum.org>
+ * @date 2017
+ *
+ * Moustache-like templates.
+ */
+
+#include <libdevcore/Whiskers.h>
+
+#include <libdevcore/Assertions.h>
+
+#include <boost/regex.hpp>
+
+using namespace std;
+using namespace dev;
+
+Whiskers::Whiskers(string const& _template):
+m_template(_template)
+{
+}
+
+Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value)
+{
+ assertThrow(
+ m_parameters.count(_parameter) == 0,
+ WhiskersError,
+ _parameter + " already set."
+ );
+ assertThrow(
+ m_listParameters.count(_parameter) == 0,
+ WhiskersError,
+ _parameter + " already set as list parameter."
+ );
+ m_parameters[_parameter] = _value;
+
+ return *this;
+}
+
+Whiskers& Whiskers::operator ()(
+ string const& _listParameter,
+ vector<map<string, string>> const& _values
+)
+{
+ assertThrow(
+ m_listParameters.count(_listParameter) == 0,
+ WhiskersError,
+ _listParameter + " already set."
+ );
+ assertThrow(
+ m_parameters.count(_listParameter) == 0,
+ WhiskersError,
+ _listParameter + " already set as value parameter."
+ );
+ m_listParameters[_listParameter] = _values;
+
+ return *this;
+}
+
+string Whiskers::render() const
+{
+ return replace(m_template, m_parameters, m_listParameters);
+}
+
+string Whiskers::replace(
+ string const& _template,
+ StringMap const& _parameters,
+ map<string, vector<StringMap>> const& _listParameters
+)
+{
+ using namespace boost;
+ static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>");
+ return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string
+ {
+ string tagName(_match[1]);
+ if (!tagName.empty())
+ {
+ assertThrow(_parameters.count(tagName), WhiskersError, "Tag " + tagName + " not found.");
+ return _parameters.at(tagName);
+ }
+ else
+ {
+ string listName(_match[2]);
+ string templ(_match[3]);
+ assertThrow(!listName.empty(), WhiskersError, "");
+ assertThrow(
+ _listParameters.count(listName),
+ WhiskersError, "List parameter " + listName + " not set."
+ );
+ string replacement;
+ for (auto const& parameters: _listParameters.at(listName))
+ replacement += replace(templ, joinMaps(_parameters, parameters));
+ return replacement;
+ }
+ });
+}
+
+Whiskers::StringMap Whiskers::joinMaps(
+ Whiskers::StringMap const& _a,
+ Whiskers::StringMap const& _b
+)
+{
+ Whiskers::StringMap ret = _a;
+ for (auto const& x: _b)
+ assertThrow(
+ ret.insert(x).second,
+ WhiskersError,
+ "Parameter collision"
+ );
+ return ret;
+}
+
diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h
new file mode 100644
index 00000000..21d46af4
--- /dev/null
+++ b/libdevcore/Whiskers.h
@@ -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/>.
+*/
+/** @file Whiskers.h
+ * @author Chris <chis@ethereum.org>
+ * @date 2017
+ *
+ * Moustache-like templates.
+ */
+
+#pragma once
+
+#include <libdevcore/Exceptions.h>
+
+#include <string>
+#include <map>
+#include <vector>
+
+namespace dev
+{
+
+DEV_SIMPLE_EXCEPTION(WhiskersError);
+
+///
+/// Moustache-like templates.
+///
+/// Usage:
+/// std::vector<std::map<std::string, std::string>> listValues(2);
+/// listValues[0]["k"] = "key1";
+/// listValues[0]["v"] = "value1";
+/// listValues[1]["k"] = "key2";
+/// listValues[1]["v"] = "value2";
+/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>")
+/// ("p1", "HEAD")
+/// ("list", listValues)
+/// .render();
+///
+/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
+///
+/// Note that lists cannot themselves contain lists - this would be a future feature.
+class Whiskers
+{
+public:
+ using StringMap = std::map<std::string, std::string>;
+ using StringListMap = std::map<std::string, std::vector<StringMap>>;
+
+ explicit Whiskers(std::string const& _template);
+
+ /// Sets a single parameter, <paramName>.
+ Whiskers& operator()(std::string const& _parameter, std::string const& _value);
+ /// Sets a list parameter, <#listName> </listName>.
+ Whiskers& operator()(
+ std::string const& _listParameter,
+ std::vector<StringMap> const& _values
+ );
+
+ std::string render() const;
+
+private:
+ static std::string replace(
+ std::string const& _template,
+ StringMap const& _parameters,
+ StringListMap const& _listParameters = StringListMap()
+ );
+
+ /// Joins the two maps throwing an exception if two keys are equal.
+ static StringMap joinMaps(StringMap const& _a, StringMap const& _b);
+
+ std::string m_template;
+ StringMap m_parameters;
+ StringListMap m_listParameters;
+};
+
+}
diff --git a/libdevcore/debugbreak.h b/libdevcore/debugbreak.h
deleted file mode 100644
index f8838a5f..00000000
--- a/libdevcore/debugbreak.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/* Copyright (c) 2013, Scott Tsai
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef DEBUG_BREAK_H
-#define DEBUG_BREAK_H
-
-#if defined(_MSC_VER) || defined(__MINGW32__)
-
-#define debug_break __debugbreak
-
-#else
-
-#include <signal.h>
-#include <unistd.h>
-#include <sys/syscall.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-enum {
- /* gcc optimizers consider code after __builtin_trap() dead.
- * Making __builtin_trap() unsuitable for breaking into the debugger */
- DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP = 0,
-};
-
-#if defined(__i386__) || defined(__x86_64__)
-enum { HAVE_TRAP_INSTRUCTION = 1, };
-__attribute__((gnu_inline, always_inline))
-static void __inline__ trap_instruction(void)
-{
- __asm__ volatile("int $0x03");
-}
-#elif defined(__thumb__)
-enum { HAVE_TRAP_INSTRUCTION = 1, };
-/* FIXME: handle __THUMB_INTERWORK__ */
-__attribute__((gnu_inline, always_inline))
-static void __inline__ trap_instruction(void)
-{
- /* See 'arm-linux-tdep.c' in GDB source.
- * Both instruction sequences below works. */
-#if 1
- /* 'eabi_linux_thumb_le_breakpoint' */
- __asm__ volatile(".inst 0xde01");
-#else
- /* 'eabi_linux_thumb2_le_breakpoint' */
- __asm__ volatile(".inst.w 0xf7f0a000");
-#endif
-
- /* Known problem:
- * After a breakpoint hit, can't stepi, step, or continue in GDB.
- * 'step' stuck on the same instruction.
- *
- * Workaround: a new GDB command,
- * 'debugbreak-step' is defined in debugbreak-gdb.py
- * that does:
- * (gdb) set $instruction_len = 2
- * (gdb) tbreak *($pc + $instruction_len)
- * (gdb) jump *($pc + $instruction_len)
- */
-}
-#elif defined(__arm__) && !defined(__thumb__)
-enum { HAVE_TRAP_INSTRUCTION = 1, };
-__attribute__((gnu_inline, always_inline))
-static void __inline__ trap_instruction(void)
-{
- /* See 'arm-linux-tdep.c' in GDB source,
- * 'eabi_linux_arm_le_breakpoint' */
- __asm__ volatile(".inst 0xe7f001f0");
- /* Has same known problem and workaround
- * as Thumb mode */
-}
-#elif defined(ETH_EMSCRIPTEN)
-enum { HAVE_TRAP_INSTRUCTION = 1, };
-__attribute__((gnu_inline, always_inline))
-static void __inline__ trap_instruction(void)
-{
- asm("debugger");
-}
-#else
-enum { HAVE_TRAP_INSTRUCTION = 0, };
-#endif
-
-__attribute__((gnu_inline, always_inline))
-static void __inline__ debug_break(void)
-{
- if (HAVE_TRAP_INSTRUCTION) {
- trap_instruction();
- } else if (DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP) {
- /* raises SIGILL on Linux x86{,-64}, to continue in gdb:
- * (gdb) handle SIGILL stop nopass
- * */
- __builtin_trap();
- } else {
- raise(SIGTRAP);
- }
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
-
-#endif
diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp
index a9ca24dc..597fdae1 100644
--- a/libevmasm/Assembly.cpp
+++ b/libevmasm/Assembly.cpp
@@ -55,28 +55,15 @@ void Assembly::append(Assembly const& _a)
m_subs += _a.m_subs;
for (auto const& lib: _a.m_libraries)
m_libraries.insert(lib);
-
- assert(!_a.m_baseDeposit);
- assert(!_a.m_totalDeposit);
}
void Assembly::append(Assembly const& _a, int _deposit)
{
- if (_deposit > _a.m_deposit)
- BOOST_THROW_EXCEPTION(InvalidDeposit());
- else
- {
- append(_a);
- while (_deposit++ < _a.m_deposit)
- append(Instruction::POP);
- }
-}
+ assertThrow(_deposit <= _a.m_deposit, InvalidDeposit, "");
-string Assembly::out() const
-{
- stringstream ret;
- stream(ret);
- return ret.str();
+ append(_a);
+ while (_deposit++ < _a.m_deposit)
+ append(Instruction::POP);
}
unsigned Assembly::bytesRequired(unsigned subTagSize) const
@@ -94,7 +81,10 @@ unsigned Assembly::bytesRequired(unsigned subTagSize) const
}
}
-string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const
+namespace
+{
+
+string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location)
{
if (_location.isEmpty() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0)
return "";
@@ -115,71 +105,107 @@ string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocati
return cut;
}
-ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
+class Functionalizer
{
- _out << _prefix << ".code:" << endl;
- for (AssemblyItem const& i: m_items)
+public:
+ Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes):
+ m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes)
+ {}
+
+ void feed(AssemblyItem const& _item)
{
- _out << _prefix;
- switch (i.type())
+ if (!_item.location().isEmpty() && _item.location() != m_location)
{
- case Operation:
- _out << " " << instructionInfo(i.instruction()).name << "\t" << i.getJumpTypeAsString();
- break;
- case Push:
- _out << " PUSH" << dec << max<unsigned>(1, dev::bytesRequired(i.data())) << " 0x" << hex << i.data();
- break;
- case PushString:
- _out << " PUSH \"" << m_strings.at((h256)i.data()) << "\"";
- break;
- case PushTag:
- if (i.data() == 0)
- _out << " PUSH [ErrorTag]";
- else
+ flush();
+ m_location = _item.location();
+ printLocation();
+ }
+ if (!(
+ _item.canBeFunctional() &&
+ _item.returnValues() <= 1 &&
+ _item.arguments() <= int(m_pending.size())
+ ))
+ {
+ flush();
+ m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl;
+ return;
+ }
+ string expression = _item.toAssemblyText();
+ if (_item.arguments() > 0)
+ {
+ expression += "(";
+ for (int i = 0; i < _item.arguments(); ++i)
{
- size_t subId = i.splitForeignPushTag().first;
- if (subId == size_t(-1))
- _out << " PUSH [tag" << dec << i.splitForeignPushTag().second << "]";
- else
- _out << " PUSH [tag" << dec << subId << ":" << i.splitForeignPushTag().second << "]";
+ expression += m_pending.back();
+ m_pending.pop_back();
+ if (i + 1 < _item.arguments())
+ expression += ", ";
}
- break;
- case PushSub:
- _out << " PUSH [$" << size_t(i.data()) << "]";
- break;
- case PushSubSize:
- _out << " PUSH #[$" << size_t(i.data()) << "]";
- break;
- case PushProgramSize:
- _out << " PUSHSIZE";
- break;
- case PushLibraryAddress:
- _out << " PUSHLIB \"" << m_libraries.at(h256(i.data())) << "\"";
- break;
- case Tag:
- _out << "tag" << dec << i.data() << ": " << endl << _prefix << " JUMPDEST";
- break;
- case PushData:
- _out << " PUSH [" << hex << (unsigned)i.data() << "]";
- break;
- default:
- BOOST_THROW_EXCEPTION(InvalidOpcode());
+ expression += ")";
}
- _out << "\t\t" << locationFromSources(_sourceCodes, i.location()) << endl;
+
+ m_pending.push_back(expression);
+ if (_item.returnValues() != 1)
+ flush();
+ }
+
+ void flush()
+ {
+ for (string const& expression: m_pending)
+ m_out << m_prefix << " " << expression << endl;
+ m_pending.clear();
}
+ void printLocation()
+ {
+ if (!m_location.sourceName && m_location.isEmpty())
+ return;
+ m_out << m_prefix << " /*";
+ if (m_location.sourceName)
+ m_out << " \"" + *m_location.sourceName + "\"";
+ if (!m_location.isEmpty())
+ m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
+ m_out << " " << locationFromSources(m_sourceCodes, m_location);
+ m_out << " */" << endl;
+ }
+
+private:
+ strings m_pending;
+ SourceLocation m_location;
+
+ ostream& m_out;
+ string const& m_prefix;
+ StringMap const& m_sourceCodes;
+};
+
+}
+
+ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
+{
+ Functionalizer f(_out, _prefix, _sourceCodes);
+
+ for (auto const& i: m_items)
+ f.feed(i);
+ f.flush();
+
if (!m_data.empty() || !m_subs.empty())
{
- _out << _prefix << ".data:" << endl;
+ _out << _prefix << "stop" << endl;
for (auto const& i: m_data)
if (u256(i.first) >= m_subs.size())
- _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << dev::toHex(i.second) << endl;
+ _out << _prefix << "data_" << toHex(u256(i.first)) << " " << toHex(i.second) << endl;
+
for (size_t i = 0; i < m_subs.size(); ++i)
{
- _out << _prefix << " " << hex << i << ": " << endl;
- m_subs[i]->stream(_out, _prefix + " ", _sourceCodes);
+ _out << endl << _prefix << "sub_" << i << ": assembly {\n";
+ m_subs[i]->streamAsm(_out, _prefix + " ", _sourceCodes);
+ _out << _prefix << "}" << endl;
}
}
+
+ if (m_auxiliaryData.size() > 0)
+ _out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl;
+
return _out;
}
@@ -279,8 +305,13 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes
data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true);
}
root[".data"] = data;
- _out << root;
}
+
+ if (m_auxiliaryData.size() > 0)
+ root[".auxdata"] = toHex(m_auxiliaryData);
+
+ _out << root;
+
return root;
}
@@ -297,6 +328,7 @@ Json::Value Assembly::stream(ostream& _out, string const& _prefix, StringMap con
AssemblyItem const& Assembly::append(AssemblyItem const& _i)
{
+ assertThrow(m_deposit >= 0, AssemblyException, "");
m_deposit += _i.deposit();
m_items.push_back(_i);
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty())
@@ -449,7 +481,7 @@ LinkerObject const& Assembly::assemble() const
switch (i.type())
{
case Operation:
- ret.bytecode.push_back((byte)i.data());
+ ret.bytecode.push_back((byte)i.instruction());
break;
case PushString:
{
diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h
index 9e7f9f7b..0d40abcf 100644
--- a/libevmasm/Assembly.h
+++ b/libevmasm/Assembly.h
@@ -69,7 +69,13 @@ public:
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 errorTag() { return AssemblyItem(PushTag, 0); }
+
+ /// 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.
+ AssemblyItem appendSubroutine(AssemblyPointer const& _assembly) { auto sub = newSub(_assembly); append(newPushSubSize(size_t(sub.data()))); return sub; }
+ void pushSubroutineSize(size_t _subRoutine) { append(newPushSubSize(_subRoutine)); }
+ /// Pushes the offset of the subroutine.
+ void pushSubroutineOffset(size_t _subRoutine) { append(AssemblyItem(PushSub, _subRoutine)); }
/// Appends @a _data literally to the very end of the bytecode.
void appendAuxiliaryDataToEnd(bytes const& _data) { m_auxiliaryData += _data; }
@@ -79,19 +85,10 @@ public:
AssemblyItem const& back() const { return m_items.back(); }
std::string backString() const { return m_items.size() && m_items.back().type() == PushString ? m_strings.at((h256)m_items.back().data()) : std::string(); }
- void onePath() { if (asserts(!m_totalDeposit && !m_baseDeposit)) BOOST_THROW_EXCEPTION(InvalidDeposit()); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; }
- void otherPath() { donePath(); m_totalDeposit = m_deposit; m_deposit = m_baseDeposit; }
- void donePaths() { donePath(); m_totalDeposit = m_baseDeposit = 0; }
- void ignored() { m_baseDeposit = m_deposit; }
- void endIgnored() { m_deposit = m_baseDeposit; m_baseDeposit = 0; }
-
- void popTo(int _deposit) { while (m_deposit > _deposit) append(solidity::Instruction::POP); }
-
void injectStart(AssemblyItem const& _i);
- std::string out() const;
int deposit() const { return m_deposit; }
- void adjustDeposit(int _adjustment) { m_deposit += _adjustment; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
- void setDeposit(int _deposit) { m_deposit = _deposit; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
+ void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); }
+ void setDeposit(int _deposit) { m_deposit = _deposit; assertThrow(m_deposit >= 0, InvalidDeposit, ""); }
/// Changes the source location used for each appended item.
void setSourceLocation(SourceLocation const& _location) { m_currentSourceLocation = _location; }
@@ -118,8 +115,6 @@ protected:
/// returns the replaced tags.
std::map<u256, u256> optimiseInternal(bool _enable, bool _isCreation, size_t _runs);
- std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const;
- void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
unsigned bytesRequired(unsigned subTagSize) const;
private:
@@ -142,8 +137,6 @@ protected:
mutable std::vector<size_t> m_tagPositionsInBytecode;
int m_deposit = 0;
- int m_baseDeposit = 0;
- int m_totalDeposit = 0;
SourceLocation m_currentSourceLocation;
};
diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp
index 54e38de8..e69b5932 100644
--- a/libevmasm/AssemblyItem.cpp
+++ b/libevmasm/AssemblyItem.cpp
@@ -20,6 +20,7 @@
*/
#include "AssemblyItem.h"
+#include <libevmasm/SemanticInformation.h>
#include <fstream>
using namespace std;
@@ -28,19 +29,19 @@ using namespace dev::eth;
AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const
{
- assertThrow(m_data < (u256(1) << 64), Exception, "Tag already has subassembly set.");
+ assertThrow(data() < (u256(1) << 64), Exception, "Tag already has subassembly set.");
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
AssemblyItem r = *this;
r.m_type = PushTag;
- r.setPushTagSubIdAndTag(_subId, size_t(m_data));
+ r.setPushTagSubIdAndTag(_subId, size_t(data()));
return r;
}
pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const
{
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
- return make_pair(size_t(m_data / (u256(1) << 64)) - 1, size_t(m_data));
+ return make_pair(size_t((data()) / (u256(1) << 64)) - 1, size_t(data()));
}
void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
@@ -59,7 +60,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
case PushString:
return 33;
case Push:
- return 1 + max<unsigned>(1, dev::bytesRequired(m_data));
+ return 1 + max<unsigned>(1, dev::bytesRequired(data()));
case PushSubSize:
case PushProgramSize:
return 4; // worst case: a 16MB program
@@ -75,12 +76,20 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
BOOST_THROW_EXCEPTION(InvalidOpcode());
}
-int AssemblyItem::deposit() const
+int AssemblyItem::arguments() const
+{
+ if (type() == Operation)
+ return instructionInfo(instruction()).args;
+ else
+ return 0;
+}
+
+int AssemblyItem::returnValues() const
{
switch (m_type)
{
case Operation:
- return instructionInfo(instruction()).ret - instructionInfo(instruction()).args;
+ return instructionInfo(instruction()).ret;
case Push:
case PushString:
case PushTag:
@@ -97,6 +106,28 @@ int AssemblyItem::deposit() const
return 0;
}
+bool AssemblyItem::canBeFunctional() const
+{
+ switch (m_type)
+ {
+ case Operation:
+ return !SemanticInformation::isDupInstruction(*this) && !SemanticInformation::isSwapInstruction(*this);
+ case Push:
+ case PushString:
+ case PushTag:
+ case PushData:
+ case PushSub:
+ case PushSubSize:
+ case PushProgramSize:
+ case PushLibraryAddress:
+ return true;
+ case Tag:
+ return false;
+ default:;
+ }
+ return 0;
+}
+
string AssemblyItem::getJumpTypeAsString() const
{
switch (m_jumpType)
@@ -111,6 +142,72 @@ string AssemblyItem::getJumpTypeAsString() const
}
}
+string AssemblyItem::toAssemblyText() const
+{
+ string text;
+ switch (type())
+ {
+ case Operation:
+ {
+ assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction.");
+ string name = instructionInfo(instruction()).name;
+ transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
+ text = name;
+ break;
+ }
+ case Push:
+ text = toHex(toCompactBigEndian(data(), 1), 1, HexPrefix::Add);
+ break;
+ case PushString:
+ text = string("data_") + toHex(data());
+ break;
+ case PushTag:
+ {
+ size_t sub{0};
+ size_t tag{0};
+ tie(sub, tag) = splitForeignPushTag();
+ if (sub == size_t(-1))
+ text = string("tag_") + to_string(tag);
+ else
+ text = string("tag_") + to_string(sub) + "_" + to_string(tag);
+ break;
+ }
+ case Tag:
+ assertThrow(data() < 0x10000, AssemblyException, "Declaration of sub-assembly tag.");
+ text = string("tag_") + to_string(size_t(data())) + ":";
+ break;
+ case PushData:
+ text = string("data_") + toHex(data());
+ break;
+ case PushSub:
+ text = string("dataOffset(sub_") + to_string(size_t(data())) + ")";
+ break;
+ case PushSubSize:
+ text = string("dataSize(sub_") + to_string(size_t(data())) + ")";
+ break;
+ case PushProgramSize:
+ text = string("bytecodeSize");
+ break;
+ case PushLibraryAddress:
+ text = string("linkerSymbol(\"") + toHex(data()) + string("\")");
+ break;
+ case UndefinedItem:
+ assertThrow(false, AssemblyException, "Invalid assembly item.");
+ break;
+ default:
+ BOOST_THROW_EXCEPTION(InvalidOpcode());
+ }
+ if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
+ {
+ text += "\t//";
+ if (m_jumpType == JumpType::IntoFunction)
+ text += " in";
+ else
+ text += " out";
+ }
+ return text;
+}
+
ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item)
{
switch (_item.type())
diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h
index b5bd3ed8..d38db927 100644
--- a/libevmasm/AssemblyItem.h
+++ b/libevmasm/AssemblyItem.h
@@ -59,16 +59,22 @@ public:
AssemblyItem(u256 _push, SourceLocation const& _location = SourceLocation()):
AssemblyItem(Push, _push, _location) { }
AssemblyItem(solidity::Instruction _i, SourceLocation const& _location = SourceLocation()):
- AssemblyItem(Operation, byte(_i), _location) { }
+ m_type(Operation),
+ m_instruction(_i),
+ m_location(_location)
+ {}
AssemblyItem(AssemblyItemType _type, u256 _data = 0, SourceLocation const& _location = SourceLocation()):
m_type(_type),
- m_data(_data),
m_location(_location)
{
+ if (m_type == Operation)
+ m_instruction = Instruction(byte(_data));
+ else
+ m_data = std::make_shared<u256>(_data);
}
- AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); }
- AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); }
+ AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, data()); }
+ AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, data()); }
/// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies.
/// @param _subId the identifier of the subassembly the tag is taken from.
AssemblyItem toSubAssemblyTag(size_t _subId) const;
@@ -79,25 +85,44 @@ public:
void setPushTagSubIdAndTag(size_t _subId, size_t _tag);
AssemblyItemType type() const { return m_type; }
- u256 const& data() const { return m_data; }
- void setType(AssemblyItemType const _type) { m_type = _type; }
- void setData(u256 const& _data) { m_data = _data; }
+ u256 const& data() const { assertThrow(m_type != Operation, Exception, ""); return *m_data; }
+ void setData(u256 const& _data) { assertThrow(m_type != Operation, Exception, ""); m_data = std::make_shared<u256>(_data); }
/// @returns the instruction of this item (only valid if type() == Operation)
- Instruction instruction() const { return Instruction(byte(m_data)); }
+ Instruction instruction() const { assertThrow(m_type == Operation, Exception, ""); return m_instruction; }
/// @returns true if the type and data of the items are equal.
- bool operator==(AssemblyItem const& _other) const { return m_type == _other.m_type && m_data == _other.m_data; }
+ bool operator==(AssemblyItem const& _other) const
+ {
+ if (type() != _other.type())
+ return false;
+ if (type() == Operation)
+ return instruction() == _other.instruction();
+ else
+ return data() == _other.data();
+ }
bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); }
/// Less-than operator compatible with operator==.
- bool operator<(AssemblyItem const& _other) const { return std::tie(m_type, m_data) < std::tie(_other.m_type, _other.m_data); }
+ bool operator<(AssemblyItem const& _other) const
+ {
+ if (type() != _other.type())
+ return type() < _other.type();
+ else if (type() == Operation)
+ return instruction() < _other.instruction();
+ else
+ return data() < _other.data();
+ }
/// @returns an upper bound for the number of bytes required by this item, assuming that
/// the value of a jump tag takes @a _addressLength bytes.
unsigned bytesRequired(unsigned _addressLength) const;
- int deposit() const;
+ int arguments() const;
+ int returnValues() const;
+ int deposit() const { return returnValues() - arguments(); }
+
+ /// @returns true if the assembly item can be used in a functional context.
+ bool canBeFunctional() const;
- bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); }
void setLocation(SourceLocation const& _location) { m_location = _location; }
SourceLocation const& location() const { return m_location; }
@@ -108,9 +133,12 @@ public:
void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_value); }
u256 const* pushedValue() const { return m_pushedValue.get(); }
+ std::string toAssemblyText() const;
+
private:
AssemblyItemType m_type;
- u256 m_data;
+ Instruction m_instruction; ///< Only valid if m_type == Operation
+ std::shared_ptr<u256> m_data; ///< Only valid if m_type != Operation
SourceLocation m_location;
JumpType m_jumpType = JumpType::Ordinary;
/// Pushed value for operations with data to be determined during assembly stage,
@@ -120,6 +148,14 @@ private:
using AssemblyItems = std::vector<AssemblyItem>;
+inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
+{
+ size_t size = 0;
+ for (AssemblyItem const& item: _items)
+ size += item.bytesRequired(_addressLength);
+ return size;
+}
+
std::ostream& operator<<(std::ostream& _out, AssemblyItem const& _item);
inline std::ostream& operator<<(std::ostream& _out, AssemblyItems const& _items)
{
diff --git a/libevmasm/CommonSubexpressionEliminator.cpp b/libevmasm/CommonSubexpressionEliminator.cpp
index 6294e579..70324e7f 100644
--- a/libevmasm/CommonSubexpressionEliminator.cpp
+++ b/libevmasm/CommonSubexpressionEliminator.cpp
@@ -234,7 +234,7 @@ void CSECodeGenerator::addDependencies(Id _c)
if (expr.item && expr.item->type() == Operation && (
expr.item->instruction() == Instruction::SLOAD ||
expr.item->instruction() == Instruction::MLOAD ||
- expr.item->instruction() == Instruction::SHA3
+ expr.item->instruction() == Instruction::KECCAK256
))
{
// this loads an unknown value from storage or memory and thus, in addition to its
@@ -260,7 +260,7 @@ void CSECodeGenerator::addDependencies(Id _c)
case Instruction::MLOAD:
knownToBeIndependent = m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom);
break;
- case Instruction::SHA3:
+ case Instruction::KECCAK256:
{
Id length = expr.arguments.at(1);
AssemblyItem offsetInstr(Instruction::SUB, expr.item->location());
@@ -303,7 +303,9 @@ void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
for (auto it: m_classPositions)
for (auto p: it.second)
if (p > m_stackHeight)
+ {
assertThrow(false, OptimizerException, "");
+ }
// do some cleanup
removeStackTopIfPossible();
diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp
index f4a50c2d..2ecbfa7f 100644
--- a/libevmasm/ConstantOptimiser.cpp
+++ b/libevmasm/ConstantOptimiser.cpp
@@ -38,6 +38,7 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
for (AssemblyItem const& item: _items)
if (item.type() == Push)
pushes[item]++;
+ map<u256, AssemblyItems> pendingReplacements;
for (auto it: pushes)
{
AssemblyItem const& item = it.first;
@@ -53,17 +54,22 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
bigint copyGas = copy.gasNeeded();
ComputeMethod compute(params, item.data());
bigint computeGas = compute.gasNeeded();
+ AssemblyItems replacement;
if (copyGas < literalGas && copyGas < computeGas)
{
- copy.execute(_assembly, _items);
+ replacement = copy.execute(_assembly);
optimisations++;
}
- else if (computeGas < literalGas && computeGas < copyGas)
+ else if (computeGas < literalGas && computeGas <= copyGas)
{
- compute.execute(_assembly, _items);
+ replacement = compute.execute(_assembly);
optimisations++;
}
+ if (!replacement.empty())
+ pendingReplacements[item.data()] = replacement;
}
+ if (!pendingReplacements.empty())
+ replaceConstants(_items, pendingReplacements);
return optimisations;
}
@@ -93,26 +99,29 @@ bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items)
{
- size_t size = 0;
- for (AssemblyItem const& item: _items)
- size += item.bytesRequired(3); // assume 3 byte addresses
- return size;
+ return eth::bytesRequired(_items, 3); // assume 3 byte addresses
}
void ConstantOptimisationMethod::replaceConstants(
AssemblyItems& _items,
- AssemblyItems const& _replacement
-) const
+ map<u256, AssemblyItems> const& _replacements
+)
{
- assertThrow(_items.size() > 0, OptimizerException, "");
- for (size_t i = 0; i < _items.size(); ++i)
+ AssemblyItems replaced;
+ for (AssemblyItem const& item: _items)
{
- if (_items.at(i) != AssemblyItem(m_value))
- continue;
- _items[i] = _replacement[0];
- _items.insert(_items.begin() + i + 1, _replacement.begin() + 1, _replacement.end());
- i += _replacement.size() - 1;
+ if (item.type() == Push)
+ {
+ auto it = _replacements.find(item.data());
+ if (it != _replacements.end())
+ {
+ replaced += it->second;
+ continue;
+ }
+ }
+ replaced.push_back(item);
}
+ _items = std::move(replaced);
}
bigint LiteralMethod::gasNeeded()
@@ -128,38 +137,44 @@ bigint LiteralMethod::gasNeeded()
CodeCopyMethod::CodeCopyMethod(Params const& _params, u256 const& _value):
ConstantOptimisationMethod(_params, _value)
{
- m_copyRoutine = AssemblyItems{
- u256(0),
- Instruction::DUP1,
- Instruction::MLOAD, // back up memory
- u256(32),
- AssemblyItem(PushData, u256(1) << 16), // has to be replaced
- Instruction::DUP4,
- Instruction::CODECOPY,
- Instruction::DUP2,
- Instruction::MLOAD,
- Instruction::SWAP2,
- Instruction::MSTORE
- };
}
bigint CodeCopyMethod::gasNeeded()
{
return combineGas(
// Run gas: we ignore memory increase costs
- simpleRunGas(m_copyRoutine) + GasCosts::copyGas,
+ simpleRunGas(copyRoutine()) + GasCosts::copyGas,
// Data gas for copy routines: Some bytes are zero, but we ignore them.
- bytesRequired(m_copyRoutine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas),
+ bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas),
// Data gas for data itself
dataGas(toBigEndian(m_value))
);
}
-void CodeCopyMethod::execute(Assembly& _assembly, AssemblyItems& _items)
+AssemblyItems CodeCopyMethod::execute(Assembly& _assembly)
{
bytes data = toBigEndian(m_value);
- m_copyRoutine[4] = _assembly.newData(data);
- replaceConstants(_items, m_copyRoutine);
+ AssemblyItems actualCopyRoutine = copyRoutine();
+ actualCopyRoutine[4] = _assembly.newData(data);
+ return actualCopyRoutine;
+}
+
+AssemblyItems const& CodeCopyMethod::copyRoutine() const
+{
+ AssemblyItems static copyRoutine{
+ u256(0),
+ Instruction::DUP1,
+ Instruction::MLOAD, // back up memory
+ u256(32),
+ AssemblyItem(PushData, u256(1) << 16), // has to be replaced
+ Instruction::DUP4,
+ Instruction::CODECOPY,
+ Instruction::DUP2,
+ Instruction::MLOAD,
+ Instruction::SWAP2,
+ Instruction::MSTORE
+ };
+ return copyRoutine;
}
AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
@@ -176,7 +191,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
// Is not always better, try literal and decomposition method.
AssemblyItems routine{u256(_value)};
bigint bestGas = gasNeeded(routine);
- for (unsigned bits = 255; bits > 8; --bits)
+ for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits)
{
unsigned gapDetector = unsigned(_value >> (bits - 8)) & 0x1ff;
if (gapDetector != 0xff && gapDetector != 0x100)
@@ -185,8 +200,13 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
u256 powerOfTwo = u256(1) << bits;
u256 upperPart = _value >> bits;
bigint lowerPart = _value & (powerOfTwo - 1);
- if (abs(powerOfTwo - lowerPart) < lowerPart)
+ if ((powerOfTwo - lowerPart) < lowerPart)
+ {
lowerPart = lowerPart - powerOfTwo; // make it negative
+ upperPart++;
+ }
+ if (upperPart == 0)
+ continue;
if (abs(lowerPart) >= (powerOfTwo >> 8))
continue;
@@ -194,13 +214,15 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
if (lowerPart != 0)
newRoutine += findRepresentation(u256(abs(lowerPart)));
newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
- if (upperPart != 1 && upperPart != 0)
+ if (upperPart != 1)
newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
if (lowerPart > 0)
newRoutine += AssemblyItems{Instruction::ADD};
else if (lowerPart < 0)
newRoutine.push_back(Instruction::SUB);
+ if (m_maxSteps > 0)
+ m_maxSteps--;
bigint newGas = gasNeeded(newRoutine);
if (newGas < bestGas)
{
@@ -212,6 +234,54 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
}
}
+bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine)
+{
+ // This is a tiny EVM that can only evaluate some instructions.
+ vector<u256> stack;
+ for (AssemblyItem const& item: _routine)
+ {
+ switch (item.type())
+ {
+ case Operation:
+ {
+ if (stack.size() < size_t(item.arguments()))
+ return false;
+ u256* sp = &stack.back();
+ switch (item.instruction())
+ {
+ case Instruction::MUL:
+ sp[-1] = sp[0] * sp[-1];
+ break;
+ case Instruction::EXP:
+ if (sp[-1] > 0xff)
+ return false;
+ sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1]));
+ break;
+ case Instruction::ADD:
+ sp[-1] = sp[0] + sp[-1];
+ break;
+ case Instruction::SUB:
+ sp[-1] = sp[0] - sp[-1];
+ break;
+ case Instruction::NOT:
+ sp[0] = ~sp[0];
+ break;
+ default:
+ return false;
+ }
+ stack.resize(stack.size() + item.deposit());
+ break;
+ }
+ case Push:
+ stack.push_back(item.data());
+ break;
+ default:
+ return false;
+ }
+ }
+ return stack.size() == 1 && stack.front() == _value;
+}
+
bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine)
{
size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP);
diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h
index b35b2a69..85bdabac 100644
--- a/libevmasm/ConstantOptimiser.h
+++ b/libevmasm/ConstantOptimiser.h
@@ -21,10 +21,14 @@
#pragma once
-#include <vector>
+#include <libevmasm/Exceptions.h>
+
+#include <libdevcore/Assertions.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/CommonIO.h>
+#include <vector>
+
namespace dev
{
namespace eth
@@ -60,7 +64,10 @@ public:
explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value):
m_params(_params), m_value(_value) {}
virtual bigint gasNeeded() = 0;
- virtual void execute(Assembly& _assembly, AssemblyItems& _items) = 0;
+ /// Executes the method, potentially appending to the assembly and returns a vector of
+ /// assembly items the constant should be relpaced with in one sweep.
+ /// If the vector is empty, the constants will not be deleted.
+ virtual AssemblyItems execute(Assembly& _assembly) = 0;
protected:
size_t dataSize() const { return std::max<size_t>(1, dev::bytesRequired(m_value)); }
@@ -83,8 +90,8 @@ protected:
return m_params.runs * _runGas + m_params.multiplicity * _repeatedDataGas + _uniqueDataGas;
}
- /// Replaces the constant by the code given in @a _replacement.
- void replaceConstants(AssemblyItems& _items, AssemblyItems const& _replacement) const;
+ /// Replaces all constants i by the code given in @a _replacement[i].
+ static void replaceConstants(AssemblyItems& _items, std::map<u256, AssemblyItems> const& _replacement);
Params m_params;
u256 const& m_value;
@@ -100,7 +107,7 @@ public:
explicit LiteralMethod(Params const& _params, u256 const& _value):
ConstantOptimisationMethod(_params, _value) {}
virtual bigint gasNeeded() override;
- virtual void execute(Assembly&, AssemblyItems&) override {}
+ virtual AssemblyItems execute(Assembly&) override { return AssemblyItems{}; }
};
/**
@@ -111,10 +118,10 @@ class CodeCopyMethod: public ConstantOptimisationMethod
public:
explicit CodeCopyMethod(Params const& _params, u256 const& _value);
virtual bigint gasNeeded() override;
- virtual void execute(Assembly& _assembly, AssemblyItems& _items) override;
+ virtual AssemblyItems execute(Assembly& _assembly) override;
protected:
- AssemblyItems m_copyRoutine;
+ AssemblyItems const& copyRoutine() const;
};
/**
@@ -127,19 +134,28 @@ public:
ConstantOptimisationMethod(_params, _value)
{
m_routine = findRepresentation(m_value);
+ assertThrow(
+ checkRepresentation(m_value, m_routine),
+ OptimizerException,
+ "Invalid constant expression created."
+ );
}
virtual bigint gasNeeded() override { return gasNeeded(m_routine); }
- virtual void execute(Assembly&, AssemblyItems& _items) override
+ virtual AssemblyItems execute(Assembly&) override
{
- replaceConstants(_items, m_routine);
+ return m_routine;
}
protected:
/// Tries to recursively find a way to compute @a _value.
AssemblyItems findRepresentation(u256 const& _value);
+ /// Recomputes the value from the calculated representation and checks for correctness.
+ bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine);
bigint gasNeeded(AssemblyItems const& _routine);
+ /// Counter for the complexity of optimization, will stop when it reaches zero.
+ size_t m_maxSteps = 10000;
AssemblyItems m_routine;
};
diff --git a/libevmasm/EVMSchedule.h b/libevmasm/EVMSchedule.h
index f882f006..1695a59c 100644
--- a/libevmasm/EVMSchedule.h
+++ b/libevmasm/EVMSchedule.h
@@ -32,9 +32,9 @@ struct EVMSchedule
unsigned stackLimit = 1024;
unsigned expGas = 10;
unsigned expByteGas = 10;
- unsigned sha3Gas = 30;
- unsigned sha3WordGas = 6;
- unsigned sloadGas = 50;
+ unsigned keccak256Gas = 30;
+ unsigned keccak256WordGas = 6;
+ unsigned sloadGas = 200;
unsigned sstoreSetGas = 20000;
unsigned sstoreResetGas = 5000;
unsigned sstoreRefundGas = 15000;
@@ -47,7 +47,7 @@ struct EVMSchedule
unsigned callStipend = 2300;
unsigned callValueTransferGas = 9000;
unsigned callNewAccountGas = 25000;
- unsigned suicideRefundGas = 24000;
+ unsigned selfdestructRefundGas = 24000;
unsigned memoryGas = 3;
unsigned quadCoeffDiv = 512;
unsigned createDataGas = 200;
diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp
index d5ccd7e3..fc283b0b 100644
--- a/libevmasm/ExpressionClasses.cpp
+++ b/libevmasm/ExpressionClasses.cpp
@@ -29,6 +29,7 @@
#include <boost/noncopyable.hpp>
#include <libevmasm/Assembly.h>
#include <libevmasm/CommonSubexpressionEliminator.h>
+#include <libevmasm/SimplificationRules.h>
using namespace std;
using namespace dev;
@@ -40,8 +41,18 @@ bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression cons
assertThrow(!!item && !!_other.item, OptimizerException, "");
auto type = item->type();
auto otherType = _other.item->type();
- return std::tie(type, item->data(), arguments, sequenceNumber) <
- std::tie(otherType, _other.item->data(), _other.arguments, _other.sequenceNumber);
+ if (type != otherType)
+ return type < otherType;
+ else if (type == Operation)
+ {
+ auto instr = item->instruction();
+ auto otherInstr = _other.item->instruction();
+ return std::tie(instr, arguments, sequenceNumber) <
+ std::tie(otherInstr, _other.arguments, _other.sequenceNumber);
+ }
+ else
+ return std::tie(item->data(), arguments, sequenceNumber) <
+ std::tie(_other.item->data(), _other.arguments, _other.sequenceNumber);
}
ExpressionClasses::Id ExpressionClasses::find(
@@ -170,191 +181,6 @@ string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const
return str.str();
}
-class Rules: public boost::noncopyable
-{
-public:
- Rules();
- void resetMatchGroups() { m_matchGroups.clear(); }
- vector<pair<Pattern, function<Pattern()>>> rules() const { return m_rules; }
-
-private:
- using Expression = ExpressionClasses::Expression;
- map<unsigned, Expression const*> m_matchGroups;
- vector<pair<Pattern, function<Pattern()>>> m_rules;
-};
-
-template <class S> S divWorkaround(S const& _a, S const& _b)
-{
- return (S)(bigint(_a) / bigint(_b));
-}
-
-template <class S> S modWorkaround(S const& _a, S const& _b)
-{
- return (S)(bigint(_a) % bigint(_b));
-}
-
-Rules::Rules()
-{
- // Multiple occurences of one of these inside one rule must match the same equivalence class.
- // Constants.
- Pattern A(Push);
- Pattern B(Push);
- Pattern C(Push);
- // Anything.
- Pattern X;
- Pattern Y;
- Pattern Z;
- A.setMatchGroup(1, m_matchGroups);
- B.setMatchGroup(2, m_matchGroups);
- C.setMatchGroup(3, m_matchGroups);
- X.setMatchGroup(4, m_matchGroups);
- Y.setMatchGroup(5, m_matchGroups);
- Z.setMatchGroup(6, m_matchGroups);
-
- m_rules = vector<pair<Pattern, function<Pattern()>>>{
- // arithmetics on constants
- {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }},
- {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }},
- {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }},
- {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }},
- {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }},
- {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }},
- {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }},
- {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }},
- {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }},
- {{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }},
- {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }},
- {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }},
- {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }},
- {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }},
- {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }},
- {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }},
- {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }},
- {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }},
- {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }},
- {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }},
- {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }},
- {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }},
- {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 {
- if (A.d() >= 31)
- return B.d();
- unsigned testBit = unsigned(A.d()) * 8 + 7;
- u256 mask = (u256(1) << testBit) - 1;
- return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
- }},
-
- // invariants involving known constants
- {{Instruction::ADD, {X, 0}}, [=]{ return X; }},
- {{Instruction::SUB, {X, 0}}, [=]{ return X; }},
- {{Instruction::MUL, {X, 1}}, [=]{ return X; }},
- {{Instruction::DIV, {X, 1}}, [=]{ return X; }},
- {{Instruction::SDIV, {X, 1}}, [=]{ return X; }},
- {{Instruction::OR, {X, 0}}, [=]{ return X; }},
- {{Instruction::XOR, {X, 0}}, [=]{ return X; }},
- {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }},
- {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }},
- {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }},
- {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }},
- {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }},
- {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }},
- {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }},
- {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }},
- {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } },
- // operations involving an expression and itself
- {{Instruction::AND, {X, X}}, [=]{ return X; }},
- {{Instruction::OR, {X, X}}, [=]{ return X; }},
- {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }},
- {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }},
- {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }},
- {{Instruction::LT, {X, X}}, [=]{ return u256(0); }},
- {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }},
- {{Instruction::GT, {X, X}}, [=]{ return u256(0); }},
- {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }},
- {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }},
-
- {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }},
- {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }},
- {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }},
- {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }},
- {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }},
- {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }},
- };
- // Double negation of opcodes with binary result
- for (auto const& op: vector<Instruction>{
- Instruction::EQ,
- Instruction::LT,
- Instruction::SLT,
- Instruction::GT,
- Instruction::SGT
- })
- m_rules.push_back({
- {Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}},
- [=]() -> Pattern { return {op, {X, Y}}; }
- });
- m_rules.push_back({
- {Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}},
- [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }
- });
- m_rules.push_back({
- {Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}},
- [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; }
- });
- // Associative operations
- for (auto const& opFun: vector<pair<Instruction,function<u256(u256 const&,u256 const&)>>>{
- {Instruction::ADD, plus<u256>()},
- {Instruction::MUL, multiplies<u256>()},
- {Instruction::AND, bit_and<u256>()},
- {Instruction::OR, bit_or<u256>()},
- {Instruction::XOR, bit_xor<u256>()}
- })
- {
- auto op = opFun.first;
- auto fun = opFun.second;
- // Moving constants to the outside, order matters here!
- // we need actions that return expressions (or patterns?) here, and we need also reversed rules
- // (X+A)+B -> X+(A+B)
- m_rules += vector<pair<Pattern, function<Pattern()>>>{{
- {op, {{op, {X, A}}, B}},
- [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
- }, {
- // X+(Y+A) -> (X+Y)+A
- {op, {{op, {X, A}}, Y}},
- [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
- }, {
- // For now, we still need explicit commutativity for the inner pattern
- {op, {{op, {A, X}}, B}},
- [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
- }, {
- {op, {{op, {A, X}}, Y}},
- [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
- }};
- }
- // move constants across subtractions
- m_rules += vector<pair<Pattern, function<Pattern()>>>{
- {
- // X - A -> X + (-A)
- {Instruction::SUB, {X, A}},
- [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; }
- }, {
- // (X + A) - Y -> (X - Y) + A
- {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}},
- [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
- }, {
- // (A + X) - Y -> (X - Y) + A
- {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}},
- [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
- }, {
- // X - (Y + A) -> (X - Y) + (-A)
- {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}},
- [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
- }, {
- // X - (A + Y) -> (X - Y) + (-A)
- {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}},
- [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
- }
- };
-}
-
ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun)
{
static Rules rules;
@@ -366,21 +192,17 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr,
)
return -1;
- for (auto const& rule: rules.rules())
+ if (auto match = rules.findFirstMatch(_expr, *this))
{
- rules.resetMatchGroups();
- if (rule.first.matches(_expr, *this))
- {
- // Debug info
- //cout << "Simplifying " << *_expr.item << "(";
- //for (Id arg: _expr.arguments)
- // cout << fullDAGToString(arg) << ", ";
- //cout << ")" << endl;
- //cout << "with rule " << rule.first.toString() << endl;
- //ExpressionTemplate t(rule.second());
- //cout << "to " << rule.second().toString() << endl;
- return rebuildExpression(ExpressionTemplate(rule.second(), _expr.item->location()));
- }
+ // Debug info
+ //cout << "Simplifying " << *_expr.item << "(";
+ //for (Id arg: _expr.arguments)
+ // cout << fullDAGToString(arg) << ", ";
+ //cout << ")" << endl;
+ //cout << "with rule " << match->first.toString() << endl;
+ //ExpressionTemplate t(match->second());
+ //cout << "to " << match->second().toString() << endl;
+ return rebuildExpression(ExpressionTemplate(match->second(), _expr.item->location()));
}
if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item))
@@ -403,122 +225,3 @@ ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate co
arguments.push_back(rebuildExpression(t));
return find(_template.item, arguments);
}
-
-
-Pattern::Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments):
- m_type(Operation),
- m_requireDataMatch(true),
- m_data(_instruction),
- m_arguments(_arguments)
-{
-}
-
-void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _matchGroups)
-{
- m_matchGroup = _group;
- m_matchGroups = &_matchGroups;
-}
-
-bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const
-{
- if (!matchesBaseItem(_expr.item))
- return false;
- if (m_matchGroup)
- {
- if (!m_matchGroups->count(m_matchGroup))
- (*m_matchGroups)[m_matchGroup] = &_expr;
- else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id)
- return false;
- }
- assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, "");
- for (size_t i = 0; i < m_arguments.size(); ++i)
- if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes))
- return false;
- return true;
-}
-
-AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const
-{
- return AssemblyItem(m_type, m_data, _location);
-}
-
-string Pattern::toString() const
-{
- stringstream s;
- switch (m_type)
- {
- case Operation:
- s << instructionInfo(Instruction(unsigned(m_data))).name;
- break;
- case Push:
- s << "PUSH " << hex << m_data;
- break;
- case UndefinedItem:
- s << "ANY";
- break;
- default:
- s << "t=" << dec << m_type << " d=" << hex << m_data;
- break;
- }
- if (!m_requireDataMatch)
- s << " ~";
- if (m_matchGroup)
- s << "[" << dec << m_matchGroup << "]";
- s << "(";
- for (Pattern const& p: m_arguments)
- s << p.toString() << ", ";
- s << ")";
- return s.str();
-}
-
-bool Pattern::matchesBaseItem(AssemblyItem const* _item) const
-{
- if (m_type == UndefinedItem)
- return true;
- if (!_item)
- return false;
- if (m_type != _item->type())
- return false;
- if (m_requireDataMatch && m_data != _item->data())
- return false;
- return true;
-}
-
-Pattern::Expression const& Pattern::matchGroupValue() const
-{
- assertThrow(m_matchGroup > 0, OptimizerException, "");
- assertThrow(!!m_matchGroups, OptimizerException, "");
- assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, "");
- return *(*m_matchGroups)[m_matchGroup];
-}
-
-
-ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location)
-{
- if (_pattern.matchGroup())
- {
- hasId = true;
- id = _pattern.id();
- }
- else
- {
- hasId = false;
- item = _pattern.toAssemblyItem(_location);
- }
- for (auto const& arg: _pattern.arguments())
- arguments.push_back(ExpressionTemplate(arg, _location));
-}
-
-string ExpressionTemplate::toString() const
-{
- stringstream s;
- if (hasId)
- s << id;
- else
- s << item;
- s << "(";
- for (auto const& arg: arguments)
- s << arg.toString();
- s << ")";
- return s.str();
-}
diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h
index 11a698dd..5d53b292 100644
--- a/libevmasm/ExpressionClasses.h
+++ b/libevmasm/ExpressionClasses.h
@@ -121,70 +121,5 @@ private:
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItems;
};
-/**
- * Pattern to match against an expression.
- * Also stores matched expressions to retrieve them later, for constructing new expressions using
- * ExpressionTemplate.
- */
-class Pattern
-{
-public:
- using Expression = ExpressionClasses::Expression;
- using Id = ExpressionClasses::Id;
-
- // Matches a specific constant value.
- Pattern(unsigned _value): Pattern(u256(_value)) {}
- // Matches a specific constant value.
- Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(_value) {}
- // Matches a specific assembly item type or anything if not given.
- Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {}
- // Matches a given instruction with given arguments
- Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments = {});
- /// Sets this pattern to be part of the match group with the identifier @a _group.
- /// Inside one rule, all patterns in the same match group have to match expressions from the
- /// same expression equivalence class.
- void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups);
- unsigned matchGroup() const { return m_matchGroup; }
- bool matches(Expression const& _expr, ExpressionClasses const& _classes) const;
-
- AssemblyItem toAssemblyItem(SourceLocation const& _location) const;
- std::vector<Pattern> arguments() const { return m_arguments; }
-
- /// @returns the id of the matched expression if this pattern is part of a match group.
- Id id() const { return matchGroupValue().id; }
- /// @returns the data of the matched expression if this pattern is part of a match group.
- u256 const& d() const { return matchGroupValue().item->data(); }
-
- std::string toString() const;
-
-private:
- bool matchesBaseItem(AssemblyItem const* _item) const;
- Expression const& matchGroupValue() const;
-
- AssemblyItemType m_type;
- bool m_requireDataMatch = false;
- u256 m_data = 0;
- std::vector<Pattern> m_arguments;
- unsigned m_matchGroup = 0;
- std::map<unsigned, Expression const*>* m_matchGroups = nullptr;
-};
-
-/**
- * Template for a new expression that can be built from matched patterns.
- */
-struct ExpressionTemplate
-{
- using Expression = ExpressionClasses::Expression;
- using Id = ExpressionClasses::Id;
- explicit ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location);
- std::string toString() const;
- bool hasId = false;
- /// Id of the matched expression, if available.
- Id id = Id(-1);
- // Otherwise, assembly item.
- AssemblyItem item = UndefinedItem;
- std::vector<ExpressionTemplate> arguments;
-};
-
}
}
diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp
index 21db3565..c96c6ca5 100644
--- a/libevmasm/GasMeter.cpp
+++ b/libevmasm/GasMeter.cpp
@@ -80,6 +80,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
gas += GasCosts::sloadGas;
break;
case Instruction::RETURN:
+ case Instruction::REVERT:
gas += memoryGas(0, -1);
break;
case Instruction::MLOAD:
@@ -95,13 +96,14 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
classes.find(AssemblyItem(1))
}));
break;
- case Instruction::SHA3:
- gas = GasCosts::sha3Gas;
- gas += wordGas(GasCosts::sha3WordGas, m_state->relativeStackElement(-1));
+ case Instruction::KECCAK256:
+ gas = GasCosts::keccak256Gas;
+ gas += wordGas(GasCosts::keccak256WordGas, m_state->relativeStackElement(-1));
gas += memoryGas(0, -1);
break;
case Instruction::CALLDATACOPY:
case Instruction::CODECOPY:
+ case Instruction::RETURNDATACOPY:
gas += memoryGas(0, -2);
gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2));
break;
@@ -127,6 +129,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
+ case Instruction::STATICCALL:
{
if (_includeExternalCosts)
// We assume that we do not know the target contract and thus, the consumption is infinite.
@@ -140,15 +143,22 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
gas = GasConsumption::infinite();
if (_item.instruction() == Instruction::CALL)
gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists.
- int valueSize = _item.instruction() == Instruction::DELEGATECALL ? 0 : 1;
- if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize)))
+ int valueSize = 1;
+ if (_item.instruction() == Instruction::DELEGATECALL || _item.instruction() == Instruction::STATICCALL)
+ valueSize = 0;
+ else if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize)))
gas += GasCosts::callValueTransferGas;
gas += memoryGas(-2 - valueSize, -3 - valueSize);
gas += memoryGas(-4 - valueSize, -5 - valueSize);
}
break;
}
+ case Instruction::SELFDESTRUCT:
+ gas = GasCosts::selfdestructGas;
+ gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists.
+ break;
case Instruction::CREATE:
+ case Instruction::CREATE2:
if (_includeExternalCosts)
// We assume that we do not know the target contract and thus, the consumption is infinite.
gas = GasConsumption::infinite();
@@ -223,14 +233,16 @@ unsigned GasMeter::runGas(Instruction _instruction)
switch (instructionInfo(_instruction).gasPriceTier)
{
- case 0: return GasCosts::tier0Gas;
- case 1: return GasCosts::tier1Gas;
- case 2: return GasCosts::tier2Gas;
- case 3: return GasCosts::tier3Gas;
- case 4: return GasCosts::tier4Gas;
- case 5: return GasCosts::tier5Gas;
- case 6: return GasCosts::tier6Gas;
- case 7: return GasCosts::tier7Gas;
+ case Tier::Zero: return GasCosts::tier0Gas;
+ case Tier::Base: return GasCosts::tier1Gas;
+ case Tier::VeryLow: return GasCosts::tier2Gas;
+ case Tier::Low: return GasCosts::tier3Gas;
+ case Tier::Mid: return GasCosts::tier4Gas;
+ case Tier::High: return GasCosts::tier5Gas;
+ case Tier::Ext: return GasCosts::tier6Gas;
+ case Tier::Special: return GasCosts::tier7Gas;
+ case Tier::ExtCode: return GasCosts::extCodeGas;
+ case Tier::Balance: return GasCosts::balanceGas;
default: break;
}
assertThrow(false, OptimizerException, "Invalid gas tier.");
diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h
index 0bc10f1f..2c3ecf5a 100644
--- a/libevmasm/GasMeter.h
+++ b/libevmasm/GasMeter.h
@@ -44,11 +44,13 @@ namespace GasCosts
static unsigned const tier5Gas = 10;
static unsigned const tier6Gas = 20;
static unsigned const tier7Gas = 0;
+ static unsigned const extCodeGas = 700;
+ static unsigned const balanceGas = 400;
static unsigned const expGas = 10;
- static unsigned const expByteGas = 10;
- static unsigned const sha3Gas = 30;
- static unsigned const sha3WordGas = 6;
- static unsigned const sloadGas = 50;
+ static unsigned const expByteGas = 50;
+ static unsigned const keccak256Gas = 30;
+ static unsigned const keccak256WordGas = 6;
+ static unsigned const sloadGas = 200;
static unsigned const sstoreSetGas = 20000;
static unsigned const sstoreResetGas = 5000;
static unsigned const sstoreRefundGas = 15000;
@@ -57,11 +59,12 @@ namespace GasCosts
static unsigned const logDataGas = 8;
static unsigned const logTopicGas = 375;
static unsigned const createGas = 32000;
- static unsigned const callGas = 40;
+ static unsigned const callGas = 700;
static unsigned const callStipend = 2300;
static unsigned const callValueTransferGas = 9000;
static unsigned const callNewAccountGas = 25000;
- static unsigned const suicideRefundGas = 24000;
+ static unsigned const selfdestructGas = 5000;
+ static unsigned const selfdestructRefundGas = 24000;
static unsigned const memoryGas = 3;
static unsigned const quadCoeffDiv = 512;
static unsigned const createDataGas = 200;
diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp
index 5244a91f..b38981d2 100644
--- a/libevmasm/Instruction.cpp
+++ b/libevmasm/Instruction.cpp
@@ -53,7 +53,7 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "ADDMOD", Instruction::ADDMOD },
{ "MULMOD", Instruction::MULMOD },
{ "SIGNEXTEND", Instruction::SIGNEXTEND },
- { "SHA3", Instruction::SHA3 },
+ { "KECCAK256", Instruction::KECCAK256 },
{ "ADDRESS", Instruction::ADDRESS },
{ "BALANCE", Instruction::BALANCE },
{ "ORIGIN", Instruction::ORIGIN },
@@ -67,6 +67,8 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "GASPRICE", Instruction::GASPRICE },
{ "EXTCODESIZE", Instruction::EXTCODESIZE },
{ "EXTCODECOPY", Instruction::EXTCODECOPY },
+ { "RETURNDATASIZE", Instruction::RETURNDATASIZE },
+ { "RETURNDATACOPY", Instruction::RETURNDATACOPY },
{ "BLOCKHASH", Instruction::BLOCKHASH },
{ "COINBASE", Instruction::COINBASE },
{ "TIMESTAMP", Instruction::TIMESTAMP },
@@ -157,143 +159,153 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "CREATE", Instruction::CREATE },
{ "CALL", Instruction::CALL },
{ "CALLCODE", Instruction::CALLCODE },
+ { "STATICCALL", Instruction::STATICCALL },
{ "RETURN", Instruction::RETURN },
{ "DELEGATECALL", Instruction::DELEGATECALL },
- { "SUICIDE", Instruction::SUICIDE }
+ { "CREATE2", Instruction::CREATE2 },
+ { "REVERT", Instruction::REVERT },
+ { "INVALID", Instruction::INVALID },
+ { "SELFDESTRUCT", Instruction::SELFDESTRUCT }
};
static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ // Add, Args, Ret, SideEffects, GasPriceTier
- { Instruction::STOP, { "STOP", 0, 0, 0, true, ZeroTier } },
- { Instruction::ADD, { "ADD", 0, 2, 1, false, VeryLowTier } },
- { Instruction::SUB, { "SUB", 0, 2, 1, false, VeryLowTier } },
- { Instruction::MUL, { "MUL", 0, 2, 1, false, LowTier } },
- { Instruction::DIV, { "DIV", 0, 2, 1, false, LowTier } },
- { Instruction::SDIV, { "SDIV", 0, 2, 1, false, LowTier } },
- { Instruction::MOD, { "MOD", 0, 2, 1, false, LowTier } },
- { Instruction::SMOD, { "SMOD", 0, 2, 1, false, LowTier } },
- { Instruction::EXP, { "EXP", 0, 2, 1, false, SpecialTier } },
- { Instruction::NOT, { "NOT", 0, 1, 1, false, VeryLowTier } },
- { Instruction::LT, { "LT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::GT, { "GT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::SLT, { "SLT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::SGT, { "SGT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::EQ, { "EQ", 0, 2, 1, false, VeryLowTier } },
- { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, VeryLowTier } },
- { Instruction::AND, { "AND", 0, 2, 1, false, VeryLowTier } },
- { Instruction::OR, { "OR", 0, 2, 1, false, VeryLowTier } },
- { Instruction::XOR, { "XOR", 0, 2, 1, false, VeryLowTier } },
- { Instruction::BYTE, { "BYTE", 0, 2, 1, false, VeryLowTier } },
- { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, MidTier } },
- { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, MidTier } },
- { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, LowTier } },
- { Instruction::SHA3, { "SHA3", 0, 2, 1, false, SpecialTier } },
- { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, BaseTier } },
- { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, ExtTier } },
- { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLER, { "CALLER", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, VeryLowTier } },
- { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, VeryLowTier } },
- { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, BaseTier } },
- { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, VeryLowTier } },
- { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, BaseTier } },
- { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, ExtTier } },
- { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, ExtTier } },
- { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, ExtTier } },
- { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, BaseTier } },
- { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, BaseTier } },
- { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, BaseTier } },
- { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, BaseTier } },
- { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, BaseTier } },
- { Instruction::POP, { "POP", 0, 1, 0, false, BaseTier } },
- { Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, VeryLowTier } },
- { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, VeryLowTier } },
- { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, VeryLowTier } },
- { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, SpecialTier } },
- { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, SpecialTier } },
- { Instruction::JUMP, { "JUMP", 0, 1, 0, true, MidTier } },
- { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, HighTier } },
- { Instruction::PC, { "PC", 0, 0, 1, false, BaseTier } },
- { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, BaseTier } },
- { Instruction::GAS, { "GAS", 0, 0, 1, false, BaseTier } },
- { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, SpecialTier } },
- { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, VeryLowTier } },
- { Instruction::DUP1, { "DUP1", 0, 1, 2, false, VeryLowTier } },
- { Instruction::DUP2, { "DUP2", 0, 2, 3, false, VeryLowTier } },
- { Instruction::DUP3, { "DUP3", 0, 3, 4, false, VeryLowTier } },
- { Instruction::DUP4, { "DUP4", 0, 4, 5, false, VeryLowTier } },
- { Instruction::DUP5, { "DUP5", 0, 5, 6, false, VeryLowTier } },
- { Instruction::DUP6, { "DUP6", 0, 6, 7, false, VeryLowTier } },
- { Instruction::DUP7, { "DUP7", 0, 7, 8, false, VeryLowTier } },
- { Instruction::DUP8, { "DUP8", 0, 8, 9, false, VeryLowTier } },
- { Instruction::DUP9, { "DUP9", 0, 9, 10, false, VeryLowTier } },
- { Instruction::DUP10, { "DUP10", 0, 10, 11, false, VeryLowTier } },
- { Instruction::DUP11, { "DUP11", 0, 11, 12, false, VeryLowTier } },
- { Instruction::DUP12, { "DUP12", 0, 12, 13, false, VeryLowTier } },
- { Instruction::DUP13, { "DUP13", 0, 13, 14, false, VeryLowTier } },
- { Instruction::DUP14, { "DUP14", 0, 14, 15, false, VeryLowTier } },
- { Instruction::DUP15, { "DUP15", 0, 15, 16, false, VeryLowTier } },
- { Instruction::DUP16, { "DUP16", 0, 16, 17, false, VeryLowTier } },
- { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, VeryLowTier } },
- { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, VeryLowTier } },
- { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, VeryLowTier } },
- { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, VeryLowTier } },
- { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, VeryLowTier } },
- { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, VeryLowTier } },
- { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, VeryLowTier } },
- { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, VeryLowTier } },
- { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, VeryLowTier } },
- { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, VeryLowTier } },
- { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, VeryLowTier } },
- { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, VeryLowTier } },
- { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, VeryLowTier } },
- { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, VeryLowTier } },
- { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, VeryLowTier } },
- { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, VeryLowTier } },
- { Instruction::LOG0, { "LOG0", 0, 2, 0, true, SpecialTier } },
- { Instruction::LOG1, { "LOG1", 0, 3, 0, true, SpecialTier } },
- { Instruction::LOG2, { "LOG2", 0, 4, 0, true, SpecialTier } },
- { Instruction::LOG3, { "LOG3", 0, 5, 0, true, SpecialTier } },
- { Instruction::LOG4, { "LOG4", 0, 6, 0, true, SpecialTier } },
- { Instruction::CREATE, { "CREATE", 0, 3, 1, true, SpecialTier } },
- { Instruction::CALL, { "CALL", 0, 7, 1, true, SpecialTier } },
- { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, SpecialTier } },
- { Instruction::RETURN, { "RETURN", 0, 2, 0, true, ZeroTier } },
- { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, SpecialTier } },
- { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, ZeroTier } }
+ { Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::Zero } },
+ { Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::SUB, { "SUB", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::MUL, { "MUL", 0, 2, 1, false, Tier::Low } },
+ { Instruction::DIV, { "DIV", 0, 2, 1, false, Tier::Low } },
+ { Instruction::SDIV, { "SDIV", 0, 2, 1, false, Tier::Low } },
+ { Instruction::MOD, { "MOD", 0, 2, 1, false, Tier::Low } },
+ { Instruction::SMOD, { "SMOD", 0, 2, 1, false, Tier::Low } },
+ { Instruction::EXP, { "EXP", 0, 2, 1, false, Tier::Special } },
+ { Instruction::NOT, { "NOT", 0, 1, 1, false, Tier::VeryLow } },
+ { Instruction::LT, { "LT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::GT, { "GT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::SLT, { "SLT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::SGT, { "SGT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::EQ, { "EQ", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, Tier::VeryLow } },
+ { Instruction::AND, { "AND", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::OR, { "OR", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::XOR, { "XOR", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::BYTE, { "BYTE", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::Mid } },
+ { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } },
+ { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } },
+ { Instruction::KECCAK256, { "KECCAK256", 0, 2, 1, false, Tier::Special } },
+ { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } },
+ { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } },
+ { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, Tier::VeryLow } },
+ { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, Tier::VeryLow } },
+ { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } },
+ { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::ExtCode } },
+ { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtCode } },
+ { Instruction::RETURNDATASIZE, {"RETURNDATASIZE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::RETURNDATACOPY, {"RETURNDATACOPY", 0, 3, 0, true, Tier::VeryLow } },
+ { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } },
+ { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } },
+ { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } },
+ { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } },
+ { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } },
+ { Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } },
+ { Instruction::MLOAD, { "MLOAD", 0, 1, 1, true, Tier::VeryLow } },
+ { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } },
+ { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, Tier::VeryLow } },
+ { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, Tier::Special } },
+ { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, Tier::Special } },
+ { Instruction::JUMP, { "JUMP", 0, 1, 0, true, Tier::Mid } },
+ { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, Tier::High } },
+ { Instruction::PC, { "PC", 0, 0, 1, false, Tier::Base } },
+ { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::GAS, { "GAS", 0, 0, 1, false, Tier::Base } },
+ { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, Tier::Special } },
+ { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, Tier::VeryLow } },
+ { Instruction::DUP1, { "DUP1", 0, 1, 2, false, Tier::VeryLow } },
+ { Instruction::DUP2, { "DUP2", 0, 2, 3, false, Tier::VeryLow } },
+ { Instruction::DUP3, { "DUP3", 0, 3, 4, false, Tier::VeryLow } },
+ { Instruction::DUP4, { "DUP4", 0, 4, 5, false, Tier::VeryLow } },
+ { Instruction::DUP5, { "DUP5", 0, 5, 6, false, Tier::VeryLow } },
+ { Instruction::DUP6, { "DUP6", 0, 6, 7, false, Tier::VeryLow } },
+ { Instruction::DUP7, { "DUP7", 0, 7, 8, false, Tier::VeryLow } },
+ { Instruction::DUP8, { "DUP8", 0, 8, 9, false, Tier::VeryLow } },
+ { Instruction::DUP9, { "DUP9", 0, 9, 10, false, Tier::VeryLow } },
+ { Instruction::DUP10, { "DUP10", 0, 10, 11, false, Tier::VeryLow } },
+ { Instruction::DUP11, { "DUP11", 0, 11, 12, false, Tier::VeryLow } },
+ { Instruction::DUP12, { "DUP12", 0, 12, 13, false, Tier::VeryLow } },
+ { Instruction::DUP13, { "DUP13", 0, 13, 14, false, Tier::VeryLow } },
+ { Instruction::DUP14, { "DUP14", 0, 14, 15, false, Tier::VeryLow } },
+ { Instruction::DUP15, { "DUP15", 0, 15, 16, false, Tier::VeryLow } },
+ { Instruction::DUP16, { "DUP16", 0, 16, 17, false, Tier::VeryLow } },
+ { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, Tier::VeryLow } },
+ { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, Tier::VeryLow } },
+ { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, Tier::VeryLow } },
+ { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, Tier::VeryLow } },
+ { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, Tier::VeryLow } },
+ { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, Tier::VeryLow } },
+ { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, Tier::VeryLow } },
+ { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, Tier::VeryLow } },
+ { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, Tier::VeryLow } },
+ { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, Tier::VeryLow } },
+ { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, Tier::VeryLow } },
+ { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, Tier::VeryLow } },
+ { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, Tier::VeryLow } },
+ { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, Tier::VeryLow } },
+ { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, Tier::VeryLow } },
+ { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, Tier::VeryLow } },
+ { Instruction::LOG0, { "LOG0", 0, 2, 0, true, Tier::Special } },
+ { Instruction::LOG1, { "LOG1", 0, 3, 0, true, Tier::Special } },
+ { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } },
+ { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } },
+ { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } },
+ { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } },
+ { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } },
+ { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } },
+ { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } },
+ { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } },
+ { Instruction::STATICCALL, { "STATICCALL", 0, 6, 1, true, Tier::Special } },
+ { Instruction::CREATE2, { "CREATE2", 0, 4, 1, true, Tier::Special } },
+ { Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } },
+ { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } },
+ { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Special } }
};
void dev::solidity::eachInstruction(
@@ -343,7 +355,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst)
}
catch (...)
{
- return InstructionInfo({"<INVALID_INSTRUCTION: " + toString((unsigned)_inst) + ">", 0, 0, 0, false, InvalidTier});
+ return InstructionInfo({"<INVALID_INSTRUCTION: " + toString((unsigned)_inst) + ">", 0, 0, 0, false, Tier::Invalid});
}
}
diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h
index c7fad1c5..89a25fb7 100644
--- a/libevmasm/Instruction.h
+++ b/libevmasm/Instruction.h
@@ -62,7 +62,7 @@ enum class Instruction: uint8_t
NOT, ///< bitwise NOT opertation
BYTE, ///< retrieve single byte from word
- SHA3 = 0x20, ///< compute SHA3-256 hash
+ KECCAK256 = 0x20, ///< compute KECCAK-256 hash
ADDRESS = 0x30, ///< get address of currently executing account
BALANCE, ///< get balance of the given account
@@ -77,6 +77,8 @@ enum class Instruction: uint8_t
GASPRICE, ///< get price of gas in current environment
EXTCODESIZE, ///< get external code size (from another contract)
EXTCODECOPY, ///< copy external code (from another contract)
+ RETURNDATASIZE = 0x3d, ///< get size of return data buffer
+ RETURNDATACOPY = 0x3e, ///< copy return data in current environment to memory
BLOCKHASH = 0x40, ///< get hash of most recent complete block
COINBASE, ///< get the block's coinbase address
@@ -85,6 +87,13 @@ enum class Instruction: uint8_t
DIFFICULTY, ///< get the block's difficulty
GASLIMIT, ///< get the block's gas limit
+ JUMPTO = 0x4a, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
+ JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
+ JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
+ JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
+ JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
+ RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
+
POP = 0x50, ///< remove item from stack
MLOAD, ///< load word from memory
MSTORE, ///< save word to memory
@@ -97,6 +106,8 @@ enum class Instruction: uint8_t
MSIZE, ///< get the size of active memory
GAS, ///< get the amount of available gas
JUMPDEST, ///< set a potential jump destination
+ BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
+ BEGINDATA, ///< begine the data section -- not part of Instructions.cpp
PUSH1 = 0x60, ///< place 1 byte item on stack
PUSH2, ///< place 2 byte item on stack
@@ -176,7 +187,12 @@ enum class Instruction: uint8_t
CALLCODE, ///< message-call with another account's code only
RETURN, ///< halt execution returning output data
DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender
- SUICIDE = 0xff ///< halt execution and register account for later deletion
+ STATICCALL = 0xfa, ///< like CALL but disallow state modifications
+ CREATE2 = 0xfb, ///< create new account with associated code at address `sha3(sender + salt + sha3(init code)) % 2**160`
+
+ REVERT = 0xfd, ///< halt execution, revert state and return output data
+ INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
+ SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion
};
/// @returns the number of PUSH Instruction _inst
@@ -200,42 +216,44 @@ inline unsigned getSwapNumber(Instruction _inst)
/// @returns the PUSH<_number> instruction
inline Instruction pushInstruction(unsigned _number)
{
- assertThrow(1 <= _number && _number <= 32, InvalidOpcode, "Invalid PUSH instruction requested.");
+ assertThrow(1 <= _number && _number <= 32, InvalidOpcode, std::string("Invalid PUSH instruction requested (") + std::to_string(_number) + ").");
return Instruction(unsigned(Instruction::PUSH1) + _number - 1);
}
/// @returns the DUP<_number> instruction
inline Instruction dupInstruction(unsigned _number)
{
- assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid DUP instruction requested.");
+ assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid DUP instruction requested (") + std::to_string(_number) + ").");
return Instruction(unsigned(Instruction::DUP1) + _number - 1);
}
/// @returns the SWAP<_number> instruction
inline Instruction swapInstruction(unsigned _number)
{
- assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid SWAP instruction requested.");
+ assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid SWAP instruction requested (") + std::to_string(_number) + ").");
return Instruction(unsigned(Instruction::SWAP1) + _number - 1);
}
/// @returns the LOG<_number> instruction
inline Instruction logInstruction(unsigned _number)
{
- assertThrow(_number <= 4, InvalidOpcode, "Invalid LOG instruction requested.");
+ assertThrow(_number <= 4, InvalidOpcode, std::string("Invalid LOG instruction requested (") + std::to_string(_number) + ").");
return Instruction(unsigned(Instruction::LOG0) + _number);
}
-enum Tier
+enum class Tier : unsigned
{
- ZeroTier = 0, // 0, Zero
- BaseTier, // 2, Quick
- VeryLowTier, // 3, Fastest
- LowTier, // 5, Fast
- MidTier, // 8, Mid
- HighTier, // 10, Slow
- ExtTier, // 20, Ext
- SpecialTier, // multiparam or otherwise special
- InvalidTier // Invalid.
+ Zero = 0, // 0, Zero
+ Base, // 2, Quick
+ VeryLow, // 3, Fastest
+ Low, // 5, Fast
+ Mid, // 8, Mid
+ High, // 10, Slow
+ Ext, // 20, Ext
+ ExtCode, // 700, Extcode
+ Balance, // 400, Balance
+ Special, // multiparam or otherwise special
+ Invalid // Invalid.
};
/// Information structure for a particular instruction.
@@ -246,7 +264,7 @@ struct InstructionInfo
int args; ///< Number of items required on the stack for this instruction (and, for the purposes of ret, the number taken from the stack).
int ret; ///< Number of items placed (back) on the stack by this instruction, assuming args items were removed.
bool sideEffects; ///< false if the only effect on the execution environment (apart from gas usage) is a change to a topmost segment of the stack
- int gasPriceTier; ///< Tier for gas pricing.
+ Tier gasPriceTier; ///< Tier for gas pricing.
};
/// Information on all the instructions.
diff --git a/libevmasm/KnownState.cpp b/libevmasm/KnownState.cpp
index 6e3130dd..e2f10f22 100644
--- a/libevmasm/KnownState.cpp
+++ b/libevmasm/KnownState.cpp
@@ -136,10 +136,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
m_stackHeight + _item.deposit(),
loadFromMemory(arguments[0], _item.location())
);
- else if (_item.instruction() == Instruction::SHA3)
+ else if (_item.instruction() == Instruction::KECCAK256)
setStackElement(
m_stackHeight + _item.deposit(),
- applySha3(arguments.at(0), arguments.at(1), _item.location())
+ applyKeccak256(arguments.at(0), arguments.at(1), _item.location())
);
else
{
@@ -346,18 +346,18 @@ ExpressionClasses::Id KnownState::loadFromMemory(Id _slot, SourceLocation const&
return m_memoryContent[_slot] = m_expressionClasses->find(item, {_slot}, true, m_sequenceNumber);
}
-KnownState::Id KnownState::applySha3(
+KnownState::Id KnownState::applyKeccak256(
Id _start,
Id _length,
SourceLocation const& _location
)
{
- AssemblyItem sha3Item(Instruction::SHA3, _location);
+ AssemblyItem keccak256Item(Instruction::KECCAK256, _location);
// Special logic if length is a short constant, otherwise we cannot tell.
u256 const* l = m_expressionClasses->knownConstant(_length);
// unknown or too large length
if (!l || *l > 128)
- return m_expressionClasses->find(sha3Item, {_start, _length}, true, m_sequenceNumber);
+ return m_expressionClasses->find(keccak256Item, {_start, _length}, true, m_sequenceNumber);
vector<Id> arguments;
for (u256 i = 0; i < *l; i += 32)
@@ -368,10 +368,10 @@ KnownState::Id KnownState::applySha3(
);
arguments.push_back(loadFromMemory(slot, _location));
}
- if (m_knownSha3Hashes.count(arguments))
- return m_knownSha3Hashes.at(arguments);
+ if (m_knownKeccak256Hashes.count(arguments))
+ return m_knownKeccak256Hashes.at(arguments);
Id v;
- // If all arguments are known constants, compute the sha3 here
+ // If all arguments are known constants, compute the Keccak-256 here
if (all_of(arguments.begin(), arguments.end(), [this](Id _a) { return !!m_expressionClasses->knownConstant(_a); }))
{
bytes data;
@@ -381,8 +381,8 @@ KnownState::Id KnownState::applySha3(
v = m_expressionClasses->find(AssemblyItem(u256(dev::keccak256(data)), _location));
}
else
- v = m_expressionClasses->find(sha3Item, {_start, _length}, true, m_sequenceNumber);
- return m_knownSha3Hashes[arguments] = v;
+ v = m_expressionClasses->find(keccak256Item, {_start, _length}, true, m_sequenceNumber);
+ return m_knownKeccak256Hashes[arguments] = v;
}
set<u256> KnownState::tagsInExpression(KnownState::Id _expressionId)
diff --git a/libevmasm/KnownState.h b/libevmasm/KnownState.h
index fd6a26c1..8568b163 100644
--- a/libevmasm/KnownState.h
+++ b/libevmasm/KnownState.h
@@ -150,8 +150,8 @@ private:
StoreOperation storeInMemory(Id _slot, Id _value, SourceLocation const& _location);
/// Retrieves the current value at the given slot in memory or creates a new special mload class.
Id loadFromMemory(Id _slot, SourceLocation const& _location);
- /// Finds or creates a new expression that applies the sha3 hash function to the contents in memory.
- Id applySha3(Id _start, Id _length, SourceLocation const& _location);
+ /// Finds or creates a new expression that applies the Keccak-256 hash function to the contents in memory.
+ Id applyKeccak256(Id _start, Id _length, SourceLocation const& _location);
/// @returns a new or already used Id representing the given set of tags.
Id tagUnion(std::set<u256> _tags);
@@ -167,8 +167,8 @@ private:
/// Knowledge about memory content. Keys are memory addresses, note that the values overlap
/// and are not contained here if they are not completely known.
std::map<Id, Id> m_memoryContent;
- /// Keeps record of all sha3 hashes that are computed.
- std::map<std::vector<Id>, Id> m_knownSha3Hashes;
+ /// Keeps record of all Keccak-256 hashes that are computed.
+ std::map<std::vector<Id>, Id> m_knownKeccak256Hashes;
/// Structure containing the classes of equivalent expressions.
std::shared_ptr<ExpressionClasses> m_expressionClasses;
/// Container for unions of tags stored on the stack.
diff --git a/libevmasm/LinkerObject.cpp b/libevmasm/LinkerObject.cpp
index 93e4067c..06607089 100644
--- a/libevmasm/LinkerObject.cpp
+++ b/libevmasm/LinkerObject.cpp
@@ -37,13 +37,10 @@ void LinkerObject::link(map<string, h160> const& _libraryAddresses)
{
std::map<size_t, std::string> remainingRefs;
for (auto const& linkRef: linkReferences)
- {
- auto it = _libraryAddresses.find(linkRef.second);
- if (it == _libraryAddresses.end())
- remainingRefs.insert(linkRef);
+ if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses))
+ address->ref().copyTo(ref(bytecode).cropped(linkRef.first, 20));
else
- it->second.ref().copyTo(ref(bytecode).cropped(linkRef.first, 20));
- }
+ remainingRefs.insert(linkRef);
linkReferences.swap(remainingRefs);
}
@@ -60,3 +57,23 @@ string LinkerObject::toHex() const
}
return hex;
}
+
+h160 const*
+LinkerObject::matchLibrary(
+ string const& _linkRefName,
+ map<string, h160> const& _libraryAddresses
+)
+{
+ auto it = _libraryAddresses.find(_linkRefName);
+ if (it != _libraryAddresses.end())
+ return &it->second;
+ // If the user did not supply a fully qualified library name,
+ // try to match only the simple libary name
+ size_t colon = _linkRefName.find(':');
+ if (colon == string::npos)
+ return nullptr;
+ it = _libraryAddresses.find(_linkRefName.substr(colon + 1));
+ if (it != _libraryAddresses.end())
+ return &it->second;
+ return nullptr;
+}
diff --git a/libevmasm/LinkerObject.h b/libevmasm/LinkerObject.h
index d3ec3e97..152487b4 100644
--- a/libevmasm/LinkerObject.h
+++ b/libevmasm/LinkerObject.h
@@ -49,6 +49,12 @@ struct LinkerObject
/// @returns a hex representation of the bytecode of the given object, replacing unlinked
/// addresses by placeholders.
std::string toHex() const;
+
+private:
+ static h160 const* matchLibrary(
+ std::string const& _linkRefName,
+ std::map<std::string, h160> const& _libraryAddresses
+ );
};
}
diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp
index b96b0295..e94a8ba4 100644
--- a/libevmasm/PeepholeOptimiser.cpp
+++ b/libevmasm/PeepholeOptimiser.cpp
@@ -120,7 +120,7 @@ struct OpPop: SimplePeepholeOptimizerMethod<OpPop, 2>
if (instructionInfo(instr).ret == 1 && !instructionInfo(instr).sideEffects)
{
for (int j = 0; j < instructionInfo(instr).args; j++)
- *_out = Instruction::POP;
+ *_out = {Instruction::POP, _op.location()};
return true;
}
}
@@ -136,6 +136,21 @@ struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap, 2>
}
};
+struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2>
+{
+ static bool applySimple(AssemblyItem const& _push1, AssemblyItem const& _push2, std::back_insert_iterator<AssemblyItems> _out)
+ {
+ if (_push1.type() == Push && _push2.type() == Push && _push1.data() == _push2.data())
+ {
+ *_out = _push1;
+ *_out = {Instruction::DUP1, _push2.location()};
+ return true;
+ }
+ else
+ return false;
+ }
+};
+
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
{
static size_t applySimple(
@@ -199,7 +214,9 @@ struct UnreachableCode
it[0] != Instruction::JUMP &&
it[0] != Instruction::RETURN &&
it[0] != Instruction::STOP &&
- it[0] != Instruction::SUICIDE
+ it[0] != Instruction::INVALID &&
+ it[0] != Instruction::SELFDESTRUCT &&
+ it[0] != Instruction::REVERT
)
return false;
@@ -233,13 +250,15 @@ bool PeepholeOptimiser::optimise()
{
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
while (state.i < m_items.size())
- applyMethods(state, PushPop(), OpPop(), DoubleSwap(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity());
- if (m_optimisedItems.size() < m_items.size())
+ applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity());
+ if (m_optimisedItems.size() < m_items.size() || (
+ m_optimisedItems.size() == m_items.size() &&
+ eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3)
+ ))
{
m_items = std::move(m_optimisedItems);
return true;
}
else
return false;
-
}
diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp
index 23a00d95..f63f0c61 100644
--- a/libevmasm/SemanticInformation.cpp
+++ b/libevmasm/SemanticInformation.cpp
@@ -116,8 +116,10 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
case Instruction::JUMP:
case Instruction::JUMPI:
case Instruction::RETURN:
- case Instruction::SUICIDE:
+ case Instruction::SELFDESTRUCT:
case Instruction::STOP:
+ case Instruction::INVALID:
+ case Instruction::REVERT:
return true;
default:
return false;
@@ -135,12 +137,16 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
+ case Instruction::STATICCALL:
case Instruction::CREATE:
+ case Instruction::CREATE2:
case Instruction::GAS:
case Instruction::PC:
case Instruction::MSIZE: // depends on previous writes and reads, not only on content
case Instruction::BALANCE: // depends on previous calls
case Instruction::EXTCODESIZE:
+ case Instruction::RETURNDATACOPY: // depends on previous calls
+ case Instruction::RETURNDATASIZE:
return false;
default:
return true;
@@ -154,11 +160,13 @@ bool SemanticInformation::invalidatesMemory(Instruction _instruction)
case Instruction::CALLDATACOPY:
case Instruction::CODECOPY:
case Instruction::EXTCODECOPY:
+ case Instruction::RETURNDATACOPY:
case Instruction::MSTORE:
case Instruction::MSTORE8:
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
+ case Instruction::STATICCALL:
return true;
default:
return false;
@@ -173,6 +181,7 @@ bool SemanticInformation::invalidatesStorage(Instruction _instruction)
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::CREATE:
+ case Instruction::CREATE2:
case Instruction::SSTORE:
return true;
default:
diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp
new file mode 100644
index 00000000..e6c51f95
--- /dev/null
+++ b/libevmasm/SimplificationRules.cpp
@@ -0,0 +1,379 @@
+/*
+ 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/>.
+*/
+/**
+ * @file ExpressionClasses.cpp
+ * @author Christian <c@ethdev.com>
+ * @date 2015
+ * Container for equivalence classes of expressions for use in common subexpression elimination.
+ */
+
+#include <libevmasm/ExpressionClasses.h>
+#include <utility>
+#include <tuple>
+#include <functional>
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/noncopyable.hpp>
+#include <libevmasm/Assembly.h>
+#include <libevmasm/CommonSubexpressionEliminator.h>
+#include <libevmasm/SimplificationRules.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::eth;
+
+
+pair<Pattern, function<Pattern()> > const* Rules::findFirstMatch(
+ Expression const& _expr,
+ ExpressionClasses const& _classes
+)
+{
+ resetMatchGroups();
+
+ assertThrow(_expr.item, OptimizerException, "");
+ for (auto const& rule: m_rules[byte(_expr.item->instruction())])
+ {
+ if (rule.first.matches(_expr, _classes))
+ return &rule;
+ resetMatchGroups();
+ }
+ return nullptr;
+}
+
+void Rules::addRules(std::vector<std::pair<Pattern, std::function<Pattern ()> > > const& _rules)
+{
+ for (auto const& r: _rules)
+ addRule(r);
+}
+
+void Rules::addRule(std::pair<Pattern, std::function<Pattern()> > const& _rule)
+{
+ m_rules[byte(_rule.first.instruction())].push_back(_rule);
+}
+
+template <class S> S divWorkaround(S const& _a, S const& _b)
+{
+ return (S)(bigint(_a) / bigint(_b));
+}
+
+template <class S> S modWorkaround(S const& _a, S const& _b)
+{
+ return (S)(bigint(_a) % bigint(_b));
+}
+
+Rules::Rules()
+{
+ // Multiple occurences of one of these inside one rule must match the same equivalence class.
+ // Constants.
+ Pattern A(Push);
+ Pattern B(Push);
+ Pattern C(Push);
+ // Anything.
+ Pattern X;
+ Pattern Y;
+ Pattern Z;
+ A.setMatchGroup(1, m_matchGroups);
+ B.setMatchGroup(2, m_matchGroups);
+ C.setMatchGroup(3, m_matchGroups);
+ X.setMatchGroup(4, m_matchGroups);
+ Y.setMatchGroup(5, m_matchGroups);
+ Z.setMatchGroup(6, m_matchGroups);
+
+ addRules(vector<pair<Pattern, function<Pattern()>>>{
+ // arithmetics on constants
+ {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }},
+ {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }},
+ {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }},
+ {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }},
+ {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }},
+ {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }},
+ {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }},
+ {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }},
+ {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }},
+ {{Instruction::LT, {A, B}}, [=]() -> u256 { return A.d() < B.d() ? 1 : 0; }},
+ {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }},
+ {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }},
+ {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }},
+ {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }},
+ {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }},
+ {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }},
+ {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }},
+ {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }},
+ {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }},
+ {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }},
+ {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }},
+ {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }},
+ {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 {
+ if (A.d() >= 31)
+ return B.d();
+ unsigned testBit = unsigned(A.d()) * 8 + 7;
+ u256 mask = (u256(1) << testBit) - 1;
+ return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
+ }},
+
+ // invariants involving known constants (commutative instructions will be checked with swapped operants too)
+ {{Instruction::ADD, {X, 0}}, [=]{ return X; }},
+ {{Instruction::SUB, {X, 0}}, [=]{ return X; }},
+ {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }},
+ {{Instruction::MUL, {X, 1}}, [=]{ return X; }},
+ {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }},
+ {{Instruction::DIV, {0, X}}, [=]{ return u256(0); }},
+ {{Instruction::DIV, {X, 1}}, [=]{ return X; }},
+ {{Instruction::SDIV, {X, 0}}, [=]{ return u256(0); }},
+ {{Instruction::SDIV, {0, X}}, [=]{ return u256(0); }},
+ {{Instruction::SDIV, {X, 1}}, [=]{ return X; }},
+ {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }},
+ {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }},
+ {{Instruction::OR, {X, 0}}, [=]{ return X; }},
+ {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }},
+ {{Instruction::XOR, {X, 0}}, [=]{ return X; }},
+ {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }},
+ {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }},
+ {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } },
+
+ // operations involving an expression and itself
+ {{Instruction::AND, {X, X}}, [=]{ return X; }},
+ {{Instruction::OR, {X, X}}, [=]{ return X; }},
+ {{Instruction::XOR, {X, X}}, [=]{ return u256(0); }},
+ {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }},
+ {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }},
+ {{Instruction::LT, {X, X}}, [=]{ return u256(0); }},
+ {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }},
+ {{Instruction::GT, {X, X}}, [=]{ return u256(0); }},
+ {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }},
+ {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }},
+
+ // logical instruction combinations
+ {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }},
+ {{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }},
+ {{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }},
+ {{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }},
+ {{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }},
+ {{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }},
+ });
+
+ // Double negation of opcodes with binary result
+ for (auto const& op: vector<Instruction>{
+ Instruction::EQ,
+ Instruction::LT,
+ Instruction::SLT,
+ Instruction::GT,
+ Instruction::SGT
+ })
+ addRule({
+ {Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}},
+ [=]() -> Pattern { return {op, {X, Y}}; }
+ });
+
+ addRule({
+ {Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}},
+ [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }
+ });
+
+ addRule({
+ {Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}},
+ [=]() -> Pattern { return { Instruction::EQ, {X, Y} }; }
+ });
+
+ // Associative operations
+ for (auto const& opFun: vector<pair<Instruction,function<u256(u256 const&,u256 const&)>>>{
+ {Instruction::ADD, plus<u256>()},
+ {Instruction::MUL, multiplies<u256>()},
+ {Instruction::AND, bit_and<u256>()},
+ {Instruction::OR, bit_or<u256>()},
+ {Instruction::XOR, bit_xor<u256>()}
+ })
+ {
+ auto op = opFun.first;
+ auto fun = opFun.second;
+ // Moving constants to the outside, order matters here!
+ // we need actions that return expressions (or patterns?) here, and we need also reversed rules
+ // (X+A)+B -> X+(A+B)
+ addRules(vector<pair<Pattern, function<Pattern()>>>{{
+ {op, {{op, {X, A}}, B}},
+ [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
+ }, {
+ // X+(Y+A) -> (X+Y)+A
+ {op, {{op, {X, A}}, Y}},
+ [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
+ }, {
+ // For now, we still need explicit commutativity for the inner pattern
+ {op, {{op, {A, X}}, B}},
+ [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
+ }, {
+ {op, {{op, {A, X}}, Y}},
+ [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
+ }});
+ }
+
+ // move constants across subtractions
+ addRules(vector<pair<Pattern, function<Pattern()>>>{
+ {
+ // X - A -> X + (-A)
+ {Instruction::SUB, {X, A}},
+ [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; }
+ }, {
+ // (X + A) - Y -> (X - Y) + A
+ {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}},
+ [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
+ }, {
+ // (A + X) - Y -> (X - Y) + A
+ {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}},
+ [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
+ }, {
+ // X - (Y + A) -> (X - Y) + (-A)
+ {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}},
+ [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
+ }, {
+ // X - (A + Y) -> (X - Y) + (-A)
+ {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}},
+ [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
+ }
+ });
+}
+
+Pattern::Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments):
+ m_type(Operation),
+ m_instruction(_instruction),
+ m_arguments(_arguments)
+{
+}
+
+void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _matchGroups)
+{
+ m_matchGroup = _group;
+ m_matchGroups = &_matchGroups;
+}
+
+bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const
+{
+ if (!matchesBaseItem(_expr.item))
+ return false;
+ if (m_matchGroup)
+ {
+ if (!m_matchGroups->count(m_matchGroup))
+ (*m_matchGroups)[m_matchGroup] = &_expr;
+ else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id)
+ return false;
+ }
+ assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, "");
+ for (size_t i = 0; i < m_arguments.size(); ++i)
+ if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes))
+ return false;
+ return true;
+}
+
+AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const
+{
+ if (m_type == Operation)
+ return AssemblyItem(m_instruction, _location);
+ else
+ return AssemblyItem(m_type, data(), _location);
+}
+
+string Pattern::toString() const
+{
+ stringstream s;
+ switch (m_type)
+ {
+ case Operation:
+ s << instructionInfo(m_instruction).name;
+ break;
+ case Push:
+ if (m_data)
+ s << "PUSH " << hex << data();
+ else
+ s << "PUSH ";
+ break;
+ case UndefinedItem:
+ s << "ANY";
+ break;
+ default:
+ if (m_data)
+ s << "t=" << dec << m_type << " d=" << hex << data();
+ else
+ s << "t=" << dec << m_type << " d: nullptr";
+ break;
+ }
+ if (!m_requireDataMatch)
+ s << " ~";
+ if (m_matchGroup)
+ s << "[" << dec << m_matchGroup << "]";
+ s << "(";
+ for (Pattern const& p: m_arguments)
+ s << p.toString() << ", ";
+ s << ")";
+ return s.str();
+}
+
+bool Pattern::matchesBaseItem(AssemblyItem const* _item) const
+{
+ if (m_type == UndefinedItem)
+ return true;
+ if (!_item)
+ return false;
+ if (m_type != _item->type())
+ return false;
+ else if (m_type == Operation)
+ return m_instruction == _item->instruction();
+ else if (m_requireDataMatch)
+ return data() == _item->data();
+ return true;
+}
+
+Pattern::Expression const& Pattern::matchGroupValue() const
+{
+ assertThrow(m_matchGroup > 0, OptimizerException, "");
+ assertThrow(!!m_matchGroups, OptimizerException, "");
+ assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, "");
+ return *(*m_matchGroups)[m_matchGroup];
+}
+
+u256 const& Pattern::data() const
+{
+ assertThrow(m_data, OptimizerException, "");
+ return *m_data;
+}
+
+ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location)
+{
+ if (_pattern.matchGroup())
+ {
+ hasId = true;
+ id = _pattern.id();
+ }
+ else
+ {
+ hasId = false;
+ item = _pattern.toAssemblyItem(_location);
+ }
+ for (auto const& arg: _pattern.arguments())
+ arguments.push_back(ExpressionTemplate(arg, _location));
+}
+
+string ExpressionTemplate::toString() const
+{
+ stringstream s;
+ if (hasId)
+ s << id;
+ else
+ s << item;
+ s << "(";
+ for (auto const& arg: arguments)
+ s << arg.toString();
+ s << ")";
+ return s.str();
+}
diff --git a/libevmasm/SimplificationRules.h b/libevmasm/SimplificationRules.h
new file mode 100644
index 00000000..a4da5849
--- /dev/null
+++ b/libevmasm/SimplificationRules.h
@@ -0,0 +1,140 @@
+/*
+ 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/>.
+*/
+/**
+ * @file SimplificationRules
+ * @author Christian <chris@ethereum.org>
+ * @date 2017
+ * Module for applying replacement rules against Expressions.
+ */
+
+#pragma once
+
+#include <libevmasm/ExpressionClasses.h>
+
+#include <functional>
+#include <vector>
+
+namespace dev
+{
+namespace eth
+{
+
+class Pattern;
+
+/**
+ * Container for all simplification rules.
+ */
+class Rules: public boost::noncopyable
+{
+public:
+ using Expression = ExpressionClasses::Expression;
+
+ Rules();
+
+ /// @returns a pointer to the first matching pattern and sets the match
+ /// groups accordingly.
+ std::pair<Pattern, std::function<Pattern()>> const* findFirstMatch(
+ Expression const& _expr,
+ ExpressionClasses const& _classes
+ );
+
+private:
+ void addRules(std::vector<std::pair<Pattern, std::function<Pattern()>>> const& _rules);
+ void addRule(std::pair<Pattern, std::function<Pattern()>> const& _rule);
+
+ void resetMatchGroups() { m_matchGroups.clear(); }
+
+ std::map<unsigned, Expression const*> m_matchGroups;
+ std::vector<std::pair<Pattern, std::function<Pattern()>>> m_rules[256];
+};
+
+/**
+ * Pattern to match against an expression.
+ * Also stores matched expressions to retrieve them later, for constructing new expressions using
+ * ExpressionTemplate.
+ */
+class Pattern
+{
+public:
+ using Expression = ExpressionClasses::Expression;
+ using Id = ExpressionClasses::Id;
+
+ // Matches a specific constant value.
+ Pattern(unsigned _value): Pattern(u256(_value)) {}
+ // Matches a specific constant value.
+ Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(std::make_shared<u256>(_value)) {}
+ // Matches a specific assembly item type or anything if not given.
+ Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {}
+ // Matches a given instruction with given arguments
+ Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments = {});
+ /// Sets this pattern to be part of the match group with the identifier @a _group.
+ /// Inside one rule, all patterns in the same match group have to match expressions from the
+ /// same expression equivalence class.
+ void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups);
+ unsigned matchGroup() const { return m_matchGroup; }
+ bool matches(Expression const& _expr, ExpressionClasses const& _classes) const;
+
+ AssemblyItem toAssemblyItem(SourceLocation const& _location) const;
+ std::vector<Pattern> arguments() const { return m_arguments; }
+
+ /// @returns the id of the matched expression if this pattern is part of a match group.
+ Id id() const { return matchGroupValue().id; }
+ /// @returns the data of the matched expression if this pattern is part of a match group.
+ u256 const& d() const { return matchGroupValue().item->data(); }
+
+ std::string toString() const;
+
+ AssemblyItemType type() const { return m_type; }
+ Instruction instruction() const
+ {
+ assertThrow(type() == Operation, OptimizerException, "");
+ return m_instruction;
+ }
+
+private:
+ bool matchesBaseItem(AssemblyItem const* _item) const;
+ Expression const& matchGroupValue() const;
+ u256 const& data() const;
+
+ AssemblyItemType m_type;
+ bool m_requireDataMatch = false;
+ Instruction m_instruction; ///< Only valid if m_type is Operation
+ std::shared_ptr<u256> m_data; ///< Only valid if m_type is not Operation
+ std::vector<Pattern> m_arguments;
+ unsigned m_matchGroup = 0;
+ std::map<unsigned, Expression const*>* m_matchGroups = nullptr;
+};
+
+/**
+ * Template for a new expression that can be built from matched patterns.
+ */
+struct ExpressionTemplate
+{
+ using Expression = ExpressionClasses::Expression;
+ using Id = ExpressionClasses::Id;
+ explicit ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location);
+ std::string toString() const;
+ bool hasId = false;
+ /// Id of the matched expression, if available.
+ Id id = Id(-1);
+ // Otherwise, assembly item.
+ AssemblyItem item = UndefinedItem;
+ std::vector<ExpressionTemplate> arguments;
+};
+
+}
+}
diff --git a/libjulia/backends/evm/AbstractAssembly.h b/libjulia/backends/evm/AbstractAssembly.h
new file mode 100644
index 00000000..cfc9b8a5
--- /dev/null
+++ b/libjulia/backends/evm/AbstractAssembly.h
@@ -0,0 +1,117 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Abstract assembly interface, subclasses of which are to be used with the generic
+ * bytecode generator.
+ */
+
+#pragma once
+
+#include <libdevcore/CommonData.h>
+
+#include <functional>
+
+namespace dev
+{
+struct SourceLocation;
+namespace solidity
+{
+enum class Instruction: uint8_t;
+namespace assembly
+{
+struct Instruction;
+struct Identifier;
+}
+}
+namespace julia
+{
+
+///
+/// Assembly class that abstracts both the libevmasm assembly and the new julia evm assembly.
+///
+class AbstractAssembly
+{
+public:
+ using LabelID = size_t;
+
+ virtual ~AbstractAssembly() {}
+
+ /// Set a new source location valid starting from the next instruction.
+ virtual void setSourceLocation(SourceLocation const& _location) = 0;
+ /// Retrieve the current height of the stack. This does not have to be zero
+ /// at the beginning.
+ virtual int stackHeight() const = 0;
+ /// Append an EVM instruction.
+ virtual void appendInstruction(solidity::Instruction _instruction) = 0;
+ /// Append a constant.
+ virtual void appendConstant(u256 const& _constant) = 0;
+ /// Append a label.
+ virtual void appendLabel(LabelID _labelId) = 0;
+ /// Append a label reference.
+ virtual void appendLabelReference(LabelID _labelId) = 0;
+ /// Generate a new unique label.
+ virtual LabelID newLabelId() = 0;
+ /// Append a reference to a to-be-linked symobl.
+ /// Currently, we assume that the value is always a 20 byte number.
+ virtual void appendLinkerSymbol(std::string const& _name) = 0;
+
+ /// Append a jump instruction.
+ /// @param _stackDiffAfter the stack adjustment after this instruction.
+ /// This is helpful to stack height analysis if there is no continuing control flow.
+ virtual void appendJump(int _stackDiffAfter) = 0;
+
+ /// Append a jump-to-immediate operation.
+ /// @param _stackDiffAfter the stack adjustment after this instruction.
+ virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter = 0) = 0;
+ /// Append a jump-to-if-immediate operation.
+ virtual void appendJumpToIf(LabelID _labelId) = 0;
+ /// Start a subroutine identified by @a _labelId that takes @a _arguments
+ /// stack slots as arguments.
+ virtual void appendBeginsub(LabelID _labelId, int _arguments) = 0;
+ /// Call a subroutine identified by @a _labelId, taking @a _arguments from the
+ /// stack upon call and putting @a _returns arguments onto the stack upon return.
+ virtual void appendJumpsub(LabelID _labelId, int _arguments, int _returns) = 0;
+ /// Return from a subroutine.
+ /// @param _stackDiffAfter the stack adjustment after this instruction.
+ virtual void appendReturnsub(int _returns, int _stackDiffAfter = 0) = 0;
+
+ /// Append the assembled size as a constant.
+ virtual void appendAssemblySize() = 0;
+};
+
+enum class IdentifierContext { LValue, RValue };
+
+/// Object that is used to resolve references and generate code for access to identifiers external
+/// to inline assembly (not used in standalone assembly mode).
+struct ExternalIdentifierAccess
+{
+ using Resolver = std::function<size_t(solidity::assembly::Identifier const&, IdentifierContext, bool /*_crossesFunctionBoundary*/)>;
+ /// Resolve a an external reference given by the identifier in the given context.
+ /// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
+ Resolver resolve;
+ using CodeGenerator = std::function<void(solidity::assembly::Identifier const&, IdentifierContext, julia::AbstractAssembly&)>;
+ /// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context)
+ /// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed
+ /// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack.
+ CodeGenerator generateCode;
+};
+
+
+
+}
+}
diff --git a/libjulia/backends/evm/EVMAssembly.cpp b/libjulia/backends/evm/EVMAssembly.cpp
new file mode 100644
index 00000000..173d5e93
--- /dev/null
+++ b/libjulia/backends/evm/EVMAssembly.cpp
@@ -0,0 +1,194 @@
+/*
+ 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/>.
+*/
+/**
+ * Assembly interface for EVM and EVM1.5.
+ */
+
+#include <libjulia/backends/evm/EVMAssembly.h>
+
+#include <libevmasm/Instruction.h>
+
+#include <libsolidity/interface/Exceptions.h>
+
+using namespace std;
+using namespace dev;
+using namespace julia;
+
+namespace
+{
+/// Size of labels in bytes. Four-byte labels are required by some EVM1.5 instructions.
+size_t constexpr labelReferenceSize = 4;
+
+size_t constexpr assemblySizeReferenceSize = 4;
+}
+
+
+void EVMAssembly::setSourceLocation(SourceLocation const&)
+{
+ // Ignored for now;
+}
+
+void EVMAssembly::appendInstruction(solidity::Instruction _instr)
+{
+ m_bytecode.push_back(byte(_instr));
+ m_stackHeight += solidity::instructionInfo(_instr).ret - solidity::instructionInfo(_instr).args;
+}
+
+void EVMAssembly::appendConstant(u256 const& _constant)
+{
+ bytes data = toCompactBigEndian(_constant, 1);
+ appendInstruction(solidity::pushInstruction(data.size()));
+ m_bytecode += data;
+}
+
+void EVMAssembly::appendLabel(LabelID _labelId)
+{
+ setLabelToCurrentPosition(_labelId);
+ appendInstruction(solidity::Instruction::JUMPDEST);
+}
+
+void EVMAssembly::appendLabelReference(LabelID _labelId)
+{
+ solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode.");
+ // @TODO we now always use labelReferenceSize for all labels, it could be shortened
+ // for some of them.
+ appendInstruction(solidity::pushInstruction(labelReferenceSize));
+ m_labelReferences[m_bytecode.size()] = _labelId;
+ m_bytecode += bytes(labelReferenceSize);
+}
+
+EVMAssembly::LabelID EVMAssembly::newLabelId()
+{
+ m_labelPositions[m_nextLabelId] = size_t(-1);
+ return m_nextLabelId++;
+}
+
+void EVMAssembly::appendLinkerSymbol(string const&)
+{
+ solAssert(false, "Linker symbols not yet implemented.");
+}
+
+void EVMAssembly::appendJump(int _stackDiffAfter)
+{
+ solAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
+ appendInstruction(solidity::Instruction::JUMP);
+ m_stackHeight += _stackDiffAfter;
+}
+
+void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
+{
+ if (m_evm15)
+ {
+ m_bytecode.push_back(byte(solidity::Instruction::JUMPTO));
+ appendLabelReferenceInternal(_labelId);
+ m_stackHeight += _stackDiffAfter;
+ }
+ else
+ {
+ appendLabelReference(_labelId);
+ appendJump(_stackDiffAfter);
+ }
+}
+
+void EVMAssembly::appendJumpToIf(LabelID _labelId)
+{
+ if (m_evm15)
+ {
+ m_bytecode.push_back(byte(solidity::Instruction::JUMPIF));
+ appendLabelReferenceInternal(_labelId);
+ m_stackHeight--;
+ }
+ else
+ {
+ appendLabelReference(_labelId);
+ appendInstruction(solidity::Instruction::JUMPI);
+ }
+}
+
+void EVMAssembly::appendBeginsub(LabelID _labelId, int _arguments)
+{
+ solAssert(m_evm15, "BEGINSUB used for EVM 1.0");
+ solAssert(_arguments >= 0, "");
+ setLabelToCurrentPosition(_labelId);
+ m_bytecode.push_back(byte(solidity::Instruction::BEGINSUB));
+ m_stackHeight += _arguments;
+}
+
+void EVMAssembly::appendJumpsub(LabelID _labelId, int _arguments, int _returns)
+{
+ solAssert(m_evm15, "JUMPSUB used for EVM 1.0");
+ solAssert(_arguments >= 0 && _returns >= 0, "");
+ m_bytecode.push_back(byte(solidity::Instruction::JUMPSUB));
+ appendLabelReferenceInternal(_labelId);
+ m_stackHeight += _returns - _arguments;
+}
+
+void EVMAssembly::appendReturnsub(int _returns, int _stackDiffAfter)
+{
+ solAssert(m_evm15, "RETURNSUB used for EVM 1.0");
+ solAssert(_returns >= 0, "");
+ m_bytecode.push_back(byte(solidity::Instruction::RETURNSUB));
+ m_stackHeight += _stackDiffAfter - _returns;
+}
+
+eth::LinkerObject EVMAssembly::finalize()
+{
+ size_t bytecodeSize = m_bytecode.size();
+ for (auto const& ref: m_assemblySizePositions)
+ updateReference(ref, assemblySizeReferenceSize, u256(bytecodeSize));
+
+ for (auto const& ref: m_labelReferences)
+ {
+ size_t referencePos = ref.first;
+ solAssert(m_labelPositions.count(ref.second), "");
+ size_t labelPos = m_labelPositions.at(ref.second);
+ solAssert(labelPos != size_t(-1), "Undefined but allocated label used.");
+ updateReference(referencePos, labelReferenceSize, u256(labelPos));
+ }
+
+ eth::LinkerObject obj;
+ obj.bytecode = m_bytecode;
+ return obj;
+}
+
+void EVMAssembly::setLabelToCurrentPosition(LabelID _labelId)
+{
+ solAssert(m_labelPositions.count(_labelId), "Label not found.");
+ solAssert(m_labelPositions[_labelId] == size_t(-1), "Label already set.");
+ m_labelPositions[_labelId] = m_bytecode.size();
+}
+
+void EVMAssembly::appendLabelReferenceInternal(LabelID _labelId)
+{
+ m_labelReferences[m_bytecode.size()] = _labelId;
+ m_bytecode += bytes(labelReferenceSize);
+}
+
+void EVMAssembly::appendAssemblySize()
+{
+ appendInstruction(solidity::pushInstruction(assemblySizeReferenceSize));
+ m_assemblySizePositions.push_back(m_bytecode.size());
+ m_bytecode += bytes(assemblySizeReferenceSize);
+}
+
+void EVMAssembly::updateReference(size_t pos, size_t size, u256 value)
+{
+ solAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, "");
+ solAssert(value < (u256(1) << (8 * size)), "");
+ for (size_t i = 0; i < size; i++)
+ m_bytecode[pos + i] = byte((value >> (8 * (size - i - 1))) & 0xff);
+}
diff --git a/libjulia/backends/evm/EVMAssembly.h b/libjulia/backends/evm/EVMAssembly.h
new file mode 100644
index 00000000..69585822
--- /dev/null
+++ b/libjulia/backends/evm/EVMAssembly.h
@@ -0,0 +1,94 @@
+/*
+ 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/>.
+*/
+/**
+ * Assembly interface for EVM and EVM1.5.
+ */
+
+#pragma once
+
+#include <libjulia/backends/evm/AbstractAssembly.h>
+
+#include <libevmasm/LinkerObject.h>
+
+#include <map>
+
+namespace dev
+{
+namespace julia
+{
+
+class EVMAssembly: public AbstractAssembly
+{
+public:
+ explicit EVMAssembly(bool _evm15 = false): m_evm15(_evm15) { }
+ virtual ~EVMAssembly() {}
+
+ /// Set a new source location valid starting from the next instruction.
+ virtual void setSourceLocation(SourceLocation const& _location) override;
+ /// Retrieve the current height of the stack. This does not have to be zero
+ /// at the beginning.
+ virtual int stackHeight() const override { return m_stackHeight; }
+ /// Append an EVM instruction.
+ virtual void appendInstruction(solidity::Instruction _instruction) override;
+ /// Append a constant.
+ virtual void appendConstant(u256 const& _constant) override;
+ /// Append a label.
+ virtual void appendLabel(LabelID _labelId) override;
+ /// Append a label reference.
+ virtual void appendLabelReference(LabelID _labelId) override;
+ /// Generate a new unique label.
+ virtual LabelID newLabelId() override;
+ /// Append a reference to a to-be-linked symobl.
+ /// Currently, we assume that the value is always a 20 byte number.
+ virtual void appendLinkerSymbol(std::string const& _name) override;
+
+ /// Append a jump instruction.
+ /// @param _stackDiffAfter the stack adjustment after this instruction.
+ virtual void appendJump(int _stackDiffAfter) override;
+ /// Append a jump-to-immediate operation.
+ virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
+ /// Append a jump-to-if-immediate operation.
+ virtual void appendJumpToIf(LabelID _labelId) override;
+ /// Start a subroutine.
+ virtual void appendBeginsub(LabelID _labelId, int _arguments) override;
+ /// Call a subroutine.
+ virtual void appendJumpsub(LabelID _labelId, int _arguments, int _returns) override;
+ /// Return from a subroutine.
+ virtual void appendReturnsub(int _returns, int _stackDiffAfter) override;
+
+ /// Append the assembled size as a constant.
+ virtual void appendAssemblySize() override;
+
+ /// Resolves references inside the bytecode and returns the linker object.
+ eth::LinkerObject finalize();
+
+private:
+ void setLabelToCurrentPosition(AbstractAssembly::LabelID _labelId);
+ void appendLabelReferenceInternal(AbstractAssembly::LabelID _labelId);
+ void updateReference(size_t pos, size_t size, u256 value);
+
+ bool m_evm15 = false; ///< if true, switch to evm1.5 mode
+ LabelID m_nextLabelId = 0;
+ int m_stackHeight = 0;
+ bytes m_bytecode;
+ std::map<LabelID, size_t> m_labelPositions;
+ std::map<size_t, LabelID> m_labelReferences;
+ std::vector<size_t> m_assemblySizePositions;
+};
+
+}
+}
diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp
new file mode 100644
index 00000000..efbe5647
--- /dev/null
+++ b/libjulia/backends/evm/EVMCodeTransform.cpp
@@ -0,0 +1,500 @@
+/*
+ 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/>.
+*/
+/**
+ * Common code generator for translating Julia / inline assembly to EVM and EVM1.5.
+ */
+
+#include <libjulia/backends/evm/EVMCodeTransform.h>
+
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+#include <libsolidity/inlineasm/AsmData.h>
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+
+using namespace std;
+using namespace dev;
+using namespace dev::julia;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+void CodeTransform::operator()(VariableDeclaration const& _varDecl)
+{
+ solAssert(m_scope, "");
+
+ int expectedItems = _varDecl.variables.size();
+ int height = m_assembly.stackHeight();
+ boost::apply_visitor(*this, *_varDecl.value);
+ expectDeposit(expectedItems, height);
+ for (auto const& variable: _varDecl.variables)
+ {
+ auto& var = boost::get<Scope::Variable>(m_scope->identifiers.at(variable.name));
+ m_context->variableStackHeights[&var] = height++;
+ }
+ checkStackHeight(&_varDecl);
+}
+
+void CodeTransform::operator()(Assignment const& _assignment)
+{
+ visitExpression(*_assignment.value);
+ m_assembly.setSourceLocation(_assignment.location);
+ generateAssignment(_assignment.variableName);
+ checkStackHeight(&_assignment);
+}
+
+void CodeTransform::operator()(StackAssignment const& _assignment)
+{
+ m_assembly.setSourceLocation(_assignment.location);
+ generateAssignment(_assignment.variableName);
+ checkStackHeight(&_assignment);
+}
+
+void CodeTransform::operator()(Label const& _label)
+{
+ m_assembly.setSourceLocation(_label.location);
+ solAssert(m_scope, "");
+ solAssert(m_scope->identifiers.count(_label.name), "");
+ Scope::Label& label = boost::get<Scope::Label>(m_scope->identifiers.at(_label.name));
+ m_assembly.appendLabel(labelID(label));
+ checkStackHeight(&_label);
+}
+
+void CodeTransform::operator()(FunctionCall const& _call)
+{
+ solAssert(m_scope, "");
+
+ m_assembly.setSourceLocation(_call.location);
+ EVMAssembly::LabelID returnLabel(-1); // only used for evm 1.0
+ if (!m_evm15)
+ {
+ returnLabel = m_assembly.newLabelId();
+ m_assembly.appendLabelReference(returnLabel);
+ m_stackAdjustment++;
+ }
+
+ Scope::Function* function = nullptr;
+ solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor(
+ [=](Scope::Variable&) { solAssert(false, "Expected function name."); },
+ [=](Scope::Label&) { solAssert(false, "Expected function name."); },
+ [&](Scope::Function& _function) { function = &_function; }
+ )), "Function name not found.");
+ solAssert(function, "");
+ solAssert(function->arguments.size() == _call.arguments.size(), "");
+ for (auto const& arg: _call.arguments | boost::adaptors::reversed)
+ visitExpression(arg);
+ m_assembly.setSourceLocation(_call.location);
+ if (m_evm15)
+ m_assembly.appendJumpsub(functionEntryID(*function), function->arguments.size(), function->returns.size());
+ else
+ {
+ m_assembly.appendJumpTo(functionEntryID(*function), function->returns.size() - function->arguments.size() - 1);
+ m_assembly.appendLabel(returnLabel);
+ m_stackAdjustment--;
+ }
+ checkStackHeight(&_call);
+}
+
+void CodeTransform::operator()(FunctionalInstruction const& _instruction)
+{
+ if (m_evm15 && (
+ _instruction.instruction.instruction == solidity::Instruction::JUMP ||
+ _instruction.instruction.instruction == solidity::Instruction::JUMPI
+ ))
+ {
+ bool const isJumpI = _instruction.instruction.instruction == solidity::Instruction::JUMPI;
+ if (isJumpI)
+ {
+ solAssert(_instruction.arguments.size() == 2, "");
+ visitExpression(_instruction.arguments.at(1));
+ }
+ else
+ {
+ solAssert(_instruction.arguments.size() == 1, "");
+ }
+ m_assembly.setSourceLocation(_instruction.location);
+ auto label = labelFromIdentifier(boost::get<assembly::Identifier>(_instruction.arguments.at(0)));
+ if (isJumpI)
+ m_assembly.appendJumpToIf(label);
+ else
+ m_assembly.appendJumpTo(label);
+ }
+ else
+ {
+ for (auto const& arg: _instruction.arguments | boost::adaptors::reversed)
+ visitExpression(arg);
+ (*this)(_instruction.instruction);
+ }
+ checkStackHeight(&_instruction);
+}
+
+void CodeTransform::operator()(assembly::Identifier const& _identifier)
+{
+ m_assembly.setSourceLocation(_identifier.location);
+ // First search internals, then externals.
+ solAssert(m_scope, "");
+ if (m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
+ [=](Scope::Variable& _var)
+ {
+ if (int heightDiff = variableHeightDiff(_var, false))
+ m_assembly.appendInstruction(solidity::dupInstruction(heightDiff));
+ else
+ // Store something to balance the stack
+ m_assembly.appendConstant(u256(0));
+ },
+ [=](Scope::Label& _label)
+ {
+ m_assembly.appendLabelReference(labelID(_label));
+ },
+ [=](Scope::Function&)
+ {
+ solAssert(false, "Function not removed during desugaring.");
+ }
+ )))
+ {
+ return;
+ }
+ solAssert(
+ m_identifierAccess.generateCode,
+ "Identifier not found and no external access available."
+ );
+ m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_assembly);
+ checkStackHeight(&_identifier);
+}
+
+void CodeTransform::operator()(assembly::Literal const& _literal)
+{
+ m_assembly.setSourceLocation(_literal.location);
+ if (_literal.kind == assembly::LiteralKind::Number)
+ m_assembly.appendConstant(u256(_literal.value));
+ else if (_literal.kind == assembly::LiteralKind::Boolean)
+ {
+ if (_literal.value == "true")
+ m_assembly.appendConstant(u256(1));
+ else
+ m_assembly.appendConstant(u256(0));
+ }
+ else
+ {
+ solAssert(_literal.value.size() <= 32, "");
+ m_assembly.appendConstant(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
+ }
+ checkStackHeight(&_literal);
+}
+
+void CodeTransform::operator()(assembly::Instruction const& _instruction)
+{
+ solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5");
+ solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5");
+ m_assembly.setSourceLocation(_instruction.location);
+ m_assembly.appendInstruction(_instruction.instruction);
+ checkStackHeight(&_instruction);
+}
+
+void CodeTransform::operator()(Switch const& _switch)
+{
+ //@TODO use JUMPV in EVM1.5?
+
+ visitExpression(*_switch.expression);
+ int expressionHeight = m_assembly.stackHeight();
+ map<Case const*, AbstractAssembly::LabelID> caseBodies;
+ AbstractAssembly::LabelID end = m_assembly.newLabelId();
+ for (Case const& c: _switch.cases)
+ {
+ if (c.value)
+ {
+ (*this)(*c.value);
+ m_assembly.setSourceLocation(c.location);
+ AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId();
+ caseBodies[&c] = bodyLabel;
+ solAssert(m_assembly.stackHeight() == expressionHeight + 1, "");
+ m_assembly.appendInstruction(solidity::dupInstruction(2));
+ m_assembly.appendInstruction(solidity::Instruction::EQ);
+ m_assembly.appendJumpToIf(bodyLabel);
+ }
+ else
+ // default case
+ (*this)(c.body);
+ }
+ m_assembly.setSourceLocation(_switch.location);
+ m_assembly.appendJumpTo(end);
+
+ size_t numCases = caseBodies.size();
+ for (auto const& c: caseBodies)
+ {
+ m_assembly.setSourceLocation(c.first->location);
+ m_assembly.appendLabel(c.second);
+ (*this)(c.first->body);
+ // Avoid useless "jump to next" for the last case.
+ if (--numCases > 0)
+ {
+ m_assembly.setSourceLocation(c.first->location);
+ m_assembly.appendJumpTo(end);
+ }
+ }
+
+ m_assembly.setSourceLocation(_switch.location);
+ m_assembly.appendLabel(end);
+ m_assembly.appendInstruction(solidity::Instruction::POP);
+ checkStackHeight(&_switch);
+}
+
+void CodeTransform::operator()(FunctionDefinition const& _function)
+{
+ solAssert(m_scope, "");
+ solAssert(m_scope->identifiers.count(_function.name), "");
+ Scope::Function& function = boost::get<Scope::Function>(m_scope->identifiers.at(_function.name));
+
+ int const localStackAdjustment = m_evm15 ? 0 : 1;
+ int height = localStackAdjustment;
+ solAssert(m_info.scopes.at(&_function.body), "");
+ Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
+ solAssert(varScope, "");
+ for (auto const& v: _function.arguments | boost::adaptors::reversed)
+ {
+ auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
+ m_context->variableStackHeights[&var] = height++;
+ }
+
+ m_assembly.setSourceLocation(_function.location);
+ int stackHeightBefore = m_assembly.stackHeight();
+ AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId();
+
+ if (m_evm15)
+ {
+ m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
+ m_assembly.appendBeginsub(functionEntryID(function), _function.arguments.size());
+ }
+ else
+ {
+ m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
+ m_assembly.appendLabel(functionEntryID(function));
+ }
+ m_stackAdjustment += localStackAdjustment;
+
+ for (auto const& v: _function.returns)
+ {
+ auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
+ m_context->variableStackHeights[&var] = height++;
+ // Preset stack slots for return variables to zero.
+ m_assembly.appendConstant(u256(0));
+ }
+
+ CodeTransform(m_assembly, m_info, m_julia, m_evm15, m_identifierAccess, localStackAdjustment, m_context)
+ (_function.body);
+
+ {
+ // The stack layout here is:
+ // <return label>? <arguments...> <return values...>
+ // But we would like it to be:
+ // <return values...> <return label>?
+ // So we have to append some SWAP and POP instructions.
+
+ // This vector holds the desired target positions of all stack slots and is
+ // modified parallel to the actual stack.
+ vector<int> stackLayout;
+ if (!m_evm15)
+ stackLayout.push_back(_function.returns.size()); // Move return label to the top
+ stackLayout += vector<int>(_function.arguments.size(), -1); // discard all arguments
+ for (size_t i = 0; i < _function.returns.size(); ++i)
+ stackLayout.push_back(i); // Move return values down, but keep order.
+
+ solAssert(stackLayout.size() <= 17, "Stack too deep");
+ while (!stackLayout.empty() && stackLayout.back() != int(stackLayout.size() - 1))
+ if (stackLayout.back() < 0)
+ {
+ m_assembly.appendInstruction(solidity::Instruction::POP);
+ stackLayout.pop_back();
+ }
+ else
+ {
+ m_assembly.appendInstruction(swapInstruction(stackLayout.size() - stackLayout.back() - 1));
+ swap(stackLayout[stackLayout.back()], stackLayout.back());
+ }
+ for (int i = 0; size_t(i) < stackLayout.size(); ++i)
+ solAssert(i == stackLayout[i], "Error reshuffling stack.");
+ }
+
+ if (m_evm15)
+ m_assembly.appendReturnsub(_function.returns.size(), stackHeightBefore);
+ else
+ m_assembly.appendJump(stackHeightBefore - _function.returns.size());
+ m_stackAdjustment -= localStackAdjustment;
+ m_assembly.appendLabel(afterFunction);
+ checkStackHeight(&_function);
+}
+
+void CodeTransform::operator()(ForLoop const& _forLoop)
+{
+ Scope* originalScope = m_scope;
+ // We start with visiting the block, but not finalizing it.
+ m_scope = m_info.scopes.at(&_forLoop.pre).get();
+ int stackStartHeight = m_assembly.stackHeight();
+
+ visitStatements(_forLoop.pre.statements);
+
+ // TODO: When we implement break and continue, the labels and the stack heights at that point
+ // have to be stored in a stack.
+ AbstractAssembly::LabelID loopStart = m_assembly.newLabelId();
+ AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
+ AbstractAssembly::LabelID postPart = m_assembly.newLabelId();
+
+ m_assembly.setSourceLocation(_forLoop.location);
+ m_assembly.appendLabel(loopStart);
+
+ visitExpression(*_forLoop.condition);
+ m_assembly.setSourceLocation(_forLoop.location);
+ m_assembly.appendInstruction(solidity::Instruction::ISZERO);
+ m_assembly.appendJumpToIf(loopEnd);
+
+ (*this)(_forLoop.body);
+
+ m_assembly.setSourceLocation(_forLoop.location);
+ m_assembly.appendLabel(postPart);
+
+ (*this)(_forLoop.post);
+
+ m_assembly.setSourceLocation(_forLoop.location);
+ m_assembly.appendJumpTo(loopStart);
+ m_assembly.appendLabel(loopEnd);
+
+ finalizeBlock(_forLoop.pre, stackStartHeight);
+ m_scope = originalScope;
+}
+
+void CodeTransform::operator()(Block const& _block)
+{
+ Scope* originalScope = m_scope;
+ m_scope = m_info.scopes.at(&_block).get();
+
+ int blockStartStackHeight = m_assembly.stackHeight();
+ visitStatements(_block.statements);
+
+ finalizeBlock(_block, blockStartStackHeight);
+ m_scope = originalScope;
+}
+
+AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier)
+{
+ AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1);
+ if (!m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
+ [=](Scope::Variable&) { solAssert(false, "Expected label"); },
+ [&](Scope::Label& _label)
+ {
+ label = labelID(_label);
+ },
+ [=](Scope::Function&) { solAssert(false, "Expected label"); }
+ )))
+ {
+ solAssert(false, "Identifier not found.");
+ }
+ return label;
+}
+
+AbstractAssembly::LabelID CodeTransform::labelID(Scope::Label const& _label)
+{
+ if (!m_context->labelIDs.count(&_label))
+ m_context->labelIDs[&_label] = m_assembly.newLabelId();
+ return m_context->labelIDs[&_label];
+}
+
+AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _function)
+{
+ if (!m_context->functionEntryIDs.count(&_function))
+ m_context->functionEntryIDs[&_function] = m_assembly.newLabelId();
+ return m_context->functionEntryIDs[&_function];
+}
+
+void CodeTransform::visitExpression(Statement const& _expression)
+{
+ int height = m_assembly.stackHeight();
+ boost::apply_visitor(*this, _expression);
+ expectDeposit(1, height);
+}
+
+void CodeTransform::visitStatements(vector<Statement> const& _statements)
+{
+ for (auto const& statement: _statements)
+ boost::apply_visitor(*this, statement);
+}
+
+void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight)
+{
+ m_assembly.setSourceLocation(_block.location);
+
+ // pop variables
+ solAssert(m_info.scopes.at(&_block).get() == m_scope, "");
+ for (size_t i = 0; i < m_scope->numberOfVariables(); ++i)
+ m_assembly.appendInstruction(solidity::Instruction::POP);
+
+ int deposit = m_assembly.stackHeight() - blockStartStackHeight;
+ solAssert(deposit == 0, "Invalid stack height at end of block.");
+ checkStackHeight(&_block);
+}
+
+void CodeTransform::generateAssignment(Identifier const& _variableName)
+{
+ solAssert(m_scope, "");
+ auto var = m_scope->lookup(_variableName.name);
+ if (var)
+ {
+ Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
+ if (int heightDiff = variableHeightDiff(_var, true))
+ m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1));
+ m_assembly.appendInstruction(solidity::Instruction::POP);
+ }
+ else
+ {
+ solAssert(
+ m_identifierAccess.generateCode,
+ "Identifier not found and no external access available."
+ );
+ m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_assembly);
+ }
+}
+
+int CodeTransform::variableHeightDiff(solidity::assembly::Scope::Variable const& _var, bool _forSwap)
+{
+ solAssert(m_context->variableStackHeights.count(&_var), "");
+ int heightDiff = m_assembly.stackHeight() - m_context->variableStackHeights[&_var];
+ if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
+ {
+ solUnimplemented(
+ "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")"
+ );
+ return 0;
+ }
+ else
+ return heightDiff;
+}
+
+void CodeTransform::expectDeposit(int _deposit, int _oldHeight)
+{
+ solAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
+}
+
+void CodeTransform::checkStackHeight(void const* _astElement)
+{
+ solAssert(m_info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
+ solAssert(
+ m_info.stackHeightInfo.at(_astElement) == m_assembly.stackHeight() - m_stackAdjustment,
+ "Stack height mismatch between analysis and code generation phase: Analysis: " +
+ to_string(m_info.stackHeightInfo.at(_astElement)) +
+ " code gen: " +
+ to_string(m_assembly.stackHeight() - m_stackAdjustment)
+ );
+}
diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h
new file mode 100644
index 00000000..cd452c5b
--- /dev/null
+++ b/libjulia/backends/evm/EVMCodeTransform.h
@@ -0,0 +1,149 @@
+/*
+ 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/>.
+*/
+/**
+ * Common code generator for translating Julia / inline assembly to EVM and EVM1.5.
+ */
+
+#include <libjulia/backends/evm/EVMAssembly.h>
+
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+#include <boost/optional.hpp>
+
+namespace dev
+{
+namespace solidity
+{
+class ErrorReporter;
+namespace assembly
+{
+struct AsmAnalysisInfo;
+}
+}
+namespace julia
+{
+class EVMAssembly;
+
+class CodeTransform: public boost::static_visitor<>
+{
+public:
+ /// Create the code transformer.
+ /// @param _identifierAccess used to resolve identifiers external to the inline assembly
+ CodeTransform(
+ julia::AbstractAssembly& _assembly,
+ solidity::assembly::AsmAnalysisInfo& _analysisInfo,
+ bool _julia = false,
+ bool _evm15 = false,
+ ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
+ ): CodeTransform(
+ _assembly,
+ _analysisInfo,
+ _julia,
+ _evm15,
+ _identifierAccess,
+ _assembly.stackHeight(),
+ std::make_shared<Context>()
+ )
+ {
+ }
+
+protected:
+ struct Context
+ {
+ using Scope = solidity::assembly::Scope;
+ std::map<Scope::Label const*, AbstractAssembly::LabelID> labelIDs;
+ std::map<Scope::Function const*, AbstractAssembly::LabelID> functionEntryIDs;
+ std::map<Scope::Variable const*, int> variableStackHeights;
+ };
+
+ CodeTransform(
+ julia::AbstractAssembly& _assembly,
+ solidity::assembly::AsmAnalysisInfo& _analysisInfo,
+ bool _julia,
+ bool _evm15,
+ ExternalIdentifierAccess const& _identifierAccess,
+ int _stackAdjustment,
+ std::shared_ptr<Context> _context
+ ):
+ m_assembly(_assembly),
+ m_info(_analysisInfo),
+ m_julia(_julia),
+ m_evm15(_evm15),
+ m_identifierAccess(_identifierAccess),
+ m_stackAdjustment(_stackAdjustment),
+ m_context(_context)
+ {}
+
+public:
+ void operator()(solidity::assembly::Instruction const& _instruction);
+ void operator()(solidity::assembly::Literal const& _literal);
+ void operator()(solidity::assembly::Identifier const& _identifier);
+ void operator()(solidity::assembly::FunctionalInstruction const& _instr);
+ void operator()(solidity::assembly::FunctionCall const&);
+ void operator()(solidity::assembly::Label const& _label);
+ void operator()(solidity::assembly::StackAssignment const& _assignment);
+ void operator()(solidity::assembly::Assignment const& _assignment);
+ void operator()(solidity::assembly::VariableDeclaration const& _varDecl);
+ void operator()(solidity::assembly::Switch const& _switch);
+ void operator()(solidity::assembly::FunctionDefinition const&);
+ void operator()(solidity::assembly::ForLoop const&);
+ void operator()(solidity::assembly::Block const& _block);
+
+private:
+ AbstractAssembly::LabelID labelFromIdentifier(solidity::assembly::Identifier const& _identifier);
+ /// @returns the label ID corresponding to the given label, allocating a new one if
+ /// necessary.
+ AbstractAssembly::LabelID labelID(solidity::assembly::Scope::Label const& _label);
+ AbstractAssembly::LabelID functionEntryID(solidity::assembly::Scope::Function const& _function);
+ /// Generates code for an expression that is supposed to return a single value.
+ void visitExpression(solidity::assembly::Statement const& _expression);
+
+ void visitStatements(std::vector<solidity::assembly::Statement> const& _statements);
+
+ /// Pops all variables declared in the block and checks that the stack height is equal
+ /// to @a _blackStartStackHeight.
+ void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
+
+ void generateAssignment(solidity::assembly::Identifier const& _variableName);
+
+ /// Determines the stack height difference to the given variables. Throws
+ /// if it is not yet in scope or the height difference is too large. Returns
+ /// the (positive) stack height difference otherwise.
+ int variableHeightDiff(solidity::assembly::Scope::Variable const& _var, bool _forSwap);
+
+ void expectDeposit(int _deposit, int _oldHeight);
+
+ void checkStackHeight(void const* _astElement);
+
+ julia::AbstractAssembly& m_assembly;
+ solidity::assembly::AsmAnalysisInfo& m_info;
+ solidity::assembly::Scope* m_scope = nullptr;
+ bool m_julia = false;
+ bool m_evm15 = false;
+ ExternalIdentifierAccess m_identifierAccess;
+ /// Adjustment between the stack height as determined during the analysis phase
+ /// and the stack height in the assembly. This is caused by an initial stack being present
+ /// for inline assembly and different stack heights depending on the EVM backend used
+ /// (EVM 1.0 or 1.5).
+ int m_stackAdjustment = 0;
+ std::shared_ptr<Context> m_context;
+};
+
+}
+}
diff --git a/liblll/All.h b/liblll/All.h
deleted file mode 100644
index 7c4192f6..00000000
--- a/liblll/All.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-#include "CodeFragment.h"
-#include "Compiler.h"
-#include "CompilerState.h"
-#include "Parser.h"
diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp
index af7d7f0a..f637dfb1 100644
--- a/liblll/CodeFragment.cpp
+++ b/liblll/CodeFragment.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CodeFragment.cpp
* @author Gav Wood <i@gavwood.com>
@@ -171,11 +171,23 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
return string();
};
- auto varAddress = [&](string const& n)
+ auto varAddress = [&](string const& n, bool createMissing = false)
{
+ if (n.empty())
+ error<InvalidName>("Empty variable name not allowed");
auto it = _s.vars.find(n);
if (it == _s.vars.end())
- error<InvalidName>(std::string("Symbol not found: ") + s);
+ {
+ if (createMissing)
+ {
+ // Create new variable
+ bool ok;
+ tie(it, ok) = _s.vars.insert(make_pair(n, make_pair(_s.stackSize, 32)));
+ _s.stackSize += 32;
+ }
+ else
+ error<InvalidName>(std::string("Symbol not found: ") + n);
+ }
return it->second.first;
};
@@ -192,7 +204,13 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
if (_t.size() != 2)
error<IncorrectParameterCount>();
- m_asm.append(CodeFragment::compile(contentsString(firstAsString()), _s).m_asm);
+ string fileName = firstAsString();
+ if (fileName.empty())
+ error<InvalidName>("Empty file name provided");
+ string contents = contentsString(fileName);
+ if (contents.empty())
+ error<InvalidName>(std::string("File not found (or empty): ") + fileName);
+ m_asm.append(CodeFragment::compile(contents, _s).m_asm);
}
else if (us == "SET")
{
@@ -202,7 +220,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
for (auto const& i: _t)
if (c++ == 2)
m_asm.append(CodeFragment(i, _s, false).m_asm);
- m_asm.append((u256)varAddress(firstAsString()));
+ m_asm.append((u256)varAddress(firstAsString(), true));
m_asm.append(Instruction::MSTORE);
}
else if (us == "GET")
@@ -240,7 +258,11 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
}
else if (ii == 2)
if (_t.size() == 3)
- _s.defs[n] = CodeFragment(i, _s);
+ {
+ /// NOTE: some compilers could do the assignment first if this is done in a single line
+ CodeFragment code = CodeFragment(i, _s);
+ _s.defs[n] = code;
+ }
else
for (auto const& j: i)
{
@@ -439,15 +461,21 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
int minDep = min(code[1].m_asm.deposit(), code[2].m_asm.deposit());
m_asm.append(code[0].m_asm);
- auto pos = m_asm.appendJumpI();
- m_asm.onePath();
+ auto mainBranch = m_asm.appendJumpI();
+
+ /// The else branch.
+ int startDeposit = m_asm.deposit();
m_asm.append(code[2].m_asm, minDep);
auto end = m_asm.appendJump();
- m_asm.otherPath();
- m_asm << pos.tag();
+ int deposit = m_asm.deposit();
+ m_asm.setDeposit(startDeposit);
+
+ /// The main branch.
+ m_asm << mainBranch.tag();
m_asm.append(code[1].m_asm, minDep);
m_asm << end.tag();
- m_asm.donePaths();
+ if (m_asm.deposit() != deposit)
+ error<InvalidDeposit>();
}
else if (us == "WHEN" || us == "UNLESS")
{
@@ -458,11 +486,8 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
if (us == "WHEN")
m_asm.append(Instruction::ISZERO);
auto end = m_asm.appendJumpI();
- m_asm.onePath();
- m_asm.otherPath();
m_asm.append(code[1].m_asm, 0);
m_asm << end.tag();
- m_asm.donePaths();
}
else if (us == "WHILE" || us == "UNTIL")
{
@@ -498,14 +523,30 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
requireSize(1);
requireDeposit(0, 1);
- m_asm.append(Instruction::MSIZE);
- m_asm.append(u256(0));
+ // (alloc N):
+ // - Evaluates to (msize) before the allocation - the start of the allocated memory
+ // - Does not allocate memory when N is zero
+ // - Size of memory allocated is N bytes rounded up to a multiple of 32
+ // - Uses MLOAD to expand MSIZE to avoid modifying memory.
+
+ auto end = m_asm.newTag();
+ m_asm.append(Instruction::MSIZE); // Result will be original top of memory
+ m_asm.append(code[0].m_asm, 1); // The alloc argument N
+ m_asm.append(Instruction::DUP1);
+ m_asm.append(Instruction::ISZERO);// (alloc 0) does not change MSIZE
+ m_asm.appendJumpI(end);
m_asm.append(u256(1));
- m_asm.append(code[0].m_asm, 1);
- m_asm.append(Instruction::MSIZE);
+ m_asm.append(Instruction::DUP2); // Copy N
+ m_asm.append(Instruction::SUB); // N-1
+ m_asm.append(u256(0x1f)); // Bit mask
+ m_asm.append(Instruction::NOT); // Invert
+ m_asm.append(Instruction::AND); // Align N-1 on 32 byte boundary
+ m_asm.append(Instruction::MSIZE); // MSIZE is cheap
m_asm.append(Instruction::ADD);
- m_asm.append(Instruction::SUB);
- m_asm.append(Instruction::MSTORE8);
+ m_asm.append(Instruction::MLOAD); // Updates MSIZE
+ m_asm.append(Instruction::POP); // Discard the result of the MLOAD
+ m_asm.append(end);
+ m_asm.append(Instruction::POP); // Discard duplicate N
_s.usedAlloc = true;
}
@@ -515,8 +556,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
requireMaxSize(3);
requireDeposit(1, 1);
- auto subPush = m_asm.newSub(make_shared<Assembly>(code[0].assembly(ns)));
- m_asm.append(m_asm.newPushSubSize(subPush.data()));
+ auto subPush = m_asm.appendSubroutine(make_shared<Assembly>(code[0].assembly(ns)));
m_asm.append(Instruction::DUP1);
if (code.size() == 3)
{
@@ -571,11 +611,9 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
for (auto const& i: code)
m_asm.append(i.m_asm);
- m_asm.popTo(1);
- }
- else if (us == "PANIC")
- {
- m_asm.appendJump(m_asm.errorTag());
+ // Leave only the last item on stack.
+ while (m_asm.deposit() > 1)
+ m_asm.append(Instruction::POP);
}
else if (us == "BYTECODESIZE")
{
diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h
index 637d169f..6622de3e 100644
--- a/liblll/CodeFragment.h
+++ b/liblll/CodeFragment.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CodeFragment.h
* @author Gav Wood <i@gavwood.com>
diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp
index cd909f34..05376cd5 100644
--- a/liblll/Compiler.cpp
+++ b/liblll/Compiler.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Compiler.cpp
* @author Gav Wood <i@gavwood.com>
@@ -69,10 +69,11 @@ std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::v
{
CompilerState cs;
cs.populateStandard();
- string ret = CodeFragment::compile(_src, cs).assembly(cs).optimise(_opt).out();
+ stringstream ret;
+ CodeFragment::compile(_src, cs).assembly(cs).optimise(_opt).stream(ret);
for (auto i: cs.treesToKill)
killBigints(i);
- return ret;
+ return ret.str();
}
catch (Exception const& _e)
{
diff --git a/liblll/Compiler.h b/liblll/Compiler.h
index 0fadd278..04aa1e26 100644
--- a/liblll/Compiler.h
+++ b/liblll/Compiler.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Compiler.h
* @author Gav Wood <i@gavwood.com>
diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp
index 91c2452d..9701e16b 100644
--- a/liblll/CompilerState.cpp
+++ b/liblll/CompilerState.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CompilerState.cpp
* @author Gav Wood <i@gavwood.com>
@@ -45,26 +45,31 @@ CodeFragment const& CompilerState::getDef(std::string const& _s)
void CompilerState::populateStandard()
{
static const string s = "{"
+ "(def 'panic () (asm INVALID))"
+ // Alternative macro version of alloc, which is currently implemented in the parser
+ // "(def 'alloc (n) (raw (msize) (when n (pop (mload (+ (msize) (& (- n 1) (~ 0x1f))))))))"
"(def 'allgas (- (gas) 21))"
"(def 'send (to value) (call allgas to value 0 0 0 0))"
"(def 'send (gaslimit to value) (call gaslimit to value 0 0 0 0))"
- "(def 'msg (gaslimit to value data datasize outsize) { (set x outsize) (set y (alloc @32)) (call gaslimit to value data datasize @0 @32) @0 })"
+ // NOTE: in this macro, memory location 0 is set in order to force msize to be at least 32 bytes.
+ "(def 'msg (gaslimit to value data datasize outsize) { [0]:0 [0]:(msize) (call gaslimit to value data datasize @0 outsize) @0 })"
"(def 'msg (gaslimit to value data datasize) { (call gaslimit to value data datasize 0 32) @0 })"
"(def 'msg (gaslimit to value data) { [0]:data (msg gaslimit to value 0 32) })"
"(def 'msg (to value data) { [0]:data (msg allgas to value 0 32) })"
"(def 'msg (to data) { [0]:data (msg allgas to 0 0 32) })"
- "(def 'create (value code) { [0]:(msize) (create value @0 (lll code @0)) })"
- "(def 'create (code) { [0]:(msize) (create 0 @0 (lll code @0)) })"
+ // NOTE: in the create macros, memory location 0 is set in order to force msize to be at least 32 bytes.
+ "(def 'create (value code) { [0]:0 [0]:(msize) (create value @0 (lll code @0)) })"
+ "(def 'create (code) { [0]:0 [0]:(msize) (create 0 @0 (lll code @0)) })"
+ "(def 'sha3 (loc len) (keccak256 loc len))"
"(def 'sha3 (val) { [0]:val (sha3 0 32) })"
"(def 'sha3pair (a b) { [0]:a [32]:b (sha3 0 64) })"
"(def 'sha3trip (a b c) { [0]:a [32]:b [64]:c (sha3 0 96) })"
- "(def 'keccak256 (loc len) (sha3 loc len))"
"(def 'return (val) { [0]:val (return 0 32) })"
"(def 'returnlll (code) (return 0 (lll code 0)) )"
"(def 'makeperm (name pos) { (def name (sload pos)) (def name (v) (sstore pos v)) } )"
"(def 'permcount 0)"
"(def 'perm (name) { (makeperm name permcount) (def 'permcount (+ permcount 1)) } )"
- "(def 'ecrecover (r s v hash) { [0] r [32] s [64] v [96] hash (msg allgas 1 0 0 128) })"
+ "(def 'ecrecover (hash v r s) { [0] hash [32] v [64] r [96] s (msg allgas 1 0 0 128) })"
"(def 'sha256 (data datasize) (msg allgas 2 0 data datasize))"
"(def 'ripemd160 (data datasize) (msg allgas 3 0 data datasize))"
"(def 'sha256 (val) { [0]:val (sha256 0 32) })"
@@ -73,6 +78,9 @@ void CompilerState::populateStandard()
"(def 'szabo 1000000000000)"
"(def 'finney 1000000000000000)"
"(def 'ether 1000000000000000000)"
+ // these could be replaced by native instructions once supported by EVM
+ "(def 'shl (val shift) (mul val (exp 2 shift)))"
+ "(def 'shr (val shift) (div val (exp 2 shift)))"
"}";
CodeFragment::compile(s, *this);
}
diff --git a/liblll/CompilerState.h b/liblll/CompilerState.h
index bfe56f92..c29d3b7d 100644
--- a/liblll/CompilerState.h
+++ b/liblll/CompilerState.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file CompilerState.h
* @author Gav Wood <i@gavwood.com>
diff --git a/liblll/Exceptions.h b/liblll/Exceptions.h
index aa4bf4e8..e8ca99db 100644
--- a/liblll/Exceptions.h
+++ b/liblll/Exceptions.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Exceptions.h
* @author Gav Wood <i@gavwood.com>
diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp
index ad5e1885..a3962df4 100644
--- a/liblll/Parser.cpp
+++ b/liblll/Parser.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Parser.cpp
* @author Gav Wood <i@gavwood.com>
@@ -109,7 +109,7 @@ void dev::eth::parseTreeLLL(string const& _s, sp::utree& o_out)
qi::rule<it, space_type, sp::utree::list_type()> mstore = '[' > element > ']' > -qi::lit(":") > element;
qi::rule<it, space_type, sp::utree::list_type()> sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element;
qi::rule<it, space_type, sp::utree::list_type()> calldataload = qi::lit("$") > element;
- qi::rule<it, space_type, sp::utree::list_type()> list = '(' > +element > ')';
+ qi::rule<it, space_type, sp::utree::list_type()> list = '(' > *element > ')';
qi::rule<it, space_type, sp::utree()> extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()];
element = atom | list | extra;
diff --git a/liblll/Parser.h b/liblll/Parser.h
index e4542888..694d0cc6 100644
--- a/liblll/Parser.h
+++ b/liblll/Parser.h
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Parser.h
* @author Gav Wood <i@gavwood.com>
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt
index bcc47e5a..2342f0f9 100644
--- a/libsolidity/CMakeLists.txt
+++ b/libsolidity/CMakeLists.txt
@@ -7,10 +7,12 @@ aux_source_directory(formal SRC_LIST)
aux_source_directory(interface SRC_LIST)
aux_source_directory(parsing SRC_LIST)
aux_source_directory(inlineasm SRC_LIST)
+# Until we have a clear separation, libjulia has to be included here
+aux_source_directory(../libjulia SRC_LIST)
set(EXECUTABLE solidity)
-file(GLOB HEADERS "*/*.h")
+file(GLOB HEADERS "*/*.h" "../libjulia/backends/evm/*")
include_directories(BEFORE ..)
add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS})
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp
index 1599b83a..b33c8568 100644
--- a/libsolidity/analysis/DeclarationContainer.cpp
+++ b/libsolidity/analysis/DeclarationContainer.cpp
@@ -42,12 +42,33 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
if (m_invisibleDeclarations.count(*_name))
declarations += m_invisibleDeclarations.at(*_name);
- if (dynamic_cast<FunctionDefinition const*>(&_declaration))
+ if (
+ dynamic_cast<FunctionDefinition const*>(&_declaration) ||
+ dynamic_cast<EventDefinition const*>(&_declaration)
+ )
{
- // check that all other declarations with the same name are functions
+ // check that all other declarations with the same name are functions or a public state variable or events.
+ // And then check that the signatures are different.
for (Declaration const* declaration: declarations)
- if (!dynamic_cast<FunctionDefinition const*>(declaration))
+ {
+ if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(declaration))
+ {
+ if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic())
+ continue;
return declaration;
+ }
+ if (
+ dynamic_cast<FunctionDefinition const*>(&_declaration) &&
+ !dynamic_cast<FunctionDefinition const*>(declaration)
+ )
+ return declaration;
+ if (
+ dynamic_cast<EventDefinition const*>(&_declaration) &&
+ !dynamic_cast<EventDefinition const*>(declaration)
+ )
+ return declaration;
+ // Or, continue.
+ }
}
else if (declarations.size() == 1 && declarations.front() == &_declaration)
return nullptr;
diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp
index 58261144..9a846b31 100644
--- a/libsolidity/analysis/DocStringAnalyser.cpp
+++ b/libsolidity/analysis/DocStringAnalyser.cpp
@@ -23,6 +23,7 @@
#include <libsolidity/analysis/DocStringAnalyser.h>
#include <libsolidity/ast/AST.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/parsing/DocStringParser.h>
using namespace std;
@@ -39,7 +40,7 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
bool DocStringAnalyser::visit(ContractDefinition const& _node)
{
- static const set<string> validTags = set<string>{"author", "title", "dev", "notice", "why3"};
+ static const set<string> validTags = set<string>{"author", "title", "dev", "notice"};
parseDocStrings(_node, _node.annotation(), validTags, "contracts");
return true;
@@ -65,16 +66,6 @@ bool DocStringAnalyser::visit(EventDefinition const& _node)
return true;
}
-bool DocStringAnalyser::visitNode(ASTNode const& _node)
-{
- if (auto node = dynamic_cast<Statement const*>(&_node))
- {
- static const set<string> validTags = {"why3"};
- parseDocStrings(*node, node->annotation(), validTags, "statements");
- }
- return true;
-}
-
void DocStringAnalyser::handleCallable(
CallableDeclaration const& _callable,
Documented const& _node,
@@ -110,7 +101,7 @@ void DocStringAnalyser::parseDocStrings(
DocStringParser parser;
if (_node.documentation() && !_node.documentation()->empty())
{
- if (!parser.parse(*_node.documentation(), m_errors))
+ if (!parser.parse(*_node.documentation(), m_errorReporter))
m_errorOccured = true;
_annotation.docTags = parser.tags();
}
@@ -121,8 +112,6 @@ void DocStringAnalyser::parseDocStrings(
void DocStringAnalyser::appendError(string const& _description)
{
- auto err = make_shared<Error>(Error::Type::DocstringParsingError);
- *err << errinfo_comment(_description);
- m_errors.push_back(err);
m_errorOccured = true;
+ m_errorReporter.docstringParsingError(_description);
}
diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h
index bfc8befc..158b4060 100644
--- a/libsolidity/analysis/DocStringAnalyser.h
+++ b/libsolidity/analysis/DocStringAnalyser.h
@@ -30,6 +30,8 @@ namespace dev
namespace solidity
{
+class ErrorReporter;
+
/**
* Parses and analyses the doc strings.
* Stores the parsing results in the AST annotations and reports errors.
@@ -37,7 +39,7 @@ namespace solidity
class DocStringAnalyser: private ASTConstVisitor
{
public:
- DocStringAnalyser(ErrorList& _errors): m_errors(_errors) {}
+ DocStringAnalyser(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool analyseDocStrings(SourceUnit const& _sourceUnit);
private:
@@ -46,8 +48,6 @@ private:
virtual bool visit(ModifierDefinition const& _modifier) override;
virtual bool visit(EventDefinition const& _event) override;
- virtual bool visitNode(ASTNode const&) override;
-
void handleCallable(
CallableDeclaration const& _callable,
Documented const& _node,
@@ -64,7 +64,7 @@ private:
void appendError(std::string const& _description);
bool m_errorOccured = false;
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
};
}
diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp
index e46868be..a54b8c8d 100644
--- a/libsolidity/analysis/GlobalContext.cpp
+++ b/libsolidity/analysis/GlobalContext.cpp
@@ -39,33 +39,39 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)),
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
make_shared<MagicVariableDeclaration>("suicide",
- make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)),
+ make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("selfdestruct",
- make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)),
+ make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("addmod",
- make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::AddMod)),
+ make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod)),
make_shared<MagicVariableDeclaration>("mulmod",
- make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::MulMod)),
+ make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod)),
make_shared<MagicVariableDeclaration>("sha3",
- make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)),
+ make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)),
make_shared<MagicVariableDeclaration>("keccak256",
- make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)),
+ make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)),
make_shared<MagicVariableDeclaration>("log0",
- make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)),
+ make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)),
make_shared<MagicVariableDeclaration>("log1",
- make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)),
+ make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)),
make_shared<MagicVariableDeclaration>("log2",
- make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::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::Location::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::Location::Log4)),
+ make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)),
make_shared<MagicVariableDeclaration>("sha256",
- make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)),
+ make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true)),
make_shared<MagicVariableDeclaration>("ecrecover",
- make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)),
+ make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover)),
make_shared<MagicVariableDeclaration>("ripemd160",
- make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))})
+ make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true)),
+ make_shared<MagicVariableDeclaration>("assert",
+ make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert)),
+ make_shared<MagicVariableDeclaration>("require",
+ make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require)),
+ make_shared<MagicVariableDeclaration>("revert",
+ make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert))})
{
}
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 2a33a501..aac90311 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -21,9 +21,12 @@
*/
#include <libsolidity/analysis/NameAndTypeResolver.h>
+
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h>
-#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <boost/algorithm/string.hpp>
using namespace std;
@@ -34,9 +37,11 @@ namespace solidity
NameAndTypeResolver::NameAndTypeResolver(
vector<Declaration const*> const& _globals,
- ErrorList& _errors
+ map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
+ ErrorReporter& _errorReporter
) :
- m_errors(_errors)
+ m_scopes(_scopes),
+ m_errorReporter(_errorReporter)
{
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer());
@@ -44,22 +49,16 @@ NameAndTypeResolver::NameAndTypeResolver(
m_scopes[nullptr]->registerDeclaration(*declaration);
}
-bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit)
+bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope)
{
- if (!m_scopes[&_sourceUnit])
- // By importing, it is possible that the container already exists.
- m_scopes[&_sourceUnit].reset(new DeclarationContainer(nullptr, m_scopes[nullptr].get()));
- m_currentScope = m_scopes[&_sourceUnit].get();
-
// The helper registers all declarations in m_scopes as a side-effect of its construction.
try
{
- DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors);
- _sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
+ DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope);
}
catch (FatalError const&)
{
- if (m_errors.empty())
+ if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return false;
}
@@ -76,7 +75,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
string const& path = imp->annotation().absolutePath;
if (!_sourceUnits.count(path))
{
- reportDeclarationError(
+ m_errorReporter.declarationError(
imp->location(),
"Import \"" + path + "\" (referenced as \"" + imp->path() + "\") not found."
);
@@ -91,7 +90,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
auto declarations = scope->second->resolveName(alias.first->name(), false);
if (declarations.empty())
{
- reportDeclarationError(
+ m_errorReporter.declarationError(
imp->location(),
"Declaration \"" +
alias.first->name() +
@@ -109,7 +108,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
ASTString const* name = alias.second ? alias.second.get() : &declaration->name();
if (!target.registerDeclaration(*declaration, name))
{
- reportDeclarationError(
+ m_errorReporter.declarationError(
imp->location(),
"Identifier \"" + *name + "\" already declared."
);
@@ -122,7 +121,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
for (auto const& declaration: nameAndDeclaration.second)
if (!target.registerDeclaration(*declaration, &nameAndDeclaration.first))
{
- reportDeclarationError(
+ m_errorReporter.declarationError(
imp->location(),
"Identifier \"" + nameAndDeclaration.first + "\" already declared."
);
@@ -132,76 +131,18 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
return !error;
}
-bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
+bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode)
{
try
{
- m_currentScope = m_scopes[_contract.scope()].get();
- solAssert(!!m_currentScope, "");
-
- ReferencesResolver resolver(m_errors, *this, nullptr);
- bool success = true;
- for (ASTPointer<InheritanceSpecifier> const& baseContract: _contract.baseContracts())
- if (!resolver.resolve(*baseContract))
- success = false;
-
- m_currentScope = m_scopes[&_contract].get();
-
- if (success)
- {
- linearizeBaseContracts(_contract);
- vector<ContractDefinition const*> properBases(
- ++_contract.annotation().linearizedBaseContracts.begin(),
- _contract.annotation().linearizedBaseContracts.end()
- );
-
- for (ContractDefinition const* base: properBases)
- importInheritedScope(*base);
- }
-
- // these can contain code, only resolve parameters for now
- for (ASTPointer<ASTNode> const& node: _contract.subNodes())
- {
- m_currentScope = m_scopes[m_scopes.count(node.get()) ? node.get() : &_contract].get();
- if (!resolver.resolve(*node))
- success = false;
- }
-
- if (!success)
- return false;
-
- m_currentScope = m_scopes[&_contract].get();
-
- // now resolve references inside the code
- for (ModifierDefinition const* modifier: _contract.functionModifiers())
- {
- m_currentScope = m_scopes[modifier].get();
- ReferencesResolver resolver(m_errors, *this, nullptr, true);
- if (!resolver.resolve(*modifier))
- success = false;
- }
-
- for (FunctionDefinition const* function: _contract.definedFunctions())
- {
- m_currentScope = m_scopes[function].get();
- if (!ReferencesResolver(
- m_errors,
- *this,
- function->returnParameterList().get(),
- true
- ).resolve(*function))
- success = false;
- }
- if (!success)
- return false;
+ return resolveNamesAndTypesInternal(_node, _resolveInsideCode);
}
catch (FatalError const&)
{
- if (m_errors.empty())
+ if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return false;
}
- return true;
}
bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
@@ -213,7 +154,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
}
catch (FatalError const&)
{
- if (m_errors.empty())
+ if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return false;
}
@@ -257,30 +198,124 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert(_declarations.size() > 1, "");
vector<Declaration const*> uniqueFunctions;
- for (auto it = _declarations.begin(); it != _declarations.end(); ++it)
+ for (Declaration const* declaration: _declarations)
{
- solAssert(*it, "");
- // the declaration is functionDefinition while declarations > 1
- FunctionDefinition const& functionDefinition = dynamic_cast<FunctionDefinition const&>(**it);
- FunctionType functionType(functionDefinition);
- for (auto parameter: functionType.parameterTypes() + functionType.returnParameterTypes())
+ solAssert(declaration, "");
+ // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
+ solAssert(
+ dynamic_cast<FunctionDefinition const*>(declaration) ||
+ dynamic_cast<EventDefinition const*>(declaration) ||
+ dynamic_cast<VariableDeclaration const*>(declaration),
+ "Found overloading involving something not a function or a variable."
+ );
+
+ FunctionTypePointer functionType { declaration->functionType(false) };
+ if (!functionType)
+ functionType = declaration->functionType(true);
+ solAssert(functionType, "Failed to determine the function type of the overloaded.");
+
+ for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
if (!parameter)
- reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context");
+ m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
if (uniqueFunctions.end() == find_if(
uniqueFunctions.begin(),
uniqueFunctions.end(),
[&](Declaration const* d)
{
- FunctionType newFunctionType(dynamic_cast<FunctionDefinition const&>(*d));
- return functionType.hasEqualArgumentTypes(newFunctionType);
+ shared_ptr<FunctionType const> newFunctionType { d->functionType(false) };
+ if (!newFunctionType)
+ newFunctionType = d->functionType(true);
+ return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType);
}
))
- uniqueFunctions.push_back(*it);
+ uniqueFunctions.push_back(declaration);
}
return uniqueFunctions;
}
+void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
+{
+ for (auto const& instruction: c_instructions)
+ {
+ string const instructionName{boost::algorithm::to_lower_copy(instruction.first)};
+ auto declarations = nameFromCurrentScope(instructionName);
+ for (Declaration const* const declaration: declarations)
+ {
+ solAssert(!!declaration, "");
+ if (dynamic_cast<MagicVariableDeclaration const* const>(declaration))
+ // Don't warn the user for what the user did not.
+ continue;
+ m_errorReporter.warning(
+ declaration->location(),
+ "Variable is shadowed in inline assembly by an instruction of the same name"
+ );
+ }
+ }
+}
+
+bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode)
+{
+ if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node))
+ {
+ bool success = true;
+ m_currentScope = m_scopes[contract->scope()].get();
+ solAssert(!!m_currentScope, "");
+
+ for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
+ if (!resolveNamesAndTypes(*baseContract, true))
+ success = false;
+
+ m_currentScope = m_scopes[contract].get();
+
+ if (success)
+ {
+ linearizeBaseContracts(*contract);
+ vector<ContractDefinition const*> properBases(
+ ++contract->annotation().linearizedBaseContracts.begin(),
+ contract->annotation().linearizedBaseContracts.end()
+ );
+
+ for (ContractDefinition const* base: properBases)
+ importInheritedScope(*base);
+ }
+
+ // these can contain code, only resolve parameters for now
+ for (ASTPointer<ASTNode> const& node: contract->subNodes())
+ {
+ m_currentScope = m_scopes[contract].get();
+ if (!resolveNamesAndTypes(*node, false))
+ {
+ success = false;
+ break;
+ }
+ }
+
+ if (!success)
+ return false;
+
+ if (!_resolveInsideCode)
+ return success;
+
+ m_currentScope = m_scopes[contract].get();
+
+ // now resolve references inside the code
+ for (ASTPointer<ASTNode> const& node: contract->subNodes())
+ {
+ m_currentScope = m_scopes[contract].get();
+ if (!resolveNamesAndTypes(*node, true))
+ success = false;
+ }
+ return success;
+ }
+ else
+ {
+ if (m_scopes.count(&_node))
+ m_currentScope = m_scopes[&_node].get();
+ return ReferencesResolver(m_errorReporter, *this, _resolveInsideCode).resolve(_node);
+ }
+}
+
void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
{
auto iterator = m_scopes.find(&_base);
@@ -289,7 +324,38 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
for (auto const& declaration: nameAndDeclaration.second)
// Import if it was declared in the base, is not the constructor and is visible in derived classes
if (declaration->scope() == &_base && declaration->isVisibleInDerivedContracts())
- m_currentScope->registerDeclaration(*declaration);
+ if (!m_currentScope->registerDeclaration(*declaration))
+ {
+ SourceLocation firstDeclarationLocation;
+ SourceLocation secondDeclarationLocation;
+ Declaration const* conflictingDeclaration = m_currentScope->conflictingDeclaration(*declaration);
+ solAssert(conflictingDeclaration, "");
+
+ // Usual shadowing is not an error
+ if (dynamic_cast<VariableDeclaration const*>(declaration) && dynamic_cast<VariableDeclaration const*>(conflictingDeclaration))
+ continue;
+
+ // Usual shadowing is not an error
+ if (dynamic_cast<ModifierDefinition const*>(declaration) && dynamic_cast<ModifierDefinition const*>(conflictingDeclaration))
+ continue;
+
+ if (declaration->location().start < conflictingDeclaration->location().start)
+ {
+ firstDeclarationLocation = declaration->location();
+ secondDeclarationLocation = conflictingDeclaration->location();
+ }
+ else
+ {
+ firstDeclarationLocation = conflictingDeclaration->location();
+ secondDeclarationLocation = declaration->location();
+ }
+
+ m_errorReporter.declarationError(
+ secondDeclarationLocation,
+ SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
+ "Identifier already declared."
+ );
+ }
}
void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
@@ -302,19 +368,19 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
UserDefinedTypeName const& baseName = baseSpecifier->name();
auto base = dynamic_cast<ContractDefinition const*>(baseName.annotation().referencedDeclaration);
if (!base)
- reportFatalTypeError(baseName.createTypeError("Contract expected."));
+ m_errorReporter.fatalTypeError(baseName.location(), "Contract expected.");
// "push_front" has the effect that bases mentioned later can overwrite members of bases
// mentioned earlier
input.back().push_front(base);
vector<ContractDefinition const*> const& basesBases = base->annotation().linearizedBaseContracts;
if (basesBases.empty())
- reportFatalTypeError(baseName.createTypeError("Definition of base has to precede definition of derived contract"));
+ m_errorReporter.fatalTypeError(baseName.location(), "Definition of base has to precede definition of derived contract");
input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end()));
}
input.back().push_front(&_contract);
vector<ContractDefinition const*> result = cThreeMerge(input);
if (result.empty())
- reportFatalTypeError(_contract.createTypeError("Linearization of inheritance graph impossible"));
+ m_errorReporter.fatalTypeError(_contract.location(), "Linearization of inheritance graph impossible");
_contract.annotation().linearizedBaseContracts = result;
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
}
@@ -370,63 +436,33 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
return result;
}
-void NameAndTypeResolver::reportDeclarationError(
- SourceLocation _sourceLoction,
- string const& _description,
- SourceLocation _secondarySourceLocation,
- string const& _secondaryDescription
-)
-{
- auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error?
- *err <<
- errinfo_sourceLocation(_sourceLoction) <<
- errinfo_comment(_description) <<
- errinfo_secondarySourceLocation(
- SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation)
- );
-
- m_errors.push_back(err);
-}
-
-void NameAndTypeResolver::reportDeclarationError(SourceLocation _sourceLocation, string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error?
- *err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description);
-
- m_errors.push_back(err);
-}
-
-void NameAndTypeResolver::reportFatalDeclarationError(
- SourceLocation _sourceLocation,
- string const& _description
-)
-{
- reportDeclarationError(_sourceLocation, _description);
- BOOST_THROW_EXCEPTION(FatalError());
-}
-
-void NameAndTypeResolver::reportTypeError(Error const& _e)
-{
- m_errors.push_back(make_shared<Error>(_e));
-}
-
-void NameAndTypeResolver::reportFatalTypeError(Error const& _e)
-{
- reportTypeError(_e);
- BOOST_THROW_EXCEPTION(FatalError());
-}
-
DeclarationRegistrationHelper::DeclarationRegistrationHelper(
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
- ErrorList& _errors
+ ErrorReporter& _errorReporter,
+ ASTNode const* _currentScope
):
m_scopes(_scopes),
- m_currentScope(&_astRoot),
- m_errors(_errors)
+ m_currentScope(_currentScope),
+ m_errorReporter(_errorReporter)
{
- solAssert(!!m_scopes.at(m_currentScope), "");
_astRoot.accept(*this);
+ solAssert(m_currentScope == _currentScope, "Scopes not correctly closed.");
+}
+
+bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit)
+{
+ if (!m_scopes[&_sourceUnit])
+ // By importing, it is possible that the container already exists.
+ m_scopes[&_sourceUnit].reset(new DeclarationContainer(m_currentScope, m_scopes[m_currentScope].get()));
+ m_currentScope = &_sourceUnit;
+ return true;
+}
+
+void DeclarationRegistrationHelper::endVisit(SourceUnit& _sourceUnit)
+{
+ _sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
+ closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(ImportDirective& _import)
@@ -547,12 +583,13 @@ void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declara
void DeclarationRegistrationHelper::closeCurrentScope()
{
- solAssert(m_currentScope, "Closed non-existing scope.");
+ solAssert(m_currentScope && m_scopes.count(m_currentScope), "Closed non-existing scope.");
m_currentScope = m_scopes[m_currentScope]->enclosingNode();
}
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope)
{
+ solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope.");
if (!m_scopes[m_currentScope]->registerDeclaration(_declaration, nullptr, !_declaration.isVisibleInContract()))
{
SourceLocation firstDeclarationLocation;
@@ -571,11 +608,10 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
secondDeclarationLocation = _declaration.location();
}
- declarationError(
+ m_errorReporter.declarationError(
secondDeclarationLocation,
- "Identifier already declared.",
- firstDeclarationLocation,
- "The previous declaration is here:"
+ SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
+ "Identifier already declared."
);
}
@@ -603,40 +639,5 @@ string DeclarationRegistrationHelper::currentCanonicalName() const
return ret;
}
-void DeclarationRegistrationHelper::declarationError(
- SourceLocation _sourceLocation,
- string const& _description,
- SourceLocation _secondarySourceLocation,
- string const& _secondaryDescription
-)
-{
- auto err = make_shared<Error>(Error::Type::DeclarationError);
- *err <<
- errinfo_sourceLocation(_sourceLocation) <<
- errinfo_comment(_description) <<
- errinfo_secondarySourceLocation(
- SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation)
- );
-
- m_errors.push_back(err);
-}
-
-void DeclarationRegistrationHelper::declarationError(SourceLocation _sourceLocation, string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::DeclarationError);
- *err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description);
-
- m_errors.push_back(err);
-}
-
-void DeclarationRegistrationHelper::fatalDeclarationError(
- SourceLocation _sourceLocation,
- string const& _description
-)
-{
- declarationError(_sourceLocation, _description);
- BOOST_THROW_EXCEPTION(FatalError());
-}
-
}
}
diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h
index 68c3ffa1..84628778 100644
--- a/libsolidity/analysis/NameAndTypeResolver.h
+++ b/libsolidity/analysis/NameAndTypeResolver.h
@@ -35,6 +35,8 @@ namespace dev
namespace solidity
{
+class ErrorReporter;
+
/**
* Resolves name references, typenames and sets the (explicitly given) types for all variable
* declarations.
@@ -42,15 +44,27 @@ namespace solidity
class NameAndTypeResolver: private boost::noncopyable
{
public:
- NameAndTypeResolver(std::vector<Declaration const*> const& _globals, ErrorList& _errors);
- /// Registers all declarations found in the source unit.
+ /// Creates the resolver with the given declarations added to the global scope.
+ /// @param _scopes mapping of scopes to be used (usually default constructed), these
+ /// are filled during the lifetime of this object.
+ NameAndTypeResolver(
+ std::vector<Declaration const*> const& _globals,
+ std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
+ ErrorReporter& _errorReporter
+ );
+ /// Registers all declarations found in the AST node, usually a source unit.
/// @returns false in case of error.
- bool registerDeclarations(SourceUnit& _sourceUnit);
+ /// @param _currentScope should be nullptr but can be used to inject new declarations into
+ /// existing scopes, used by the snippets feature.
+ bool registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope = nullptr);
/// Applies the effect of import directives.
bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits);
- /// Resolves all names and types referenced from the given contract.
+ /// Resolves all names and types referenced from the given AST Node.
+ /// This is usually only called at the contract level, but with a bit of care, it can also
+ /// be called at deeper levels.
+ /// @param _resolveInsideCode if false, does not descend into nodes that contain code.
/// @returns false in case of error.
- bool resolveNamesAndTypes(ContractDefinition& _contract);
+ bool resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode = true);
/// Updates the given global declaration (used for "this"). Not to be used with declarations
/// that create their own scope.
/// @returns false in case of error.
@@ -76,8 +90,12 @@ public:
std::vector<Declaration const*> const& _declarations
);
+ /// Generate and store warnings about variables that are named like instructions.
+ void warnVariablesNamedLikeInstructions();
+
private:
- void reset();
+ /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors.
+ bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true);
/// Imports all members declared directly in the given contract (i.e. does not import inherited members)
/// into the current scope if they are not present already.
@@ -90,32 +108,14 @@ private:
template <class _T>
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge);
- // creates the Declaration error and adds it in the errors list
- void reportDeclarationError(
- SourceLocation _sourceLoction,
- std::string const& _description,
- SourceLocation _secondarySourceLocation,
- std::string const& _secondaryDescription
- );
- // creates the Declaration error and adds it in the errors list
- void reportDeclarationError(SourceLocation _sourceLocation, std::string const& _description);
- // creates the Declaration error and adds it in the errors list and throws FatalError
- void reportFatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description);
-
- // creates the Declaration error and adds it in the errors list
- void reportTypeError(Error const& _e);
- // creates the Declaration error and adds it in the errors list and throws FatalError
- void reportFatalTypeError(Error const& _e);
-
-
/// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration,
/// where nullptr denotes the global scope. Note that structs are not scope since they do
/// not contain code.
/// Aliases (for example `import "x" as y;`) create multiple pointers to the same scope.
- std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes;
+ std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
DeclarationContainer* m_currentScope = nullptr;
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
};
/**
@@ -125,13 +125,20 @@ private:
class DeclarationRegistrationHelper: private ASTVisitor
{
public:
+ /// Registers declarations in their scopes and creates new scopes as a side-effect
+ /// of construction.
+ /// @param _currentScope should be nullptr if we start at SourceUnit, but can be different
+ /// to inject new declarations into an existing scope, used by snippets.
DeclarationRegistrationHelper(
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
- ErrorList& _errors
+ ErrorReporter& _errorReporter,
+ ASTNode const* _currentScope = nullptr
);
private:
+ bool visit(SourceUnit& _sourceUnit) override;
+ void endVisit(SourceUnit& _sourceUnit) override;
bool visit(ImportDirective& _declaration) override;
bool visit(ContractDefinition& _contract) override;
void endVisit(ContractDefinition& _contract) override;
@@ -155,23 +162,11 @@ private:
/// @returns the canonical name of the current scope.
std::string currentCanonicalName() const;
- // creates the Declaration error and adds it in the errors list
- void declarationError(
- SourceLocation _sourceLocation,
- std::string const& _description,
- SourceLocation _secondarySourceLocation,
- std::string const& _secondaryDescription
- );
-
- // creates the Declaration error and adds it in the errors list
- void declarationError(SourceLocation _sourceLocation, std::string const& _description);
- // creates the Declaration error and adds it in the errors list and throws FatalError
- void fatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description);
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
};
}
diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp
new file mode 100644
index 00000000..fbc72e52
--- /dev/null
+++ b/libsolidity/analysis/PostTypeChecker.cpp
@@ -0,0 +1,106 @@
+/*
+ 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/analysis/PostTypeChecker.h>
+#include <libsolidity/ast/AST.h>
+#include <libsolidity/analysis/SemVerHandler.h>
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libsolidity/interface/Version.h>
+
+#include <boost/range/adaptor/map.hpp>
+
+#include <memory>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+
+bool PostTypeChecker::check(ASTNode const& _astRoot)
+{
+ _astRoot.accept(*this);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
+}
+
+bool PostTypeChecker::visit(ContractDefinition const&)
+{
+ solAssert(!m_currentConstVariable, "");
+ solAssert(m_constVariableDependencies.empty(), "");
+ return true;
+}
+
+void PostTypeChecker::endVisit(ContractDefinition const&)
+{
+ solAssert(!m_currentConstVariable, "");
+ for (auto declaration: m_constVariables)
+ if (auto identifier = findCycle(declaration))
+ m_errorReporter.typeError(
+ declaration->location(),
+ "The value of the constant " + declaration->name() +
+ " has a cyclic dependency via " + identifier->name() + "."
+ );
+
+ m_constVariables.clear();
+ m_constVariableDependencies.clear();
+}
+
+bool PostTypeChecker::visit(VariableDeclaration const& _variable)
+{
+ solAssert(!m_currentConstVariable, "");
+ if (_variable.isConstant())
+ {
+ m_currentConstVariable = &_variable;
+ m_constVariables.push_back(&_variable);
+ }
+ return true;
+}
+
+void PostTypeChecker::endVisit(VariableDeclaration const& _variable)
+{
+ if (_variable.isConstant())
+ {
+ solAssert(m_currentConstVariable == &_variable, "");
+ m_currentConstVariable = nullptr;
+ }
+}
+
+bool PostTypeChecker::visit(Identifier const& _identifier)
+{
+ if (m_currentConstVariable)
+ if (auto var = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
+ if (var->isConstant())
+ m_constVariableDependencies[m_currentConstVariable].insert(var);
+ return true;
+}
+
+VariableDeclaration const* PostTypeChecker::findCycle(
+ VariableDeclaration const* _startingFrom,
+ set<VariableDeclaration const*> const& _seen
+)
+{
+ if (_seen.count(_startingFrom))
+ return _startingFrom;
+ else if (m_constVariableDependencies.count(_startingFrom))
+ {
+ set<VariableDeclaration const*> seen(_seen);
+ seen.insert(_startingFrom);
+ for (auto v: m_constVariableDependencies[_startingFrom])
+ if (findCycle(v, seen))
+ return v;
+ }
+ return nullptr;
+}
diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h
new file mode 100644
index 00000000..dbdf50e0
--- /dev/null
+++ b/libsolidity/analysis/PostTypeChecker.h
@@ -0,0 +1,71 @@
+/*
+ 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/analysis/TypeChecker.h>
+#include <libsolidity/ast/Types.h>
+#include <libsolidity/ast/ASTAnnotations.h>
+#include <libsolidity/ast/ASTForward.h>
+#include <libsolidity/ast/ASTVisitor.h>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ErrorReporter;
+
+/**
+ * This module performs analyses on the AST that are done after type checking and assignments of types:
+ * - whether there are circular references in constant state variables
+ * @TODO factor out each use-case into an individual class (but do the traversal only once)
+ */
+class PostTypeChecker: private ASTConstVisitor
+{
+public:
+ /// @param _errors the reference to the list of errors and warnings to add them found during type checking.
+ PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
+
+ bool check(ASTNode const& _astRoot);
+
+private:
+ /// Adds a new error to the list of errors.
+ void typeError(SourceLocation const& _location, std::string const& _description);
+
+ virtual bool visit(ContractDefinition const& _contract) override;
+ virtual void endVisit(ContractDefinition const& _contract) override;
+
+ virtual bool visit(VariableDeclaration const& _declaration) override;
+ virtual void endVisit(VariableDeclaration const& _declaration) override;
+
+ virtual bool visit(Identifier const& _identifier) override;
+
+ VariableDeclaration const* findCycle(
+ VariableDeclaration const* _startingFrom,
+ std::set<VariableDeclaration const*> const& _seen = std::set<VariableDeclaration const*>{}
+ );
+
+ ErrorReporter& m_errorReporter;
+
+ VariableDeclaration const* m_currentConstVariable = nullptr;
+ std::vector<VariableDeclaration const*> m_constVariables; ///< Required for determinism.
+ std::map<VariableDeclaration const*, std::set<VariableDeclaration const*>> m_constVariableDependencies;
+};
+
+}
+}
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index 66bf1d0e..8f07d43a 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -25,8 +25,12 @@
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
-#include <libsolidity/inlineasm/AsmCodeGen.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <boost/algorithm/string.hpp>
using namespace std;
using namespace dev;
@@ -35,14 +39,7 @@ using namespace dev::solidity;
bool ReferencesResolver::resolve(ASTNode const& _root)
{
- try
- {
- _root.accept(*this);
- }
- catch (FatalError const&)
- {
- solAssert(m_errorOccurred, "");
- }
+ _root.accept(*this);
return !m_errorOccurred;
}
@@ -65,6 +62,30 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
return true;
}
+bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
+{
+ m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
+ return true;
+}
+
+void ReferencesResolver::endVisit(FunctionDefinition const&)
+{
+ solAssert(!m_returnParameters.empty(), "");
+ m_returnParameters.pop_back();
+}
+
+bool ReferencesResolver::visit(ModifierDefinition const&)
+{
+ m_returnParameters.push_back(nullptr);
+ return true;
+}
+
+void ReferencesResolver::endVisit(ModifierDefinition const&)
+{
+ solAssert(!m_returnParameters.empty(), "");
+ m_returnParameters.pop_back();
+}
+
void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
{
Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath());
@@ -87,12 +108,11 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
{
switch (_typeName.visibility())
{
- case VariableDeclaration::Visibility::Default:
case VariableDeclaration::Visibility::Internal:
case VariableDeclaration::Visibility::External:
break;
default:
- typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
+ fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
}
if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External)
@@ -131,6 +151,8 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
auto const* lengthType = dynamic_cast<RationalNumberType const*>(length->annotation().type.get());
if (!lengthType || lengthType->isFractional())
fatalTypeError(length->location(), "Invalid array length, expected integer literal.");
+ 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));
}
@@ -140,27 +162,56 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
{
- // We need to perform a full code generation pass here as inline assembly does not distinguish
- // reference resolution and code generation.
+ m_resolver.warnVariablesNamedLikeInstructions();
+
// Errors created in this stage are completely ignored because we do not yet know
// the type and size of external identifiers, which would result in false errors.
- ErrorList errorsIgnored;
- assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored);
- codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) {
+ // The only purpose of this step is to fill the inline assembly annotation with
+ // external references.
+ ErrorList errors;
+ ErrorReporter errorsIgnored(errors);
+ julia::ExternalIdentifierAccess::Resolver resolver =
+ [&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool _crossesFunctionBoundary) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name);
+ bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot");
+ bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset");
+ if (isSlot || isOffset)
+ {
+ // special mode to access storage variables
+ if (!declarations.empty())
+ // the special identifier exists itself, we should not allow that.
+ return size_t(-1);
+ string realName = _identifier.name.substr(0, _identifier.name.size() - (
+ isSlot ?
+ string("_slot").size() :
+ string("_offset").size()
+ ));
+ declarations = m_resolver.nameFromCurrentScope(realName);
+ }
if (declarations.size() != 1)
- return false;
- _inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front();
- // At this stage we neither know the code to generate nor the stack size of the identifier,
- // so we do not modify assembly.
- return true;
- });
+ return size_t(-1);
+ if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
+ if (var->isLocalVariable() && _crossesFunctionBoundary)
+ {
+ declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function.");
+ return size_t(-1);
+ }
+ _inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
+ _inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
+ _inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
+ return size_t(1);
+ };
+
+ // Will be re-generated later with correct information
+ assembly::AsmAnalysisInfo analysisInfo;
+ assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations());
return false;
}
bool ReferencesResolver::visit(Return const& _return)
{
- _return.annotation().functionReturnParameters = m_returnParameters;
+ solAssert(!m_returnParameters.empty(), "");
+ _return.annotation().functionReturnParameters = m_returnParameters.back();
return true;
}
@@ -238,7 +289,20 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
typeLoc = DataLocation::Memory;
}
else if (varLoc == Location::Default)
- typeLoc = _variable.isCallableParameter() ? DataLocation::Memory : DataLocation::Storage;
+ {
+ if (_variable.isCallableParameter())
+ typeLoc = DataLocation::Memory;
+ else
+ {
+ typeLoc = DataLocation::Storage;
+ if (_variable.isLocalVariable())
+ m_errorReporter.warning(
+ _variable.location(),
+ "Variable is declared as a storage pointer. "
+ "Use an explicit \"storage\" keyword to silence this warning."
+ );
+ }
+ }
else
typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage;
isPointer = !_variable.isStateVariable();
@@ -262,29 +326,25 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
{
- auto err = make_shared<Error>(Error::Type::TypeError);
- *err << errinfo_sourceLocation(_location) << errinfo_comment(_description);
m_errorOccurred = true;
- m_errors.push_back(err);
+ m_errorReporter.typeError(_location, _description);
}
void ReferencesResolver::fatalTypeError(SourceLocation const& _location, string const& _description)
{
- typeError(_location, _description);
- BOOST_THROW_EXCEPTION(FatalError());
+ m_errorOccurred = true;
+ m_errorReporter.fatalTypeError(_location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
{
- auto err = make_shared<Error>(Error::Type::DeclarationError);
- *err << errinfo_sourceLocation(_location) << errinfo_comment(_description);
m_errorOccurred = true;
- m_errors.push_back(err);
+ m_errorReporter.declarationError(_location, _description);
}
void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
- declarationError(_location, _description);
- BOOST_THROW_EXCEPTION(FatalError());
+ m_errorOccurred = true;
+ m_errorReporter.fatalDeclarationError(_location, _description);
}
diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h
index caa3a78f..fef2e73f 100644
--- a/libsolidity/analysis/ReferencesResolver.h
+++ b/libsolidity/analysis/ReferencesResolver.h
@@ -33,6 +33,7 @@ namespace dev
namespace solidity
{
+class ErrorReporter;
class NameAndTypeResolver;
/**
@@ -43,24 +44,26 @@ class ReferencesResolver: private ASTConstVisitor
{
public:
ReferencesResolver(
- ErrorList& _errors,
+ ErrorReporter& _errorReporter,
NameAndTypeResolver& _resolver,
- ParameterList const* _returnParameters,
bool _resolveInsideCode = false
):
- m_errors(_errors),
+ m_errorReporter(_errorReporter),
m_resolver(_resolver),
- m_returnParameters(_returnParameters),
m_resolveInsideCode(_resolveInsideCode)
{}
- /// @returns true if no errors during resolving
+ /// @returns true if no errors during resolving and throws exceptions on fatal errors.
bool resolve(ASTNode const& _root);
private:
virtual bool visit(Block const&) override { return m_resolveInsideCode; }
virtual bool visit(Identifier const& _identifier) override;
virtual bool visit(ElementaryTypeName const& _typeName) override;
+ virtual bool visit(FunctionDefinition const& _functionDefinition) override;
+ virtual void endVisit(FunctionDefinition const& _functionDefinition) override;
+ virtual bool visit(ModifierDefinition const& _modifierDefinition) override;
+ virtual void endVisit(ModifierDefinition const& _modifierDefinition) override;
virtual void endVisit(UserDefinedTypeName const& _typeName) override;
virtual void endVisit(FunctionTypeName const& _typeName) override;
virtual void endVisit(Mapping const& _typeName) override;
@@ -72,18 +75,19 @@ private:
/// Adds a new error to the list of errors.
void typeError(SourceLocation const& _location, std::string const& _description);
- /// Adds a new error to the list of errors and throws to abort type checking.
+ /// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalTypeError(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors.
- void declarationError(const SourceLocation& _location, std::string const& _description);
+ void declarationError(SourceLocation const& _location, std::string const& _description);
- /// Adds a new error to the list of errors and throws to abort type checking.
- void fatalDeclarationError(const SourceLocation& _location, std::string const& _description);
+ /// Adds a new error to the list of errors and throws to abort reference resolving.
+ void fatalDeclarationError(SourceLocation const& _location, std::string const& _description);
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
NameAndTypeResolver& m_resolver;
- ParameterList const* m_returnParameters;
+ /// Stack of return parameters.
+ std::vector<ParameterList const*> m_returnParameters;
bool const m_resolveInsideCode;
bool m_errorOccurred = false;
};
diff --git a/libsolidity/analysis/SemVerHandler.h b/libsolidity/analysis/SemVerHandler.h
index fae0a764..76b70c5b 100644
--- a/libsolidity/analysis/SemVerHandler.h
+++ b/libsolidity/analysis/SemVerHandler.h
@@ -34,6 +34,9 @@ class SemVerError: dev::Exception
{
};
+#undef major
+#undef minor
+
struct SemVerVersion
{
unsigned numbers[3];
diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp
new file mode 100644
index 00000000..b1b31163
--- /dev/null
+++ b/libsolidity/analysis/StaticAnalyzer.cpp
@@ -0,0 +1,153 @@
+/*
+ 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 Federico Bond <federicobond@gmail.com>
+ * @date 2016
+ * Static analyzer and checker.
+ */
+
+#include <libsolidity/analysis/StaticAnalyzer.h>
+#include <libsolidity/ast/AST.h>
+#include <libsolidity/interface/ErrorReporter.h>
+#include <memory>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit)
+{
+ _sourceUnit.accept(*this);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
+}
+
+bool StaticAnalyzer::visit(ContractDefinition const& _contract)
+{
+ m_library = _contract.isLibrary();
+ return true;
+}
+
+void StaticAnalyzer::endVisit(ContractDefinition const&)
+{
+ m_library = false;
+}
+
+bool StaticAnalyzer::visit(FunctionDefinition const& _function)
+{
+ if (_function.isImplemented())
+ m_currentFunction = &_function;
+ else
+ solAssert(!m_currentFunction, "");
+ solAssert(m_localVarUseCount.empty(), "");
+ m_nonPayablePublic = _function.isPublic() && !_function.isPayable();
+ return true;
+}
+
+void StaticAnalyzer::endVisit(FunctionDefinition const&)
+{
+ m_currentFunction = nullptr;
+ m_nonPayablePublic = false;
+ for (auto const& var: m_localVarUseCount)
+ if (var.second == 0)
+ m_errorReporter.warning(var.first->location(), "Unused local variable");
+ m_localVarUseCount.clear();
+}
+
+bool StaticAnalyzer::visit(Identifier const& _identifier)
+{
+ if (m_currentFunction)
+ if (auto var = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
+ {
+ solAssert(!var->name().empty(), "");
+ if (var->isLocalVariable())
+ m_localVarUseCount[var] += 1;
+ }
+ return true;
+}
+
+bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
+{
+ if (m_currentFunction)
+ {
+ solAssert(_variable.isLocalVariable(), "");
+ if (_variable.name() != "")
+ // This is not a no-op, the entry might pre-exist.
+ m_localVarUseCount[&_variable] += 0;
+ }
+ return true;
+}
+
+bool StaticAnalyzer::visit(Return const& _return)
+{
+ // If the return has an expression, it counts as
+ // a "use" of the return parameters.
+ if (m_currentFunction && _return.expression())
+ for (auto const& var: m_currentFunction->returnParameters())
+ if (!var->name().empty())
+ m_localVarUseCount[var.get()] += 1;
+ return true;
+}
+
+bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
+{
+ if (_statement.expression().annotation().isPure)
+ m_errorReporter.warning(
+ _statement.location(),
+ "Statement has no effect."
+ );
+
+ return true;
+}
+
+bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
+{
+ if (m_nonPayablePublic && !m_library)
+ if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
+ if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "value")
+ m_errorReporter.warning(
+ _memberAccess.location(),
+ "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?"
+ );
+
+ if (_memberAccess.memberName() == "callcode")
+ if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
+ if (type->kind() == FunctionType::Kind::BareCallCode)
+ m_errorReporter.warning(
+ _memberAccess.location(),
+ "\"callcode\" has been deprecated in favour of \"delegatecall\"."
+ );
+
+ return true;
+}
+
+bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
+{
+ if (!m_currentFunction)
+ return true;
+
+ for (auto const& ref: _inlineAssembly.annotation().externalReferences)
+ {
+ if (auto var = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
+ {
+ solAssert(!var->name().empty(), "");
+ if (var->isLocalVariable())
+ m_localVarUseCount[var] += 1;
+ }
+ }
+
+ return true;
+}
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
new file mode 100644
index 00000000..cd6913b5
--- /dev/null
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -0,0 +1,83 @@
+/*
+ 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 Federico Bond <federicobond@gmail.com>
+ * @date 2016
+ * Static analyzer and checker.
+ */
+
+#pragma once
+
+#include <libsolidity/analysis/TypeChecker.h>
+#include <libsolidity/ast/Types.h>
+#include <libsolidity/ast/ASTAnnotations.h>
+#include <libsolidity/ast/ASTForward.h>
+#include <libsolidity/ast/ASTVisitor.h>
+
+namespace dev
+{
+namespace solidity
+{
+
+
+/**
+ * The module that performs static analysis on the AST.
+ * In this context, static analysis is anything that can produce warnings which can help
+ * programmers write cleaner code. For every warning generated eher, it has to be possible to write
+ * equivalent code that does generate the warning.
+ */
+class StaticAnalyzer: private ASTConstVisitor
+{
+public:
+ /// @param _errors the reference to the list of errors and warnings to add them found during static analysis.
+ explicit StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
+
+ /// Performs static analysis on the given source unit and all of its sub-nodes.
+ /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
+ bool analyze(SourceUnit const& _sourceUnit);
+
+private:
+
+ virtual bool visit(ContractDefinition const& _contract) override;
+ virtual void endVisit(ContractDefinition const& _contract) override;
+
+ virtual bool visit(FunctionDefinition const& _function) override;
+ virtual void endVisit(FunctionDefinition const& _function) override;
+
+ virtual bool visit(ExpressionStatement const& _statement) override;
+ virtual bool visit(VariableDeclaration const& _variable) override;
+ virtual bool visit(Identifier const& _identifier) override;
+ virtual bool visit(Return const& _return) override;
+ virtual bool visit(MemberAccess const& _memberAccess) override;
+ virtual bool visit(InlineAssembly const& _inlineAssembly) override;
+
+ ErrorReporter& m_errorReporter;
+
+ /// Flag that indicates whether the current contract definition is a library.
+ bool m_library = false;
+
+ /// Flag that indicates whether a public function does not contain the "payable" modifier.
+ bool m_nonPayablePublic = false;
+
+ /// Number of uses of each (named) local variable in a function, counter is initialized with zero.
+ std::map<VariableDeclaration const*, int> m_localVarUseCount;
+
+ FunctionDefinition const* m_currentFunction = nullptr;
+};
+
+}
+}
diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp
index 0a4943fe..bde0e616 100644
--- a/libsolidity/analysis/SyntaxChecker.cpp
+++ b/libsolidity/analysis/SyntaxChecker.cpp
@@ -19,6 +19,7 @@
#include <memory>
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/SemVerHandler.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Version.h>
using namespace std;
@@ -26,20 +27,10 @@ using namespace dev;
using namespace dev::solidity;
-bool SyntaxChecker::checkSyntax(SourceUnit const& _sourceUnit)
+bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
{
- _sourceUnit.accept(*this);
- return Error::containsOnlyWarnings(m_errors);
-}
-
-void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::SyntaxError);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
-
- m_errors.push_back(err);
+ _astRoot.accept(*this);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool SyntaxChecker::visit(SourceUnit const&)
@@ -64,11 +55,7 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
to_string(recommendedVersion.patch());
string(";\"");
- auto err = make_shared<Error>(Error::Type::Warning);
- *err <<
- errinfo_sourceLocation(_sourceUnit.location()) <<
- errinfo_comment(errorString);
- m_errors.push_back(err);
+ m_errorReporter.warning(_sourceUnit.location(), errorString);
}
}
@@ -77,7 +64,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
solAssert(!_pragma.tokens().empty(), "");
solAssert(_pragma.tokens().size() == _pragma.literals().size(), "");
if (_pragma.tokens()[0] != Token::Identifier || _pragma.literals()[0] != "solidity")
- syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
+ m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
else
{
vector<Token::Value> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
@@ -86,7 +73,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
auto matchExpression = parser.parse();
SemVerVersion currentVersion{string(VersionString)};
if (!matchExpression.matches(currentVersion))
- syntaxError(
+ m_errorReporter.syntaxError(
_pragma.location(),
"Source file requires different compiler version (current compiler is " +
string(VersionString) + " - note that nightly builds are considered to be "
@@ -106,7 +93,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&)
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
{
if (!m_placeholderFound)
- syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
+ m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
m_placeholderFound = false;
}
@@ -136,7 +123,7 @@ bool SyntaxChecker::visit(Continue const& _continueStatement)
{
if (m_inLoopDepth <= 0)
// we're not in a for/while loop, report syntax error
- syntaxError(_continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop.");
+ m_errorReporter.syntaxError(_continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop.");
return true;
}
@@ -144,7 +131,24 @@ bool SyntaxChecker::visit(Break const& _breakStatement)
{
if (m_inLoopDepth <= 0)
// we're not in a for/while loop, report syntax error
- syntaxError(_breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop.");
+ m_errorReporter.syntaxError(_breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop.");
+ return true;
+}
+
+bool SyntaxChecker::visit(Throw const& _throwStatement)
+{
+ m_errorReporter.warning(
+ _throwStatement.location(),
+ "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"."
+ );
+
+ return true;
+}
+
+bool SyntaxChecker::visit(UnaryOperation const& _operation)
+{
+ if (_operation.getOperator() == Token::Add)
+ m_errorReporter.warning(_operation.location(), "Use of unary + is deprecated.");
return true;
}
@@ -154,3 +158,15 @@ bool SyntaxChecker::visit(PlaceholderStatement const&)
return true;
}
+bool SyntaxChecker::visit(FunctionTypeName const& _node)
+{
+ for (auto const& decl: _node.parameterTypeList()->parameters())
+ if (!decl->name().empty())
+ m_errorReporter.warning(decl->location(), "Naming function type parameters is deprecated.");
+
+ for (auto const& decl: _node.returnParameterTypeList()->parameters())
+ if (!decl->name().empty())
+ m_errorReporter.warning(decl->location(), "Naming function type return parameters is deprecated.");
+
+ return true;
+}
diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h
index c24bae09..fb5cc6d7 100644
--- a/libsolidity/analysis/SyntaxChecker.h
+++ b/libsolidity/analysis/SyntaxChecker.h
@@ -32,18 +32,18 @@ namespace solidity
* The module that performs syntax analysis on the AST:
* - whether continue/break is in a for/while loop.
* - whether a modifier contains at least one '_'
+ * - issues deprecation warnings for unary '+'
+ * - issues deprecation warning for throw
*/
class SyntaxChecker: private ASTConstVisitor
{
public:
/// @param _errors the reference to the list of errors and warnings to add them found during type checking.
- SyntaxChecker(ErrorList& _errors): m_errors(_errors) {}
+ SyntaxChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
- bool checkSyntax(SourceUnit const& _sourceUnit);
+ bool checkSyntax(ASTNode const& _astRoot);
private:
- /// Adds a new error to the list of errors.
- void syntaxError(SourceLocation const& _location, std::string const& _description);
virtual bool visit(SourceUnit const& _sourceUnit) override;
virtual void endVisit(SourceUnit const& _sourceUnit) override;
@@ -60,9 +60,15 @@ private:
virtual bool visit(Continue const& _continueStatement) override;
virtual bool visit(Break const& _breakStatement) override;
+ virtual bool visit(Throw const& _throwStatement) override;
+
+ virtual bool visit(UnaryOperation const& _operation) override;
+
virtual bool visit(PlaceholderStatement const& _placeholderStatement) override;
- ErrorList& m_errors;
+ virtual bool visit(FunctionTypeName const& _node) override;
+
+ ErrorReporter& m_errorReporter;
/// Flag that indicates whether a function modifier actually contains '_'.
bool m_placeholderFound = false;
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 7235b57a..7306a36d 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -22,30 +22,33 @@
#include <libsolidity/analysis/TypeChecker.h>
#include <memory>
+#include <boost/algorithm/string/predicate.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/ast/AST.h>
-#include <libevmasm/Assembly.h> // needed for inline assembly
-#include <libsolidity/inlineasm/AsmCodeGen.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/interface/ErrorReporter.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
-bool TypeChecker::checkTypeRequirements(ContractDefinition const& _contract)
+bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
{
try
{
- visit(_contract);
+ _contract.accept(*this);
}
catch (FatalError const&)
{
// We got a fatal error which required to stop further type checking, but we can
// continue normally from here.
- if (m_errors.empty())
+ if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
}
- return Error::containsOnlyWarnings(m_errors);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
}
TypePointer const& TypeChecker::type(Expression const& _expression) const
@@ -64,8 +67,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
{
m_scope = &_contract;
- // We force our own visiting order here.
- //@TODO structs will be visited again below, but it is probably fine.
+ // We force our own visiting order here. The structs have to be excluded below.
+ set<ASTNode const*> visited;
+ for (auto const& s: _contract.definedStructs())
+ visited.insert(s);
ASTNode::listAccept(_contract.definedStructs(), *this);
ASTNode::listAccept(_contract.baseContracts(), *this);
@@ -75,13 +80,14 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
checkContractAbstractConstructors(_contract);
FunctionDefinition const* function = _contract.constructor();
- if (function) {
+ if (function)
+ {
if (!function->returnParameters().empty())
- typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
+ m_errorReporter.typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (function->isDeclaredConst())
- typeError(function->location(), "Constructor cannot be defined as constant.");
+ m_errorReporter.typeError(function->location(), "Constructor cannot be defined as constant.");
if (function->visibility() != FunctionDefinition::Visibility::Public && function->visibility() != FunctionDefinition::Visibility::Internal)
- typeError(function->location(), "Constructor must be public or internal.");
+ m_errorReporter.typeError(function->location(), "Constructor must be public or internal.");
}
FunctionDefinition const* fallbackFunction = nullptr;
@@ -91,28 +97,28 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
{
if (fallbackFunction)
{
- auto err = make_shared<Error>(Error::Type::DeclarationError);
- *err << errinfo_comment("Only one fallback function is allowed.");
- m_errors.push_back(err);
+ m_errorReporter.declarationError(function->location(), "Only one fallback function is allowed.");
}
else
{
fallbackFunction = function;
if (_contract.isLibrary())
- typeError(fallbackFunction->location(), "Libraries cannot have fallback functions.");
+ m_errorReporter.typeError(fallbackFunction->location(), "Libraries cannot have fallback functions.");
if (fallbackFunction->isDeclaredConst())
- typeError(fallbackFunction->location(), "Fallback function cannot be declared constant.");
+ m_errorReporter.typeError(fallbackFunction->location(), "Fallback function cannot be declared constant.");
if (!fallbackFunction->parameters().empty())
- typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters.");
+ m_errorReporter.typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters.");
if (!fallbackFunction->returnParameters().empty())
- typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values.");
+ m_errorReporter.typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values.");
}
}
if (!function->isImplemented())
_contract.annotation().isFullyImplemented = false;
}
- ASTNode::listAccept(_contract.subNodes(), *this);
+ for (auto const& n: _contract.subNodes())
+ if (!visited.count(n.get()))
+ n->accept(*this);
checkContractExternalTypeClashes(_contract);
// check for hash collisions in function signatures
@@ -121,7 +127,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
{
FixedHash<4> const& hash = it.first;
if (hashes.count(hash))
- typeError(
+ m_errorReporter.typeError(
_contract.location(),
string("Function signature hash collision for ") + it.second->externalSignature()
);
@@ -150,12 +156,11 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con
for (; it != functions[_contract.name()].end(); ++it)
ssl.append("Another declaration is here:", (*it)->location());
- auto err = make_shared<Error>(Error(Error::Type::DeclarationError));
- *err <<
- errinfo_sourceLocation(functions[_contract.name()].front()->location()) <<
- errinfo_comment("More than one constructor defined.") <<
- errinfo_secondarySourceLocation(ssl);
- m_errors.push_back(err);
+ m_errorReporter.declarationError(
+ functions[_contract.name()].front()->location(),
+ ssl,
+ "More than one constructor defined."
+ );
}
for (auto const& it: functions)
{
@@ -164,13 +169,14 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con
for (size_t j = i + 1; j < overloads.size(); ++j)
if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j])))
{
- auto err = make_shared<Error>(Error(Error::Type::DeclarationError));
- *err <<
- errinfo_sourceLocation(overloads[j]->location()) <<
- errinfo_comment("Function with same name and arguments defined twice.") <<
- errinfo_secondarySourceLocation(SecondarySourceLocation().append(
- "Other declaration is here:", overloads[i]->location()));
- m_errors.push_back(err);
+ m_errorReporter.declarationError(
+ overloads[j]->location(),
+ SecondarySourceLocation().append(
+ "Other declaration is here:",
+ overloads[i]->location()
+ ),
+ "Function with same name and arguments defined twice."
+ );
}
}
}
@@ -182,13 +188,20 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>;
map<string, vector<FunTypeAndFlag>> functions;
+ bool allBaseConstructorsImplemented = true;
// Search from base to derived
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
for (FunctionDefinition const* function: contract->definedFunctions())
{
// Take constructors out of overload hierarchy
if (function->isConstructor())
+ {
+ if (!function->isImplemented())
+ // Base contract's constructor is not fully implemented, no way to get
+ // out of this.
+ allBaseConstructorsImplemented = false;
continue;
+ }
auto& overloads = functions[function->name()];
FunctionTypePointer funType = make_shared<FunctionType>(*function);
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
@@ -200,12 +213,15 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
else if (it->second)
{
if (!function->isImplemented())
- typeError(function->location(), "Redeclaring an already implemented function as abstract");
+ m_errorReporter.typeError(function->location(), "Redeclaring an already implemented function as abstract");
}
else if (function->isImplemented())
it->second = true;
}
+ if (!allBaseConstructorsImplemented)
+ _contract.annotation().isFullyImplemented = false;
+
// Set to not fully implemented if at least one flag is false.
for (auto const& it: functions)
for (auto const& funAndFlag: it.second)
@@ -269,7 +285,7 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr
continue; // constructors can neither be overridden nor override anything
string const& name = function->name();
if (modifiers.count(name))
- typeError(modifiers[name]->location(), "Override changes function to modifier.");
+ m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier.");
FunctionType functionType(*function);
// function should not change the return type
for (FunctionDefinition const* overriding: functions[name])
@@ -283,7 +299,7 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr
overriding->isPayable() != function->isPayable() ||
overridingType != functionType
)
- typeError(overriding->location(), "Override changes extended function signature.");
+ m_errorReporter.typeError(overriding->location(), "Override changes extended function signature.");
}
functions[name].push_back(function);
}
@@ -294,9 +310,9 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr
if (!override)
override = modifier;
else if (ModifierType(*override) != ModifierType(*modifier))
- typeError(override->location(), "Override changes modifier signature.");
+ m_errorReporter.typeError(override->location(), "Override changes modifier signature.");
if (!functions[name].empty())
- typeError(override->location(), "Override changes modifier to function.");
+ m_errorReporter.typeError(override->location(), "Override changes modifier to function.");
}
}
}
@@ -331,7 +347,7 @@ void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _co
for (size_t i = 0; i < it.second.size(); ++i)
for (size_t j = i + 1; j < it.second.size(); ++j)
if (!it.second[i].second->hasEqualArgumentTypes(*it.second[j].second))
- typeError(
+ m_errorReporter.typeError(
it.second[j].first->location(),
"Function overload clash during conversion to external types for arguments."
);
@@ -341,11 +357,40 @@ void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract)
{
solAssert(_contract.isLibrary(), "");
if (!_contract.baseContracts().empty())
- typeError(_contract.location(), "Library is not allowed to inherit.");
+ m_errorReporter.typeError(_contract.location(), "Library is not allowed to inherit.");
for (auto const& var: _contract.stateVariables())
if (!var->isConstant())
- typeError(var->location(), "Library cannot have non-constant state variables");
+ m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables");
+}
+
+void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
+{
+ TupleType const& lhs = dynamic_cast<TupleType const&>(*type(_assignment.leftHandSide()));
+ TupleType const& rhs = dynamic_cast<TupleType const&>(*type(_assignment.rightHandSide()));
+
+ bool fillRight = !lhs.components().empty() && (!lhs.components().back() || lhs.components().front());
+ size_t storageToStorageCopies = 0;
+ 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());
+ if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer())
+ continue;
+ size_t rhsPos = fillRight ? i : rhs.components().size() - (lhs.components().size() - i);
+ solAssert(rhsPos < rhs.components().size(), "");
+ toStorageCopies++;
+ if (rhs.components()[rhsPos]->dataStoredIn(DataLocation::Storage))
+ storageToStorageCopies++;
+ }
+ if (storageToStorageCopies >= 1 && toStorageCopies >= 2)
+ m_errorReporter.warning(
+ _assignment.location(),
+ "This assignment performs two copies to storage. Since storage copies do not first "
+ "copy to a temporary location, one of them might be overwritten before the second "
+ "is executed and thus may have unexpected effects. It is safer to perform the copies "
+ "separately or assign to storage pointers first."
+ );
}
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
@@ -353,14 +398,17 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
solAssert(base, "Base contract not available.");
+ if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
+ m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit.");
+
if (base->isLibrary())
- typeError(_inheritance.location(), "Libraries cannot be inherited from.");
+ m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from.");
auto const& arguments = _inheritance.arguments();
TypePointers parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
if (!arguments.empty() && parameterTypes.size() != arguments.size())
{
- typeError(
+ m_errorReporter.typeError(
_inheritance.location(),
"Wrong argument count for constructor call: " +
toString(arguments.size()) +
@@ -373,7 +421,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
for (size_t i = 0; i < arguments.size(); ++i)
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
- typeError(
+ m_errorReporter.typeError(
arguments[i]->location(),
"Invalid type for argument in constructor call. "
"Invalid implicit conversion from " +
@@ -390,14 +438,17 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
_usingFor.libraryName().annotation().referencedDeclaration
);
if (!library || !library->isLibrary())
- typeError(_usingFor.libraryName().location(), "Library name expected.");
+ m_errorReporter.typeError(_usingFor.libraryName().location(), "Library name expected.");
}
bool TypeChecker::visit(StructDefinition const& _struct)
{
+ if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
+ m_errorReporter.typeError(_struct.location(), "Structs cannot be defined in interfaces.");
+
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
if (!type(*member)->canBeStored())
- typeError(member->location(), "Type cannot be used in struct.");
+ m_errorReporter.typeError(member->location(), "Type cannot be used in struct.");
// Check recursion, fatal error if detected.
using StructPointer = StructDefinition const*;
@@ -405,7 +456,7 @@ bool TypeChecker::visit(StructDefinition const& _struct)
function<void(StructPointer,StructPointersSet const&)> check = [&](StructPointer _struct, StructPointersSet const& _parents)
{
if (_parents.count(_struct))
- fatalTypeError(_struct->location(), "Recursive struct definition.");
+ m_errorReporter.fatalTypeError(_struct->location(), "Recursive struct definition.");
StructPointersSet parents = _parents;
parents.insert(_struct);
for (ASTPointer<VariableDeclaration> const& member: _struct->members())
@@ -424,30 +475,56 @@ bool TypeChecker::visit(StructDefinition const& _struct)
bool TypeChecker::visit(FunctionDefinition const& _function)
{
- bool isLibraryFunction = dynamic_cast<ContractDefinition const&>(*_function.scope()).isLibrary();
+ bool isLibraryFunction =
+ dynamic_cast<ContractDefinition const*>(_function.scope()) &&
+ dynamic_cast<ContractDefinition const*>(_function.scope())->isLibrary();
if (_function.isPayable())
{
if (isLibraryFunction)
- typeError(_function.location(), "Library functions cannot be payable.");
+ m_errorReporter.typeError(_function.location(), "Library functions cannot be payable.");
if (!_function.isConstructor() && !_function.name().empty() && !_function.isPartOfExternalInterface())
- typeError(_function.location(), "Internal functions cannot be payable.");
+ m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable.");
if (_function.isDeclaredConst())
- typeError(_function.location(), "Functions cannot be constant and payable at the same time.");
+ m_errorReporter.typeError(_function.location(), "Functions cannot be constant and payable at the same time.");
}
for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters())
{
if (!type(*var)->canLiveOutsideStorage())
- typeError(var->location(), "Type is required to live outside storage.");
+ m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
- fatalTypeError(var->location(), "Internal type is not allowed for public or external functions.");
+ m_errorReporter.fatalTypeError(var->location(), "Internal type is not allowed for public or external functions.");
+
+ var->accept(*this);
}
+ set<Declaration const*> modifiers;
for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers())
+ {
visitManually(
*modifier,
_function.isConstructor() ?
dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts :
vector<ContractDefinition const*>()
);
+ Declaration const* decl = &dereference(*modifier->name());
+ if (modifiers.count(decl))
+ {
+ if (dynamic_cast<ContractDefinition const*>(decl))
+ m_errorReporter.declarationError(modifier->location(), "Base constructor already provided.");
+ else
+ m_errorReporter.declarationError(modifier->location(), "Modifier already used for this function.");
+ }
+ else
+ modifiers.insert(decl);
+ }
+ if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
+ {
+ if (_function.isImplemented())
+ m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation.");
+ if (_function.visibility() < FunctionDefinition::Visibility::Public)
+ m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot be internal or private.");
+ if (_function.isConstructor())
+ m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces.");
+ }
if (_function.isImplemented())
_function.body().accept(*this);
return false;
@@ -455,6 +532,14 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
bool TypeChecker::visit(VariableDeclaration const& _variable)
{
+ // Forbid any variable declarations inside interfaces unless they are part of
+ // a function's input/output parameters.
+ if (
+ m_scope->contractKind() == ContractDefinition::ContractKind::Interface
+ && !_variable.isCallableParameter()
+ )
+ m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
+
// Variables can be declared without type (with "var"), in which case the first assignment
// sets the type.
// Note that assignments before the first declaration are legal because of the special scoping
@@ -464,38 +549,47 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
// TypeChecker at the VariableDeclarationStatement level.
TypePointer varType = _variable.annotation().type;
solAssert(!!varType, "Failed to infer variable type.");
+ if (_variable.value())
+ expectType(*_variable.value(), *varType);
if (_variable.isConstant())
{
- if (!dynamic_cast<ContractDefinition const*>(_variable.scope()))
- typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
- if (!_variable.value())
- typeError(_variable.location(), "Uninitialized \"constant\" variable.");
- if (!varType->isValueType())
+ if (!_variable.isStateVariable())
+ m_errorReporter.typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
+ if (!_variable.type()->isValueType())
{
- bool constImplemented = false;
- if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get()))
- constImplemented = arrayType->isByteArray();
- if (!constImplemented)
- typeError(
- _variable.location(),
- "Illegal use of \"constant\" specifier. \"constant\" "
- "is not yet implemented for this type."
- );
+ bool allowed = false;
+ if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type().get()))
+ allowed = arrayType->isString();
+ if (!allowed)
+ m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented.");
}
+ if (!_variable.value())
+ m_errorReporter.typeError(_variable.location(), "Uninitialized \"constant\" variable.");
+ else if (!_variable.value()->annotation().isPure)
+ m_errorReporter.warning(
+ _variable.value()->location(),
+ "Initial value for constant variable has to be compile-time constant. "
+ "This will fail to compile with the next breaking version change."
+ );
}
- if (_variable.value())
- expectType(*_variable.value(), *varType);
if (!_variable.isStateVariable())
{
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
if (!varType->canLiveOutsideStorage())
- typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage.");
+ m_errorReporter.typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage.");
}
else if (
_variable.visibility() >= VariableDeclaration::Visibility::Public &&
!FunctionType(_variable).interfaceFunctionType()
)
- typeError(_variable.location(), "Internal type is not allowed for public state variables.");
+ m_errorReporter.typeError(_variable.location(), "Internal type is not allowed for public state variables.");
+ return false;
+}
+
+bool TypeChecker::visit(EnumDefinition const& _enum)
+{
+ if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
+ m_errorReporter.typeError(_enum.location(), "Enumerable cannot be declared in interfaces.");
return false;
}
@@ -527,12 +621,12 @@ void TypeChecker::visitManually(
}
if (!parameters)
{
- typeError(_modifier.location(), "Referenced declaration is neither modifier nor base class.");
+ m_errorReporter.typeError(_modifier.location(), "Referenced declaration is neither modifier nor base class.");
return;
}
if (parameters->size() != arguments.size())
{
- typeError(
+ m_errorReporter.typeError(
_modifier.location(),
"Wrong argument count for modifier invocation: " +
toString(arguments.size()) +
@@ -544,7 +638,7 @@ void TypeChecker::visitManually(
}
for (size_t i = 0; i < _modifier.arguments().size(); ++i)
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i])))
- typeError(
+ m_errorReporter.typeError(
arguments[i]->location(),
"Invalid type for argument in modifier invocation. "
"Invalid implicit conversion from " +
@@ -563,13 +657,13 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
if (var->isIndexed())
numIndexed++;
if (_eventDef.isAnonymous() && numIndexed > 4)
- typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
+ m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
else if (!_eventDef.isAnonymous() && numIndexed > 3)
- typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
+ m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
if (!type(*var)->canLiveOutsideStorage())
- typeError(var->location(), "Type is required to live outside storage.");
+ m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (!type(*var)->interfaceType(false))
- typeError(var->location(), "Internal type is not allowed as event parameter type.");
+ m_errorReporter.typeError(var->location(), "Internal type is not allowed as event parameter type.");
}
return false;
}
@@ -577,67 +671,102 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
void TypeChecker::endVisit(FunctionTypeName const& _funType)
{
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
- if (fun.location() == FunctionType::Location::External)
+ if (fun.kind() == FunctionType::Kind::External)
if (!fun.canBeUsedExternally(false))
- typeError(_funType.location(), "External function type uses internal types.");
+ m_errorReporter.typeError(_funType.location(), "External function type uses internal types.");
}
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
- // Inline assembly does not have its own type-checking phase, so we just run the
- // code-generator and see whether it produces any errors.
// External references have already been resolved in a prior stage and stored in the annotation.
- assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
- codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
+ // We run the resolve step again regardless.
+ julia::ExternalIdentifierAccess::Resolver identifierAccess = [&](
+ assembly::Identifier const& _identifier,
+ julia::IdentifierContext _context,
+ bool
+ )
+ {
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
- return false;
- Declaration const* declaration = ref->second;
+ return size_t(-1);
+ Declaration const* declaration = ref->second.declaration;
solAssert(!!declaration, "");
- if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
+ if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
- solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
- unsigned pushes = 0;
- if (dynamic_cast<FunctionDefinition const*>(declaration))
- pushes = 1;
- else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
+ if (ref->second.isSlot || ref->second.isOffset)
{
- if (var->isConstant())
- fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly.");
- if (var->isLocalVariable())
- pushes = var->type()->sizeOnStack();
- else if (var->type()->isValueType())
- pushes = 1;
- else
- pushes = 2; // slot number, intra slot offset
+ if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
+ {
+ m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
+ return size_t(-1);
+ }
+ else if (_context != julia::IdentifierContext::RValue)
+ {
+ m_errorReporter.typeError(_identifier.location, "Storage variables cannot be assigned to.");
+ return size_t(-1);
+ }
}
- else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
+ else if (var->isConstant())
{
- if (!contract->isLibrary())
- return false;
- pushes = 1;
+ m_errorReporter.typeError(_identifier.location, "Constant variables not supported by inline assembly.");
+ return size_t(-1);
+ }
+ else if (!var->isLocalVariable())
+ {
+ m_errorReporter.typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
+ return size_t(-1);
+ }
+ else if (var->type()->dataStoredIn(DataLocation::Storage))
+ {
+ m_errorReporter.typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables.");
+ return size_t(-1);
+ }
+ else if (var->type()->sizeOnStack() != 1)
+ {
+ m_errorReporter.typeError(_identifier.location, "Only types that use one stack slot are supported.");
+ return size_t(-1);
}
- else
- return false;
- for (unsigned i = 0; i < pushes; ++i)
- _assembly.append(u256(0)); // just to verify the stack height
}
- else
+ else if (_context == julia::IdentifierContext::LValue)
+ {
+ m_errorReporter.typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
+ return size_t(-1);
+ }
+
+ if (_context == julia::IdentifierContext::RValue)
{
- // lvalue context
- if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
+ solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
+ if (dynamic_cast<FunctionDefinition const*>(declaration))
+ {
+ }
+ else if (dynamic_cast<VariableDeclaration const*>(declaration))
{
- if (!varDecl->isLocalVariable())
- return false; // only local variables are inline-assemlby lvalues
- for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i)
- _assembly.append(Instruction::POP); // remove value just to verify the stack height
+ }
+ else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
+ {
+ if (!contract->isLibrary())
+ {
+ m_errorReporter.typeError(_identifier.location, "Expected a library.");
+ return size_t(-1);
+ }
}
else
- return false;
+ return size_t(-1);
}
- return true;
- });
- return false;
+ ref->second.valueSize = 1;
+ return size_t(1);
+ };
+ solAssert(!_inlineAssembly.annotation().analysisInfo, "");
+ _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
+ assembly::AsmAnalyzer analyzer(
+ *_inlineAssembly.annotation().analysisInfo,
+ m_errorReporter,
+ false,
+ identifierAccess
+ );
+ if (!analyzer.analyze(_inlineAssembly.operations()))
+ return false;
+ return true;
}
bool TypeChecker::visit(IfStatement const& _ifStatement)
@@ -675,7 +804,7 @@ void TypeChecker::endVisit(Return const& _return)
ParameterList const* params = _return.annotation().functionReturnParameters;
if (!params)
{
- typeError(_return.location(), "Return arguments not allowed.");
+ m_errorReporter.typeError(_return.location(), "Return arguments not allowed.");
return;
}
TypePointers returnTypes;
@@ -684,9 +813,9 @@ void TypeChecker::endVisit(Return const& _return)
if (auto tupleType = dynamic_cast<TupleType const*>(type(*_return.expression()).get()))
{
if (tupleType->components().size() != params->parameters().size())
- typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
+ m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
else if (!tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes)))
- typeError(
+ m_errorReporter.typeError(
_return.expression()->location(),
"Return argument type " +
type(*_return.expression())->toString() +
@@ -696,12 +825,12 @@ void TypeChecker::endVisit(Return const& _return)
);
}
else if (params->parameters().size() != 1)
- typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
+ m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration.");
else
{
TypePointer const& expected = type(*params->parameters().front());
if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected))
- typeError(
+ m_errorReporter.typeError(
_return.expression()->location(),
"Return argument type " +
type(*_return.expression())->toString() +
@@ -718,20 +847,25 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
{
// No initial value is only permitted for single variables with specified type.
if (_statement.declarations().size() != 1 || !_statement.declarations().front())
- fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
+ m_errorReporter.fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
VariableDeclaration const& varDecl = *_statement.declarations().front();
if (!varDecl.annotation().type)
- fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
+ m_errorReporter.fatalTypeError(_statement.location(), "Assignment necessary for type detection.");
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl).get()))
{
if (ref->dataStoredIn(DataLocation::Storage))
{
- warning(
- varDecl.location(),
- "Uninitialized storage pointer. Did you mean '<type> memory " + varDecl.name() + "'?"
- );
+ string errorText{"Uninitialized storage pointer."};
+ if (varDecl.referenceLocation() == VariableDeclaration::Location::Default)
+ errorText += " Did you mean '<type> memory " + varDecl.name() + "'?";
+ m_errorReporter.warning(varDecl.location(), errorText);
}
}
+ else if (dynamic_cast<MappingType const*>(type(varDecl).get()))
+ m_errorReporter.typeError(
+ varDecl.location(),
+ "Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
+ );
varDecl.accept(*this);
return false;
}
@@ -754,7 +888,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
if (variables.empty())
{
if (!valueTypes.empty())
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_statement.location(),
"Too many components (" +
toString(valueTypes.size()) +
@@ -762,7 +896,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
);
}
else if (valueTypes.size() != variables.size() && !variables.front() && !variables.back())
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_statement.location(),
"Wildcard both at beginning and end of variable declaration list is only allowed "
"if the number of components is equal."
@@ -771,7 +905,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
if (!variables.empty() && (!variables.back() || !variables.front()))
--minNumValues;
if (valueTypes.size() < minNumValues)
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_statement.location(),
"Not enough components (" +
toString(valueTypes.size()) +
@@ -779,7 +913,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
toString(minNumValues) + ")."
);
if (valueTypes.size() > variables.size() && variables.front() && variables.back())
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_statement.location(),
"Too many components (" +
toString(valueTypes.size()) +
@@ -810,7 +944,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
if (!var.annotation().type)
{
if (valueComponentType->category() == Type::Category::RationalNumber)
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_statement.initialValue()->location(),
"Invalid rational " +
valueComponentType->toString() +
@@ -819,6 +953,43 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
else
solAssert(false, "");
}
+ else if (*var.annotation().type == TupleType())
+ m_errorReporter.typeError(
+ var.location(),
+ "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()))
+ {
+ int numBits = type->numBits();
+ bool isSigned = type->isSigned();
+ string minValue;
+ string maxValue;
+ if (isSigned)
+ {
+ numBits--;
+ minValue = "-" + bigint(bigint(1) << numBits).str();
+ }
+ else
+ minValue = "0";
+ maxValue = bigint((bigint(1) << numBits) - 1).str();
+ extension = ", which can hold values between " + minValue + " and " + maxValue;
+ }
+ else
+ solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type.get()), "Unknown type.");
+
+ m_errorReporter.warning(
+ _statement.location(),
+ "The type of this variable was inferred as " +
+ typeName +
+ extension +
+ ". This is probably not desired. Use an explicit type to silence this warning."
+ );
+ }
+
var.accept(*this);
}
else
@@ -831,7 +1002,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() &&
valueComponentType->mobileType()
)
- typeError(
+ m_errorReporter.typeError(
_statement.location(),
"Type " +
valueComponentType->toString() +
@@ -842,7 +1013,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
" or use an explicit conversion."
);
else
- typeError(
+ m_errorReporter.typeError(
_statement.location(),
"Type " +
valueComponentType->toString() +
@@ -860,21 +1031,21 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
{
if (type(_statement.expression())->category() == Type::Category::RationalNumber)
if (!dynamic_cast<RationalNumberType const&>(*type(_statement.expression())).mobileType())
- typeError(_statement.expression().location(), "Invalid rational number.");
+ m_errorReporter.typeError(_statement.expression().location(), "Invalid rational number.");
if (auto call = dynamic_cast<FunctionCall const*>(&_statement.expression()))
{
if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression()).get()))
{
- using Location = FunctionType::Location;
- Location location = callType->location();
+ auto kind = callType->kind();
if (
- location == Location::Bare ||
- location == Location::BareCallCode ||
- location == Location::BareDelegateCall ||
- location == Location::Send
+ kind == FunctionType::Kind::Bare ||
+ kind == FunctionType::Kind::BareCallCode ||
+ kind == FunctionType::Kind::BareDelegateCall
)
- warning(_statement.location(), "Return value of low-level calls not used.");
+ m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used.");
+ else if (kind == FunctionType::Kind::Send)
+ m_errorReporter.warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead.");
}
}
}
@@ -889,14 +1060,14 @@ bool TypeChecker::visit(Conditional const& _conditional)
TypePointer trueType = type(_conditional.trueExpression())->mobileType();
TypePointer falseType = type(_conditional.falseExpression())->mobileType();
if (!trueType)
- fatalTypeError(_conditional.trueExpression().location(), "Invalid mobile type.");
+ m_errorReporter.fatalTypeError(_conditional.trueExpression().location(), "Invalid mobile type.");
if (!falseType)
- fatalTypeError(_conditional.falseExpression().location(), "Invalid mobile type.");
+ m_errorReporter.fatalTypeError(_conditional.falseExpression().location(), "Invalid mobile type.");
TypePointer commonType = Type::commonType(trueType, falseType);
if (!commonType)
{
- typeError(
+ m_errorReporter.typeError(
_conditional.location(),
"True expression's type " +
trueType->toString() +
@@ -910,9 +1081,13 @@ bool TypeChecker::visit(Conditional const& _conditional)
}
_conditional.annotation().type = commonType;
+ _conditional.annotation().isPure =
+ _conditional.condition().annotation().isPure &&
+ _conditional.trueExpression().annotation().isPure &&
+ _conditional.falseExpression().annotation().isPure;
if (_conditional.annotation().lValueRequested)
- typeError(
+ m_errorReporter.typeError(
_conditional.location(),
"Conditional expression as left value is not supported yet."
);
@@ -927,13 +1102,20 @@ bool TypeChecker::visit(Assignment const& _assignment)
_assignment.annotation().type = t;
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get()))
{
+ if (_assignment.assignmentOperator() != Token::Assign)
+ m_errorReporter.typeError(
+ _assignment.location(),
+ "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>();
expectType(_assignment.rightHandSide(), *tupleType);
+
+ checkDoubleStorageAssignment(_assignment);
}
else if (t->category() == Type::Category::Mapping)
{
- typeError(_assignment.location(), "Mappings cannot be assigned to.");
+ m_errorReporter.typeError(_assignment.location(), "Mappings cannot be assigned to.");
_assignment.rightHandSide().accept(*this);
}
else if (_assignment.assignmentOperator() == Token::Assign)
@@ -947,7 +1129,7 @@ bool TypeChecker::visit(Assignment const& _assignment)
type(_assignment.rightHandSide())
);
if (!resultType || *resultType != *t)
- typeError(
+ m_errorReporter.typeError(
_assignment.location(),
"Operator " +
string(Token::toString(_assignment.assignmentOperator())) +
@@ -968,7 +1150,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
if (_tuple.annotation().lValueRequested)
{
if (_tuple.isInlineArray())
- fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue.");
+ m_errorReporter.fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue.");
for (auto const& component: components)
if (component)
{
@@ -986,12 +1168,13 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
}
else
{
+ bool isPure = true;
TypePointer inlineArrayType;
for (size_t i = 0; i < components.size(); ++i)
{
// Outside of an lvalue-context, the only situation where a component can be empty is (x,).
if (!components[i] && !(i == 1 && components.size() == 2))
- fatalTypeError(_tuple.location(), "Tuple component cannot be empty.");
+ m_errorReporter.fatalTypeError(_tuple.location(), "Tuple component cannot be empty.");
else if (components[i])
{
components[i]->accept(*this);
@@ -1001,21 +1184,24 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
if (_tuple.isInlineArray())
{
if ((i == 0 || inlineArrayType) && !types[i]->mobileType())
- fatalTypeError(components[i]->location(), "Invalid mobile type.");
+ m_errorReporter.fatalTypeError(components[i]->location(), "Invalid mobile type.");
if (i == 0)
- inlineArrayType = types[i];
+ inlineArrayType = types[i]->mobileType();
else if (inlineArrayType)
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
}
+ if (!components[i]->annotation().isPure)
+ isPure = false;
}
else
types.push_back(TypePointer());
}
+ _tuple.annotation().isPure = isPure;
if (_tuple.isInlineArray())
{
if (!inlineArrayType)
- fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements.");
+ m_errorReporter.fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements.");
_tuple.annotation().type = make_shared<ArrayType>(DataLocation::Memory, inlineArrayType, types.size());
}
else
@@ -1038,7 +1224,8 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
{
// Inc, Dec, Add, Sub, Not, BitNot, Delete
Token::Value op = _operation.getOperator();
- if (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete)
+ bool const modifying = (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete);
+ if (modifying)
requireLValue(_operation.subExpression());
else
_operation.subExpression().accept(*this);
@@ -1046,7 +1233,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op);
if (!t)
{
- typeError(
+ m_errorReporter.typeError(
_operation.location(),
"Unary operator " +
string(Token::toString(op)) +
@@ -1056,6 +1243,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
t = subExprType;
}
_operation.annotation().type = t;
+ _operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure;
return false;
}
@@ -1066,7 +1254,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
if (!commonType)
{
- typeError(
+ m_errorReporter.typeError(
_operation.location(),
"Operator " +
string(Token::toString(_operation.getOperator())) +
@@ -1082,6 +1270,30 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
Token::isCompareOp(_operation.getOperator()) ?
make_shared<BoolType>() :
commonType;
+ _operation.annotation().isPure =
+ _operation.leftExpression().annotation().isPure &&
+ _operation.rightExpression().annotation().isPure;
+
+ if (_operation.getOperator() == Token::Exp)
+ {
+ if (
+ leftType->category() == Type::Category::RationalNumber &&
+ rightType->category() != Type::Category::RationalNumber
+ )
+ if ((
+ commonType->category() == Type::Category::Integer &&
+ dynamic_cast<IntegerType const&>(*commonType).numBits() != 256
+ ) || (
+ commonType->category() == Type::Category::FixedPoint &&
+ dynamic_cast<FixedPointType const&>(*commonType).numBits() != 256
+ ))
+ m_errorReporter.warning(
+ _operation.location(),
+ "Result of exponentiation has type " + commonType->toString() + " and thus "
+ "might overflow. Silence this warning by converting the literal to the "
+ "expected type."
+ );
+ }
}
bool TypeChecker::visit(FunctionCall const& _functionCall)
@@ -1090,6 +1302,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
vector<ASTPointer<ASTString>> const& argumentNames = _functionCall.names();
+ bool isPure = true;
+
// We need to check arguments' type first as they will be needed for overload resolution.
shared_ptr<TypePointers> argumentTypes;
if (isPositionalCall)
@@ -1097,6 +1311,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
for (ASTPointer<Expression const> const& argument: arguments)
{
argument->accept(*this);
+ if (!argument->annotation().isPure)
+ isPure = false;
// only store them for positional calls
if (isPositionalCall)
argumentTypes->push_back(type(*argument));
@@ -1109,20 +1325,24 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (auto const* typeType = dynamic_cast<TypeType const*>(expressionType.get()))
{
- _functionCall.annotation().isStructConstructorCall = (typeType->actualType()->category() == Type::Category::Struct);
- _functionCall.annotation().isTypeConversion = !_functionCall.annotation().isStructConstructorCall;
+ if (typeType->actualType()->category() == Type::Category::Struct)
+ _functionCall.annotation().kind = FunctionCallKind::StructConstructorCall;
+ else
+ _functionCall.annotation().kind = FunctionCallKind::TypeConversion;
+
}
else
- _functionCall.annotation().isStructConstructorCall = _functionCall.annotation().isTypeConversion = false;
+ _functionCall.annotation().kind = FunctionCallKind::FunctionCall;
+ solAssert(_functionCall.annotation().kind != FunctionCallKind::Unset, "");
- if (_functionCall.annotation().isTypeConversion)
+ if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
{
TypeType const& t = dynamic_cast<TypeType const&>(*expressionType);
TypePointer resultType = t.actualType();
if (arguments.size() != 1)
- typeError(_functionCall.location(), "Exactly one argument expected for explicit type conversion.");
+ m_errorReporter.typeError(_functionCall.location(), "Exactly one argument expected for explicit type conversion.");
else if (!isPositionalCall)
- typeError(_functionCall.location(), "Type conversion cannot allow named arguments.");
+ m_errorReporter.typeError(_functionCall.location(), "Type conversion cannot allow named arguments.");
else
{
TypePointer const& argType = type(*arguments.front());
@@ -1131,9 +1351,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
// (data location cannot yet be specified for type conversions)
resultType = ReferenceType::copyForLocationIfReference(argRefType->location(), resultType);
if (!argType->isExplicitlyConvertibleTo(*resultType))
- typeError(_functionCall.location(), "Explicit type conversion not allowed.");
+ m_errorReporter.typeError(_functionCall.location(), "Explicit type conversion not allowed.");
}
_functionCall.annotation().type = resultType;
+ _functionCall.annotation().isPure = isPure;
return false;
}
@@ -1144,19 +1365,23 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
/// For error message: Struct members that were removed during conversion to memory.
set<string> membersRemovedForStructConstructor;
- if (_functionCall.annotation().isStructConstructorCall)
+ if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
TypeType const& t = dynamic_cast<TypeType const&>(*expressionType);
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
functionType = structType.constructorType();
membersRemovedForStructConstructor = structType.membersMissingInMemory();
+ _functionCall.annotation().isPure = isPure;
}
- else
- functionType = dynamic_pointer_cast<FunctionType const>(expressionType);
+ else if ((functionType = dynamic_pointer_cast<FunctionType const>(expressionType)))
+ _functionCall.annotation().isPure =
+ isPure &&
+ _functionCall.expression().annotation().isPure &&
+ functionType->isPure();
if (!functionType)
{
- typeError(_functionCall.location(), "Type is not callable");
+ m_errorReporter.typeError(_functionCall.location(), "Type is not callable");
_functionCall.annotation().type = make_shared<TupleType>();
return false;
}
@@ -1175,13 +1400,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
toString(parameterTypes.size()) +
".";
// Extend error message in case we try to construct a struct with mapping member.
- if (_functionCall.annotation().isStructConstructorCall && !membersRemovedForStructConstructor.empty())
+ if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall && !membersRemovedForStructConstructor.empty())
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
}
- typeError(_functionCall.location(), msg);
+ m_errorReporter.typeError(_functionCall.location(), msg);
}
else if (isPositionalCall)
{
@@ -1193,10 +1418,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
{
if (auto t = dynamic_cast<RationalNumberType const*>(argType.get()))
if (!t->mobileType())
- typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero).");
+ m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero).");
}
else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
- typeError(
+ m_errorReporter.typeError(
arguments[i]->location(),
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
@@ -1212,14 +1437,14 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
// call by named arguments
auto const& parameterNames = functionType->parameterNames();
if (functionType->takesArbitraryParameters())
- typeError(
+ m_errorReporter.typeError(
_functionCall.location(),
"Named arguments cannnot be used for functions that take arbitrary parameters."
);
else if (parameterNames.size() > argumentNames.size())
- typeError(_functionCall.location(), "Some argument names are missing.");
+ m_errorReporter.typeError(_functionCall.location(), "Some argument names are missing.");
else if (parameterNames.size() < argumentNames.size())
- typeError(_functionCall.location(), "Too many arguments.");
+ m_errorReporter.typeError(_functionCall.location(), "Too many arguments.");
else
{
// check duplicate names
@@ -1229,7 +1454,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (*argumentNames[i] == *argumentNames[j])
{
duplication = true;
- typeError(arguments[i]->location(), "Duplicate named argument.");
+ m_errorReporter.typeError(arguments[i]->location(), "Duplicate named argument.");
}
// check actual types
@@ -1243,7 +1468,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
found = true;
// check type convertible
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[j]))
- typeError(
+ m_errorReporter.typeError(
arguments[i]->location(),
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
@@ -1256,7 +1481,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
if (!found)
- typeError(
+ m_errorReporter.typeError(
_functionCall.location(),
"Named argument does not match function declaration."
);
@@ -1277,9 +1502,11 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
auto contract = dynamic_cast<ContractDefinition const*>(&dereference(*contractName));
if (!contract)
- fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
+ m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
if (!contract->annotation().isFullyImplemented)
- typeError(_newExpression.location(), "Trying to create an instance of an abstract contract.");
+ m_errorReporter.typeError(_newExpression.location(), "Trying to create an instance of an abstract contract.");
+ if (!contract->constructorIsPublic())
+ m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
solAssert(!!m_scope, "");
m_scope->annotation().contractDependencies.insert(contract);
@@ -1288,7 +1515,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
"Linearized base contracts not yet available."
);
if (contractDependenciesAreCyclic(*m_scope))
- typeError(
+ m_errorReporter.typeError(
_newExpression.location(),
"Circular reference for contract creation (cannot create instance of derived or same contract)."
);
@@ -1298,12 +1525,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
else if (type->category() == Type::Category::Array)
{
if (!type->canLiveOutsideStorage())
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_newExpression.typeName().location(),
"Type cannot live outside storage."
);
if (!type->isDynamicallySized())
- typeError(
+ m_errorReporter.typeError(
_newExpression.typeName().location(),
"Length has to be placed in parentheses after the array type for new expression."
);
@@ -1313,11 +1540,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
TypePointers{type},
strings(),
strings(),
- FunctionType::Location::ObjectCreation
+ FunctionType::Kind::ObjectCreation
);
+ _newExpression.annotation().isPure = true;
}
else
- fatalTypeError(_newExpression.location(), "Contract or array type expected.");
+ m_errorReporter.fatalTypeError(_newExpression.location(), "Contract or array type expected.");
}
bool TypeChecker::visit(MemberAccess const& _memberAccess)
@@ -1348,13 +1576,13 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
exprType
);
if (!storageType->members(m_scope).membersByName(memberName).empty())
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_memberAccess.location(),
"Member \"" + memberName + "\" is not available in " +
exprType->toString() +
" outside of storage."
);
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_memberAccess.location(),
"Member \"" + memberName + "\" not found or not visible "
"after argument-dependent lookup in " + exprType->toString() +
@@ -1362,7 +1590,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
);
}
else if (possibleMembers.size() > 1)
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_memberAccess.location(),
"Member \"" + memberName + "\" not unique "
"after argument-dependent lookup in " + exprType->toString() +
@@ -1375,7 +1603,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type.get()))
if (funType->bound() && !exprType->isImplicitlyConvertibleTo(*funType->selfType()))
- typeError(
+ m_errorReporter.typeError(
_memberAccess.location(),
"Function \"" + memberName + "\" cannot be called on an object of type " +
exprType->toString() + " (expected " + funType->selfType()->toString() + ")"
@@ -1400,6 +1628,12 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
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 (tt->actualType()->category() == Type::Category::Enum)
+ annotation.isPure = true;
+
return false;
}
@@ -1409,6 +1643,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
TypePointer baseType = type(_access.baseExpression());
TypePointer resultType;
bool isLValue = false;
+ bool isPure = _access.baseExpression().annotation().isPure;
Expression const* index = _access.indexExpression();
switch (baseType->category())
{
@@ -1416,10 +1651,10 @@ bool TypeChecker::visit(IndexAccess const& _access)
{
ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType);
if (!index)
- typeError(_access.location(), "Index expression cannot be omitted.");
+ m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted.");
else if (actualType.isString())
{
- typeError(_access.location(), "Index access for string is not possible.");
+ m_errorReporter.typeError(_access.location(), "Index access for string is not possible.");
index->accept(*this);
}
else
@@ -1429,7 +1664,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
{
if (!numberType->isFractional()) // error is reported above
if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr))
- typeError(_access.location(), "Out of bounds array access.");
+ m_errorReporter.typeError(_access.location(), "Out of bounds array access.");
}
}
resultType = actualType.baseType();
@@ -1440,7 +1675,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
{
MappingType const& actualType = dynamic_cast<MappingType const&>(*baseType);
if (!index)
- typeError(_access.location(), "Index expression cannot be omitted.");
+ m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted.");
else
expectType(*index, *actualType.keyType());
resultType = actualType.valueType();
@@ -1462,7 +1697,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
length->literalValue(nullptr)
));
else
- fatalTypeError(index->location(), "Integer constant expected.");
+ m_errorReporter.fatalTypeError(index->location(), "Integer constant expected.");
}
break;
}
@@ -1470,26 +1705,29 @@ bool TypeChecker::visit(IndexAccess const& _access)
{
FixedBytesType const& bytesType = dynamic_cast<FixedBytesType const&>(*baseType);
if (!index)
- typeError(_access.location(), "Index expression cannot be omitted.");
+ m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted.");
else
{
expectType(*index, IntegerType(256));
if (auto integerType = dynamic_cast<RationalNumberType const*>(type(*index).get()))
if (bytesType.numBytes() <= integerType->literalValue(nullptr))
- typeError(_access.location(), "Out of bounds array access.");
+ m_errorReporter.typeError(_access.location(), "Out of bounds array access.");
}
resultType = make_shared<FixedBytesType>(1);
isLValue = false; // @todo this heavily depends on how it is embedded
break;
}
default:
- fatalTypeError(
+ m_errorReporter.fatalTypeError(
_access.baseExpression().location(),
"Indexed expression has to be a type, mapping or array (is " + baseType->toString() + ")"
);
}
_access.annotation().type = move(resultType);
_access.annotation().isLValue = isLValue;
+ if (index && !index->annotation().isPure)
+ isPure = false;
+ _access.annotation().isPure = isPure;
return false;
}
@@ -1500,9 +1738,24 @@ bool TypeChecker::visit(Identifier const& _identifier)
if (!annotation.referencedDeclaration)
{
if (!annotation.argumentTypes)
- fatalTypeError(_identifier.location(), "Unable to determine overloaded type.");
- if (annotation.overloadedDeclarations.empty())
- fatalTypeError(_identifier.location(), "No candidates for overload resolution found.");
+ {
+ // The identifier should be a public state variable shadowing other functions
+ vector<Declaration const*> candidates;
+
+ for (Declaration const* declaration: annotation.overloadedDeclarations)
+ {
+ if (VariableDeclaration const* variableDeclaration = dynamic_cast<decltype(variableDeclaration)>(declaration))
+ candidates.push_back(declaration);
+ }
+ if (candidates.empty())
+ m_errorReporter.fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup.");
+ else if (candidates.size() == 1)
+ annotation.referencedDeclaration = candidates.front();
+ else
+ m_errorReporter.fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup.");
+ }
+ else if (annotation.overloadedDeclarations.empty())
+ m_errorReporter.fatalTypeError(_identifier.location(), "No candidates for overload resolution found.");
else if (annotation.overloadedDeclarations.size() == 1)
annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin();
else
@@ -1518,11 +1771,11 @@ bool TypeChecker::visit(Identifier const& _identifier)
candidates.push_back(declaration);
}
if (candidates.empty())
- fatalTypeError(_identifier.location(), "No matching declaration found after argument-dependent lookup.");
+ m_errorReporter.fatalTypeError(_identifier.location(), "No matching declaration found after argument-dependent lookup.");
else if (candidates.size() == 1)
annotation.referencedDeclaration = candidates.front();
else
- fatalTypeError(_identifier.location(), "No unique declaration found after argument-dependent lookup.");
+ m_errorReporter.fatalTypeError(_identifier.location(), "No unique declaration found after argument-dependent lookup.");
}
}
solAssert(
@@ -1532,20 +1785,41 @@ bool TypeChecker::visit(Identifier const& _identifier)
annotation.isLValue = annotation.referencedDeclaration->isLValue();
annotation.type = annotation.referencedDeclaration->type();
if (!annotation.type)
- fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined.");
+ m_errorReporter.fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined.");
+ if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
+ annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
+ else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
+ if (dynamic_cast<FunctionType const*>(annotation.type.get()))
+ annotation.isPure = true;
return false;
}
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{
_expr.annotation().type = make_shared<TypeType>(Type::fromElementaryTypeName(_expr.typeName()));
+ _expr.annotation().isPure = true;
}
void TypeChecker::endVisit(Literal const& _literal)
{
- _literal.annotation().type = Type::forLiteral(_literal);
+ if (_literal.looksLikeAddress())
+ {
+ if (_literal.passesAddressChecksum())
+ _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address);
+ else
+ m_errorReporter.warning(
+ _literal.location(),
+ "This looks like an address but has an invalid checksum. "
+ "If this is not used as an address, please prepend '00'."
+ );
+ }
if (!_literal.annotation().type)
- fatalTypeError(_literal.location(), "Invalid literal value.");
+ _literal.annotation().type = Type::forLiteral(_literal);
+
+ if (!_literal.annotation().type)
+ m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value.");
+
+ _literal.annotation().isPure = true;
}
bool TypeChecker::contractDependenciesAreCyclic(
@@ -1586,7 +1860,7 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte
dynamic_pointer_cast<RationalNumberType const>(type(_expression))->isFractional() &&
type(_expression)->mobileType()
)
- typeError(
+ m_errorReporter.typeError(
_expression.location(),
"Type " +
type(_expression)->toString() +
@@ -1597,7 +1871,7 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte
" or use an explicit conversion."
);
else
- typeError(
+ m_errorReporter.typeError(
_expression.location(),
"Type " +
type(_expression)->toString() +
@@ -1605,39 +1879,33 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte
_expectedType.toString() +
"."
);
- }
-}
+ }
-void TypeChecker::requireLValue(Expression const& _expression)
-{
- _expression.annotation().lValueRequested = true;
- _expression.accept(*this);
- if (!_expression.annotation().isLValue)
- typeError(_expression.location(), "Expression has to be an lvalue.");
-}
+ if (
+ type(_expression)->category() == Type::Category::RationalNumber &&
+ _expectedType.category() == Type::Category::FixedBytes
+ )
+ {
+ auto literal = dynamic_cast<Literal const*>(&_expression);
-void TypeChecker::typeError(SourceLocation const& _location, string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::TypeError);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
+ if (literal && !literal->isHexNumber())
+ m_errorReporter.warning(
+ _expression.location(),
+ "Decimal literal assigned to bytesXX variable will be left-aligned. "
+ "Use an explicit conversion to silence this warning."
+ );
+ }
- m_errors.push_back(err);
}
-void TypeChecker::warning(SourceLocation const& _location, string const& _description)
+void TypeChecker::requireLValue(Expression const& _expression)
{
- auto err = make_shared<Error>(Error::Type::Warning);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
+ _expression.annotation().lValueRequested = true;
+ _expression.accept(*this);
- m_errors.push_back(err);
+ if (_expression.annotation().isConstant)
+ m_errorReporter.typeError(_expression.location(), "Cannot assign to a constant variable.");
+ else if (!_expression.annotation().isLValue)
+ m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue.");
}
-void TypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description)
-{
- typeError(_location, _description);
- BOOST_THROW_EXCEPTION(FatalError());
-}
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index 143b15b2..ee43d13a 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -22,7 +22,6 @@
#pragma once
-#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <libsolidity/ast/ASTForward.h>
@@ -33,6 +32,7 @@ namespace dev
namespace solidity
{
+class ErrorReporter;
/**
* The module that performs type analysis on the AST, checks the applicability of operations on
@@ -43,11 +43,11 @@ class TypeChecker: private ASTConstVisitor
{
public:
/// @param _errors the reference to the list of errors and warnings to add them found during type checking.
- TypeChecker(ErrorList& _errors): m_errors(_errors) {}
+ TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
/// Performs type checking on the given contract and all of its sub-nodes.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
- bool checkTypeRequirements(ContractDefinition const& _contract);
+ bool checkTypeRequirements(ASTNode const& _contract);
/// @returns the type of an expression and asserts that it is present.
TypePointer const& type(Expression const& _expression) const;
@@ -56,14 +56,6 @@ public:
TypePointer const& type(VariableDeclaration const& _variable) const;
private:
- /// Adds a new error to the list of errors.
- void typeError(SourceLocation const& _location, std::string const& _description);
-
- /// Adds a new warning to the list of errors.
- void warning(SourceLocation const& _location, std::string const& _description);
-
- /// Adds a new error to the list of errors and throws to abort type checking.
- void fatalTypeError(SourceLocation const& _location, std::string const& _description);
virtual bool visit(ContractDefinition const& _contract) override;
/// Checks that two functions defined in this contract with the same name have different
@@ -77,12 +69,16 @@ private:
void checkContractExternalTypeClashes(ContractDefinition const& _contract);
/// Checks that all requirements for a library are fulfilled if this is a library.
void checkLibraryRequirements(ContractDefinition const& _contract);
+ /// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage.
+ /// Should only be called if the left hand side is tuple-typed.
+ void checkDoubleStorageAssignment(Assignment const& _assignment);
virtual void endVisit(InheritanceSpecifier const& _inheritance) override;
virtual void endVisit(UsingForDirective const& _usingFor) override;
virtual bool visit(StructDefinition const& _struct) override;
virtual bool visit(FunctionDefinition const& _function) override;
virtual bool visit(VariableDeclaration const& _variable) override;
+ virtual bool visit(EnumDefinition const& _enum) override;
/// We need to do this manually because we want to pass the bases of the current contract in
/// case this is a base constructor call.
void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases);
@@ -126,7 +122,7 @@ private:
ContractDefinition const* m_scope = nullptr;
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
};
}
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 3cd1dfbe..724a908f 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -20,21 +20,37 @@
* Solidity abstract syntax tree.
*/
-#include <algorithm>
-#include <functional>
-#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
-#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST_accept.h>
#include <libdevcore/SHA3.h>
+#include <boost/algorithm/string.hpp>
+
+#include <algorithm>
+#include <functional>
+
using namespace std;
using namespace dev;
using namespace dev::solidity;
+class IDDispenser
+{
+public:
+ static size_t next() { return ++instance(); }
+ static void reset() { instance() = 0; }
+private:
+ static size_t& instance()
+ {
+ static IDDispenser dispenser;
+ return dispenser.id;
+ }
+ size_t id = 0;
+};
+
ASTNode::ASTNode(SourceLocation const& _location):
+ m_id(IDDispenser::next()),
m_location(_location)
{
}
@@ -44,6 +60,11 @@ ASTNode::~ASTNode()
delete m_annotation;
}
+void ASTNode::resetID()
+{
+ IDDispenser::reset();
+}
+
ASTAnnotation& ASTNode::annotation() const
{
if (!m_annotation)
@@ -60,7 +81,7 @@ SourceUnitAnnotation& SourceUnit::annotation() const
{
if (!m_annotation)
m_annotation = new SourceUnitAnnotation();
- return static_cast<SourceUnitAnnotation&>(*m_annotation);
+ return dynamic_cast<SourceUnitAnnotation&>(*m_annotation);
}
string Declaration::sourceUnitName() const
@@ -76,7 +97,7 @@ ImportAnnotation& ImportDirective::annotation() const
{
if (!m_annotation)
m_annotation = new ImportAnnotation();
- return static_cast<ImportAnnotation&>(*m_annotation);
+ return dynamic_cast<ImportAnnotation&>(*m_annotation);
}
TypePointer ImportDirective::type() const
@@ -109,6 +130,12 @@ FunctionDefinition const* ContractDefinition::constructor() const
return nullptr;
}
+bool ContractDefinition::constructorIsPublic() const
+{
+ FunctionDefinition const* f = constructor();
+ return !f || f->isPublic();
+}
+
FunctionDefinition const* ContractDefinition::fallbackFunction() const
{
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
@@ -189,7 +216,6 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat
m_userDocumentation = _userDocumentation;
}
-
vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
{
if (!m_inheritableMembers)
@@ -217,6 +243,9 @@ vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
for (EnumDefinition const* e: definedEnums())
addInheritableMember(e);
+
+ for (EventDefinition const* e: events())
+ addInheritableMember(e);
}
return *m_inheritableMembers;
}
@@ -230,14 +259,14 @@ ContractDefinitionAnnotation& ContractDefinition::annotation() const
{
if (!m_annotation)
m_annotation = new ContractDefinitionAnnotation();
- return static_cast<ContractDefinitionAnnotation&>(*m_annotation);
+ return dynamic_cast<ContractDefinitionAnnotation&>(*m_annotation);
}
TypeNameAnnotation& TypeName::annotation() const
{
if (!m_annotation)
m_annotation = new TypeNameAnnotation();
- return static_cast<TypeNameAnnotation&>(*m_annotation);
+ return dynamic_cast<TypeNameAnnotation&>(*m_annotation);
}
TypePointer StructDefinition::type() const
@@ -249,7 +278,7 @@ TypeDeclarationAnnotation& StructDefinition::annotation() const
{
if (!m_annotation)
m_annotation = new TypeDeclarationAnnotation();
- return static_cast<TypeDeclarationAnnotation&>(*m_annotation);
+ return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation);
}
TypePointer EnumValue::type() const
@@ -268,7 +297,46 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const
{
if (!m_annotation)
m_annotation = new TypeDeclarationAnnotation();
- return static_cast<TypeDeclarationAnnotation&>(*m_annotation);
+ return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation);
+}
+
+shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
+{
+ if (_internal)
+ {
+ switch (visibility())
+ {
+ case Declaration::Visibility::Default:
+ solAssert(false, "visibility() should not return Default");
+ case Declaration::Visibility::Private:
+ case Declaration::Visibility::Internal:
+ case Declaration::Visibility::Public:
+ return make_shared<FunctionType>(*this, _internal);
+ case Declaration::Visibility::External:
+ return {};
+ default:
+ solAssert(false, "visibility() should not return a Visibility");
+ }
+ }
+ else
+ {
+ switch (visibility())
+ {
+ case Declaration::Visibility::Default:
+ solAssert(false, "visibility() should not return Default");
+ case Declaration::Visibility::Private:
+ case Declaration::Visibility::Internal:
+ return {};
+ case Declaration::Visibility::Public:
+ case Declaration::Visibility::External:
+ return make_shared<FunctionType>(*this, _internal);
+ default:
+ solAssert(false, "visibility() should not return a Visibility");
+ }
+ }
+
+ // To make the compiler happy
+ return {};
}
TypePointer FunctionDefinition::type() const
@@ -285,7 +353,7 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
{
if (!m_annotation)
m_annotation = new FunctionDefinitionAnnotation();
- return static_cast<FunctionDefinitionAnnotation&>(*m_annotation);
+ return dynamic_cast<FunctionDefinitionAnnotation&>(*m_annotation);
}
TypePointer ModifierDefinition::type() const
@@ -297,7 +365,7 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const
{
if (!m_annotation)
m_annotation = new ModifierDefinitionAnnotation();
- return static_cast<ModifierDefinitionAnnotation&>(*m_annotation);
+ return dynamic_cast<ModifierDefinitionAnnotation&>(*m_annotation);
}
TypePointer EventDefinition::type() const
@@ -305,18 +373,26 @@ TypePointer EventDefinition::type() const
return make_shared<FunctionType>(*this);
}
+std::shared_ptr<FunctionType> EventDefinition::functionType(bool _internal) const
+{
+ if (_internal)
+ return make_shared<FunctionType>(*this);
+ else
+ return {};
+}
+
EventDefinitionAnnotation& EventDefinition::annotation() const
{
if (!m_annotation)
m_annotation = new EventDefinitionAnnotation();
- return static_cast<EventDefinitionAnnotation&>(*m_annotation);
+ return dynamic_cast<EventDefinitionAnnotation&>(*m_annotation);
}
UserDefinedTypeNameAnnotation& UserDefinedTypeName::annotation() const
{
if (!m_annotation)
m_annotation = new UserDefinedTypeNameAnnotation();
- return static_cast<UserDefinedTypeNameAnnotation&>(*m_annotation);
+ return dynamic_cast<UserDefinedTypeNameAnnotation&>(*m_annotation);
}
bool VariableDeclaration::isLValue() const
@@ -340,6 +416,23 @@ bool VariableDeclaration::isCallableParameter() const
return false;
}
+bool VariableDeclaration::isLocalOrReturn() const
+{
+ return isReturnParameter() || (isLocalVariable() && !isCallableParameter());
+}
+
+bool VariableDeclaration::isReturnParameter() const
+{
+ auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
+ if (!callable)
+ return false;
+ if (callable->returnParameterList())
+ for (auto const& variable: callable->returnParameterList()->parameters())
+ if (variable.get() == this)
+ return true;
+ return false;
+}
+
bool VariableDeclaration::isExternalCallableParameter() const
{
auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
@@ -362,72 +455,118 @@ TypePointer VariableDeclaration::type() const
return annotation().type;
}
+shared_ptr<FunctionType> VariableDeclaration::functionType(bool _internal) const
+{
+ if (_internal)
+ return {};
+ switch (visibility())
+ {
+ case Declaration::Visibility::Default:
+ solAssert(false, "visibility() should not return Default");
+ case Declaration::Visibility::Private:
+ case Declaration::Visibility::Internal:
+ return {};
+ case Declaration::Visibility::Public:
+ case Declaration::Visibility::External:
+ return make_shared<FunctionType>(*this);
+ default:
+ solAssert(false, "visibility() should not return a Visibility");
+ }
+
+ // To make the compiler happy
+ return {};
+}
+
VariableDeclarationAnnotation& VariableDeclaration::annotation() const
{
if (!m_annotation)
m_annotation = new VariableDeclarationAnnotation();
- return static_cast<VariableDeclarationAnnotation&>(*m_annotation);
+ return dynamic_cast<VariableDeclarationAnnotation&>(*m_annotation);
}
StatementAnnotation& Statement::annotation() const
{
if (!m_annotation)
m_annotation = new StatementAnnotation();
- return static_cast<StatementAnnotation&>(*m_annotation);
+ return dynamic_cast<StatementAnnotation&>(*m_annotation);
}
InlineAssemblyAnnotation& InlineAssembly::annotation() const
{
if (!m_annotation)
m_annotation = new InlineAssemblyAnnotation();
- return static_cast<InlineAssemblyAnnotation&>(*m_annotation);
+ return dynamic_cast<InlineAssemblyAnnotation&>(*m_annotation);
}
ReturnAnnotation& Return::annotation() const
{
if (!m_annotation)
m_annotation = new ReturnAnnotation();
- return static_cast<ReturnAnnotation&>(*m_annotation);
+ return dynamic_cast<ReturnAnnotation&>(*m_annotation);
}
VariableDeclarationStatementAnnotation& VariableDeclarationStatement::annotation() const
{
if (!m_annotation)
m_annotation = new VariableDeclarationStatementAnnotation();
- return static_cast<VariableDeclarationStatementAnnotation&>(*m_annotation);
+ return dynamic_cast<VariableDeclarationStatementAnnotation&>(*m_annotation);
}
ExpressionAnnotation& Expression::annotation() const
{
if (!m_annotation)
m_annotation = new ExpressionAnnotation();
- return static_cast<ExpressionAnnotation&>(*m_annotation);
+ return dynamic_cast<ExpressionAnnotation&>(*m_annotation);
}
MemberAccessAnnotation& MemberAccess::annotation() const
{
if (!m_annotation)
m_annotation = new MemberAccessAnnotation();
- return static_cast<MemberAccessAnnotation&>(*m_annotation);
+ return dynamic_cast<MemberAccessAnnotation&>(*m_annotation);
}
BinaryOperationAnnotation& BinaryOperation::annotation() const
{
if (!m_annotation)
m_annotation = new BinaryOperationAnnotation();
- return static_cast<BinaryOperationAnnotation&>(*m_annotation);
+ return dynamic_cast<BinaryOperationAnnotation&>(*m_annotation);
}
FunctionCallAnnotation& FunctionCall::annotation() const
{
if (!m_annotation)
m_annotation = new FunctionCallAnnotation();
- return static_cast<FunctionCallAnnotation&>(*m_annotation);
+ return dynamic_cast<FunctionCallAnnotation&>(*m_annotation);
}
IdentifierAnnotation& Identifier::annotation() const
{
if (!m_annotation)
m_annotation = new IdentifierAnnotation();
- return static_cast<IdentifierAnnotation&>(*m_annotation);
+ return dynamic_cast<IdentifierAnnotation&>(*m_annotation);
+}
+
+bool Literal::isHexNumber() const
+{
+ if (token() != Token::Number)
+ return false;
+ return boost::starts_with(value(), "0x");
+}
+
+bool Literal::looksLikeAddress() const
+{
+ if (subDenomination() != SubDenomination::None)
+ return false;
+
+ if (!isHexNumber())
+ return false;
+
+ return abs(int(value().length()) - 42) <= 1;
+}
+
+bool Literal::passesAddressChecksum() const
+{
+ solAssert(isHexNumber(), "Expected hex number");
+ return dev::passesAddressChecksum(value(), true);
}
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index ab4be1ea..f90a9b2f 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -29,7 +29,6 @@
#include <boost/noncopyable.hpp>
#include <libevmasm/SourceLocation.h>
#include <libevmasm/Instruction.h>
-#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/parsing/Token.h>
#include <libsolidity/ast/Types.h>
@@ -57,6 +56,11 @@ public:
explicit ASTNode(SourceLocation const& _location);
virtual ~ASTNode();
+ /// @returns an identifier of this AST node that is unique for a single compilation run.
+ size_t id() const { return m_id; }
+ /// Resets the global ID counter. This invalidates all previous IDs.
+ static void resetID();
+
virtual void accept(ASTVisitor& _visitor) = 0;
virtual void accept(ASTConstVisitor& _visitor) const = 0;
template <class T>
@@ -94,6 +98,7 @@ public:
///@}
protected:
+ size_t const m_id = 0;
/// Annotation - is specialised in derived classes, is created upon request (because of polymorphism).
mutable ASTAnnotation* m_annotation = nullptr;
@@ -161,6 +166,7 @@ public:
/// @returns the source name this declaration is present in.
/// Can be combined with annotation().canonicalName to form a globally unique name.
std::string sourceUnitName() const;
+ std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; }
@@ -171,6 +177,10 @@ public:
/// This can only be called once types of variable declarations have already been resolved.
virtual TypePointer type() const = 0;
+ /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
+ /// @returns null when it is not accessible as a function.
+ virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const { return {}; }
+
protected:
virtual Visibility defaultVisibility() const { return Visibility::Public; }
@@ -305,19 +315,21 @@ protected:
class ContractDefinition: public Declaration, public Documented
{
public:
+ enum class ContractKind { Interface, Contract, Library };
+
ContractDefinition(
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<ASTNode>> const& _subNodes,
- bool _isLibrary
+ ContractKind _contractKind = ContractKind::Contract
):
Declaration(_location, _name),
Documented(_documentation),
m_baseContracts(_baseContracts),
m_subNodes(_subNodes),
- m_isLibrary(_isLibrary)
+ m_contractKind(_contractKind)
{}
virtual void accept(ASTVisitor& _visitor) override;
@@ -333,7 +345,7 @@ public:
std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); }
std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); }
std::vector<EventDefinition const*> const& interfaceEvents() const;
- bool isLibrary() const { return m_isLibrary; }
+ bool isLibrary() const { return m_contractKind == ContractKind::Library; }
/// @returns a map of canonical function signatures to FunctionDefinitions
/// as intended for use by the ABI.
@@ -345,6 +357,8 @@ public:
/// Returns the constructor or nullptr if no constructor was specified.
FunctionDefinition const* constructor() const;
+ /// @returns true iff the constructor of this contract is public (or non-existing).
+ bool constructorIsPublic() const;
/// Returns the fallback function or nullptr if no fallback function was specified.
FunctionDefinition const* fallbackFunction() const;
@@ -358,10 +372,12 @@ public:
virtual ContractDefinitionAnnotation& annotation() const override;
+ ContractKind contractKind() const { return m_contractKind; }
+
private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<ASTNode>> m_subNodes;
- bool m_isLibrary;
+ ContractKind m_contractKind;
// parsed Natspec documentation of the contract.
Json::Value m_userDocumentation;
@@ -566,8 +582,7 @@ public:
bool isPayable() const { return m_isPayable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
- Block const& body() const { return *m_body; }
-
+ Block const& body() const { solAssert(m_body, ""); return *m_body; }
virtual bool isVisibleInContract() const override
{
return Declaration::isVisibleInContract() && !isConstructor() && !name().empty();
@@ -581,6 +596,10 @@ public:
virtual TypePointer type() const override;
+ /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
+ /// @returns null when it is not accessible as a function.
+ virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
+
virtual FunctionDefinitionAnnotation& annotation() const override;
private:
@@ -593,7 +612,7 @@ private:
/**
* Declaration of a variable. This can be used in various places, e.g. in function parameter
- * lists, struct definitions and even function bodys.
+ * lists, struct definitions and even function bodies.
*/
class VariableDeclaration: public Declaration
{
@@ -631,6 +650,10 @@ public:
bool isLocalVariable() const { return !!dynamic_cast<CallableDeclaration const*>(scope()); }
/// @returns true if this variable is a parameter or return parameter of a function.
bool isCallableParameter() const;
+ /// @returns true if this variable is a return parameter of a function.
+ bool isReturnParameter() const;
+ /// @returns true if this variable is a local variable or return parameter.
+ bool isLocalOrReturn() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function.
bool isExternalCallableParameter() const;
/// @returns true if the type of the variable does not need to be specified, i.e. it is declared
@@ -643,6 +666,10 @@ public:
virtual TypePointer type() const override;
+ /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
+ /// @returns null when it is not accessible as a function.
+ virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
+
virtual VariableDeclarationAnnotation& annotation() const override;
protected:
@@ -740,6 +767,7 @@ public:
bool isAnonymous() const { return m_anonymous; }
virtual TypePointer type() const override;
+ virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
virtual EventDefinitionAnnotation& annotation() const override;
@@ -848,8 +876,13 @@ public:
std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); }
+ ASTPointer<ParameterList> const& parameterTypeList() const { return m_parameterTypes; }
+ ASTPointer<ParameterList> const& returnParameterTypeList() const { return m_returnTypes; }
- Declaration::Visibility visibility() const { return m_visibility; }
+ Declaration::Visibility visibility() const
+ {
+ return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility;
+ }
bool isDeclaredConst() const { return m_isDeclaredConst; }
bool isPayable() const { return m_isPayable; }
@@ -1285,7 +1318,7 @@ private:
/**
* Tuple, parenthesized expression, or bracketed expression.
- * Examples: (1, 2), (x,), (x), (), [1, 2],
+ * Examples: (1, 2), (x,), (x), (), [1, 2],
* Individual components might be empty shared pointers (as in the second example).
* The respective types in lvalue context are: 2-tuple, 2-tuple (with wildcard), type of x, 0-tuple
* Not in lvalue context: 2-tuple, _1_-tuple, type of x, 0-tuple.
@@ -1298,8 +1331,8 @@ public:
std::vector<ASTPointer<Expression>> const& _components,
bool _isArray
):
- Expression(_location),
- m_components(_components),
+ Expression(_location),
+ m_components(_components),
m_isArray(_isArray) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@@ -1561,6 +1594,14 @@ public:
SubDenomination subDenomination() const { return m_subDenomination; }
+ /// @returns true if this is a number with a hex prefix.
+ bool isHexNumber() const;
+
+ /// @returns true if this looks like a checksummed address.
+ bool looksLikeAddress() const;
+ /// @returns true if it passes the address checksum test.
+ bool passesAddressChecksum() const;
+
private:
Token::Value m_token;
ASTPointer<ASTString> m_value;
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index 768e56db..45a6dd1a 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -22,11 +22,12 @@
#pragma once
+#include <libsolidity/ast/ASTForward.h>
+
#include <map>
#include <memory>
#include <vector>
#include <set>
-#include <libsolidity/ast/ASTForward.h>
namespace dev
{
@@ -112,13 +113,24 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
namespace assembly
{
-struct Identifier; // forward
+ struct AsmAnalysisInfo;
+ struct Identifier;
}
struct InlineAssemblyAnnotation: StatementAnnotation
{
- /// Mapping containing resolved references to external identifiers.
- std::map<assembly::Identifier const*, Declaration const*> externalReferences;
+ struct ExternalIdentifierInfo
+ {
+ Declaration const* declaration = nullptr;
+ bool isSlot = false; ///< Whether the storage slot of a variable is queried.
+ bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried.
+ size_t valueSize = size_t(-1);
+ };
+
+ /// Mapping containing resolved references to external identifiers and their value size
+ std::map<assembly::Identifier const*, ExternalIdentifierInfo> externalReferences;
+ /// Information generated during analysis phase.
+ std::shared_ptr<assembly::AsmAnalysisInfo> analysisInfo;
};
struct ReturnAnnotation: StatementAnnotation
@@ -154,6 +166,10 @@ struct ExpressionAnnotation: ASTAnnotation
{
/// Inferred type of the expression.
TypePointer type;
+ /// Whether the expression is a constant variable
+ bool isConstant = false;
+ /// Whether the expression is pure, i.e. compile-time constant.
+ bool isPure = false;
/// Whether it is an LValue (i.e. something that can be assigned to).
bool isLValue = false;
/// Whether the expression is used in a context where the LValue is actually required.
@@ -184,12 +200,17 @@ struct BinaryOperationAnnotation: ExpressionAnnotation
TypePointer commonType;
};
+enum class FunctionCallKind
+{
+ Unset,
+ FunctionCall,
+ TypeConversion,
+ StructConstructorCall
+};
+
struct FunctionCallAnnotation: ExpressionAnnotation
{
- /// Whether this is an explicit type conversion.
- bool isTypeConversion = false;
- /// Whether this is a struct constructor call.
- bool isStructConstructorCall = false;
+ FunctionCallKind kind = FunctionCallKind::Unset;
};
}
diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp
index f6b06be6..a90debb2 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -24,6 +24,8 @@
#include <boost/algorithm/string/join.hpp>
#include <libdevcore/UTF8.h>
#include <libsolidity/ast/AST.h>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmPrinter.h>
using namespace std;
@@ -32,34 +34,86 @@ namespace dev
namespace solidity
{
-void ASTJsonConverter::addJsonNode(
+ASTJsonConverter::ASTJsonConverter(bool _legacy, map<string, unsigned> _sourceIndices):
+ m_legacy(_legacy),
+ m_sourceIndices(_sourceIndices)
+{
+}
+
+
+void ASTJsonConverter::setJsonNode(
ASTNode const& _node,
string const& _nodeName,
- initializer_list<pair<string const, Json::Value const>> _attributes,
- bool _hasChildren = false
+ initializer_list<pair<string, Json::Value>>&& _attributes
)
{
- Json::Value node;
+ ASTJsonConverter::setJsonNode(
+ _node,
+ _nodeName,
+ std::vector<pair<string, Json::Value>>(std::move(_attributes))
+ );
+}
- node["id"] = reinterpret_cast<Json::UInt64>(&_node);
- node["src"] = sourceLocationToString(_node.location());
- node["name"] = _nodeName;
- if (_attributes.size() != 0)
+void ASTJsonConverter::setJsonNode(
+ ASTNode const& _node,
+ string const& _nodeType,
+ std::vector<pair<string, Json::Value>>&& _attributes
+)
+{
+ m_currentValue = Json::objectValue;
+ m_currentValue["id"] = nodeId(_node);
+ m_currentValue["src"] = sourceLocationToString(_node.location());
+ if (!m_legacy)
{
- Json::Value attrs;
+ m_currentValue["nodeType"] = _nodeType;
for (auto& e: _attributes)
- attrs[e.first] = e.second;
- node["attributes"] = attrs;
+ m_currentValue[e.first] = std::move(e.second);
}
-
- m_jsonNodePtrs.top()->append(node);
-
- if (_hasChildren)
+ else
{
- Json::Value& addedNode = (*m_jsonNodePtrs.top())[m_jsonNodePtrs.top()->size() - 1];
- Json::Value children(Json::arrayValue);
- addedNode["children"] = children;
- m_jsonNodePtrs.push(&addedNode["children"]);
+ m_currentValue["name"] = _nodeType;
+ Json::Value attrs(Json::objectValue);
+ if (
+ //these nodeTypes need to have a children-node even if it is empty
+ (_nodeType == "VariableDeclaration") ||
+ (_nodeType == "ParameterList") ||
+ (_nodeType == "Block") ||
+ (_nodeType == "InlineAssembly") ||
+ (_nodeType == "Throw")
+ )
+ {
+ Json::Value children(Json::arrayValue);
+ m_currentValue["children"] = children;
+ }
+
+ for (auto& e: _attributes)
+ {
+ if (
+ (!e.second.isNull()) &&
+ (
+ (e.second.isObject() && e.second.isMember("name")) ||
+ (e.second.isArray() && e.second[0].isObject() && e.second[0].isMember("name")) ||
+ (e.first == "declarations") // (in the case (_,x)= ... there's a nullpointer at [0]
+ )
+ )
+ {
+ if (e.second.isObject())
+ m_currentValue["children"].append(std::move(e.second));
+ if (e.second.isArray())
+ for (auto& child: e.second)
+ if (!child.isNull())
+ m_currentValue["children"].append(std::move(child));
+ }
+ else
+ {
+ if (e.first == "typeDescriptions")
+ attrs["type"] = Json::Value(e.second["typeString"]);
+ else
+ attrs[e.first] = std::move(e.second);
+ }
+ }
+ if (!attrs.empty())
+ m_currentValue["attributes"] = std::move(attrs);
}
}
@@ -74,34 +128,89 @@ string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location)
return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex);
}
-ASTJsonConverter::ASTJsonConverter(
- ASTNode const& _ast,
- map<string, unsigned> _sourceIndices
-): m_ast(&_ast), m_sourceIndices(_sourceIndices)
+string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePath) const
{
+ return boost::algorithm::join(_namePath, ".");
}
-void ASTJsonConverter::print(ostream& _stream)
+Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp)
{
- process();
- _stream << m_astJson;
+ Json::Value typeDescriptions(Json::objectValue);
+ typeDescriptions["typeString"] = _tp ? Json::Value(_tp->toString()) : Json::nullValue;
+ typeDescriptions["typeIdentifier"] = _tp ? Json::Value(_tp->identifier()) : Json::nullValue;
+ return typeDescriptions;
+
+}
+Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps)
+{
+ if (_tps)
+ {
+ Json::Value arguments(Json::arrayValue);
+ for (auto const& tp: *_tps)
+ arguments.append(typePointerToJson(tp));
+ return arguments;
+ }
+ else
+ return Json::nullValue;
}
-Json::Value const& ASTJsonConverter::json()
+void ASTJsonConverter::appendExpressionAttributes(
+ std::vector<pair<string, Json::Value>>& _attributes,
+ ExpressionAnnotation const& _annotation
+)
{
- process();
- return m_astJson;
+ std::vector<pair<string, Json::Value>> exprAttributes = {
+ make_pair("typeDescriptions", typePointerToJson(_annotation.type)),
+ make_pair("isConstant", _annotation.isConstant),
+ make_pair("isPure", _annotation.isPure),
+ make_pair("isLValue", _annotation.isLValue),
+ make_pair("lValueRequested", _annotation.lValueRequested),
+ make_pair("argumentTypes", typePointerToJson(_annotation.argumentTypes))
+ };
+ _attributes += exprAttributes;
}
-bool ASTJsonConverter::visit(SourceUnit const&)
+Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<assembly::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info)
{
- Json::Value children(Json::arrayValue);
+ Json::Value tuple(Json::objectValue);
+ tuple["src"] = sourceLocationToString(_info.first->location);
+ tuple["declaration"] = idOrNull(_info.second.declaration);
+ tuple["isSlot"] = Json::Value(_info.second.isSlot);
+ tuple["isOffset"] = Json::Value(_info.second.isOffset);
+ tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));
+ return tuple;
+}
- m_astJson["name"] = "SourceUnit";
- m_astJson["children"] = children;
- m_jsonNodePtrs.push(&m_astJson["children"]);
+void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node)
+{
+ _stream << toJson(_node);
+}
- return true;
+Json::Value ASTJsonConverter::toJson(ASTNode const& _node)
+{
+ _node.accept(*this);
+ return std::move(m_currentValue);
+}
+
+bool ASTJsonConverter::visit(SourceUnit const& _node)
+{
+ Json::Value exportedSymbols = Json::objectValue;
+ for (auto const& sym: _node.annotation().exportedSymbols)
+ {
+ exportedSymbols[sym.first] = Json::arrayValue;
+ for (Declaration const* overload: sym.second)
+ exportedSymbols[sym.first].append(nodeId(*overload));
+ }
+ setJsonNode(
+ _node,
+ "SourceUnit",
+ {
+ make_pair("absolutePath", _node.annotation().path),
+ make_pair("exportedSymbols", move(exportedSymbols)),
+ make_pair("nodes", toJson(_node.nodes()))
+ }
+ );
+ return false;
}
bool ASTJsonConverter::visit(PragmaDirective const& _node)
@@ -109,551 +218,594 @@ bool ASTJsonConverter::visit(PragmaDirective const& _node)
Json::Value literals(Json::arrayValue);
for (auto const& literal: _node.literals())
literals.append(literal);
- addJsonNode(_node, "PragmaDirective", { make_pair("literals", literals) });
- return true;
+ setJsonNode( _node, "PragmaDirective", {
+ make_pair("literals", std::move(literals))
+ });
+ return false;
}
bool ASTJsonConverter::visit(ImportDirective const& _node)
{
- addJsonNode(_node, "ImportDirective", { make_pair("file", _node.path())});
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("file", _node.path()),
+ make_pair("absolutePath", _node.annotation().absolutePath),
+ make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)),
+ make_pair("scope", idOrNull(_node.scope()))
+ };
+ attributes.push_back(make_pair("unitAlias", _node.name()));
+ Json::Value symbolAliases(Json::arrayValue);
+ for (auto const& symbolAlias: _node.symbolAliases())
+ {
+ Json::Value tuple(Json::objectValue);
+ solAssert(symbolAlias.first, "");
+ tuple["foreign"] = nodeId(*symbolAlias.first);
+ tuple["local"] = symbolAlias.second ? Json::Value(*symbolAlias.second) : Json::nullValue;
+ symbolAliases.append(tuple);
+ }
+ attributes.push_back(make_pair("symbolAliases", std::move(symbolAliases)));
+ setJsonNode(_node, "ImportDirective", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(ContractDefinition const& _node)
{
- Json::Value linearizedBaseContracts(Json::arrayValue);
- for (auto const& baseContract: _node.annotation().linearizedBaseContracts)
- linearizedBaseContracts.append(reinterpret_cast<Json::UInt64>(baseContract));
- addJsonNode(_node, "ContractDefinition", {
+ setJsonNode(_node, "ContractDefinition", {
make_pair("name", _node.name()),
- make_pair("isLibrary", _node.isLibrary()),
+ make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
+ make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("fullyImplemented", _node.annotation().isFullyImplemented),
- make_pair("linearizedBaseContracts", linearizedBaseContracts),
- }, true);
- return true;
+ make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
+ make_pair("baseContracts", toJson(_node.baseContracts())),
+ make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies)),
+ make_pair("nodes", toJson(_node.subNodes())),
+ make_pair("scope", idOrNull(_node.scope()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(InheritanceSpecifier const& _node)
{
- addJsonNode(_node, "InheritanceSpecifier", {}, true);
- return true;
+ setJsonNode(_node, "InheritanceSpecifier", {
+ make_pair("baseName", toJson(_node.name())),
+ make_pair("arguments", toJson(_node.arguments()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(UsingForDirective const& _node)
{
- addJsonNode(_node, "UsingForDirective", {}, true);
- return true;
+ setJsonNode(_node, "UsingForDirective", {
+ make_pair("libraryName", toJson(_node.libraryName())),
+ make_pair("typeName", _node.typeName() ? toJson(*_node.typeName()) : Json::nullValue)
+ });
+ return false;
}
bool ASTJsonConverter::visit(StructDefinition const& _node)
{
- addJsonNode(_node, "StructDefinition", { make_pair("name", _node.name()) }, true);
- return true;
+ setJsonNode(_node, "StructDefinition", {
+ make_pair("name", _node.name()),
+ make_pair("visibility", visibility(_node.visibility())),
+ make_pair("canonicalName", _node.annotation().canonicalName),
+ make_pair("members", toJson(_node.members())),
+ make_pair("scope", idOrNull(_node.scope()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(EnumDefinition const& _node)
{
- addJsonNode(_node, "EnumDefinition", { make_pair("name", _node.name()) }, true);
- return true;
+ setJsonNode(_node, "EnumDefinition", {
+ make_pair("name", _node.name()),
+ make_pair("canonicalName", _node.annotation().canonicalName),
+ make_pair("members", toJson(_node.members()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(EnumValue const& _node)
{
- addJsonNode(_node, "EnumValue", { make_pair("name", _node.name()) });
- return true;
+ setJsonNode(_node, "EnumValue", {
+ make_pair("name", _node.name())
+ });
+ return false;
}
bool ASTJsonConverter::visit(ParameterList const& _node)
{
- addJsonNode(_node, "ParameterList", {}, true);
- return true;
+ setJsonNode(_node, "ParameterList", {
+ make_pair("parameters", toJson(_node.parameters()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(FunctionDefinition const& _node)
{
- addJsonNode(_node, "FunctionDefinition", {
+ std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
- make_pair("public", _node.isPublic()),
- make_pair("constant", _node.isDeclaredConst()),
- make_pair("payable", _node.isPayable())
- }, true);
- return true;
+ make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()),
+ make_pair("payable", _node.isPayable()),
+ make_pair("visibility", visibility(_node.visibility())),
+ make_pair("parameters", toJson(_node.parameterList())),
+ make_pair("isConstructor", _node.isConstructor()),
+ make_pair("returnParameters", toJson(*_node.returnParameterList())),
+ make_pair("modifiers", toJson(_node.modifiers())),
+ make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue),
+ make_pair("implemented", _node.isImplemented()),
+ make_pair("scope", idOrNull(_node.scope()))
+ };
+ setJsonNode(_node, "FunctionDefinition", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(VariableDeclaration const& _node)
{
- addJsonNode(_node, "VariableDeclaration", {
+ std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
- make_pair("type", type(_node))
- }, true);
- return true;
+ make_pair("typeName", toJsonOrNull(_node.typeName())),
+ make_pair("constant", _node.isConstant()),
+ make_pair("stateVariable", _node.isStateVariable()),
+ make_pair("storageLocation", location(_node.referenceLocation())),
+ make_pair("visibility", visibility(_node.visibility())),
+ make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue),
+ make_pair("scope", idOrNull(_node.scope())),
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ };
+ if (m_inEvent)
+ attributes.push_back(make_pair("indexed", _node.isIndexed()));
+ setJsonNode(_node, "VariableDeclaration", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(ModifierDefinition const& _node)
{
- addJsonNode(_node, "ModifierDefinition", { make_pair("name", _node.name()) }, true);
- return true;
+ setJsonNode(_node, "ModifierDefinition", {
+ make_pair("name", _node.name()),
+ make_pair("visibility", visibility(_node.visibility())),
+ make_pair("parameters", toJson(_node.parameterList())),
+ make_pair("body", toJson(_node.body()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(ModifierInvocation const& _node)
{
- addJsonNode(_node, "ModifierInvocation", {}, true);
- return true;
+ setJsonNode(_node, "ModifierInvocation", {
+ make_pair("modifierName", toJson(*_node.name())),
+ make_pair("arguments", toJson(_node.arguments()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(TypeName const&)
{
- return true;
+ solAssert(false, "AST node of abstract type used.");
+ return false;
}
bool ASTJsonConverter::visit(EventDefinition const& _node)
{
- addJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()) }, true);
- return true;
+ m_inEvent = true;
+ setJsonNode(_node, "EventDefinition", {
+ make_pair("name", _node.name()),
+ make_pair("parameters", toJson(_node.parameterList())),
+ make_pair("anonymous", _node.isAnonymous())
+ });
+ return false;
}
bool ASTJsonConverter::visit(ElementaryTypeName const& _node)
{
- addJsonNode(_node, "ElementaryTypeName", { make_pair("name", _node.typeName().toString()) });
- return true;
+ setJsonNode(_node, "ElementaryTypeName", {
+ make_pair("name", _node.typeName().toString()),
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ });
+ return false;
}
bool ASTJsonConverter::visit(UserDefinedTypeName const& _node)
{
- addJsonNode(_node, "UserDefinedTypeName", {
- make_pair("name", boost::algorithm::join(_node.namePath(), "."))
+ setJsonNode(_node, "UserDefinedTypeName", {
+ make_pair("name", namePathToString(_node.namePath())),
+ make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)),
+ make_pair("contractScope", idOrNull(_node.annotation().contractScope)),
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
});
- return true;
+ return false;
}
bool ASTJsonConverter::visit(FunctionTypeName const& _node)
{
- string visibility = "internal";
- if (_node.visibility() == Declaration::Visibility::External)
- visibility = "external";
-
- addJsonNode(_node, "FunctionTypeName", {
+ setJsonNode(_node, "FunctionTypeName", {
make_pair("payable", _node.isPayable()),
- make_pair("visibility", visibility),
- make_pair("constant", _node.isDeclaredConst())
- }, true);
- return true;
+ make_pair("visibility", visibility(_node.visibility())),
+ make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()),
+ make_pair("parameterTypes", toJson(*_node.parameterTypeList())),
+ make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())),
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ });
+ return false;
}
bool ASTJsonConverter::visit(Mapping const& _node)
{
- addJsonNode(_node, "Mapping", {}, true);
- return true;
+ setJsonNode(_node, "Mapping", {
+ make_pair("keyType", toJson(_node.keyType())),
+ make_pair("valueType", toJson(_node.valueType())),
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ });
+ return false;
}
bool ASTJsonConverter::visit(ArrayTypeName const& _node)
{
- addJsonNode(_node, "ArrayTypeName", {}, true);
- return true;
+ setJsonNode(_node, "ArrayTypeName", {
+ make_pair("baseType", toJson(_node.baseType())),
+ make_pair("length", toJsonOrNull(_node.length())),
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ });
+ return false;
}
bool ASTJsonConverter::visit(InlineAssembly const& _node)
{
- addJsonNode(_node, "InlineAssembly", {}, true);
- return true;
+ Json::Value externalReferences(Json::arrayValue);
+ for (auto const& it : _node.annotation().externalReferences)
+ {
+ if (it.first)
+ {
+ Json::Value tuple(Json::objectValue);
+ tuple[it.first->name] = inlineAssemblyIdentifierToJson(it);
+ externalReferences.append(tuple);
+ }
+ }
+ setJsonNode(_node, "InlineAssembly", {
+ make_pair("operations", Json::Value(assembly::AsmPrinter()(_node.operations()))),
+ make_pair("externalReferences", std::move(externalReferences))
+ });
+ return false;
}
bool ASTJsonConverter::visit(Block const& _node)
{
- addJsonNode(_node, "Block", {}, true);
- return true;
+ setJsonNode(_node, "Block", {
+ make_pair("statements", toJson(_node.statements()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(PlaceholderStatement const& _node)
{
- addJsonNode(_node, "PlaceholderStatement", {});
- return true;
+ setJsonNode(_node, "PlaceholderStatement", {});
+ return false;
}
bool ASTJsonConverter::visit(IfStatement const& _node)
{
- addJsonNode(_node, "IfStatement", {}, true);
- return true;
+ setJsonNode(_node, "IfStatement", {
+ make_pair("condition", toJson(_node.condition())),
+ make_pair("trueBody", toJson(_node.trueStatement())),
+ make_pair("falseBody", toJsonOrNull(_node.falseStatement()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(WhileStatement const& _node)
{
- addJsonNode(
+ setJsonNode(
_node,
_node.isDoWhile() ? "DoWhileStatement" : "WhileStatement",
- {},
- true);
- return true;
+ {
+ make_pair("condition", toJson(_node.condition())),
+ make_pair("body", toJson(_node.body()))
+ }
+ );
+ return false;
}
bool ASTJsonConverter::visit(ForStatement const& _node)
{
- addJsonNode(_node, "ForStatement", {}, true);
- return true;
+ setJsonNode(_node, "ForStatement", {
+ make_pair("initializationExpression", toJsonOrNull(_node.initializationExpression())),
+ make_pair("condition", toJsonOrNull(_node.condition())),
+ make_pair("loopExpression", toJsonOrNull(_node.loopExpression())),
+ make_pair("body", toJson(_node.body()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(Continue const& _node)
{
- addJsonNode(_node, "Continue", {});
- return true;
+ setJsonNode(_node, "Continue", {});
+ return false;
}
bool ASTJsonConverter::visit(Break const& _node)
{
- addJsonNode(_node, "Break", {});
- return true;
+ setJsonNode(_node, "Break", {});
+ return false;
}
bool ASTJsonConverter::visit(Return const& _node)
{
- addJsonNode(_node, "Return", {}, true);;
- return true;
+ setJsonNode(_node, "Return", {
+ make_pair("expression", toJsonOrNull(_node.expression())),
+ make_pair("functionReturnParameters", idOrNull(_node.annotation().functionReturnParameters))
+ });
+ return false;
}
bool ASTJsonConverter::visit(Throw const& _node)
{
- addJsonNode(_node, "Throw", {}, true);;
- return true;
+ setJsonNode(_node, "Throw", {});;
+ return false;
}
bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node)
{
- addJsonNode(_node, "VariableDefinitionStatement", {}, true);
- return true;
+ Json::Value varDecs(Json::arrayValue);
+ for (auto const& v: _node.annotation().assignments)
+ varDecs.append(idOrNull(v));
+ setJsonNode(_node, "VariableDeclarationStatement", {
+ make_pair("assignments", std::move(varDecs)),
+ make_pair("declarations", toJson(_node.declarations())),
+ make_pair("initialValue", toJsonOrNull(_node.initialValue()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(ExpressionStatement const& _node)
{
- addJsonNode(_node, "ExpressionStatement", {}, true);
- return true;
+ setJsonNode(_node, "ExpressionStatement", {
+ make_pair("expression", toJson(_node.expression()))
+ });
+ return false;
}
bool ASTJsonConverter::visit(Conditional const& _node)
{
- addJsonNode(_node, "Conditional", {}, true);
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("condition", toJson(_node.condition())),
+ make_pair("trueExpression", toJson(_node.trueExpression())),
+ make_pair("falseExpression", toJson(_node.falseExpression()))
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "Conditional", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(Assignment const& _node)
{
- addJsonNode(_node, "Assignment",
- { make_pair("operator", Token::toString(_node.assignmentOperator())),
- make_pair("type", type(_node)) },
- true);
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("operator", Token::toString(_node.assignmentOperator())),
+ make_pair("leftHandSide", toJson(_node.leftHandSide())),
+ make_pair("rightHandSide", toJson(_node.rightHandSide()))
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode( _node, "Assignment", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(TupleExpression const& _node)
{
- addJsonNode(_node, "TupleExpression",{}, true);
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("isInlineArray", Json::Value(_node.isInlineArray())),
+ make_pair("components", toJson(_node.components())),
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "TupleExpression", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(UnaryOperation const& _node)
{
- addJsonNode(_node, "UnaryOperation",
- { make_pair("prefix", _node.isPrefixOperation()),
- make_pair("operator", Token::toString(_node.getOperator())),
- make_pair("type", type(_node)) },
- true);
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("prefix", _node.isPrefixOperation()),
+ make_pair("operator", Token::toString(_node.getOperator())),
+ make_pair("subExpression", toJson(_node.subExpression()))
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "UnaryOperation", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(BinaryOperation const& _node)
{
- addJsonNode(_node, "BinaryOperation", {
+ std::vector<pair<string, Json::Value>> attributes = {
make_pair("operator", Token::toString(_node.getOperator())),
- make_pair("type", type(_node))
- }, true);
- return true;
+ make_pair("leftExpression", toJson(_node.leftExpression())),
+ make_pair("rightExpression", toJson(_node.rightExpression())),
+ make_pair("commonType", typePointerToJson(_node.annotation().commonType)),
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "BinaryOperation", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(FunctionCall const& _node)
{
- addJsonNode(_node, "FunctionCall", {
- make_pair("type_conversion", _node.annotation().isTypeConversion),
- make_pair("type", type(_node))
- }, true);
- return true;
+ Json::Value names(Json::arrayValue);
+ for (auto const& name: _node.names())
+ names.append(Json::Value(*name));
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("expression", toJson(_node.expression())),
+ make_pair("names", std::move(names)),
+ make_pair("arguments", toJson(_node.arguments()))
+ };
+ if (m_legacy)
+ {
+ attributes.push_back(make_pair("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall));
+ attributes.push_back(make_pair("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion));
+ }
+ else
+ attributes.push_back(make_pair("kind", functionCallKind(_node.annotation().kind)));
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "FunctionCall", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(NewExpression const& _node)
{
- addJsonNode(_node, "NewExpression", { make_pair("type", type(_node)) }, true);
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("typeName", toJson(_node.typeName()))
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "NewExpression", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(MemberAccess const& _node)
{
- addJsonNode(_node, "MemberAccess", {
- make_pair("member_name", _node.memberName()),
- make_pair("type", type(_node))
- }, true);
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair(m_legacy ? "member_name" : "memberName", _node.memberName()),
+ make_pair("expression", toJson(_node.expression())),
+ make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)),
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "MemberAccess", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(IndexAccess const& _node)
{
- addJsonNode(_node, "IndexAccess", { make_pair("type", type(_node)) }, true);
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair("baseExpression", toJson(_node.baseExpression())),
+ make_pair("indexExpression", toJsonOrNull(_node.indexExpression())),
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "IndexAccess", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(Identifier const& _node)
{
- addJsonNode(_node, "Identifier",
- { make_pair("value", _node.name()), make_pair("type", type(_node)) });
- return true;
+ Json::Value overloads(Json::arrayValue);
+ for (auto const& dec: _node.annotation().overloadedDeclarations)
+ overloads.append(nodeId(*dec));
+ setJsonNode(_node, "Identifier", {
+ make_pair(m_legacy ? "value" : "name", _node.name()),
+ make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)),
+ make_pair("overloadedDeclarations", overloads),
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)),
+ make_pair("argumentTypes", typePointerToJson(_node.annotation().argumentTypes))
+ });
+ return false;
}
bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node)
{
- addJsonNode(_node, "ElementaryTypenameExpression", {
- make_pair("value", _node.typeName().toString()),
- make_pair("type", type(_node))
- });
- return true;
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair(m_legacy ? "value" : "typeName", _node.typeName().toString())
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "ElementaryTypeNameExpression", std::move(attributes));
+ return false;
}
bool ASTJsonConverter::visit(Literal const& _node)
{
- char const* tokenString = Token::toString(_node.token());
Json::Value value{_node.value()};
if (!dev::validateUTF8(_node.value()))
value = Json::nullValue;
Token::Value subdenomination = Token::Value(_node.subDenomination());
- addJsonNode(_node, "Literal", {
- make_pair("token", tokenString ? tokenString : Json::Value()),
+ std::vector<pair<string, Json::Value>> attributes = {
+ make_pair(m_legacy ? "token" : "kind", literalTokenKind(_node.token())),
make_pair("value", value),
- make_pair("hexvalue", toHex(_node.value())),
+ make_pair(m_legacy ? "hexvalue" : "hexValue", toHex(_node.value())),
make_pair(
"subdenomination",
subdenomination == Token::Illegal ?
Json::nullValue :
Json::Value{Token::toString(subdenomination)}
- ),
- make_pair("type", type(_node))
- });
- return true;
-}
-
-void ASTJsonConverter::endVisit(SourceUnit const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(PragmaDirective const&)
-{
-}
-
-void ASTJsonConverter::endVisit(ImportDirective const&)
-{
-}
-
-void ASTJsonConverter::endVisit(ContractDefinition const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(InheritanceSpecifier const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(UsingForDirective const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(StructDefinition const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(EnumDefinition const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(EnumValue const&)
-{
-}
-
-void ASTJsonConverter::endVisit(ParameterList const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(FunctionDefinition const&)
-{
- goUp();
+ )
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "Literal", std::move(attributes));
+ return false;
}
-void ASTJsonConverter::endVisit(VariableDeclaration const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(ModifierDefinition const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(ModifierInvocation const&)
-{
- goUp();
-}
void ASTJsonConverter::endVisit(EventDefinition const&)
{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(TypeName const&)
-{
-}
-
-void ASTJsonConverter::endVisit(ElementaryTypeName const&)
-{
-}
-
-void ASTJsonConverter::endVisit(UserDefinedTypeName const&)
-{
-}
-
-void ASTJsonConverter::endVisit(FunctionTypeName const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(Mapping const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(ArrayTypeName const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(InlineAssembly const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(Block const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(PlaceholderStatement const&)
-{
-}
-
-void ASTJsonConverter::endVisit(IfStatement const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(WhileStatement const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(ForStatement const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(Continue const&)
-{
+ m_inEvent = false;
}
-void ASTJsonConverter::endVisit(Break const&)
+string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility)
{
+ switch (_visibility)
+ {
+ case Declaration::Visibility::Private:
+ return "private";
+ case Declaration::Visibility::Internal:
+ return "internal";
+ case Declaration::Visibility::Public:
+ return "public";
+ case Declaration::Visibility::External:
+ return "external";
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown declaration visibility."));
+ }
}
-void ASTJsonConverter::endVisit(Return const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(Throw const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(VariableDeclarationStatement const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(ExpressionStatement const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(Conditional const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(Assignment const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(TupleExpression const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(UnaryOperation const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(BinaryOperation const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(FunctionCall const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(NewExpression const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(MemberAccess const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(IndexAccess const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(Identifier const&)
+string ASTJsonConverter::location(VariableDeclaration::Location _location)
{
+ switch (_location)
+ {
+ case VariableDeclaration::Location::Default:
+ return "default";
+ case VariableDeclaration::Location::Storage:
+ return "storage";
+ case VariableDeclaration::Location::Memory:
+ return "memory";
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown declaration location."));
+ }
}
-void ASTJsonConverter::endVisit(ElementaryTypeNameExpression const&)
+string ASTJsonConverter::contractKind(ContractDefinition::ContractKind _kind)
{
+ switch (_kind)
+ {
+ case ContractDefinition::ContractKind::Interface:
+ return "interface";
+ case ContractDefinition::ContractKind::Contract:
+ return "contract";
+ case ContractDefinition::ContractKind::Library:
+ return "library";
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of contract."));
+ }
}
-void ASTJsonConverter::endVisit(Literal const&)
+string ASTJsonConverter::functionCallKind(FunctionCallKind _kind)
{
+ switch (_kind)
+ {
+ case FunctionCallKind::FunctionCall:
+ return "functionCall";
+ case FunctionCallKind::TypeConversion:
+ return "typeConversion";
+ case FunctionCallKind::StructConstructorCall:
+ return "structConstructorCall";
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of function call ."));
+ }
}
-void ASTJsonConverter::process()
+string ASTJsonConverter::literalTokenKind(Token::Value _token)
{
- if (!processed)
- m_ast->accept(*this);
- processed = true;
+ switch (_token)
+ {
+ case dev::solidity::Token::Number:
+ return "number";
+ case dev::solidity::Token::StringLiteral:
+ return "string";
+ case dev::solidity::Token::TrueLiteral:
+ case dev::solidity::Token::FalseLiteral:
+ return "bool";
+ default:
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of literal token."));
+ }
}
string ASTJsonConverter::type(Expression const& _expression)
diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h
index 277476d5..27114c2a 100644
--- a/libsolidity/ast/ASTJsonConverter.h
+++ b/libsolidity/ast/ASTJsonConverter.h
@@ -26,7 +26,6 @@
#include <stack>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <json/json.h>
@@ -42,15 +41,23 @@ class ASTJsonConverter: public ASTConstVisitor
{
public:
/// Create a converter to JSON for the given abstract syntax tree.
+ /// @a _legacy if true, use legacy format
/// @a _sourceIndices is used to abbreviate source names in source locations.
explicit ASTJsonConverter(
- ASTNode const& _ast,
+ bool _legacy,
std::map<std::string, unsigned> _sourceIndices = std::map<std::string, unsigned>()
);
/// Output the json representation of the AST to _stream.
- void print(std::ostream& _stream);
- Json::Value const& json();
-
+ void print(std::ostream& _stream, ASTNode const& _node);
+ Json::Value toJson(ASTNode const& _node);
+ template <class T>
+ Json::Value toJson(std::vector<ASTPointer<T>> const& _nodes)
+ {
+ Json::Value ret(Json::arrayValue);
+ for (auto const& n: _nodes)
+ ret.append(n ? toJson(*n) : Json::nullValue);
+ return ret;
+ }
bool visit(SourceUnit const& _node) override;
bool visit(PragmaDirective const& _node) override;
bool visit(ImportDirective const& _node) override;
@@ -97,73 +104,61 @@ public:
bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override;
- void endVisit(SourceUnit const&) override;
- void endVisit(PragmaDirective const&) override;
- void endVisit(ImportDirective const&) override;
- void endVisit(ContractDefinition const&) override;
- void endVisit(InheritanceSpecifier const&) override;
- void endVisit(UsingForDirective const&) override;
- void endVisit(StructDefinition const&) override;
- void endVisit(EnumDefinition const&) override;
- void endVisit(EnumValue const&) override;
- void endVisit(ParameterList const&) override;
- void endVisit(FunctionDefinition const&) override;
- void endVisit(VariableDeclaration const&) override;
- void endVisit(ModifierDefinition const&) override;
- void endVisit(ModifierInvocation const&) override;
void endVisit(EventDefinition const&) override;
- void endVisit(TypeName const&) override;
- void endVisit(ElementaryTypeName const&) override;
- void endVisit(UserDefinedTypeName const&) override;
- void endVisit(FunctionTypeName const&) override;
- void endVisit(Mapping const&) override;
- void endVisit(ArrayTypeName const&) override;
- void endVisit(InlineAssembly const&) override;
- void endVisit(Block const&) override;
- void endVisit(PlaceholderStatement const&) override;
- void endVisit(IfStatement const&) override;
- void endVisit(WhileStatement const&) override;
- void endVisit(ForStatement const&) override;
- void endVisit(Continue const&) override;
- void endVisit(Break const&) override;
- void endVisit(Return const&) override;
- void endVisit(Throw const&) override;
- void endVisit(VariableDeclarationStatement const&) override;
- void endVisit(ExpressionStatement const&) override;
- void endVisit(Conditional const&) override;
- void endVisit(Assignment const&) override;
- void endVisit(TupleExpression const&) override;
- void endVisit(UnaryOperation const&) override;
- void endVisit(BinaryOperation const&) override;
- void endVisit(FunctionCall const&) override;
- void endVisit(NewExpression const&) override;
- void endVisit(MemberAccess const&) override;
- void endVisit(IndexAccess const&) override;
- void endVisit(Identifier const&) override;
- void endVisit(ElementaryTypeNameExpression const&) override;
- void endVisit(Literal const&) override;
private:
- void process();
- void addJsonNode(
+ void setJsonNode(
ASTNode const& _node,
std::string const& _nodeName,
- std::initializer_list<std::pair<std::string const, Json::Value const>> _attributes,
- bool _hasChildren
+ std::initializer_list<std::pair<std::string, Json::Value>>&& _attributes
+ );
+ void setJsonNode(
+ ASTNode const& _node,
+ std::string const& _nodeName,
+ std::vector<std::pair<std::string, Json::Value>>&& _attributes
);
std::string sourceLocationToString(SourceLocation const& _location) const;
+ std::string namePathToString(std::vector<ASTString> const& _namePath) const;
+ Json::Value idOrNull(ASTNode const* _pt)
+ {
+ return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue;
+ }
+ Json::Value toJsonOrNull(ASTNode const* _node)
+ {
+ return _node ? toJson(*_node) : Json::nullValue;
+ }
+ Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _info);
+ std::string visibility(Declaration::Visibility const& _visibility);
+ std::string location(VariableDeclaration::Location _location);
+ std::string contractKind(ContractDefinition::ContractKind _kind);
+ std::string functionCallKind(FunctionCallKind _kind);
+ std::string literalTokenKind(Token::Value _token);
std::string type(Expression const& _expression);
std::string type(VariableDeclaration const& _varDecl);
- inline void goUp()
+ int nodeId(ASTNode const& _node)
{
- solAssert(!m_jsonNodePtrs.empty(), "Uneven json nodes stack. Internal error.");
- m_jsonNodePtrs.pop();
+ return _node.id();
}
-
- bool processed = false;
- Json::Value m_astJson;
- std::stack<Json::Value*> m_jsonNodePtrs;
- ASTNode const* m_ast;
+ template<class Container>
+ Json::Value getContainerIds(Container const& container)
+ {
+ Json::Value tmp(Json::arrayValue);
+ for (auto const& element: container)
+ {
+ solAssert(element, "");
+ tmp.append(nodeId(*element));
+ }
+ return tmp;
+ }
+ Json::Value typePointerToJson(TypePointer _tp);
+ Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps);
+ void appendExpressionAttributes(
+ std::vector<std::pair<std::string, Json::Value>> &_attributes,
+ ExpressionAnnotation const& _annotation
+ );
+ bool m_legacy = false; ///< if true, use legacy format
+ bool m_inEvent = false; ///< whether we are currently inside an event or not
+ Json::Value m_currentValue;
std::map<std::string, unsigned> m_sourceIndices;
};
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index d9660bc0..6ecf509d 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -21,15 +21,22 @@
*/
#include <libsolidity/ast/Types.h>
-#include <limits>
-#include <boost/range/adaptor/reversed.hpp>
-#include <boost/range/adaptor/sliced.hpp>
+
+#include <libsolidity/ast/AST.h>
+
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/SHA3.h>
#include <libdevcore/UTF8.h>
-#include <libsolidity/interface/Utils.h>
-#include <libsolidity/ast/AST.h>
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/range/adaptor/sliced.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+
+#include <limits>
using namespace std;
using namespace dev;
@@ -117,6 +124,51 @@ u256 const& MemberList::storageSize() const
return m_storageOffsets->storageSize();
}
+/// Helper functions for type identifier
+namespace
+{
+
+string parenthesizeIdentifier(string const& _internal)
+{
+ return "$_" + _internal + "_$";
+}
+
+template <class Range>
+string identifierList(Range const&& _list)
+{
+ return parenthesizeIdentifier(boost::algorithm::join(_list, "_$_"));
+}
+
+string identifier(TypePointer const& _type)
+{
+ return _type ? _type->identifier() : "";
+}
+
+string identifierList(vector<TypePointer> const& _list)
+{
+ return identifierList(_list | boost::adaptors::transformed(identifier));
+}
+
+string identifierList(TypePointer const& _type)
+{
+ return parenthesizeIdentifier(identifier(_type));
+}
+
+string identifierList(TypePointer const& _type1, TypePointer const& _type2)
+{
+ TypePointers list;
+ list.push_back(_type1);
+ list.push_back(_type2);
+ return identifierList(list);
+}
+
+string parenthesizeUserIdentifier(string const& _internal)
+{
+ return parenthesizeIdentifier(boost::algorithm::replace_all_copy(_internal, "$", "$$$"));
+}
+
+}
+
TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
{
solAssert(Token::isElementaryTypeName(_type.token()),
@@ -200,9 +252,9 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b)
{
if (!_a || !_b)
return TypePointer();
- else if (_b->isImplicitlyConvertibleTo(*_a->mobileType()))
+ else if (_a->mobileType() && _b->isImplicitlyConvertibleTo(*_a->mobileType()))
return _a->mobileType();
- else if (_a->isImplicitlyConvertibleTo(*_b->mobileType()))
+ else if (_b->mobileType() && _a->isImplicitlyConvertibleTo(*_b->mobileType()))
return _b->mobileType();
else
return TypePointer();
@@ -251,6 +303,19 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
return members;
}
+bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountType)
+{
+ // Disable >>> here.
+ if (_operator == Token::SHR)
+ return false;
+ else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType))
+ return !otherInt->isAddress();
+ else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType))
+ return otherRat->integerType() && !otherRat->integerType()->isSigned();
+ else
+ return false;
+}
+
IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
m_bits(_bits), m_modifier(_modifier)
{
@@ -259,7 +324,15 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
solAssert(
m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
"Invalid bit number for integer type: " + dev::toString(_bits)
- );
+ );
+}
+
+string IntegerType::identifier() const
+{
+ if (isAddress())
+ return "t_address";
+ else
+ return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits());
}
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -332,6 +405,30 @@ string IntegerType::toString(bool) const
return prefix + dev::toString(m_bits);
}
+u256 IntegerType::literalValue(Literal const* _literal) const
+{
+ solAssert(m_modifier == Modifier::Address, "");
+ solAssert(_literal, "");
+ solAssert(_literal->value().substr(0, 2) == "0x", "");
+ return u256(_literal->value());
+}
+
+bigint IntegerType::minValue() const
+{
+ if (isSigned())
+ return -(bigint(1) << (m_bits - 1));
+ else
+ return bigint(0);
+}
+
+bigint IntegerType::maxValue() const
+{
+ if (isSigned())
+ return (bigint(1) << (m_bits - 1)) - 1;
+ else
+ return (bigint(1) << m_bits) - 1;
+}
+
TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
if (
@@ -340,6 +437,17 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe
_other->category() != category()
)
return TypePointer();
+ if (Token::isShiftOp(_operator))
+ {
+ // Shifts are not symmetric with respect to the type
+ if (isAddress())
+ return TypePointer();
+ if (isValidShiftAndAmountType(_operator, *_other))
+ return shared_from_this();
+ else
+ return TypePointer();
+ }
+
auto commonType = Type::commonType(shared_from_this(), _other); //might be a integer or fixed point
if (!commonType)
return TypePointer();
@@ -369,10 +477,11 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
if (isAddress())
return {
{"balance", make_shared<IntegerType >(256)},
- {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true, false, true)},
- {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true, false, true)},
- {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)},
- {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}
+ {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::Bare, true, false, true)},
+ {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)},
+ {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)},
+ {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)},
+ {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}
};
else
return MemberList::MemberMap();
@@ -388,7 +497,12 @@ FixedPointType::FixedPointType(int _integerBits, int _fractionalBits, FixedPoint
m_fractionalBits % 8 == 0,
"Invalid bit number(s) for fixed type: " +
dev::toString(_integerBits) + "x" + dev::toString(_fractionalBits)
- );
+ );
+}
+
+string FixedPointType::identifier() const
+{
+ return "t_" + string(isSigned() ? "" : "u") + "fixed" + std::to_string(integerBits()) + "x" + std::to_string(fractionalBits());
}
bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -473,39 +587,99 @@ TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePoi
return commonType;
}
-tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal)
+tuple<bool, rational> RationalNumberType::parseRational(string const& _value)
{
- rational x;
+ rational value;
try
{
- rational numerator;
- rational denominator(1);
-
- auto radixPoint = find(_literal.value().begin(), _literal.value().end(), '.');
- if (radixPoint != _literal.value().end())
+ auto radixPoint = find(_value.begin(), _value.end(), '.');
+
+ if (radixPoint != _value.end())
{
if (
- !all_of(radixPoint + 1, _literal.value().end(), ::isdigit) ||
- !all_of(_literal.value().begin(), radixPoint, ::isdigit)
+ !all_of(radixPoint + 1, _value.end(), ::isdigit) ||
+ !all_of(_value.begin(), radixPoint, ::isdigit)
)
return make_tuple(false, rational(0));
- //Only decimal notation allowed here, leading zeros would switch to octal.
+
+ // Only decimal notation allowed here, leading zeros would switch to octal.
auto fractionalBegin = find_if_not(
- radixPoint + 1,
- _literal.value().end(),
+ radixPoint + 1,
+ _value.end(),
[](char const& a) { return a == '0'; }
);
- denominator = bigint(string(fractionalBegin, _literal.value().end()));
+ rational numerator;
+ rational denominator(1);
+
+ denominator = bigint(string(fractionalBegin, _value.end()));
denominator /= boost::multiprecision::pow(
- bigint(10),
- distance(radixPoint + 1, _literal.value().end())
+ bigint(10),
+ distance(radixPoint + 1, _value.end())
);
- numerator = bigint(string(_literal.value().begin(), radixPoint));
- x = numerator + denominator;
+ numerator = bigint(string(_value.begin(), radixPoint));
+ value = numerator + denominator;
+ }
+ else
+ value = bigint(_value);
+ return make_tuple(true, value);
+ }
+ catch (...)
+ {
+ return make_tuple(false, rational(0));
+ }
+}
+
+tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal)
+{
+ rational value;
+ try
+ {
+ auto expPoint = find(_literal.value().begin(), _literal.value().end(), 'e');
+ if (expPoint == _literal.value().end())
+ expPoint = find(_literal.value().begin(), _literal.value().end(), 'E');
+
+ if (boost::starts_with(_literal.value(), "0x"))
+ {
+ // process as hex
+ value = bigint(_literal.value());
+ }
+ else if (expPoint != _literal.value().end())
+ {
+ // parse the exponent
+ bigint exp = bigint(string(expPoint + 1, _literal.value().end()));
+
+ if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
+ return make_tuple(false, rational(0));
+
+ // parse the base
+ tuple<bool, rational> base = parseRational(string(_literal.value().begin(), expPoint));
+ if (!get<0>(base))
+ return make_tuple(false, rational(0));
+ value = get<1>(base);
+
+ if (exp < 0)
+ {
+ exp *= -1;
+ value /= boost::multiprecision::pow(
+ bigint(10),
+ exp.convert_to<int32_t>()
+ );
+ }
+ else
+ value *= boost::multiprecision::pow(
+ bigint(10),
+ exp.convert_to<int32_t>()
+ );
}
else
- x = bigint(_literal.value());
+ {
+ // parse as rational number
+ tuple<bool, rational> tmp = parseRational(_literal.value());
+ if (!get<0>(tmp))
+ return tmp;
+ value = get<1>(tmp);
+ }
}
catch (...)
{
@@ -518,33 +692,33 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
case Literal::SubDenomination::Second:
break;
case Literal::SubDenomination::Szabo:
- x *= bigint("1000000000000");
+ value *= bigint("1000000000000");
break;
case Literal::SubDenomination::Finney:
- x *= bigint("1000000000000000");
+ value *= bigint("1000000000000000");
break;
case Literal::SubDenomination::Ether:
- x *= bigint("1000000000000000000");
+ value *= bigint("1000000000000000000");
break;
case Literal::SubDenomination::Minute:
- x *= bigint("60");
+ value *= bigint("60");
break;
case Literal::SubDenomination::Hour:
- x *= bigint("3600");
+ value *= bigint("3600");
break;
case Literal::SubDenomination::Day:
- x *= bigint("86400");
+ value *= bigint("86400");
break;
case Literal::SubDenomination::Week:
- x *= bigint("604800");
+ value *= bigint("604800");
break;
case Literal::SubDenomination::Year:
- x *= bigint("31536000");
+ value *= bigint("31536000");
break;
}
- return make_tuple(true, x);
+ return make_tuple(true, value);
}
bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -552,12 +726,12 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
if (_convertTo.category() == Category::Integer)
{
auto targetType = dynamic_cast<IntegerType const*>(&_convertTo);
- if (m_value == 0)
+ if (m_value == rational(0))
return true;
if (isFractional())
return false;
int forSignBit = (targetType->isSigned() ? 1 : 0);
- if (m_value > 0)
+ if (m_value > rational(0))
{
if (m_value.numerator() <= (u256(-1) >> (256 - targetType->numBits() + forSignBit)))
return true;
@@ -678,13 +852,13 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
value = m_value * other.m_value;
break;
case Token::Div:
- if (other.m_value == 0)
+ if (other.m_value == rational(0))
return TypePointer();
else
value = m_value / other.m_value;
break;
case Token::Mod:
- if (other.m_value == 0)
+ if (other.m_value == rational(0))
return TypePointer();
else if (fractional)
{
@@ -746,6 +920,11 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
}
}
+string RationalNumberType::identifier() const
+{
+ return "t_rational_" + m_value.numerator().str() + "_by_" + m_value.denominator().str();
+}
+
bool RationalNumberType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -784,7 +963,7 @@ u256 RationalNumberType::literalValue(Literal const*) const
solAssert(shiftedValue <= u256(-1), "Integer constant too large.");
solAssert(shiftedValue >= -(bigint(1) << 255), "Number constant too small.");
- if (m_value >= 0)
+ if (m_value >= rational(0))
value = u256(shiftedValue);
else
value = s2u(s256(shiftedValue));
@@ -885,6 +1064,13 @@ bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return false;
}
+string StringLiteralType::identifier() const
+{
+ // Since we have to return a valid identifier and the string itself may contain
+ // anything, we hash it.
+ return "t_stringliteral_" + toHex(keccak256(m_value).asBytes());
+}
+
bool StringLiteralType::operator==(const Type& _other) const
{
if (_other.category() != category())
@@ -954,6 +1140,14 @@ TypePointer FixedBytesType::unaryOperatorResult(Token::Value _operator) const
TypePointer FixedBytesType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
+ if (Token::isShiftOp(_operator))
+ {
+ if (isValidShiftAndAmountType(_operator, *_other))
+ return shared_from_this();
+ else
+ return TypePointer();
+ }
+
auto commonType = dynamic_pointer_cast<FixedBytesType const>(Type::commonType(shared_from_this(), _other));
if (!commonType)
return TypePointer();
@@ -970,6 +1164,11 @@ MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) c
return MemberList::MemberMap{MemberList::Member{"length", make_shared<IntegerType>(8)}};
}
+string FixedBytesType::identifier() const
+{
+ return "t_bytes" + std::to_string(m_bytes);
+}
+
bool FixedBytesType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1083,6 +1282,20 @@ string ReferenceType::stringForReferencePart() const
return "";
}
+string ReferenceType::identifierLocationSuffix() const
+{
+ string id;
+ if (location() == DataLocation::Storage)
+ id += "_storage";
+ else if (location() == DataLocation::Memory)
+ id += "_memory";
+ else
+ id += "_calldata";
+ if (isPointer())
+ id += "_ptr";
+ return id;
+}
+
bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
{
if (_convertTo.category() != category())
@@ -1138,6 +1351,27 @@ bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const
return true;
}
+string ArrayType::identifier() const
+{
+ string id;
+ if (isString())
+ id = "t_string";
+ else if (isByteArray())
+ id = "t_bytes";
+ else
+ {
+ id = "t_array";
+ id += identifierList(baseType());
+ if (isDynamicallySized())
+ id += "dyn";
+ else
+ id += length().str();
+ }
+ id += identifierLocationSuffix();
+
+ return id;
+}
+
bool ArrayType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1152,7 +1386,7 @@ bool ArrayType::operator==(Type const& _other) const
return false;
if (*other.baseType() != *baseType())
return false;
- return isDynamicallySized() || length() == other.length();
+ return isDynamicallySized() || length() == other.length();
}
unsigned ArrayType::calldataEncodedSize(bool _padded) const
@@ -1247,7 +1481,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
TypePointers{make_shared<IntegerType>(256)},
strings{string()},
strings{string()},
- isByteArray() ? FunctionType::Location::ByteArrayPush : FunctionType::Location::ArrayPush
+ isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
)});
}
return members;
@@ -1324,6 +1558,11 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer)
return copy;
}
+string ContractType::identifier() const
+{
+ return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id());
+}
+
bool ContractType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1433,6 +1672,11 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const
return this->m_struct == convertTo.m_struct;
}
+string StructType::identifier() const
+{
+ return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()) + identifierLocationSuffix();
+}
+
bool StructType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1485,6 +1729,7 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
{
TypePointer type = variable->annotation().type;
+ solAssert(type, "");
// Skip all mapping members if we are not in storage.
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
continue;
@@ -1536,7 +1781,7 @@ FunctionTypePointer StructType::constructorType() const
TypePointers{copyForLocation(DataLocation::Memory, false)},
paramNames,
strings(),
- FunctionType::Location::Internal
+ FunctionType::Kind::Internal
);
}
@@ -1573,6 +1818,11 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
}
+string EnumType::identifier() const
+{
+ return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id());
+}
+
bool EnumType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1654,6 +1904,11 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
return false;
}
+string TupleType::identifier() const
+{
+ return "t_tuple" + identifierList(components());
+}
+
bool TupleType::operator==(Type const& _other) const
{
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
@@ -1718,13 +1973,16 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons
size_t si = fillRight ? i : components().size() - i - 1;
size_t ti = fillRight ? i : targetComponents.size() - i - 1;
if (components()[si] && targetComponents[ti])
+ {
tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[ti]);
+ solAssert(tempComponents[ti], "");
+ }
}
return make_shared<TupleType>(tempComponents);
}
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
- m_location(_isInternal ? Location::Internal : Location::External),
+ m_kind(_isInternal ? Kind::Internal : Kind::External),
m_isConstant(_function.isDeclaredConst()),
m_isPayable(_isInternal ? false : _function.isPayable()),
m_declaration(&_function)
@@ -1755,7 +2013,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal
}
FunctionType::FunctionType(VariableDeclaration const& _varDecl):
- m_location(Location::External), m_isConstant(true), m_declaration(&_varDecl)
+ m_kind(Kind::External), m_isConstant(true), m_declaration(&_varDecl)
{
TypePointers paramTypes;
vector<string> paramNames;
@@ -1787,6 +2045,8 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
if (auto structType = dynamic_cast<StructType const*>(returnType.get()))
{
for (auto const& member: structType->members(nullptr))
+ {
+ solAssert(member.type, "");
if (member.type->category() != Category::Mapping)
{
if (auto arrayType = dynamic_cast<ArrayType const*>(member.type.get()))
@@ -1795,6 +2055,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
retParams.push_back(member.type);
retParamNames.push_back(member.name);
}
+ }
}
else
{
@@ -1812,7 +2073,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
}
FunctionType::FunctionType(EventDefinition const& _event):
- m_location(Location::Event), m_isConstant(true), m_declaration(&_event)
+ m_kind(Kind::Event), m_isConstant(true), m_declaration(&_event)
{
TypePointers params;
vector<string> paramNames;
@@ -1828,19 +2089,19 @@ FunctionType::FunctionType(EventDefinition const& _event):
}
FunctionType::FunctionType(FunctionTypeName const& _typeName):
- m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal),
+ m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal),
m_isConstant(_typeName.isDeclaredConst()),
m_isPayable(_typeName.isPayable())
{
if (_typeName.isPayable())
{
- solAssert(m_location == Location::External, "Internal payable function type used.");
+ solAssert(m_kind == Kind::External, "Internal payable function type used.");
solAssert(!m_isConstant, "Payable constant function");
}
for (auto const& t: _typeName.parameterTypes())
{
solAssert(t->annotation().type, "Type not set for parameter.");
- if (m_location == Location::External)
+ if (m_kind == Kind::External)
solAssert(
t->annotation().type->canBeUsedExternally(false),
"Internal type used as parameter for external function."
@@ -1850,7 +2111,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName):
for (auto const& t: _typeName.returnParameterTypes())
{
solAssert(t->annotation().type, "Type not set for return parameter.");
- if (m_location == Location::External)
+ if (m_kind == Kind::External)
solAssert(
t->annotation().type->canBeUsedExternally(false),
"Internal type used as return parameter for external function."
@@ -1880,7 +2141,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c
TypePointers{make_shared<ContractType>(_contract)},
parameterNames,
strings{""},
- Location::Creation,
+ Kind::Creation,
false,
nullptr,
false,
@@ -1902,13 +2163,64 @@ TypePointers FunctionType::parameterTypes() const
return TypePointers(m_parameterTypes.cbegin() + 1, m_parameterTypes.cend());
}
+string FunctionType::identifier() const
+{
+ string id = "t_function_";
+ switch (m_kind)
+ {
+ case Kind::Internal: id += "internal"; break;
+ case Kind::External: id += "external"; break;
+ case Kind::CallCode: id += "callcode"; break;
+ case Kind::DelegateCall: id += "delegatecall"; break;
+ case Kind::Bare: id += "bare"; break;
+ case Kind::BareCallCode: id += "barecallcode"; break;
+ case Kind::BareDelegateCall: id += "baredelegatecall"; break;
+ case Kind::Creation: id += "creation"; break;
+ case Kind::Send: id += "send"; break;
+ case Kind::Transfer: id += "transfer"; break;
+ case Kind::SHA3: id += "sha3"; break;
+ case Kind::Selfdestruct: id += "selfdestruct"; break;
+ case Kind::Revert: id += "revert"; break;
+ case Kind::ECRecover: id += "ecrecover"; break;
+ case Kind::SHA256: id += "sha256"; break;
+ case Kind::RIPEMD160: id += "ripemd160"; break;
+ case Kind::Log0: id += "log0"; break;
+ case Kind::Log1: id += "log1"; break;
+ case Kind::Log2: id += "log2"; break;
+ case Kind::Log3: id += "log3"; break;
+ case Kind::Log4: id += "log4"; break;
+ case Kind::Event: id += "event"; break;
+ case Kind::SetGas: id += "setgas"; break;
+ case Kind::SetValue: id += "setvalue"; break;
+ case Kind::BlockHash: id += "blockhash"; break;
+ case Kind::AddMod: id += "addmod"; break;
+ case Kind::MulMod: id += "mulmod"; break;
+ case Kind::ArrayPush: id += "arraypush"; break;
+ case Kind::ByteArrayPush: id += "bytearraypush"; break;
+ case Kind::ObjectCreation: id += "objectcreation"; break;
+ case Kind::Assert: id += "assert"; break;
+ case Kind::Require: id += "require";break;
+ default: solAssert(false, "Unknown function location."); break;
+ }
+ if (isConstant())
+ id += "_constant";
+ id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
+ if (m_gasSet)
+ id += "gas";
+ if (m_valueSet)
+ id += "value";
+ if (bound())
+ id += "bound_to" + identifierList(selfType());
+ return id;
+}
+
bool FunctionType::operator==(Type const& _other) const
{
if (_other.category() != category())
return false;
FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
- if (m_location != other.m_location)
+ if (m_kind != other.m_kind)
return false;
if (m_isConstant != other.isConstant())
return false;
@@ -1934,6 +2246,17 @@ bool FunctionType::operator==(Type const& _other) const
return true;
}
+bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (m_kind == Kind::External && _convertTo.category() == Category::Integer)
+ {
+ IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
+ if (convertTo.isAddress())
+ return true;
+ }
+ return _convertTo.category() == category();
+}
+
TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
{
if (_operator == Token::Value::Delete)
@@ -1941,9 +2264,19 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
return TypePointer();
}
+TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
+{
+ if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual))
+ return TypePointer();
+ FunctionType const& other = dynamic_cast<FunctionType const&>(*_other);
+ if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1)
+ return commonType(shared_from_this(), _other);
+ return TypePointer();
+}
+
string FunctionType::canonicalName(bool) const
{
- solAssert(m_location == Location::External, "");
+ solAssert(m_kind == Kind::External, "");
return "function";
}
@@ -1957,7 +2290,7 @@ string FunctionType::toString(bool _short) const
name += " constant";
if (m_isPayable)
name += " payable";
- if (m_location == Location::External)
+ if (m_kind == Kind::External)
name += " external";
if (!m_returnParameterTypes.empty())
{
@@ -1979,7 +2312,7 @@ unsigned FunctionType::calldataEncodedSize(bool _padded) const
u256 FunctionType::storageSize() const
{
- if (m_location == Location::External || m_location == Location::Internal)
+ if (m_kind == Kind::External || m_kind == Kind::Internal)
return 1;
else
BOOST_THROW_EXCEPTION(
@@ -1989,9 +2322,9 @@ u256 FunctionType::storageSize() const
unsigned FunctionType::storageBytes() const
{
- if (m_location == Location::External)
+ if (m_kind == Kind::External)
return 20 + 4;
- else if (m_location == Location::Internal)
+ else if (m_kind == Kind::Internal)
return 8; // it should really not be possible to create larger programs
else
BOOST_THROW_EXCEPTION(
@@ -2001,21 +2334,21 @@ unsigned FunctionType::storageBytes() const
unsigned FunctionType::sizeOnStack() const
{
- Location location = m_location;
- if (m_location == Location::SetGas || m_location == Location::SetValue)
+ Kind kind = m_kind;
+ if (m_kind == Kind::SetGas || m_kind == Kind::SetValue)
{
solAssert(m_returnParameterTypes.size() == 1, "");
- location = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_location;
+ kind = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_kind;
}
unsigned size = 0;
- if (location == Location::External || location == Location::CallCode || location == Location::DelegateCall)
+ if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall)
size = 2;
- else if (location == Location::Bare || location == Location::BareCallCode || location == Location::BareDelegateCall)
+ else if (kind == Kind::Bare || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall)
size = 1;
- else if (location == Location::Internal)
+ else if (kind == Kind::Internal)
size = 1;
- else if (location == Location::ArrayPush || location == Location::ByteArrayPush)
+ else if (kind == Kind::ArrayPush || kind == Kind::ByteArrayPush)
size = 1;
if (m_gasSet)
size++;
@@ -2056,26 +2389,26 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
return make_shared<FunctionType>(
paramTypes, retParamTypes,
m_parameterNames, m_returnParameterNames,
- m_location, m_arbitraryParameters,
+ m_kind, m_arbitraryParameters,
m_declaration, m_isConstant, m_isPayable
);
}
MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) const
{
- switch (m_location)
+ switch (m_kind)
{
- case Location::External:
- case Location::Creation:
- case Location::ECRecover:
- case Location::SHA256:
- case Location::RIPEMD160:
- case Location::Bare:
- case Location::BareCallCode:
- case Location::BareDelegateCall:
+ case Kind::External:
+ case Kind::Creation:
+ case Kind::ECRecover:
+ case Kind::SHA256:
+ case Kind::RIPEMD160:
+ case Kind::Bare:
+ case Kind::BareCallCode:
+ case Kind::BareDelegateCall:
{
MemberList::MemberMap members;
- if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall)
+ if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
{
if (m_isPayable)
members.push_back(MemberList::Member(
@@ -2085,7 +2418,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
TypePointers{copyAndSetGasOrValue(false, true)},
strings(),
strings(),
- Location::SetValue,
+ Kind::SetValue,
false,
nullptr,
false,
@@ -2095,7 +2428,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
)
));
}
- if (m_location != Location::Creation)
+ if (m_kind != Kind::Creation)
members.push_back(MemberList::Member(
"gas",
make_shared<FunctionType>(
@@ -2103,7 +2436,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
TypePointers{copyAndSetGasOrValue(true, false)},
strings(),
strings(),
- Location::SetGas,
+ Kind::SetGas,
false,
nullptr,
false,
@@ -2122,7 +2455,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
TypePointer FunctionType::encodingType() const
{
// Only external functions can be encoded, internal functions cannot leave code boundaries.
- if (m_location == Location::External)
+ if (m_kind == Kind::External)
return shared_from_this();
else
return TypePointer();
@@ -2130,7 +2463,7 @@ TypePointer FunctionType::encodingType() const
TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const
{
- if (m_location == Location::External)
+ if (m_kind == Kind::External)
return shared_from_this();
else
return TypePointer();
@@ -2172,14 +2505,14 @@ bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const
bool FunctionType::isBareCall() const
{
- switch (m_location)
+ switch (m_kind)
{
- case Location::Bare:
- case Location::BareCallCode:
- case Location::BareDelegateCall:
- case Location::ECRecover:
- case Location::SHA256:
- case Location::RIPEMD160:
+ case Kind::Bare:
+ case Kind::BareCallCode:
+ case Kind::BareDelegateCall:
+ case Kind::ECRecover:
+ case Kind::SHA256:
+ case Kind::RIPEMD160:
return true;
default:
return false;
@@ -2211,6 +2544,18 @@ u256 FunctionType::externalIdentifier() const
return FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(externalSignature())));
}
+bool FunctionType::isPure() const
+{
+ return
+ m_kind == Kind::SHA3 ||
+ m_kind == Kind::ECRecover ||
+ m_kind == Kind::SHA256 ||
+ m_kind == Kind::RIPEMD160 ||
+ m_kind == Kind::AddMod ||
+ m_kind == Kind::MulMod ||
+ m_kind == Kind::ObjectCreation;
+}
+
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
{
TypePointers pointers;
@@ -2227,7 +2572,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con
m_returnParameterTypes,
m_parameterNames,
m_returnParameterNames,
- m_location,
+ m_kind,
m_arbitraryParameters,
m_declaration,
m_isConstant,
@@ -2248,23 +2593,23 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
{
auto refType = dynamic_cast<ReferenceType const*>(t.get());
if (refType && refType->location() == DataLocation::CallData)
- parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, false));
+ parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, true));
else
parameterTypes.push_back(t);
}
- Location location = m_location;
+ Kind kind = m_kind;
if (_inLibrary)
{
solAssert(!!m_declaration, "Declaration has to be available.");
if (!m_declaration->isPublic())
- location = Location::Internal; // will be inlined
+ kind = Kind::Internal; // will be inlined
else
- location = Location::DelegateCall;
+ kind = Kind::DelegateCall;
}
TypePointers returnParameterTypes = m_returnParameterTypes;
- if (location != Location::Internal)
+ if (kind != Kind::Internal)
{
// Alter dynamic types to be non-accessible.
for (auto& param: returnParameterTypes)
@@ -2277,7 +2622,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
returnParameterTypes,
m_parameterNames,
m_returnParameterNames,
- location,
+ kind,
m_arbitraryParameters,
m_declaration,
m_isConstant,
@@ -2288,25 +2633,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
);
}
-vector<string> const FunctionType::parameterTypeNames(bool _addDataLocation) const
-{
- vector<string> names;
- for (TypePointer const& t: parameterTypes())
- names.push_back(t->canonicalName(_addDataLocation));
-
- return names;
-}
-
-vector<string> const FunctionType::returnParameterTypeNames(bool _addDataLocation) const
-{
- vector<string> names;
- for (TypePointer const& t: m_returnParameterTypes)
- names.push_back(t->canonicalName(_addDataLocation));
-
- return names;
-}
-
-TypePointer FunctionType::selfType() const
+TypePointer const& FunctionType::selfType() const
{
solAssert(bound(), "Function is not bound.");
solAssert(m_parameterTypes.size() > 0, "Function has no self type.");
@@ -2322,6 +2649,11 @@ ASTPointer<ASTString> FunctionType::documentation() const
return ASTPointer<ASTString>();
}
+string MappingType::identifier() const
+{
+ return "t_mapping" + identifierList(m_keyType, m_valueType);
+}
+
bool MappingType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2340,6 +2672,11 @@ string MappingType::canonicalName(bool) const
return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")";
}
+string TypeType::identifier() const
+{
+ return "t_type" + identifierList(actualType());
+}
+
bool TypeType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2424,6 +2761,11 @@ u256 ModifierType::storageSize() const
<< errinfo_comment("Storage size of non-storable type type requested."));
}
+string ModifierType::identifier() const
+{
+ return "t_modifier" + identifierList(m_parameterTypes);
+}
+
bool ModifierType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2448,6 +2790,11 @@ string ModifierType::toString(bool _short) const
return name + ")";
}
+string ModuleType::identifier() const
+{
+ return "t_module_" + std::to_string(m_sourceUnit.id());
+}
+
bool ModuleType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2469,6 +2816,22 @@ string ModuleType::toString(bool) const
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
}
+string MagicType::identifier() const
+{
+ switch (m_kind)
+ {
+ case Kind::Block:
+ return "t_magic_block";
+ case Kind::Message:
+ return "t_magic_message";
+ case Kind::Transaction:
+ return "t_magic_transaction";
+ default:
+ solAssert(false, "Unknown kind of magic");
+ }
+ return "";
+}
+
bool MagicType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2485,7 +2848,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
return MemberList::MemberMap({
{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"timestamp", make_shared<IntegerType>(256)},
- {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)},
+ {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash)},
{"difficulty", make_shared<IntegerType>(256)},
{"number", make_shared<IntegerType>(256)},
{"gaslimit", make_shared<IntegerType>(256)}
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 72640a1c..87d88a79 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -22,18 +22,21 @@
#pragma once
-#include <memory>
-#include <string>
-#include <map>
-#include <boost/noncopyable.hpp>
-#include <boost/rational.hpp>
-#include <libdevcore/Common.h>
-#include <libdevcore/CommonIO.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/parsing/Token.h>
+
+#include <libdevcore/Common.h>
+#include <libdevcore/CommonIO.h>
#include <libdevcore/UndefMacros.h>
+#include <boost/noncopyable.hpp>
+#include <boost/rational.hpp>
+
+#include <memory>
+#include <string>
+#include <map>
+
namespace dev
{
namespace solidity
@@ -155,6 +158,13 @@ public:
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b);
virtual Category category() const = 0;
+ /// @returns a valid solidity identifier such that two types should compare equal if and
+ /// only if they have the same identifier.
+ /// The identifier should start with "t_".
+ /// More complex identifier strings use "parentheses", where $_ is interpreted as as
+ /// "opening parenthesis", _$ as "closing parenthesis", _$_ as "comma" and any $ that
+ /// appears as part of a user-supplied identifier is escaped as _$$$_.
+ virtual std::string identifier() const = 0;
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const
{
@@ -288,6 +298,7 @@ public:
explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned);
+ virtual std::string identifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -303,6 +314,8 @@ public:
virtual std::string toString(bool _short) const override;
+ virtual u256 literalValue(Literal const* _literal) const override;
+
virtual TypePointer encodingType() const override { return shared_from_this(); }
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
@@ -310,6 +323,9 @@ public:
bool isAddress() const { return m_modifier == Modifier::Address; }
bool isSigned() const { return m_modifier == Modifier::Signed; }
+ bigint minValue() const;
+ bigint maxValue() const;
+
private:
int m_bits;
Modifier m_modifier;
@@ -329,6 +345,7 @@ public:
explicit FixedPointType(int _integerBits, int _fractionalBits, Modifier _modifier = Modifier::Unsigned);
+ virtual std::string identifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -378,6 +395,7 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
@@ -396,8 +414,14 @@ public:
/// @returns true if the value is not an integer.
bool isFractional() const { return m_value.denominator() != 1; }
+ /// @returns true if the value is negative.
+ bool isNegative() const { return m_value < 0; }
+
private:
rational m_value;
+
+ /// @returns true if the literal is a valid rational number.
+ static std::tuple<bool, rational> parseRational(std::string const& _value);
};
/**
@@ -416,6 +440,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
@@ -449,6 +474,7 @@ public:
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
@@ -476,6 +502,7 @@ class BoolType: public Type
public:
BoolType() {}
virtual Category category() const override { return Category::Bool; }
+ virtual std::string identifier() const override { return "t_bool"; }
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
@@ -533,6 +560,8 @@ protected:
TypePointer copyForLocationIfReference(TypePointer 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();
+ std::string identifierLocationSuffix() const;
DataLocation m_location = DataLocation::Storage;
bool m_isPointer = true;
@@ -573,6 +602,7 @@ public:
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(const Type& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
virtual bool isDynamicallySized() const override { return m_hasDynamicLength; }
@@ -622,6 +652,7 @@ public:
/// Contracts can be converted to themselves and to integers.
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded ) const override
{
@@ -677,6 +708,7 @@ public:
explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage):
ReferenceType(_location), m_struct(_struct) {}
virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
u256 memorySize() const;
@@ -720,6 +752,7 @@ public:
virtual Category category() const override { return Category::Enum; }
explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {}
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override
{
@@ -760,6 +793,7 @@ public:
virtual Category category() const override { return Category::Tuple; }
explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {}
virtual bool isImplicitlyConvertibleTo(Type const& _other) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual std::string toString(bool) const override;
@@ -786,20 +820,21 @@ class FunctionType: public Type
{
public:
/// How this function is invoked on the EVM.
- /// @todo This documentation is outdated, and Location should rather be named "Type"
- enum class Location
+ enum class Kind
{
Internal, ///< stack-call using plain JUMP
External, ///< external call using CALL
- CallCode, ///< extercnal call using CALLCODE, i.e. not exchanging the storage
- DelegateCall, ///< extercnal call using DELEGATECALL, i.e. not exchanging the storage
+ CallCode, ///< external call using CALLCODE, i.e. not exchanging the storage
+ DelegateCall, ///< external call using DELEGATECALL, i.e. not exchanging the storage
Bare, ///< CALL without function hash
BareCallCode, ///< CALLCODE without function hash
BareDelegateCall, ///< DELEGATECALL without function hash
Creation, ///< external call using CREATE
Send, ///< CALL, but without data and gas
+ Transfer, ///< CALL, but without data and throws on error
SHA3, ///< SHA3
Selfdestruct, ///< SELFDESTRUCT
+ Revert, ///< REVERT
ECRecover, ///< CALL to special contract for ecrecover
SHA256, ///< CALL to special contract for sha256
RIPEMD160, ///< CALL to special contract for ripemd160
@@ -816,7 +851,9 @@ public:
MulMod, ///< MULMOD
ArrayPush, ///< .push() to a dynamically sized array in storage
ByteArrayPush, ///< .push() to a dynamically sized byte array in storage
- ObjectCreation ///< array creation using new
+ ObjectCreation, ///< array creation using new
+ Assert, ///< assert()
+ Require ///< require()
};
virtual Category category() const override { return Category::Function; }
@@ -833,7 +870,7 @@ public:
FunctionType(
strings const& _parameterTypes,
strings const& _returnParameterTypes,
- Location _location = Location::Internal,
+ Kind _kind = Kind::Internal,
bool _arbitraryParameters = false,
bool _constant = false,
bool _payable = false
@@ -842,7 +879,7 @@ public:
parseElementaryTypeVector(_returnParameterTypes),
strings(),
strings(),
- _location,
+ _kind,
_arbitraryParameters,
nullptr,
_constant,
@@ -860,7 +897,7 @@ public:
TypePointers const& _returnParameterTypes,
strings _parameterNames = strings(),
strings _returnParameterNames = strings(),
- Location _location = Location::Internal,
+ Kind _kind = Kind::Internal,
bool _arbitraryParameters = false,
Declaration const* _declaration = nullptr,
bool _isConstant = false,
@@ -873,7 +910,7 @@ public:
m_returnParameterTypes(_returnParameterTypes),
m_parameterNames(_parameterNames),
m_returnParameterNames(_returnParameterNames),
- m_location(_location),
+ m_kind(_kind),
m_arbitraryParameters(_arbitraryParameters),
m_gasSet(_gasSet),
m_valueSet(_valueSet),
@@ -890,23 +927,24 @@ public:
TypePointers parameterTypes() const;
std::vector<std::string> parameterNames() const;
- std::vector<std::string> const parameterTypeNames(bool _addDataLocation) const;
TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; }
std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; }
- std::vector<std::string> const returnParameterTypeNames(bool _addDataLocation) const;
/// @returns the "self" parameter type for a bound function
- TypePointer selfType() const;
+ TypePointer const& selfType() const;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
+ virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override;
virtual std::string canonicalName(bool /*_addDataLocation*/) const override;
virtual std::string toString(bool _short) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
- virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; }
+ virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
virtual u256 storageSize() const override;
virtual unsigned storageBytes() const override;
virtual bool isValueType() const override { return true; }
- virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; }
+ virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
virtual unsigned sizeOnStack() const override;
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override;
@@ -929,7 +967,7 @@ public:
/// @returns true if the ABI is used for this call (only meaningful for external calls)
bool isBareCall() const;
- Location const& location() const { return m_location; }
+ Kind const& kind() const { return m_kind; }
/// @returns the external signature of this function type given the function name
std::string externalSignature() const;
/// @returns the external identifier of this function (the hash of the signature).
@@ -941,13 +979,17 @@ public:
}
bool hasDeclaration() const { return !!m_declaration; }
bool isConstant() const { return m_isConstant; }
+ /// @returns true if the the result of this function only depends on its arguments
+ /// and it does not modify the state.
+ /// Currently, this will only return true for internal functions like keccak and ecrecover.
+ bool isPure() const;
bool isPayable() const { return m_isPayable; }
/// @return A shared pointer of an ASTString.
/// Can contain a nullptr in which case indicates absence of documentation
ASTPointer<ASTString> documentation() const;
/// true iff arguments are to be padded to multiples of 32 bytes for external calls
- bool padArguments() const { return !(m_location == Location::SHA3 || m_location == Location::SHA256 || m_location == Location::RIPEMD160); }
+ bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160); }
bool takesArbitraryParameters() const { return m_arbitraryParameters; }
bool gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; }
@@ -973,7 +1015,7 @@ private:
TypePointers m_returnParameterTypes;
std::vector<std::string> m_parameterNames;
std::vector<std::string> m_returnParameterNames;
- Location const m_location;
+ Kind const m_kind;
/// true if the function takes an arbitrary number of arguments of arbitrary types
bool const m_arbitraryParameters = false;
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack
@@ -995,10 +1037,12 @@ public:
MappingType(TypePointer const& _keyType, TypePointer const& _valueType):
m_keyType(_keyType), m_valueType(_valueType) {}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override;
virtual std::string canonicalName(bool _addDataLocation) const override;
virtual bool canLiveOutsideStorage() const override { return false; }
+ virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual TypePointer encodingType() const override
{
return std::make_shared<IntegerType>(256);
@@ -1029,6 +1073,7 @@ public:
TypePointer const& actualType() const { return m_actualType; }
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual u256 storageSize() const override;
@@ -1056,6 +1101,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override { return 0; }
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override;
@@ -1075,11 +1121,8 @@ public:
explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {}
- virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override
- {
- return TypePointer();
- }
-
+ virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
@@ -1109,6 +1152,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
@@ -1117,6 +1161,8 @@ public:
virtual std::string toString(bool _short) const override;
+ Kind kind() const { return m_kind; }
+
private:
Kind m_kind;
};
@@ -1130,8 +1176,10 @@ class InaccessibleDynamicType: public Type
public:
virtual Category category() const override { return Category::InaccessibleDynamic; }
+ virtual std::string identifier() const override { return "t_inaccessible"; }
virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; }
virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; }
+ virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; }
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return false; }
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp
index 2c982982..67ca22f1 100644
--- a/libsolidity/codegen/ArrayUtils.cpp
+++ b/libsolidity/codegen/ArrayUtils.cpp
@@ -25,7 +25,7 @@
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/Types.h>
-#include <libsolidity/interface/Utils.h>
+#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/codegen/LValue.h>
using namespace std;
@@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack layout: [source_ref] [source length] target_ref (top)
solAssert(_targetType.location() == DataLocation::Storage, "");
- IntegerType uint256(256);
- Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType());
- Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType());
+ TypePointer uint256 = make_shared<IntegerType>(256);
+ TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType();
+ TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType();
// TODO unroll loop for small sizes
@@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
}
// stack: target_ref source_ref source_length
- m_context << Instruction::DUP3;
- // stack: target_ref source_ref source_length target_ref
- retrieveLength(_targetType);
- // stack: target_ref source_ref source_length target_ref target_length
- if (_targetType.isDynamicallySized())
- // store new target length
- if (!_targetType.isByteArray())
- // Otherwise, length will be stored below.
- m_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
- if (sourceBaseType->category() == Type::Category::Mapping)
- {
- solAssert(targetBaseType->category() == Type::Category::Mapping, "");
- solAssert(_sourceType.location() == DataLocation::Storage, "");
- // nothing to copy
- m_context
- << Instruction::POP << Instruction::POP
- << Instruction::POP << Instruction::POP;
- return;
- }
- // stack: target_ref source_ref source_length target_ref target_length
- // compute hashes (data positions)
- m_context << Instruction::SWAP1;
- if (_targetType.isDynamicallySized())
- CompilerUtils(m_context).computeHashStatic();
- // stack: target_ref source_ref source_length target_length target_data_pos
- m_context << Instruction::SWAP1;
- convertLengthToSize(_targetType);
- m_context << Instruction::DUP2 << Instruction::ADD;
- // stack: target_ref source_ref source_length target_data_pos target_data_end
- m_context << Instruction::SWAP3;
- // stack: target_ref target_data_end source_length target_data_pos source_ref
-
- eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
-
- // special case for short byte arrays: Store them together with their length.
- if (_targetType.isByteArray())
- {
- // stack: target_ref target_data_end source_length target_data_pos source_ref
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- eth::AssemblyItem longByteArray = m_context.appendConditionalJump();
- // store the short byte array
- solAssert(_sourceType.isByteArray(), "");
- if (_sourceType.location() == DataLocation::Storage)
- {
- // just copy the slot, it contains length and data
- m_context << Instruction::DUP1 << Instruction::SLOAD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
- else
+ TypePointer targetType = _targetType.shared_from_this();
+ TypePointer sourceType = _sourceType.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(),
+ 3,
+ 1,
+ [=](CompilerContext& _context)
{
- m_context << Instruction::DUP1;
- CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
- // stack: target_ref target_data_end source_length target_data_pos source_ref value
- // clear the lower-order byte - which will hold the length
- m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
- // fetch the length and shift it left by one
- m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
- // combine value and length and store them
- m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
- }
- // end of special case, jump right into cleaning target data area
- m_context.appendJumpTo(copyLoopEndWithoutByteOffset);
- m_context << longByteArray;
- // Store length (2*length+1)
- m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
+ ArrayUtils utils(_context);
+ ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType);
+ ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType);
+ // stack: target_ref source_ref source_length
+ _context << Instruction::DUP3;
+ // stack: target_ref source_ref source_length target_ref
+ utils.retrieveLength(_targetType);
+ // stack: target_ref source_ref source_length target_ref target_length
+ if (_targetType.isDynamicallySized())
+ // store new target length
+ if (!_targetType.isByteArray())
+ // Otherwise, length will be stored below.
+ _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
+ if (sourceBaseType->category() == Type::Category::Mapping)
+ {
+ solAssert(targetBaseType->category() == Type::Category::Mapping, "");
+ solAssert(_sourceType.location() == DataLocation::Storage, "");
+ // nothing to copy
+ _context
+ << Instruction::POP << Instruction::POP
+ << Instruction::POP << Instruction::POP;
+ return;
+ }
+ // stack: target_ref source_ref source_length target_ref target_length
+ // compute hashes (data positions)
+ _context << Instruction::SWAP1;
+ if (_targetType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref source_ref source_length target_length target_data_pos
+ _context << Instruction::SWAP1;
+ utils.convertLengthToSize(_targetType);
+ _context << Instruction::DUP2 << Instruction::ADD;
+ // stack: target_ref source_ref source_length target_data_pos target_data_end
+ _context << Instruction::SWAP3;
+ // stack: target_ref target_data_end source_length target_data_pos source_ref
+
+ eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag();
+
+ // special case for short byte arrays: Store them together with their length.
+ if (_targetType.isByteArray())
+ {
+ // stack: target_ref target_data_end source_length target_data_pos source_ref
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem longByteArray = _context.appendConditionalJump();
+ // store the short byte array
+ solAssert(_sourceType.isByteArray(), "");
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ // just copy the slot, it contains length and data
+ _context << Instruction::DUP1 << Instruction::SLOAD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ else
+ {
+ _context << Instruction::DUP1;
+ CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
+ // stack: target_ref target_data_end source_length target_data_pos source_ref value
+ // clear the lower-order byte - which will hold the length
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // fetch the length and shift it left by one
+ _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
+ // combine value and length and store them
+ _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ // end of special case, jump right into cleaning target data area
+ _context.appendJumpTo(copyLoopEndWithoutByteOffset);
+ _context << longByteArray;
+ // Store length (2*length+1)
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
- // skip copying if source length is zero
- m_context << Instruction::DUP3 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
-
- if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
- CompilerUtils(m_context).computeHashStatic();
- // stack: target_ref target_data_end source_length target_data_pos source_data_pos
- m_context << Instruction::SWAP2;
- convertLengthToSize(_sourceType);
- m_context << Instruction::DUP3 << Instruction::ADD;
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
- if (haveByteOffsetTarget)
- m_context << u256(0);
- if (haveByteOffsetSource)
- m_context << u256(0);
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- eth::AssemblyItem copyLoopStart = m_context.newTag();
- m_context << copyLoopStart;
- // check for loop condition
- m_context
- << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
- << Instruction::GT << Instruction::ISZERO;
- eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump();
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- // copy
- if (sourceBaseType->category() == Type::Category::Array)
- {
- solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
- auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
- m_context << Instruction::DUP3;
- if (sourceBaseArrayType.location() == DataLocation::Memory)
- m_context << Instruction::MLOAD;
- m_context << Instruction::DUP3;
- copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
- m_context << Instruction::POP;
- }
- else if (directCopy)
- {
- solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
- m_context
- << Instruction::DUP3 << Instruction::SLOAD
- << Instruction::DUP3 << Instruction::SSTORE;
- }
- else
- {
- // Note that we have to copy each element on its own in case conversion is involved.
- // We might copy too much if there is padding at the last element, but this way end
- // checking is easier.
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- m_context << dupInstruction(3 + byteOffsetSize);
- if (_sourceType.location() == DataLocation::Storage)
- {
+ // skip copying if source length is zero
+ _context << Instruction::DUP3 << Instruction::ISZERO;
+ _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
+
+ if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref target_data_end source_length target_data_pos source_data_pos
+ _context << Instruction::SWAP2;
+ utils.convertLengthToSize(_sourceType);
+ _context << Instruction::DUP3 << Instruction::ADD;
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
+ if (haveByteOffsetTarget)
+ _context << u256(0);
if (haveByteOffsetSource)
- m_context << Instruction::DUP2;
+ _context << u256(0);
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ eth::AssemblyItem copyLoopStart = _context.newTag();
+ _context << copyLoopStart;
+ // check for loop condition
+ _context
+ << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem copyLoopEnd = _context.appendConditionalJump();
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ // copy
+ if (sourceBaseType->category() == Type::Category::Array)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
+ auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
+ _context << Instruction::DUP3;
+ if (sourceBaseArrayType.location() == DataLocation::Memory)
+ _context << Instruction::MLOAD;
+ _context << Instruction::DUP3;
+ utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
+ _context << Instruction::POP;
+ }
+ else if (directCopy)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
+ _context
+ << Instruction::DUP3 << Instruction::SLOAD
+ << Instruction::DUP3 << Instruction::SSTORE;
+ }
+ else
+ {
+ // Note that we have to copy each element on its own in case conversion is involved.
+ // We might copy too much if there is padding at the last element, but this way end
+ // checking is easier.
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ _context << dupInstruction(3 + byteOffsetSize);
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ if (haveByteOffsetSource)
+ _context << Instruction::DUP2;
+ else
+ _context << u256(0);
+ StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ }
+ else if (sourceBaseType->isValueType())
+ CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
+ else
+ solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
+ solAssert(
+ 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
+ "Stack too deep, try removing local variables."
+ );
+ // fetch target storage reference
+ _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ if (haveByteOffsetTarget)
+ _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ else
+ _context << u256(0);
+ StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
+ }
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ // increment source
+ if (haveByteOffsetSource)
+ utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
+ else
+ {
+ _context << swapInstruction(2 + byteOffsetSize);
+ if (sourceIsStorage)
+ _context << sourceBaseType->storageSize();
+ else if (_sourceType.location() == DataLocation::Memory)
+ _context << sourceBaseType->memoryHeadSize();
+ else
+ _context << sourceBaseType->calldataEncodedSize(true);
+ _context
+ << Instruction::ADD
+ << swapInstruction(2 + byteOffsetSize);
+ }
+ // increment target
+ if (haveByteOffsetTarget)
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
else
- m_context << u256(0);
- StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ _context
+ << swapInstruction(1 + byteOffsetSize)
+ << targetBaseType->storageSize()
+ << Instruction::ADD
+ << swapInstruction(1 + byteOffsetSize);
+ _context.appendJumpTo(copyLoopStart);
+ _context << copyLoopEnd;
+ if (haveByteOffsetTarget)
+ {
+ // clear elements that might be left over in the current slot in target
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
+ _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
+ eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump();
+ _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
+ StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true);
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ _context.appendJumpTo(copyLoopEnd);
+
+ _context << copyCleanupLoopEnd;
+ _context << Instruction::POP; // might pop the source, but then target is popped next
+ }
+ if (haveByteOffsetSource)
+ _context << Instruction::POP;
+ _context << copyLoopEndWithoutByteOffset;
+
+ // zero-out leftovers in target
+ // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
+ _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
+ // stack: target_ref target_data_end target_data_pos_updated
+ utils.clearStorageLoop(targetBaseType);
+ _context << Instruction::POP;
}
- else if (sourceBaseType->isValueType())
- CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
- else
- solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
- solAssert(
- 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
- "Stack too deep, try removing local variables."
- );
- // fetch target storage reference
- m_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
- if (haveByteOffsetTarget)
- m_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
- else
- m_context << u256(0);
- StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
- }
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- // increment source
- if (haveByteOffsetSource)
- incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
- else
- {
- m_context << swapInstruction(2 + byteOffsetSize);
- if (sourceIsStorage)
- m_context << sourceBaseType->storageSize();
- else if (_sourceType.location() == DataLocation::Memory)
- m_context << sourceBaseType->memoryHeadSize();
- else
- m_context << sourceBaseType->calldataEncodedSize(true);
- m_context
- << Instruction::ADD
- << swapInstruction(2 + byteOffsetSize);
- }
- // increment target
- if (haveByteOffsetTarget)
- incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
- else
- m_context
- << swapInstruction(1 + byteOffsetSize)
- << targetBaseType->storageSize()
- << Instruction::ADD
- << swapInstruction(1 + byteOffsetSize);
- m_context.appendJumpTo(copyLoopStart);
- m_context << copyLoopEnd;
- if (haveByteOffsetTarget)
- {
- // clear elements that might be left over in the current slot in target
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
- m_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
- eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
- m_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
- StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true);
- incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
- m_context.appendJumpTo(copyLoopEnd);
-
- m_context << copyCleanupLoopEnd;
- m_context << Instruction::POP; // might pop the source, but then target is popped next
- }
- if (haveByteOffsetSource)
- m_context << Instruction::POP;
- m_context << copyLoopEndWithoutByteOffset;
-
- // zero-out leftovers in target
- // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
- m_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
- // stack: target_ref target_data_end target_data_pos_updated
- clearStorageLoop(*targetBaseType);
- m_context << Instruction::POP;
+ );
}
void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
@@ -335,9 +349,14 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
if (baseSize > 1)
m_context << u256(baseSize) << Instruction::MUL;
// stack: <target> <source> <size>
- //@TODO do not use ::CALL if less than 32 bytes?
m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4;
- utils.memoryCopy();
+ // We can resort to copying full 32 bytes only if
+ // - the length is known to be a multiple of 32 or
+ // - we will pad to full 32 bytes later anyway.
+ if (((baseSize % 32) == 0) || _padToWordBoundaries)
+ utils.memoryCopy32();
+ else
+ utils.memoryCopy();
m_context << Instruction::SWAP1 << Instruction::POP;
// stack: <target> <size>
@@ -430,7 +449,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2;
if (_sourceType.isDynamicallySized())
{
- // actual array data is stored at SHA3(storage_offset)
+ // actual array data is stored at KECCAK256(storage_offset)
m_context << Instruction::SWAP1;
utils.computeHashStatic();
m_context << Instruction::SWAP1;
@@ -497,60 +516,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
}
}
-void ArrayUtils::clearArray(ArrayType const& _type) const
+void ArrayUtils::clearArray(ArrayType const& _typeIn) const
{
- unsigned stackHeightStart = m_context.stackHeight();
- solAssert(_type.location() == DataLocation::Storage, "");
- if (_type.baseType()->storageBytes() < 32)
- {
- solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
- solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
- }
- if (_type.baseType()->isValueType())
- solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
-
- m_context << Instruction::POP; // remove byte offset
- if (_type.isDynamicallySized())
- clearDynamicArray(_type);
- else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
- m_context << Instruction::POP;
- else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
- {
- // unroll loop for small arrays @todo choose a good value
- // Note that we loop over storage slots here, not elements.
- for (unsigned i = 1; i < _type.storageSize(); ++i)
- m_context
- << u256(0) << Instruction::DUP2 << Instruction::SSTORE
- << u256(1) << Instruction::ADD;
- m_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
- }
- else if (!_type.baseType()->isValueType() && _type.length() <= 4)
- {
- // unroll loop for small arrays @todo choose a good value
- solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
- for (unsigned i = 1; i < _type.length(); ++i)
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$clearArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
{
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false);
- m_context
- << Instruction::POP
- << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ unsigned stackHeightStart = _context.stackHeight();
+ solAssert(_type.location() == DataLocation::Storage, "");
+ if (_type.baseType()->storageBytes() < 32)
+ {
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+ solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
+ }
+ if (_type.baseType()->isValueType())
+ solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
+
+ _context << Instruction::POP; // remove byte offset
+ if (_type.isDynamicallySized())
+ ArrayUtils(_context).clearDynamicArray(_type);
+ else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
+ _context << Instruction::POP;
+ else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
+ {
+ // unroll loop for small arrays @todo choose a good value
+ // Note that we loop over storage slots here, not elements.
+ for (unsigned i = 1; i < _type.storageSize(); ++i)
+ _context
+ << u256(0) << Instruction::DUP2 << Instruction::SSTORE
+ << u256(1) << Instruction::ADD;
+ _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
+ }
+ else if (!_type.baseType()->isValueType() && _type.length() <= 4)
+ {
+ // unroll loop for small arrays @todo choose a good value
+ solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
+ for (unsigned i = 1; i < _type.length(); ++i)
+ {
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false);
+ _context
+ << Instruction::POP
+ << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ }
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true);
+ }
+ else
+ {
+ _context << Instruction::DUP1 << _type.length();
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::ADD << Instruction::SWAP1;
+ if (_type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
+ _context << Instruction::POP;
+ }
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
}
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
- }
- else
- {
- m_context << Instruction::DUP1 << _type.length();
- convertLengthToSize(_type);
- m_context << Instruction::ADD << Instruction::SWAP1;
- if (_type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
- m_context << Instruction::POP;
- }
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ );
}
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
@@ -584,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
<< Instruction::SWAP1;
// stack: data_pos_end data_pos
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
+ clearStorageLoop(make_shared<IntegerType>(256));
else
- clearStorageLoop(*_type.baseType());
+ clearStorageLoop(_type.baseType());
// cleanup
m_context << endTag;
m_context << Instruction::POP;
}
-void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const
+void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
{
- solAssert(_type.location() == DataLocation::Storage, "");
- solAssert(_type.isDynamicallySized(), "");
- if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
- solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
-
- unsigned stackHeightStart = m_context.stackHeight();
- eth::AssemblyItem resizeEnd = m_context.newTag();
-
- // stack: ref new_length
- // fetch old length
- retrieveLength(_type, 1);
- // stack: ref new_length old_length
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2");
-
- // Special case for short byte arrays, they are stored together with their length
- if (_type.isByteArray())
- {
- eth::AssemblyItem regularPath = m_context.newTag();
- // We start by a large case-distinction about the old and new length of the byte array.
-
- m_context << Instruction::DUP3 << Instruction::SLOAD;
- // stack: ref new_length current_length ref_value
-
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context << Instruction::DUP2 << u256(31) << Instruction::LT;
- eth::AssemblyItem currentIsLong = m_context.appendConditionalJump();
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- eth::AssemblyItem newIsLong = m_context.appendConditionalJump();
-
- // Here: short -> short
-
- // Compute 1 << (256 - 8 * new_size)
- eth::AssemblyItem shortToShort = m_context.newTag();
- m_context << shortToShort;
- m_context << Instruction::DUP3 << u256(8) << Instruction::MUL;
- m_context << u256(0x100) << Instruction::SUB;
- m_context << u256(2) << Instruction::EXP;
- // Divide and multiply by that value, clearing bits.
- m_context << Instruction::DUP1 << Instruction::SWAP2;
- m_context << Instruction::DIV << Instruction::MUL;
- // Insert 2*length.
- m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << Instruction::OR;
- // Store.
- m_context << Instruction::DUP4 << Instruction::SSTORE;
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
- m_context.appendJumpTo(resizeEnd);
-
- m_context.adjustStackOffset(1); // we have to do that because of the jumps
- // Here: short -> long
-
- m_context << newIsLong;
- // stack: ref new_length current_length ref_value
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- // Zero out lower-order byte.
- m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
- // Store at data location.
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::SSTORE;
- // stack: ref new_length current_length
- // Store new length: Compule 2*length + 1 and store it.
- m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- // stack: ref new_length current_length 2*new_length+1
- m_context << Instruction::DUP4 << Instruction::SSTORE;
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
- m_context.appendJumpTo(resizeEnd);
-
- m_context.adjustStackOffset(1); // we have to do that because of the jumps
-
- m_context << currentIsLong;
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- m_context.appendConditionalJumpTo(regularPath);
-
- // Here: long -> short
- // Read the first word of the data and store it on the stack. Clear the data location and
- // then jump to the short -> short case.
-
- // stack: ref new_length current_length ref_value
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context << Instruction::POP << Instruction::DUP3;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location
- m_context << Instruction::DUP3;
- convertLengthToSize(_type);
- m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location_end data_location
- clearStorageLoop(IntegerType(256));
- m_context << Instruction::POP;
- // stack: ref new_length current_length first_word
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context.appendJumpTo(shortToShort);
-
- m_context << regularPath;
- // stack: ref new_length current_length ref_value
- m_context << Instruction::POP;
- }
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$resizeDynamicArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
+ {
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ solAssert(_type.location() == DataLocation::Storage, "");
+ solAssert(_type.isDynamicallySized(), "");
+ if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+
+ unsigned stackHeightStart = _context.stackHeight();
+ eth::AssemblyItem resizeEnd = _context.newTag();
+
+ // stack: ref new_length
+ // fetch old length
+ ArrayUtils(_context).retrieveLength(_type, 1);
+ // stack: ref new_length old_length
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2");
+
+ // Special case for short byte arrays, they are stored together with their length
+ if (_type.isByteArray())
+ {
+ eth::AssemblyItem regularPath = _context.newTag();
+ // We start by a large case-distinction about the old and new length of the byte array.
+
+ _context << Instruction::DUP3 << Instruction::SLOAD;
+ // stack: ref new_length current_length ref_value
+
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::DUP2 << u256(31) << Instruction::LT;
+ eth::AssemblyItem currentIsLong = _context.appendConditionalJump();
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem newIsLong = _context.appendConditionalJump();
+
+ // Here: short -> short
+
+ // Compute 1 << (256 - 8 * new_size)
+ eth::AssemblyItem shortToShort = _context.newTag();
+ _context << shortToShort;
+ _context << Instruction::DUP3 << u256(8) << Instruction::MUL;
+ _context << u256(0x100) << Instruction::SUB;
+ _context << u256(2) << Instruction::EXP;
+ // Divide and multiply by that value, clearing bits.
+ _context << Instruction::DUP1 << Instruction::SWAP2;
+ _context << Instruction::DIV << Instruction::MUL;
+ // Insert 2*length.
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << Instruction::OR;
+ // Store.
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+ // Here: short -> long
+
+ _context << newIsLong;
+ // stack: ref new_length current_length ref_value
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ // Zero out lower-order byte.
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // Store at data location.
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::SSTORE;
+ // stack: ref new_length current_length
+ // Store new length: Compule 2*length + 1 and store it.
+ _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ // stack: ref new_length current_length 2*new_length+1
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+
+ _context << currentIsLong;
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ _context.appendConditionalJumpTo(regularPath);
+
+ // Here: long -> short
+ // Read the first word of the data and store it on the stack. Clear the data location and
+ // then jump to the short -> short case.
+
+ // stack: ref new_length current_length ref_value
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::POP << Instruction::DUP3;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
+ // stack: ref new_length current_length first_word data_location
+ _context << Instruction::DUP3;
+ 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));
+ _context << Instruction::POP;
+ // stack: ref new_length current_length first_word
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context.appendJumpTo(shortToShort);
+
+ _context << regularPath;
+ // stack: ref new_length current_length ref_value
+ _context << Instruction::POP;
+ }
- // Change of length for a regular array (i.e. length at location, data at sha3(location)).
- // stack: ref new_length old_length
- // store new length
- m_context << Instruction::DUP2;
- if (_type.isByteArray())
- // For a "long" byte array, store length as 2*length+1
- m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
- m_context<< Instruction::DUP4 << Instruction::SSTORE;
- // skip if size is not reduced
- m_context << Instruction::DUP2 << Instruction::DUP2
- << Instruction::ISZERO << Instruction::GT;
- m_context.appendConditionalJumpTo(resizeEnd);
-
- // size reduced, clear the end of the array
- // stack: ref new_length old_length
- convertLengthToSize(_type);
- m_context << Instruction::DUP2;
- convertLengthToSize(_type);
- // stack: ref new_length old_size new_size
- // compute data positions
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- // stack: ref new_length old_size new_size data_pos
- m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
- // stack: ref new_length data_pos new_size delete_end
- m_context << Instruction::SWAP2 << Instruction::ADD;
- // stack: ref new_length delete_end delete_start
- if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
+ // Change of length for a regular array (i.e. length at location, data at KECCAK256(location)).
+ // stack: ref new_length old_length
+ // store new length
+ _context << Instruction::DUP2;
+ if (_type.isByteArray())
+ // For a "long" byte array, store length as 2*length+1
+ _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
+ _context<< Instruction::DUP4 << Instruction::SSTORE;
+ // skip if size is not reduced
+ _context << Instruction::DUP2 << Instruction::DUP2
+ << Instruction::ISZERO << Instruction::GT;
+ _context.appendConditionalJumpTo(resizeEnd);
+
+ // size reduced, clear the end of the array
+ // stack: ref new_length old_length
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::DUP2;
+ ArrayUtils(_context).convertLengthToSize(_type);
+ // stack: ref new_length old_size new_size
+ // compute data positions
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ // stack: ref new_length old_size new_size data_pos
+ _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
+ // stack: ref new_length data_pos new_size delete_end
+ _context << Instruction::SWAP2 << Instruction::ADD;
+ // stack: ref new_length delete_end delete_start
+ if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
- m_context << resizeEnd;
- // cleanup
- m_context << Instruction::POP << Instruction::POP << Instruction::POP;
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ _context << resizeEnd;
+ // cleanup
+ _context << Instruction::POP << Instruction::POP << Instruction::POP;
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
+ }
+ );
}
-void ArrayUtils::clearStorageLoop(Type const& _type) const
+void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{
- unsigned stackHeightStart = m_context.stackHeight();
- if (_type.category() == Type::Category::Mapping)
- {
- m_context << Instruction::POP;
- return;
- }
- // stack: end_pos pos
-
- // jump to and return from the loop to allow for duplicate code removal
- eth::AssemblyItem returnTag = m_context.pushNewTag();
- m_context << Instruction::SWAP2 << Instruction::SWAP1;
-
- // stack: <return tag> end_pos pos
- eth::AssemblyItem loopStart = m_context.appendJumpToNew();
- m_context << loopStart;
- // check for loop condition
- m_context << Instruction::DUP1 << Instruction::DUP3
- << Instruction::GT << Instruction::ISZERO;
- eth::AssemblyItem zeroLoopEnd = m_context.newTag();
- m_context.appendConditionalJumpTo(zeroLoopEnd);
- // delete
- m_context << u256(0);
- StorageItem(m_context, _type).setToZero(SourceLocation(), false);
- m_context << Instruction::POP;
- // increment
- m_context << _type.storageSize() << Instruction::ADD;
- m_context.appendJumpTo(loopStart);
- // cleanup
- m_context << zeroLoopEnd;
- m_context << Instruction::POP << Instruction::SWAP1;
- // "return"
- m_context << Instruction::JUMP;
-
- m_context << returnTag;
- solAssert(m_context.stackHeight() == stackHeightStart - 1, "");
+ m_context.callLowLevelFunction(
+ "$clearStorageLoop_" + _type->identifier(),
+ 2,
+ 1,
+ [_type](CompilerContext& _context)
+ {
+ unsigned stackHeightStart = _context.stackHeight();
+ if (_type->category() == Type::Category::Mapping)
+ {
+ _context << Instruction::POP;
+ return;
+ }
+ // stack: end_pos pos
+
+ // jump to and return from the loop to allow for duplicate code removal
+ eth::AssemblyItem returnTag = _context.pushNewTag();
+ _context << Instruction::SWAP2 << Instruction::SWAP1;
+
+ // stack: <return tag> end_pos pos
+ eth::AssemblyItem loopStart = _context.appendJumpToNew();
+ _context << loopStart;
+ // check for loop condition
+ _context << Instruction::DUP1 << Instruction::DUP3
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem zeroLoopEnd = _context.newTag();
+ _context.appendConditionalJumpTo(zeroLoopEnd);
+ // delete
+ _context << u256(0);
+ StorageItem(_context, *_type).setToZero(SourceLocation(), false);
+ _context << Instruction::POP;
+ // increment
+ _context << _type->storageSize() << Instruction::ADD;
+ _context.appendJumpTo(loopStart);
+ // cleanup
+ _context << zeroLoopEnd;
+ _context << Instruction::POP << Instruction::SWAP1;
+ // "return"
+ _context << Instruction::JUMP;
+
+ _context << returnTag;
+ solAssert(_context.stackHeight() == stackHeightStart - 1, "");
+ }
+ );
}
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
@@ -854,7 +901,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
// check out-of-bounds access
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
if (location == DataLocation::CallData && _arrayType.isDynamicallySized())
// remove length if present
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index d0ba2892..806fbea7 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -22,6 +22,8 @@
#pragma once
+#include <memory>
+
namespace dev
{
namespace solidity
@@ -30,6 +32,7 @@ namespace solidity
class CompilerContext;
class Type;
class ArrayType;
+using TypePointer = std::shared_ptr<Type const>;
/**
* Class that provides code generation for handling arrays.
@@ -67,7 +70,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(Type const& _type) const;
+ void clearStorageLoop(TypePointer const& _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
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index 2de5a3ec..6875bda1 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -21,14 +21,21 @@
*/
#include <libsolidity/codegen/CompilerContext.h>
-#include <utility>
-#include <numeric>
-#include <boost/algorithm/string/replace.hpp>
+#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/interface/Version.h>
-#include <libsolidity/inlineasm/AsmData.h>
-#include <libsolidity/inlineasm/AsmStack.h>
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libsolidity/parsing/Scanner.h>
+#include <libsolidity/inlineasm/AsmParser.h>
+#include <libsolidity/inlineasm/AsmCodeGen.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <boost/algorithm/string/replace.hpp>
+
+#include <utility>
+#include <numeric>
using namespace std;
@@ -57,10 +64,67 @@ void CompilerContext::startFunction(Declaration const& _function)
*this << functionEntryLabel(_function);
}
+void CompilerContext::callLowLevelFunction(
+ string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ function<void(CompilerContext&)> const& _generator
+)
+{
+ eth::AssemblyItem retTag = pushNewTag();
+ CompilerUtils(*this).moveIntoStack(_inArgs);
+
+ *this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator);
+
+ appendJump(eth::AssemblyItem::JumpType::IntoFunction);
+ adjustStackOffset(int(_outArgs) - 1 - _inArgs);
+ *this << retTag.tag();
+}
+
+eth::AssemblyItem CompilerContext::lowLevelFunctionTag(
+ string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ function<void(CompilerContext&)> const& _generator
+)
+{
+ auto it = m_lowLevelFunctions.find(_name);
+ if (it == m_lowLevelFunctions.end())
+ {
+ eth::AssemblyItem tag = newTag().pushTag();
+ m_lowLevelFunctions.insert(make_pair(_name, tag));
+ m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator));
+ return tag;
+ }
+ else
+ return it->second;
+}
+
+void CompilerContext::appendMissingLowLevelFunctions()
+{
+ while (!m_lowLevelFunctionGenerationQueue.empty())
+ {
+ string name;
+ unsigned inArgs;
+ unsigned outArgs;
+ function<void(CompilerContext&)> generator;
+ tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front();
+ m_lowLevelFunctionGenerationQueue.pop();
+
+ setStackOffset(inArgs + 1);
+ *this << m_lowLevelFunctions.at(name).tag();
+ generator(*this);
+ CompilerUtils(*this).moveToStackTop(outArgs);
+ appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + ".");
+ }
+}
+
void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, "");
+ solAssert(m_localVariables.count(&_declaration) == 0, "Variable already present");
m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent;
}
@@ -167,6 +231,34 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy
return *this << item;
}
+CompilerContext& CompilerContext::appendInvalid()
+{
+ return *this << Instruction::INVALID;
+}
+
+CompilerContext& CompilerContext::appendConditionalInvalid()
+{
+ *this << Instruction::ISZERO;
+ eth::AssemblyItem afterTag = appendConditionalJump();
+ *this << Instruction::INVALID;
+ *this << afterTag;
+ return *this;
+}
+
+CompilerContext& CompilerContext::appendRevert()
+{
+ return *this << u256(0) << u256(0) << Instruction::REVERT;
+}
+
+CompilerContext& CompilerContext::appendConditionalRevert()
+{
+ *this << Instruction::ISZERO;
+ eth::AssemblyItem afterTag = appendConditionalJump();
+ appendRevert();
+ *this << afterTag;
+ return *this;
+}
+
void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{
stack<ASTNode const*> newStack;
@@ -191,33 +283,56 @@ void CompilerContext::appendInlineAssembly(
assembly = &replacedAssembly;
}
- unsigned startStackHeight = stackHeight();
- auto identifierAccess = [&](
+ int startStackHeight = stackHeight();
+
+ julia::ExternalIdentifierAccess identifierAccess;
+ identifierAccess.resolve = [&](
assembly::Identifier const& _identifier,
- eth::Assembly& _assembly,
- assembly::CodeGenerator::IdentifierContext _context
- ) {
+ julia::IdentifierContext,
+ bool
+ )
+ {
+ auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
+ return it == _localVariables.end() ? size_t(-1) : 1;
+ };
+ identifierAccess.generateCode = [&](
+ assembly::Identifier const& _identifier,
+ julia::IdentifierContext _context,
+ julia::AbstractAssembly& _assembly
+ )
+ {
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
- if (it == _localVariables.end())
- return false;
- unsigned stackDepth = _localVariables.end() - it;
- int stackDiff = _assembly.deposit() - startStackHeight + stackDepth;
+ solAssert(it != _localVariables.end(), "");
+ int stackDepth = _localVariables.end() - it;
+ int stackDiff = _assembly.stackHeight() - startStackHeight + stackDepth;
+ if (_context == julia::IdentifierContext::LValue)
+ stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
- errinfo_comment("Stack too deep, try removing local variables.")
+ errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
);
- if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
- _assembly.append(dupInstruction(stackDiff));
+ if (_context == julia::IdentifierContext::RValue)
+ _assembly.appendInstruction(dupInstruction(stackDiff));
else
{
- _assembly.append(swapInstruction(stackDiff));
- _assembly.append(Instruction::POP);
+ _assembly.appendInstruction(swapInstruction(stackDiff));
+ _assembly.appendInstruction(Instruction::POP);
}
- return true;
};
- solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "");
+ ErrorList errors;
+ ErrorReporter errorReporter(errors);
+ auto scanner = make_shared<Scanner>(CharStream(*assembly), "--CODEGEN--");
+ auto parserResult = assembly::Parser(errorReporter).parse(scanner);
+ solAssert(parserResult, "Failed to parse inline assembly block.");
+ solAssert(errorReporter.errors().empty(), "Failed to parse inline assembly block.");
+
+ assembly::AsmAnalysisInfo analysisInfo;
+ assembly::AsmAnalyzer analyzer(analysisInfo, errorReporter, false, identifierAccess.resolve);
+ solAssert(analyzer.analyze(*parserResult), "Failed to analyze inline assembly block.");
+ solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block.");
+ assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess);
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 80671528..1968c1e1 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -22,17 +22,21 @@
#pragma once
-#include <ostream>
-#include <stack>
-#include <queue>
-#include <utility>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/Assembly.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/ASTAnnotations.h>
+
+#include <libevmasm/Instruction.h>
+#include <libevmasm/Assembly.h>
+
#include <libdevcore/Common.h>
+#include <ostream>
+#include <stack>
+#include <queue>
+#include <utility>
+#include <functional>
+
namespace dev {
namespace solidity {
@@ -90,6 +94,29 @@ public:
/// as "having code".
void startFunction(Declaration const& _function);
+ /// Appends a call to the named low-level function and inserts the generator into the
+ /// list of low-level-functions to be generated, unless it already exists.
+ /// Note that the generator should not assume that objects are still alive when it is called,
+ /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
+ void callLowLevelFunction(
+ std::string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ std::function<void(CompilerContext&)> const& _generator
+ );
+ /// Returns the tag of the named low-level function and inserts the generator into the
+ /// list of low-level-functions to be generated, unless it already exists.
+ /// Note that the generator should not assume that objects are still alive when it is called,
+ /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
+ eth::AssemblyItem lowLevelFunctionTag(
+ std::string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ std::function<void(CompilerContext&)> const& _generator
+ );
+ /// Generates the code for missing low-level functions, i.e. calls the generators passed above.
+ void appendMissingLowLevelFunctions();
+
ModifierDefinition const& functionModifier(std::string const& _name) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
@@ -109,9 +136,15 @@ public:
/// Appends a JUMP to a new tag and @returns the tag
eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); }
/// Appends a JUMP to a tag already on the stack
- CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
- /// Returns an "ErrorTag"
- eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
+ CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
+ /// Appends an INVALID instruction
+ CompilerContext& appendInvalid();
+ /// Appends a conditional INVALID instruction
+ CompilerContext& appendConditionalInvalid();
+ /// Appends a REVERT(0, 0) call
+ CompilerContext& appendRevert();
+ /// Appends a conditional REVERT(0, 0) call
+ CompilerContext& appendConditionalRevert();
/// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag.
@@ -120,10 +153,10 @@ public:
eth::AssemblyItem newTag() { return m_asm->newTag(); }
/// 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.
- eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { auto sub = m_asm->newSub(_assembly); m_asm->append(m_asm->newPushSubSize(size_t(sub.data()))); return sub; }
- void pushSubroutineSize(size_t _subRoutine) { m_asm->append(m_asm->newPushSubSize(_subRoutine)); }
+ eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); }
+ void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); }
/// Pushes the offset of the subroutine.
- void pushSubroutineOffset(size_t _subRoutine) { m_asm->append(eth::AssemblyItem(eth::PushSub, _subRoutine)); }
+ void pushSubroutineOffset(size_t _subRoutine) { m_asm->pushSubroutineOffset(_subRoutine); }
/// Pushes the size of the final program
void appendProgramSize() { m_asm->appendProgramSize(); }
/// Adds data to the data section, pushes a reference to the stack
@@ -248,6 +281,10 @@ private:
CompilerContext *m_runtimeContext;
/// The index of the runtime subroutine.
size_t m_runtimeSub = -1;
+ /// An index of low-level function labels by name.
+ std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions;
+ /// The queue of low-level functions to generate.
+ std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
};
}
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index d5361ac6..7067ddd5 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -128,14 +128,14 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
m_context << Instruction::DUP1;
storeStringData(bytesConstRef(str->value()));
if (_padToWordBoundaries)
- m_context << u256(((str->value().size() + 31) / 32) * 32);
+ m_context << u256(max<size_t>(32, ((str->value().size() + 31) / 32) * 32));
else
m_context << u256(str->value().size());
m_context << Instruction::ADD;
}
else if (
_type.category() == Type::Category::Function &&
- dynamic_cast<FunctionType const&>(_type).location() == FunctionType::Location::External
+ dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
)
{
solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented.");
@@ -180,6 +180,9 @@ void CompilerUtils::encodeToMemory(
t = t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
}
+ if (_givenTypes.empty())
+ return;
+
// Stack during operation:
// <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
// The values dyn_head_i are added during the first loop and they point to the head part
@@ -200,6 +203,7 @@ void CompilerUtils::encodeToMemory(
// leave end_of_mem as dyn head pointer
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
dynPointers++;
+ solAssert((argSize + dynPointers) < 16, "Stack too deep, try using less variables.");
}
else
{
@@ -298,21 +302,49 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
m_context << Instruction::SWAP1 << Instruction::POP;
}
+void CompilerUtils::memoryCopy32()
+{
+ // Stack here: size target source
+
+ m_context.appendInlineAssembly(R"(
+ {
+ for { let i := 0 } lt(i, len) { i := add(i, 32) } {
+ mstore(add(dst, i), mload(add(src, i)))
+ }
+ }
+ )",
+ { "len", "dst", "src" }
+ );
+ m_context << Instruction::POP << Instruction::POP << Instruction::POP;
+}
+
void CompilerUtils::memoryCopy()
{
// Stack here: size target source
- // stack for call: outsize target size source value contract gas
- //@TODO do not use ::CALL if less than 32 bytes?
- m_context << Instruction::DUP3 << Instruction::SWAP1;
- m_context << u256(0) << u256(identityContractAddress);
- // compute gas costs
- m_context << u256(32) << Instruction::DUP5 << u256(31) << Instruction::ADD;
- static unsigned c_identityGas = 15;
- static unsigned c_identityWordGas = 3;
- m_context << Instruction::DIV << u256(c_identityWordGas) << Instruction::MUL;
- m_context << u256(c_identityGas) << Instruction::ADD;
- m_context << Instruction::CALL;
- m_context << Instruction::POP; // ignore return value
+
+ m_context.appendInlineAssembly(R"(
+ {
+ // copy 32 bytes at once
+ for
+ {}
+ iszero(lt(len, 32))
+ {
+ dst := add(dst, 32)
+ src := add(src, 32)
+ len := sub(len, 32)
+ }
+ { mstore(dst, mload(src)) }
+
+ // copy the remainder (0 < len < 32)
+ let mask := sub(exp(256, sub(32, len)), 1)
+ let srcpart := and(mload(src), not(mask))
+ let dstpart := and(mload(dst), mask)
+ mstore(dst, or(srcpart, dstpart))
+ }
+ )",
+ { "len", "dst", "src" }
+ );
+ m_context << Instruction::POP << Instruction::POP << Instruction::POP;
}
void CompilerUtils::splitExternalFunctionType(bool _leftAligned)
@@ -321,13 +353,16 @@ void CompilerUtils::splitExternalFunctionType(bool _leftAligned)
// address (right aligned), function identifier (right aligned)
if (_leftAligned)
{
- m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV;
+ m_context << Instruction::DUP1;
+ rightShiftNumberOnStack(64 + 32, false);
// <input> <address>
- m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV;
+ m_context << Instruction::SWAP1;
+ rightShiftNumberOnStack(64, false);
}
else
{
- m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
+ m_context << Instruction::DUP1;
+ rightShiftNumberOnStack(32, false);
m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1;
}
m_context << u256(0xffffffffUL) << Instruction::AND;
@@ -339,10 +374,10 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1;
if (!_leftAligned)
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
- m_context << (u256(1) << 32) << Instruction::MUL;
+ leftShiftNumberOnStack(32);
m_context << Instruction::OR;
if (_leftAligned)
- m_context << (u256(1) << 64) << Instruction::MUL;
+ leftShiftNumberOnStack(64);
}
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
@@ -351,14 +386,21 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
// If there is a runtime context, we have to merge both labels into the same
// stack slot in case we store it in storage.
if (CompilerContext* rtc = m_context.runtimeContext())
+ {
+ leftShiftNumberOnStack(32);
m_context <<
- (u256(1) << 32) <<
- Instruction::MUL <<
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
Instruction::OR;
+ }
}
-void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded, bool _chopSignBits)
+void CompilerUtils::convertType(
+ Type const& _typeOnStack,
+ Type const& _targetType,
+ bool _cleanupNeeded,
+ bool _chopSignBits,
+ bool _asPartOfArgumentDecoding
+)
{
// For a type extension, we need to remove all higher-order bits that we might have ignored in
// previous operations.
@@ -387,7 +429,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
// conversion from bytes to integer. no need to clean the high bit
// only to shift right because of opposite alignment
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
- m_context << (u256(1) << (256 - typeOnStack.numBytes() * 8)) << Instruction::SWAP1 << Instruction::DIV;
+ rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8, false);
if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
}
@@ -416,7 +458,10 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ if (_asPartOfArgumentDecoding)
+ m_context.appendConditionalRevert();
+ else
+ m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
break;
@@ -435,7 +480,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
if (targetBytesType.numBytes() * 8 > typeOnStack->numBits())
cleanHigherOrderBits(*typeOnStack);
- m_context << (u256(1) << (256 - targetBytesType.numBytes() * 8)) << Instruction::MUL;
+ leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8);
}
else if (targetTypeCategory == Type::Category::Enum)
{
@@ -445,7 +490,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
else if (targetTypeCategory == Type::Category::FixedPoint)
@@ -735,6 +780,20 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
if (_cleanupNeeded)
m_context << Instruction::ISZERO << Instruction::ISZERO;
break;
+ case Type::Category::Function:
+ {
+ if (targetTypeCategory == Type::Category::Integer)
+ {
+ IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType);
+ solAssert(targetType.isAddress(), "Function type can only be converted to address.");
+ FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
+ solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted.");
+
+ // stack: <address> <function_id>
+ m_context << Instruction::POP;
+ break;
+ }
+ }
default:
// All other types should not be convertible to non-equal types.
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
@@ -753,9 +812,11 @@ void CompilerUtils::pushZeroValue(Type const& _type)
{
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
{
- if (funType->location() == FunctionType::Location::Internal)
+ if (funType->kind() == FunctionType::Kind::Internal)
{
- m_context << m_context.errorTag();
+ m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
+ _context.appendInvalid();
+ });
return;
}
}
@@ -768,37 +829,46 @@ void CompilerUtils::pushZeroValue(Type const& _type)
}
solAssert(referenceType->location() == DataLocation::Memory, "");
- m_context << u256(max(32u, _type.calldataEncodedSize()));
- allocateMemory();
- m_context << Instruction::DUP1;
+ TypePointer type = _type.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$pushZeroValue_" + referenceType->identifier(),
+ 0,
+ 1,
+ [type](CompilerContext& _context) {
+ CompilerUtils utils(_context);
+ _context << u256(max(32u, type->calldataEncodedSize()));
+ utils.allocateMemory();
+ _context << Instruction::DUP1;
- if (auto structType = dynamic_cast<StructType const*>(&_type))
- for (auto const& member: structType->members(nullptr))
- {
- pushZeroValue(*member.type);
- storeInMemoryDynamic(*member.type);
- }
- else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
- {
- if (arrayType->isDynamicallySized())
- {
- // zero length
- m_context << u256(0);
- storeInMemoryDynamic(IntegerType(256));
- }
- else if (arrayType->length() > 0)
- {
- m_context << arrayType->length() << Instruction::SWAP1;
- // stack: items_to_do memory_pos
- zeroInitialiseMemoryArray(*arrayType);
- // stack: updated_memory_pos
- }
- }
- else
- solAssert(false, "Requested initialisation for unknown type: " + _type.toString());
+ if (auto structType = dynamic_cast<StructType const*>(type.get()))
+ 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()))
+ {
+ if (arrayType->isDynamicallySized())
+ {
+ // zero length
+ _context << u256(0);
+ utils.storeInMemoryDynamic(IntegerType(256));
+ }
+ else if (arrayType->length() > 0)
+ {
+ _context << arrayType->length() << Instruction::SWAP1;
+ // stack: items_to_do memory_pos
+ utils.zeroInitialiseMemoryArray(*arrayType);
+ // stack: updated_memory_pos
+ }
+ }
+ else
+ solAssert(false, "Requested initialisation for unknown type: " + type->toString());
- // remove the updated memory pointer
- m_context << Instruction::POP;
+ // remove the updated memory pointer
+ _context << Instruction::POP;
+ }
+ );
}
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
@@ -875,7 +945,7 @@ unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _varia
void CompilerUtils::computeHashStatic()
{
storeInMemory(0);
- m_context << u256(32) << u256(0) << Instruction::SHA3;
+ m_context << u256(32) << u256(0) << Instruction::KECCAK256;
}
void CompilerUtils::storeStringData(bytesConstRef _data)
@@ -900,12 +970,12 @@ void CompilerUtils::storeStringData(bytesConstRef _data)
}
}
-unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
+unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords)
{
- unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries);
+ unsigned numBytes = _type.calldataEncodedSize(_padToWords);
bool isExternalFunctionType = false;
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
- if (funType->location() == FunctionType::Location::External)
+ if (funType->kind() == FunctionType::Kind::External)
isExternalFunctionType = true;
if (numBytes == 0)
{
@@ -920,11 +990,13 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
{
bool leftAligned = _type.category() == Type::Category::FixedBytes;
// add leading or trailing zeros by dividing/multiplying depending on alignment
- u256 shiftFactor = u256(1) << ((32 - numBytes) * 8);
- m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV;
+ int shiftFactor = (32 - numBytes) * 8;
+ rightShiftNumberOnStack(shiftFactor, false);
if (leftAligned)
- m_context << shiftFactor << Instruction::MUL;
+ leftShiftNumberOnStack(shiftFactor);
}
+ if (_fromCalldata)
+ convertType(_type, _type, true, false, true);
return numBytes;
}
@@ -939,18 +1011,31 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack)
m_context << ((u256(1) << _typeOnStack.numBits()) - 1) << Instruction::AND;
}
-unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
+void CompilerUtils::leftShiftNumberOnStack(unsigned _bits)
+{
+ solAssert(_bits < 256, "");
+ m_context << (u256(1) << _bits) << Instruction::MUL;
+}
+
+void CompilerUtils::rightShiftNumberOnStack(unsigned _bits, bool _isSigned)
+{
+ solAssert(_bits < 256, "");
+ m_context << (u256(1) << _bits) << Instruction::SWAP1 << (_isSigned ? Instruction::SDIV : Instruction::DIV);
+}
+
+unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
{
- unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries);
+ unsigned numBytes = _type.calldataEncodedSize(_padToWords);
bool leftAligned = _type.category() == Type::Category::FixedBytes;
if (numBytes == 0)
m_context << Instruction::POP;
else
{
solAssert(numBytes <= 32, "Memory store of more than 32 bytes requested.");
- if (numBytes != 32 && !leftAligned && !_padToWordBoundaries)
+ convertType(_type, _type, true);
+ if (numBytes != 32 && !leftAligned && !_padToWords)
// shift the value accordingly before storing
- m_context << (u256(1) << ((32 - numBytes) * 8)) << Instruction::MUL;
+ leftShiftNumberOnStack((32 - numBytes) * 8);
}
return numBytes;
}
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index 4baf48ff..fb169463 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -52,13 +52,13 @@ public:
/// @param _offset offset in memory (or calldata)
/// @param _type data type to load
/// @param _fromCalldata if true, load from calldata, not from memory
- /// @param _padToWordBoundaries if true, assume the data is padded to word (32 byte) boundaries
+ /// @param _padToWords if true, assume the data is padded to full words (32 bytes)
/// @returns the number of bytes consumed in memory.
unsigned loadFromMemory(
unsigned _offset,
Type const& _type = IntegerType(256),
bool _fromCalldata = false,
- bool _padToWordBoundaries = false
+ bool _padToWords = false
);
/// Dynamic version of @see loadFromMemory, expects the memory offset on the stack.
/// Stack pre: memory_offset
@@ -66,7 +66,7 @@ public:
void loadFromMemoryDynamic(
Type const& _type,
bool _fromCalldata = false,
- bool _padToWordBoundaries = true,
+ bool _padToWords = true,
bool _keepUpdatedMemoryOffset = true
);
/// Stores a 256 bit integer from stack in memory.
@@ -76,11 +76,11 @@ public:
/// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack
/// and also updates that. For reference types, only copies the data pointer. Fails for
/// non-memory-references.
- /// @param _padToWordBoundaries if true, adds zeros to pad to multiple of 32 bytes. Array elements
+ /// @param _padToWords if true, adds zeros to pad to multiple of 32 bytes. Array elements
/// are always padded (except for byte arrays), regardless of this parameter.
/// Stack pre: memory_offset value...
/// Stack post: (memory_offset+length)
- void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true);
+ void storeInMemoryDynamic(Type const& _type, bool _padToWords = true);
/// Copies values (of types @a _givenTypes) given on the stack to a location in memory given
/// at the stack top, encoding them according to the ABI as the given types @a _targetTypes.
@@ -88,7 +88,7 @@ public:
/// Stack pre: <v1> <v2> ... <vn> <memptr>
/// Stack post: <memptr_updated>
/// Does not touch the memory-free pointer.
- /// @param _padToWordBoundaries if false, all values are concatenated without padding.
+ /// @param _padToWords if false, all values are concatenated without padding.
/// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length)
/// together with fixed-length data.
/// @param _encodeAsLibraryTypes if true, encodes for a library function, e.g. does not
@@ -98,7 +98,7 @@ public:
void encodeToMemory(
TypePointers const& _givenTypes = {},
TypePointers const& _targetTypes = {},
- bool _padToWordBoundaries = true,
+ bool _padToWords = true,
bool _copyDynamicDataInPlace = false,
bool _encodeAsLibraryTypes = false
);
@@ -109,7 +109,13 @@ public:
/// Stack post: <updated_memptr>
void zeroInitialiseMemoryArray(ArrayType const& _type);
- /// Uses a CALL to the identity contract to perform a memory-to-memory copy.
+ /// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length.
+ /// Length can be zero, in this case, it copies nothing.
+ /// Stack pre: <size> <target> <source>
+ /// Stack post:
+ void memoryCopy32();
+ /// Copies data in memory (regions cannot overlap).
+ /// Length can be zero, in this case, it copies nothing.
/// Stack pre: <size> <target> <source>
/// Stack post:
void memoryCopy();
@@ -131,7 +137,15 @@ public:
/// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be
/// necessary.
/// If @a _chopSignBits, the function resets the signed bits out of the width of the signed integer.
- void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false, bool _chopSignBits = false);
+ /// If @a _asPartOfArgumentDecoding is true, failed conversions are flagged via REVERT,
+ /// otherwise they are flagged with INVALID.
+ void convertType(
+ Type const& _typeOnStack,
+ Type const& _targetType,
+ bool _cleanupNeeded = false,
+ bool _chopSignBits = false,
+ bool _asPartOfArgumentDecoding = false
+ );
/// Creates a zero-value for the given type and puts it onto the stack. This might allocate
/// memory for memory references.
@@ -162,7 +176,13 @@ public:
static unsigned sizeOnStack(std::vector<T> const& _variables);
static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes);
- /// Appends code that computes tha SHA3 hash of the topmost stack element of 32 byte type.
+ /// Helper function to shift top value on the stack to the left.
+ void leftShiftNumberOnStack(unsigned _bits);
+
+ /// Helper function to shift top value on the stack to the right.
+ void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false);
+
+ /// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type.
void computeHashStatic();
/// Bytes we need to the start of call data.
@@ -185,9 +205,9 @@ private:
void cleanHigherOrderBits(IntegerType const& _typeOnStack);
/// Prepares the given type for storing in memory by shifting it if necessary.
- unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const;
+ unsigned prepareMemoryStore(Type const& _type, bool _padToWords);
/// Loads type from memory assuming memory offset is on stack top.
- unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries);
+ unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords);
CompilerContext& m_context;
};
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index a0f196bc..cad388df 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -21,15 +21,20 @@
*/
#include <libsolidity/codegen/ContractCompiler.h>
-#include <algorithm>
-#include <boost/range/adaptor/reversed.hpp>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/Assembly.h>
-#include <libevmasm/GasMeter.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/ast/AST.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/codegen/CompilerUtils.h>
+
+#include <libevmasm/Instruction.h>
+#include <libevmasm/Assembly.h>
+#include <libevmasm/GasMeter.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+
+#include <algorithm>
+
using namespace std;
using namespace dev;
using namespace dev::solidity;
@@ -42,7 +47,7 @@ class StackHeightChecker
public:
StackHeightChecker(CompilerContext const& _context):
m_context(_context), stackHeight(m_context.stackHeight()) {}
- void check() { solAssert(m_context.stackHeight() == stackHeight, "I sense a disturbance in the stack."); }
+ void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + std::to_string(m_context.stackHeight()) + " vs " + std::to_string(stackHeight)); }
private:
CompilerContext const& m_context;
unsigned stackHeight;
@@ -106,7 +111,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalRevert();
}
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
@@ -262,16 +267,22 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << notFound;
if (fallback)
{
+ m_context.setStackOffset(0);
if (!fallback->isPayable())
appendCallValueCheck();
+ // Return tag is used to jump out of the function.
eth::AssemblyItem returnTag = m_context.pushNewTag();
fallback->accept(*this);
m_context << returnTag;
- appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary());
+ solAssert(FunctionType(*fallback).parameterTypes().empty(), "");
+ solAssert(FunctionType(*fallback).returnParameterTypes().empty(), "");
+ // Return tag gets consumed.
+ m_context.adjustStackOffset(-1);
+ m_context << Instruction::STOP;
}
else
- m_context.appendJumpTo(m_context.errorTag());
+ m_context.appendRevert();
for (auto const& it: interfaceFunctions)
{
@@ -280,16 +291,29 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration());
m_context << callDataUnpackerEntryPoints.at(it.first);
+ m_context.setStackOffset(0);
// We have to allow this for libraries, because value of the previous
// call is still visible in the delegatecall.
if (!functionType->isPayable() && !_contract.isLibrary())
appendCallValueCheck();
+ // Return tag is used to jump out of the function.
eth::AssemblyItem returnTag = m_context.pushNewTag();
- m_context << CompilerUtils::dataStartOffset;
- appendCalldataUnpacker(functionType->parameterTypes());
+ if (!functionType->parameterTypes().empty())
+ {
+ // Parameter for calldataUnpacker
+ m_context << CompilerUtils::dataStartOffset;
+ appendCalldataUnpacker(functionType->parameterTypes());
+ }
m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration()));
m_context << returnTag;
+ // Return tag and input parameters get consumed.
+ m_context.adjustStackOffset(
+ CompilerUtils(m_context).sizeOnStack(functionType->returnParameterTypes()) -
+ CompilerUtils(m_context).sizeOnStack(functionType->parameterTypes()) -
+ 1
+ );
+ // Consumes the return parameters.
appendReturnValuePacker(functionType->returnParameterTypes(), _contract.isLibrary());
}
}
@@ -363,7 +387,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter
// copy to memory
// move calldata type up again
CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack());
- CompilerUtils(m_context).convertType(*calldataType, arrayType);
+ CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true);
// fetch next pointer again
CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack());
}
@@ -486,7 +510,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
stackLayout.push_back(i);
stackLayout += vector<int>(c_localVariablesSize, -1);
- solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables.");
+ if (stackLayout.size() > 17)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_function.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
@@ -514,94 +543,133 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
- ErrorList errors;
- assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors);
unsigned startStackHeight = m_context.stackHeight();
- codeGen.assemble(
- m_context.nonConstAssembly(),
- [&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
- auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
- if (ref == _inlineAssembly.annotation().externalReferences.end())
- return false;
- Declaration const* decl = ref->second;
- solAssert(!!decl, "");
- if (_context == assembly::CodeGenerator::IdentifierContext::RValue)
+ julia::ExternalIdentifierAccess identifierAccess;
+ identifierAccess.resolve = [&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool)
+ {
+ auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
+ if (ref == _inlineAssembly.annotation().externalReferences.end())
+ return size_t(-1);
+ return ref->second.valueSize;
+ };
+ identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, julia::IdentifierContext _context, julia::AbstractAssembly& _assembly)
+ {
+ auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
+ solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), "");
+ Declaration const* decl = ref->second.declaration;
+ solAssert(!!decl, "");
+ if (_context == julia::IdentifierContext::RValue)
+ {
+ int const depositBefore = _assembly.stackHeight();
+ solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
+ if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
- solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
- if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
+ solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
+ functionDef = &m_context.resolveVirtualFunction(*functionDef);
+ auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag();
+ solAssert(functionEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
+ _assembly.appendLabelReference(size_t(functionEntryLabel.data()));
+ // If there is a runtime context, we have to merge both labels into the same
+ // stack slot in case we store it in storage.
+ if (CompilerContext* rtc = m_context.runtimeContext())
{
- functionDef = &m_context.resolveVirtualFunction(*functionDef);
- _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
- // If there is a runtime context, we have to merge both labels into the same
- // stack slot in case we store it in storage.
- if (CompilerContext* rtc = m_context.runtimeContext())
- {
- _assembly.append(u256(1) << 32);
- _assembly.append(Instruction::MUL);
- _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
- _assembly.append(Instruction::OR);
- }
+ _assembly.appendConstant(u256(1) << 32);
+ _assembly.appendInstruction(Instruction::MUL);
+ auto runtimeEntryLabel = rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub());
+ solAssert(runtimeEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
+ _assembly.appendLabelReference(size_t(runtimeEntryLabel.data()));
+ _assembly.appendInstruction(Instruction::OR);
}
- else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
+ }
+ else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
+ {
+ solAssert(!variable->isConstant(), "");
+ if (m_context.isStateVariable(decl))
{
- solAssert(!variable->isConstant(), "");
- if (m_context.isLocalVariable(variable))
- {
- int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
- if (stackDiff < 1 || stackDiff > 16)
- BOOST_THROW_EXCEPTION(
- CompilerError() <<
- errinfo_comment("Stack too deep, try removing local variables.")
- );
- for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i)
- _assembly.append(dupInstruction(stackDiff));
- }
+ auto const& location = m_context.storageLocationOfVariable(*decl);
+ if (ref->second.isSlot)
+ m_context << location.first;
+ else if (ref->second.isOffset)
+ m_context << u256(location.second);
else
+ solAssert(false, "");
+ }
+ else if (m_context.isLocalVariable(decl))
+ {
+ int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable);
+ if (ref->second.isSlot || ref->second.isOffset)
{
- solAssert(m_context.isStateVariable(variable), "Invalid variable type.");
- auto const& location = m_context.storageLocationOfVariable(*variable);
- if (!variable->type()->isValueType())
+ solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
+ unsigned size = variable->type()->sizeOnStack();
+ if (size == 2)
{
- solAssert(location.second == 0, "Intra-slot offest assumed to be zero.");
- _assembly.append(location.first);
+ // slot plus offset
+ if (ref->second.isOffset)
+ stackDiff--;
}
else
{
- _assembly.append(location.first);
- _assembly.append(u256(location.second));
+ solAssert(size == 1, "");
+ // only slot, offset is zero
+ if (ref->second.isOffset)
+ {
+ _assembly.appendConstant(u256(0));
+ return;
+ }
}
}
- }
- else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
- {
- solAssert(contract->isLibrary(), "");
- _assembly.appendLibraryAddress(contract->name());
+ else
+ solAssert(variable->type()->sizeOnStack() == 1, "");
+ if (stackDiff < 1 || stackDiff > 16)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
+ solAssert(variable->type()->sizeOnStack() == 1, "");
+ _assembly.appendInstruction(dupInstruction(stackDiff));
}
else
- solAssert(false, "Invalid declaration type.");
- } else {
- // lvalue context
- auto variable = dynamic_cast<VariableDeclaration const*>(decl);
- solAssert(
- !!variable && m_context.isLocalVariable(variable),
- "Can only assign to stack variables in inline assembly."
- );
- unsigned size = variable->type()->sizeOnStack();
- int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size;
- if (stackDiff > 16 || stackDiff < 1)
- BOOST_THROW_EXCEPTION(
- CompilerError() <<
- errinfo_comment("Stack too deep, try removing local variables.")
- );
- for (unsigned i = 0; i < size; ++i) {
- _assembly.append(swapInstruction(stackDiff));
- _assembly.append(Instruction::POP);
- }
+ solAssert(false, "");
}
- return true;
+ else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
+ {
+ solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
+ solAssert(contract->isLibrary(), "");
+ _assembly.appendLinkerSymbol(contract->fullyQualifiedName());
+ }
+ else
+ solAssert(false, "Invalid declaration type.");
+ solAssert(_assembly.stackHeight() - depositBefore == int(ref->second.valueSize), "");
+ }
+ else
+ {
+ // lvalue context
+ solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
+ auto variable = dynamic_cast<VariableDeclaration const*>(decl);
+ solAssert(
+ !!variable && m_context.isLocalVariable(variable),
+ "Can only assign to stack variables in inline assembly."
+ );
+ solAssert(variable->type()->sizeOnStack() == 1, "");
+ int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1;
+ if (stackDiff > 16 || stackDiff < 1)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
+ errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
+ );
+ _assembly.appendInstruction(swapInstruction(stackDiff));
+ _assembly.appendInstruction(Instruction::POP);
}
+ };
+ solAssert(_inlineAssembly.annotation().analysisInfo, "");
+ assembly::CodeGenerator::assemble(
+ _inlineAssembly.operations(),
+ *_inlineAssembly.annotation().analysisInfo,
+ m_context.nonConstAssembly(),
+ identifierAccess
);
- solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested.");
m_context.setStackOffset(startStackHeight);
return false;
}
@@ -755,7 +823,8 @@ bool ContractCompiler::visit(Return const& _return)
bool ContractCompiler::visit(Throw const& _throw)
{
CompilerContext::LocationSetter locationSetter(m_context, _throw);
- m_context.appendJumpTo(m_context.errorTag());
+ // Do not send back an error detail.
+ m_context.appendRevert();
return false;
}
@@ -820,6 +889,7 @@ void ContractCompiler::appendMissingFunctions()
function->accept(*this);
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
}
+ m_context.appendMissingLowLevelFunctions();
}
void ContractCompiler::appendModifierOrFunctionCode()
@@ -827,6 +897,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
solAssert(m_currentFunction, "");
unsigned stackSurplus = 0;
Block const* codeBlock = nullptr;
+ vector<VariableDeclaration const*> addedVariables;
m_modifierDepth++;
@@ -850,13 +921,17 @@ void ContractCompiler::appendModifierOrFunctionCode()
for (unsigned i = 0; i < modifier.parameters().size(); ++i)
{
m_context.addVariable(*modifier.parameters()[i]);
+ addedVariables.push_back(modifier.parameters()[i].get());
compileExpression(
*modifierInvocation->arguments()[i],
modifier.parameters()[i]->annotation().type
);
}
for (VariableDeclaration const* localVariable: modifier.localVariables())
+ {
+ addedVariables.push_back(localVariable);
appendStackVariableInitialisation(*localVariable);
+ }
stackSurplus =
CompilerUtils::sizeOnStack(modifier.parameters()) +
@@ -876,6 +951,8 @@ void ContractCompiler::appendModifierOrFunctionCode()
m_returnTags.pop_back();
CompilerUtils(m_context).popStackSlots(stackSurplus);
+ for (auto var: addedVariables)
+ m_context.removeVariable(*var);
}
m_modifierDepth--;
}
@@ -910,7 +987,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime()
a << Instruction::DELEGATECALL;
//Propagate error condition (if DELEGATECALL pushes 0 on stack).
a << Instruction::ISZERO;
- a.appendJumpI(a.errorTag());
+ a << Instruction::ISZERO;
+ eth::AssemblyItem afterTag = a.appendJumpI().tag();
+ a << Instruction::INVALID << afterTag;
//@todo adjust for larger return values, make this dynamic.
a << u256(0x20) << u256(0) << Instruction::RETURN;
return make_shared<eth::Assembly>(a);
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index 5748d818..82518e8c 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -32,6 +32,7 @@
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/codegen/LValue.h>
#include <libevmasm/GasMeter.h>
+
using namespace std;
namespace dev
@@ -87,6 +88,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
FunctionType accessorType(_varDecl);
TypePointers paramTypes = accessorType.parameterTypes();
+ m_context.adjustStackOffset(1 + CompilerUtils::sizeOnStack(paramTypes));
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
@@ -110,7 +112,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
// move key to memory.
utils().copyToStackTop(paramTypes.size() - i, 1);
utils().storeInMemory(0);
- m_context << u256(64) << u256(0) << Instruction::SHA3;
+ m_context << u256(64) << u256(0) << Instruction::KECCAK256;
// push offset
m_context << u256(0);
returnType = mappingType->valueType();
@@ -197,21 +199,40 @@ bool ExpressionCompiler::visit(Conditional const& _condition)
bool ExpressionCompiler::visit(Assignment const& _assignment)
{
CompilerContext::LocationSetter locationSetter(m_context, _assignment);
+ Token::Value op = _assignment.assignmentOperator();
+ Token::Value binOp = op == Token::Assign ? op : Token::AssignmentToBinaryOp(op);
+ Type const& leftType = *_assignment.leftHandSide().annotation().type;
+ if (leftType.category() == Type::Category::Tuple)
+ {
+ solAssert(*_assignment.annotation().type == TupleType(), "");
+ solAssert(op == Token::Assign, "");
+ }
+ else
+ solAssert(*_assignment.annotation().type == leftType, "");
+ bool cleanupNeeded = false;
+ if (op != Token::Assign)
+ cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp);
_assignment.rightHandSide().accept(*this);
// Perform some conversion already. This will convert storage types to memory and literals
// to their actual type, but will not convert e.g. memory to storage.
- TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType(
- _assignment.leftHandSide().annotation().type
- );
- utils().convertType(*_assignment.rightHandSide().annotation().type, *type);
+ TypePointer rightIntermediateType;
+ if (op != Token::Assign && Token::isShiftOp(binOp))
+ rightIntermediateType = _assignment.rightHandSide().annotation().type->mobileType();
+ else
+ rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType(
+ _assignment.leftHandSide().annotation().type
+ );
+ solAssert(rightIntermediateType, "");
+ utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded);
_assignment.leftHandSide().accept(*this);
solAssert(!!m_currentLValue, "LValue not retrieved.");
- Token::Value op = _assignment.assignmentOperator();
- if (op != Token::Assign) // compound assignment
+ if (op == Token::Assign)
+ m_currentLValue->storeValue(*rightIntermediateType, _assignment.location());
+ else // compound assignment
{
- solUnimplementedAssert(_assignment.annotation().type->isValueType(), "Compound operators not implemented for non-value types.");
+ solAssert(leftType.isValueType(), "Compound operators only available for value types.");
unsigned lvalueSize = m_currentLValue->sizeOnStack();
unsigned itemSize = _assignment.annotation().type->sizeOnStack();
if (lvalueSize > 0)
@@ -221,16 +242,29 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
// value lvalue_ref value lvalue_ref
}
m_currentLValue->retrieveValue(_assignment.location(), true);
- appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.annotation().type);
+ utils().convertType(leftType, leftType, cleanupNeeded);
+
+ if (Token::isShiftOp(binOp))
+ appendShiftOperatorCode(binOp, leftType, *rightIntermediateType);
+ else
+ {
+ solAssert(leftType == *rightIntermediateType, "");
+ appendOrdinaryBinaryOperatorCode(binOp, leftType);
+ }
if (lvalueSize > 0)
{
- solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables.");
+ if (itemSize + lvalueSize > 16)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_assignment.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
// value [lvalue_ref] updated_value
for (unsigned i = 0; i < itemSize; ++i)
m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP;
}
+ m_currentLValue->storeValue(*_assignment.annotation().type, _assignment.location());
}
- m_currentLValue->storeValue(*type, _assignment.location());
m_currentLValue.reset();
return false;
}
@@ -351,20 +385,20 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
Expression const& leftExpression = _binaryOperation.leftExpression();
Expression const& rightExpression = _binaryOperation.rightExpression();
solAssert(!!_binaryOperation.annotation().commonType, "");
- Type const& commonType = *_binaryOperation.annotation().commonType;
+ TypePointer const& commonType = _binaryOperation.annotation().commonType;
Token::Value const c_op = _binaryOperation.getOperator();
if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting
appendAndOrOperatorCode(_binaryOperation);
- else if (commonType.category() == Type::Category::RationalNumber)
- m_context << commonType.literalValue(nullptr);
+ else if (commonType->category() == Type::Category::RationalNumber)
+ m_context << commonType->literalValue(nullptr);
else
{
- bool cleanupNeeded = false;
- if (Token::isCompareOp(c_op))
- cleanupNeeded = true;
- if (commonType.category() == Type::Category::Integer && (c_op == Token::Div || c_op == Token::Mod))
- cleanupNeeded = true;
+ bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op);
+
+ TypePointer leftTargetType = commonType;
+ TypePointer rightTargetType = Token::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType;
+ solAssert(rightTargetType, "");
// for commutative operators, push the literal as late as possible to allow improved optimization
auto isLiteral = [](Expression const& _e)
@@ -375,21 +409,24 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
if (swap)
{
leftExpression.accept(*this);
- utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded);
+ utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded);
rightExpression.accept(*this);
- utils().convertType(*rightExpression.annotation().type, commonType, cleanupNeeded);
+ utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded);
}
else
{
rightExpression.accept(*this);
- utils().convertType(*rightExpression.annotation().type, commonType, cleanupNeeded);
+ utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded);
leftExpression.accept(*this);
- utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded);
+ utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded);
}
- if (Token::isCompareOp(c_op))
- appendCompareOperatorCode(c_op, commonType);
+ if (Token::isShiftOp(c_op))
+ // shift only cares about the signedness of both sides
+ appendShiftOperatorCode(c_op, *leftTargetType, *rightTargetType);
+ else if (Token::isCompareOp(c_op))
+ appendCompareOperatorCode(c_op, *commonType);
else
- appendOrdinaryBinaryOperatorCode(c_op, commonType);
+ appendOrdinaryBinaryOperatorCode(c_op, *commonType);
}
// do not visit the child nodes, we already did that explicitly
@@ -399,8 +436,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
- using Location = FunctionType::Location;
- if (_functionCall.annotation().isTypeConversion)
+ if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
{
solAssert(_functionCall.arguments().size() == 1, "");
solAssert(_functionCall.names().empty(), "");
@@ -411,7 +447,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
FunctionTypePointer functionType;
- if (_functionCall.annotation().isStructConstructorCall)
+ if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@@ -442,7 +478,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(found, "");
}
- if (_functionCall.annotation().isStructConstructorCall)
+ if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@@ -464,10 +500,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
FunctionType const& function = *functionType;
if (function.bound())
// Only delegatecall and internal functions can be bound, this might be lifted later.
- solAssert(function.location() == Location::DelegateCall || function.location() == Location::Internal, "");
- switch (function.location())
+ solAssert(function.kind() == FunctionType::Kind::DelegateCall || function.kind() == FunctionType::Kind::Internal, "");
+ switch (function.kind())
{
- case Location::Internal:
+ case FunctionType::Kind::Internal:
{
// Calling convention: Caller pushes return address and arguments
// Callee removes them and pushes return values
@@ -490,7 +526,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (m_context.runtimeContext())
// We have a runtime context, so we need the creation part.
- m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
+ utils().rightShiftNumberOnStack(32, false);
else
// Extract the runtime part.
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
@@ -503,16 +539,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
break;
}
- case Location::External:
- case Location::CallCode:
- case Location::DelegateCall:
- case Location::Bare:
- case Location::BareCallCode:
- case Location::BareDelegateCall:
+ case FunctionType::Kind::External:
+ case FunctionType::Kind::CallCode:
+ case FunctionType::Kind::DelegateCall:
+ case FunctionType::Kind::Bare:
+ case FunctionType::Kind::BareCallCode:
+ case FunctionType::Kind::BareDelegateCall:
_functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments);
break;
- case Location::Creation:
+ case FunctionType::Kind::Creation:
{
_functionCall.expression().accept(*this);
solAssert(!function.gasSet(), "Gas limit set for contract creation.");
@@ -523,20 +559,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arg->accept(*this);
argumentTypes.push_back(arg->annotation().type);
}
- ContractDefinition const& contract =
- dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
- // copy the contract's code into memory
- eth::Assembly const& assembly = m_context.compiledContract(contract);
- utils().fetchFreeMemoryPointer();
- // TODO we create a copy here, which is actually what we want.
- // This should be revisited at the point where we fix
- // https://github.com/ethereum/solidity/issues/1092
- // pushes size
- auto subroutine = m_context.addSubroutine(make_shared<eth::Assembly>(assembly));
- m_context << Instruction::DUP1 << subroutine;
- m_context << Instruction::DUP4 << Instruction::CODECOPY;
-
- m_context << Instruction::ADD;
+ ContractDefinition const* contract =
+ &dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
+ m_context.callLowLevelFunction(
+ "$copyContractCreationCodeToMemory_" + contract->type()->identifier(),
+ 0,
+ 1,
+ [contract](CompilerContext& _context)
+ {
+ // copy the contract's code into memory
+ eth::Assembly const& assembly = _context.compiledContract(*contract);
+ CompilerUtils(_context).fetchFreeMemoryPointer();
+ // pushes size
+ auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly));
+ _context << Instruction::DUP1 << subroutine;
+ _context << Instruction::DUP4 << Instruction::CODECOPY;
+ _context << Instruction::ADD;
+ }
+ );
utils().encodeToMemory(argumentTypes, function.parameterTypes());
// now on stack: memory_end_ptr
// need: size, offset, endowment
@@ -548,12 +588,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalRevert();
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
break;
}
- case Location::SetGas:
+ case FunctionType::Kind::SetGas:
{
// stack layout: contract_address function_id [gas] [value]
_functionCall.expression().accept(*this);
@@ -569,7 +609,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::POP;
break;
}
- case Location::SetValue:
+ case FunctionType::Kind::SetValue:
// stack layout: contract_address function_id [gas] [value]
_functionCall.expression().accept(*this);
// Note that function is not the original function, but the ".value" function.
@@ -578,7 +618,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::POP;
arguments.front()->accept(*this);
break;
- case Location::Send:
+ case FunctionType::Kind::Send:
+ case FunctionType::Kind::Transfer:
_functionCall.expression().accept(*this);
// Provide the gas stipend manually at first because we may send zero ether.
// Will be zeroed if we send more than zero ether.
@@ -597,7 +638,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
TypePointers{},
strings(),
strings(),
- Location::Bare,
+ FunctionType::Kind::Bare,
false,
nullptr,
false,
@@ -607,13 +648,22 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
),
{}
);
+ if (function.kind() == FunctionType::Kind::Transfer)
+ {
+ // Check if zero (out of stack or not enough balance).
+ m_context << Instruction::ISZERO;
+ m_context.appendConditionalRevert();
+ }
break;
- case Location::Selfdestruct:
+ case FunctionType::Kind::Selfdestruct:
arguments.front()->accept(*this);
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true);
- m_context << Instruction::SUICIDE;
+ m_context << Instruction::SELFDESTRUCT;
+ break;
+ case FunctionType::Kind::Revert:
+ m_context.appendRevert();
break;
- case Location::SHA3:
+ case FunctionType::Kind::SHA3:
{
TypePointers argumentTypes;
for (auto const& arg: arguments)
@@ -624,16 +674,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().fetchFreeMemoryPointer();
utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true);
utils().toSizeAfterFreeMemoryPointer();
- m_context << Instruction::SHA3;
+ m_context << Instruction::KECCAK256;
break;
}
- case Location::Log0:
- case Location::Log1:
- case Location::Log2:
- case Location::Log3:
- case Location::Log4:
+ case FunctionType::Kind::Log0:
+ case FunctionType::Kind::Log1:
+ case FunctionType::Kind::Log2:
+ case FunctionType::Kind::Log3:
+ case FunctionType::Kind::Log4:
{
- unsigned logNumber = int(function.location()) - int(Location::Log0);
+ unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0);
for (unsigned arg = logNumber; arg > 0; --arg)
{
arguments[arg]->accept(*this);
@@ -650,7 +700,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << logInstruction(logNumber);
break;
}
- case Location::Event:
+ case FunctionType::Kind::Event:
{
_functionCall.expression().accept(*this);
auto const& event = dynamic_cast<EventDefinition const&>(function.declaration());
@@ -671,7 +721,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
true
);
utils().toSizeAfterFreeMemoryPointer();
- m_context << Instruction::SHA3;
+ m_context << Instruction::KECCAK256;
}
else
utils().convertType(
@@ -704,50 +754,50 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << logInstruction(numIndexed);
break;
}
- case Location::BlockHash:
+ case FunctionType::Kind::BlockHash:
{
arguments[0]->accept(*this);
utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true);
m_context << Instruction::BLOCKHASH;
break;
}
- case Location::AddMod:
- case Location::MulMod:
+ case FunctionType::Kind::AddMod:
+ case FunctionType::Kind::MulMod:
{
for (unsigned i = 0; i < 3; i ++)
{
arguments[2 - i]->accept(*this);
utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256));
}
- if (function.location() == Location::AddMod)
+ if (function.kind() == FunctionType::Kind::AddMod)
m_context << Instruction::ADDMOD;
else
m_context << Instruction::MULMOD;
break;
}
- case Location::ECRecover:
- case Location::SHA256:
- case Location::RIPEMD160:
+ case FunctionType::Kind::ECRecover:
+ case FunctionType::Kind::SHA256:
+ case FunctionType::Kind::RIPEMD160:
{
_functionCall.expression().accept(*this);
- static const map<Location, u256> contractAddresses{{Location::ECRecover, 1},
- {Location::SHA256, 2},
- {Location::RIPEMD160, 3}};
- m_context << contractAddresses.find(function.location())->second;
+ static const map<FunctionType::Kind, u256> contractAddresses{{FunctionType::Kind::ECRecover, 1},
+ {FunctionType::Kind::SHA256, 2},
+ {FunctionType::Kind::RIPEMD160, 3}};
+ m_context << contractAddresses.find(function.kind())->second;
for (unsigned i = function.sizeOnStack(); i > 0; --i)
m_context << swapInstruction(i);
appendExternalFunctionCall(function, arguments);
break;
}
- case Location::ByteArrayPush:
- case Location::ArrayPush:
+ case FunctionType::Kind::ByteArrayPush:
+ case FunctionType::Kind::ArrayPush:
{
_functionCall.expression().accept(*this);
solAssert(function.parameterTypes().size() == 1, "");
solAssert(!!function.parameterTypes()[0], "");
TypePointer paramType = function.parameterTypes()[0];
shared_ptr<ArrayType> arrayType =
- function.location() == Location::ArrayPush ?
+ function.kind() == FunctionType::Kind::ArrayPush ?
make_shared<ArrayType>(DataLocation::Storage, paramType) :
make_shared<ArrayType>(DataLocation::Storage);
// get the current length
@@ -766,17 +816,18 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arguments[0]->accept(*this);
// stack: newLength storageSlot slotOffset argValue
TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
+ solAssert(type, "");
utils().convertType(*arguments[0]->annotation().type, *type);
utils().moveToStackTop(1 + type->sizeOnStack());
utils().moveToStackTop(1 + type->sizeOnStack());
// stack: newLength argValue storageSlot slotOffset
- if (function.location() == Location::ArrayPush)
+ if (function.kind() == FunctionType::Kind::ArrayPush)
StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true);
else
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
break;
}
- case Location::ObjectCreation:
+ case FunctionType::Kind::ObjectCreation:
{
// Will allocate at the end of memory (MSIZE) and not write at all unless the base
// type is dynamically sized.
@@ -826,6 +877,23 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::POP;
break;
}
+ case FunctionType::Kind::Assert:
+ case FunctionType::Kind::Require:
+ {
+ arguments.front()->accept(*this);
+ utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false);
+ // jump if condition was met
+ m_context << Instruction::ISZERO << Instruction::ISZERO;
+ auto success = m_context.appendConditionalJump();
+ if (function.kind() == FunctionType::Kind::Assert)
+ // condition was not met, flag an error
+ m_context.appendInvalid();
+ else
+ m_context.appendRevert();
+ // the success branch
+ m_context << success;
+ break;
+ }
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid function type."));
}
@@ -853,7 +921,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
*funType->selfType(),
true
);
- if (funType->location() == FunctionType::Location::Internal)
+ if (funType->kind() == FunctionType::Kind::Internal)
{
FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration());
utils().pushCombinedFunctionEntryLabel(funDef);
@@ -861,10 +929,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
}
else
{
- solAssert(funType->location() == FunctionType::Location::DelegateCall, "");
+ solAssert(funType->kind() == FunctionType::Kind::DelegateCall, "");
auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope());
solAssert(contract && contract->isLibrary(), "");
- m_context.appendLibraryAddress(contract->name());
+ m_context.appendLibraryAddress(contract->fullyQualifiedName());
m_context << funType->externalIdentifier();
utils().moveIntoStack(funType->selfType()->sizeOnStack(), 2);
}
@@ -880,19 +948,44 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(_memberAccess.annotation().type, "_memberAccess has no type");
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
{
- if (funType->location() != FunctionType::Location::Internal)
- {
- _memberAccess.expression().accept(*this);
- m_context << funType->externalIdentifier();
- }
- else
+ switch (funType->kind())
{
+ case FunctionType::Kind::Internal:
// We do not visit the expression here on purpose, because in the case of an
// internal library function call, this would push the library address forcing
// us to link against it although we actually do not need it.
- auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
- solAssert(!!function, "Function not found in member access");
- utils().pushCombinedFunctionEntryLabel(*function);
+ if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
+ utils().pushCombinedFunctionEntryLabel(*function);
+ else
+ solAssert(false, "Function not found in member access");
+ break;
+ case FunctionType::Kind::Event:
+ if (!dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration))
+ solAssert(false, "event not found");
+ // no-op, because the parent node will do the job
+ break;
+ case FunctionType::Kind::External:
+ case FunctionType::Kind::Creation:
+ case FunctionType::Kind::DelegateCall:
+ case FunctionType::Kind::CallCode:
+ case FunctionType::Kind::Send:
+ case FunctionType::Kind::Bare:
+ case FunctionType::Kind::BareCallCode:
+ case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::Transfer:
+ _memberAccess.expression().accept(*this);
+ m_context << funType->externalIdentifier();
+ break;
+ case FunctionType::Kind::Log0:
+ case FunctionType::Kind::Log1:
+ case FunctionType::Kind::Log2:
+ case FunctionType::Kind::Log3:
+ case FunctionType::Kind::Log4:
+ case FunctionType::Kind::ECRecover:
+ case FunctionType::Kind::SHA256:
+ case FunctionType::Kind::RIPEMD160:
+ default:
+ solAssert(false, "unsupported member function");
}
}
else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type.get()))
@@ -961,7 +1054,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
);
m_context << Instruction::BALANCE;
}
- else if ((set<string>{"send", "call", "callcode", "delegatecall"}).count(member))
+ else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member))
utils().convertType(
*_memberAccess.expression().annotation().type,
IntegerType(0, IntegerType::Modifier::Address),
@@ -1121,7 +1214,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
utils().storeInMemoryDynamic(IntegerType(256));
m_context << u256(0);
}
- m_context << Instruction::SHA3;
+ m_context << Instruction::KECCAK256;
m_context << u256(0);
setLValueToStorageItem(_indexAccess);
}
@@ -1173,10 +1266,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
m_context << u256(fixedBytesType.numBytes());
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
m_context << Instruction::BYTE;
- m_context << (u256(1) << (256 - 8)) << Instruction::MUL;
+ utils().leftShiftNumberOnStack(256 - 8);
}
else if (baseType.category() == Type::Category::TypeType)
{
@@ -1218,7 +1311,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
{
if (contract->isLibrary())
- m_context.appendLibraryAddress(contract->name());
+ m_context.appendLibraryAddress(contract->fullyQualifiedName());
}
else if (dynamic_cast<EventDefinition const*>(declaration))
{
@@ -1247,6 +1340,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
{
case Type::Category::RationalNumber:
case Type::Category::Bool:
+ case Type::Category::Integer:
m_context << type->literalValue(&_literal);
break;
case Type::Category::StringLiteral:
@@ -1273,11 +1367,12 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
{
+ solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
if (_operator == Token::Equal || _operator == Token::NotEqual)
{
if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type))
{
- if (funType->location() == FunctionType::Location::Internal)
+ if (funType->kind() == FunctionType::Kind::Internal)
{
// We have to remove the upper bits (construction time value) because they might
// be "unknown" in one of the operands and not in the other.
@@ -1326,8 +1421,6 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator
appendArithmeticOperatorCode(_operator, _type);
else if (Token::isBitOp(_operator))
appendBitOperatorCode(_operator);
- else if (Token::isShiftOp(_operator))
- appendShiftOperatorCode(_operator);
else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator."));
}
@@ -1356,7 +1449,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty
{
// Test for division by zero
m_context << Instruction::DUP2 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
if (_operator == Token::Div)
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV);
@@ -1390,17 +1483,45 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator)
}
}
-void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator)
+void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type const& _valueType, Type const& _shiftAmountType)
{
- solUnimplemented("Shift operators not yet implemented.");
+ // stack: shift_amount value_to_shift
+
+ bool c_valueSigned = false;
+ if (auto valueType = dynamic_cast<IntegerType const*>(&_valueType))
+ c_valueSigned = valueType->isSigned();
+ else
+ solAssert(dynamic_cast<FixedBytesType const*>(&_valueType), "Only integer and fixed bytes type supported for shifts.");
+
+ // The amount can be a RationalNumberType too.
+ bool c_amountSigned = false;
+ if (auto amountType = dynamic_cast<RationalNumberType const*>(&_shiftAmountType))
+ {
+ // This should be handled by the type checker.
+ solAssert(amountType->integerType(), "");
+ solAssert(!amountType->integerType()->isSigned(), "");
+ }
+ else if (auto amountType = dynamic_cast<IntegerType const*>(&_shiftAmountType))
+ c_amountSigned = amountType->isSigned();
+ else
+ solAssert(false, "Invalid shift amount type.");
+
+ // shift by negative amount throws exception
+ if (c_amountSigned)
+ {
+ m_context << u256(0) << Instruction::DUP3 << Instruction::SLT;
+ m_context.appendConditionalInvalid();
+ }
+
switch (_operator)
{
case Token::SHL:
+ m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::MUL;
break;
case Token::SAR:
+ m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV);
break;
case Token::SHR:
- break;
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator."));
}
@@ -1434,11 +1555,10 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (_functionType.bound())
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
- using FunctionKind = FunctionType::Location;
- FunctionKind funKind = _functionType.location();
- bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode;
- bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode;
- bool isDelegateCall = funKind == FunctionKind::BareDelegateCall || funKind == FunctionKind::DelegateCall;
+ auto funKind = _functionType.kind();
+ bool returnSuccessCondition = funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode;
+ bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode;
+ bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
unsigned retSize = 0;
if (returnSuccessCondition)
@@ -1455,7 +1575,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
TypePointers parameterTypes = _functionType.parameterTypes();
bool manualFunctionId = false;
if (
- (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode || funKind == FunctionKind::BareDelegateCall) &&
+ (funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) &&
!_arguments.empty()
)
{
@@ -1490,7 +1610,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
argumentTypes.push_back(_arguments[i]->annotation().type);
}
- if (funKind == FunctionKind::ECRecover)
+ if (funKind == FunctionType::Kind::ECRecover)
{
// Clears 32 bytes of currently free memory and advances free memory pointer.
// Output area will be "start of input area" - 32.
@@ -1546,7 +1666,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
// put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input>
m_context << u256(retSize);
utils().fetchFreeMemoryPointer(); // This is the start of input
- if (funKind == FunctionKind::ECRecover)
+ if (funKind == FunctionType::Kind::ECRecover)
{
// In this case, output is 32 bytes before input and has already been cleared.
m_context << u256(32) << Instruction::DUP2 << Instruction::SUB << Instruction::SWAP1;
@@ -1572,10 +1692,10 @@ void ExpressionCompiler::appendExternalFunctionCall(
bool existenceChecked = false;
// Check the the target contract exists (has code) for non-low-level calls.
- if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall)
+ if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalRevert();
existenceChecked = true;
}
@@ -1611,7 +1731,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalRevert();
}
utils().popStackSlots(remainsSize);
@@ -1620,14 +1740,14 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
// already there
}
- else if (funKind == FunctionKind::RIPEMD160)
+ else if (funKind == FunctionType::Kind::RIPEMD160)
{
// fix: built-in contract returns right-aligned data
utils().fetchFreeMemoryPointer();
utils().loadFromMemoryDynamic(IntegerType(160), false, true, false);
utils().convertType(IntegerType(160), FixedBytesType(20));
}
- else if (funKind == FunctionKind::ECRecover)
+ else if (funKind == FunctionType::Kind::ECRecover)
{
// Output is 32 bytes before input / free mem pointer.
// Failing ecrecover cannot be detected, so we clear output before the call.
@@ -1688,6 +1808,16 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
setLValue<StorageItem>(_expression, *_expression.annotation().type);
}
+bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op)
+{
+ if (Token::isCompareOp(_op) || Token::isShiftOp(_op))
+ return true;
+ else if (_type == Type::Category::Integer && (_op == Token::Div || _op == Token::Mod))
+ return true;
+ else
+ return false;
+}
+
CompilerUtils ExpressionCompiler::utils()
{
return CompilerUtils(m_context);
diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h
index f08bded9..3b8cf1c6 100644
--- a/libsolidity/codegen/ExpressionCompiler.h
+++ b/libsolidity/codegen/ExpressionCompiler.h
@@ -28,7 +28,7 @@
#include <libevmasm/SourceLocation.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/LValue.h>
-#include <libsolidity/interface/Utils.h>
+#include <libsolidity/interface/Exceptions.h>
namespace dev {
namespace eth
@@ -91,7 +91,7 @@ private:
void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type);
void appendBitOperatorCode(Token::Value _operator);
- void appendShiftOperatorCode(Token::Value _operator);
+ void appendShiftOperatorCode(Token::Value _operator, Type const& _valueType, Type const& _shiftAmountType);
/// @}
/// Appends code to call a function of the given type with the given arguments.
@@ -117,6 +117,10 @@ private:
template <class _LValueType, class... _Arguments>
void setLValue(Expression const& _expression, _Arguments const&... _arguments);
+ /// @returns true if the operator applied to the given type requires a cleanup prior to the
+ /// operation.
+ bool cleanupNeededForOp(Type::Category _type, Token::Value _op);
+
/// @returns the CompilerUtils object containing the current context.
CompilerUtils utils();
diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp
index 23fe2d4e..e19cf41e 100644
--- a/libsolidity/codegen/LValue.cpp
+++ b/libsolidity/codegen/LValue.cpp
@@ -186,7 +186,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
solUnimplemented("Not yet implemented - FixedPointType.");
if (m_dataType->category() == Type::Category::FixedBytes)
{
- m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL;
+ CompilerUtils(m_context).leftShiftNumberOnStack(256 - 8 * m_dataType->storageBytes());
cleaned = true;
}
else if (
@@ -199,7 +199,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
}
else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType))
{
- if (fun->location() == FunctionType::Location::External)
+ if (fun->kind() == FunctionType::Kind::External)
{
CompilerUtils(m_context).splitExternalFunctionType(false);
cleaned = true;
@@ -223,7 +223,6 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
{
solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size.");
-
if (m_dataType->storageBytes() == 32)
{
solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size.");
@@ -257,7 +256,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType))
{
solAssert(_sourceType == *m_dataType, "function item stored but target is not equal to source");
- if (fun->location() == FunctionType::Location::External)
+ if (fun->kind() == FunctionType::Kind::External)
// Combine the two-item function type into a single stack slot.
utils.combineExternalFunctionType(false);
else
@@ -268,9 +267,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
else if (m_dataType->category() == Type::Category::FixedBytes)
{
solAssert(_sourceType.category() == Type::Category::FixedBytes, "source not fixed bytes");
- m_context
- << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes()))
- << Instruction::SWAP1 << Instruction::DIV;
+ CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes(), false);
}
else
{
diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp
deleted file mode 100644
index 2903a4e3..00000000
--- a/libsolidity/formal/Why3Translator.cpp
+++ /dev/null
@@ -1,902 +0,0 @@
-/*
- 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 Christian <c@ethdev.com>
- * @date 2015
- * Component that translates Solidity code into the why3 programming language.
- */
-
-#include <libsolidity/formal/Why3Translator.h>
-#include <boost/algorithm/string/predicate.hpp>
-
-using namespace std;
-using namespace dev;
-using namespace dev::solidity;
-
-bool Why3Translator::process(SourceUnit const& _source)
-{
- try
- {
- if (m_lines.size() != 1 || !m_lines.back().contents.empty())
- fatalError(_source, "Multiple source units not yet supported");
- appendPreface();
- _source.accept(*this);
- }
- catch (NoFormalType&)
- {
- solAssert(false, "There is a call to toFormalType() that does not catch NoFormalType exceptions.");
- }
- catch (FatalError& /*_e*/)
- {
- solAssert(m_errorOccured, "");
- }
- return !m_errorOccured;
-}
-
-string Why3Translator::translation() const
-{
- string result;
- for (auto const& line: m_lines)
- result += string(line.indentation, '\t') + line.contents + "\n";
- return result;
-}
-
-void Why3Translator::error(ASTNode const& _node, string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::Why3TranslatorError);
- *err <<
- errinfo_sourceLocation(_node.location()) <<
- errinfo_comment(_description);
- m_errors.push_back(err);
- m_errorOccured = true;
-}
-
-void Why3Translator::fatalError(ASTNode const& _node, string const& _description)
-{
- error(_node, _description);
- BOOST_THROW_EXCEPTION(FatalError());
-}
-
-string Why3Translator::toFormalType(Type const& _type) const
-{
- if (_type.category() == Type::Category::Bool)
- return "bool";
- else if (auto type = dynamic_cast<IntegerType const*>(&_type))
- {
- if (!type->isAddress() && !type->isSigned() && type->numBits() == 256)
- return "uint256";
- }
- else if (auto type = dynamic_cast<ArrayType const*>(&_type))
- {
- if (!type->isByteArray() && type->isDynamicallySized() && type->dataStoredIn(DataLocation::Memory))
- {
- // Not catching NoFormalType exception. Let the caller deal with it.
- string base = toFormalType(*type->baseType());
- return "array " + base;
- }
- }
- else if (auto mappingType = dynamic_cast<MappingType const*>(&_type))
- {
- solAssert(mappingType->keyType(), "A mappingType misses a keyType.");
- if (dynamic_cast<IntegerType const*>(&*mappingType->keyType()))
- {
- //@TODO Use the information from the key type and specify the length of the array as an invariant.
- // Also the constructor need to specify the length of the array.
- solAssert(mappingType->valueType(), "A mappingType misses a valueType.");
- // Not catching NoFormalType exception. Let the caller deal with it.
- string valueTypeFormal = toFormalType(*mappingType->valueType());
- return "array " + valueTypeFormal;
- }
- }
-
- BOOST_THROW_EXCEPTION(NoFormalType()
- << errinfo_noFormalTypeFrom(_type.toString(true)));
-}
-
-void Why3Translator::addLine(string const& _line)
-{
- newLine();
- add(_line);
- newLine();
-}
-
-void Why3Translator::add(string const& _str)
-{
- m_lines.back().contents += _str;
-}
-
-void Why3Translator::newLine()
-{
- if (!m_lines.back().contents.empty())
- m_lines.push_back({"", m_lines.back().indentation});
-}
-
-void Why3Translator::unindent()
-{
- newLine();
- solAssert(m_lines.back().indentation > 0, "");
- m_lines.back().indentation--;
-}
-
-bool Why3Translator::visit(ContractDefinition const& _contract)
-{
- if (m_seenContract)
- error(_contract, "More than one contract not supported.");
- m_seenContract = true;
- m_currentContract.contract = &_contract;
- if (_contract.isLibrary())
- error(_contract, "Libraries not supported.");
-
- addLine("module Contract_" + _contract.name());
- indent();
- addLine("use import int.Int");
- addLine("use import ref.Ref");
- addLine("use import map.Map");
- addLine("use import array.Array");
- addLine("use import int.ComputerDivision");
- addLine("use import mach.int.Unsigned");
- addLine("use import UInt256");
- addLine("exception Revert");
- addLine("exception Return");
-
- if (_contract.stateVariables().empty())
- addLine("type state = ()");
- else
- {
- addLine("type state = {");
- indent();
- m_currentContract.stateVariables = _contract.stateVariables();
- for (VariableDeclaration const* variable: m_currentContract.stateVariables)
- {
- string varType;
- try
- {
- varType = toFormalType(*variable->annotation().type);
- }
- catch (NoFormalType &err)
- {
- string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
- string typeName = typeNamePtr ? " \"" + *typeNamePtr + "\"" : "";
- fatalError(*variable, "Type" + typeName + " not supported for state variable.");
- }
- addLine("mutable _" + variable->name() + ": " + varType);
- }
- unindent();
- addLine("}");
- }
-
- addLine("type account = {");
- indent();
- addLine("mutable balance: uint256;");
- addLine("storage: state");
- unindent();
- addLine("}");
-
- addLine("val external_call (this: account): bool");
- indent();
- addLine("ensures { result = false -> this = (old this) }");
- addLine("writes { this }");
- addSourceFromDocStrings(m_currentContract.contract->annotation());
- unindent();
-
- if (!_contract.baseContracts().empty())
- error(*_contract.baseContracts().front(), "Inheritance not supported.");
- if (!_contract.definedStructs().empty())
- error(*_contract.definedStructs().front(), "User-defined types not supported.");
- if (!_contract.definedEnums().empty())
- error(*_contract.definedEnums().front(), "User-defined types not supported.");
- if (!_contract.events().empty())
- error(*_contract.events().front(), "Events not supported.");
- if (!_contract.functionModifiers().empty())
- error(*_contract.functionModifiers().front(), "Modifiers not supported.");
-
- ASTNode::listAccept(_contract.definedFunctions(), *this);
-
- return false;
-}
-
-void Why3Translator::endVisit(ContractDefinition const&)
-{
- m_currentContract.reset();
- unindent();
- addLine("end");
-}
-
-bool Why3Translator::visit(FunctionDefinition const& _function)
-{
- if (!_function.isImplemented())
- {
- error(_function, "Unimplemented functions not supported.");
- return false;
- }
- if (_function.name().empty())
- {
- error(_function, "Fallback functions not supported.");
- return false;
- }
- if (!_function.modifiers().empty())
- {
- error(_function, "Modifiers not supported.");
- return false;
- }
-
- m_localVariables.clear();
- for (auto const& var: _function.parameters())
- m_localVariables[var->name()] = var.get();
- for (auto const& var: _function.returnParameters())
- m_localVariables[var->name()] = var.get();
- for (auto const& var: _function.localVariables())
- m_localVariables[var->name()] = var;
-
- add("let rec _" + _function.name());
- add(" (this: account)");
- for (auto const& param: _function.parameters())
- {
- string paramType;
- try
- {
- paramType = toFormalType(*param->annotation().type);
- }
- catch (NoFormalType &err)
- {
- string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
- error(*param, "Parameter type \"" + (typeName ? *typeName : "") + "\" not supported.");
- }
- if (param->name().empty())
- error(*param, "Anonymous function parameters not supported.");
- add(" (arg_" + param->name() + ": " + paramType + ")");
- }
- add(":");
-
- indent();
- indent();
- string retString = "(";
- for (auto const& retParam: _function.returnParameters())
- {
- string paramType;
- try
- {
- paramType = toFormalType(*retParam->annotation().type);
- }
- catch (NoFormalType &err)
- {
- string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
- error(*retParam, "Parameter type " + (typeName ? *typeName : "") + " not supported.");
- }
- if (retString.size() != 1)
- retString += ", ";
- retString += paramType;
- }
- add(retString + ")");
- unindent();
-
- addSourceFromDocStrings(_function.annotation());
- if (!m_currentContract.contract)
- error(_function, "Only functions inside contracts allowed.");
- addSourceFromDocStrings(m_currentContract.contract->annotation());
-
- if (_function.isDeclaredConst())
- addLine("ensures { (old this) = this }");
- else
- addLine("writes { this }");
-
- addLine("=");
-
- // store the prestate in the case we need to revert
- addLine("let prestate = {balance = this.balance; storage = " + copyOfStorage() + "} in ");
-
- // initialise local variables
- for (auto const& variable: _function.parameters())
- addLine("let _" + variable->name() + " = ref arg_" + variable->name() + " in");
- for (auto const& variable: _function.returnParameters())
- {
- if (variable->name().empty())
- error(*variable, "Unnamed return variables not yet supported.");
- string varType;
- try
- {
- varType = toFormalType(*variable->annotation().type);
- }
- catch (NoFormalType &err)
- {
- string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
- error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in return parameter not yet supported.");
- }
- addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in");
- }
- for (VariableDeclaration const* variable: _function.localVariables())
- {
- if (variable->name().empty())
- error(*variable, "Unnamed variables not yet supported.");
- string varType;
- try
- {
- varType = toFormalType(*variable->annotation().type);
- }
- catch (NoFormalType &err)
- {
- string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
- error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in variable declaration not yet supported.");
- }
- addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in");
- }
- addLine("try");
-
- _function.body().accept(*this);
- add(";");
- addLine("raise Return");
-
- string retVals;
- for (auto const& variable: _function.returnParameters())
- {
- if (!retVals.empty())
- retVals += ", ";
- retVals += "!_" + variable->name();
- }
- addLine("with Return -> (" + retVals + ") |");
- string reversion = " Revert -> this.balance <- prestate.balance; ";
- for (auto const* variable: m_currentContract.stateVariables)
- reversion += "this.storage._" + variable->name() + " <- prestate.storage._" + variable->name() + "; ";
- //@TODO in case of reversion the return values are wrong - we need to change the
- // return type to include a bool to signify if an exception was thrown.
- reversion += "(" + retVals + ")";
- addLine(reversion);
- unindent();
- addLine("end");
- addLine("");
- return false;
-}
-
-void Why3Translator::endVisit(FunctionDefinition const&)
-{
- m_localVariables.clear();
-}
-
-bool Why3Translator::visit(Block const& _node)
-{
- addSourceFromDocStrings(_node.annotation());
- add("begin");
- indent();
- for (size_t i = 0; i < _node.statements().size(); ++i)
- {
- _node.statements()[i]->accept(*this);
- if (i != _node.statements().size() - 1)
- {
- auto it = m_lines.end() - 1;
- while (it != m_lines.begin() && it->contents.empty())
- --it;
- if (!boost::algorithm::ends_with(it->contents, "begin"))
- it->contents += ";";
- }
- newLine();
- }
- unindent();
- add("end");
- return false;
-}
-
-bool Why3Translator::visit(IfStatement const& _node)
-{
- addSourceFromDocStrings(_node.annotation());
-
- add("if ");
- _node.condition().accept(*this);
- add(" then");
- visitIndentedUnlessBlock(_node.trueStatement());
- if (_node.falseStatement())
- {
- newLine();
- add("else");
- visitIndentedUnlessBlock(*_node.falseStatement());
- }
- return false;
-}
-
-bool Why3Translator::visit(WhileStatement const& _node)
-{
- addSourceFromDocStrings(_node.annotation());
-
- // Why3 does not appear to support do-while loops,
- // so we will simulate them by performing a while
- // loop with the body prepended once.
-
- if (_node.isDoWhile())
- {
- visitIndentedUnlessBlock(_node.body());
- newLine();
- }
-
- add("while ");
- _node.condition().accept(*this);
- newLine();
- add("do");
- visitIndentedUnlessBlock(_node.body());
- add("done");
- return false;
-}
-
-bool Why3Translator::visit(Return const& _node)
-{
- addSourceFromDocStrings(_node.annotation());
-
- if (_node.expression())
- {
- solAssert(!!_node.annotation().functionReturnParameters, "");
- auto const& params = _node.annotation().functionReturnParameters->parameters();
- if (params.size() != 1)
- {
- error(_node, "Directly returning tuples not supported. Rather assign to return variable.");
- return false;
- }
- add("begin _" + params.front()->name() + " := ");
- _node.expression()->accept(*this);
- add("; raise Return end");
- }
- else
- add("raise Return");
- return false;
-}
-
-bool Why3Translator::visit(Throw const& _node)
-{
- addSourceFromDocStrings(_node.annotation());
- add("raise Revert");
- return false;
-}
-
-bool Why3Translator::visit(VariableDeclarationStatement const& _node)
-{
- addSourceFromDocStrings(_node.annotation());
-
- if (_node.declarations().size() != 1)
- {
- error(_node, "Multiple variables not supported.");
- return false;
- }
- if (_node.initialValue())
- {
- add("_" + _node.declarations().front()->name() + " := ");
- _node.initialValue()->accept(*this);
- }
- return false;
-}
-
-bool Why3Translator::visit(ExpressionStatement const& _node)
-{
- addSourceFromDocStrings(_node.annotation());
- return true;
-}
-
-bool Why3Translator::visit(Assignment const& _node)
-{
- if (_node.assignmentOperator() != Token::Assign)
- error(_node, "Compound assignment not supported.");
-
- _node.leftHandSide().accept(*this);
-
- add(m_currentLValueIsRef ? " := " : " <- ");
- _node.rightHandSide().accept(*this);
-
- return false;
-}
-
-bool Why3Translator::visit(TupleExpression const& _node)
-{
- if (_node.components().size() != 1)
- error(_node, "Only tuples with exactly one component supported.");
- add("(");
- return true;
-}
-
-bool Why3Translator::visit(UnaryOperation const& _unaryOperation)
-{
- try
- {
- toFormalType(*_unaryOperation.annotation().type);
- }
- catch (NoFormalType &err)
- {
- string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
- error(_unaryOperation, "Type \"" + (typeNamePtr ? *typeNamePtr : "") + "\" supported in unary operation.");
- }
-
- switch (_unaryOperation.getOperator())
- {
- case Token::Not: // !
- add("(not ");
- break;
- default:
- error(_unaryOperation, "Operator not supported.");
- break;
- }
-
- _unaryOperation.subExpression().accept(*this);
- add(")");
-
- return false;
-}
-
-bool Why3Translator::visit(BinaryOperation const& _binaryOperation)
-{
- Expression const& leftExpression = _binaryOperation.leftExpression();
- Expression const& rightExpression = _binaryOperation.rightExpression();
- solAssert(!!_binaryOperation.annotation().commonType, "");
- Type const& commonType = *_binaryOperation.annotation().commonType;
- Token::Value const c_op = _binaryOperation.getOperator();
-
- if (commonType.category() == Type::Category::RationalNumber)
- {
- auto const& constantNumber = dynamic_cast<RationalNumberType const&>(commonType);
- if (constantNumber.isFractional())
- error(_binaryOperation, "Fractional numbers not supported.");
- else
- add("(of_int " + toString(commonType.literalValue(nullptr)) + ")");
- return false;
- }
- static const map<Token::Value, char const*> optrans({
- {Token::And, " && "},
- {Token::Or, " || "},
- {Token::BitOr, " lor "},
- {Token::BitXor, " lxor "},
- {Token::BitAnd, " land "},
- {Token::Add, " + "},
- {Token::Sub, " - "},
- {Token::Mul, " * "},
- {Token::Div, " / "},
- {Token::Mod, " mod "},
- {Token::Equal, " = "},
- {Token::NotEqual, " <> "},
- {Token::LessThan, " < "},
- {Token::GreaterThan, " > "},
- {Token::LessThanOrEqual, " <= "},
- {Token::GreaterThanOrEqual, " >= "}
- });
- if (!optrans.count(c_op))
- {
- error(_binaryOperation, "Operator not supported.");
- return true;
- }
-
- add("(");
- leftExpression.accept(*this);
- add(optrans.at(c_op));
- rightExpression.accept(*this);
- add(")");
-
- return false;
-}
-
-bool Why3Translator::visit(FunctionCall const& _node)
-{
- if (_node.annotation().isTypeConversion || _node.annotation().isStructConstructorCall)
- {
- error(_node, "Only ordinary function calls supported.");
- return true;
- }
- FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type);
- switch (function.location())
- {
- case FunctionType::Location::AddMod:
- case FunctionType::Location::MulMod:
- {
- //@todo require that third parameter is not zero
- add("(of_int (mod (Int.(");
- add(function.location() == FunctionType::Location::AddMod ? "+" : "*");
- add(") (to_int ");
- _node.arguments().at(0)->accept(*this);
- add(") (to_int ");
- _node.arguments().at(1)->accept(*this);
- add(")) (to_int ");
- _node.arguments().at(2)->accept(*this);
- add(")))");
- return false;
- }
- case FunctionType::Location::Internal:
- {
- if (!_node.names().empty())
- {
- error(_node, "Function calls with named arguments not supported.");
- return true;
- }
-
- //@TODO check type conversions
-
- add("(");
- _node.expression().accept(*this);
- add(" state");
- for (auto const& arg: _node.arguments())
- {
- add(" ");
- arg->accept(*this);
- }
- add(")");
- return false;
- }
- case FunctionType::Location::Bare:
- {
- if (!_node.arguments().empty())
- {
- error(_node, "Function calls with named arguments not supported.");
- return true;
- }
-
- add("(");
- indent();
- add("let amount = 0 in ");
- _node.expression().accept(*this);
- addLine("if amount <= this.balance then");
- indent();
- addLine("let balance_precall = this.balance in");
- addLine("begin");
- indent();
- addLine("this.balance <- this.balance - amount;");
- addLine("if not (external_call this) then begin this.balance = balance_precall; false end else true");
- unindent();
- addLine("end");
- unindent();
- addLine("else false");
-
- unindent();
- add(")");
- return false;
- }
- case FunctionType::Location::SetValue:
- {
- add("let amount = ");
- solAssert(_node.arguments().size() == 1, "");
- _node.arguments()[0]->accept(*this);
- add(" in ");
- return false;
- }
- default:
- error(_node, "Only internal function calls supported.");
- return true;
- }
-}
-
-bool Why3Translator::visit(MemberAccess const& _node)
-{
- if (
- _node.expression().annotation().type->category() == Type::Category::Array &&
- _node.memberName() == "length" &&
- !_node.annotation().lValueRequested
- )
- {
- add("(of_int ");
- _node.expression().accept(*this);
- add(".length");
- add(")");
- }
- else if (
- _node.memberName() == "call" &&
- *_node.expression().annotation().type == IntegerType(160, IntegerType::Modifier::Address)
- )
- {
- // Do nothing, do not even visit the address because this will be an external call
- //@TODO ensure that the expression itself does not have side-effects
- return false;
- }
- else
- error(_node, "Member access: Only call and array length supported.");
- return false;
-}
-
-bool Why3Translator::visit(IndexAccess const& _node)
-{
- auto baseType = dynamic_cast<ArrayType const*>(_node.baseExpression().annotation().type.get());
- if (!baseType)
- {
- error(_node, "Index access only supported for arrays.");
- return true;
- }
- if (_node.annotation().lValueRequested)
- {
- error(_node, "Assignment to array elements not supported.");
- return true;
- }
- add("(");
- _node.baseExpression().accept(*this);
- add("[to_int ");
- _node.indexExpression()->accept(*this);
- add("]");
- add(")");
-
- return false;
-}
-
-bool Why3Translator::visit(Identifier const& _identifier)
-{
- Declaration const* declaration = _identifier.annotation().referencedDeclaration;
- if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
- add("_" + functionDef->name());
- else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
- {
- bool isStateVar = isStateVariable(variable);
- bool lvalue = _identifier.annotation().lValueRequested;
- if (isStateVar)
- add("this.storage.");
- else if (!lvalue)
- add("!(");
- add("_" + variable->name());
- if (!isStateVar && !lvalue)
- add(")");
- m_currentLValueIsRef = !isStateVar;
- }
- else
- error(_identifier, "Not supported.");
- return false;
-}
-
-bool Why3Translator::visit(Literal const& _literal)
-{
- TypePointer type = _literal.annotation().type;
- switch (type->category())
- {
- case Type::Category::Bool:
- if (type->literalValue(&_literal) == 0)
- add("false");
- else
- add("true");
- break;
- case Type::Category::RationalNumber:
- {
- auto const& constantNumber = dynamic_cast<RationalNumberType const&>(*type);
- if (constantNumber.isFractional())
- error(_literal, "Fractional numbers not supported.");
- else
- add("(of_int " + toString(type->literalValue(&_literal)) + ")");
- break;
- }
- default:
- error(_literal, "Not supported.");
- }
- return false;
-}
-
-bool Why3Translator::visit(PragmaDirective const& _pragma)
-{
- if (_pragma.tokens().empty())
- error(_pragma, "Not supported");
- else if (_pragma.literals().empty())
- error(_pragma, "Not supported");
- else if (_pragma.literals()[0] != "solidity")
- error(_pragma, "Not supported");
- else if (_pragma.tokens()[0] != Token::Identifier)
- error(_pragma, "A literal 'solidity' is not an identifier. Strange");
-
- return false;
-}
-
-bool Why3Translator::isStateVariable(VariableDeclaration const* _var) const
-{
- return contains(m_currentContract.stateVariables, _var);
-}
-
-bool Why3Translator::isStateVariable(string const& _name) const
-{
- for (auto const& var: m_currentContract.stateVariables)
- if (var->name() == _name)
- return true;
- return false;
-}
-
-bool Why3Translator::isLocalVariable(VariableDeclaration const* _var) const
-{
- for (auto const& var: m_localVariables)
- if (var.second == _var)
- return true;
- return false;
-}
-
-bool Why3Translator::isLocalVariable(string const& _name) const
-{
- return m_localVariables.count(_name);
-}
-
-string Why3Translator::copyOfStorage() const
-{
- if (m_currentContract.stateVariables.empty())
- return "()";
- string ret = "{";
- bool first = true;
- for (auto const* variable: m_currentContract.stateVariables)
- {
- if (first)
- first = false;
- else
- ret += "; ";
- ret += "_" + variable->name() + " = this.storage._" + variable->name();
- }
- return ret + "}";
-}
-
-void Why3Translator::visitIndentedUnlessBlock(Statement const& _statement)
-{
- bool isBlock = !!dynamic_cast<Block const*>(&_statement);
- if (isBlock)
- newLine();
- else
- indent();
- _statement.accept(*this);
- if (isBlock)
- newLine();
- else
- unindent();
-}
-
-void Why3Translator::addSourceFromDocStrings(DocumentedAnnotation const& _annotation)
-{
- auto why3Range = _annotation.docTags.equal_range("why3");
- for (auto i = why3Range.first; i != why3Range.second; ++i)
- addLine(transformVariableReferences(i->second.content));
-}
-
-string Why3Translator::transformVariableReferences(string const& _annotation)
-{
- string ret;
- auto pos = _annotation.begin();
- while (true)
- {
- auto hash = find(pos, _annotation.end(), '#');
- ret.append(pos, hash);
- if (hash == _annotation.end())
- break;
-
- auto hashEnd = find_if(hash + 1, _annotation.end(), [](char _c)
- {
- return
- (_c != '_' && _c != '$') &&
- !('a' <= _c && _c <= 'z') &&
- !('A' <= _c && _c <= 'Z') &&
- !('0' <= _c && _c <= '9');
- });
- string varName(hash + 1, hashEnd);
- if (isLocalVariable(varName))
- ret += "(!_" + varName + ")";
- else if (isStateVariable(varName))
- ret += "(this.storage._" + varName + ")";
- //@todo return variables
- else
- ret.append(hash, hashEnd);
-
- pos = hashEnd;
- }
- return ret;
-}
-
-void Why3Translator::appendPreface()
-{
- m_lines.push_back(Line{R"(
-module UInt256
- use import mach.int.Unsigned
- type uint256
- constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
- clone export mach.int.Unsigned with
- type t = uint256,
- constant max = max_uint256
-end
-
-module Address
- use import mach.int.Unsigned
- type address
- constant max_address: int = 0xffffffffffffffffffffffffffffffffffffffff (* 160 bit = 40 f's *)
- clone export mach.int.Unsigned with
- type t = address,
- constant max = max_address
-end
- )", 0});
-}
diff --git a/libsolidity/formal/Why3Translator.h b/libsolidity/formal/Why3Translator.h
deleted file mode 100644
index 03f3bf9c..00000000
--- a/libsolidity/formal/Why3Translator.h
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- 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 Christian <c@ethdev.com>
- * @date 2015
- * Component that translates Solidity code into the why3 programming language.
- */
-
-#pragma once
-
-#include <libsolidity/ast/ASTVisitor.h>
-#include <libsolidity/interface/Exceptions.h>
-#include <string>
-
-namespace dev
-{
-namespace solidity
-{
-
-class SourceUnit;
-
-/**
- * Simple translator from Solidity to Why3.
- *
- * @todo detect side effects in sub-expressions and limit them to one per statement. #1043
- * @todo `x = y = z`
- * @todo implicit and explicit type conversion
- */
-class Why3Translator: private ASTConstVisitor
-{
-public:
- Why3Translator(ErrorList& _errors): m_lines(std::vector<Line>{{std::string(), 0}}), m_errors(_errors) {}
-
- /// Appends formalisation of the given source unit to the output.
- /// @returns false on error.
- bool process(SourceUnit const& _source);
-
- std::string translation() const;
-
-private:
- /// Creates an error and adds it to errors list.
- void error(ASTNode const& _node, std::string const& _description);
- /// Reports a fatal error and throws.
- void fatalError(ASTNode const& _node, std::string const& _description);
-
- /// Appends imports and constants use throughout the formal code.
- void appendPreface();
-
- /// @returns a string representation of the corresponding formal type or throws NoFormalType exception.
- std::string toFormalType(Type const& _type) const;
- using errinfo_noFormalTypeFrom = boost::error_info<struct tag_noFormalTypeFrom, std::string /* name of the type that cannot be translated */ >;
- struct NoFormalType: virtual Exception {};
-
- void indent() { newLine(); m_lines.back().indentation++; }
- void unindent();
- void addLine(std::string const& _line);
- void add(std::string const& _str);
- void newLine();
- void appendSemicolon();
-
- virtual bool visit(SourceUnit const&) override { return true; }
- virtual bool visit(ContractDefinition const& _contract) override;
- virtual void endVisit(ContractDefinition const& _contract) override;
- virtual bool visit(FunctionDefinition const& _function) override;
- virtual void endVisit(FunctionDefinition const& _function) override;
- virtual bool visit(Block const&) override;
- virtual bool visit(IfStatement const& _node) override;
- virtual bool visit(WhileStatement const& _node) override;
- virtual bool visit(Return const& _node) override;
- virtual bool visit(Throw const& _node) override;
- virtual bool visit(VariableDeclarationStatement const& _node) override;
- virtual bool visit(ExpressionStatement const&) override;
- virtual bool visit(Assignment const& _node) override;
- virtual bool visit(TupleExpression const& _node) override;
- virtual void endVisit(TupleExpression const&) override { add(")"); }
- virtual bool visit(UnaryOperation const& _node) override;
- virtual bool visit(BinaryOperation const& _node) override;
- virtual bool visit(FunctionCall const& _node) override;
- virtual bool visit(MemberAccess const& _node) override;
- virtual bool visit(IndexAccess const& _node) override;
- virtual bool visit(Identifier const& _node) override;
- virtual bool visit(Literal const& _node) override;
- virtual bool visit(PragmaDirective const& _node) override;
-
- virtual bool visitNode(ASTNode const& _node) override
- {
- error(_node, "Code not supported for formal verification.");
- return false;
- }
-
- bool isStateVariable(VariableDeclaration const* _var) const;
- bool isStateVariable(std::string const& _name) const;
- bool isLocalVariable(VariableDeclaration const* _var) const;
- bool isLocalVariable(std::string const& _name) const;
-
- /// @returns a string representing an expression that is a copy of this.storage
- std::string copyOfStorage() const;
-
- /// Visits the given statement and indents it unless it is a block
- /// (which does its own indentation).
- void visitIndentedUnlessBlock(Statement const& _statement);
-
- void addSourceFromDocStrings(DocumentedAnnotation const& _annotation);
- /// Transforms substring like `#varName` and `#stateVarName` to code that evaluates to their value.
- std::string transformVariableReferences(std::string const& _annotation);
-
- /// True if we have already seen a contract. For now, only a single contract
- /// is supported.
- bool m_seenContract = false;
- bool m_errorOccured = false;
-
- /// Metadata relating to the current contract
- struct ContractMetadata
- {
- ContractDefinition const* contract = nullptr;
- std::vector<VariableDeclaration const*> stateVariables;
-
- void reset() { contract = nullptr; stateVariables.clear(); }
- };
-
- ContractMetadata m_currentContract;
- bool m_currentLValueIsRef = false;
- std::map<std::string, VariableDeclaration const*> m_localVariables;
-
- struct Line
- {
- std::string contents;
- unsigned indentation;
- };
- std::vector<Line> m_lines;
- ErrorList& m_errors;
-};
-
-}
-}
diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp
new file mode 100644
index 00000000..7e00ffae
--- /dev/null
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -0,0 +1,496 @@
+/*
+ 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/>.
+*/
+/**
+ * Analyzer part of inline assembly.
+ */
+
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScopeFiller.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+namespace {
+
+set<string> const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", "u128", "s128", "u256", "s256"};
+
+}
+
+bool AsmAnalyzer::analyze(Block const& _block)
+{
+ if (!(ScopeFiller(m_info, m_errorReporter))(_block))
+ return false;
+
+ return (*this)(_block);
+}
+
+bool AsmAnalyzer::operator()(Label const& _label)
+{
+ solAssert(!m_julia, "");
+ m_info.stackHeightInfo[&_label] = m_stackHeight;
+ return true;
+}
+
+bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
+{
+ solAssert(!m_julia, "");
+ auto const& info = instructionInfo(_instruction.instruction);
+ m_stackHeight += info.ret - info.args;
+ m_info.stackHeightInfo[&_instruction] = m_stackHeight;
+ warnOnInstructions(_instruction.instruction, _instruction.location);
+ return true;
+}
+
+bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
+{
+ expectValidType(_literal.type, _literal.location);
+ ++m_stackHeight;
+ if (_literal.kind == assembly::LiteralKind::String && _literal.value.size() > 32)
+ {
+ m_errorReporter.typeError(
+ _literal.location,
+ "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)"
+ );
+ return false;
+ }
+ m_info.stackHeightInfo[&_literal] = m_stackHeight;
+ return true;
+}
+
+bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
+{
+ size_t numErrorsBefore = m_errorReporter.errors().size();
+ bool success = true;
+ if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
+ [&](Scope::Variable const& _var)
+ {
+ if (!m_activeVariables.count(&_var))
+ {
+ m_errorReporter.declarationError(
+ _identifier.location,
+ "Variable " + _identifier.name + " used before it was declared."
+ );
+ success = false;
+ }
+ ++m_stackHeight;
+ },
+ [&](Scope::Label const&)
+ {
+ ++m_stackHeight;
+ },
+ [&](Scope::Function const&)
+ {
+ m_errorReporter.typeError(
+ _identifier.location,
+ "Function " + _identifier.name + " used without being called."
+ );
+ success = false;
+ }
+ )))
+ {
+ }
+ else
+ {
+ size_t stackSize(-1);
+ if (m_resolver)
+ {
+ bool insideFunction = m_currentScope->insideFunction();
+ stackSize = m_resolver(_identifier, julia::IdentifierContext::RValue, insideFunction);
+ }
+ if (stackSize == size_t(-1))
+ {
+ // Only add an error message if the callback did not do it.
+ if (numErrorsBefore == m_errorReporter.errors().size())
+ m_errorReporter.declarationError(_identifier.location, "Identifier not found.");
+ success = false;
+ }
+ m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
+ }
+ m_info.stackHeightInfo[&_identifier] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
+{
+ solAssert(!m_julia, "");
+ bool success = true;
+ for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
+ if (!expectExpression(arg))
+ success = false;
+ // Parser already checks that the number of arguments is correct.
+ solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), "");
+ if (!(*this)(_instr.instruction))
+ success = false;
+ m_info.stackHeightInfo[&_instr] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
+{
+ solAssert(!m_julia, "");
+ bool success = checkAssignment(_assignment.variableName, size_t(-1));
+ m_info.stackHeightInfo[&_assignment] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
+{
+ int const stackHeight = m_stackHeight;
+ bool success = boost::apply_visitor(*this, *_assignment.value);
+ solAssert(m_stackHeight >= stackHeight, "Negative value size.");
+ if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight))
+ success = false;
+ m_info.stackHeightInfo[&_assignment] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
+{
+ int const expectedItems = _varDecl.variables.size();
+ int const stackHeight = m_stackHeight;
+ bool success = boost::apply_visitor(*this, *_varDecl.value);
+ if ((m_stackHeight - stackHeight) != expectedItems)
+ {
+ m_errorReporter.declarationError(_varDecl.location, "Variable count mismatch.");
+ return false;
+ }
+
+ for (auto const& variable: _varDecl.variables)
+ {
+ expectValidType(variable.type, variable.location);
+ m_activeVariables.insert(&boost::get<Scope::Variable>(m_currentScope->identifiers.at(variable.name)));
+ }
+ m_info.stackHeightInfo[&_varDecl] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
+{
+ Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
+ solAssert(virtualBlock, "");
+ Scope& varScope = scope(virtualBlock);
+ for (auto const& var: _funDef.arguments + _funDef.returns)
+ {
+ expectValidType(var.type, var.location);
+ m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name)));
+ }
+
+ int const stackHeight = m_stackHeight;
+ m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
+
+ bool success = (*this)(_funDef.body);
+
+ m_stackHeight = stackHeight;
+ m_info.stackHeightInfo[&_funDef] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
+{
+ bool success = true;
+ size_t arguments = 0;
+ size_t returns = 0;
+ if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor(
+ [&](Scope::Variable const&)
+ {
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Attempt to call variable instead of function."
+ );
+ success = false;
+ },
+ [&](Scope::Label const&)
+ {
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Attempt to call label instead of function."
+ );
+ success = false;
+ },
+ [&](Scope::Function const& _fun)
+ {
+ /// TODO: compare types too
+ arguments = _fun.arguments.size();
+ returns = _fun.returns.size();
+ }
+ )))
+ {
+ m_errorReporter.declarationError(_funCall.functionName.location, "Function not found.");
+ success = false;
+ }
+ if (success)
+ {
+ if (_funCall.arguments.size() != arguments)
+ {
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Expected " + boost::lexical_cast<string>(arguments) + " arguments but got " +
+ boost::lexical_cast<string>(_funCall.arguments.size()) + "."
+ );
+ success = false;
+ }
+ }
+ for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
+ if (!expectExpression(arg))
+ success = false;
+ m_stackHeight += int(returns) - int(arguments);
+ m_info.stackHeightInfo[&_funCall] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(Switch const& _switch)
+{
+ bool success = true;
+
+ if (!expectExpression(*_switch.expression))
+ success = false;
+
+ set<tuple<LiteralKind, string>> cases;
+ for (auto const& _case: _switch.cases)
+ {
+ if (_case.value)
+ {
+ int const initialStackHeight = m_stackHeight;
+ // We cannot use "expectExpression" here because *_case.value is not a
+ // Statement and would be converted to a Statement otherwise.
+ if (!(*this)(*_case.value))
+ success = false;
+ expectDeposit(1, initialStackHeight, _case.value->location);
+ m_stackHeight--;
+
+ /// Note: the parser ensures there is only one default case
+ auto val = make_tuple(_case.value->kind, _case.value->value);
+ if (!cases.insert(val).second)
+ {
+ m_errorReporter.declarationError(
+ _case.location,
+ "Duplicate case defined"
+ );
+ success = false;
+ }
+ }
+
+ if (!(*this)(_case.body))
+ success = false;
+ }
+
+ m_stackHeight--;
+ m_info.stackHeightInfo[&_switch] = m_stackHeight;
+
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::ForLoop const& _for)
+{
+ Scope* originalScope = m_currentScope;
+
+ bool success = true;
+ if (!(*this)(_for.pre))
+ success = false;
+ // The block was closed already, but we re-open it again and stuff the
+ // condition, the body and the post part inside.
+ m_stackHeight += scope(&_for.pre).numberOfVariables();
+ m_currentScope = &scope(&_for.pre);
+
+ if (!expectExpression(*_for.condition))
+ success = false;
+ m_stackHeight--;
+ if (!(*this)(_for.body))
+ success = false;
+ if (!(*this)(_for.post))
+ success = false;
+
+ m_stackHeight -= scope(&_for.pre).numberOfVariables();
+ m_info.stackHeightInfo[&_for] = m_stackHeight;
+ m_currentScope = originalScope;
+
+ return success;
+}
+
+bool AsmAnalyzer::operator()(Block const& _block)
+{
+ bool success = true;
+ auto previousScope = m_currentScope;
+ m_currentScope = &scope(&_block);
+
+ int const initialStackHeight = m_stackHeight;
+
+ for (auto const& s: _block.statements)
+ if (!boost::apply_visitor(*this, s))
+ success = false;
+
+ m_stackHeight -= scope(&_block).numberOfVariables();
+
+ int const stackDiff = m_stackHeight - initialStackHeight;
+ if (stackDiff != 0)
+ {
+ m_errorReporter.declarationError(
+ _block.location,
+ "Unbalanced stack at the end of a block: " +
+ (
+ stackDiff > 0 ?
+ to_string(stackDiff) + string(" surplus item(s).") :
+ to_string(-stackDiff) + string(" missing item(s).")
+ )
+ );
+ success = false;
+ }
+
+ m_info.stackHeightInfo[&_block] = m_stackHeight;
+ m_currentScope = previousScope;
+ return success;
+}
+
+bool AsmAnalyzer::expectExpression(Statement const& _statement)
+{
+ bool success = true;
+ int const initialHeight = m_stackHeight;
+ if (!boost::apply_visitor(*this, _statement))
+ success = false;
+ if (!expectDeposit(1, initialHeight, locationOf(_statement)))
+ success = false;
+ return success;
+}
+
+bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
+{
+ if (m_stackHeight - _oldHeight != _deposit)
+ {
+ m_errorReporter.typeError(
+ _location,
+ "Expected expression to return one item to the stack, but did return " +
+ boost::lexical_cast<string>(m_stackHeight - _oldHeight) +
+ " items."
+ );
+ return false;
+ }
+ return true;
+}
+
+bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
+{
+ bool success = true;
+ size_t numErrorsBefore = m_errorReporter.errors().size();
+ size_t variableSize(-1);
+ if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
+ {
+ // Check that it is a variable
+ if (var->type() != typeid(Scope::Variable))
+ {
+ m_errorReporter.typeError(_variable.location, "Assignment requires variable.");
+ success = false;
+ }
+ else if (!m_activeVariables.count(&boost::get<Scope::Variable>(*var)))
+ {
+ m_errorReporter.declarationError(
+ _variable.location,
+ "Variable " + _variable.name + " used before it was declared."
+ );
+ success = false;
+ }
+ variableSize = 1;
+ }
+ else if (m_resolver)
+ {
+ bool insideFunction = m_currentScope->insideFunction();
+ variableSize = m_resolver(_variable, julia::IdentifierContext::LValue, insideFunction);
+ }
+ if (variableSize == size_t(-1))
+ {
+ // Only add message if the callback did not.
+ if (numErrorsBefore == m_errorReporter.errors().size())
+ m_errorReporter.declarationError(_variable.location, "Variable not found or variable not lvalue.");
+ success = false;
+ }
+ if (_valueSize == size_t(-1))
+ _valueSize = variableSize == size_t(-1) ? 1 : variableSize;
+
+ m_stackHeight -= _valueSize;
+
+ if (_valueSize != variableSize && variableSize != size_t(-1))
+ {
+ m_errorReporter.typeError(
+ _variable.location,
+ "Variable size (" +
+ to_string(variableSize) +
+ ") and value size (" +
+ to_string(_valueSize) +
+ ") do not match."
+ );
+ success = false;
+ }
+ return success;
+}
+
+Scope& AsmAnalyzer::scope(Block const* _block)
+{
+ solAssert(m_info.scopes.count(_block) == 1, "Scope requested but not present.");
+ auto scopePtr = m_info.scopes.at(_block);
+ solAssert(scopePtr, "Scope requested but not present.");
+ return *scopePtr;
+}
+void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location)
+{
+ if (!m_julia)
+ return;
+
+ if (!builtinTypes.count(type))
+ m_errorReporter.typeError(
+ _location,
+ "\"" + type + "\" is not a valid type (user defined types are not yet supported)."
+ );
+}
+
+void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location)
+{
+ static set<solidity::Instruction> futureInstructions{
+ solidity::Instruction::CREATE2,
+ solidity::Instruction::RETURNDATACOPY,
+ solidity::Instruction::RETURNDATASIZE,
+ solidity::Instruction::STATICCALL
+ };
+ if (futureInstructions.count(_instr))
+ m_errorReporter.warning(
+ _location,
+ "The \"" +
+ boost::to_lower_copy(instructionInfo(_instr).name)
+ + "\" instruction is only available after " +
+ "the Metropolis hard fork. Before that it acts as an invalid instruction."
+ );
+
+ if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI)
+ m_errorReporter.warning(
+ _location,
+ "Jump instructions are low-level EVM features that can lead to "
+ "incorrect stack access. Because of that they are discouraged. "
+ "Please consider using \"switch\" or \"for\" statements instead."
+ );
+}
diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h
new file mode 100644
index 00000000..9b2a8f9c
--- /dev/null
+++ b/libsolidity/inlineasm/AsmAnalysis.h
@@ -0,0 +1,103 @@
+/*
+ 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/>.
+*/
+/**
+ * Analysis part of inline assembly.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+#include <libjulia/backends/evm/AbstractAssembly.h>
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+class ErrorReporter;
+namespace assembly
+{
+
+struct AsmAnalysisInfo;
+
+/**
+ * Performs the full analysis stage, calls the ScopeFiller internally, then resolves
+ * references and performs other checks.
+ * If all these checks pass, code generation should not throw errors.
+ */
+class AsmAnalyzer: public boost::static_visitor<bool>
+{
+public:
+ explicit AsmAnalyzer(
+ AsmAnalysisInfo& _analysisInfo,
+ ErrorReporter& _errorReporter,
+ bool _julia = false,
+ julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver()
+ ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {}
+
+ bool analyze(assembly::Block const& _block);
+
+ bool operator()(assembly::Instruction const&);
+ bool operator()(assembly::Literal const& _literal);
+ bool operator()(assembly::Identifier const&);
+ bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
+ bool operator()(assembly::Label const& _label);
+ bool operator()(assembly::StackAssignment const&);
+ bool operator()(assembly::Assignment const& _assignment);
+ bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
+ bool operator()(assembly::FunctionDefinition const& _functionDefinition);
+ bool operator()(assembly::FunctionCall const& _functionCall);
+ bool operator()(assembly::Switch const& _switch);
+ bool operator()(assembly::ForLoop const& _forLoop);
+ bool operator()(assembly::Block const& _block);
+
+private:
+ /// Visits the statement and expects it to deposit one item onto the stack.
+ bool expectExpression(Statement const& _statement);
+ bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
+
+ /// Verifies that a variable to be assigned to exists and has the same size
+ /// as the value, @a _valueSize, unless that is equal to -1.
+ bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1));
+
+ Scope& scope(assembly::Block const* _block);
+ void expectValidType(std::string const& type, SourceLocation const& _location);
+ void warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location);
+
+ int m_stackHeight = 0;
+ julia::ExternalIdentifierAccess::Resolver m_resolver;
+ Scope* m_currentScope = nullptr;
+ /// Variables that are active at the current point in assembly (as opposed to
+ /// "part of the scope but not yet declared")
+ std::set<Scope::Variable const*> m_activeVariables;
+ AsmAnalysisInfo& m_info;
+ ErrorReporter& m_errorReporter;
+ bool m_julia = false;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.cpp b/libsolidity/inlineasm/AsmAnalysisInfo.cpp
new file mode 100644
index 00000000..22318b12
--- /dev/null
+++ b/libsolidity/inlineasm/AsmAnalysisInfo.cpp
@@ -0,0 +1,26 @@
+/*
+ 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/>.
+*/
+/**
+ * Information generated during analyzer part of inline assembly.
+ */
+
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+#include <ostream>
+
diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libsolidity/inlineasm/AsmAnalysisInfo.h
new file mode 100644
index 00000000..bd3b28c4
--- /dev/null
+++ b/libsolidity/inlineasm/AsmAnalysisInfo.h
@@ -0,0 +1,52 @@
+/*
+ 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/>.
+*/
+/**
+ * Information generated during analyzer part of inline assembly.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct Scope;
+
+struct AsmAnalysisInfo
+{
+ using StackHeightInfo = std::map<void const*, int>;
+ using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
+ Scopes scopes;
+ StackHeightInfo stackHeightInfo;
+ /// Virtual blocks which will be used for scopes for function arguments and return values.
+ std::map<FunctionDefinition const*, std::shared_ptr<assembly::Block const>> virtualBlocks;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp
index 43c3b27a..74743737 100644
--- a/libsolidity/inlineasm/AsmCodeGen.cpp
+++ b/libsolidity/inlineasm/AsmCodeGen.cpp
@@ -21,301 +21,129 @@
*/
#include <libsolidity/inlineasm/AsmCodeGen.h>
-#include <memory>
-#include <functional>
-#include <libdevcore/CommonIO.h>
+
+#include <libsolidity/inlineasm/AsmParser.h>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
#include <libevmasm/Assembly.h>
#include <libevmasm/SourceLocation.h>
#include <libevmasm/Instruction.h>
-#include <libsolidity/inlineasm/AsmParser.h>
-#include <libsolidity/inlineasm/AsmData.h>
-using namespace std;
-using namespace dev;
-using namespace dev::solidity;
-using namespace dev::solidity::assembly;
+#include <libjulia/backends/evm/AbstractAssembly.h>
+#include <libjulia/backends/evm/EVMCodeTransform.h>
-struct GeneratorState
-{
- GeneratorState(ErrorList& _errors, eth::Assembly& _assembly):
- errors(_errors), assembly(_assembly) {}
+#include <libdevcore/CommonIO.h>
- void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation())
- {
- auto err = make_shared<Error>(_type);
- if (!_location.isEmpty())
- *err << errinfo_sourceLocation(_location);
- *err << errinfo_comment(_description);
- errors.push_back(err);
- }
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/range/adaptor/map.hpp>
+#include <boost/range/algorithm/count_if.hpp>
- int const* findVariable(string const& _variableName) const
- {
- auto localVariable = find_if(
- variables.rbegin(),
- variables.rend(),
- [&](pair<string, int> const& _var) { return _var.first == _variableName; }
- );
- return localVariable != variables.rend() ? &localVariable->second : nullptr;
- }
- eth::AssemblyItem const* findLabel(string const& _labelName) const
- {
- auto label = find_if(
- labels.begin(),
- labels.end(),
- [&](pair<string, eth::AssemblyItem> const& _label) { return _label.first == _labelName; }
- );
- return label != labels.end() ? &label->second : nullptr;
- }
+#include <memory>
+#include <functional>
- map<string, eth::AssemblyItem> labels;
- vector<pair<string, int>> variables; ///< name plus stack height
- ErrorList& errors;
- eth::Assembly& assembly;
-};
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
-/**
- * Scans the inline assembly data for labels, creates tags in the assembly and searches for
- * duplicate labels.
- */
-class LabelOrganizer: public boost::static_visitor<>
+class EthAssemblyAdapter: public julia::AbstractAssembly
{
public:
- LabelOrganizer(GeneratorState& _state): m_state(_state)
+ EthAssemblyAdapter(eth::Assembly& _assembly):
+ m_assembly(_assembly)
{
- // Make the Solidity ErrorTag available to inline assembly
- m_state.labels.insert(make_pair("invalidJumpLabel", m_state.assembly.errorTag()));
}
-
- template <class T>
- void operator()(T const& /*_item*/) { }
- void operator()(Label const& _item)
+ virtual void setSourceLocation(SourceLocation const& _location) override
{
- if (m_state.labels.count(_item.name))
- //@TODO secondary location
- m_state.addError(
- Error::Type::DeclarationError,
- "Label " + _item.name + " declared twice.",
- _item.location
- );
- m_state.labels.insert(make_pair(_item.name, m_state.assembly.newTag()));
+ m_assembly.setSourceLocation(_location);
}
- void operator()(assembly::Block const& _block)
+ virtual int stackHeight() const override { return m_assembly.deposit(); }
+ virtual void appendInstruction(solidity::Instruction _instruction) override
{
- std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
+ m_assembly.append(_instruction);
}
-
-private:
- GeneratorState& m_state;
-};
-
-class CodeTransform: public boost::static_visitor<>
-{
-public:
- /// Create the code transformer which appends assembly to _state.assembly when called
- /// with parsed assembly data.
- /// @param _identifierAccess used to resolve identifiers external to the inline assembly
- explicit CodeTransform(
- GeneratorState& _state,
- assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess()
- ):
- m_state(_state)
+ virtual void appendConstant(u256 const& _constant) override
{
- if (_identifierAccess)
- m_identifierAccess = _identifierAccess;
- else
- m_identifierAccess = [](assembly::Identifier const&, eth::Assembly&, CodeGenerator::IdentifierContext) { return false; };
+ m_assembly.append(_constant);
}
-
- void operator()(assembly::Instruction const& _instruction)
+ /// Append a label.
+ virtual void appendLabel(LabelID _labelId) override
{
- m_state.assembly.setSourceLocation(_instruction.location);
- m_state.assembly.append(_instruction.instruction);
+ m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId));
}
- void operator()(assembly::Literal const& _literal)
+ /// Append a label reference.
+ virtual void appendLabelReference(LabelID _labelId) override
{
- m_state.assembly.setSourceLocation(_literal.location);
- if (_literal.isNumber)
- m_state.assembly.append(u256(_literal.value));
- else if (_literal.value.size() > 32)
- {
- m_state.addError(
- Error::Type::TypeError,
- "String literal too long (" + boost::lexical_cast<string>(_literal.value.size()) + " > 32)"
- );
- m_state.assembly.append(u256(0));
- }
- else
- m_state.assembly.append(_literal.value);
+ m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId));
}
- void operator()(assembly::Identifier const& _identifier)
+ virtual size_t newLabelId() override
{
- m_state.assembly.setSourceLocation(_identifier.location);
- // First search local variables, then labels, then externals.
- if (int const* stackHeight = m_state.findVariable(_identifier.name))
- {
- int heightDiff = m_state.assembly.deposit() - *stackHeight;
- if (heightDiff <= 0 || heightDiff > 16)
- {
- m_state.addError(
- Error::Type::TypeError,
- "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
- _identifier.location
- );
- m_state.assembly.append(u256(0));
- }
- else
- m_state.assembly.append(solidity::dupInstruction(heightDiff));
- return;
- }
- else if (eth::AssemblyItem const* label = m_state.findLabel(_identifier.name))
- m_state.assembly.append(label->pushTag());
- else if (!m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue))
- {
- m_state.addError(
- Error::Type::DeclarationError,
- "Identifier not found or not unique",
- _identifier.location
- );
- m_state.assembly.append(u256(0));
- }
+ return assemblyTagToIdentifier(m_assembly.newTag());
}
- void operator()(FunctionalInstruction const& _instr)
+ virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override
{
- for (auto it = _instr.arguments.rbegin(); it != _instr.arguments.rend(); ++it)
- {
- int height = m_state.assembly.deposit();
- boost::apply_visitor(*this, *it);
- expectDeposit(1, height, locationOf(*it));
- }
- (*this)(_instr.instruction);
+ m_assembly.appendLibraryAddress(_linkerSymbol);
}
- void operator()(Label const& _label)
+ virtual void appendJump(int _stackDiffAfter) override
{
- m_state.assembly.setSourceLocation(_label.location);
- m_state.assembly.append(m_state.labels.at(_label.name));
+ appendInstruction(solidity::Instruction::JUMP);
+ m_assembly.adjustDeposit(_stackDiffAfter);
}
- void operator()(assembly::Assignment const& _assignment)
+ virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override
{
- m_state.assembly.setSourceLocation(_assignment.location);
- generateAssignment(_assignment.variableName, _assignment.location);
+ appendLabelReference(_labelId);
+ appendJump(_stackDiffAfter);
}
- void operator()(FunctionalAssignment const& _assignment)
+ virtual void appendJumpToIf(LabelID _labelId) override
{
- int height = m_state.assembly.deposit();
- boost::apply_visitor(*this, *_assignment.value);
- expectDeposit(1, height, locationOf(*_assignment.value));
- m_state.assembly.setSourceLocation(_assignment.location);
- generateAssignment(_assignment.variableName, _assignment.location);
+ appendLabelReference(_labelId);
+ appendInstruction(solidity::Instruction::JUMPI);
}
- void operator()(assembly::VariableDeclaration const& _varDecl)
+ virtual void appendBeginsub(LabelID, int) override
{
- int height = m_state.assembly.deposit();
- boost::apply_visitor(*this, *_varDecl.value);
- expectDeposit(1, height, locationOf(*_varDecl.value));
- m_state.variables.push_back(make_pair(_varDecl.name, height));
+ // TODO we could emulate that, though
+ solAssert(false, "BEGINSUB not implemented for EVM 1.0");
}
- void operator()(assembly::Block const& _block)
+ /// Call a subroutine.
+ virtual void appendJumpsub(LabelID, int, int) override
{
- size_t numVariables = m_state.variables.size();
- int deposit = m_state.assembly.deposit();
- std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
-
- // pop variables
- while (m_state.variables.size() > numVariables)
- {
- m_state.assembly.append(solidity::Instruction::POP);
- m_state.variables.pop_back();
- }
-
- m_state.assembly.setSourceLocation(_block.location);
-
- deposit = m_state.assembly.deposit() - deposit;
-
- // issue warnings for stack height discrepancies
- if (deposit < 0)
- {
- m_state.addError(
- Error::Type::Warning,
- "Inline assembly block is not balanced. It takes " + toString(-deposit) + " item(s) from the stack.",
- _block.location
- );
- }
- else if (deposit > 0)
- {
- m_state.addError(
- Error::Type::Warning,
- "Inline assembly block is not balanced. It leaves " + toString(deposit) + " item(s) on the stack.",
- _block.location
- );
- }
+ // TODO we could emulate that, though
+ solAssert(false, "JUMPSUB not implemented for EVM 1.0");
+ }
+ /// Return from a subroutine.
+ virtual void appendReturnsub(int, int) override
+ {
+ // TODO we could emulate that, though
+ solAssert(false, "RETURNSUB not implemented for EVM 1.0");
}
-private:
- void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location)
+ virtual void appendAssemblySize() override
{
- if (int const* stackHeight = m_state.findVariable(_variableName.name))
- {
- int heightDiff = m_state.assembly.deposit() - *stackHeight - 1;
- if (heightDiff <= 0 || heightDiff > 16)
- m_state.addError(
- Error::Type::TypeError,
- "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
- _location
- );
- else
- m_state.assembly.append(solidity::swapInstruction(heightDiff));
- m_state.assembly.append(solidity::Instruction::POP);
- return;
- }
- else if (!m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue))
- m_state.addError(
- Error::Type::DeclarationError,
- "Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue."
- );
+ m_assembly.appendProgramSize();
}
- void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
+private:
+ LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
{
- if (m_state.assembly.deposit() != _oldHeight + 1)
- m_state.addError(Error::Type::TypeError,
- "Expected instruction(s) to deposit " +
- boost::lexical_cast<string>(_deposit) +
- " item(s) to the stack, but did deposit " +
- boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) +
- " item(s).",
- _location
- );
+ u256 id = _tag.data();
+ solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large.");
+ return LabelID(id);
}
- GeneratorState& m_state;
- assembly::CodeGenerator::IdentifierAccess m_identifierAccess;
+ eth::Assembly& m_assembly;
};
-bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
+void assembly::CodeGenerator::assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ eth::Assembly& _assembly,
+ julia::ExternalIdentifierAccess const& _identifierAccess
+)
{
- size_t initialErrorLen = m_errors.size();
- eth::Assembly assembly;
- GeneratorState state(m_errors, assembly);
- (LabelOrganizer(state))(m_parsedData);
- (CodeTransform(state, _identifierAccess))(m_parsedData);
- return m_errors.size() == initialErrorLen;
+ EthAssemblyAdapter assemblyAdapter(_assembly);
+ julia::CodeTransform(assemblyAdapter, _analysisInfo, false, false, _identifierAccess)(_parsedData);
}
-
-eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
-{
- eth::Assembly assembly;
- GeneratorState state(m_errors, assembly);
- (LabelOrganizer(state))(m_parsedData);
- (CodeTransform(state, _identifierAccess))(m_parsedData);
- return assembly;
-}
-
-void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
-{
- GeneratorState state(m_errors, _assembly);
- (LabelOrganizer(state))(m_parsedData);
- (CodeTransform(state, _identifierAccess))(m_parsedData);
-}
-
diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h
index bd71812e..2a36a590 100644
--- a/libsolidity/inlineasm/AsmCodeGen.h
+++ b/libsolidity/inlineasm/AsmCodeGen.h
@@ -22,8 +22,9 @@
#pragma once
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+
#include <functional>
-#include <libsolidity/interface/Exceptions.h>
namespace dev
{
@@ -36,31 +37,17 @@ namespace solidity
namespace assembly
{
struct Block;
-struct Identifier;
class CodeGenerator
{
public:
- enum class IdentifierContext { LValue, RValue };
- /// Function type that is called for external identifiers. Such a function should search for
- /// the identifier and append appropriate assembly items to the assembly. If in lvalue context,
- /// the value to assign is assumed to be on the stack and an assignment is to be performed.
- /// If in rvalue context, the function is assumed to append instructions to
- /// push the value of the identifier onto the stack. On error, the function should return false.
- using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>;
- CodeGenerator(Block const& _parsedData, ErrorList& _errors):
- m_parsedData(_parsedData), m_errors(_errors) {}
- /// Performs type checks and @returns false on error.
- /// Actually runs the full code generation but discards the result.
- bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess());
- /// Performs code generation and @returns the result.
- eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess());
/// Performs code generation and appends generated to to _assembly.
- void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess());
-
-private:
- Block const& m_parsedData;
- ErrorList& m_errors;
+ static void assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ eth::Assembly& _assembly,
+ julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess()
+ );
};
}
diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h
index d622ff54..db5840bc 100644
--- a/libsolidity/inlineasm/AsmData.h
+++ b/libsolidity/inlineasm/AsmData.h
@@ -22,10 +22,13 @@
#pragma once
-#include <boost/variant.hpp>
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
#include <libevmasm/Instruction.h>
#include <libevmasm/SourceLocation.h>
+#include <boost/variant.hpp>
+
namespace dev
{
namespace solidity
@@ -33,32 +36,39 @@ namespace solidity
namespace assembly
{
-/// What follows are the AST nodes for assembly.
+using Type = std::string;
+
+struct TypedName { SourceLocation location; std::string name; Type type; };
+using TypedNameList = std::vector<TypedName>;
/// Direct EVM instruction (except PUSHi and JUMPDEST)
struct Instruction { SourceLocation location; solidity::Instruction instruction; };
/// Literal number or string (up to 32 bytes)
-struct Literal { SourceLocation location; bool isNumber; std::string value; };
+enum class LiteralKind { Number, Boolean, String };
+struct Literal { SourceLocation location; LiteralKind kind; std::string value; Type type; };
/// External / internal identifier or label reference
struct Identifier { SourceLocation location; std::string name; };
-struct FunctionalInstruction;
/// Jump label ("name:")
struct Label { SourceLocation location; std::string name; };
-/// Assignemnt (":= x", moves stack top into x, potentially multiple slots)
-struct Assignment { SourceLocation location; Identifier variableName; };
-struct FunctionalAssignment;
-struct VariableDeclaration;
-struct Block;
-using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionalInstruction, VariableDeclaration, Block>;
-/// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand
+/// Assignment from stack (":= x", moves stack top into x, potentially multiple slots)
+struct StackAssignment { SourceLocation location; Identifier variableName; };
+/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand
/// side and requires x to occupy exactly one stack slot.
-struct FunctionalAssignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; };
-/// Functional instruction, e.g. "mul(mload(20), add(2, x))"
+struct Assignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; };
+/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
-/// Block-scope variable declaration ("let x := mload(20)"), non-hoisted
-struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr<Statement> value; };
+struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
+/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted
+struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Statement> value; };
/// Block that creates a scope (frees declared stack variables)
struct Block { SourceLocation location; std::vector<Statement> statements; };
+/// Function definition ("function f(a, b) -> (d, e) { ... }")
+struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; };
+/// Switch case or default case
+struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; };
+/// Switch statement
+struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; };
+struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; };
struct LocationExtractor: boost::static_visitor<SourceLocation>
{
diff --git a/libsolidity/interface/Utils.h b/libsolidity/inlineasm/AsmDataForward.h
index 0027759c..4ead7ff5 100644
--- a/libsolidity/interface/Utils.h
+++ b/libsolidity/inlineasm/AsmDataForward.h
@@ -16,30 +16,37 @@
*/
/**
* @author Christian <c@ethdev.com>
- * @date 2014
- * Solidity Utilities.
+ * @date 2016
+ * Forward declaration of classes for inline assembly / JULIA AST
*/
#pragma once
-#include <libdevcore/Assertions.h>
-#include <libsolidity/interface/Exceptions.h>
+#include <boost/variant.hpp>
namespace dev
{
namespace solidity
{
-struct InternalCompilerError;
-struct UnimplementedFeatureError;
-}
-}
-
-/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
-#define solAssert(CONDITION, DESCRIPTION) \
- assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION)
+namespace assembly
+{
-#define solUnimplementedAssert(CONDITION, DESCRIPTION) \
- assertThrow(CONDITION, ::dev::solidity::UnimplementedFeatureError, DESCRIPTION)
+struct Instruction;
+struct Literal;
+struct Label;
+struct StackAssignment;
+struct Identifier;
+struct Assignment;
+struct VariableDeclaration;
+struct FunctionalInstruction;
+struct FunctionDefinition;
+struct FunctionCall;
+struct Switch;
+struct ForLoop;
+struct Block;
+
+using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>;
-#define solUnimplemented(DESCRIPTION) \
- solUnimplementedAssert(false, DESCRIPTION)
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index ef3da255..d282a30d 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -21,9 +21,10 @@
*/
#include <libsolidity/inlineasm/AsmParser.h>
+#include <libsolidity/parsing/Scanner.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include <ctype.h>
#include <algorithm>
-#include <libsolidity/parsing/Scanner.h>
using namespace std;
using namespace dev;
@@ -39,7 +40,7 @@ shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scann
}
catch (FatalError const&)
{
- if (m_errors.empty())
+ if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
}
return nullptr;
@@ -49,34 +50,60 @@ assembly::Block Parser::parseBlock()
{
assembly::Block block = createWithLocation<Block>();
expectToken(Token::LBrace);
- while (m_scanner->currentToken() != Token::RBrace)
+ while (currentToken() != Token::RBrace)
block.statements.emplace_back(parseStatement());
block.location.end = endPosition();
- m_scanner->next();
+ advance();
return block;
}
assembly::Statement Parser::parseStatement()
{
- switch (m_scanner->currentToken())
+ switch (currentToken())
{
case Token::Let:
return parseVariableDeclaration();
+ case Token::Function:
+ return parseFunctionDefinition();
case Token::LBrace:
return parseBlock();
- case Token::Assign:
+ case Token::Switch:
{
- assembly::Assignment assignment = createWithLocation<assembly::Assignment>();
+ assembly::Switch _switch = createWithLocation<assembly::Switch>();
m_scanner->next();
+ _switch.expression = make_shared<Statement>(parseExpression());
+ if (_switch.expression->type() == typeid(assembly::Instruction))
+ fatalParserError("Instructions are not supported as expressions for switch.");
+ while (m_scanner->currentToken() == Token::Case)
+ _switch.cases.emplace_back(parseCase());
+ if (m_scanner->currentToken() == Token::Default)
+ _switch.cases.emplace_back(parseCase());
+ if (m_scanner->currentToken() == Token::Default)
+ fatalParserError("Only one default case allowed.");
+ else if (m_scanner->currentToken() == Token::Case)
+ fatalParserError("Case not allowed after default case.");
+ if (_switch.cases.size() == 0)
+ fatalParserError("Switch statement without any cases.");
+ _switch.location.end = _switch.cases.back().body.location.end;
+ return _switch;
+ }
+ case Token::For:
+ return parseForLoop();
+ case Token::Assign:
+ {
+ if (m_julia)
+ break;
+ assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>();
+ advance();
expectToken(Token::Colon);
assignment.variableName.location = location();
- assignment.variableName.name = m_scanner->currentLiteral();
+ assignment.variableName.name = currentLiteral();
+ if (!m_julia && instructions().count(assignment.variableName.name))
+ fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition();
expectToken(Token::Identifier);
return assignment;
}
- case Token::Return: // opcode
- case Token::Byte: // opcode
default:
break;
}
@@ -84,53 +111,105 @@ assembly::Statement Parser::parseStatement()
// Simple instruction (might turn into functional),
// literal,
// identifier (might turn into label or functional assignment)
- Statement statement(parseElementaryOperation());
- switch (m_scanner->currentToken())
+ Statement statement(parseElementaryOperation(false));
+ switch (currentToken())
{
case Token::LParen:
- return parseFunctionalInstruction(std::move(statement));
+ return parseCall(std::move(statement));
case Token::Colon:
{
if (statement.type() != typeid(assembly::Identifier))
fatalParserError("Label name / variable name must precede \":\".");
assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
- m_scanner->next();
+ advance();
// identifier:=: should be parsed as identifier: =: (i.e. a label),
// while identifier:= (being followed by a non-colon) as identifier := (assignment).
- if (m_scanner->currentToken() == Token::Assign && m_scanner->peekNextToken() != Token::Colon)
+ if (currentToken() == Token::Assign && peekNextToken() != Token::Colon)
{
- // functional assignment
- FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location);
- m_scanner->next();
- funAss.variableName = identifier;
- funAss.value.reset(new Statement(parseExpression()));
- funAss.location.end = locationOf(*funAss.value).end;
- return funAss;
+ assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location);
+ if (!m_julia && instructions().count(identifier.name))
+ fatalParserError("Cannot use instruction names for identifier names.");
+ advance();
+ assignment.variableName = identifier;
+ assignment.value.reset(new Statement(parseExpression()));
+ assignment.location.end = locationOf(*assignment.value).end;
+ return assignment;
}
else
{
// label
+ if (m_julia)
+ fatalParserError("Labels are not supported.");
Label label = createWithLocation<Label>(identifier.location);
label.name = identifier.name;
return label;
}
}
default:
+ if (m_julia)
+ fatalParserError("Call or assignment expected.");
break;
}
return statement;
}
+assembly::Case Parser::parseCase()
+{
+ assembly::Case _case = createWithLocation<assembly::Case>();
+ if (m_scanner->currentToken() == Token::Default)
+ m_scanner->next();
+ else if (m_scanner->currentToken() == Token::Case)
+ {
+ m_scanner->next();
+ assembly::Statement statement = parseElementaryOperation();
+ if (statement.type() != typeid(assembly::Literal))
+ fatalParserError("Literal expected.");
+ _case.value = make_shared<Literal>(std::move(boost::get<assembly::Literal>(statement)));
+ }
+ else
+ fatalParserError("Case or default case expected.");
+ _case.body = parseBlock();
+ _case.location.end = _case.body.location.end;
+ return _case;
+}
+
+assembly::ForLoop Parser::parseForLoop()
+{
+ ForLoop forLoop = createWithLocation<ForLoop>();
+ expectToken(Token::For);
+ forLoop.pre = parseBlock();
+ forLoop.condition = make_shared<Statement>(parseExpression());
+ if (forLoop.condition->type() == typeid(assembly::Instruction))
+ fatalParserError("Instructions are not supported as conditions for the for statement.");
+ forLoop.post = parseBlock();
+ forLoop.body = parseBlock();
+ forLoop.location.end = forLoop.body.location.end;
+ return forLoop;
+}
+
assembly::Statement Parser::parseExpression()
{
Statement operation = parseElementaryOperation(true);
- if (m_scanner->currentToken() == Token::LParen)
- return parseFunctionalInstruction(std::move(operation));
+ if (operation.type() == typeid(Instruction))
+ {
+ Instruction const& instr = boost::get<Instruction>(operation);
+ int args = instructionInfo(instr.instruction).args;
+ if (args > 0 && currentToken() != Token::LParen)
+ fatalParserError(string(
+ "Expected token \"(\" (\"" +
+ instructionNames().at(instr.instruction) +
+ "\" expects " +
+ boost::lexical_cast<string>(args) +
+ " arguments)"
+ ));
+ }
+ if (currentToken() == Token::LParen)
+ return parseCall(std::move(operation));
else
return operation;
}
-assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+std::map<string, dev::solidity::Instruction> const& Parser::instructions()
{
// Allowed instructions, lowercase names.
static map<string, dev::solidity::Instruction> s_instructions;
@@ -148,12 +227,32 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
s_instructions[name] = instruction.second;
}
- // add alias for selfdestruct
- s_instructions["selfdestruct"] = solidity::Instruction::SUICIDE;
+ // add alias for suicide
+ s_instructions["suicide"] = solidity::Instruction::SELFDESTRUCT;
+ // add alis for sha3
+ s_instructions["sha3"] = solidity::Instruction::KECCAK256;
+ }
+ return s_instructions;
+}
+
+std::map<dev::solidity::Instruction, string> const& Parser::instructionNames()
+{
+ static map<dev::solidity::Instruction, string> s_instructionNames;
+ if (s_instructionNames.empty())
+ {
+ for (auto const& instr: instructions())
+ s_instructionNames[instr.second] = instr.first;
+ // set the ambiguous instructions to a clear default
+ s_instructionNames[solidity::Instruction::SELFDESTRUCT] = "selfdestruct";
+ s_instructionNames[solidity::Instruction::KECCAK256] = "keccak256";
}
+ return s_instructionNames;
+}
+assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+{
Statement ret;
- switch (m_scanner->currentToken())
+ switch (currentToken())
{
case Token::Identifier:
case Token::Return:
@@ -161,44 +260,78 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
case Token::Address:
{
string literal;
- if (m_scanner->currentToken() == Token::Return)
+ if (currentToken() == Token::Return)
literal = "return";
- else if (m_scanner->currentToken() == Token::Byte)
+ else if (currentToken() == Token::Byte)
literal = "byte";
- else if (m_scanner->currentToken() == Token::Address)
+ else if (currentToken() == Token::Address)
literal = "address";
else
- literal = m_scanner->currentLiteral();
+ literal = currentLiteral();
// first search the set of instructions.
- if (s_instructions.count(literal))
+ if (!m_julia && instructions().count(literal))
{
- dev::solidity::Instruction const& instr = s_instructions[literal];
+ dev::solidity::Instruction const& instr = instructions().at(literal);
if (_onlySinglePusher)
{
InstructionInfo info = dev::solidity::instructionInfo(instr);
if (info.ret != 1)
- fatalParserError("Instruction " + info.name + " not allowed in this context.");
+ fatalParserError("Instruction \"" + literal + "\" not allowed in this context.");
}
ret = Instruction{location(), instr};
}
else
ret = Identifier{location(), literal};
+ advance();
break;
}
case Token::StringLiteral:
case Token::Number:
+ case Token::TrueLiteral:
+ case Token::FalseLiteral:
{
- ret = Literal{
+ LiteralKind kind = LiteralKind::Number;
+ switch (currentToken())
+ {
+ case Token::StringLiteral:
+ kind = LiteralKind::String;
+ break;
+ case Token::Number:
+ kind = LiteralKind::Number;
+ break;
+ case Token::TrueLiteral:
+ case Token::FalseLiteral:
+ kind = LiteralKind::Boolean;
+ break;
+ default:
+ break;
+ }
+
+ Literal literal{
location(),
- m_scanner->currentToken() == Token::Number,
- m_scanner->currentLiteral()
+ kind,
+ currentLiteral(),
+ ""
};
+ advance();
+ if (m_julia)
+ {
+ expectToken(Token::Colon);
+ literal.location.end = endPosition();
+ literal.type = expectAsmIdentifier();
+ }
+ else if (kind == LiteralKind::Boolean)
+ fatalParserError("True and false are not valid literals.");
+ ret = std::move(literal);
break;
}
default:
- fatalParserError("Expected elementary inline assembly operation.");
+ fatalParserError(
+ m_julia ?
+ "Literal or identifier expected." :
+ "Literal, identifier or instruction expected."
+ );
}
- m_scanner->next();
return ret;
}
@@ -206,8 +339,14 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
{
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let);
- varDecl.name = m_scanner->currentLiteral();
- expectToken(Token::Identifier);
+ while (true)
+ {
+ varDecl.variables.emplace_back(parseTypedName());
+ if (currentToken() == Token::Comma)
+ expectToken(Token::Comma);
+ else
+ break;
+ }
expectToken(Token::Colon);
expectToken(Token::Assign);
varDecl.value.reset(new Statement(parseExpression()));
@@ -215,44 +354,145 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
return varDecl;
}
-FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction)
+assembly::FunctionDefinition Parser::parseFunctionDefinition()
{
- if (_instruction.type() != typeid(Instruction))
- fatalParserError("Assembly instruction required in front of \"(\")");
- FunctionalInstruction ret;
- ret.instruction = std::move(boost::get<Instruction>(_instruction));
- ret.location = ret.instruction.location;
- solidity::Instruction instr = ret.instruction.instruction;
- InstructionInfo instrInfo = instructionInfo(instr);
- if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16)
- fatalParserError("DUPi instructions not allowed for functional notation");
- if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16)
- fatalParserError("SWAPi instructions not allowed for functional notation");
-
+ FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
+ expectToken(Token::Function);
+ funDef.name = expectAsmIdentifier();
expectToken(Token::LParen);
- unsigned args = unsigned(instrInfo.args);
- for (unsigned i = 0; i < args; ++i)
+ while (currentToken() != Token::RParen)
+ {
+ funDef.arguments.emplace_back(parseTypedName());
+ if (currentToken() == Token::RParen)
+ break;
+ expectToken(Token::Comma);
+ }
+ expectToken(Token::RParen);
+ if (currentToken() == Token::Sub)
{
- ret.arguments.emplace_back(parseExpression());
- if (i != args - 1)
+ expectToken(Token::Sub);
+ expectToken(Token::GreaterThan);
+ while (true)
{
- if (m_scanner->currentToken() != Token::Comma)
+ funDef.returns.emplace_back(parseTypedName());
+ if (currentToken() == Token::LBrace)
+ break;
+ expectToken(Token::Comma);
+ }
+ }
+ funDef.body = parseBlock();
+ funDef.location.end = funDef.body.location.end;
+ return funDef;
+}
+
+assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
+{
+ if (_instruction.type() == typeid(Instruction))
+ {
+ solAssert(!m_julia, "Instructions are invalid in JULIA");
+ FunctionalInstruction ret;
+ ret.instruction = std::move(boost::get<Instruction>(_instruction));
+ ret.location = ret.instruction.location;
+ solidity::Instruction instr = ret.instruction.instruction;
+ InstructionInfo instrInfo = instructionInfo(instr);
+ if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16)
+ fatalParserError("DUPi instructions not allowed for functional notation");
+ if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16)
+ fatalParserError("SWAPi instructions not allowed for functional notation");
+ expectToken(Token::LParen);
+ unsigned args = unsigned(instrInfo.args);
+ for (unsigned i = 0; i < args; ++i)
+ {
+ /// check for premature closing parentheses
+ if (currentToken() == Token::RParen)
fatalParserError(string(
- "Expected comma (" +
- instrInfo.name +
- " expects " +
+ "Expected expression (\"" +
+ instructionNames().at(instr) +
+ "\" expects " +
boost::lexical_cast<string>(args) +
" arguments)"
));
- else
- m_scanner->next();
+
+ ret.arguments.emplace_back(parseExpression());
+ if (i != args - 1)
+ {
+ if (currentToken() != Token::Comma)
+ fatalParserError(string(
+ "Expected comma (\"" +
+ instructionNames().at(instr) +
+ "\" expects " +
+ boost::lexical_cast<string>(args) +
+ " arguments)"
+ ));
+ else
+ advance();
+ }
+ }
+ ret.location.end = endPosition();
+ if (currentToken() == Token::Comma)
+ fatalParserError(string(
+ "Expected ')' (\"" +
+ instructionNames().at(instr) +
+ "\" expects " +
+ boost::lexical_cast<string>(args) +
+ " arguments)"
+ ));
+ expectToken(Token::RParen);
+ return ret;
+ }
+ else if (_instruction.type() == typeid(Identifier))
+ {
+ FunctionCall ret;
+ ret.functionName = std::move(boost::get<Identifier>(_instruction));
+ ret.location = ret.functionName.location;
+ expectToken(Token::LParen);
+ while (currentToken() != Token::RParen)
+ {
+ ret.arguments.emplace_back(parseExpression());
+ if (currentToken() == Token::RParen)
+ break;
+ expectToken(Token::Comma);
}
+ ret.location.end = endPosition();
+ expectToken(Token::RParen);
+ return ret;
}
- ret.location.end = endPosition();
- if (m_scanner->currentToken() == Token::Comma)
+ else
fatalParserError(
- string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)")
+ m_julia ?
+ "Function name expected." :
+ "Assembly instruction or function name required in front of \"(\")"
);
- expectToken(Token::RParen);
- return ret;
+
+ return {};
+}
+
+TypedName Parser::parseTypedName()
+{
+ TypedName typedName = createWithLocation<TypedName>();
+ typedName.name = expectAsmIdentifier();
+ if (m_julia)
+ {
+ expectToken(Token::Colon);
+ typedName.location.end = endPosition();
+ typedName.type = expectAsmIdentifier();
+ }
+ return typedName;
+}
+
+string Parser::expectAsmIdentifier()
+{
+ string name = currentLiteral();
+ if (m_julia)
+ {
+ if (currentToken() == Token::Bool)
+ {
+ advance();
+ return name;
+ }
+ }
+ else if (instructions().count(name))
+ fatalParserError("Cannot use instruction names for identifier names.");
+ expectToken(Token::Identifier);
+ return name;
}
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index 8b56ab90..45708afd 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -37,7 +37,7 @@ namespace assembly
class Parser: public ParserBase
{
public:
- Parser(ErrorList& _errors): ParserBase(_errors) {}
+ explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {}
/// Parses an inline assembly block starting with `{` and ending with `}`.
/// @returns an empty shared pointer on error.
@@ -62,11 +62,21 @@ protected:
Block parseBlock();
Statement parseStatement();
+ Case parseCase();
+ ForLoop parseForLoop();
/// Parses a functional expression that has to push exactly one stack element
Statement parseExpression();
+ static std::map<std::string, dev::solidity::Instruction> const& instructions();
+ static std::map<dev::solidity::Instruction, std::string> const& instructionNames();
Statement parseElementaryOperation(bool _onlySinglePusher = false);
VariableDeclaration parseVariableDeclaration();
- FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction);
+ FunctionDefinition parseFunctionDefinition();
+ Statement parseCall(Statement&& _instruction);
+ TypedName parseTypedName();
+ std::string expectAsmIdentifier();
+
+private:
+ bool m_julia = false;
};
}
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
new file mode 100644
index 00000000..062ff453
--- /dev/null
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -0,0 +1,214 @@
+/*
+ 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 Christian <c@ethdev.com>
+ * @date 2017
+ * Converts a parsed assembly into its textual form.
+ */
+
+#include <libsolidity/inlineasm/AsmPrinter.h>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+//@TODO source locations
+
+string AsmPrinter::operator()(assembly::Instruction const& _instruction)
+{
+ solAssert(!m_julia, "");
+ return boost::to_lower_copy(instructionInfo(_instruction.instruction).name);
+}
+
+string AsmPrinter::operator()(assembly::Literal const& _literal)
+{
+ switch (_literal.kind)
+ {
+ case LiteralKind::Number:
+ return _literal.value + appendTypeName(_literal.type);
+ case LiteralKind::Boolean:
+ return ((_literal.value == "true") ? "true" : "false") + appendTypeName(_literal.type);
+ case LiteralKind::String:
+ break;
+ }
+
+ string out;
+ for (char c: _literal.value)
+ if (c == '\\')
+ out += "\\\\";
+ else if (c == '"')
+ out += "\\\"";
+ else if (c == '\b')
+ out += "\\b";
+ else if (c == '\f')
+ out += "\\f";
+ else if (c == '\n')
+ out += "\\n";
+ else if (c == '\r')
+ out += "\\r";
+ else if (c == '\t')
+ out += "\\t";
+ else if (c == '\v')
+ out += "\\v";
+ else if (!isprint(c, locale::classic()))
+ {
+ ostringstream o;
+ o << std::hex << setfill('0') << setw(2) << (unsigned)(unsigned char)(c);
+ out += "\\x" + o.str();
+ }
+ else
+ out += c;
+ return "\"" + out + "\"" + appendTypeName(_literal.type);
+}
+
+string AsmPrinter::operator()(assembly::Identifier const& _identifier)
+{
+ return _identifier.name;
+}
+
+string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functionalInstruction)
+{
+ solAssert(!m_julia, "");
+ return
+ (*this)(_functionalInstruction.instruction) +
+ "(" +
+ boost::algorithm::join(
+ _functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
+ ", " ) +
+ ")";
+}
+
+string AsmPrinter::operator()(assembly::Label const& _label)
+{
+ solAssert(!m_julia, "");
+ return _label.name + ":";
+}
+
+string AsmPrinter::operator()(assembly::StackAssignment const& _assignment)
+{
+ solAssert(!m_julia, "");
+ return "=: " + (*this)(_assignment.variableName);
+}
+
+string AsmPrinter::operator()(assembly::Assignment const& _assignment)
+{
+ return (*this)(_assignment.variableName) + " := " + boost::apply_visitor(*this, *_assignment.value);
+}
+
+string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration)
+{
+ string out = "let ";
+ out += boost::algorithm::join(
+ _variableDeclaration.variables | boost::adaptors::transformed(
+ [this](TypedName variable) { return variable.name + appendTypeName(variable.type); }
+ ),
+ ", "
+ );
+ out += " := ";
+ out += boost::apply_visitor(*this, *_variableDeclaration.value);
+ return out;
+}
+
+string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefinition)
+{
+ string out = "function " + _functionDefinition.name + "(";
+ out += boost::algorithm::join(
+ _functionDefinition.arguments | boost::adaptors::transformed(
+ [this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
+ ),
+ ", "
+ );
+ out += ")";
+ if (!_functionDefinition.returns.empty())
+ {
+ out += " -> ";
+ out += boost::algorithm::join(
+ _functionDefinition.returns | boost::adaptors::transformed(
+ [this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
+ ),
+ ", "
+ );
+ }
+
+ return out + "\n" + (*this)(_functionDefinition.body);
+}
+
+string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
+{
+ return
+ (*this)(_functionCall.functionName) + "(" +
+ boost::algorithm::join(
+ _functionCall.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
+ ", " ) +
+ ")";
+}
+
+string AsmPrinter::operator()(Switch const& _switch)
+{
+ string out = "switch " + boost::apply_visitor(*this, *_switch.expression);
+ for (auto const& _case: _switch.cases)
+ {
+ if (!_case.value)
+ out += "\ndefault ";
+ else
+ out += "\ncase " + (*this)(*_case.value) + " ";
+ out += (*this)(_case.body);
+ }
+ return out;
+}
+
+string AsmPrinter::operator()(assembly::ForLoop const& _forLoop)
+{
+ string out = "for ";
+ out += (*this)(_forLoop.pre);
+ out += "\n";
+ out += boost::apply_visitor(*this, *_forLoop.condition);
+ out += "\n";
+ out += (*this)(_forLoop.post);
+ out += "\n";
+ out += (*this)(_forLoop.body);
+ return out;
+}
+
+string AsmPrinter::operator()(Block const& _block)
+{
+ if (_block.statements.empty())
+ return "{\n}";
+ string body = boost::algorithm::join(
+ _block.statements | boost::adaptors::transformed(boost::apply_visitor(*this)),
+ "\n"
+ );
+ boost::replace_all(body, "\n", "\n ");
+ return "{\n " + body + "\n}";
+}
+
+string AsmPrinter::appendTypeName(std::string const& _type)
+{
+ if (m_julia)
+ return ":" + _type;
+ return "";
+}
diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h
new file mode 100644
index 00000000..f57dddc8
--- /dev/null
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -0,0 +1,63 @@
+/*
+ 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 Christian <c@ethdev.com>
+ * @date 2017
+ * Converts a parsed assembly into its textual form.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+class AsmPrinter: public boost::static_visitor<std::string>
+{
+public:
+ explicit AsmPrinter(bool _julia = false): m_julia(_julia) {}
+
+ std::string operator()(assembly::Instruction const& _instruction);
+ std::string operator()(assembly::Literal const& _literal);
+ std::string operator()(assembly::Identifier const& _identifier);
+ std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction);
+ std::string operator()(assembly::Label const& _label);
+ std::string operator()(assembly::StackAssignment const& _assignment);
+ std::string operator()(assembly::Assignment const& _assignment);
+ std::string operator()(assembly::VariableDeclaration const& _variableDeclaration);
+ std::string operator()(assembly::FunctionDefinition const& _functionDefinition);
+ std::string operator()(assembly::FunctionCall const& _functionCall);
+ std::string operator()(assembly::Switch const& _switch);
+ std::string operator()(assembly::ForLoop const& _forLoop);
+ std::string operator()(assembly::Block const& _block);
+
+private:
+ std::string appendTypeName(std::string const& _type);
+
+ bool m_julia = false;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp
new file mode 100644
index 00000000..315d5953
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScope.cpp
@@ -0,0 +1,98 @@
+/*
+ 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/>.
+*/
+/**
+ * Scopes for identifiers.
+ */
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+using namespace std;
+using namespace dev::solidity::assembly;
+
+
+bool Scope::registerLabel(string const& _name)
+{
+ if (exists(_name))
+ return false;
+ identifiers[_name] = Label();
+ return true;
+}
+
+bool Scope::registerVariable(string const& _name, JuliaType const& _type)
+{
+ if (exists(_name))
+ return false;
+ Variable variable;
+ variable.type = _type;
+ identifiers[_name] = variable;
+ return true;
+}
+
+bool Scope::registerFunction(string const& _name, std::vector<JuliaType> const& _arguments, std::vector<JuliaType> const& _returns)
+{
+ if (exists(_name))
+ return false;
+ identifiers[_name] = Function{_arguments, _returns};
+ return true;
+}
+
+Scope::Identifier* Scope::lookup(string const& _name)
+{
+ bool crossedFunctionBoundary = false;
+ for (Scope* s = this; s; s = s->superScope)
+ {
+ auto id = s->identifiers.find(_name);
+ if (id != s->identifiers.end())
+ {
+ if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable))
+ return nullptr;
+ else
+ return &id->second;
+ }
+
+ if (s->functionScope)
+ crossedFunctionBoundary = true;
+ }
+ return nullptr;
+}
+
+bool Scope::exists(string const& _name)
+{
+ if (identifiers.count(_name))
+ return true;
+ else if (superScope)
+ return superScope->exists(_name);
+ else
+ return false;
+}
+
+size_t Scope::numberOfVariables() const
+{
+ size_t count = 0;
+ for (auto const& identifier: identifiers)
+ if (identifier.second.type() == typeid(Scope::Variable))
+ count++;
+ return count;
+}
+
+bool Scope::insideFunction() const
+{
+ for (Scope const* s = this; s; s = s->superScope)
+ if (s->functionScope)
+ return true;
+ return false;
+}
diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h
new file mode 100644
index 00000000..cc240565
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScope.h
@@ -0,0 +1,126 @@
+/*
+ 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/>.
+*/
+/**
+ * Scopes for identifiers.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/variant.hpp>
+#include <boost/optional.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+template <class...>
+struct GenericVisitor{};
+
+template <class Visitable, class... Others>
+struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
+{
+ using GenericVisitor<Others...>::operator ();
+ explicit GenericVisitor(
+ std::function<void(Visitable&)> _visitor,
+ std::function<void(Others&)>... _otherVisitors
+ ):
+ GenericVisitor<Others...>(_otherVisitors...),
+ m_visitor(_visitor)
+ {}
+
+ void operator()(Visitable& _v) const { m_visitor(_v); }
+
+ std::function<void(Visitable&)> m_visitor;
+};
+template <>
+struct GenericVisitor<>: public boost::static_visitor<> {
+ void operator()() const {}
+};
+
+
+struct Scope
+{
+ using JuliaType = std::string;
+ using LabelID = size_t;
+
+ struct Variable { JuliaType type; };
+ struct Label { };
+ struct Function
+ {
+ std::vector<JuliaType> arguments;
+ std::vector<JuliaType> returns;
+ };
+
+ using Identifier = boost::variant<Variable, Label, Function>;
+ using Visitor = GenericVisitor<Variable const, Label const, Function const>;
+ using NonconstVisitor = GenericVisitor<Variable, Label, Function>;
+
+ bool registerVariable(std::string const& _name, JuliaType const& _type);
+ bool registerLabel(std::string const& _name);
+ bool registerFunction(
+ std::string const& _name,
+ std::vector<JuliaType> const& _arguments,
+ std::vector<JuliaType> const& _returns
+ );
+
+ /// Looks up the identifier in this or super scopes and returns a valid pointer if found
+ /// or a nullptr if not found. Variable lookups up across function boundaries will fail, as
+ /// will any lookups across assembly boundaries.
+ /// The pointer will be invalidated if the scope is modified.
+ /// @param _crossedFunction if true, we already crossed a function boundary during recursive lookup
+ Identifier* lookup(std::string const& _name);
+ /// Looks up the identifier in this and super scopes (will not find variables across function
+ /// boundaries and generally stops at assembly boundaries) and calls the visitor, returns
+ /// false if not found.
+ template <class V>
+ bool lookup(std::string const& _name, V const& _visitor)
+ {
+ if (Identifier* id = lookup(_name))
+ {
+ boost::apply_visitor(_visitor, *id);
+ return true;
+ }
+ else
+ return false;
+ }
+ /// @returns true if the name exists in this scope or in super scopes (also searches
+ /// across function and assembly boundaries).
+ bool exists(std::string const& _name);
+
+ /// @returns the number of variables directly registered inside the scope.
+ size_t numberOfVariables() const;
+ /// @returns true if this scope is inside a function.
+ bool insideFunction() const;
+
+ Scope* superScope = nullptr;
+ /// If true, variables from the super scope are not visible here (other identifiers are),
+ /// but they are still taken into account to prevent shadowing.
+ bool functionScope = false;
+ std::map<std::string, Identifier> identifiers;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp
new file mode 100644
index 00000000..5b3174b8
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScopeFiller.cpp
@@ -0,0 +1,168 @@
+/*
+ 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/>.
+*/
+/**
+ * Module responsible for registering identifiers inside their scopes.
+ */
+
+#include <libsolidity/inlineasm/AsmScopeFiller.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter):
+ m_info(_info), m_errorReporter(_errorReporter)
+{
+ m_currentScope = &scope(nullptr);
+}
+
+bool ScopeFiller::operator()(Label const& _item)
+{
+ if (!m_currentScope->registerLabel(_item.name))
+ {
+ //@TODO secondary location
+ m_errorReporter.declarationError(
+ _item.location,
+ "Label name " + _item.name + " already taken in this scope."
+ );
+ return false;
+ }
+ return true;
+}
+
+bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
+{
+ for (auto const& variable: _varDecl.variables)
+ if (!registerVariable(variable, _varDecl.location, *m_currentScope))
+ return false;
+ return true;
+}
+
+bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
+{
+ bool success = true;
+ vector<Scope::JuliaType> arguments;
+ for (auto const& _argument: _funDef.arguments)
+ arguments.push_back(_argument.type);
+ vector<Scope::JuliaType> returns;
+ for (auto const& _return: _funDef.returns)
+ returns.push_back(_return.type);
+ if (!m_currentScope->registerFunction(_funDef.name, arguments, returns))
+ {
+ //@TODO secondary location
+ m_errorReporter.declarationError(
+ _funDef.location,
+ "Function name " + _funDef.name + " already taken in this scope."
+ );
+ success = false;
+ }
+
+ auto virtualBlock = m_info.virtualBlocks[&_funDef] = make_shared<Block>();
+ Scope& varScope = scope(virtualBlock.get());
+ varScope.superScope = m_currentScope;
+ m_currentScope = &varScope;
+ varScope.functionScope = true;
+ for (auto const& var: _funDef.arguments + _funDef.returns)
+ if (!registerVariable(var, _funDef.location, varScope))
+ success = false;
+
+ if (!(*this)(_funDef.body))
+ success = false;
+
+ solAssert(m_currentScope == &varScope, "");
+ m_currentScope = m_currentScope->superScope;
+
+ return success;
+}
+
+bool ScopeFiller::operator()(Switch const& _switch)
+{
+ bool success = true;
+ for (auto const& _case: _switch.cases)
+ if (!(*this)(_case.body))
+ success = false;
+ return success;
+}
+
+bool ScopeFiller::operator()(ForLoop const& _forLoop)
+{
+ Scope* originalScope = m_currentScope;
+
+ bool success = true;
+ if (!(*this)(_forLoop.pre))
+ success = false;
+ m_currentScope = &scope(&_forLoop.pre);
+ if (!boost::apply_visitor(*this, *_forLoop.condition))
+ success = false;
+ if (!(*this)(_forLoop.body))
+ success = false;
+ if (!(*this)(_forLoop.post))
+ success = false;
+
+ m_currentScope = originalScope;
+
+ return success;
+}
+
+bool ScopeFiller::operator()(Block const& _block)
+{
+ bool success = true;
+ scope(&_block).superScope = m_currentScope;
+ m_currentScope = &scope(&_block);
+
+ for (auto const& s: _block.statements)
+ if (!boost::apply_visitor(*this, s))
+ success = false;
+
+ m_currentScope = m_currentScope->superScope;
+ return success;
+}
+
+bool ScopeFiller::registerVariable(TypedName const& _name, SourceLocation const& _location, Scope& _scope)
+{
+ if (!_scope.registerVariable(_name.name, _name.type))
+ {
+ //@TODO secondary location
+ m_errorReporter.declarationError(
+ _location,
+ "Variable name " + _name.name + " already taken in this scope."
+ );
+ return false;
+ }
+ return true;
+}
+
+Scope& ScopeFiller::scope(Block const* _block)
+{
+ auto& scope = m_info.scopes[_block];
+ if (!scope)
+ scope = make_shared<Scope>();
+ return *scope;
+}
diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h
new file mode 100644
index 00000000..80c03d2c
--- /dev/null
+++ b/libsolidity/inlineasm/AsmScopeFiller.h
@@ -0,0 +1,82 @@
+/*
+ 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/>.
+*/
+/**
+ * Module responsible for registering identifiers inside their scopes.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace dev
+{
+struct SourceLocation;
+namespace solidity
+{
+class ErrorReporter;
+namespace assembly
+{
+
+struct TypedName;
+struct Scope;
+struct AsmAnalysisInfo;
+
+/**
+ * Fills scopes with identifiers and checks for name clashes.
+ * Does not resolve references.
+ */
+class ScopeFiller: public boost::static_visitor<bool>
+{
+public:
+ ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter);
+
+ bool operator()(assembly::Instruction const&) { return true; }
+ bool operator()(assembly::Literal const&) { return true; }
+ bool operator()(assembly::Identifier const&) { return true; }
+ bool operator()(assembly::FunctionalInstruction const&) { return true; }
+ bool operator()(assembly::Label const& _label);
+ bool operator()(assembly::StackAssignment const&) { return true; }
+ bool operator()(assembly::Assignment const&) { return true; }
+ bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
+ bool operator()(assembly::FunctionDefinition const& _functionDefinition);
+ bool operator()(assembly::FunctionCall const&) { return true; }
+ bool operator()(assembly::Switch const& _switch);
+ bool operator()(assembly::ForLoop const& _forLoop);
+ bool operator()(assembly::Block const& _block);
+
+private:
+ bool registerVariable(
+ TypedName const& _name,
+ SourceLocation const& _location,
+ Scope& _scope
+ );
+
+ Scope& scope(assembly::Block const* _block);
+
+ Scope* m_currentScope = nullptr;
+ AsmAnalysisInfo& m_info;
+ ErrorReporter& m_errorReporter;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp
deleted file mode 100644
index b8e0e857..00000000
--- a/libsolidity/inlineasm/AsmStack.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- 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 Christian <c@ethdev.com>
- * @date 2016
- * Full-stack Solidity inline assember.
- */
-
-#include <libsolidity/inlineasm/AsmStack.h>
-#include <memory>
-#include <libevmasm/Assembly.h>
-#include <libevmasm/SourceLocation.h>
-#include <libsolidity/parsing/Scanner.h>
-#include <libsolidity/inlineasm/AsmParser.h>
-#include <libsolidity/inlineasm/AsmCodeGen.h>
-
-using namespace std;
-using namespace dev;
-using namespace dev::solidity;
-using namespace dev::solidity::assembly;
-
-bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
-{
- m_parserResult = make_shared<Block>();
- Parser parser(m_errors);
- auto result = parser.parse(_scanner);
- if (!result)
- return false;
- *m_parserResult = std::move(*result);
- return true;
-}
-
-eth::Assembly InlineAssemblyStack::assemble()
-{
- CodeGenerator codeGen(*m_parserResult, m_errors);
- return codeGen.assemble();
-}
-
-bool InlineAssemblyStack::parseAndAssemble(
- string const& _input,
- eth::Assembly& _assembly,
- CodeGenerator::IdentifierAccess const& _identifierAccess
-)
-{
- ErrorList errors;
- auto scanner = make_shared<Scanner>(CharStream(_input), "--CODEGEN--");
- auto parserResult = Parser(errors).parse(scanner);
- if (!errors.empty())
- return false;
-
- CodeGenerator(*parserResult, errors).assemble(_assembly, _identifierAccess);
-
- // At this point, the assembly might be messed up, but we should throw an
- // internal compiler error anyway.
- return errors.empty();
-}
-
diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h
deleted file mode 100644
index 1543cb2a..00000000
--- a/libsolidity/inlineasm/AsmStack.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- 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 Christian <c@ethdev.com>
- * @date 2016
- * Full-stack Solidity inline assember.
- */
-
-#pragma once
-
-#include <string>
-#include <functional>
-#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/inlineasm/AsmCodeGen.h>
-
-namespace dev
-{
-namespace eth
-{
-class Assembly;
-}
-namespace solidity
-{
-class Scanner;
-namespace assembly
-{
-struct Block;
-
-class InlineAssemblyStack
-{
-public:
- /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`.
- /// @return false or error.
- bool parse(std::shared_ptr<Scanner> const& _scanner);
- eth::Assembly assemble();
-
- /// Parse and assemble a string in one run - for use in Solidity code generation itself.
- bool parseAndAssemble(
- std::string const& _input,
- eth::Assembly& _assembly,
- CodeGenerator::IdentifierAccess const& _identifierAccess = CodeGenerator::IdentifierAccess()
- );
-
- ErrorList const& errors() const { return m_errors; }
-
-private:
- std::shared_ptr<Block> m_parserResult;
- ErrorList m_errors;
-};
-
-}
-}
-}
diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp
new file mode 100644
index 00000000..12f958fc
--- /dev/null
+++ b/libsolidity/interface/ABI.cpp
@@ -0,0 +1,116 @@
+/*
+ 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/>.
+*/
+/**
+ * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI)
+ */
+
+#include <libsolidity/interface/ABI.h>
+#include <boost/range/irange.hpp>
+#include <libsolidity/ast/AST.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+Json::Value ABI::generate(ContractDefinition const& _contractDef)
+{
+ Json::Value abi(Json::arrayValue);
+
+ for (auto it: _contractDef.interfaceFunctions())
+ {
+ auto externalFunctionType = it.second->interfaceFunctionType();
+ Json::Value method;
+ method["type"] = "function";
+ method["name"] = it.second->declaration().name();
+ method["constant"] = it.second->isConstant();
+ method["payable"] = it.second->isPayable();
+ method["inputs"] = formatTypeList(
+ externalFunctionType->parameterNames(),
+ externalFunctionType->parameterTypes(),
+ _contractDef.isLibrary()
+ );
+ method["outputs"] = formatTypeList(
+ externalFunctionType->returnParameterNames(),
+ externalFunctionType->returnParameterTypes(),
+ _contractDef.isLibrary()
+ );
+ abi.append(method);
+ }
+ if (_contractDef.constructor())
+ {
+ Json::Value method;
+ method["type"] = "constructor";
+ auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
+ solAssert(!!externalFunction, "");
+ method["payable"] = externalFunction->isPayable();
+ method["inputs"] = formatTypeList(
+ externalFunction->parameterNames(),
+ externalFunction->parameterTypes(),
+ _contractDef.isLibrary()
+ );
+ abi.append(method);
+ }
+ if (_contractDef.fallbackFunction())
+ {
+ auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType();
+ solAssert(!!externalFunctionType, "");
+ Json::Value method;
+ method["type"] = "fallback";
+ method["payable"] = externalFunctionType->isPayable();
+ abi.append(method);
+ }
+ for (auto const& it: _contractDef.interfaceEvents())
+ {
+ Json::Value event;
+ event["type"] = "event";
+ event["name"] = it->name();
+ event["anonymous"] = it->isAnonymous();
+ Json::Value params(Json::arrayValue);
+ for (auto const& p: it->parameters())
+ {
+ solAssert(!!p->annotation().type->interfaceType(false), "");
+ Json::Value input;
+ input["name"] = p->name();
+ input["type"] = p->annotation().type->interfaceType(false)->canonicalName(false);
+ input["indexed"] = p->isIndexed();
+ params.append(input);
+ }
+ event["inputs"] = params;
+ abi.append(event);
+ }
+
+ return abi;
+}
+
+Json::Value ABI::formatTypeList(
+ vector<string> const& _names,
+ vector<TypePointer> const& _types,
+ bool _forLibrary
+)
+{
+ Json::Value params(Json::arrayValue);
+ solAssert(_names.size() == _types.size(), "Names and types vector size does not match");
+ for (unsigned i = 0; i < _names.size(); ++i)
+ {
+ solAssert(_types[i], "");
+ Json::Value param;
+ param["name"] = _names[i];
+ param["type"] = _types[i]->canonicalName(_forLibrary);
+ params.append(param);
+ }
+ return params;
+}
diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h
new file mode 100644
index 00000000..95b162a9
--- /dev/null
+++ b/libsolidity/interface/ABI.h
@@ -0,0 +1,56 @@
+/*
+ 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/>.
+*/
+/**
+ * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI)
+ */
+
+#pragma once
+
+#include <string>
+#include <memory>
+#include <json/json.h>
+
+namespace dev
+{
+namespace solidity
+{
+
+// Forward declarations
+class ContractDefinition;
+class Type;
+using TypePointer = std::shared_ptr<Type const>;
+
+class ABI
+{
+public:
+ /// Get the ABI Interface of the contract
+ /// @param _contractDef The contract definition
+ /// @return A JSONrepresentation of the contract's ABI Interface
+ static Json::Value generate(ContractDefinition const& _contractDef);
+private:
+ /// @returns a json value suitable for a list of types in function input or output
+ /// parameters or other places. If @a _forLibrary is true, complex types are referenced
+ /// by name, otherwise they are anonymously expanded.
+ static Json::Value formatTypeList(
+ std::vector<std::string> const& _names,
+ std::vector<TypePointer> const& _types,
+ bool _forLibrary
+ );
+};
+
+}
+}
diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp
new file mode 100644
index 00000000..23524bb3
--- /dev/null
+++ b/libsolidity/interface/AssemblyStack.cpp
@@ -0,0 +1,119 @@
+/*
+ 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/>.
+*/
+/**
+ * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and
+ * eWasm as output.
+ */
+
+
+#include <libsolidity/interface/AssemblyStack.h>
+
+#include <libsolidity/parsing/Scanner.h>
+#include <libsolidity/inlineasm/AsmPrinter.h>
+#include <libsolidity/inlineasm/AsmParser.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+#include <libsolidity/inlineasm/AsmCodeGen.h>
+
+#include <libevmasm/Assembly.h>
+
+#include <libjulia/backends/evm/EVMCodeTransform.h>
+#include <libjulia/backends/evm/EVMAssembly.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+
+Scanner const& AssemblyStack::scanner() const
+{
+ solAssert(m_scanner, "");
+ return *m_scanner;
+}
+
+bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source)
+{
+ m_errors.clear();
+ m_analysisSuccessful = false;
+ m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName);
+ m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner);
+ if (!m_errorReporter.errors().empty())
+ return false;
+ solAssert(m_parserResult, "");
+
+ return analyzeParsed();
+}
+
+bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scanner)
+{
+ m_errors.clear();
+ m_analysisSuccessful = false;
+ if (_scanner)
+ m_scanner = make_shared<Scanner>(*_scanner);
+ m_parserResult = make_shared<assembly::Block>(_block);
+
+ return analyzeParsed();
+}
+
+bool AssemblyStack::analyzeParsed()
+{
+ m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
+ assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA);
+ m_analysisSuccessful = analyzer.analyze(*m_parserResult);
+ return m_analysisSuccessful;
+}
+
+MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
+{
+ solAssert(m_analysisSuccessful, "");
+ solAssert(m_parserResult, "");
+ solAssert(m_analysisInfo, "");
+
+ switch (_machine)
+ {
+ case Machine::EVM:
+ {
+ MachineAssemblyObject object;
+ eth::Assembly assembly;
+ assembly::CodeGenerator::assemble(*m_parserResult, *m_analysisInfo, assembly);
+ object.bytecode = make_shared<eth::LinkerObject>(assembly.assemble());
+ ostringstream tmp;
+ assembly.stream(tmp);
+ object.assembly = tmp.str();
+ return object;
+ }
+ case Machine::EVM15:
+ {
+ MachineAssemblyObject object;
+ julia::EVMAssembly assembly(true);
+ julia::CodeTransform(assembly, *m_analysisInfo, m_language == Language::JULIA, true)(*m_parserResult);
+ object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize());
+ /// TOOD: fill out text representation
+ return object;
+ }
+ case Machine::eWasm:
+ solUnimplemented("eWasm backend is not yet implemented.");
+ }
+ // unreachable
+ return MachineAssemblyObject();
+}
+
+string AssemblyStack::print() const
+{
+ solAssert(m_parserResult, "");
+ return assembly::AsmPrinter(m_language == Language::JULIA)(*m_parserResult);
+}
diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h
new file mode 100644
index 00000000..2ae596ed
--- /dev/null
+++ b/libsolidity/interface/AssemblyStack.h
@@ -0,0 +1,96 @@
+/*
+ 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/>.
+*/
+/**
+ * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and
+ * eWasm as output.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libevmasm/LinkerObject.h>
+
+#include <string>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+class Scanner;
+namespace assembly
+{
+struct AsmAnalysisInfo;
+struct Block;
+}
+
+struct MachineAssemblyObject
+{
+ std::shared_ptr<eth::LinkerObject> bytecode;
+ std::string assembly;
+};
+
+/*
+ * Full assembly stack that can support EVM-assembly and JULIA as input and EVM, EVM1.5 and
+ * eWasm as output.
+ */
+class AssemblyStack
+{
+public:
+ enum class Language { JULIA, Assembly };
+ enum class Machine { EVM, EVM15, eWasm };
+
+ explicit AssemblyStack(Language _language = Language::Assembly):
+ m_language(_language), m_errorReporter(m_errors)
+ {}
+
+ /// @returns the scanner used during parsing
+ Scanner const& scanner() const;
+
+ /// Runs parsing and analysis steps, returns false if input cannot be assembled.
+ /// Multiple calls overwrite the previous state.
+ bool parseAndAnalyze(std::string const& _sourceName, std::string const& _source);
+
+ /// Runs analysis step on the supplied block, returns false if input cannot be assembled.
+ /// Multiple calls overwrite the previous state.
+ bool analyze(assembly::Block const& _block, Scanner const* _scanner = nullptr);
+
+ /// Run the assembly step (should only be called after parseAndAnalyze).
+ MachineAssemblyObject assemble(Machine _machine) const;
+
+ /// @returns the errors generated during parsing, analysis (and potentially assembly).
+ ErrorList const& errors() const { return m_errors; }
+
+ /// Pretty-print the input after having parsed it.
+ std::string print() const;
+
+private:
+ bool analyzeParsed();
+
+ Language m_language = Language::Assembly;
+
+ std::shared_ptr<Scanner> m_scanner;
+
+ bool m_analysisSuccessful = false;
+ std::shared_ptr<assembly::Block> m_parserResult;
+ std::shared_ptr<assembly::AsmAnalysisInfo> m_analysisInfo;
+ ErrorList m_errors;
+ ErrorReporter m_errorReporter;
+};
+
+}
+}
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index b4fd6d87..e2507821 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -21,6 +21,7 @@
* Full-stack compiler that converts a source code string to bytecode.
*/
+
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/Version.h>
@@ -32,10 +33,13 @@
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/analysis/DocStringAnalyser.h>
+#include <libsolidity/analysis/StaticAnalyzer.h>
+#include <libsolidity/analysis/PostTypeChecker.h>
#include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/codegen/Compiler.h>
-#include <libsolidity/interface/InterfaceHandler.h>
-#include <libsolidity/formal/Why3Translator.h>
+#include <libsolidity/interface/ABI.h>
+#include <libsolidity/interface/Natspec.h>
+#include <libsolidity/interface/GasEstimator.h>
#include <libevmasm/Exceptions.h>
@@ -52,9 +56,6 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
-CompilerStack::CompilerStack(ReadFileCallback const& _readFile):
- m_readFile(_readFile), m_parseSuccessful(false) {}
-
void CompilerStack::setRemappings(vector<string> const& _remappings)
{
vector<Remapping> remappings;
@@ -75,20 +76,24 @@ void CompilerStack::setRemappings(vector<string> const& _remappings)
void CompilerStack::reset(bool _keepSources)
{
- m_parseSuccessful = false;
if (_keepSources)
+ {
+ m_stackState = SourcesSet;
for (auto sourcePair: m_sources)
sourcePair.second.reset();
+ }
else
{
+ m_stackState = Empty;
m_sources.clear();
}
m_optimize = false;
m_optimizeRuns = 200;
m_globalContext.reset();
+ m_scopes.clear();
m_sourceOrder.clear();
m_contracts.clear();
- m_errors.clear();
+ m_errorReporter.clear();
}
bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary)
@@ -97,6 +102,7 @@ bool CompilerStack::addSource(string const& _name, string const& _content, bool
reset(true);
m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name);
m_sources[_name].isLibrary = _isLibrary;
+ m_stackState = SourcesSet;
return existed;
}
@@ -109,29 +115,25 @@ void CompilerStack::setSource(string const& _sourceCode)
bool CompilerStack::parse()
{
//reset
- m_errors.clear();
- m_parseSuccessful = false;
+ if(m_stackState != SourcesSet)
+ return false;
+ m_errorReporter.clear();
+ ASTNode::resetID();
if (SemVerVersion{string(VersionString)}.isPrerelease())
- {
- auto err = make_shared<Error>(Error::Type::Warning);
- *err << errinfo_comment("This is a pre-release compiler version, please do not use it in production.");
- m_errors.push_back(err);
- }
+ m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production.");
vector<string> sourcesToParse;
for (auto const& s: m_sources)
sourcesToParse.push_back(s.first);
- map<string, SourceUnit const*> sourceUnitsByName;
for (size_t i = 0; i < sourcesToParse.size(); ++i)
{
string const& path = sourcesToParse[i];
Source& source = m_sources[path];
source.scanner->reset();
- source.ast = Parser(m_errors).parse(source.scanner);
- sourceUnitsByName[path] = source.ast.get();
+ source.ast = Parser(m_errorReporter).parse(source.scanner);
if (!source.ast)
- solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error.");
+ solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error.");
else
{
source.ast->annotation().path = path;
@@ -144,29 +146,41 @@ bool CompilerStack::parse()
}
}
}
- if (!Error::containsOnlyWarnings(m_errors))
- // errors while parsing. sould stop before type checking
+ if (Error::containsOnlyWarnings(m_errorReporter.errors()))
+ {
+ m_stackState = ParsingSuccessful;
+ return true;
+ }
+ else
return false;
+}
+bool CompilerStack::analyze()
+{
+ if (m_stackState != ParsingSuccessful)
+ return false;
resolveImports();
bool noErrors = true;
- SyntaxChecker syntaxChecker(m_errors);
+ SyntaxChecker syntaxChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!syntaxChecker.checkSyntax(*source->ast))
noErrors = false;
- DocStringAnalyser docStringAnalyser(m_errors);
+ DocStringAnalyser docStringAnalyser(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!docStringAnalyser.analyseDocStrings(*source->ast))
noErrors = false;
m_globalContext = make_shared<GlobalContext>();
- NameAndTypeResolver resolver(m_globalContext->declarations(), m_errors);
+ NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!resolver.registerDeclarations(*source->ast))
return false;
+ map<string, SourceUnit const*> sourceUnitsByName;
+ for (auto& source: m_sources)
+ sourceUnitsByName[source.first] = source.second.ast.get();
for (Source const* source: m_sourceOrder)
if (!resolver.performImports(*source->ast, sourceUnitsByName))
return false;
@@ -179,11 +193,15 @@ bool CompilerStack::parse()
if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false;
if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
if (!resolver.resolveNamesAndTypes(*contract)) return false;
- m_contracts[contract->name()].contract = contract;
- }
- if (!checkLibraryNameClashes())
- noErrors = false;
+ // Note that we now reference contracts by their fully qualified names, and
+ // thus contracts can only conflict if declared in the same source file. This
+ // already causes a double-declaration error elsewhere, so we do not report
+ // an error here and instead silently drop any additional contracts we find.
+
+ if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
+ m_contracts[contract->fullyQualifiedName()].contract = contract;
+ }
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
@@ -191,19 +209,47 @@ bool CompilerStack::parse()
{
m_globalContext->setCurrentContract(*contract);
resolver.updateDeclaration(*m_globalContext->currentThis());
- TypeChecker typeChecker(m_errors);
+ TypeChecker typeChecker(m_errorReporter);
if (typeChecker.checkTypeRequirements(*contract))
{
- contract->setDevDocumentation(InterfaceHandler::devDocumentation(*contract));
- contract->setUserDocumentation(InterfaceHandler::userDocumentation(*contract));
+ contract->setDevDocumentation(Natspec::devDocumentation(*contract));
+ contract->setUserDocumentation(Natspec::userDocumentation(*contract));
}
else
noErrors = false;
- m_contracts[contract->name()].contract = contract;
+ // Note that we now reference contracts by their fully qualified names, and
+ // thus contracts can only conflict if declared in the same source file. This
+ // already causes a double-declaration error elsewhere, so we do not report
+ // an error here and instead silently drop any additional contracts we find.
+
+ if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
+ m_contracts[contract->fullyQualifiedName()].contract = contract;
}
- m_parseSuccessful = noErrors;
- return m_parseSuccessful;
+
+ if (noErrors)
+ {
+ PostTypeChecker postTypeChecker(m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!postTypeChecker.check(*source->ast))
+ noErrors = false;
+ }
+
+ if (noErrors)
+ {
+ StaticAnalyzer staticAnalyzer(m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!staticAnalyzer.analyze(*source->ast))
+ noErrors = false;
+ }
+
+ if (noErrors)
+ {
+ m_stackState = AnalysisSuccessful;
+ return true;
+ }
+ else
+ return false;
}
bool CompilerStack::parse(string const& _sourceCode)
@@ -212,9 +258,20 @@ bool CompilerStack::parse(string const& _sourceCode)
return parse();
}
+bool CompilerStack::parseAndAnalyze()
+{
+ return parse() && analyze();
+}
+
+bool CompilerStack::parseAndAnalyze(std::string const& _sourceCode)
+{
+ setSource(_sourceCode);
+ return parseAndAnalyze();
+}
+
vector<string> CompilerStack::contractNames() const
{
- if (!m_parseSuccessful)
+ if (m_stackState < AnalysisSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
vector<string> contractNames;
for (auto const& contract: m_contracts)
@@ -225,8 +282,8 @@ vector<string> CompilerStack::contractNames() const
bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> const& _libraries)
{
- if (!m_parseSuccessful)
- if (!parse())
+ if (m_stackState < AnalysisSuccessful)
+ if (!parseAndAnalyze())
return false;
m_optimize = _optimize;
@@ -239,12 +296,13 @@ bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> co
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
compileContract(*contract, compiledContracts);
this->link();
+ m_stackState = CompilationSuccessful;
return true;
}
bool CompilerStack::compile(string const& _sourceCode, bool _optimize, unsigned _runs)
{
- return parse(_sourceCode) && compile(_optimize, _runs);
+ return parseAndAnalyze(_sourceCode) && compile(_optimize, _runs);
}
void CompilerStack::link()
@@ -257,20 +315,6 @@ void CompilerStack::link()
}
}
-bool CompilerStack::prepareFormalAnalysis(ErrorList* _errors)
-{
- if (!_errors)
- _errors = &m_errors;
- Why3Translator translator(*_errors);
- for (Source const* source: m_sourceOrder)
- if (!translator.process(*source->ast))
- return false;
-
- m_formalTranslation = translator.translation();
-
- return true;
-}
-
eth::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const
{
Contract const& currentContract = contract(_contractName);
@@ -305,6 +349,27 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
return c.runtimeSourceMapping.get();
}
+std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const
+{
+ // Look up the contract (by its fully-qualified name)
+ Contract const& matchContract = m_contracts.at(_contractName);
+ // Check to see if it could collide on name
+ for (auto const& contract: m_contracts)
+ {
+ if (contract.second.contract->name() == matchContract.contract->name() &&
+ contract.second.contract != matchContract.contract)
+ {
+ // If it does, then return its fully-qualified name, made fs-friendly
+ std::string friendlyName = boost::algorithm::replace_all_copy(_contractName, "/", "_");
+ boost::algorithm::replace_all(friendlyName, ":", "_");
+ boost::algorithm::replace_all(friendlyName, ".", "_");
+ return friendlyName;
+ }
+ }
+ // If no collision, return the contract's name
+ return matchContract.contract->name();
+}
+
eth::LinkerObject const& CompilerStack::object(string const& _contractName) const
{
return contract(_contractName).object;
@@ -320,15 +385,6 @@ eth::LinkerObject const& CompilerStack::cloneObject(string const& _contractName)
return contract(_contractName).cloneObject;
}
-dev::h256 CompilerStack::contractCodeHash(string const& _contractName) const
-{
- auto const& obj = runtimeObject(_contractName);
- if (obj.bytecode.empty() || !obj.linkReferences.empty())
- return dev::h256();
- else
- return dev::keccak256(obj.bytecode);
-}
-
Json::Value CompilerStack::streamAssembly(ostream& _outStream, string const& _contractName, StringMap _sourceCodes, bool _inJsonFormat) const
{
Contract const& currentContract = contract(_contractName);
@@ -352,27 +408,39 @@ vector<string> CompilerStack::sourceNames() const
map<string, unsigned> CompilerStack::sourceIndices() const
{
map<string, unsigned> indices;
+ unsigned index = 0;
for (auto const& s: m_sources)
- indices[s.first] = indices.size();
+ indices[s.first] = index++;
return indices;
}
-Json::Value const& CompilerStack::interface(string const& _contractName) const
+Json::Value const& CompilerStack::contractABI(string const& _contractName) const
{
- return metadata(_contractName, DocumentationType::ABIInterface);
+ return contractABI(contract(_contractName));
}
-Json::Value const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const
+Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
{
- if (!m_parseSuccessful)
+ if (m_stackState < AnalysisSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
- return metadata(contract(_contractName), _type);
+ solAssert(_contract.contract, "");
+
+ // caches the result
+ if (!_contract.abi)
+ _contract.abi.reset(new Json::Value(ABI::generate(*_contract.contract)));
+
+ return *_contract.abi;
}
-Json::Value const& CompilerStack::metadata(Contract const& _contract, DocumentationType _type) const
+Json::Value const& CompilerStack::natspec(string const& _contractName, DocumentationType _type) const
{
- if (!m_parseSuccessful)
+ return natspec(contract(_contractName), _type);
+}
+
+Json::Value const& CompilerStack::natspec(Contract const& _contract, DocumentationType _type) const
+{
+ if (m_stackState < AnalysisSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
solAssert(_contract.contract, "");
@@ -387,40 +455,54 @@ Json::Value const& CompilerStack::metadata(Contract const& _contract, Documentat
case DocumentationType::NatspecDev:
doc = &_contract.devDocumentation;
break;
- case DocumentationType::ABIInterface:
- doc = &_contract.interface;
- break;
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal documentation type."));
}
// caches the result
if (!*doc)
- doc->reset(new Json::Value(InterfaceHandler::documentation(*_contract.contract, _type)));
+ doc->reset(new Json::Value(Natspec::documentation(*_contract.contract, _type)));
return *(*doc);
}
+Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
+{
+ Json::Value methodIdentifiers(Json::objectValue);
+ for (auto const& it: contractDefinition(_contractName).interfaceFunctions())
+ methodIdentifiers[it.second->externalSignature()] = toHex(it.first.ref());
+ return methodIdentifiers;
+}
+
string const& CompilerStack::onChainMetadata(string const& _contractName) const
{
- if (!m_parseSuccessful)
- BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
+ if (m_stackState != CompilationSuccessful)
+ BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
return contract(_contractName).onChainMetadata;
}
Scanner const& CompilerStack::scanner(string const& _sourceName) const
{
+ if (m_stackState < SourcesSet)
+ BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No sources set."));
+
return *source(_sourceName).scanner;
}
SourceUnit const& CompilerStack::ast(string const& _sourceName) const
{
+ if (m_stackState < ParsingSuccessful)
+ BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
+
return *source(_sourceName).ast;
}
ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const
{
+ if (m_stackState != CompilationSuccessful)
+ BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
+
return *contract(_contractName).contract;
}
@@ -469,19 +551,18 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
if (m_sources.count(importPath) || newSources.count(importPath))
continue;
- ReadFileResult result{false, string("File not supplied initially.")};
+ ReadFile::Result result{false, string("File not supplied initially.")};
if (m_readFile)
result = m_readFile(importPath);
if (result.success)
- newSources[importPath] = result.contentsOrErrorMesage;
+ newSources[importPath] = result.contentsOrErrorMessage;
else
{
- auto err = make_shared<Error>(Error::Type::ParserError);
- *err <<
- errinfo_sourceLocation(import->location()) <<
- errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMesage);
- m_errors.push_back(std::move(err));
+ m_errorReporter.parserError(
+ import->location(),
+ string("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage)
+ );
continue;
}
}
@@ -499,23 +580,32 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context
};
size_t longestPrefix = 0;
- string longestPrefixTarget;
+ size_t longestContext = 0;
+ string bestMatchTarget;
+
for (auto const& redir: m_remappings)
{
- // Skip if we already have a closer match.
- if (longestPrefix > 0 && redir.prefix.length() <= longestPrefix)
+ string context = sanitizePath(redir.context);
+ string prefix = sanitizePath(redir.prefix);
+
+ // Skip if current context is closer
+ if (context.length() < longestContext)
continue;
// Skip if redir.context is not a prefix of _context
- if (!isPrefixOf(redir.context, _context))
+ if (!isPrefixOf(context, _context))
+ continue;
+ // Skip if we already have a closer prefix match.
+ if (prefix.length() < longestPrefix && context.length() == longestContext)
continue;
// Skip if the prefix does not match.
- if (!isPrefixOf(redir.prefix, _path))
+ if (!isPrefixOf(prefix, _path))
continue;
- longestPrefix = redir.prefix.length();
- longestPrefixTarget = redir.target;
+ longestContext = context.length();
+ longestPrefix = prefix.length();
+ bestMatchTarget = sanitizePath(redir.target);
}
- string path = longestPrefixTarget;
+ string path = bestMatchTarget;
path.append(_path.begin() + longestPrefix, _path.end());
return path;
}
@@ -550,44 +640,13 @@ void CompilerStack::resolveImports()
swap(m_sourceOrder, sourceOrder);
}
-bool CompilerStack::checkLibraryNameClashes()
-{
- bool clashFound = false;
- map<string, SourceLocation> libraries;
- for (Source const* source: m_sourceOrder)
- for (ASTPointer<ASTNode> const& node: source->ast->nodes())
- if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
- if (contract->isLibrary())
- {
- if (libraries.count(contract->name()))
- {
- auto err = make_shared<Error>(Error::Type::DeclarationError);
- *err <<
- errinfo_sourceLocation(contract->location()) <<
- errinfo_comment(
- "Library \"" + contract->name() + "\" declared twice "
- "(will create ambiguities during linking)."
- ) <<
- errinfo_secondarySourceLocation(SecondarySourceLocation().append(
- "The other declaration is here:", libraries[contract->name()]
- ));
-
- m_errors.push_back(err);
- clashFound = true;
- }
- else
- libraries[contract->name()] = contract->location();
- }
- return !clashFound;
-}
-
string CompilerStack::absolutePath(string const& _path, string const& _reference) const
{
- // Anything that does not start with `.` is an absolute path.
- if (_path.empty() || _path.front() != '.')
- return _path;
using path = boost::filesystem::path;
path p(_path);
+ // Anything that does not start with `.` is an absolute path.
+ if (p.begin() == p.end() || (*p.begin() != "." && *p.begin() != ".."))
+ return _path;
path result(_reference);
result.remove_filename();
for (path::iterator it = p.begin(); it != p.end(); ++it)
@@ -603,13 +662,17 @@ void CompilerStack::compileContract(
map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
)
{
- if (_compiledContracts.count(&_contract) || !_contract.annotation().isFullyImplemented)
+ if (
+ _compiledContracts.count(&_contract) ||
+ !_contract.annotation().isFullyImplemented ||
+ !_contract.constructorIsPublic()
+ )
return;
for (auto const* dependency: _contract.annotation().contractDependencies)
compileContract(*dependency, _compiledContracts);
shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns);
- Contract& compiledContract = m_contracts.at(_contract.name());
+ Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
string onChainMetadata = createOnChainMetadata(compiledContract);
bytes cborEncodedMetadata =
// CBOR-encoding of {"bzzr0": dev::swarmHash(onChainMetadata)}
@@ -620,8 +683,33 @@ void CompilerStack::compileContract(
cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2);
compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata);
compiledContract.compiler = compiler;
- compiledContract.object = compiler->assembledObject();
- compiledContract.runtimeObject = compiler->runtimeObject();
+
+ try
+ {
+ compiledContract.object = compiler->assembledObject();
+ }
+ catch(eth::OptimizerException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for bytecode"));
+ }
+ catch(eth::AssemblyException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for bytecode"));
+ }
+
+ try
+ {
+ compiledContract.runtimeObject = compiler->runtimeObject();
+ }
+ catch(eth::OptimizerException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for deployed bytecode"));
+ }
+ catch(eth::AssemblyException const&)
+ {
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for deployed bytecode"));
+ }
+
compiledContract.onChainMetadata = onChainMetadata;
_compiledContracts[compiledContract.contract] = &compiler->assembly();
@@ -640,11 +728,6 @@ void CompilerStack::compileContract(
}
}
-std::string CompilerStack::defaultContractName() const
-{
- return contract("").contract->name();
-}
-
CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const
{
if (m_contracts.empty())
@@ -655,10 +738,27 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa
for (auto const& it: m_sources)
for (ASTPointer<ASTNode> const& node: it.second.ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
- contractName = contract->name();
+ contractName = contract->fullyQualifiedName();
auto it = m_contracts.find(contractName);
- if (it == m_contracts.end())
+ // To provide a measure of backward-compatibility, if a contract is not located by its
+ // fully-qualified name, a lookup will be attempted purely on the contract's name to see
+ // if anything will satisfy.
+ if (it == m_contracts.end() && contractName.find(":") == string::npos)
+ {
+ for (auto const& contractEntry: m_contracts)
+ {
+ stringstream ss;
+ ss.str(contractEntry.first);
+ // All entries are <source>:<contract>
+ string source;
+ string foundName;
+ getline(ss, source, ':');
+ getline(ss, foundName, ':');
+ if (foundName == contractName) return contractEntry.second;
+ }
+ // If we get here, both lookup methods failed.
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found."));
+ }
return it->second;
}
@@ -676,7 +776,7 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const
Json::Value meta;
meta["version"] = 1;
meta["language"] = "Solidity";
- meta["compiler"]["version"] = VersionString;
+ meta["compiler"]["version"] = VersionStringStrict;
meta["sources"] = Json::objectValue;
for (auto const& s: m_sources)
@@ -684,8 +784,15 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const
solAssert(s.second.scanner, "Scanner not available");
meta["sources"][s.first]["keccak256"] =
"0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes());
- meta["sources"][s.first]["url"] =
- "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes());
+ if (m_metadataLiteralSources)
+ meta["sources"][s.first]["content"] = s.second.scanner->source();
+ else
+ {
+ meta["sources"][s.first]["urls"] = Json::arrayValue;
+ meta["sources"][s.first]["urls"].append(
+ "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes())
+ );
+ }
}
meta["settings"]["optimizer"]["enabled"] = m_optimize;
meta["settings"]["optimizer"]["runs"] = m_optimizeRuns;
@@ -703,9 +810,9 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const
for (auto const& library: m_libraries)
meta["settings"]["libraries"][library.first] = "0x" + toHex(library.second.asBytes());
- meta["output"]["abi"] = metadata(_contract, DocumentationType::ABIInterface);
- meta["output"]["userdoc"] = metadata(_contract, DocumentationType::NatspecUser);
- meta["output"]["devdoc"] = metadata(_contract, DocumentationType::NatspecDev);
+ meta["output"]["abi"] = contractABI(_contract);
+ meta["output"]["userdoc"] = natspec(_contract, DocumentationType::NatspecUser);
+ meta["output"]["devdoc"] = natspec(_contract, DocumentationType::NatspecDev);
return jsonCompactPrint(meta);
}
@@ -782,3 +889,88 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con
}
return ret;
}
+
+namespace
+{
+
+Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
+{
+ if (_gas.isInfinite)
+ return Json::Value("infinite");
+ else
+ return Json::Value(toString(_gas.value));
+}
+
+}
+
+Json::Value CompilerStack::gasEstimates(string const& _contractName) const
+{
+ if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName))
+ return Json::Value();
+
+ using Gas = GasEstimator::GasConsumption;
+ Json::Value output(Json::objectValue);
+
+ if (eth::AssemblyItems const* items = assemblyItems(_contractName))
+ {
+ Gas executionGas = GasEstimator::functionalEstimation(*items);
+ u256 bytecodeSize(runtimeObject(_contractName).bytecode.size());
+ Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas;
+
+ Json::Value creation(Json::objectValue);
+ creation["codeDepositCost"] = gasToJson(codeDepositGas);
+ creation["executionCost"] = gasToJson(executionGas);
+ /// TODO: implement + overload to avoid the need of +=
+ executionGas += codeDepositGas;
+ creation["totalCost"] = gasToJson(executionGas);
+ output["creation"] = creation;
+ }
+
+ if (eth::AssemblyItems const* items = runtimeAssemblyItems(_contractName))
+ {
+ /// External functions
+ ContractDefinition const& contract = contractDefinition(_contractName);
+ Json::Value externalFunctions(Json::objectValue);
+ for (auto it: contract.interfaceFunctions())
+ {
+ string sig = it.second->externalSignature();
+ externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig));
+ }
+
+ if (contract.fallbackFunction())
+ /// This needs to be set to an invalid signature in order to trigger the fallback,
+ /// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound.
+ /// An empty string ("") would work to trigger the shortcut only.
+ externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID"));
+
+ if (!externalFunctions.empty())
+ output["external"] = externalFunctions;
+
+ /// Internal functions
+ Json::Value internalFunctions(Json::objectValue);
+ for (auto const& it: contract.definedFunctions())
+ {
+ /// Exclude externally visible functions, constructor and the fallback function
+ if (it->isPartOfExternalInterface() || it->isConstructor() || it->name().empty())
+ continue;
+
+ size_t entry = functionEntryPoint(_contractName, *it);
+ GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite();
+ if (entry > 0)
+ gas = GasEstimator::functionalEstimation(*items, entry, *it);
+
+ FunctionType type(*it);
+ string sig = it->name() + "(";
+ auto paramTypes = type.parameterTypes();
+ for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it)
+ sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ",");
+ sig += ")";
+ internalFunctions[sig] = gasToJson(gas);
+ }
+
+ if (!internalFunctions.empty())
+ output["internal"] = internalFunctions;
+ }
+
+ return output;
+}
diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h
index f98a457a..03a1b806 100644
--- a/libsolidity/interface/CompilerStack.h
+++ b/libsolidity/interface/CompilerStack.h
@@ -29,12 +29,14 @@
#include <vector>
#include <functional>
#include <boost/noncopyable.hpp>
+#include <boost/filesystem.hpp>
#include <json/json.h>
#include <libdevcore/Common.h>
#include <libdevcore/FixedHash.h>
#include <libevmasm/SourceLocation.h>
#include <libevmasm/LinkerObject.h>
-#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libsolidity/interface/ReadFile.h>
namespace dev
{
@@ -51,19 +53,20 @@ namespace solidity
// forward declarations
class Scanner;
+class ASTNode;
class ContractDefinition;
class FunctionDefinition;
class SourceUnit;
class Compiler;
class GlobalContext;
-class InterfaceHandler;
+class Natspec;
class Error;
+class DeclarationContainer;
enum class DocumentationType: uint8_t
{
NatspecUser = 1,
- NatspecDev,
- ABIInterface
+ NatspecDev
};
/**
@@ -74,18 +77,21 @@ enum class DocumentationType: uint8_t
class CompilerStack: boost::noncopyable
{
public:
- struct ReadFileResult
- {
- bool success;
- std::string contentsOrErrorMesage;
+ enum State {
+ Empty,
+ SourcesSet,
+ ParsingSuccessful,
+ AnalysisSuccessful,
+ CompilationSuccessful
};
- /// File reading callback.
- using ReadFileCallback = std::function<ReadFileResult(std::string const&)>;
-
/// Creates a new compiler stack.
- /// @param _readFile callback to used to read files for import statements. Should return
- explicit CompilerStack(ReadFileCallback const& _readFile = ReadFileCallback());
+ /// @param _readFile callback to used to read files for import statements. Must return
+ /// and must not emit exceptions.
+ explicit CompilerStack(ReadFile::Callback const& _readFile = ReadFile::Callback()):
+ m_readFile(_readFile),
+ m_errorList(),
+ m_errorReporter(m_errorList) {}
/// Sets path remappings in the format "context:prefix=target"
void setRemappings(std::vector<std::string> const& _remappings);
@@ -107,9 +113,18 @@ public:
/// Sets the given source code as the only source unit apart from standard sources and parses it.
/// @returns false on error.
bool parse(std::string const& _sourceCode);
+ /// performs the analyisis steps (imports, scopesetting, syntaxCheck, referenceResolving,
+ /// typechecking, staticAnalysis) on previously set sources
+ /// @returns false on error.
+ bool analyze();
+ /// Parses and analyzes all source units that were added
+ /// @returns false on error.
+ bool parseAndAnalyze();
+ /// Sets the given source code as the only source unit apart from standard sources and parses and analyzes it.
+ /// @returns false on error.
+ bool parseAndAnalyze(std::string const& _sourceCode);
/// @returns a list of the contract names in the sources.
std::vector<std::string> contractNames() const;
- std::string defaultContractName() const;
/// Compiles the source units that were previously added and parsed.
/// @returns false on error.
@@ -122,12 +137,6 @@ public:
/// @returns false on error.
bool compile(std::string const& _sourceCode, bool _optimize = false, unsigned _runs = 200);
- /// Tries to translate all source files into a language suitable for formal analysis.
- /// @param _errors list to store errors - defaults to the internal error list.
- /// @returns false on error.
- bool prepareFormalAnalysis(ErrorList* _errors = nullptr);
- std::string const& formalTranslation() const { return m_formalTranslation; }
-
/// @returns the assembled object for a contract.
eth::LinkerObject const& object(std::string const& _contractName = "") const;
/// @returns the runtime object for the contract.
@@ -147,10 +156,9 @@ public:
/// @returns the string that provides a mapping between runtime bytecode and sourcecode.
/// if the contract does not (yet) have bytecode.
std::string const* runtimeSourceMapping(std::string const& _contractName = "") const;
- /// @returns hash of the runtime bytecode for the contract, i.e. the code that is
- /// returned by the constructor or the zero-h256 if the contract still needs to be linked or
- /// does not have runtime code.
- dev::h256 contractCodeHash(std::string const& _contractName = "") const;
+
+ /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use
+ std::string const filesystemFriendlyName(std::string const& _contractName) const;
/// Streams a verbose version of the assembly to @a _outStream.
/// @arg _sourceCodes is the map of input files to source code strings
@@ -163,30 +171,28 @@ public:
/// @returns a mapping assigning each source name its index inside the vector returned
/// by sourceNames().
std::map<std::string, unsigned> sourceIndices() const;
- /// @returns a JSON representing the contract interface.
+ /// @returns a JSON representing the contract ABI.
/// Prerequisite: Successful call to parse or compile.
- Json::Value const& interface(std::string const& _contractName = "") const;
+ Json::Value const& contractABI(std::string const& _contractName = "") const;
/// @returns a JSON representing the contract's documentation.
/// Prerequisite: Successful call to parse or compile.
/// @param type The type of the documentation to get.
/// Can be one of 4 types defined at @c DocumentationType
- Json::Value const& metadata(std::string const& _contractName, DocumentationType _type) const;
+ Json::Value const& natspec(std::string const& _contractName, DocumentationType _type) const;
+
+ /// @returns a JSON representing a map of method identifiers (hashes) to function names.
+ Json::Value methodIdentifiers(std::string const& _contractName) const;
+
std::string const& onChainMetadata(std::string const& _contractName) const;
+ void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; }
+
+ /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
+ Json::Value gasEstimates(std::string const& _contractName) const;
/// @returns the previously used scanner, useful for counting lines during error reporting.
Scanner const& scanner(std::string const& _sourceName = "") const;
/// @returns the parsed source unit with the supplied name.
SourceUnit const& ast(std::string const& _sourceName = "") const;
- /// @returns the parsed contract with the supplied name. Throws an exception if the contract
- /// does not exist.
- ContractDefinition const& contractDefinition(std::string const& _contractName) const;
-
- /// @returns the offset of the entry point of the given function into the list of assembly items
- /// or zero if it is not found or does not exist.
- size_t functionEntryPoint(
- std::string const& _contractName,
- FunctionDefinition const& _function
- ) const;
/// Helper function for logs printing. Do only use in error cases, it's quite expensive.
/// line and columns are numbered starting from 1 with following order:
@@ -194,7 +200,9 @@ public:
std::tuple<int, int, int, int> positionFromSourceLocation(SourceLocation const& _sourceLocation) const;
/// @returns the list of errors that occured during parsing and type checking.
- ErrorList const& errors() const { return m_errors; }
+ ErrorList const& errors() { return m_errorReporter.errors(); }
+
+ State state() const { return m_stackState; }
private:
/**
@@ -216,38 +224,48 @@ private:
eth::LinkerObject runtimeObject;
eth::LinkerObject cloneObject;
std::string onChainMetadata; ///< The metadata json that will be hashed into the chain.
- mutable std::unique_ptr<Json::Value const> interface;
+ mutable std::unique_ptr<Json::Value const> abi;
mutable std::unique_ptr<Json::Value const> userDocumentation;
mutable std::unique_ptr<Json::Value const> devDocumentation;
mutable std::unique_ptr<std::string const> sourceMapping;
mutable std::unique_ptr<std::string const> runtimeSourceMapping;
};
-
/// Loads the missing sources from @a _ast (named @a _path) using the callback
/// @a m_readFile and stores the absolute paths of all imports in the AST annotations.
/// @returns the newly loaded sources.
StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path);
std::string applyRemapping(std::string const& _path, std::string const& _context);
void resolveImports();
- /// Checks whether there are libraries with the same name, reports that as an error and
- /// @returns false in this case.
- bool checkLibraryNameClashes();
/// @returns the absolute path corresponding to @a _path relative to @a _reference.
std::string absolutePath(std::string const& _path, std::string const& _reference) const;
+ /// Helper function to return path converted strings.
+ std::string sanitizePath(std::string const& _path) const { return boost::filesystem::path(_path).generic_string(); }
+
/// Compile a single contract and put the result in @a _compiledContracts.
void compileContract(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
);
-
void link();
Contract const& contract(std::string const& _contractName = "") const;
Source const& source(std::string const& _sourceName = "") const;
+ /// @returns the parsed contract with the supplied name. Throws an exception if the contract
+ /// does not exist.
+ ContractDefinition const& contractDefinition(std::string const& _contractName) const;
+
std::string createOnChainMetadata(Contract const& _contract) const;
std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
- Json::Value const& metadata(Contract const&, DocumentationType _type) const;
+ Json::Value const& contractABI(Contract const&) const;
+ Json::Value const& natspec(Contract const&, DocumentationType _type) const;
+
+ /// @returns the offset of the entry point of the given function into the list of assembly items
+ /// or zero if it is not found or does not exist.
+ size_t functionEntryPoint(
+ std::string const& _contractName,
+ FunctionDefinition const& _function
+ ) const;
struct Remapping
{
@@ -256,20 +274,22 @@ private:
std::string target;
};
- ReadFileCallback m_readFile;
+ ReadFile::Callback m_readFile;
bool m_optimize = false;
unsigned m_optimizeRuns = 200;
std::map<std::string, h160> m_libraries;
/// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum
/// "context:prefix=target"
std::vector<Remapping> m_remappings;
- bool m_parseSuccessful;
std::map<std::string const, Source> m_sources;
std::shared_ptr<GlobalContext> m_globalContext;
+ std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes;
std::vector<Source const*> m_sourceOrder;
std::map<std::string const, Contract> m_contracts;
- std::string m_formalTranslation;
- ErrorList m_errors;
+ ErrorList m_errorList;
+ ErrorReporter m_errorReporter;
+ bool m_metadataLiteralSources = false;
+ State m_stackState = Empty;
};
}
diff --git a/libsolidity/interface/ErrorReporter.cpp b/libsolidity/interface/ErrorReporter.cpp
new file mode 100644
index 00000000..6e2667a5
--- /dev/null
+++ b/libsolidity/interface/ErrorReporter.cpp
@@ -0,0 +1,167 @@
+/*
+ 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 Rhett <roadriverrail@gmail.com>
+ * @date 2017
+ * Error helper class.
+ */
+
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libsolidity/ast/AST.h>
+#include <memory>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+ErrorReporter& ErrorReporter::operator=(ErrorReporter const& _errorReporter)
+{
+ if (&_errorReporter == this)
+ return *this;
+ m_errorList = _errorReporter.m_errorList;
+ return *this;
+}
+
+
+void ErrorReporter::warning(string const& _description)
+{
+ error(Error::Type::Warning, SourceLocation(), _description);
+}
+
+void ErrorReporter::warning(SourceLocation const& _location, string const& _description)
+{
+ error(Error::Type::Warning, _location, _description);
+}
+
+void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description)
+{
+ auto err = make_shared<Error>(_type);
+ *err <<
+ errinfo_sourceLocation(_location) <<
+ errinfo_comment(_description);
+
+ m_errorList.push_back(err);
+}
+
+void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description)
+{
+ auto err = make_shared<Error>(_type);
+ *err <<
+ errinfo_sourceLocation(_location) <<
+ errinfo_secondarySourceLocation(_secondaryLocation) <<
+ errinfo_comment(_description);
+
+ m_errorList.push_back(err);
+}
+
+
+void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description)
+{
+ error(_type, _location, _description);
+ BOOST_THROW_EXCEPTION(FatalError());
+}
+
+ErrorList const& ErrorReporter::errors() const
+{
+ return m_errorList;
+}
+
+void ErrorReporter::clear()
+{
+ m_errorList.clear();
+}
+
+void ErrorReporter::declarationError(SourceLocation const& _location, SecondarySourceLocation const&_secondaryLocation, string const& _description)
+{
+ error(
+ Error::Type::DeclarationError,
+ _location,
+ _secondaryLocation,
+ _description
+ );
+}
+
+void ErrorReporter::declarationError(SourceLocation const& _location, string const& _description)
+{
+ error(
+ Error::Type::DeclarationError,
+ _location,
+ _description
+ );
+}
+
+void ErrorReporter::fatalDeclarationError(SourceLocation const& _location, std::string const& _description)
+{
+ fatalError(
+ Error::Type::DeclarationError,
+ _location,
+ _description);
+}
+
+void ErrorReporter::parserError(SourceLocation const& _location, string const& _description)
+{
+ error(
+ Error::Type::ParserError,
+ _location,
+ _description
+ );
+}
+
+void ErrorReporter::fatalParserError(SourceLocation const& _location, string const& _description)
+{
+ fatalError(
+ Error::Type::ParserError,
+ _location,
+ _description
+ );
+}
+
+void ErrorReporter::syntaxError(SourceLocation const& _location, string const& _description)
+{
+ error(
+ Error::Type::SyntaxError,
+ _location,
+ _description
+ );
+}
+
+void ErrorReporter::typeError(SourceLocation const& _location, string const& _description)
+{
+ error(
+ Error::Type::TypeError,
+ _location,
+ _description
+ );
+}
+
+
+void ErrorReporter::fatalTypeError(SourceLocation const& _location, string const& _description)
+{
+ fatalError(Error::Type::TypeError,
+ _location,
+ _description
+ );
+}
+
+void ErrorReporter::docstringParsingError(string const& _description)
+{
+ error(
+ Error::Type::DocstringParsingError,
+ SourceLocation(),
+ _description
+ );
+}
diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h
new file mode 100644
index 00000000..e5605d24
--- /dev/null
+++ b/libsolidity/interface/ErrorReporter.h
@@ -0,0 +1,102 @@
+/*
+ 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 Rhett <roadriverrail@gmail.com>
+ * @date 2017
+ * Error reporting helper class.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/Exceptions.h>
+#include <libevmasm/SourceLocation.h>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ASTNode;
+
+class ErrorReporter
+{
+public:
+
+ ErrorReporter(ErrorList& _errors):
+ m_errorList(_errors) { }
+
+ ErrorReporter& operator=(ErrorReporter const& _errorReporter);
+
+ void warning(std::string const& _description = std::string());
+
+ void warning(
+ SourceLocation const& _location = SourceLocation(),
+ std::string const& _description = std::string()
+ );
+
+ void error(
+ Error::Type _type,
+ SourceLocation const& _location = SourceLocation(),
+ std::string const& _description = std::string()
+ );
+
+ void declarationError(
+ SourceLocation const& _location,
+ SecondarySourceLocation const& _secondaryLocation = SecondarySourceLocation(),
+ std::string const& _description = std::string()
+ );
+
+ void declarationError(
+ SourceLocation const& _location,
+ std::string const& _description = std::string()
+ );
+
+ void fatalDeclarationError(SourceLocation const& _location, std::string const& _description);
+
+ void parserError(SourceLocation const& _location, std::string const& _description);
+
+ void fatalParserError(SourceLocation const& _location, std::string const& _description);
+
+ void syntaxError(SourceLocation const& _location, std::string const& _description);
+
+ void typeError(SourceLocation const& _location, std::string const& _description);
+
+ void fatalTypeError(SourceLocation const& _location, std::string const& _description);
+
+ void docstringParsingError(std::string const& _location);
+
+ ErrorList const& errors() const;
+
+ void clear();
+
+private:
+ void error(Error::Type _type,
+ SourceLocation const& _location,
+ SecondarySourceLocation const& _secondaryLocation,
+ std::string const& _description = std::string());
+
+ void fatalError(Error::Type _type,
+ SourceLocation const& _location = SourceLocation(),
+ std::string const& _description = std::string());
+
+ ErrorList& m_errorList;
+};
+
+
+}
+}
+
diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp
index 90a680b4..9f2a2d06 100644
--- a/libsolidity/interface/Exceptions.cpp
+++ b/libsolidity/interface/Exceptions.cpp
@@ -21,32 +21,30 @@
*/
#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/interface/Utils.h>
+using namespace std;
using namespace dev;
using namespace dev::solidity;
-Error::Error(Type _type): m_type(_type)
+Error::Error(Type _type, SourceLocation const& _location, string const& _description):
+ m_type(_type)
{
switch(m_type)
{
case Type::DeclarationError:
- m_typeName = "Declaration Error";
+ m_typeName = "DeclarationError";
break;
case Type::DocstringParsingError:
- m_typeName = "Docstring Parsing Error";
+ m_typeName = "DocstringParsingError";
break;
case Type::ParserError:
- m_typeName = "Parser Error";
+ m_typeName = "ParserError";
break;
case Type::SyntaxError:
- m_typeName = "Syntax Error";
+ m_typeName = "SyntaxError";
break;
case Type::TypeError:
- m_typeName = "Type Error";
- break;
- case Type::Why3TranslatorError:
- m_typeName = "Why3 Translator Error";
+ m_typeName = "TypeError";
break;
case Type::Warning:
m_typeName = "Warning";
@@ -55,4 +53,30 @@ Error::Error(Type _type): m_type(_type)
solAssert(false, "");
break;
}
+
+ if (!_location.isEmpty())
+ *this << errinfo_sourceLocation(_location);
+ if (!_description.empty())
+ *this << errinfo_comment(_description);
+}
+
+Error::Error(Error::Type _type, const std::string& _description, const SourceLocation& _location):
+ Error(_type)
+{
+ if (!_location.isEmpty())
+ *this << errinfo_sourceLocation(_location);
+ *this << errinfo_comment(_description);
+}
+
+string Exception::lineInfo() const
+{
+ char const* const* file = boost::get_error_info<boost::throw_file>(*this);
+ int const* line = boost::get_error_info<boost::throw_line>(*this);
+ string ret;
+ if (file)
+ ret += *file;
+ ret += ':';
+ if (line)
+ ret += boost::lexical_cast<string>(*line);
+ return ret;
}
diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h
index 81716c41..09301b10 100644
--- a/libsolidity/interface/Exceptions.h
+++ b/libsolidity/interface/Exceptions.h
@@ -25,6 +25,7 @@
#include <string>
#include <utility>
#include <libdevcore/Exceptions.h>
+#include <libdevcore/Assertions.h>
#include <libevmasm/SourceLocation.h>
namespace dev
@@ -39,6 +40,16 @@ struct InternalCompilerError: virtual Exception {};
struct FatalError: virtual Exception {};
struct UnimplementedFeatureError: virtual Exception{};
+/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
+#define solAssert(CONDITION, DESCRIPTION) \
+ assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION)
+
+#define solUnimplementedAssert(CONDITION, DESCRIPTION) \
+ assertThrow(CONDITION, ::dev::solidity::UnimplementedFeatureError, DESCRIPTION)
+
+#define solUnimplemented(DESCRIPTION) \
+ solUnimplementedAssert(false, DESCRIPTION)
+
class Error: virtual public Exception
{
public:
@@ -49,11 +60,16 @@ public:
ParserError,
TypeError,
SyntaxError,
- Why3TranslatorError,
Warning
};
- explicit Error(Type _type);
+ explicit Error(
+ Type _type,
+ SourceLocation const& _location = SourceLocation(),
+ std::string const& _description = std::string()
+ );
+
+ Error(Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation());
Type type() const { return m_type; }
std::string const& typeName() const { return m_typeName; }
diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp
deleted file mode 100644
index 9944bb22..00000000
--- a/libsolidity/interface/InterfaceHandler.cpp
+++ /dev/null
@@ -1,189 +0,0 @@
-
-#include <libsolidity/interface/InterfaceHandler.h>
-#include <boost/range/irange.hpp>
-#include <libsolidity/ast/AST.h>
-#include <libsolidity/interface/CompilerStack.h>
-
-using namespace std;
-using namespace dev;
-using namespace dev::solidity;
-
-Json::Value InterfaceHandler::documentation(
- ContractDefinition const& _contractDef,
- DocumentationType _type
-)
-{
- switch(_type)
- {
- case DocumentationType::NatspecUser:
- return userDocumentation(_contractDef);
- case DocumentationType::NatspecDev:
- return devDocumentation(_contractDef);
- case DocumentationType::ABIInterface:
- return abiInterface(_contractDef);
- }
-
- BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type"));
-}
-
-Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDef)
-{
- Json::Value abi(Json::arrayValue);
-
- auto populateParameters = [](vector<string> const& _paramNames, vector<string> const& _paramTypes)
- {
- Json::Value params(Json::arrayValue);
- solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match");
- for (unsigned i = 0; i < _paramNames.size(); ++i)
- {
- Json::Value param;
- param["name"] = _paramNames[i];
- param["type"] = _paramTypes[i];
- params.append(param);
- }
- return params;
- };
-
- for (auto it: _contractDef.interfaceFunctions())
- {
- auto externalFunctionType = it.second->interfaceFunctionType();
- Json::Value method;
- method["type"] = "function";
- method["name"] = it.second->declaration().name();
- method["constant"] = it.second->isConstant();
- method["payable"] = it.second->isPayable();
- method["inputs"] = populateParameters(
- externalFunctionType->parameterNames(),
- externalFunctionType->parameterTypeNames(_contractDef.isLibrary())
- );
- method["outputs"] = populateParameters(
- externalFunctionType->returnParameterNames(),
- externalFunctionType->returnParameterTypeNames(_contractDef.isLibrary())
- );
- abi.append(method);
- }
- if (_contractDef.constructor())
- {
- Json::Value method;
- method["type"] = "constructor";
- auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
- solAssert(!!externalFunction, "");
- method["payable"] = externalFunction->isPayable();
- method["inputs"] = populateParameters(
- externalFunction->parameterNames(),
- externalFunction->parameterTypeNames(_contractDef.isLibrary())
- );
- abi.append(method);
- }
- if (_contractDef.fallbackFunction())
- {
- auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType();
- solAssert(!!externalFunctionType, "");
- Json::Value method;
- method["type"] = "fallback";
- method["payable"] = externalFunctionType->isPayable();
- abi.append(method);
- }
- for (auto const& it: _contractDef.interfaceEvents())
- {
- Json::Value event;
- event["type"] = "event";
- event["name"] = it->name();
- event["anonymous"] = it->isAnonymous();
- Json::Value params(Json::arrayValue);
- for (auto const& p: it->parameters())
- {
- solAssert(!!p->annotation().type->interfaceType(false), "");
- Json::Value input;
- input["name"] = p->name();
- input["type"] = p->annotation().type->interfaceType(false)->canonicalName(false);
- input["indexed"] = p->isIndexed();
- params.append(input);
- }
- event["inputs"] = params;
- abi.append(event);
- }
-
- return abi;
-}
-
-Json::Value InterfaceHandler::userDocumentation(ContractDefinition const& _contractDef)
-{
- Json::Value doc;
- Json::Value methods(Json::objectValue);
-
- for (auto const& it: _contractDef.interfaceFunctions())
- if (it.second->hasDeclaration())
- if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
- {
- string value = extractDoc(f->annotation().docTags, "notice");
- if (!value.empty())
- {
- Json::Value user;
- // since @notice is the only user tag if missing function should not appear
- user["notice"] = Json::Value(value);
- methods[it.second->externalSignature()] = user;
- }
- }
- doc["methods"] = methods;
-
- return doc;
-}
-
-Json::Value InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef)
-{
- Json::Value doc;
- Json::Value methods(Json::objectValue);
-
- auto author = extractDoc(_contractDef.annotation().docTags, "author");
- if (!author.empty())
- doc["author"] = author;
- auto title = extractDoc(_contractDef.annotation().docTags, "title");
- if (!title.empty())
- doc["title"] = title;
-
- for (auto const& it: _contractDef.interfaceFunctions())
- {
- if (!it.second->hasDeclaration())
- continue;
- Json::Value method;
- if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
- {
- auto dev = extractDoc(fun->annotation().docTags, "dev");
- if (!dev.empty())
- method["details"] = Json::Value(dev);
-
- auto author = extractDoc(fun->annotation().docTags, "author");
- if (!author.empty())
- method["author"] = author;
-
- auto ret = extractDoc(fun->annotation().docTags, "return");
- if (!ret.empty())
- method["return"] = ret;
-
- Json::Value params(Json::objectValue);
- auto paramRange = fun->annotation().docTags.equal_range("param");
- for (auto i = paramRange.first; i != paramRange.second; ++i)
- params[i->second.paramName] = Json::Value(i->second.content);
-
- if (!params.empty())
- method["params"] = params;
-
- if (!method.empty())
- // add the function, only if we have any documentation to add
- methods[it.second->externalSignature()] = method;
- }
- }
- doc["methods"] = methods;
-
- return doc;
-}
-
-string InterfaceHandler::extractDoc(multimap<string, DocTag> const& _tags, string const& _name)
-{
- string value;
- auto range = _tags.equal_range(_name);
- for (auto i = range.first; i != range.second; i++)
- value += i->second.content;
- return value;
-}
diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp
new file mode 100644
index 00000000..70486e23
--- /dev/null
+++ b/libsolidity/interface/Natspec.cpp
@@ -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/>.
+*/
+/**
+ * @author Lefteris <lefteris@ethdev.com>
+ * @date 2014
+ * Takes the parsed AST and produces the Natspec documentation:
+ * https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format
+ *
+ * Can generally deal with JSON files
+ */
+
+#include <libsolidity/interface/Natspec.h>
+#include <boost/range/irange.hpp>
+#include <libsolidity/ast/AST.h>
+#include <libsolidity/interface/CompilerStack.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+Json::Value Natspec::documentation(
+ ContractDefinition const& _contractDef,
+ DocumentationType _type
+)
+{
+ switch(_type)
+ {
+ case DocumentationType::NatspecUser:
+ return userDocumentation(_contractDef);
+ case DocumentationType::NatspecDev:
+ return devDocumentation(_contractDef);
+ }
+
+ BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type"));
+}
+
+Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
+{
+ Json::Value doc;
+ Json::Value methods(Json::objectValue);
+
+ for (auto const& it: _contractDef.interfaceFunctions())
+ if (it.second->hasDeclaration())
+ if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
+ {
+ string value = extractDoc(f->annotation().docTags, "notice");
+ if (!value.empty())
+ {
+ Json::Value user;
+ // since @notice is the only user tag if missing function should not appear
+ user["notice"] = Json::Value(value);
+ methods[it.second->externalSignature()] = user;
+ }
+ }
+ doc["methods"] = methods;
+
+ return doc;
+}
+
+Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
+{
+ Json::Value doc;
+ Json::Value methods(Json::objectValue);
+
+ auto author = extractDoc(_contractDef.annotation().docTags, "author");
+ if (!author.empty())
+ doc["author"] = author;
+ auto title = extractDoc(_contractDef.annotation().docTags, "title");
+ if (!title.empty())
+ doc["title"] = title;
+
+ for (auto const& it: _contractDef.interfaceFunctions())
+ {
+ if (!it.second->hasDeclaration())
+ continue;
+ Json::Value method;
+ if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
+ {
+ auto dev = extractDoc(fun->annotation().docTags, "dev");
+ if (!dev.empty())
+ method["details"] = Json::Value(dev);
+
+ auto author = extractDoc(fun->annotation().docTags, "author");
+ if (!author.empty())
+ method["author"] = author;
+
+ auto ret = extractDoc(fun->annotation().docTags, "return");
+ if (!ret.empty())
+ method["return"] = ret;
+
+ Json::Value params(Json::objectValue);
+ auto paramRange = fun->annotation().docTags.equal_range("param");
+ for (auto i = paramRange.first; i != paramRange.second; ++i)
+ params[i->second.paramName] = Json::Value(i->second.content);
+
+ if (!params.empty())
+ method["params"] = params;
+
+ if (!method.empty())
+ // add the function, only if we have any documentation to add
+ methods[it.second->externalSignature()] = method;
+ }
+ }
+ doc["methods"] = methods;
+
+ return doc;
+}
+
+string Natspec::extractDoc(multimap<string, DocTag> const& _tags, string const& _name)
+{
+ string value;
+ auto range = _tags.equal_range(_name);
+ for (auto i = range.first; i != range.second; i++)
+ value += i->second.content;
+ return value;
+}
diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/Natspec.h
index b7e1bb00..bec9acd2 100644
--- a/libsolidity/interface/InterfaceHandler.h
+++ b/libsolidity/interface/Natspec.h
@@ -17,8 +17,7 @@
/**
* @author Lefteris <lefteris@ethdev.com>
* @date 2014
- * Takes the parsed AST and produces the Natspec
- * documentation and the ABI interface
+ * Takes the parsed AST and produces the Natspec documentation:
* https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format
*
* Can generally deal with JSON files
@@ -37,6 +36,8 @@ namespace solidity
// Forward declarations
class ContractDefinition;
+class Type;
+using TypePointer = std::shared_ptr<Type const>;
struct DocTag;
enum class DocumentationType: uint8_t;
@@ -57,7 +58,7 @@ enum class CommentOwner
Function
};
-class InterfaceHandler
+class Natspec
{
public:
/// Get the given type of documentation
@@ -69,10 +70,6 @@ public:
ContractDefinition const& _contractDef,
DocumentationType _type
);
- /// Get the ABI Interface of the contract
- /// @param _contractDef The contract definition
- /// @return A JSONrepresentation of the contract's ABI Interface
- static Json::Value abiInterface(ContractDefinition const& _contractDef);
/// Get the User documentation of the contract
/// @param _contractDef The contract definition
/// @return A JSON representation of the contract's user documentation
@@ -84,6 +81,14 @@ public:
static Json::Value devDocumentation(ContractDefinition const& _contractDef);
private:
+ /// @returns a json value suitable for a list of types in function input or output
+ /// parameters or other places. If @a _forLibrary is true, complex types are referenced
+ /// by name, otherwise they are anonymously expanded.
+ static Json::Value formatTypeList(
+ std::vector<std::string> const& _names,
+ std::vector<TypePointer> const& _types,
+ bool _forLibrary
+ );
/// @returns concatenation of all content under the given tag name.
static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name);
};
diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h
new file mode 100644
index 00000000..2e8a6bd8
--- /dev/null
+++ b/libsolidity/interface/ReadFile.h
@@ -0,0 +1,45 @@
+/*
+ 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 <string>
+#include <functional>
+#include <boost/noncopyable.hpp>
+
+namespace dev
+{
+
+namespace solidity
+{
+
+class ReadFile: boost::noncopyable
+{
+public:
+ /// File reading result.
+ struct Result
+ {
+ bool success;
+ std::string contentsOrErrorMessage;
+ };
+
+ /// File reading callback.
+ using Callback = std::function<Result(std::string const&)>;
+};
+
+}
+}
diff --git a/libsolidity/interface/SourceReferenceFormatter.h b/libsolidity/interface/SourceReferenceFormatter.h
index 7034f4ab..e8676d60 100644
--- a/libsolidity/interface/SourceReferenceFormatter.h
+++ b/libsolidity/interface/SourceReferenceFormatter.h
@@ -23,6 +23,7 @@
#pragma once
#include <ostream>
+#include <sstream>
#include <functional>
#include <libevmasm/SourceLocation.h>
@@ -53,6 +54,16 @@ public:
std::string const& _name,
ScannerFromSourceNameFun const& _scannerFromSourceName
);
+ static std::string formatExceptionInformation(
+ Exception const& _exception,
+ std::string const& _name,
+ ScannerFromSourceNameFun const& _scannerFromSourceName
+ )
+ {
+ std::ostringstream errorOutput;
+ printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName);
+ return errorOutput.str();
+ }
private:
/// Prints source name if location is given.
static void printSourceName(
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
new file mode 100644
index 00000000..15bb7592
--- /dev/null
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -0,0 +1,476 @@
+/*
+ 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 2016
+ * Standard JSON compiler interface.
+ */
+
+#include <libsolidity/interface/StandardCompiler.h>
+#include <libsolidity/interface/SourceReferenceFormatter.h>
+#include <libsolidity/ast/ASTJsonConverter.h>
+#include <libevmasm/Instruction.h>
+#include <libdevcore/JSON.h>
+#include <libdevcore/SHA3.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+namespace {
+
+Json::Value formatError(
+ bool _warning,
+ string const& _type,
+ string const& _component,
+ string const& _message,
+ string const& _formattedMessage = "",
+ Json::Value const& _sourceLocation = Json::Value()
+)
+{
+ Json::Value error = Json::objectValue;
+ error["type"] = _type;
+ error["component"] = _component;
+ error["severity"] = _warning ? "warning" : "error";
+ error["message"] = _message;
+ error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message;
+ if (_sourceLocation.isObject())
+ error["sourceLocation"] = _sourceLocation;
+ return error;
+}
+
+Json::Value formatFatalError(string const& _type, string const& _message)
+{
+ Json::Value output = Json::objectValue;
+ output["errors"] = Json::arrayValue;
+ output["errors"].append(formatError(false, _type, "general", _message));
+ return output;
+}
+
+Json::Value formatErrorWithException(
+ Exception const& _exception,
+ bool const& _warning,
+ string const& _type,
+ string const& _component,
+ string const& _message,
+ function<Scanner const&(string const&)> const& _scannerFromSourceName
+)
+{
+ string message;
+ string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _type, _scannerFromSourceName);
+
+ // NOTE: the below is partially a copy from SourceReferenceFormatter
+ SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception);
+
+ if (string const* description = boost::get_error_info<errinfo_comment>(_exception))
+ message = ((_message.length() > 0) ? (_message + ":") : "") + *description;
+ else
+ message = _message;
+
+ if (location && location->sourceName)
+ {
+ Json::Value sourceLocation = Json::objectValue;
+ sourceLocation["file"] = *location->sourceName;
+ sourceLocation["start"] = location->start;
+ sourceLocation["end"] = location->end;
+ }
+
+ return formatError(_warning, _type, _component, message, formattedMessage, location);
+}
+
+/// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content.
+bool hashMatchesContent(string const& _hash, string const& _content)
+{
+ try
+ {
+ return dev::h256(_hash) == dev::keccak256(_content);
+ }
+ catch (dev::BadHexCharacter)
+ {
+ return false;
+ }
+}
+
+StringMap createSourceList(Json::Value const& _input)
+{
+ StringMap sources;
+ Json::Value const& jsonSources = _input["sources"];
+ if (jsonSources.isObject())
+ for (auto const& sourceName: jsonSources.getMemberNames())
+ sources[sourceName] = jsonSources[sourceName]["content"].asString();
+ return sources;
+}
+
+Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
+{
+ Json::Value ret(Json::objectValue);
+
+ for (auto const& ref: linkReferences)
+ {
+ string const& fullname = ref.second;
+ size_t colon = fullname.find(':');
+ solAssert(colon != string::npos, "");
+ string file = fullname.substr(0, colon);
+ string name = fullname.substr(colon + 1);
+
+ Json::Value fileObject = ret.get(file, Json::objectValue);
+ Json::Value libraryArray = fileObject.get(name, Json::arrayValue);
+
+ Json::Value entry = Json::objectValue;
+ entry["start"] = Json::UInt(ref.first);
+ entry["length"] = 20;
+
+ libraryArray.append(entry);
+ fileObject[name] = libraryArray;
+ ret[file] = fileObject;
+ }
+
+ return ret;
+}
+
+Json::Value collectEVMObject(eth::LinkerObject const& _object, string const* _sourceMap)
+{
+ Json::Value output = Json::objectValue;
+ output["object"] = _object.toHex();
+ output["opcodes"] = solidity::disassemble(_object.bytecode);
+ output["sourceMap"] = _sourceMap ? *_sourceMap : "";
+ output["linkReferences"] = formatLinkReferences(_object.linkReferences);
+ return output;
+}
+
+}
+
+Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
+{
+ m_compilerStack.reset(false);
+
+ if (!_input.isObject())
+ return formatFatalError("JSONError", "Input is not a JSON object.");
+
+ if (_input["language"] != "Solidity")
+ return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language.");
+
+ Json::Value const& sources = _input["sources"];
+ if (!sources)
+ return formatFatalError("JSONError", "No input sources specified.");
+
+ Json::Value errors = Json::arrayValue;
+
+ for (auto const& sourceName: sources.getMemberNames())
+ {
+ string hash;
+
+ if (!sources[sourceName].isObject())
+ return formatFatalError("JSONError", "Source input is not a JSON object.");
+
+ if (sources[sourceName]["keccak256"].isString())
+ hash = sources[sourceName]["keccak256"].asString();
+
+ if (sources[sourceName]["content"].isString())
+ {
+ string content = sources[sourceName]["content"].asString();
+ if (!hash.empty() && !hashMatchesContent(hash, content))
+ errors.append(formatError(
+ false,
+ "IOError",
+ "general",
+ "Mismatch between content and supplied hash for \"" + sourceName + "\""
+ ));
+ else
+ m_compilerStack.addSource(sourceName, content);
+ }
+ else if (sources[sourceName]["urls"].isArray())
+ {
+ if (!m_readFile)
+ return formatFatalError("JSONError", "No import callback supplied, but URL is requested.");
+
+ bool found = false;
+ vector<string> failures;
+
+ for (auto const& url: sources[sourceName]["urls"])
+ {
+ ReadFile::Result result = m_readFile(url.asString());
+ if (result.success)
+ {
+ if (!hash.empty() && !hashMatchesContent(hash, result.contentsOrErrorMessage))
+ errors.append(formatError(
+ false,
+ "IOError",
+ "general",
+ "Mismatch between content and supplied hash for \"" + sourceName + "\" at \"" + url.asString() + "\""
+ ));
+ else
+ {
+ m_compilerStack.addSource(sourceName, result.contentsOrErrorMessage);
+ found = true;
+ break;
+ }
+ }
+ else
+ failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.contentsOrErrorMessage);
+ }
+
+ for (auto const& failure: failures)
+ {
+ /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors.
+ errors.append(formatError(
+ found ? true : false,
+ "IOError",
+ "general",
+ failure
+ ));
+ }
+ }
+ else
+ return formatFatalError("JSONError", "Invalid input source specified.");
+ }
+
+ Json::Value const& settings = _input.get("settings", Json::Value());
+
+ vector<string> remappings;
+ for (auto const& remapping: settings.get("remappings", Json::Value()))
+ remappings.push_back(remapping.asString());
+ m_compilerStack.setRemappings(remappings);
+
+ Json::Value optimizerSettings = settings.get("optimizer", Json::Value());
+ bool optimize = optimizerSettings.get("enabled", Json::Value(false)).asBool();
+ unsigned optimizeRuns = optimizerSettings.get("runs", Json::Value(200u)).asUInt();
+
+ map<string, h160> libraries;
+ Json::Value jsonLibraries = settings.get("libraries", Json::Value());
+ for (auto const& sourceName: jsonLibraries.getMemberNames())
+ {
+ auto const& jsonSourceName = jsonLibraries[sourceName];
+ for (auto const& library: jsonSourceName.getMemberNames())
+ // @TODO use libraries only for the given source
+ libraries[library] = h160(jsonSourceName[library].asString());
+ }
+
+ Json::Value metadataSettings = settings.get("metadata", Json::Value());
+ m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool());
+
+ auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compilerStack.scanner(_sourceName); };
+
+ try
+ {
+ m_compilerStack.compile(optimize, optimizeRuns, libraries);
+
+ for (auto const& error: m_compilerStack.errors())
+ {
+ Error const& err = dynamic_cast<Error const&>(*error);
+
+ errors.append(formatErrorWithException(
+ *error,
+ err.type() == Error::Type::Warning,
+ err.typeName(),
+ "general",
+ "",
+ scannerFromSourceName
+ ));
+ }
+ }
+ catch (Error const& _error)
+ {
+ if (_error.type() == Error::Type::DocstringParsingError)
+ errors.append(formatError(
+ false,
+ "DocstringParsingError",
+ "general",
+ "Documentation parsing error: " + *boost::get_error_info<errinfo_comment>(_error)
+ ));
+ else
+ errors.append(formatErrorWithException(
+ _error,
+ false,
+ _error.typeName(),
+ "general",
+ "",
+ scannerFromSourceName
+ ));
+ }
+ catch (CompilerError const& _exception)
+ {
+ errors.append(formatErrorWithException(
+ _exception,
+ false,
+ "CompilerError",
+ "general",
+ "Compiler error (" + _exception.lineInfo() + ")",
+ scannerFromSourceName
+ ));
+ }
+ catch (InternalCompilerError const& _exception)
+ {
+ errors.append(formatErrorWithException(
+ _exception,
+ false,
+ "InternalCompilerError",
+ "general",
+ "Internal compiler error (" + _exception.lineInfo() + ")", scannerFromSourceName
+ ));
+ }
+ catch (UnimplementedFeatureError const& _exception)
+ {
+ errors.append(formatErrorWithException(
+ _exception,
+ false,
+ "UnimplementedFeatureError",
+ "general",
+ "Unimplemented feature (" + _exception.lineInfo() + ")",
+ scannerFromSourceName));
+ }
+ catch (Exception const& _exception)
+ {
+ errors.append(formatError(
+ false,
+ "Exception",
+ "general",
+ "Exception during compilation: " + boost::diagnostic_information(_exception)
+ ));
+ }
+ catch (...)
+ {
+ errors.append(formatError(
+ false,
+ "Exception",
+ "general",
+ "Unknown exception during compilation."
+ ));
+ }
+
+ Json::Value output = Json::objectValue;
+
+ if (errors.size() > 0)
+ output["errors"] = errors;
+
+ bool analysisSuccess = m_compilerStack.state() >= CompilerStack::State::AnalysisSuccessful;
+ bool compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful;
+
+ /// Inconsistent state - stop here to receive error reports from users
+ if (!compilationSuccess && (errors.size() == 0))
+ return formatFatalError("InternalCompilerError", "No error reported, but compilation failed.");
+
+ output["sources"] = Json::objectValue;
+ unsigned sourceIndex = 0;
+ for (auto const& source: analysisSuccess ? m_compilerStack.sourceNames() : vector<string>())
+ {
+ Json::Value sourceResult = Json::objectValue;
+ sourceResult["id"] = sourceIndex++;
+ sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(source));
+ sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(source));
+ output["sources"][source] = sourceResult;
+ }
+
+ Json::Value contractsOutput = Json::objectValue;
+ for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>())
+ {
+ size_t colon = contractName.find(':');
+ solAssert(colon != string::npos, "");
+ string file = contractName.substr(0, colon);
+ string name = contractName.substr(colon + 1);
+
+ // ABI, documentation and metadata
+ Json::Value contractData(Json::objectValue);
+ contractData["abi"] = m_compilerStack.contractABI(contractName);
+ contractData["metadata"] = m_compilerStack.onChainMetadata(contractName);
+ contractData["userdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecUser);
+ contractData["devdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecDev);
+
+ // EVM
+ Json::Value evmData(Json::objectValue);
+ // @TODO: add ir
+ ostringstream tmp;
+ m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), false);
+ evmData["assembly"] = tmp.str();
+ evmData["legacyAssembly"] = m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), true);
+ evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
+ evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
+
+ evmData["bytecode"] = collectEVMObject(
+ m_compilerStack.object(contractName),
+ m_compilerStack.sourceMapping(contractName)
+ );
+
+ evmData["deployedBytecode"] = collectEVMObject(
+ m_compilerStack.runtimeObject(contractName),
+ m_compilerStack.runtimeSourceMapping(contractName)
+ );
+
+ contractData["evm"] = evmData;
+
+ if (!contractsOutput.isMember(file))
+ contractsOutput[file] = Json::objectValue;
+
+ contractsOutput[file][name] = contractData;
+ }
+ output["contracts"] = contractsOutput;
+
+ return output;
+}
+
+Json::Value StandardCompiler::compile(Json::Value const& _input)
+{
+ try
+ {
+ return compileInternal(_input);
+ }
+ catch (Json::LogicError const& _exception)
+ {
+ return formatFatalError("InternalCompilerError", string("JSON logic exception: ") + _exception.what());
+ }
+ catch (Json::RuntimeError const& _exception)
+ {
+ return formatFatalError("InternalCompilerError", string("JSON runtime exception: ") + _exception.what());
+ }
+ catch (Exception const& _exception)
+ {
+ return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception));
+ }
+ catch (...)
+ {
+ return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal");
+ }
+}
+
+string StandardCompiler::compile(string const& _input)
+{
+ Json::Value input;
+ Json::Reader reader;
+
+ try
+ {
+ if (!reader.parse(_input, input, false))
+ return jsonCompactPrint(formatFatalError("JSONError", reader.getFormattedErrorMessages()));
+ }
+ catch(...)
+ {
+ return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}";
+ }
+
+ // cout << "Input: " << input.toStyledString() << endl;
+ Json::Value output = compile(input);
+ // cout << "Output: " << output.toStyledString() << endl;
+
+ try
+ {
+ return jsonCompactPrint(output);
+ }
+ catch(...)
+ {
+ return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}";
+ }
+}
diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h
new file mode 100644
index 00000000..dfaf88cd
--- /dev/null
+++ b/libsolidity/interface/StandardCompiler.h
@@ -0,0 +1,63 @@
+/*
+ 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 2016
+ * Standard JSON compiler interface.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/CompilerStack.h>
+
+namespace dev
+{
+
+namespace solidity
+{
+
+/**
+ * Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput.
+ * See docs/using-the-compiler#compiler-input-and-output-json-description.
+ */
+class StandardCompiler: boost::noncopyable
+{
+public:
+ /// Creates a new StandardCompiler.
+ /// @param _readFile callback to used to read files for import statements. Must return
+ /// and must not emit exceptions.
+ StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback())
+ : m_compilerStack(_readFile), m_readFile(_readFile)
+ {
+ }
+
+ /// Sets all input parameters according to @a _input which conforms to the standardized input
+ /// format, performs compilation and returns a standardized output.
+ Json::Value compile(Json::Value const& _input);
+ /// Parses input as JSON and peforms the above processing steps, returning a serialized JSON
+ /// output. Parsing errors are returned as regular errors.
+ std::string compile(std::string const& _input);
+
+private:
+ Json::Value compileInternal(Json::Value const& _input);
+
+ CompilerStack m_compilerStack;
+ ReadFile::Callback m_readFile;
+};
+
+}
+}
diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp
index ff66f039..a35bfd29 100644
--- a/libsolidity/interface/Version.cpp
+++ b/libsolidity/interface/Version.cpp
@@ -24,7 +24,7 @@
#include <string>
#include <libdevcore/CommonData.h>
#include <libdevcore/Common.h>
-#include <libsolidity/interface/Utils.h>
+#include <libsolidity/interface/Exceptions.h>
#include <solidity/BuildInfo.h>
using namespace dev;
@@ -38,6 +38,10 @@ string const dev::solidity::VersionString =
(string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) +
(string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO));
+string const dev::solidity::VersionStringStrict =
+ string(dev::solidity::VersionNumber) +
+ (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) +
+ (string(SOL_VERSION_COMMIT).empty() ? "" : "+" + string(SOL_VERSION_COMMIT));
bytes dev::solidity::binaryVersion()
{
diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h
index 5b07b3f4..24c3555d 100644
--- a/libsolidity/interface/Version.h
+++ b/libsolidity/interface/Version.h
@@ -32,6 +32,7 @@ namespace solidity
extern char const* VersionNumber;
extern std::string const VersionString;
+extern std::string const VersionStringStrict;
/// @returns a binary form of the version string, where A.B.C-HASH is encoded such that
/// the first byte is zero, the following three bytes encode A B and C (interpreted as decimals)
diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp
index bbee35f5..0409de72 100644
--- a/libsolidity/parsing/DocStringParser.cpp
+++ b/libsolidity/parsing/DocStringParser.cpp
@@ -1,14 +1,20 @@
#include <libsolidity/parsing/DocStringParser.h>
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libsolidity/interface/Exceptions.h>
+
#include <boost/range/irange.hpp>
-#include <libsolidity/interface/Utils.h>
+#include <boost/range/algorithm.hpp>
using namespace std;
using namespace dev;
using namespace dev::solidity;
-static inline string::const_iterator skipLineOrEOS(
+namespace
+{
+
+string::const_iterator skipLineOrEOS(
string::const_iterator _nlPos,
string::const_iterator _end
)
@@ -16,19 +22,39 @@ static inline string::const_iterator skipLineOrEOS(
return (_nlPos == _end) ? _end : ++_nlPos;
}
-static inline string::const_iterator firstSpaceOrNl(
+string::const_iterator firstSpaceOrTab(
string::const_iterator _pos,
string::const_iterator _end
)
{
- auto spacePos = find(_pos, _end, ' ');
- auto nlPos = find(_pos, _end, '\n');
- return (spacePos < nlPos) ? spacePos : nlPos;
+ return boost::range::find_first_of(make_pair(_pos, _end), " \t");
}
-bool DocStringParser::parse(string const& _docString, ErrorList& _errors)
+string::const_iterator firstWhitespaceOrNewline(
+ string::const_iterator _pos,
+ string::const_iterator _end
+)
{
- m_errors = &_errors;
+ return boost::range::find_first_of(make_pair(_pos, _end), " \t\n");
+}
+
+
+string::const_iterator skipWhitespace(
+ string::const_iterator _pos,
+ string::const_iterator _end
+)
+{
+ auto currPos = _pos;
+ while (currPos != _end && (*currPos == ' ' || *currPos == '\t'))
+ currPos += 1;
+ return currPos;
+}
+
+}
+
+bool DocStringParser::parse(string const& _docString, ErrorReporter& _errorReporter)
+{
+ m_errorReporter = &_errorReporter;
m_errorsOccurred = false;
m_lastTag = nullptr;
@@ -43,7 +69,7 @@ bool DocStringParser::parse(string const& _docString, ErrorList& _errors)
if (tagPos != end && tagPos < nlPos)
{
// we found a tag
- auto tagNameEndPos = firstSpaceOrNl(tagPos, end);
+ auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
if (tagNameEndPos == end)
{
appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found");
@@ -75,27 +101,40 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo
{
solAssert(!!m_lastTag, "");
auto nlPos = find(_pos, _end, '\n');
- if (_appending && _pos < _end && *_pos != ' ')
+ if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t')
m_lastTag->content += " ";
+ else if (!_appending)
+ _pos = skipWhitespace(_pos, _end);
copy(_pos, nlPos, back_inserter(m_lastTag->content));
return skipLineOrEOS(nlPos, _end);
}
DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end)
{
- // find param name
- auto currPos = find(_pos, _end, ' ');
- if (currPos == _end)
+ // find param name start
+ auto nameStartPos = skipWhitespace(_pos, _end);
+ if (nameStartPos == _end)
{
- appendError("End of param name not found" + string(_pos, _end));
+ appendError("No param name given");
return _end;
}
+ auto nameEndPos = firstSpaceOrTab(nameStartPos, _end);
+ if (nameEndPos == _end)
+ {
+ appendError("End of param name not found: " + string(nameStartPos, _end));
+ return _end;
+ }
+ auto paramName = string(nameStartPos, nameEndPos);
- auto paramName = string(_pos, currPos);
+ auto descStartPos = skipWhitespace(nameEndPos, _end);
+ if (descStartPos == _end)
+ {
+ appendError("No description given for param " + paramName);
+ return _end;
+ }
- currPos += 1;
- auto nlPos = find(currPos, _end, '\n');
- auto paramDesc = string(currPos, nlPos);
+ auto nlPos = find(descStartPos, _end, '\n');
+ auto paramDesc = string(descStartPos, nlPos);
newTag("param");
m_lastTag->paramName = paramName;
m_lastTag->content = paramDesc;
@@ -134,8 +173,6 @@ void DocStringParser::newTag(string const& _tagName)
void DocStringParser::appendError(string const& _description)
{
- auto err = make_shared<Error>(Error::Type::DocstringParsingError);
- *err << errinfo_comment(_description);
- m_errors->push_back(err);
m_errorsOccurred = true;
+ m_errorReporter->docstringParsingError(_description);
}
diff --git a/libsolidity/parsing/DocStringParser.h b/libsolidity/parsing/DocStringParser.h
index c7f81c55..5f2819cc 100644
--- a/libsolidity/parsing/DocStringParser.h
+++ b/libsolidity/parsing/DocStringParser.h
@@ -23,7 +23,6 @@
#pragma once
#include <string>
-#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/ASTAnnotations.h>
namespace dev
@@ -31,12 +30,14 @@ namespace dev
namespace solidity
{
+class ErrorReporter;
+
class DocStringParser
{
public:
/// Parse the given @a _docString and stores the parsed components internally.
/// @returns false on error and appends the error to @a _errors.
- bool parse(std::string const& _docString, ErrorList& _errors);
+ bool parse(std::string const& _docString, ErrorReporter& _errorReporter);
std::multimap<std::string, DocTag> const& tags() const { return m_docTags; }
@@ -62,7 +63,7 @@ private:
/// Mapping tag name -> content.
std::multimap<std::string, DocTag> m_docTags;
DocTag* m_lastTag = nullptr;
- ErrorList* m_errors = nullptr;
+ ErrorReporter* m_errorReporter = nullptr;
bool m_errorsOccurred = false;
};
diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp
index f02a4a45..b0cf364e 100644
--- a/libsolidity/parsing/Parser.cpp
+++ b/libsolidity/parsing/Parser.cpp
@@ -26,8 +26,7 @@
#include <libsolidity/parsing/Parser.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/inlineasm/AsmParser.h>
-#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/interface/InterfaceHandler.h>
+#include <libsolidity/interface/ErrorReporter.h>
using namespace std;
@@ -82,9 +81,10 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
case Token::Import:
nodes.push_back(parseImportDirective());
break;
+ case Token::Interface:
case Token::Contract:
case Token::Library:
- nodes.push_back(parseContractDefinition(token == Token::Library));
+ nodes.push_back(parseContractDefinition(token));
break;
default:
fatalParserError(string("Expected import directive or contract definition."));
@@ -94,7 +94,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
}
catch (FatalError const&)
{
- if (m_errors.empty())
+ if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return nullptr;
}
@@ -193,13 +193,30 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases));
}
-ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary)
+ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token)
+{
+ switch(_token)
+ {
+ case Token::Interface:
+ return ContractDefinition::ContractKind::Interface;
+ case Token::Contract:
+ return ContractDefinition::ContractKind::Contract;
+ case Token::Library:
+ return ContractDefinition::ContractKind::Library;
+ default:
+ fatalParserError("Unsupported contract type.");
+ }
+ // FIXME: fatalParserError is not considered as throwing here
+ return ContractDefinition::ContractKind::Contract;
+}
+
+ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind)
{
ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
- expectToken(_isLibrary ? Token::Library : Token::Contract);
+ expectToken(_expectedKind);
ASTPointer<ASTString> name = expectIdentifierToken();
vector<ASTPointer<InheritanceSpecifier>> baseContracts;
if (m_scanner->currentToken() == Token::Is)
@@ -252,7 +269,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary)
docString,
baseContracts,
subNodes,
- _isLibrary
+ tokenToContractKind(_expectedKind)
);
}
@@ -306,11 +323,17 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
Token::Value token = m_scanner->currentToken();
if (token == Token::Const)
{
+ if (result.isDeclaredConst)
+ parserError(string("Multiple \"constant\" specifiers."));
+
result.isDeclaredConst = true;
m_scanner->next();
}
else if (m_scanner->currentToken() == Token::Payable)
{
+ if (result.isPayable)
+ parserError(string("Multiple \"payable\" specifiers."));
+
result.isPayable = true;
m_scanner->next();
}
@@ -330,8 +353,12 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
else if (Token::isVisibilitySpecifier(token))
{
if (result.visibility != Declaration::Visibility::Default)
- fatalParserError(string("Multiple visibility specifiers."));
- result.visibility = parseVisibilitySpecifier(token);
+ {
+ parserError(string("Multiple visibility specifiers."));
+ m_scanner->next();
+ }
+ else
+ result.visibility = parseVisibilitySpecifier(token);
}
else
break;
@@ -484,8 +511,12 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token))
{
if (visibility != Declaration::Visibility::Default)
- fatalParserError(string("Visibility already specified."));
- visibility = parseVisibilitySpecifier(token);
+ {
+ parserError(string("Visibility already specified."));
+ m_scanner->next();
+ }
+ else
+ visibility = parseVisibilitySpecifier(token);
}
else
{
@@ -496,14 +527,15 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token))
{
if (location != VariableDeclaration::Location::Default)
- fatalParserError(string("Location already specified."));
- if (!type)
- fatalParserError(string("Location specifier needs explicit type name."));
- location = (
- token == Token::Memory ?
- VariableDeclaration::Location::Memory :
- VariableDeclaration::Location::Storage
- );
+ parserError(string("Location already specified."));
+ else if (!type)
+ parserError(string("Location specifier needs explicit type name."));
+ else
+ location = (
+ token == Token::Memory ?
+ VariableDeclaration::Location::Memory :
+ VariableDeclaration::Location::Storage
+ );
}
else
break;
@@ -685,7 +717,7 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
else if (token == Token::Var)
{
if (!_allowVar)
- fatalParserError(string("Expected explicit type name."));
+ parserError(string("Expected explicit type name."));
m_scanner->next();
}
else if (token == Token::Function)
@@ -848,7 +880,7 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con
m_scanner->next();
}
- assembly::Parser asmParser(m_errors);
+ assembly::Parser asmParser(m_errorReporter);
shared_ptr<assembly::Block> block = asmParser.parse(m_scanner);
nodeFactory.markEndPosition();
return nodeFactory.createNode<InlineAssembly>(_docString, block);
@@ -1153,9 +1185,9 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
else if (m_scanner->currentToken() == Token::New)
{
expectToken(Token::New);
- ASTPointer<TypeName> contractName(parseTypeName(false));
- nodeFactory.setEndPositionFromNode(contractName);
- expression = nodeFactory.createNode<NewExpression>(contractName);
+ ASTPointer<TypeName> typeName(parseTypeName(false));
+ nodeFactory.setEndPositionFromNode(typeName);
+ expression = nodeFactory.createNode<NewExpression>(typeName);
}
else
expression = parsePrimaryExpression();
@@ -1311,16 +1343,27 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
{
// call({arg1 : 1, arg2 : 2 })
expectToken(Token::LBrace);
+
+ bool first = true;
while (m_scanner->currentToken() != Token::RBrace)
{
+ if (!first)
+ expectToken(Token::Comma);
+
ret.second.push_back(expectIdentifierToken());
expectToken(Token::Colon);
ret.first.push_back(parseExpression());
- if (m_scanner->currentToken() == Token::Comma)
- expectToken(Token::Comma);
- else
- break;
+ if (
+ m_scanner->currentToken() == Token::Comma &&
+ m_scanner->peekNextToken() == Token::RBrace
+ )
+ {
+ parserError("Unexpected trailing comma.");
+ m_scanner->next();
+ }
+
+ first = false;
}
expectToken(Token::RBrace);
}
@@ -1420,5 +1463,49 @@ ASTPointer<ParameterList> Parser::createEmptyParameterList()
return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>());
}
+string Parser::currentTokenName()
+{
+ Token::Value token = m_scanner->currentToken();
+ if (Token::isElementaryTypeName(token)) //for the sake of accuracy in reporting
+ {
+ ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
+ return elemTypeName.toString();
+ }
+ else
+ return Token::name(token);
+}
+
+Token::Value Parser::expectAssignmentOperator()
+{
+ Token::Value op = m_scanner->currentToken();
+ if (!Token::isAssignmentOp(op))
+ fatalParserError(
+ string("Expected assignment operator, got '") +
+ currentTokenName() +
+ string("'")
+ );
+ m_scanner->next();
+ return op;
+}
+
+ASTPointer<ASTString> Parser::expectIdentifierToken()
+{
+ Token::Value id = m_scanner->currentToken();
+ if (id != Token::Identifier)
+ fatalParserError(
+ string("Expected identifier, got '") +
+ currentTokenName() +
+ string("'")
+ );
+ return getLiteralAndAdvance();
+}
+
+ASTPointer<ASTString> Parser::getLiteralAndAdvance()
+{
+ ASTPointer<ASTString> identifier = make_shared<ASTString>(m_scanner->currentLiteral());
+ m_scanner->next();
+ return identifier;
+}
+
}
}
diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h
index 79d1d1d4..19631c58 100644
--- a/libsolidity/parsing/Parser.h
+++ b/libsolidity/parsing/Parser.h
@@ -35,7 +35,7 @@ class Scanner;
class Parser: public ParserBase
{
public:
- Parser(ErrorList& _errors): ParserBase(_errors) {}
+ Parser(ErrorReporter& _errorReporter): ParserBase(_errorReporter) {}
ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner);
@@ -69,7 +69,8 @@ private:
///@name Parsing functions for the AST nodes
ASTPointer<PragmaDirective> parsePragmaDirective();
ASTPointer<ImportDirective> parseImportDirective();
- ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary);
+ ContractDefinition::ContractKind tokenToContractKind(Token::Value _token);
+ ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind);
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
Declaration::Visibility parseVisibilitySpecifier(Token::Value _token);
FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers);
@@ -153,6 +154,11 @@ private:
std::vector<ASTPointer<PrimaryExpression>> const& _path,
std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices
);
+
+ std::string currentTokenName();
+ Token::Value expectAssignmentOperator();
+ ASTPointer<ASTString> expectIdentifierToken();
+ ASTPointer<ASTString> getLiteralAndAdvance();
///@}
/// Creates an empty ParameterList at the current location (used if parameters can be omitted).
diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp
index 87d47f4b..5657c2c0 100644
--- a/libsolidity/parsing/ParserBase.cpp
+++ b/libsolidity/parsing/ParserBase.cpp
@@ -22,6 +22,7 @@
#include <libsolidity/parsing/ParserBase.h>
#include <libsolidity/parsing/Scanner.h>
+#include <libsolidity/interface/ErrorReporter.h>
using namespace std;
using namespace dev;
@@ -42,6 +43,26 @@ int ParserBase::endPosition() const
return m_scanner->currentLocation().end;
}
+Token::Value ParserBase::currentToken() const
+{
+ return m_scanner->currentToken();
+}
+
+Token::Value ParserBase::peekNextToken() const
+{
+ return m_scanner->peekNextToken();
+}
+
+std::string ParserBase::currentLiteral() const
+{
+ return m_scanner->currentLiteral();
+}
+
+Token::Value ParserBase::advance()
+{
+ return m_scanner->next();
+}
+
void ParserBase::expectToken(Token::Value _value)
{
Token::Value tok = m_scanner->currentToken();
@@ -80,74 +101,12 @@ void ParserBase::expectToken(Token::Value _value)
m_scanner->next();
}
-Token::Value ParserBase::expectAssignmentOperator()
-{
- Token::Value op = m_scanner->currentToken();
- if (!Token::isAssignmentOp(op))
- {
- if (Token::isElementaryTypeName(op)) //for the sake of accuracy in reporting
- {
- ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
- fatalParserError(
- string("Expected assignment operator, got '") +
- elemTypeName.toString() +
- string("'")
- );
- }
- else
- fatalParserError(
- string("Expected assignment operator, got '") +
- string(Token::name(m_scanner->currentToken())) +
- string("'")
- );
- }
- m_scanner->next();
- return op;
-}
-
-ASTPointer<ASTString> ParserBase::expectIdentifierToken()
-{
- Token::Value id = m_scanner->currentToken();
- if (id != Token::Identifier)
- {
- if (Token::isElementaryTypeName(id)) //for the sake of accuracy in reporting
- {
- ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
- fatalParserError(
- string("Expected identifier, got '") +
- elemTypeName.toString() +
- string("'")
- );
- }
- else
- fatalParserError(
- string("Expected identifier, got '") +
- string(Token::name(id)) +
- string("'")
- );
- }
- return getLiteralAndAdvance();
-}
-
-ASTPointer<ASTString> ParserBase::getLiteralAndAdvance()
-{
- ASTPointer<ASTString> identifier = make_shared<ASTString>(m_scanner->currentLiteral());
- m_scanner->next();
- return identifier;
-}
-
void ParserBase::parserError(string const& _description)
{
- auto err = make_shared<Error>(Error::Type::ParserError);
- *err <<
- errinfo_sourceLocation(SourceLocation(position(), position(), sourceName())) <<
- errinfo_comment(_description);
-
- m_errors.push_back(err);
+ m_errorReporter.parserError(SourceLocation(position(), position(), sourceName()), _description);
}
void ParserBase::fatalParserError(string const& _description)
{
- parserError(_description);
- BOOST_THROW_EXCEPTION(FatalError());
+ m_errorReporter.fatalParserError(SourceLocation(position(), position(), sourceName()), _description);
}
diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h
index dfb7cab7..5b03ab5e 100644
--- a/libsolidity/parsing/ParserBase.h
+++ b/libsolidity/parsing/ParserBase.h
@@ -23,21 +23,20 @@
#pragma once
#include <memory>
-#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/parsing/Token.h>
-#include <libsolidity/ast/ASTForward.h>
namespace dev
{
namespace solidity
{
+class ErrorReporter;
class Scanner;
class ParserBase
{
public:
- ParserBase(ErrorList& errors): m_errors(errors) {}
+ ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {}
std::shared_ptr<std::string const> const& sourceName() const;
@@ -47,14 +46,14 @@ protected:
/// End position of the current token
int endPosition() const;
-
///@{
///@name Helper functions
/// If current token value is not _value, throw exception otherwise advance token.
void expectToken(Token::Value _value);
- Token::Value expectAssignmentOperator();
- ASTPointer<ASTString> expectIdentifierToken();
- ASTPointer<ASTString> getLiteralAndAdvance();
+ Token::Value currentToken() const;
+ Token::Value peekNextToken() const;
+ std::string currentLiteral() const;
+ Token::Value advance();
///@}
/// Creates a @ref ParserError and annotates it with the current position and the
@@ -67,7 +66,7 @@ protected:
std::shared_ptr<Scanner> m_scanner;
/// The reference to the list of errors and warning to add errors/warnings during parsing
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
};
}
diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp
index 3623f23f..fdca23ea 100644
--- a/libsolidity/parsing/Scanner.cpp
+++ b/libsolidity/parsing/Scanner.cpp
@@ -52,7 +52,7 @@
#include <algorithm>
#include <tuple>
-#include <libsolidity/interface/Utils.h>
+#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/parsing/Scanner.h>
using namespace std;
@@ -758,6 +758,9 @@ Token::Value Scanner::scanNumber(char _charSeen)
while (isHexDigit(m_char))
addLiteralCharAndAdvance();
}
+ else if (isDecimalDigit(m_char))
+ // We do not allow octal numbers
+ return Token::Illegal;
}
// Parse decimal digits and allow trailing fractional part.
if (kind == DECIMAL)
diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h
index 3ce0f424..39c0eff9 100644
--- a/libsolidity/parsing/Token.h
+++ b/libsolidity/parsing/Token.h
@@ -43,7 +43,6 @@
#pragma once
#include <libdevcore/Common.h>
-#include <libsolidity/interface/Utils.h>
#include <libsolidity/interface/Exceptions.h>
#include <libdevcore/UndefMacros.h>
@@ -147,7 +146,6 @@ namespace solidity
K(Const, "constant", 0) \
K(Continue, "continue", 0) \
K(Contract, "contract", 0) \
- K(Default, "default", 0) \
K(Do, "do", 0) \
K(Else, "else", 0) \
K(Enum, "enum", 0) \
@@ -158,6 +156,7 @@ namespace solidity
K(Hex, "hex", 0) \
K(If, "if", 0) \
K(Indexed, "indexed", 0) \
+ K(Interface, "interface", 0) \
K(Internal, "internal", 0) \
K(Import, "import", 0) \
K(Is, "is", 0) \
@@ -208,7 +207,6 @@ namespace solidity
T(TypesEnd, NULL, 0) /* used as type enum end marker */ \
\
/* Literals */ \
- K(NullLiteral, "null", 0) \
K(TrueLiteral, "true", 0) \
K(FalseLiteral, "false", 0) \
T(Number, NULL, 0) \
@@ -223,12 +221,13 @@ namespace solidity
K(After, "after", 0) \
K(Case, "case", 0) \
K(Catch, "catch", 0) \
+ K(Default, "default", 0) \
K(Final, "final", 0) \
K(In, "in", 0) \
K(Inline, "inline", 0) \
- K(Interface, "interface", 0) \
K(Let, "let", 0) \
K(Match, "match", 0) \
+ K(NullLiteral, "null", 0) \
K(Of, "of", 0) \
K(Pure, "pure", 0) \
K(Relocatable, "relocatable", 0) \
diff --git a/lllc/main.cpp b/lllc/main.cpp
index 9763e820..adf181c7 100644
--- a/lllc/main.cpp
+++ b/lllc/main.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file main.cpp
* @author Gav Wood <i@gavwood.com>
diff --git a/scripts/Dockerfile b/scripts/Dockerfile
index e34436ed..c984ce99 100644
--- a/scripts/Dockerfile
+++ b/scripts/Dockerfile
@@ -1,12 +1,18 @@
FROM alpine
MAINTAINER chriseth <chris@ethereum.org>
+#Official solidity docker image
-RUN \
- apk --no-cache --update add build-base cmake boost-dev git && \
- sed -i -E -e 's/include <sys\/poll.h>/include <poll.h>/' /usr/include/boost/asio/detail/socket_types.hpp && \
- git clone --depth 1 --recursive -b develop https://github.com/ethereum/solidity && \
- cd /solidity && cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSTATIC_LINKING=1 && \
- cd /solidity && make solc && install -s solc/solc /usr/bin && \
- cd / && rm -rf solidity && \
- apk del sed build-base git make cmake gcc g++ musl-dev curl-dev boost-dev && \
- rm -rf /var/cache/apk/*
+#Establish working directory as solidity
+WORKDIR /solidity
+#Copy working directory on travis to the image
+COPY / $WORKDIR
+
+#Install dependencies, eliminate annoying warnings, and build release, delete all remaining points and statically link.
+RUN ./scripts/install_deps.sh && sed -i -E -e 's/include <sys\/poll.h>/include <poll.h>/' /usr/include/boost/asio/detail/socket_types.hpp &&\
+cmake -DCMAKE_BUILD_TYPE=Release -DTESTS=0 -DSTATIC_LINKING=1 &&\
+make solc && install -s solc/solc /usr/bin &&\
+cd / && rm -rf solidity &&\
+apk del sed build-base git make cmake gcc g++ musl-dev curl-dev boost-dev &&\
+rm -rf /var/cache/apk/*
+
+ENTRYPOINT ["/usr/bin/solc"] \ No newline at end of file
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 00000000..3785e1c1
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+if [ -z "$1" ]; then
+ BUILD_TYPE=Release
+else
+ BUILD_TYPE="$1"
+fi
+
+cd $(dirname "$0")/.. &&
+mkdir -p build &&
+cd build &&
+cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" &&
+make -j2
+
+if [ $? -ne 0 ]; then
+ echo "Failed to build"
+ exit 1
+fi
+
+if [ -z $CI ]; then
+ echo "Installing solc and soltest"
+ install solc/solc /usr/local/bin && install test/soltest /usr/local/bin
+fi \ No newline at end of file
diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh
index 9b432e95..6046978e 100755
--- a/scripts/build_emscripten.sh
+++ b/scripts/build_emscripten.sh
@@ -29,12 +29,6 @@
set -e
if [[ "$OSTYPE" != "darwin"* ]]; then
- if [ "$TRAVIS_BRANCH" = release ]
- then
- echo -n > prerelease.txt
- else
- date -u +"nightly.%Y.%-m.%-d" > prerelease.txt
- fi
./scripts/travis-emscripten/install_deps.sh
docker run -v $(pwd):/src trzeci/emscripten:sdk-tag-1.35.4-64bit ./scripts/travis-emscripten/build_emscripten.sh
fi
diff --git a/scripts/bytecodecompare/deploy_key.enc b/scripts/bytecodecompare/deploy_key.enc
new file mode 100644
index 00000000..acab2a27
--- /dev/null
+++ b/scripts/bytecodecompare/deploy_key.enc
Binary files differ
diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py
new file mode 100755
index 00000000..427724b7
--- /dev/null
+++ b/scripts/bytecodecompare/prepare_report.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+import sys
+import glob
+import subprocess
+import json
+
+solc = sys.argv[1]
+report = open("report.txt", "wb")
+
+for optimize in [False, True]:
+ for f in sorted(glob.glob("*.sol")):
+ args = [solc, '--combined-json', 'bin,metadata', f]
+ if optimize:
+ args += ['--optimize']
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (out, err) = proc.communicate()
+ try:
+ result = json.loads(out.strip())
+ for contractName in sorted(result['contracts'].keys()):
+ report.write(contractName + ' ' + result['contracts'][contractName]['bin'] + '\n')
+ report.write(contractName + ' ' + result['contracts'][contractName]['metadata'] + '\n')
+ except:
+ report.write(f + ": ERROR\n")
diff --git a/scripts/bytecodecompare/storebytecode.bat b/scripts/bytecodecompare/storebytecode.bat
new file mode 100644
index 00000000..e64e9276
--- /dev/null
+++ b/scripts/bytecodecompare/storebytecode.bat
@@ -0,0 +1,42 @@
+@ECHO OFF
+
+REM ---------------------------------------------------------------------------
+REM This file is part of solidity.
+REM
+REM solidity is free software: you can redistribute it and/or modify
+REM it under the terms of the GNU General Public License as published by
+REM the Free Software Foundation, either version 3 of the License, or
+REM (at your option) any later version.
+REM
+REM solidity is distributed in the hope that it will be useful,
+REM but WITHOUT ANY WARRANTY; without even the implied warranty of
+REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+REM GNU General Public License for more details.
+REM
+REM You should have received a copy of the GNU General Public License
+REM along with solidity. If not, see <http://www.gnu.org/licenses/>
+REM
+REM Copyright (c) 2017 solidity contributors.
+REM ---------------------------------------------------------------------------
+
+set CONFIGURATION=%1
+set DIRECTORY=%2
+
+mkdir bytecode
+cd bytecode
+..\scripts\isolate_tests.py ..\test\
+..\scripts\bytecodecompare\prepare_report.py ..\build\solc\%CONFIGURATION%\solc.exe
+
+REM Send to stdout instead of stderr to not confuse powershell
+git clone --depth 2 git@github.com:ethereum/solidity-test-bytecode.git 2>&1
+cd solidity-test-bytecode
+git config user.name "travis"
+git config user.email "chris@ethereum.org"
+git clean -f -d -x 2>&1
+
+if not exist %DIRECTORY% mkdir %DIRECTORY%
+set REPORT=%DIRECTORY%/windows.txt
+cp ../report.txt %REPORT%
+git add %REPORT%
+git commit -a -m "Added report."
+git push origin 2>&1
diff --git a/scripts/bytecodecompare/storebytecode.sh b/scripts/bytecodecompare/storebytecode.sh
new file mode 100755
index 00000000..8d4100bf
--- /dev/null
+++ b/scripts/bytecodecompare/storebytecode.sh
@@ -0,0 +1,110 @@
+#!/usr/bin/env bash
+
+#------------------------------------------------------------------------------
+# Script used for cross-platform comparison as part of the travis automation.
+# Splits all test source code into multiple files, generates bytecode and
+# uploads the bytecode into github.com/ethereum/solidity-test-bytecode where
+# another travis job is triggered to do the actual comparison.
+#
+# ------------------------------------------------------------------------------
+# 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/>
+#
+# (c) 2017 solidity contributors.
+#------------------------------------------------------------------------------
+
+set -e
+
+REPO_ROOT="$(dirname "$0")"/../..
+
+echo "Compiling all test contracts into bytecode..."
+TMPDIR=$(mktemp -d)
+(
+ cd "$REPO_ROOT"
+ REPO_ROOT=$(pwd) # make it absolute
+ cd "$TMPDIR"
+
+ "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/
+
+ if [[ "$SOLC_EMSCRIPTEN" = "On" ]]
+ then
+ cp "$REPO_ROOT/build/solc/soljson.js" .
+ npm install solc
+ cat > solc <<EOF
+#!/usr/bin/env node
+var process = require('process')
+var fs = require('fs')
+
+var compiler = require('solc/wrapper.js')(require('./soljson.js'))
+
+for (var optimize of [false, true])
+{
+ for (var filename of process.argv.slice(2))
+ {
+ if (filename !== undefined)
+ {
+ var inputs = {}
+ inputs[filename] = fs.readFileSync(filename).toString()
+ var result = compiler.compile({sources: inputs}, optimize)
+ if (!('contracts' in result) || Object.keys(result['contracts']).length === 0)
+ {
+ console.log(filename + ': ERROR')
+ }
+ else
+ {
+ for (var contractName in result['contracts'])
+ {
+ console.log(contractName + ' ' + result['contracts'][contractName].bytecode)
+ console.log(contractName + ' ' + result['contracts'][contractName].metadata)
+ }
+ }
+ }
+ }
+}
+EOF
+ chmod +x solc
+ ./solc *.sol > report.txt
+ else
+ $REPO_ROOT/scripts/bytecodecompare/prepare_report.py $REPO_ROOT/build/solc/solc
+ fi
+
+ if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]
+ then
+ openssl aes-256-cbc -K $encrypted_60701c962b9c_key -iv $encrypted_60701c962b9c_iv -in "$REPO_ROOT"/scripts/bytecodecompare/deploy_key.enc -out deploy_key -d
+ chmod 600 deploy_key
+ eval `ssh-agent -s`
+ ssh-add deploy_key
+
+ git clone --depth 2 git@github.com:ethereum/solidity-test-bytecode.git
+ cd solidity-test-bytecode
+ git config user.name "travis"
+ git config user.email "chris@ethereum.org"
+ git clean -f -d -x
+
+ DIRNAME=$(cd "$REPO_ROOT" && git show -s --format="%cd-%H" --date=short)
+ mkdir -p "$DIRNAME"
+ REPORT="$DIRNAME/$ZIP_SUFFIX.txt"
+ cp ../report.txt "$REPORT"
+ # Only push if adding actually worked, i.e. there were changes.
+ if git add "$REPORT" && git commit -a -m "Added report $REPORT"
+ then
+ git pull --rebase
+ git push origin
+ else
+ echo "Adding report failed, it might already exist in the repository."
+ fi
+ fi
+)
+rm -rf "$TMPDIR"
diff --git a/scripts/create_source_tarball.sh b/scripts/create_source_tarball.sh
new file mode 100755
index 00000000..9e66799a
--- /dev/null
+++ b/scripts/create_source_tarball.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env sh
+#
+
+set -e
+
+REPO_ROOT="$(dirname "$0")"/..
+(
+ cd "$REPO_ROOT"
+ version=$(scripts/get_version.sh)
+ commithash=$(git rev-parse --short=8 HEAD)
+ commitdate=$(git show --format=%ci HEAD | head -n 1 | cut - -b1-10 | sed -e 's/-0?/./' | sed -e 's/-0?/./')
+
+ # file exists and has zero size -> not a prerelease
+ if [ -e prerelease.txt -a ! -s prerelease.txt ]
+ then
+ versionstring="$version"
+ else
+ versionstring="$version-nightly-$commitdate-$commithash"
+ fi
+
+ TEMPDIR=$(mktemp -d)
+ SOLDIR="$TEMPDIR/solidity_$versionstring/"
+ mkdir "$SOLDIR"
+ # Store the current source
+ git checkout-index -a --prefix="$SOLDIR"
+ git submodule foreach 'git checkout-index -a --prefix="'"$SOLDIR"'/$path/"'
+ # Store the commit hash
+ echo "$commithash" > "$SOLDIR/commit_hash.txt"
+ if [ -e prerelease.txt -a ! -s prerelease.txt ]
+ then
+ cp prerelease.txt "$SOLDIR/"
+ fi
+ # Add dependencies
+ mkdir -p "$SOLDIR/deps/downloads/" 2>/dev/null || true
+ wget -O "$SOLDIR/deps/downloads/jsoncpp-1.7.7.tar.gz" https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz
+ mkdir -p "$REPO_ROOT/upload"
+ tar czf "$REPO_ROOT/upload/solidity_$versionstring.tar.gz" -C "$TEMPDIR" "solidity_$versionstring"
+ rm -r "$TEMPDIR"
+)
diff --git a/scripts/docker_build.sh b/scripts/docker_build.sh
new file mode 100755
index 00000000..22657a8c
--- /dev/null
+++ b/scripts/docker_build.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+set -e
+
+docker build -t ethereum/solc:build -f scripts/Dockerfile .
+tmp_container=$(docker create ethereum/solc:build sh)
+mkdir -p upload
+docker cp ${tmp_container}:/usr/bin/solc upload/solc-static-linux
diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh
new file mode 100755
index 00000000..00705725
--- /dev/null
+++ b/scripts/docker_deploy.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env sh
+
+set -e
+
+docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
+version=$($(dirname "$0")/get_version.sh)
+if [ "$TRAVIS_BRANCH" = "develop" ]
+then
+ docker tag ethereum/solc:build ethereum/solc:nightly;
+ docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT"
+ docker push ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT";
+ docker push ethereum/solc:nightly;
+elif [ "$TRAVIS_TAG" = v"$version" ]
+then
+ docker tag ethereum/solc:build ethereum/solc:stable;
+ docker tag ethereum/solc:build ethereum/solc:"$version";
+ docker push ethereum/solc:stable;
+ docker push ethereum/solc:"$version";
+else
+ echo "Not publishing docker image from branch $TRAVIS_BRANCH or tag $TRAVIS_TAG"
+fi
diff --git a/scripts/get_version.sh b/scripts/get_version.sh
new file mode 100755
index 00000000..3df2b4c4
--- /dev/null
+++ b/scripts/get_version.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+#------------------------------------------------------------------------------
+# Bash script to execute the Solidity tests.
+#
+# The documentation for solidity is hosted at:
+#
+# https://solidity.readthedocs.org
+#
+# ------------------------------------------------------------------------------
+# 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/>
+#
+# (c) 2017 solidity contributors.
+#------------------------------------------------------------------------------
+
+set -e
+
+grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/../CMakeLists.txt
diff --git a/scripts/install_cmake.sh b/scripts/install_cmake.sh
new file mode 100755
index 00000000..00d013b9
--- /dev/null
+++ b/scripts/install_cmake.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env sh
+
+# This script downloads the CMake binary and installs it in ~/.local directory
+# (the cmake executable will be in ~/.local/bin).
+# This is mostly suitable for CIs, not end users.
+
+set -e
+
+VERSION=3.7.1
+PREFIX=~/.local
+
+OS=$(uname -s)
+case $OS in
+Linux) SHA256=7b4b7a1d9f314f45722899c0521c261e4bfab4a6b532609e37fef391da6bade2;;
+Darwin) SHA256=1851d1448964893fdc5a8c05863326119f397a3790e0c84c40b83499c7960267;;
+esac
+
+
+BIN=$PREFIX/bin
+
+if test -f $BIN/cmake && ($BIN/cmake --version | grep -q "$VERSION"); then
+ echo "CMake $VERSION already installed in $BIN"
+else
+ FILE=cmake-$VERSION-$OS-x86_64.tar.gz
+ URL=https://cmake.org/files/v3.7/$FILE
+ ERROR=0
+ TMPFILE=$(mktemp --tmpdir cmake-$VERSION-$OS-x86_64.XXXXXXXX.tar.gz)
+ echo "Downloading CMake ($URL)..."
+ wget "$URL" -O "$TMPFILE" -nv
+ if ! (shasum -a256 "$TMPFILE" | grep -q "$SHA256"); then
+ echo "Checksum mismatch ($TMPFILE)"
+ exit 1
+ fi
+ mkdir -p "$PREFIX"
+ tar xzf "$TMPFILE" -C "$PREFIX" --strip 1
+ rm $TMPFILE
+fi
diff --git a/scripts/install_deps.bat b/scripts/install_deps.bat
index bd68b07a..512a28df 100644
--- a/scripts/install_deps.bat
+++ b/scripts/install_deps.bat
@@ -59,4 +59,3 @@ REM Copyright (c) 2016 solidity contributors.
REM ---------------------------------------------------------------------------
cmake -P deps\install_deps.cmake
-cmake -P scripts\install_eth.cmake
diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh
index c9f82769..24cf49d5 100755
--- a/scripts/install_deps.sh
+++ b/scripts/install_deps.sh
@@ -57,7 +57,7 @@ detect_linux_distro() {
# extract 'foo' from NAME=foo, only on the line with NAME=foo
DISTRO=$(sed -n -e 's/^NAME="\(.*\)\"/\1/p' /etc/os-release)
elif [ -f /etc/centos-release ]; then
- DISTRO=CentOS
+ DISTRO=CentOS
else
DISTRO=''
fi
@@ -83,12 +83,6 @@ case $(uname -s) in
;;
10.12)
echo "Installing solidity dependencies on macOS 10.12 Sierra."
- echo ""
- echo "NOTE - You are in unknown territory with this preview OS."
- echo "Even Homebrew doesn't have official support yet, and there are"
- echo "known issues (see https://github.com/ethereum/webthree-umbrella/issues/614)."
- echo "If you would like to partner with us to work through these issues, that"
- echo "would be fantastic. Please just comment on that issue. Thanks!"
;;
*)
echo "Unsupported macOS version."
@@ -99,19 +93,17 @@ case $(uname -s) in
# Check for Homebrew install and abort if it is not installed.
brew -v > /dev/null 2>&1 || { echo >&2 "ERROR - solidity requires a Homebrew install. See http://brew.sh."; exit 1; }
-
brew update
- brew upgrade
-
brew install boost
brew install cmake
-
- # We should really 'brew install' our eth client here, but at the time of writing
- # the bottle is known broken, so we will just cheat and use a hardcoded ZIP for
- # the time being, which is good enough. The cause of the breaks will go away
- # when we commit the repository reorg changes anyway.
- curl -L -O https://github.com/bobsummerwill/cpp-ethereum/releases/download/v1.3.0/cpp-ethereum-osx-mavericks-v1.3.0.zip
- unzip cpp-ethereum-osx-mavericks-v1.3.0.zip
+ if [ "$CI" = true ]; then
+ brew upgrade cmake
+ brew tap ethereum/ethereum
+ brew install cpp-ethereum
+ brew linkapps cpp-ethereum
+ else
+ brew upgrade
+ fi
;;
@@ -144,11 +136,13 @@ case $(uname -s) in
# All our dependencies can be found in the Arch Linux official repositories.
# See https://wiki.archlinux.org/index.php/Official_repositories
+ # Also adding ethereum-git to allow for testing with the `eth` client
sudo pacman -Sy \
base-devel \
boost \
cmake \
git \
+ ethereum-git \
;;
#------------------------------------------------------------------------------
@@ -211,7 +205,6 @@ case $(uname -s) in
# Install "normal packages"
sudo apt-get -y update
sudo apt-get -y install \
- python-sphinx \
build-essential \
cmake \
g++ \
@@ -315,18 +308,18 @@ case $(uname -s) in
sudo apt-get -y update
sudo apt-get -y install \
- python-sphinx \
build-essential \
cmake \
git \
libboost-all-dev
-
- # Install 'eth', for use in the Solidity Tests-over-IPC.
- sudo add-apt-repository -y ppa:ethereum/ethereum
- sudo add-apt-repository -y ppa:ethereum/ethereum-dev
- sudo apt-get -y update
- sudo apt-get -y install eth
-
+ if [ "$CI" = true ]; then
+ # Install 'eth', for use in the Solidity Tests-over-IPC.
+ # We will not use this 'eth', but its dependencies
+ sudo add-apt-repository -y ppa:ethereum/ethereum
+ sudo add-apt-repository -y ppa:ethereum/ethereum-dev
+ sudo apt-get -y update
+ sudo apt-get -y install eth
+ fi
;;
#------------------------------------------------------------------------------
diff --git a/scripts/install_eth.cmake b/scripts/install_eth.cmake
deleted file mode 100644
index 25f449e0..00000000
--- a/scripts/install_eth.cmake
+++ /dev/null
@@ -1,76 +0,0 @@
-#------------------------------------------------------------------------------
-# Cmake script for installing pre-requisite package eth for solidity.
-#
-# The aim of this script is to simply download and unpack eth binaries to the deps folder.
-#
-# The documentation for solidity is hosted at:
-#
-# http://solidity.readthedocs.io/
-#
-# ------------------------------------------------------------------------------
-# 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/>
-#
-# (c) 2016 solidity contributors.
-#------------------------------------------------------------------------------
-
-function(download URL DST_FILE STATUS)
- set(TMP_FILE "${DST_FILE}.part")
-
- get_filename_component(FILE_NAME ${DST_FILE} NAME)
- if (NOT EXISTS ${DST_FILE})
- message("Downloading ${FILE_NAME}")
- file(DOWNLOAD ${URL} ${TMP_FILE} SHOW_PROGRESS STATUS DOWNLOAD_STATUS)
- list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
- if (STATUS_CODE EQUAL 0)
- file(RENAME ${TMP_FILE} ${DST_FILE})
- else()
- file(REMOVE ${TMP_FILE})
- list(GET DOWNLOAD_STATUS 1 ERROR_MSG)
-
- message("ERROR! Downloading '${FILE_NAME}' failed.")
- message(STATUS "URL: ${URL}")
- message(STATUS "Error: ${STATUS_CODE} ${ERROR_MSG}")
- set(STATUS FALSE PARENT_SCOPE)
- return()
- endif()
- else()
- message("Using cached ${FILE_NAME}")
- endif()
- set(STATUS TRUE PARENT_SCOPE)
-endfunction(download)
-
-function(download_and_unpack PACKAGE_URL DST_DIR)
- get_filename_component(FILE_NAME ${PACKAGE_URL} NAME)
-
- set(DST_FILE "${CACHE_DIR}/${FILE_NAME}")
- set(TMP_FILE "${DST_FILE}.part")
-
- file(MAKE_DIRECTORY ${CACHE_DIR})
- file(MAKE_DIRECTORY ${DST_DIR})
-
- download(${PACKAGE_URL} ${DST_FILE} STATUS)
-
- if (STATUS)
- message("Unpacking ${FILE_NAME} to ${DST_DIR}")
- execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${DST_FILE}
- WORKING_DIRECTORY ${DST_DIR})
- endif()
-endfunction(download_and_unpack)
-
-get_filename_component(ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
-set(CACHE_DIR "${ROOT_DIR}/deps/cache")
-set(INSTALL_DIR "${ROOT_DIR}/deps/install/x64/eth")
-download_and_unpack("https://github.com/bobsummerwill/cpp-ethereum/releases/download/develop-v1.3.0.401/cpp-ethereum-develop-windows.zip" ${INSTALL_DIR})
diff --git a/scripts/isolateTests.py b/scripts/isolateTests.py
deleted file mode 100755
index fed779d3..00000000
--- a/scripts/isolateTests.py
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/python
-#
-# This script reads C++ source files and writes all
-# multi-line strings into individual files.
-# This can be used to extract the Solidity test cases
-# into files for e.g. fuzz testing as
-# scripts/isolateTests.py tests/libsolidity/SolidityEndToEndTest.cpp
-
-import sys
-lines = sys.stdin.read().split('\n')
-inside = False
-tests = []
-for l in lines:
- if inside:
- if l.strip().endswith(')";'):
- inside = False
- else:
- tests[-1] += l + '\n'
- else:
- if l.strip().endswith('R"('):
- inside = True
- tests += ['']
-for i in range(len(tests)):
- open('test%d.sol' % i, 'w').write(tests[i])
diff --git a/scripts/isolate_tests.py b/scripts/isolate_tests.py
new file mode 100755
index 00000000..a1d1c75c
--- /dev/null
+++ b/scripts/isolate_tests.py
@@ -0,0 +1,48 @@
+#!/usr/bin/python
+#
+# This script reads C++ source files and writes all
+# multi-line strings into individual files.
+# This can be used to extract the Solidity test cases
+# into files for e.g. fuzz testing as
+# scripts/isolate_tests.py test/libsolidity/*
+
+import sys
+import re
+import os
+import hashlib
+from os.path import join
+
+def extract_cases(path):
+ lines = open(path, 'rb').read().splitlines()
+
+ inside = False
+ delimiter = ''
+ tests = []
+
+ for l in lines:
+ if inside:
+ if l.strip().endswith(')' + delimiter + '";'):
+ inside = False
+ else:
+ tests[-1] += l + '\n'
+ else:
+ m = re.search(r'R"([^(]*)\($', l.strip())
+ if m:
+ inside = True
+ delimiter = m.group(1)
+ tests += ['']
+
+ return tests
+
+
+def write_cases(tests):
+ for test in tests:
+ open('test_%s.sol' % hashlib.sha256(test).hexdigest(), 'wb').write(test)
+
+if __name__ == '__main__':
+ path = sys.argv[1]
+
+ for root, dir, files in os.walk(path):
+ for f in files:
+ cases = extract_cases(join(root, f))
+ write_cases(cases)
diff --git a/scripts/release.sh b/scripts/release.sh
index e9f43f6c..a2f4d98a 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -88,5 +88,5 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
fi
# And ZIP it all up, with a filename suffix passed in on the command-line.
-
-zip -j $REPO_ROOT/solidity-$ZIP_SUFFIX.zip $ZIP_TEMP_DIR/*
+mkdir -p $REPO_ROOT/upload
+zip -j $REPO_ROOT/upload/solidity-$ZIP_SUFFIX.zip $ZIP_TEMP_DIR/*
diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh
index 86288c47..4fae90ef 100755
--- a/scripts/release_ppa.sh
+++ b/scripts/release_ppa.sh
@@ -15,6 +15,21 @@
## It will clone the Solidity git from github, determine the version,
## create a source archive and push it to the ubuntu ppa servers.
##
+## This requires the following entries in /etc/dput.cf:
+##
+## [ethereum-dev]
+## fqdn = ppa.launchpad.net
+## method = ftp
+## incoming = ~ethereum/ethereum-dev
+## login = anonymous
+##
+## [ethereum]
+## fqdn = ppa.launchpad.net
+## method = ftp
+## incoming = ~ethereum/ethereum
+## login = anonymous
+
+##
##############################################################################
set -ev
@@ -28,10 +43,10 @@ fi
if [ "$branch" = develop ]
then
- pparepo=ethereum/ethereum-dev
+ pparepo=ethereum-dev
ppafilesurl=https://launchpad.net/~ethereum/+archive/ubuntu/ethereum-dev/+files
else
- pparepo=ethereum/ethereum
+ pparepo=ethereum
ppafilesurl=https://launchpad.net/~ethereum/+archive/ubuntu/ethereum/+files
fi
@@ -39,7 +54,7 @@ keyid=703F83D0
email=builds@ethereum.org
packagename=solc
-for distribution in trusty vivid wily xenial yakkety
+for distribution in trusty vivid xenial yakkety zesty
do
cd /tmp/
mkdir $distribution
@@ -55,10 +70,10 @@ wget -O ./solc/deps/downloads/jsoncpp-1.7.7.tar.gz https://github.com/open-sourc
# Determine version
cd solc
-version=`grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? CMakeLists.txt`
-commithash=`git rev-parse --short=8 HEAD`
-committimestamp=`git show --format=%ci HEAD | head -n 1`
-commitdate=`git show --format=%ci HEAD | head -n 1 | cut - -b1-10 | sed -e 's/-0?/./' | sed -e 's/-0?/./'`
+version=$($(dirname "$0")/get_version.sh)
+commithash=$(git rev-parse --short=8 HEAD)
+committimestamp=$(git show --format=%ci HEAD | head -n 1)
+commitdate=$(git show --format=%ci HEAD | head -n 1 | cut - -b1-10 | sed -e 's/-0?/./' | sed -e 's/-0?/./')
echo "$commithash" > commit_hash.txt
if [ $branch = develop ]
@@ -192,7 +207,8 @@ EMAIL="$email" dch -v 1:${debversion}-${versionsuffix} "git build of ${commithas
# build source package
# If packages is rejected because original source is already present, add
# -sd to remove it from the .changes file
-debuild -S -sa -us -uc
+# -d disables the build dependencies check
+debuild -S -d -sa -us -uc
# prepare .changes file for Launchpad
sed -i -e s/UNRELEASED/${distribution}/ -e s/urgency=medium/urgency=low/ ../*.changes
@@ -223,6 +239,6 @@ fi
debsign --re-sign -k ${keyid} ../${packagename}_${debversion}-${versionsuffix}_source.changes
# upload
-dput ppa:${pparepo} ../${packagename}_${debversion}-${versionsuffix}_source.changes
+dput ${pparepo} ../${packagename}_${debversion}-${versionsuffix}_source.changes
done
diff --git a/scripts/test_emscripten.sh b/scripts/test_emscripten.sh
new file mode 100755
index 00000000..a2dbe61c
--- /dev/null
+++ b/scripts/test_emscripten.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+
+#------------------------------------------------------------------------------
+# Bash script to execute the Solidity tests.
+#
+# The documentation for solidity is hosted at:
+#
+# https://solidity.readthedocs.org
+#
+# ------------------------------------------------------------------------------
+# 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/>
+#
+# (c) 2017 solidity contributors.
+#------------------------------------------------------------------------------
+
+set -e
+
+REPO_ROOT="$(dirname "$0")"/..
+
+cd $REPO_ROOT/build
+
+echo "Preparing solc-js..."
+rm -rf solc-js
+git clone https://github.com/ethereum/solc-js
+cd solc-js
+npm install
+
+# Replace soljson with current build
+echo "Replacing soljson.js"
+rm -f soljson.js
+# Make a copy because paths might not be absolute
+cp ../solc/soljson.js soljson.js
+
+# Update version (needed for some tests)
+VERSION=$(../../scripts/get_version.sh)
+echo "Updating package.json to version $VERSION"
+npm version $VERSION
+
+echo "Running solc-js tests..."
+npm run test
diff --git a/scripts/tests.sh b/scripts/tests.sh
index dfbda734..6b76c154 100755
--- a/scripts/tests.sh
+++ b/scripts/tests.sh
@@ -30,28 +30,28 @@ set -e
REPO_ROOT="$(dirname "$0")"/..
- # Compile all files in std and examples.
+echo "Running commandline tests..."
+"$REPO_ROOT/test/cmdlineTests.sh"
-for f in "$REPO_ROOT"/std/*.sol
-do
- echo "Compiling $f..."
- set +e
- output=$("$REPO_ROOT"/build/solc/solc "$f" 2>&1)
- failed=$?
- # Remove the pre-release warning from the compiler output
- output=$(echo "$output" | grep -v 'pre-release')
- echo "$output"
- set -e
- test -z "$output" -a "$failed" -eq 0
-done
+echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode..."
+output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l)
+test "${output//[[:blank:]]/}" = "3"
# This conditional is only needed because we don't have a working Homebrew
# install for `eth` at the time of writing, so we unzip the ZIP file locally
# instead. This will go away soon.
if [[ "$OSTYPE" == "darwin"* ]]; then
ETH_PATH="$REPO_ROOT/eth"
-else
+elif [ -z $CI ]; then
ETH_PATH="eth"
+else
+ mkdir -p /tmp/test
+ wget -O /tmp/test/eth https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth
+ test "$(shasum /tmp/test/eth)" = "c132e8989229e4840831a4fb1a1d058b732a11d5 /tmp/test/eth"
+ sync
+ chmod +x /tmp/test/eth
+ sync # Otherwise we might get a "text file busy" error
+ ETH_PATH="/tmp/test/eth"
fi
# This trailing ampersand directs the shell to run the command in the background,
@@ -60,21 +60,22 @@ fi
# true and continue as normal, either processing further commands in a script
# or returning the cursor focus back to the user in a Linux terminal.
$ETH_PATH --test -d /tmp/test &
+ETH_PID=$!
# Wait until the IPC endpoint is available. That won't be available instantly.
# The node needs to get a little way into its startup sequence before the IPC
# is available and is ready for the unit-tests to start talking to it.
while [ ! -S /tmp/test/geth.ipc ]; do sleep 2; done
echo "--> IPC available."
-
+sleep 2
# And then run the Solidity unit-tests (once without optimization, once with),
# pointing to that IPC endpoint.
echo "--> Running tests without optimizer..."
- "$REPO_ROOT"/build/test/soltest -- --ipcpath /tmp/test/geth.ipc && \
+ "$REPO_ROOT"/build/test/soltest --show-progress -- --ipcpath /tmp/test/geth.ipc && \
echo "--> Running tests WITH optimizer..." && \
- "$REPO_ROOT"/build/test/soltest -- --optimize --ipcpath /tmp/test/geth.ipc
+ "$REPO_ROOT"/build/test/soltest --show-progress -- --optimize --ipcpath /tmp/test/geth.ipc
ERROR_CODE=$?
-pkill eth || true
+pkill "$ETH_PID" || true
sleep 4
-pgrep eth && pkill -9 eth || true
+pgrep "$ETH_PID" && pkill -9 "$ETH_PID" || true
exit $ERROR_CODE
diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh
index a6eb01a0..f92b3c44 100755
--- a/scripts/travis-emscripten/build_emscripten.sh
+++ b/scripts/travis-emscripten/build_emscripten.sh
@@ -94,9 +94,11 @@ emmake make -j 4
cd ..
cp build/solc/soljson.js ./
+mkdir -p upload
+cp soljson.js upload/
OUTPUT_SIZE=`ls -la build/solc/soljson.js`
-echo "Emscripten output size: ${OUTPUT_SIZE}"
+echo "Emscripten output size: $OUTPUT_SIZE"
echo -en 'travis_fold:end:compiling_solidity\\r'
diff --git a/scripts/uniqueErrors.sh b/scripts/uniqueErrors.sh
new file mode 100755
index 00000000..eee1df90
--- /dev/null
+++ b/scripts/uniqueErrors.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+REPO=$(dirname $0)/..
+
+echo "Finding unique failures..."
+(
+for x in $*
+do
+ echo -n $x " # "
+ # This subshell is a workaround to prevent the shell from printing
+ # "Aborted"
+ ("$REPO"/build/test/solfuzzer < "$x" || true) 2>&1 | head -n 1
+done
+) | sort -u -t'#' -k 2
diff --git a/scripts/update_bugs_by_version.py b/scripts/update_bugs_by_version.py
new file mode 100755
index 00000000..c4bc0c9b
--- /dev/null
+++ b/scripts/update_bugs_by_version.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#
+# This script is used to generate the list of bugs per compiler version
+# from the list of bugs.
+# It updates the list in place and signals failure if there were changes.
+# This makes it possible to use this script as part of CI to check
+# that the list is up to date.
+
+import os
+import json
+import re
+import sys
+
+def comp(version_string):
+ return [int(c) for c in version_string.split('.')]
+
+path = os.path.dirname(os.path.realpath(__file__))
+with open(path + '/../docs/bugs.json') as bugsFile:
+ bugs = json.load(bugsFile)
+
+versions = {}
+with open(path + '/../Changelog.md') as changelog:
+ for line in changelog:
+ m = re.search(r'^### (\S+) \((\d+-\d+-\d+)\)$', line)
+ if m:
+ versions[m.group(1)] = {}
+ versions[m.group(1)]['released'] = m.group(2)
+
+for v in versions:
+ versions[v]['bugs'] = []
+ for bug in bugs:
+ if 'introduced' in bug and comp(bug['introduced']) > comp(v):
+ continue
+ if comp(bug['fixed']) <= comp(v):
+ continue
+ versions[v]['bugs'] += [bug['name']]
+
+with open(path + '/../docs/bugs_by_version.json', 'r+') as bugs_by_version:
+ old_contents = bugs_by_version.read()
+ new_contents = json.dumps(versions, sort_keys=True, indent=4)
+ bugs_by_version.seek(0)
+ bugs_by_version.write(new_contents)
+ sys.exit(old_contents != new_contents) \ No newline at end of file
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
new file mode 100644
index 00000000..f0f3310a
--- /dev/null
+++ b/snap/snapcraft.yaml
@@ -0,0 +1,29 @@
+name: solc
+version: master
+summary: The Solidity Contract-Oriented Programming Language
+description: |
+ Solidity is a contract-oriented, high-level language whose syntax is similar
+ to that of JavaScript and it is designed to target the Ethereum Virtual
+ Machine (EVM).
+
+ Solidity is statically typed, supports inheritance, libraries and complex
+ user-defined types among other features.
+
+ It is possible to create contracts for voting, crowdfunding, blind auctions,
+ multi-signature wallets and more.
+
+grade: devel # must be 'stable' to release into candidate/stable channels
+confinement: strict
+
+apps:
+ solc:
+ command: solc
+ plugs: [home]
+
+parts:
+ solidity:
+ source: .
+ source-type: git
+ plugin: cmake
+ build-packages: [build-essential, libboost-all-dev]
+ stage-packages: [libicu55]
diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt
index fa7e0bde..a5515d81 100644
--- a/solc/CMakeLists.txt
+++ b/solc/CMakeLists.txt
@@ -18,7 +18,7 @@ else()
endif()
if (EMSCRIPTEN)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\"]' -s RESERVED_FUNCTION_POINTERS=20")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\",\"_compileStandard\"]' -s RESERVED_FUNCTION_POINTERS=20")
add_executable(soljson jsonCompiler.cpp ${HEADERS})
eth_use(soljson REQUIRED Solidity::solidity)
else()
diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp
index 6e59099a..0222ccb0 100644
--- a/solc/CommandLineInterface.cpp
+++ b/solc/CommandLineInterface.cpp
@@ -22,28 +22,9 @@
*/
#include "CommandLineInterface.h"
-#ifdef _WIN32 // windows
- #include <io.h>
- #define isatty _isatty
- #define fileno _fileno
-#else // unix
- #include <unistd.h>
-#endif
-#include <string>
-#include <iostream>
-#include <fstream>
-
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/operations.hpp>
-#include <boost/algorithm/string.hpp>
-
#include "solidity/BuildInfo.h"
-#include <libdevcore/Common.h>
-#include <libdevcore/CommonData.h>
-#include <libdevcore/CommonIO.h>
-#include <libdevcore/JSON.h>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/GasMeter.h>
+#include "license.h"
+
#include <libsolidity/interface/Version.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/parsing/Parser.h>
@@ -52,10 +33,33 @@
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/CompilerStack.h>
+#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/SourceReferenceFormatter.h>
#include <libsolidity/interface/GasEstimator.h>
-#include <libsolidity/inlineasm/AsmParser.h>
-#include <libsolidity/formal/Why3Translator.h>
+#include <libsolidity/interface/AssemblyStack.h>
+
+#include <libevmasm/Instruction.h>
+#include <libevmasm/GasMeter.h>
+
+#include <libdevcore/Common.h>
+#include <libdevcore/CommonData.h>
+#include <libdevcore/CommonIO.h>
+#include <libdevcore/JSON.h>
+
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/algorithm/string.hpp>
+
+#ifdef _WIN32 // windows
+ #include <io.h>
+ #define isatty _isatty
+ #define fileno _fileno
+#else // unix
+ #include <unistd.h>
+#endif
+#include <string>
+#include <iostream>
+#include <fstream>
using namespace std;
namespace po = boost::program_options;
@@ -65,38 +69,112 @@ namespace dev
namespace solidity
{
-static string const g_argAbiStr = "abi";
-static string const g_argSignatureHashes = "hashes";
-static string const g_argGas = "gas";
-static string const g_argAsmStr = "asm";
-static string const g_argAsmJsonStr = "asm-json";
-static string const g_argAstStr = "ast";
-static string const g_argAstJson = "ast-json";
-static string const g_argBinaryStr = "bin";
-static string const g_argRuntimeBinaryStr = "bin-runtime";
-static string const g_argCloneBinaryStr = "clone-bin";
-static string const g_argOpcodesStr = "opcodes";
-static string const g_argNatspecDevStr = "devdoc";
-static string const g_argNatspecUserStr = "userdoc";
-static string const g_argMetadata = "metadata";
-static string const g_argAddStandard = "add-std";
-static string const g_stdinFileName = "<stdin>";
+static string const g_stdinFileNameStr = "<stdin>";
+static string const g_strAbi = "abi";
+static string const g_strAddStandard = "add-std";
+static string const g_strAllowPaths = "allow-paths";
+static string const g_strAsm = "asm";
+static string const g_strAsmJson = "asm-json";
+static string const g_strAssemble = "assemble";
+static string const g_strAst = "ast";
+static string const g_strAstJson = "ast-json";
+static string const g_strAstCompactJson = "ast-compact-json";
+static string const g_strBinary = "bin";
+static string const g_strBinaryRuntime = "bin-runtime";
+static string const g_strCloneBinary = "clone-bin";
+static string const g_strCombinedJson = "combined-json";
+static string const g_strCompactJSON = "compact-format";
+static string const g_strContracts = "contracts";
+static string const g_strEVM = "evm";
+static string const g_strEVM15 = "evm15";
+static string const g_streWasm = "ewasm";
+static string const g_strFormal = "formal";
+static string const g_strGas = "gas";
+static string const g_strHelp = "help";
+static string const g_strInputFile = "input-file";
+static string const g_strInterface = "interface";
+static string const g_strJulia = "julia";
+static string const g_strLicense = "license";
+static string const g_strLibraries = "libraries";
+static string const g_strLink = "link";
+static string const g_strMetadata = "metadata";
+static string const g_strMetadataLiteral = "metadata-literal";
+static string const g_strNatspecDev = "devdoc";
+static string const g_strNatspecUser = "userdoc";
+static string const g_strOpcodes = "opcodes";
+static string const g_strOptimize = "optimize";
+static string const g_strOptimizeRuns = "optimize-runs";
+static string const g_strOutputDir = "output-dir";
+static string const g_strOverwrite = "overwrite";
+static string const g_strSignatureHashes = "hashes";
+static string const g_strSources = "sources";
+static string const g_strSourceList = "sourceList";
+static string const g_strSrcMap = "srcmap";
+static string const g_strSrcMapRuntime = "srcmap-runtime";
+static string const g_strStandardJSON = "standard-json";
+static string const g_strVersion = "version";
+
+static string const g_argAbi = g_strAbi;
+static string const g_argAddStandard = g_strAddStandard;
+static string const g_argAllowPaths = g_strAllowPaths;
+static string const g_argAsm = g_strAsm;
+static string const g_argAsmJson = g_strAsmJson;
+static string const g_argAssemble = g_strAssemble;
+static string const g_argAst = g_strAst;
+static string const g_argAstCompactJson = g_strAstCompactJson;
+static string const g_argAstJson = g_strAstJson;
+static string const g_argBinary = g_strBinary;
+static string const g_argBinaryRuntime = g_strBinaryRuntime;
+static string const g_argCloneBinary = g_strCloneBinary;
+static string const g_argCombinedJson = g_strCombinedJson;
+static string const g_argCompactJSON = g_strCompactJSON;
+static string const g_argFormal = g_strFormal;
+static string const g_argGas = g_strGas;
+static string const g_argHelp = g_strHelp;
+static string const g_argInputFile = g_strInputFile;
+static string const g_argJulia = "julia";
+static string const g_argLibraries = g_strLibraries;
+static string const g_argLink = g_strLink;
+static string const g_argMachine = "machine";
+static string const g_argMetadata = g_strMetadata;
+static string const g_argMetadataLiteral = g_strMetadataLiteral;
+static string const g_argNatspecDev = g_strNatspecDev;
+static string const g_argNatspecUser = g_strNatspecUser;
+static string const g_argOpcodes = g_strOpcodes;
+static string const g_argOptimize = g_strOptimize;
+static string const g_argOptimizeRuns = g_strOptimizeRuns;
+static string const g_argOutputDir = g_strOutputDir;
+static string const g_argSignatureHashes = g_strSignatureHashes;
+static string const g_argStandardJSON = g_strStandardJSON;
+static string const g_argVersion = g_strVersion;
+static string const g_stdinFileName = g_stdinFileNameStr;
/// Possible arguments to for --combined-json
-static set<string> const g_combinedJsonArgs{
- "bin",
- "bin-runtime",
- "clone-bin",
- "srcmap",
- "srcmap-runtime",
- "opcodes",
- "abi",
- "interface",
- "metadata",
- "asm",
- "ast",
- "userdoc",
- "devdoc"
+static set<string> const g_combinedJsonArgs
+{
+ g_strAbi,
+ g_strAsm,
+ g_strAst,
+ g_strBinary,
+ g_strBinaryRuntime,
+ g_strCloneBinary,
+ g_strCompactJSON,
+ g_strInterface,
+ g_strMetadata,
+ g_strNatspecUser,
+ g_strNatspecDev,
+ g_strOpcodes,
+ g_strSignatureHashes,
+ g_strSrcMap,
+ g_strSrcMapRuntime
+};
+
+/// Possible arguments to for --machine
+static set<string> const g_machineArgs
+{
+ g_strEVM,
+ g_strEVM15,
+ g_streWasm
};
static void version()
@@ -110,26 +188,34 @@ static void version()
exit(0);
}
+static void license()
+{
+ cout << otherLicenses << endl;
+ // This is a static variable generated by cmake from LICENSE.txt
+ cout << licenseText << endl;
+ exit(0);
+}
+
static bool needsHumanTargetedStdout(po::variables_map const& _args)
{
if (_args.count(g_argGas))
return true;
- if (_args.count("output-dir"))
+ if (_args.count(g_argOutputDir))
return false;
for (string const& arg: {
- g_argAbiStr,
- g_argSignatureHashes,
- g_argMetadata,
- g_argNatspecUserStr,
+ g_argAbi,
+ g_argAsm,
+ g_argAsmJson,
g_argAstJson,
- g_argNatspecDevStr,
- g_argAsmStr,
- g_argAsmJsonStr,
- g_argOpcodesStr,
- g_argBinaryStr,
- g_argRuntimeBinaryStr,
- g_argCloneBinaryStr,
- string("formal")
+ g_argBinary,
+ g_argBinaryRuntime,
+ g_argCloneBinary,
+ g_argFormal,
+ g_argMetadata,
+ g_argNatspecUser,
+ g_argNatspecDev,
+ g_argOpcodes,
+ g_argSignatureHashes
})
if (_args.count(arg))
return true;
@@ -138,30 +224,30 @@ static bool needsHumanTargetedStdout(po::variables_map const& _args)
void CommandLineInterface::handleBinary(string const& _contract)
{
- if (m_args.count(g_argBinaryStr))
+ if (m_args.count(g_argBinary))
{
- if (m_args.count("output-dir"))
- createFile(_contract + ".bin", m_compiler->object(_contract).toHex());
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", m_compiler->object(_contract).toHex());
else
{
cout << "Binary: " << endl;
cout << m_compiler->object(_contract).toHex() << endl;
}
}
- if (m_args.count(g_argCloneBinaryStr))
+ if (m_args.count(g_argCloneBinary))
{
- if (m_args.count("output-dir"))
- createFile(_contract + ".clone_bin", m_compiler->cloneObject(_contract).toHex());
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + ".clone_bin", m_compiler->cloneObject(_contract).toHex());
else
{
cout << "Clone Binary: " << endl;
cout << m_compiler->cloneObject(_contract).toHex() << endl;
}
}
- if (m_args.count(g_argRuntimeBinaryStr))
+ if (m_args.count(g_argBinaryRuntime))
{
- if (m_args.count("output-dir"))
- createFile(_contract + ".bin-runtime", m_compiler->runtimeObject(_contract).toHex());
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin-runtime", m_compiler->runtimeObject(_contract).toHex());
else
{
cout << "Binary of the runtime part: " << endl;
@@ -172,8 +258,8 @@ void CommandLineInterface::handleBinary(string const& _contract)
void CommandLineInterface::handleOpcode(string const& _contract)
{
- if (m_args.count("output-dir"))
- createFile(_contract + ".opcode", solidity::disassemble(m_compiler->object(_contract).bytecode));
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", solidity::disassemble(m_compiler->object(_contract).bytecode));
else
{
cout << "Opcodes: " << endl;
@@ -184,9 +270,9 @@ void CommandLineInterface::handleOpcode(string const& _contract)
void CommandLineInterface::handleBytecode(string const& _contract)
{
- if (m_args.count(g_argOpcodesStr))
+ if (m_args.count(g_argOpcodes))
handleOpcode(_contract);
- if (m_args.count(g_argBinaryStr) || m_args.count(g_argCloneBinaryStr) || m_args.count(g_argRuntimeBinaryStr))
+ if (m_args.count(g_argBinary) || m_args.count(g_argCloneBinary) || m_args.count(g_argBinaryRuntime))
handleBinary(_contract);
}
@@ -195,12 +281,13 @@ void CommandLineInterface::handleSignatureHashes(string const& _contract)
if (!m_args.count(g_argSignatureHashes))
return;
+ Json::Value methodIdentifiers = m_compiler->methodIdentifiers(_contract);
string out;
- for (auto const& it: m_compiler->contractDefinition(_contract).interfaceFunctions())
- out += toHex(it.first.ref()) + ": " + it.second->externalSignature() + "\n";
+ for (auto const& name: methodIdentifiers.getMemberNames())
+ out += methodIdentifiers[name].asString() + ": " + name + "\n";
- if (m_args.count("output-dir"))
- createFile(_contract + ".signatures", out);
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + ".signatures", out);
else
cout << "Function signatures: " << endl << out;
}
@@ -211,31 +298,38 @@ void CommandLineInterface::handleOnChainMetadata(string const& _contract)
return;
string data = m_compiler->onChainMetadata(_contract);
- if (m_args.count("output-dir"))
- createFile(_contract + "_meta.json", data);
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + "_meta.json", data);
else
cout << "Metadata: " << endl << data << endl;
}
-void CommandLineInterface::handleMeta(DocumentationType _type, string const& _contract)
+void CommandLineInterface::handleABI(string const& _contract)
+{
+ if (!m_args.count(g_argAbi))
+ return;
+
+ string data = dev::jsonCompactPrint(m_compiler->contractABI(_contract));
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + ".abi", data);
+ else
+ cout << "Contract JSON ABI " << endl << data << endl;
+}
+
+void CommandLineInterface::handleNatspec(DocumentationType _type, string const& _contract)
{
std::string argName;
std::string suffix;
std::string title;
switch(_type)
{
- case DocumentationType::ABIInterface:
- argName = g_argAbiStr;
- suffix = ".abi";
- title = "Contract JSON ABI";
- break;
case DocumentationType::NatspecUser:
- argName = g_argNatspecUserStr;
+ argName = g_argNatspecUser;
suffix = ".docuser";
title = "User Documentation";
break;
case DocumentationType::NatspecDev:
- argName = g_argNatspecDevStr;
+ argName = g_argNatspecDev;
suffix = ".docdev";
title = "Developer Documentation";
break;
@@ -246,14 +340,10 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co
if (m_args.count(argName))
{
- std::string output;
- if (_type == DocumentationType::ABIInterface)
- output = dev::jsonCompactPrint(m_compiler->metadata(_contract, _type));
- else
- output = dev::jsonPrettyPrint(m_compiler->metadata(_contract, _type));
+ std::string output = dev::jsonPrettyPrint(m_compiler->natspec(_contract, _type));
- if (m_args.count("output-dir"))
- createFile(_contract + suffix, output);
+ if (m_args.count(g_argOutputDir))
+ createFile(m_compiler->filesystemFriendlyName(_contract) + suffix, output);
else
{
cout << title << endl;
@@ -265,72 +355,51 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co
void CommandLineInterface::handleGasEstimation(string const& _contract)
{
- using Gas = GasEstimator::GasConsumption;
- if (!m_compiler->assemblyItems(_contract) && !m_compiler->runtimeAssemblyItems(_contract))
- return;
+ Json::Value estimates = m_compiler->gasEstimates(_contract);
cout << "Gas estimation:" << endl;
- if (eth::AssemblyItems const* items = m_compiler->assemblyItems(_contract))
+
+ if (estimates["creation"].isObject())
{
- Gas gas = GasEstimator::functionalEstimation(*items);
- u256 bytecodeSize(m_compiler->runtimeObject(_contract).bytecode.size());
+ Json::Value creation = estimates["creation"];
cout << "construction:" << endl;
- cout << " " << gas << " + " << (bytecodeSize * eth::GasCosts::createDataGas) << " = ";
- gas += bytecodeSize * eth::GasCosts::createDataGas;
- cout << gas << endl;
+ cout << " " << creation["executionCost"].asString();
+ cout << " + " << creation["codeDepositCost"].asString();
+ cout << " = " << creation["totalCost"].asString() << endl;
}
- if (eth::AssemblyItems const* items = m_compiler->runtimeAssemblyItems(_contract))
+
+ if (estimates["external"].isObject())
{
- ContractDefinition const& contract = m_compiler->contractDefinition(_contract);
+ Json::Value externalFunctions = estimates["external"];
cout << "external:" << endl;
- for (auto it: contract.interfaceFunctions())
+ for (auto const& name: externalFunctions.getMemberNames())
{
- string sig = it.second->externalSignature();
- GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig);
- cout << " " << sig << ":\t" << gas << endl;
- }
- if (contract.fallbackFunction())
- {
- GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, "INVALID");
- cout << " fallback:\t" << gas << endl;
+ if (name.empty())
+ cout << " fallback:\t";
+ else
+ cout << " " << name << ":\t";
+ cout << externalFunctions[name].asString() << endl;
}
+ }
+
+ if (estimates["internal"].isObject())
+ {
+ Json::Value internalFunctions = estimates["internal"];
cout << "internal:" << endl;
- for (auto const& it: contract.definedFunctions())
+ for (auto const& name: internalFunctions.getMemberNames())
{
- if (it->isPartOfExternalInterface() || it->isConstructor())
- continue;
- size_t entry = m_compiler->functionEntryPoint(_contract, *it);
- GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite();
- if (entry > 0)
- gas = GasEstimator::functionalEstimation(*items, entry, *it);
- FunctionType type(*it);
- cout << " " << it->name() << "(";
- auto paramTypes = type.parameterTypes();
- for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it)
- cout << (*it)->toString() << (it + 1 == paramTypes.end() ? "" : ",");
- cout << "):\t" << gas << endl;
+ cout << " " << name << ":\t";
+ cout << internalFunctions[name].asString() << endl;
}
}
}
-void CommandLineInterface::handleFormal()
-{
- if (!m_args.count("formal"))
- return;
-
- if (m_args.count("output-dir"))
- createFile("solidity.mlw", m_compiler->formalTranslation());
- else
- cout << "Formal version:" << endl << m_compiler->formalTranslation() << endl;
-}
-
void CommandLineInterface::readInputFilesAndConfigureRemappings()
{
- vector<string> inputFiles;
bool addStdin = false;
- if (!m_args.count("input-file"))
+ if (!m_args.count(g_argInputFile))
addStdin = true;
else
- for (string path: m_args["input-file"].as<vector<string>>())
+ for (string path: m_args[g_argInputFile].as<vector<string>>())
{
auto eq = find(path.begin(), path.end(), '=');
if (eq != path.end())
@@ -371,14 +440,25 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings()
bool CommandLineInterface::parseLibraryOption(string const& _input)
{
namespace fs = boost::filesystem;
- string data = fs::is_regular_file(_input) ? contentsString(_input) : _input;
+ string data = _input;
+ try
+ {
+ if (fs::is_regular_file(_input))
+ data = contentsString(_input);
+ }
+ catch (fs::filesystem_error const&)
+ {
+ // Thrown e.g. if path is too long.
+ }
vector<string> libraries;
boost::split(libraries, data, boost::is_space() || boost::is_any_of(","), boost::token_compress_on);
for (string const& lib: libraries)
if (!lib.empty())
{
- auto colon = lib.find(':');
+ //search for last colon in string as our binaries output placeholders in the form of file:Name
+ //so we need to search for the second `:` in the string
+ auto colon = lib.rfind(':');
if (colon == string::npos)
{
cerr << "Colon separator missing in library address specifier \"" << lib << "\"" << endl;
@@ -388,6 +468,11 @@ bool CommandLineInterface::parseLibraryOption(string const& _input)
string addrString(lib.begin() + colon + 1, lib.end());
boost::trim(libName);
boost::trim(addrString);
+ if (!passesAddressChecksum(addrString, false))
+ {
+ cerr << "Invalid checksum on library address \"" << libName << "\": " << addrString << endl;
+ return false;
+ }
bytes binAddr = fromHex(addrString);
h160 address(binAddr, h160::AlignRight);
if (binAddr.size() > 20 || address == h160())
@@ -405,9 +490,17 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da
{
namespace fs = boost::filesystem;
// create directory if not existent
- fs::path p(m_args.at("output-dir").as<string>());
- fs::create_directories(p);
+ fs::path p(m_args.at(g_argOutputDir).as<string>());
+ // Do not try creating the directory if the first item is . or ..
+ if (p.filename() != "." && p.filename() != "..")
+ fs::create_directories(p);
string pathName = (p / _fileName).string();
+ if (fs::exists(pathName) && !m_args.count(g_strOverwrite))
+ {
+ cerr << "Refusing to overwrite existing file \"" << pathName << "\" (use --overwrite to force)." << endl;
+ m_error = true;
+ return;
+ }
ofstream outFile(pathName);
outFile << _data;
if (!outFile)
@@ -417,8 +510,12 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da
bool CommandLineInterface::parseArguments(int _argc, char** _argv)
{
// Declare the supported options.
- po::options_description desc(
- R"(solc, the Solidity commandline compiler.
+ po::options_description desc(R"(solc, the Solidity commandline compiler.
+
+This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you
+are welcome to redistribute it under certain conditions. See 'solc --license'
+for details.
+
Usage: solc [options] [input_file...]
Compiles the given Solidity input files (or the standard input if none given or
"-" is used as a file name) and outputs the components specified in the options
@@ -430,68 +527,92 @@ Example:
Allowed options)",
po::options_description::m_default_line_length,
- po::options_description::m_default_line_length - 23);
+ po::options_description::m_default_line_length - 23
+ );
desc.add_options()
- ("help", "Show help message and exit.")
- ("version", "Show version and exit.")
- ("optimize", "Enable bytecode optimizer.")
+ (g_argHelp.c_str(), "Show help message and exit.")
+ (g_argVersion.c_str(), "Show version and exit.")
+ (g_strLicense.c_str(), "Show licensing information and exit.")
+ (g_argOptimize.c_str(), "Enable bytecode optimizer.")
(
- "optimize-runs",
+ g_argOptimizeRuns.c_str(),
po::value<unsigned>()->value_name("n")->default_value(200),
"Estimated number of contract runs for optimizer tuning."
)
(g_argAddStandard.c_str(), "Add standard contracts.")
(
- "libraries",
+ g_argLibraries.c_str(),
po::value<vector<string>>()->value_name("libs"),
"Direct string or file containing library addresses. Syntax: "
"<libraryName>: <address> [, or whitespace] ...\n"
"Address is interpreted as a hex string optionally prefixed by 0x."
)
(
- "output-dir,o",
+ (g_argOutputDir + ",o").c_str(),
po::value<string>()->value_name("path"),
"If given, creates one file per component and contract/file at the specified directory."
)
+ (g_strOverwrite.c_str(), "Overwrite existing files (used together with -o).")
(
- "combined-json",
+ g_argCombinedJson.c_str(),
po::value<string>()->value_name(boost::join(g_combinedJsonArgs, ",")),
"Output a single json document containing the specified information."
)
(g_argGas.c_str(), "Print an estimate of the maximal gas usage for each function.")
(
- "assemble",
- "Switch to assembly mode, ignoring all options and assumes input is assembly."
+ g_argStandardJSON.c_str(),
+ "Switch to Standard JSON input / output mode, ignoring all options. "
+ "It reads from standard input and provides the result on the standard output."
)
(
- "link",
+ g_argAssemble.c_str(),
+ "Switch to assembly mode, ignoring all options except --machine and assumes input is assembly."
+ )
+ (
+ g_argJulia.c_str(),
+ "Switch to JULIA mode, ignoring all options except --machine and assumes input is JULIA."
+ )
+ (
+ g_argMachine.c_str(),
+ po::value<string>()->value_name(boost::join(g_machineArgs, ",")),
+ "Target machine in assembly or JULIA mode."
+ )
+ (
+ g_argLink.c_str(),
"Switch to linker mode, ignoring all options apart from --libraries "
"and modify binaries in place."
+ )
+ (g_argMetadataLiteral.c_str(), "Store referenced sources are literal data in the metadata output.")
+ (
+ g_argAllowPaths.c_str(),
+ po::value<string>()->value_name("path(s)"),
+ "Allow a given path for imports. A list of paths can be supplied by separating them with a comma."
);
po::options_description outputComponents("Output Components");
outputComponents.add_options()
- (g_argAstStr.c_str(), "AST of all source files.")
+ (g_argAst.c_str(), "AST of all source files.")
(g_argAstJson.c_str(), "AST of all source files in JSON format.")
- (g_argAsmStr.c_str(), "EVM assembly of the contracts.")
- (g_argAsmJsonStr.c_str(), "EVM assembly of the contracts in JSON format.")
- (g_argOpcodesStr.c_str(), "Opcodes of the contracts.")
- (g_argBinaryStr.c_str(), "Binary of the contracts in hex.")
- (g_argRuntimeBinaryStr.c_str(), "Binary of the runtime part of the contracts in hex.")
- (g_argCloneBinaryStr.c_str(), "Binary of the clone contracts in hex.")
- (g_argAbiStr.c_str(), "ABI specification of the contracts.")
+ (g_argAstCompactJson.c_str(), "AST of all source files in a compact JSON format.")
+ (g_argAsm.c_str(), "EVM assembly of the contracts.")
+ (g_argAsmJson.c_str(), "EVM assembly of the contracts in JSON format.")
+ (g_argOpcodes.c_str(), "Opcodes of the contracts.")
+ (g_argBinary.c_str(), "Binary of the contracts in hex.")
+ (g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
+ (g_argCloneBinary.c_str(), "Binary of the clone contracts in hex.")
+ (g_argAbi.c_str(), "ABI specification of the contracts.")
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
- (g_argNatspecUserStr.c_str(), "Natspec user documentation of all contracts.")
- (g_argNatspecDevStr.c_str(), "Natspec developer documentation of all contracts.")
+ (g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
+ (g_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.")
(g_argMetadata.c_str(), "Combined Metadata JSON whose Swarm hash is stored on-chain.")
- ("formal", "Translated source suitable for formal analysis.");
+ (g_argFormal.c_str(), "Translated source suitable for formal analysis.");
desc.add(outputComponents);
po::options_description allOptions = desc;
- allOptions.add_options()("input-file", po::value<vector<string>>(), "input file");
+ allOptions.add_options()(g_argInputFile.c_str(), po::value<vector<string>>(), "input file");
// All positional options should be interpreted as input files
po::positional_options_description filesPositions;
- filesPositions.add("input-file", -1);
+ filesPositions.add(g_argInputFile.c_str(), -1);
// parse the compiler arguments
try
@@ -506,22 +627,28 @@ Allowed options)",
return false;
}
- if (m_args.count("help") || (isatty(fileno(stdin)) && _argc == 1))
+ if (m_args.count(g_argHelp) || (isatty(fileno(stdin)) && _argc == 1))
{
cout << desc;
return false;
}
- if (m_args.count("version"))
+ if (m_args.count(g_argVersion))
{
version();
return false;
}
- if (m_args.count("combined-json"))
+ if (m_args.count(g_strLicense))
+ {
+ license();
+ return false;
+ }
+
+ if (m_args.count(g_argCombinedJson))
{
vector<string> requests;
- for (string const& item: boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(",")))
+ for (string const& item: boost::split(requests, m_args[g_argCombinedJson].as<string>(), boost::is_any_of(",")))
if (!g_combinedJsonArgs.count(item))
{
cerr << "Invalid option to --combined-json: " << item << endl;
@@ -535,74 +662,123 @@ Allowed options)",
bool CommandLineInterface::processInput()
{
+ ReadFile::Callback fileReader = [this](string const& _path)
+ {
+ try
+ {
+ auto path = boost::filesystem::path(_path);
+ auto canonicalPath = boost::filesystem::canonical(path);
+ bool isAllowed = false;
+ for (auto const& allowedDir: m_allowedDirectories)
+ {
+ // If dir is a prefix of boostPath, we are fine.
+ if (
+ std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) &&
+ std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin())
+ )
+ {
+ isAllowed = true;
+ break;
+ }
+ }
+ if (!isAllowed)
+ return ReadFile::Result{false, "File outside of allowed directories."};
+ else if (!boost::filesystem::exists(path))
+ return ReadFile::Result{false, "File not found."};
+ else if (!boost::filesystem::is_regular_file(canonicalPath))
+ return ReadFile::Result{false, "Not a valid file."};
+ else
+ {
+ auto contents = dev::contentsString(canonicalPath.string());
+ m_sourceCodes[path.string()] = contents;
+ return ReadFile::Result{true, contents};
+ }
+ }
+ catch (Exception const& _exception)
+ {
+ return ReadFile::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)};
+ }
+ catch (...)
+ {
+ return ReadFile::Result{false, "Unknown exception in read callback."};
+ }
+ };
+
+ if (m_args.count(g_argAllowPaths))
+ {
+ vector<string> paths;
+ for (string const& path: boost::split(paths, m_args[g_argAllowPaths].as<string>(), boost::is_any_of(",")))
+ m_allowedDirectories.push_back(boost::filesystem::path(path));
+ }
+
+ if (m_args.count(g_argStandardJSON))
+ {
+ string input;
+ while (!cin.eof())
+ {
+ string tmp;
+ getline(cin, tmp);
+ input.append(tmp + "\n");
+ }
+ StandardCompiler compiler(fileReader);
+ cout << compiler.compile(input) << endl;
+ return true;
+ }
+
readInputFilesAndConfigureRemappings();
- if (m_args.count("libraries"))
- for (string const& library: m_args["libraries"].as<vector<string>>())
+ if (m_args.count(g_argLibraries))
+ for (string const& library: m_args[g_argLibraries].as<vector<string>>())
if (!parseLibraryOption(library))
return false;
- if (m_args.count("assemble"))
+ if (m_args.count(g_argAssemble) || m_args.count(g_argJulia))
{
// switch to assembly mode
m_onlyAssemble = true;
- return assemble();
+ using Input = AssemblyStack::Language;
+ using Machine = AssemblyStack::Machine;
+ Input inputLanguage = m_args.count(g_argJulia) ? Input::JULIA : Input::Assembly;
+ Machine targetMachine = Machine::EVM;
+ if (m_args.count(g_argMachine))
+ {
+ string machine = m_args[g_argMachine].as<string>();
+ if (machine == g_strEVM)
+ targetMachine = Machine::EVM;
+ else if (machine == g_strEVM15)
+ targetMachine = Machine::EVM15;
+ else if (machine == g_streWasm)
+ targetMachine = Machine::eWasm;
+ else
+ {
+ cerr << "Invalid option for --machine: " << machine << endl;
+ return false;
+ }
+ }
+ return assemble(inputLanguage, targetMachine);
}
- if (m_args.count("link"))
+ if (m_args.count(g_argLink))
{
// switch to linker mode
m_onlyLink = true;
return link();
}
- CompilerStack::ReadFileCallback fileReader = [this](string const& _path)
- {
- auto path = boost::filesystem::path(_path);
- if (!boost::filesystem::exists(path))
- return CompilerStack::ReadFileResult{false, "File not found."};
- auto canonicalPath = boost::filesystem::canonical(path);
- bool isAllowed = false;
- for (auto const& allowedDir: m_allowedDirectories)
- {
- // If dir is a prefix of boostPath, we are fine.
- if (
- std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) &&
- std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin())
- )
- {
- isAllowed = true;
- break;
- }
- }
- if (!isAllowed)
- return CompilerStack::ReadFileResult{false, "File outside of allowed directories."};
- else if (!boost::filesystem::is_regular_file(canonicalPath))
- return CompilerStack::ReadFileResult{false, "Not a valid file."};
- else
- {
- auto contents = dev::contentsString(canonicalPath.string());
- m_sourceCodes[path.string()] = contents;
- return CompilerStack::ReadFileResult{true, contents};
- }
- };
-
m_compiler.reset(new CompilerStack(fileReader));
auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); };
try
{
- if (m_args.count("input-file"))
- m_compiler->setRemappings(m_args["input-file"].as<vector<string>>());
+ if (m_args.count(g_argMetadataLiteral) > 0)
+ m_compiler->useMetadataLiteralSources(true);
+ if (m_args.count(g_argInputFile))
+ m_compiler->setRemappings(m_args[g_argInputFile].as<vector<string>>());
for (auto const& sourceCode: m_sourceCodes)
m_compiler->addSource(sourceCode.first, sourceCode.second);
// TODO: Perhaps we should not compile unless requested
- bool optimize = m_args.count("optimize") > 0;
- unsigned runs = m_args["optimize-runs"].as<unsigned>();
+ bool optimize = m_args.count(g_argOptimize) > 0;
+ unsigned runs = m_args[g_argOptimizeRuns].as<unsigned>();
bool successful = m_compiler->compile(optimize, runs, m_libraries);
- if (successful && m_args.count("formal"))
- if (!m_compiler->prepareFormalAnalysis())
- successful = false;
-
for (auto const& error: m_compiler->errors())
SourceReferenceFormatter::printExceptionInformation(
cerr,
@@ -656,73 +832,76 @@ bool CommandLineInterface::processInput()
void CommandLineInterface::handleCombinedJSON()
{
- if (!m_args.count("combined-json"))
+ if (!m_args.count(g_argCombinedJson))
return;
Json::Value output(Json::objectValue);
- output["version"] = ::dev::solidity::VersionString;
+ output[g_strVersion] = ::dev::solidity::VersionString;
set<string> requests;
- boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(","));
+ boost::split(requests, m_args[g_argCombinedJson].as<string>(), boost::is_any_of(","));
vector<string> contracts = m_compiler->contractNames();
if (!contracts.empty())
- output["contracts"] = Json::Value(Json::objectValue);
+ output[g_strContracts] = Json::Value(Json::objectValue);
for (string const& contractName: contracts)
{
Json::Value contractData(Json::objectValue);
- if (requests.count("abi"))
- contractData["abi"] = dev::jsonCompactPrint(m_compiler->interface(contractName));
+ if (requests.count(g_strAbi))
+ contractData[g_strAbi] = dev::jsonCompactPrint(m_compiler->contractABI(contractName));
if (requests.count("metadata"))
contractData["metadata"] = m_compiler->onChainMetadata(contractName);
- if (requests.count("bin"))
- contractData["bin"] = m_compiler->object(contractName).toHex();
- if (requests.count("bin-runtime"))
- contractData["bin-runtime"] = m_compiler->runtimeObject(contractName).toHex();
- if (requests.count("clone-bin"))
- contractData["clone-bin"] = m_compiler->cloneObject(contractName).toHex();
- if (requests.count("opcodes"))
- contractData["opcodes"] = solidity::disassemble(m_compiler->object(contractName).bytecode);
- if (requests.count("asm"))
+ if (requests.count(g_strBinary))
+ contractData[g_strBinary] = m_compiler->object(contractName).toHex();
+ if (requests.count(g_strBinaryRuntime))
+ contractData[g_strBinaryRuntime] = m_compiler->runtimeObject(contractName).toHex();
+ if (requests.count(g_strCloneBinary))
+ contractData[g_strCloneBinary] = m_compiler->cloneObject(contractName).toHex();
+ if (requests.count(g_strOpcodes))
+ contractData[g_strOpcodes] = solidity::disassemble(m_compiler->object(contractName).bytecode);
+ if (requests.count(g_strAsm))
{
ostringstream unused;
- contractData["asm"] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true);
+ contractData[g_strAsm] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true);
}
- if (requests.count("srcmap"))
+ if (requests.count(g_strSrcMap))
{
auto map = m_compiler->sourceMapping(contractName);
- contractData["srcmap"] = map ? *map : "";
+ contractData[g_strSrcMap] = map ? *map : "";
}
- if (requests.count("srcmap-runtime"))
+ if (requests.count(g_strSrcMapRuntime))
{
auto map = m_compiler->runtimeSourceMapping(contractName);
- contractData["srcmap-runtime"] = map ? *map : "";
+ contractData[g_strSrcMapRuntime] = map ? *map : "";
}
- if (requests.count("devdoc"))
- contractData["devdoc"] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecDev));
- if (requests.count("userdoc"))
- contractData["userdoc"] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecUser));
- output["contracts"][contractName] = contractData;
+ if (requests.count(g_strSignatureHashes))
+ contractData[g_strSignatureHashes] = m_compiler->methodIdentifiers(contractName);
+ if (requests.count(g_strNatspecDev))
+ contractData[g_strNatspecDev] = dev::jsonCompactPrint(m_compiler->natspec(contractName, DocumentationType::NatspecDev));
+ if (requests.count(g_strNatspecUser))
+ contractData[g_strNatspecUser] = dev::jsonCompactPrint(m_compiler->natspec(contractName, DocumentationType::NatspecUser));
+ output[g_strContracts][contractName] = contractData;
}
- bool needsSourceList = requests.count("ast") || requests.count("srcmap") || requests.count("srcmap-runtime");
+ bool needsSourceList = requests.count(g_strAst) || requests.count(g_strSrcMap) || requests.count(g_strSrcMapRuntime);
if (needsSourceList)
{
// Indices into this array are used to abbreviate source names in source locations.
- output["sourceList"] = Json::Value(Json::arrayValue);
+ output[g_strSourceList] = Json::Value(Json::arrayValue);
for (auto const& source: m_compiler->sourceNames())
- output["sourceList"].append(source);
+ output[g_strSourceList].append(source);
}
- if (requests.count("ast"))
+ if (requests.count(g_strAst))
{
- output["sources"] = Json::Value(Json::objectValue);
+ bool legacyFormat = !requests.count(g_strCompactJSON);
+ output[g_strSources] = Json::Value(Json::objectValue);
for (auto const& sourceCode: m_sourceCodes)
{
- ASTJsonConverter converter(m_compiler->ast(sourceCode.first), m_compiler->sourceIndices());
- output["sources"][sourceCode.first] = Json::Value(Json::objectValue);
- output["sources"][sourceCode.first]["AST"] = converter.json();
+ ASTJsonConverter converter(legacyFormat, m_compiler->sourceIndices());
+ output[g_strSources][sourceCode.first] = Json::Value(Json::objectValue);
+ output[g_strSources][sourceCode.first]["AST"] = converter.toJson(m_compiler->ast(sourceCode.first));
}
}
cout << dev::jsonCompactPrint(output) << endl;
@@ -732,10 +911,12 @@ void CommandLineInterface::handleAst(string const& _argStr)
{
string title;
- if (_argStr == g_argAstStr)
+ if (_argStr == g_argAst)
title = "Syntax trees:";
else if (_argStr == g_argAstJson)
title = "JSON AST:";
+ else if (_argStr == g_argAstCompactJson)
+ title = "JSON AST (compact format):";
else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal argStr for AST"));
@@ -752,21 +933,21 @@ void CommandLineInterface::handleAst(string const& _argStr)
asts
);
- if (m_args.count("output-dir"))
+ bool legacyFormat = !m_args.count(g_argAstCompactJson);
+ if (m_args.count(g_argOutputDir))
{
for (auto const& sourceCode: m_sourceCodes)
{
stringstream data;
string postfix = "";
- if (_argStr == g_argAstStr)
+ if (_argStr == g_argAst)
{
ASTPrinter printer(m_compiler->ast(sourceCode.first), sourceCode.second);
printer.print(data);
}
else
{
- ASTJsonConverter converter(m_compiler->ast(sourceCode.first));
- converter.print(data);
+ ASTJsonConverter(legacyFormat, m_compiler->sourceIndices()).print(data, m_compiler->ast(sourceCode.first));
postfix += "_json";
}
boost::filesystem::path path(sourceCode.first);
@@ -779,7 +960,7 @@ void CommandLineInterface::handleAst(string const& _argStr)
for (auto const& sourceCode: m_sourceCodes)
{
cout << endl << "======= " << sourceCode.first << " =======" << endl;
- if (_argStr == g_argAstStr)
+ if (_argStr == g_argAst)
{
ASTPrinter printer(
m_compiler->ast(sourceCode.first),
@@ -789,23 +970,22 @@ void CommandLineInterface::handleAst(string const& _argStr)
printer.print(cout);
}
else
- {
- ASTJsonConverter converter(m_compiler->ast(sourceCode.first));
- converter.print(cout);
- }
+ ASTJsonConverter(legacyFormat, m_compiler->sourceIndices()).print(cout, m_compiler->ast(sourceCode.first));
}
}
}
}
-void CommandLineInterface::actOnInput()
+bool CommandLineInterface::actOnInput()
{
- if (m_onlyAssemble)
- outputAssembly();
+ if (m_args.count(g_argStandardJSON) || m_onlyAssemble)
+ // Already done in "processInput" phase.
+ return true;
else if (m_onlyLink)
writeLinkedFiles();
else
outputCompilationResults();
+ return !m_error;
}
bool CommandLineInterface::link()
@@ -861,42 +1041,91 @@ void CommandLineInterface::writeLinkedFiles()
writeFile(src.first, src.second);
}
-bool CommandLineInterface::assemble()
+bool CommandLineInterface::assemble(
+ AssemblyStack::Language _language,
+ AssemblyStack::Machine _targetMachine
+)
{
- //@TODO later, we will use the convenience interface and should also remove the include above
bool successful = true;
- map<string, shared_ptr<Scanner>> scanners;
+ map<string, AssemblyStack> assemblyStacks;
for (auto const& src: m_sourceCodes)
{
- auto scanner = make_shared<Scanner>(CharStream(src.second), src.first);
- scanners[src.first] = scanner;
- if (!m_assemblyStacks[src.first].parse(scanner))
- successful = false;
- else
- //@TODO we should not just throw away the result here
- m_assemblyStacks[src.first].assemble();
+ auto& stack = assemblyStacks[src.first] = AssemblyStack(_language);
+ try
+ {
+ if (!stack.parseAndAnalyze(src.first, src.second))
+ successful = false;
+ }
+ catch (Exception const& _exception)
+ {
+ cerr << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl;
+ return false;
+ }
+ catch (...)
+ {
+ cerr << "Unknown exception in assembler." << endl;
+ return false;
+ }
}
- for (auto const& stack: m_assemblyStacks)
- for (auto const& error: stack.second.errors())
+ for (auto const& sourceAndStack: assemblyStacks)
+ {
+ auto const& stack = sourceAndStack.second;
+ for (auto const& error: stack.errors())
SourceReferenceFormatter::printExceptionInformation(
cerr,
*error,
(error->type() == Error::Type::Warning) ? "Warning" : "Error",
- [&](string const& _source) -> Scanner const& { return *scanners.at(_source); }
+ [&](string const&) -> Scanner const& { return stack.scanner(); }
);
+ if (!Error::containsOnlyWarnings(stack.errors()))
+ successful = false;
+ }
- return successful;
-}
+ if (!successful)
+ return false;
-void CommandLineInterface::outputAssembly()
-{
for (auto const& src: m_sourceCodes)
{
- cout << endl << "======= " << src.first << " =======" << endl;
- eth::Assembly assembly = m_assemblyStacks[src.first].assemble();
- cout << assembly.assemble().toHex() << endl;
- assembly.stream(cout, "", m_sourceCodes);
+ string machine =
+ _targetMachine == AssemblyStack::Machine::EVM ? "EVM" :
+ _targetMachine == AssemblyStack::Machine::EVM15 ? "EVM 1.5" :
+ "eWasm";
+ cout << endl << "======= " << src.first << " (" << machine << ") =======" << endl;
+ AssemblyStack& stack = assemblyStacks[src.first];
+
+ cout << endl << "Pretty printed source:" << endl;
+ cout << stack.print() << endl;
+
+ MachineAssemblyObject object;
+ try
+ {
+ object = stack.assemble(_targetMachine);
+ }
+ catch (Exception const& _exception)
+ {
+ cerr << "Exception while assembling: " << boost::diagnostic_information(_exception) << endl;
+ return false;
+ }
+ catch (...)
+ {
+ cerr << "Unknown exception while assembling." << endl;
+ return false;
+ }
+
+ cout << endl << "Binary representation:" << endl;
+ if (object.bytecode)
+ cout << object.bytecode->toHex() << endl;
+ else
+ cerr << "No binary representation found." << endl;
+
+ cout << endl << "Text representation:" << endl;
+ if (!object.assembly.empty())
+ cout << object.assembly << endl;
+ else
+ cerr << "No text representation found." << endl;
}
+
+ return true;
}
void CommandLineInterface::outputCompilationResults()
@@ -904,8 +1133,9 @@ void CommandLineInterface::outputCompilationResults()
handleCombinedJSON();
// do we need AST output?
- handleAst(g_argAstStr);
+ handleAst(g_argAst);
handleAst(g_argAstJson);
+ handleAst(g_argAstCompactJson);
vector<string> contracts = m_compiler->contractNames();
for (string const& contract: contracts)
@@ -914,18 +1144,18 @@ void CommandLineInterface::outputCompilationResults()
cout << endl << "======= " << contract << " =======" << endl;
// do we need EVM assembly?
- if (m_args.count(g_argAsmStr) || m_args.count(g_argAsmJsonStr))
+ if (m_args.count(g_argAsm) || m_args.count(g_argAsmJson))
{
- if (m_args.count("output-dir"))
+ if (m_args.count(g_argOutputDir))
{
stringstream data;
- m_compiler->streamAssembly(data, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr));
- createFile(contract + (m_args.count(g_argAsmJsonStr) ? "_evm.json" : ".evm"), data.str());
+ m_compiler->streamAssembly(data, contract, m_sourceCodes, m_args.count(g_argAsmJson));
+ createFile(m_compiler->filesystemFriendlyName(contract) + (m_args.count(g_argAsmJson) ? "_evm.json" : ".evm"), data.str());
}
else
{
cout << "EVM assembly:" << endl;
- m_compiler->streamAssembly(cout, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr));
+ m_compiler->streamAssembly(cout, contract, m_sourceCodes, m_args.count(g_argAsmJson));
}
}
@@ -935,12 +1165,13 @@ void CommandLineInterface::outputCompilationResults()
handleBytecode(contract);
handleSignatureHashes(contract);
handleOnChainMetadata(contract);
- handleMeta(DocumentationType::ABIInterface, contract);
- handleMeta(DocumentationType::NatspecDev, contract);
- handleMeta(DocumentationType::NatspecUser, contract);
+ handleABI(contract);
+ handleNatspec(DocumentationType::NatspecDev, contract);
+ handleNatspec(DocumentationType::NatspecUser, contract);
} // end of contracts iteration
- handleFormal();
+ if (m_args.count(g_argFormal))
+ cerr << "Support for the Why3 output was removed." << endl;
}
}
diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h
index b8fc1823..b482c20b 100644
--- a/solc/CommandLineInterface.h
+++ b/solc/CommandLineInterface.h
@@ -21,11 +21,13 @@
*/
#pragma once
-#include <memory>
+#include <libsolidity/interface/CompilerStack.h>
+#include <libsolidity/interface/AssemblyStack.h>
+
#include <boost/program_options.hpp>
#include <boost/filesystem/path.hpp>
-#include <libsolidity/interface/CompilerStack.h>
-#include <libsolidity/inlineasm/AsmStack.h>
+
+#include <memory>
namespace dev
{
@@ -45,15 +47,14 @@ public:
/// Parse the files and create source code objects
bool processInput();
/// Perform actions on the input depending on provided compiler arguments
- void actOnInput();
+ /// @returns true on success.
+ bool actOnInput();
private:
bool link();
void writeLinkedFiles();
- /// Parse assembly input.
- bool assemble();
- void outputAssembly();
+ bool assemble(AssemblyStack::Language _language, AssemblyStack::Machine _targetMachine);
void outputCompilationResults();
@@ -64,7 +65,8 @@ private:
void handleBytecode(std::string const& _contract);
void handleSignatureHashes(std::string const& _contract);
void handleOnChainMetadata(std::string const& _contract);
- void handleMeta(DocumentationType _type, std::string const& _contract);
+ void handleABI(std::string const& _contract);
+ void handleNatspec(DocumentationType _type, std::string const& _contract);
void handleGasEstimation(std::string const& _contract);
void handleFormal();
@@ -79,7 +81,10 @@ private:
/// @arg _data to be written
void createFile(std::string const& _fileName, std::string const& _data);
+ bool m_error = false; ///< If true, some error occurred.
+
bool m_onlyAssemble = false;
+
bool m_onlyLink = false;
/// Compiler arguments variable map
@@ -92,8 +97,6 @@ private:
std::map<std::string, h160> m_libraries;
/// Solidity compiler stack
std::unique_ptr<dev::solidity::CompilerStack> m_compiler;
- /// Assembly stacks for assembly-only mode
- std::map<std::string, assembly::InlineAssemblyStack> m_assemblyStacks;
};
}
diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp
index d761b541..de797b3c 100644
--- a/solc/jsonCompiler.cpp
+++ b/solc/jsonCompiler.cpp
@@ -21,25 +21,13 @@
*/
#include <string>
-#include <functional>
-#include <iostream>
-#include <json/json.h>
#include <libdevcore/Common.h>
-#include <libdevcore/CommonData.h>
-#include <libdevcore/CommonIO.h>
#include <libdevcore/JSON.h>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/GasMeter.h>
-#include <libsolidity/parsing/Scanner.h>
-#include <libsolidity/parsing/Parser.h>
-#include <libsolidity/ast/ASTPrinter.h>
-#include <libsolidity/analysis/NameAndTypeResolver.h>
-#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/interface/CompilerStack.h>
-#include <libsolidity/interface/SourceReferenceFormatter.h>
-#include <libsolidity/ast/ASTJsonConverter.h>
+#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/Version.h>
+#include "license.h"
+
using namespace std;
using namespace dev;
using namespace solidity;
@@ -50,87 +38,9 @@ extern "C" {
typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error);
}
-string formatError(
- Exception const& _exception,
- string const& _name,
- function<Scanner const&(string const&)> const& _scannerFromSourceName
-)
-{
- ostringstream errorOutput;
- SourceReferenceFormatter::printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName);
- return errorOutput.str();
-}
-
-Json::Value functionHashes(ContractDefinition const& _contract)
+ReadFile::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullptr)
{
- Json::Value functionHashes(Json::objectValue);
- for (auto const& it: _contract.interfaceFunctions())
- functionHashes[it.second->externalSignature()] = toHex(it.first.ref());
- return functionHashes;
-}
-
-Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
-{
- if (_gas.isInfinite || _gas.value > std::numeric_limits<Json::LargestUInt>::max())
- return Json::Value(Json::nullValue);
- else
- return Json::Value(Json::LargestUInt(_gas.value));
-}
-
-Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract)
-{
- Json::Value gasEstimates(Json::objectValue);
- using Gas = GasEstimator::GasConsumption;
- if (!_compiler.assemblyItems(_contract) && !_compiler.runtimeAssemblyItems(_contract))
- return gasEstimates;
- if (eth::AssemblyItems const* items = _compiler.assemblyItems(_contract))
- {
- Gas gas = GasEstimator::functionalEstimation(*items);
- u256 bytecodeSize(_compiler.runtimeObject(_contract).bytecode.size());
- Json::Value creationGas(Json::arrayValue);
- creationGas[0] = gasToJson(gas);
- creationGas[1] = gasToJson(bytecodeSize * eth::GasCosts::createDataGas);
- gasEstimates["creation"] = creationGas;
- }
- if (eth::AssemblyItems const* items = _compiler.runtimeAssemblyItems(_contract))
- {
- ContractDefinition const& contract = _compiler.contractDefinition(_contract);
- Json::Value externalFunctions(Json::objectValue);
- for (auto it: contract.interfaceFunctions())
- {
- string sig = it.second->externalSignature();
- externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig));
- }
- if (contract.fallbackFunction())
- externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID"));
- gasEstimates["external"] = externalFunctions;
- Json::Value internalFunctions(Json::objectValue);
- for (auto const& it: contract.definedFunctions())
- {
- if (it->isPartOfExternalInterface() || it->isConstructor())
- continue;
- size_t entry = _compiler.functionEntryPoint(_contract, *it);
- GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite();
- if (entry > 0)
- gas = GasEstimator::functionalEstimation(*items, entry, *it);
- FunctionType type(*it);
- string sig = it->name() + "(";
- auto paramTypes = type.parameterTypes();
- for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it)
- sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ",");
- sig += ")";
- internalFunctions[sig] = gasToJson(gas);
- }
- gasEstimates["internal"] = internalFunctions;
- }
- return gasEstimates;
-}
-
-string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback)
-{
- Json::Value output(Json::objectValue);
- Json::Value errors(Json::arrayValue);
- CompilerStack::ReadFileCallback readCallback;
+ ReadFile::Callback readCallback;
if (_readCallback)
{
readCallback = [=](string const& _path)
@@ -138,140 +48,169 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback
char* contents_c = nullptr;
char* error_c = nullptr;
_readCallback(_path.c_str(), &contents_c, &error_c);
- CompilerStack::ReadFileResult result;
+ ReadFile::Result result;
result.success = true;
if (!contents_c && !error_c)
{
result.success = false;
- result.contentsOrErrorMesage = "File not found.";
+ result.contentsOrErrorMessage = "File not found.";
}
if (contents_c)
{
result.success = true;
- result.contentsOrErrorMesage = string(contents_c);
+ result.contentsOrErrorMessage = string(contents_c);
free(contents_c);
}
if (error_c)
{
result.success = false;
- result.contentsOrErrorMesage = string(error_c);
+ result.contentsOrErrorMessage = string(error_c);
free(error_c);
}
return result;
};
}
- CompilerStack compiler(readCallback);
- auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return compiler.scanner(_sourceName); };
- bool success = false;
- try
- {
- compiler.addSources(_sources);
- bool succ = compiler.compile(_optimize);
- for (auto const& error: compiler.errors())
- {
- auto err = dynamic_pointer_cast<Error const>(error);
- errors.append(formatError(
- *error,
- (err->type() == Error::Type::Warning) ? "Warning" : "Error",
- scannerFromSourceName
- ));
- }
- success = succ; // keep success false on exception
- }
- catch (Error const& error)
- {
- errors.append(formatError(error, error.typeName(), scannerFromSourceName));
- }
- catch (CompilerError const& exception)
- {
- errors.append(formatError(exception, "Compiler error", scannerFromSourceName));
- }
- catch (InternalCompilerError const& exception)
+ return readCallback;
+}
+
+/// Translates a gas value as a string to a JSON number or null
+Json::Value gasToJson(Json::Value const& _value)
+{
+ if (_value.isObject())
{
- errors.append(formatError(exception, "Internal compiler error", scannerFromSourceName));
+ Json::Value ret = Json::objectValue;
+ for (auto const& sig: _value.getMemberNames())
+ ret[sig] = gasToJson(_value[sig]);
+ return ret;
}
- catch (UnimplementedFeatureError const& exception)
+
+ if (_value == "infinite")
+ return Json::Value(Json::nullValue);
+
+ u256 value(_value.asString());
+ if (value > std::numeric_limits<Json::LargestUInt>::max())
+ return Json::Value(Json::nullValue);
+ else
+ return Json::Value(Json::LargestUInt(value));
+}
+
+Json::Value translateGasEstimates(Json::Value const& estimates)
+{
+ Json::Value output(Json::objectValue);
+
+ if (estimates["creation"].isObject())
{
- errors.append(formatError(exception, "Unimplemented feature", scannerFromSourceName));
+ Json::Value creation(Json::arrayValue);
+ creation[0] = gasToJson(estimates["creation"]["executionCost"]);
+ creation[1] = gasToJson(estimates["creation"]["codeDepositCost"]);
+ output["creation"] = creation;
}
- catch (Exception const& exception)
+ else
+ output["creation"] = Json::objectValue;
+ output["external"] = gasToJson(estimates.get("external", Json::objectValue));
+ output["internal"] = gasToJson(estimates.get("internal", Json::objectValue));
+
+ return output;
+}
+
+string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback)
+{
+ /// create new JSON input format
+ Json::Value input = Json::objectValue;
+ input["language"] = "Solidity";
+ input["sources"] = Json::objectValue;
+ for (auto const& source: _sources)
{
- errors.append("Exception during compilation: " + boost::diagnostic_information(exception));
+ input["sources"][source.first] = Json::objectValue;
+ input["sources"][source.first]["content"] = source.second;
}
- catch (...)
+ input["settings"] = Json::objectValue;
+ input["settings"]["optimizer"] = Json::objectValue;
+ input["settings"]["optimizer"]["enabled"] = _optimize;
+ input["settings"]["optimizer"]["runs"] = 200;
+
+ StandardCompiler compiler(wrapReadCallback(_readCallback));
+ Json::Value ret = compiler.compile(input);
+
+ /// transform JSON to match the old format
+ // {
+ // "errors": [ "Error 1", "Error 2" ],
+ // "sourceList": [ "sourcename1", "sourcename2" ],
+ // "sources": {
+ // "sourcename1": {
+ // "AST": {}
+ // }
+ // },
+ // "contracts": {
+ // "Contract1": {
+ // "interface": "[...abi...]",
+ // "bytecode": "ff0011...",
+ // "runtimeBytecode": "ff0011",
+ // "opcodes": "PUSH 1 POP STOP",
+ // "metadata": "{...metadata...}",
+ // "functionHashes": {
+ // "test(uint256)": "11ff2233"
+ // },
+ // "gasEstimates": {
+ // "creation": [ 224, 42000 ],
+ // "external": {
+ // "11ff2233": null,
+ // "3322ff11": 1234
+ // },
+ // "internal": {
+ // }
+ // },
+ // "srcmap" = "0:1:2",
+ // "srcmapRuntime" = "0:1:2",
+ // "assembly" = {}
+ // }
+ // }
+ // }
+ Json::Value output = Json::objectValue;
+
+ if (ret.isMember("errors"))
{
- errors.append("Unknown exception during compilation.");
+ output["errors"] = Json::arrayValue;
+ for (auto const& error: ret["errors"])
+ output["errors"].append(
+ !error["formattedMessage"].empty() ? error["formattedMessage"] : error["message"]
+ );
}
- if (errors.size() > 0)
- output["errors"] = errors;
+ output["sourceList"] = Json::arrayValue;
+ for (auto const& source: _sources)
+ output["sourceList"].append(source.first);
- if (success)
+ if (ret.isMember("sources"))
{
- try
- {
- output["contracts"] = Json::Value(Json::objectValue);
- for (string const& contractName: compiler.contractNames())
- {
- Json::Value contractData(Json::objectValue);
- contractData["interface"] = dev::jsonCompactPrint(compiler.interface(contractName));
- contractData["bytecode"] = compiler.object(contractName).toHex();
- contractData["runtimeBytecode"] = compiler.runtimeObject(contractName).toHex();
- contractData["opcodes"] = solidity::disassemble(compiler.object(contractName).bytecode);
- contractData["metadata"] = compiler.onChainMetadata(contractName);
- contractData["functionHashes"] = functionHashes(compiler.contractDefinition(contractName));
- contractData["gasEstimates"] = estimateGas(compiler, contractName);
- auto sourceMap = compiler.sourceMapping(contractName);
- contractData["srcmap"] = sourceMap ? *sourceMap : "";
- auto runtimeSourceMap = compiler.runtimeSourceMapping(contractName);
- contractData["srcmapRuntime"] = runtimeSourceMap ? *runtimeSourceMap : "";
- ostringstream unused;
- contractData["assembly"] = compiler.streamAssembly(unused, contractName, _sources, true);
- output["contracts"][contractName] = contractData;
- }
- }
- catch (...)
+ output["sources"] = Json::objectValue;
+ for (auto const& sourceName: ret["sources"].getMemberNames())
{
- output["errors"].append("Unknown exception while generating contract data output.");
+ output["sources"][sourceName] = Json::objectValue;
+ output["sources"][sourceName]["AST"] = ret["sources"][sourceName]["legacyAST"];
}
+ }
- try
- {
- // Do not taint the internal error list
- ErrorList formalErrors;
- if (compiler.prepareFormalAnalysis(&formalErrors))
- output["formal"]["why3"] = compiler.formalTranslation();
- if (!formalErrors.empty())
+ if (ret.isMember("contracts"))
+ {
+ output["contracts"] = Json::objectValue;
+ for (auto const& sourceName: ret["contracts"].getMemberNames())
+ for (auto const& contractName: ret["contracts"][sourceName].getMemberNames())
{
- Json::Value errors(Json::arrayValue);
- for (auto const& error: formalErrors)
- errors.append(formatError(
- *error,
- (error->type() == Error::Type::Warning) ? "Warning" : "Error",
- scannerFromSourceName
- ));
- output["formal"]["errors"] = errors;
+ Json::Value contractInput = ret["contracts"][sourceName][contractName];
+ Json::Value contractOutput = Json::objectValue;
+ contractOutput["interface"] = dev::jsonCompactPrint(contractInput["abi"]);
+ contractOutput["metadata"] = contractInput["metadata"];
+ contractOutput["functionHashes"] = contractInput["evm"]["methodIdentifiers"];
+ contractOutput["gasEstimates"] = translateGasEstimates(contractInput["evm"]["gasEstimates"]);
+ contractOutput["assembly"] = contractInput["evm"]["legacyAssembly"];
+ contractOutput["bytecode"] = contractInput["evm"]["bytecode"]["object"];
+ contractOutput["opcodes"] = contractInput["evm"]["bytecode"]["opcodes"];
+ contractOutput["srcmap"] = contractInput["evm"]["bytecode"]["sourceMap"];
+ contractOutput["runtimeBytecode"] = contractInput["evm"]["deployedBytecode"]["object"];
+ contractOutput["srcmapRuntime"] = contractInput["evm"]["deployedBytecode"]["sourceMap"];
+ output["contracts"][sourceName + ":" + contractName] = contractOutput;
}
- }
- catch (...)
- {
- output["errors"].append("Unknown exception while generating formal method output.");
- }
-
- try
- {
- // Indices into this array are used to abbreviate source names in source locations.
- output["sourceList"] = Json::Value(Json::arrayValue);
- for (auto const& source: compiler.sourceNames())
- output["sourceList"].append(source);
- output["sources"] = Json::Value(Json::objectValue);
- for (auto const& source: compiler.sourceNames())
- output["sources"][source]["AST"] = ASTJsonConverter(compiler.ast(source), compiler.sourceIndices()).json();
- }
- catch (...)
- {
- output["errors"].append("Unknown exception while generating source name output.");
- }
}
try
@@ -314,10 +253,22 @@ string compileSingle(string const& _input, bool _optimize)
return compile(sources, _optimize, nullptr);
}
+
+string compileStandardInternal(string const& _input, CStyleReadFileCallback _readCallback = nullptr)
+{
+ StandardCompiler compiler(wrapReadCallback(_readCallback));
+ return compiler.compile(_input);
+}
+
static string s_outputBuffer;
extern "C"
{
+extern char const* license()
+{
+ static string fullLicenseText = otherLicenses + licenseText;
+ return fullLicenseText.c_str();
+}
extern char const* version()
{
return VersionString.c_str();
@@ -337,4 +288,9 @@ extern char const* compileJSONCallback(char const* _input, bool _optimize, CStyl
s_outputBuffer = compileMulti(_input, _optimize, _readCallback);
return s_outputBuffer.c_str();
}
+extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback)
+{
+ s_outputBuffer = compileStandardInternal(_input, _readCallback);
+ return s_outputBuffer.c_str();
+}
}
diff --git a/solc/main.cpp b/solc/main.cpp
index 28726e26..c61da6e9 100644
--- a/solc/main.cpp
+++ b/solc/main.cpp
@@ -58,15 +58,16 @@ int main(int argc, char** argv)
return 1;
if (!cli.processInput())
return 1;
+ bool success = false;
try
{
- cli.actOnInput();
+ success = cli.actOnInput();
}
catch (boost::exception const& _exception)
{
cerr << "Exception during output generation: " << boost::diagnostic_information(_exception) << endl;
- return 1;
+ success = false;
}
- return 0;
+ return success ? 0 : 1;
}
diff --git a/std/StandardToken.sol b/std/StandardToken.sol
index 4ff1b8f9..51f925e0 100644
--- a/std/StandardToken.sol
+++ b/std/StandardToken.sol
@@ -3,31 +3,43 @@ pragma solidity ^0.4.0;
import "./Token.sol";
contract StandardToken is Token {
- uint256 public totalSupply;
- mapping (address => uint256) public balanceOf;
+ uint256 supply;
+ mapping (address => uint256) balance;
mapping (address =>
- mapping (address => uint256)) public allowance;
+ mapping (address => uint256)) m_allowance;
function StandardToken(address _initialOwner, uint256 _supply) {
- totalSupply = _supply;
- balanceOf[_initialOwner] = _supply;
+ supply = _supply;
+ balance[_initialOwner] = _supply;
+ }
+
+ function balanceOf(address _account) constant returns (uint) {
+ return balance[_account];
+ }
+
+ function totalSupply() constant returns (uint) {
+ return supply;
}
function transfer(address _to, uint256 _value) returns (bool success) {
- if (balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]) {
- balanceOf[msg.sender] -= _value;
- balanceOf[_to] += _value;
- Transfer(msg.sender, _to, _value);
+ return doTransfer(msg.sender, _to, _value);
+ }
+
+ function transferFrom(address _from, address _to, uint256 _value) returns (bool) {
+ if (m_allowance[_from][msg.sender] >= _value) {
+ if (doTransfer(_from, _to, _value)) {
+ m_allowance[_from][msg.sender] -= _value;
+ }
return true;
} else {
return false;
}
}
- function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
- if (allowance[_from][msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]) {
- allowance[_from][msg.sender] -= _value;
- balanceOf[_to] += _value;
+ function doTransfer(address _from, address _to, uint _value) internal returns (bool success) {
+ if (balance[_from] >= _value && balance[_to] + _value >= balance[_to]) {
+ balance[_from] -= _value;
+ balance[_to] += _value;
Transfer(_from, _to, _value);
return true;
} else {
@@ -36,8 +48,12 @@ contract StandardToken is Token {
}
function approve(address _spender, uint256 _value) returns (bool success) {
- allowance[msg.sender][_spender] = _value;
+ m_allowance[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
+
+ function allowance(address _owner, address _spender) constant returns (uint256) {
+ return m_allowance[_owner][_spender];
+ }
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 609aaab3..8e7b8916 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -4,26 +4,14 @@ aux_source_directory(. SRC_LIST)
aux_source_directory(libdevcore SRC_LIST)
aux_source_directory(libevmasm SRC_LIST)
aux_source_directory(libsolidity SRC_LIST)
+aux_source_directory(libjulia SRC_LIST)
aux_source_directory(contracts SRC_LIST)
aux_source_directory(liblll SRC_LIST)
+aux_source_directory(libjulia SRC_LIST)
-get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
+list(REMOVE_ITEM SRC_LIST "./fuzzer.cpp")
-# search for test names and create ctest tests
-enable_testing()
-foreach(file ${SRC_LIST})
- file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/${file} test_list_raw REGEX "BOOST_.*TEST_(SUITE|CASE)")
- set(TestSuite "DEFAULT")
- foreach(test_raw ${test_list_raw})
- string(REGEX REPLACE ".*TEST_(SUITE|CASE)\\(([^ ,\\)]*).*" "\\1 \\2" test ${test_raw})
- if(test MATCHES "^SUITE .*")
- string(SUBSTRING ${test} 6 -1 TestSuite)
- elseif(test MATCHES "^CASE .*")
- string(SUBSTRING ${test} 5 -1 TestCase)
- add_test(NAME ${TestSuite}/${TestCase} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND test -t ${TestSuite}/${TestCase})
- endif(test MATCHES "^SUITE .*")
- endforeach(test_raw)
-endforeach(file)
+get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
file(GLOB HEADERS "*.h" "*/*.h")
set(EXECUTABLE soltest)
@@ -32,7 +20,7 @@ eth_simple_add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS})
eth_use(${EXECUTABLE} REQUIRED Solidity::solidity Solidity::lll)
include_directories(BEFORE ..)
-target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
+target_link_libraries(${EXECUTABLE} soljson ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
-enable_testing()
-set(CTEST_OUTPUT_ON_FAILURE TRUE)
+add_executable(solfuzzer fuzzer.cpp)
+target_link_libraries(solfuzzer soljson ${Boost_PROGRAM_OPTIONS_LIBRARIES})
diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp
index 0c6e0cff..f4e5fcef 100644
--- a/test/ExecutionFramework.cpp
+++ b/test/ExecutionFramework.cpp
@@ -46,6 +46,7 @@ string getIPCSocketPath()
ExecutionFramework::ExecutionFramework() :
m_rpc(RPCSession::instance(getIPCSocketPath())),
m_optimize(dev::test::Options::get().optimize),
+ m_showMessages(dev::test::Options::get().showMessages),
m_sender(m_rpc.account(0))
{
m_rpc.test_rewindToBlock(0);
@@ -53,6 +54,16 @@ ExecutionFramework::ExecutionFramework() :
void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256 const& _value)
{
+ if (m_showMessages)
+ {
+ if (_isCreation)
+ cout << "CREATE " << m_sender.hex() << ":" << endl;
+ else
+ cout << "CALL " << m_sender.hex() << " -> " << m_contractAddress.hex() << ":" << endl;
+ if (_value > 0)
+ cout << " value: " << _value << endl;
+ cout << " in: " << toHex(_data) << endl;
+ }
RPCSession::TransactionData d;
d.data = "0x" + toHex(_data);
d.from = "0x" + toString(m_sender);
@@ -71,6 +82,8 @@ void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256
m_rpc.test_mineBlocks(1);
RPCSession::TransactionReceipt receipt(m_rpc.eth_getTransactionReceipt(txHash));
+ m_blockNumber = u256(receipt.blockNumber);
+
if (_isCreation)
{
m_contractAddress = Address(receipt.contractAddress);
@@ -79,6 +92,12 @@ void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256
m_output = fromHex(code, WhenError::Throw);
}
+ if (m_showMessages)
+ {
+ cout << " out: " << toHex(m_output) << endl;
+ cout << " tx hash: " << txHash << endl;
+ }
+
m_gasUsed = u256(receipt.gasUsed);
m_logs.clear();
for (auto const& log: receipt.logEntries)
@@ -108,7 +127,13 @@ void ExecutionFramework::sendEther(Address const& _to, u256 const& _value)
size_t ExecutionFramework::currentTimestamp()
{
- auto latestBlock = m_rpc.rpcCall("eth_getBlockByNumber", {"\"latest\"", "false"});
+ auto latestBlock = m_rpc.eth_getBlockByNumber("latest", false);
+ return size_t(u256(latestBlock.get("timestamp", "invalid").asString()));
+}
+
+size_t ExecutionFramework::blockTimestamp(u256 _number)
+{
+ auto latestBlock = m_rpc.eth_getBlockByNumber(toString(_number), false);
return size_t(u256(latestBlock.get("timestamp", "invalid").asString()));
}
diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h
index f47f2743..76d0fd8c 100644
--- a/test/ExecutionFramework.h
+++ b/test/ExecutionFramework.h
@@ -262,6 +262,7 @@ protected:
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
void sendEther(Address const& _to, u256 const& _value);
size_t currentTimestamp();
+ size_t blockTimestamp(u256 number);
/// @returns the (potentially newly created) _ith address.
Address account(size_t _i);
@@ -281,8 +282,10 @@ protected:
unsigned m_optimizeRuns = 200;
bool m_optimize = false;
+ bool m_showMessages = false;
Address m_sender;
Address m_contractAddress;
+ u256 m_blockNumber;
u256 const m_gasPrice = 100 * szabo;
u256 const m_gas = 100000000;
bytes m_output;
diff --git a/test/Metadata.cpp b/test/Metadata.cpp
new file mode 100644
index 00000000..03f905b1
--- /dev/null
+++ b/test/Metadata.cpp
@@ -0,0 +1,80 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Metadata processing helpers.
+ */
+
+#include <string>
+#include <iostream>
+#include <regex>
+#include <libdevcore/JSON.h>
+
+using namespace std;
+
+namespace dev
+{
+namespace test
+{
+
+string bytecodeSansMetadata(string const& _bytecode)
+{
+ /// The metadata hash takes up 43 bytes (or 86 characters in hex)
+ /// /a165627a7a72305820([0-9a-f]{64})0029$/
+
+ if (_bytecode.size() < 88)
+ return _bytecode;
+
+ if (_bytecode.substr(_bytecode.size() - 4, 4) != "0029")
+ return _bytecode;
+
+ if (_bytecode.substr(_bytecode.size() - 86, 18) != "a165627a7a72305820")
+ return _bytecode;
+
+ return _bytecode.substr(0, _bytecode.size() - 86);
+}
+
+bool isValidMetadata(string const& _metadata)
+{
+ Json::Value metadata;
+ if (!Json::Reader().parse(_metadata, metadata, false))
+ return false;
+
+ if (
+ !metadata.isObject() ||
+ !metadata.isMember("version") ||
+ !metadata.isMember("language") ||
+ !metadata.isMember("compiler") ||
+ !metadata.isMember("settings") ||
+ !metadata.isMember("sources") ||
+ !metadata.isMember("output")
+ )
+ return false;
+
+ if (!metadata["version"].isNumeric() || metadata["version"] != 1)
+ return false;
+
+ if (!metadata["language"].isString() || metadata["language"].asString() != "Solidity")
+ return false;
+
+ /// @TODO add more strict checks
+
+ return true;
+}
+
+}
+} // end namespaces
diff --git a/test/Metadata.h b/test/Metadata.h
new file mode 100644
index 00000000..cd92ecd8
--- /dev/null
+++ b/test/Metadata.h
@@ -0,0 +1,37 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @date 2017
+ * Metadata processing helpers.
+ */
+
+#include <string>
+
+namespace dev
+{
+namespace test
+{
+
+/// Returns the bytecode with the metadata hash stripped out.
+std::string bytecodeSansMetadata(std::string const& _bytecode);
+
+/// Expects a serialised metadata JSON and returns true if the
+/// content is valid metadata.
+bool isValidMetadata(std::string const& _metadata);
+
+}
+} // end namespaces
diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp
index 44d21d69..c4fbfefb 100644
--- a/test/RPCSession.cpp
+++ b/test/RPCSession.cpp
@@ -16,18 +16,20 @@
The Implementation originally from https://msdn.microsoft.com/en-us/library/windows/desktop/aa365592(v=vs.85).aspx
*/
-/** @file RPCSession.cpp
- * @author Dimtiry Khokhlov <dimitry@ethdev.com>
- * @date 2016
- */
+/// @file RPCSession.cpp
+/// Low-level IPC communication between the test framework and the Ethereum node.
+
+#include "RPCSession.h"
-#include <string>
-#include <stdio.h>
-#include <thread>
#include <libdevcore/CommonData.h>
+
#include <json/reader.h>
#include <json/writer.h>
-#include "RPCSession.h"
+
+#include <string>
+#include <stdio.h>
+#include <thread>
+#include <chrono>
using namespace std;
using namespace dev;
@@ -73,15 +75,13 @@ IPCSocket::IPCSocket(string const& _path): m_path(_path)
if (connect(m_socket, reinterpret_cast<struct sockaddr const*>(&saun), sizeof(struct sockaddr_un)) < 0)
BOOST_FAIL("Error connecting to IPC socket: " << _path);
-
- m_fp = fdopen(m_socket, "r");
#endif
}
string IPCSocket::sendRequest(string const& _req)
{
#if defined(_WIN32)
- string returnStr;
+ // Write to the pipe.
DWORD cbWritten;
BOOL fSuccess = WriteFile(
m_socket, // pipe handle
@@ -90,40 +90,44 @@ string IPCSocket::sendRequest(string const& _req)
&cbWritten, // bytes written
NULL); // not overlapped
- if (!fSuccess)
+ if (!fSuccess || (_req.size() != cbWritten))
BOOST_FAIL("WriteFile to pipe failed");
- DWORD cbRead;
- TCHAR chBuf[c_buffsize];
-
// Read from the pipe.
+ DWORD cbRead;
fSuccess = ReadFile(
- m_socket, // pipe handle
- chBuf, // buffer to receive reply
- c_buffsize,// size of buffer
- &cbRead, // number of bytes read
- NULL); // not overlapped
-
- returnStr += chBuf;
+ m_socket, // pipe handle
+ m_readBuf, // buffer to receive reply
+ sizeof(m_readBuf), // size of buffer
+ &cbRead, // number of bytes read
+ NULL); // not overlapped
if (!fSuccess)
BOOST_FAIL("ReadFile from pipe failed");
- cerr << "."; //Output for log activity
- return returnStr;
+ return string(m_readBuf, m_readBuf + cbRead);
#else
- send(m_socket, _req.c_str(), _req.length(), 0);
+ if (send(m_socket, _req.c_str(), _req.length(), 0) != (ssize_t)_req.length())
+ BOOST_FAIL("Writing on IPC failed.");
- char c;
- string response;
- while ((c = fgetc(m_fp)) != EOF)
+ auto start = chrono::steady_clock::now();
+ ssize_t ret;
+ do
{
- if (c != '\n')
- response += c;
- else
- break;
+ ret = recv(m_socket, m_readBuf, sizeof(m_readBuf), 0);
+ // Also consider closed socket an error.
+ if (ret < 0)
+ BOOST_FAIL("Reading on IPC failed.");
}
- return response;
+ while (
+ ret == 0 &&
+ chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count() < m_readTimeOutMS
+ );
+
+ if (ret == 0)
+ BOOST_FAIL("Timeout reading on IPC.");
+
+ return string(m_readBuf, m_readBuf + ret);
#endif
}
@@ -139,6 +143,12 @@ string RPCSession::eth_getCode(string const& _address, string const& _blockNumbe
return rpcCall("eth_getCode", { quote(_address), quote(_blockNumber) }).asString();
}
+Json::Value RPCSession::eth_getBlockByNumber(string const& _blockNumber, bool _fullObjects)
+{
+ // NOTE: to_string() converts bool to 0 or 1
+ return rpcCall("eth_getBlockByNumber", { quote(_blockNumber), _fullObjects ? "true" : "false" });
+}
+
RPCSession::TransactionReceipt RPCSession::eth_getTransactionReceipt(string const& _transactionHash)
{
TransactionReceipt receipt;
@@ -146,6 +156,7 @@ RPCSession::TransactionReceipt RPCSession::eth_getTransactionReceipt(string cons
BOOST_REQUIRE(!result.isNull());
receipt.gasUsed = result["gasUsed"].asString();
receipt.contractAddress = result["contractAddress"].asString();
+ receipt.blockNumber = result["blockNumber"].asString();
for (auto const& log: result["logs"])
{
LogEntry entry;
@@ -187,12 +198,17 @@ string RPCSession::eth_getStorageRoot(string const& _address, string const& _blo
void RPCSession::personal_unlockAccount(string const& _address, string const& _password, int _duration)
{
- rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) });
+ BOOST_REQUIRE_MESSAGE(
+ rpcCall("personal_unlockAccount", { quote(_address), quote(_password), to_string(_duration) }),
+ "Error unlocking account " + _address
+ );
}
string RPCSession::personal_newAccount(string const& _password)
{
- return rpcCall("personal_newAccount", { quote(_password) }).asString();
+ string addr = rpcCall("personal_newAccount", { quote(_password) }).asString();
+ BOOST_TEST_MESSAGE("Created account " + addr);
+ return addr;
}
void RPCSession::test_setChainParams(vector<string> const& _accounts)
@@ -204,7 +220,10 @@ void RPCSession::test_setChainParams(vector<string> const& _accounts)
"accountStartNonce": "0x",
"maximumExtraDataSize": "0x1000000",
"blockReward": "0x",
- "allowFutureBlocks": "1"
+ "allowFutureBlocks": "1",
+ "homsteadForkBlock": "0x00",
+ "EIP150ForkBlock": "0x00",
+ "EIP158ForkBlock": "0x00"
},
"genesis": {
"author": "0000000000000010000000000000000000000000",
@@ -231,40 +250,43 @@ void RPCSession::test_setChainParams(vector<string> const& _accounts)
void RPCSession::test_setChainParams(string const& _config)
{
- rpcCall("test_setChainParams", { _config });
+ BOOST_REQUIRE(rpcCall("test_setChainParams", { _config }) == true);
}
void RPCSession::test_rewindToBlock(size_t _blockNr)
{
- rpcCall("test_rewindToBlock", { to_string(_blockNr) });
+ BOOST_REQUIRE(rpcCall("test_rewindToBlock", { to_string(_blockNr) }) == true);
}
void RPCSession::test_mineBlocks(int _number)
{
u256 startBlock = fromBigEndian<u256>(fromHex(rpcCall("eth_blockNumber").asString()));
- rpcCall("test_mineBlocks", { to_string(_number) }, true);
-
- bool mined = false;
+ BOOST_REQUIRE(rpcCall("test_mineBlocks", { to_string(_number) }, true) == true);
// We auto-calibrate the time it takes to mine the transaction.
// It would be better to go without polling, but that would probably need a change to the test client
+ auto startTime = std::chrono::steady_clock::now();
unsigned sleepTime = m_sleepTime;
- size_t polls = 0;
- for (; polls < 14 && !mined; ++polls)
+ size_t tries = 0;
+ for (; ; ++tries)
{
std::this_thread::sleep_for(chrono::milliseconds(sleepTime));
+ auto endTime = std::chrono::steady_clock::now();
+ unsigned timeSpent = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
+ if (timeSpent > m_maxMiningTime)
+ BOOST_FAIL("Error in test_mineBlocks: block mining timeout!");
if (fromBigEndian<u256>(fromHex(rpcCall("eth_blockNumber").asString())) >= startBlock + _number)
- mined = true;
+ break;
else
sleepTime *= 2;
}
- if (polls > 1)
+ if (tries > 1)
{
m_successfulMineRuns = 0;
m_sleepTime += 2;
}
- else if (polls == 1)
+ else if (tries == 1)
{
m_successfulMineRuns++;
if (m_successfulMineRuns > 5)
@@ -274,14 +296,11 @@ void RPCSession::test_mineBlocks(int _number)
m_sleepTime--;
}
}
-
- if (!mined)
- BOOST_FAIL("Error in test_mineBlocks: block mining timeout!");
}
void RPCSession::test_modifyTimestamp(size_t _timestamp)
{
- rpcCall("test_modifyTimestamp", { to_string(_timestamp) });
+ BOOST_REQUIRE(rpcCall("test_modifyTimestamp", { to_string(_timestamp) }) == true);
}
Json::Value RPCSession::rpcCall(string const& _methodName, vector<string> const& _args, bool _canFail)
@@ -297,12 +316,12 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector<string> const&
request += "],\"id\":" + to_string(m_rpcSequence) + "}";
++m_rpcSequence;
- //cout << "Request: " << request << endl;
+ BOOST_TEST_MESSAGE("Request: " + request);
string reply = m_ipcSocket.sendRequest(request);
- //cout << "Reply: " << reply << endl;
+ BOOST_TEST_MESSAGE("Reply: " + reply);
Json::Value result;
- Json::Reader().parse(reply, result, false);
+ BOOST_REQUIRE(Json::Reader().parse(reply, result, false));
if (result.isMember("error"))
{
diff --git a/test/RPCSession.h b/test/RPCSession.h
index fc166b99..f3c3339a 100644
--- a/test/RPCSession.h
+++ b/test/RPCSession.h
@@ -28,14 +28,16 @@
#include <sys/un.h>
#endif
+#include <json/value.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/test/unit_test.hpp>
+
#include <string>
#include <stdio.h>
#include <map>
-#include <json/value.h>
-#include <boost/test/unit_test.hpp>
#if defined(_WIN32)
-const int c_buffsize = 5120000; //because windows pipe is broken and wont work as in examples. use larger buffer limit to receive whole package in one call
class IPCSocket : public boost::noncopyable
{
public:
@@ -47,7 +49,8 @@ public:
private:
std::string m_path;
- HANDLE m_socket;
+ HANDLE m_socket;
+ TCHAR m_readBuf[512000];
};
#else
class IPCSocket: public boost::noncopyable
@@ -55,14 +58,18 @@ class IPCSocket: public boost::noncopyable
public:
IPCSocket(std::string const& _path);
std::string sendRequest(std::string const& _req);
- ~IPCSocket() { close(m_socket); fclose(m_fp); }
+ ~IPCSocket() { close(m_socket); }
std::string const& path() const { return m_path; }
private:
- FILE *m_fp;
+
std::string m_path;
int m_socket;
+ /// Socket read timeout in milliseconds. Needs to be large because the key generation routine
+ /// might take long.
+ unsigned static constexpr m_readTimeOutMS = 300000;
+ char m_readBuf[512000];
};
#endif
@@ -92,11 +99,13 @@ public:
std::string gasUsed;
std::string contractAddress;
std::vector<LogEntry> logEntries;
+ std::string blockNumber;
};
static RPCSession& instance(std::string const& _path);
std::string eth_getCode(std::string const& _address, std::string const& _blockNumber);
+ Json::Value eth_getBlockByNumber(std::string const& _blockNumber, bool _fullObjects);
std::string eth_call(TransactionData const& _td, std::string const& _blockNumber);
TransactionReceipt eth_getTransactionReceipt(std::string const& _transactionHash);
std::string eth_sendTransaction(TransactionData const& _transactionData);
@@ -124,7 +133,8 @@ private:
IPCSocket m_ipcSocket;
size_t m_rpcSequence = 1;
- unsigned m_sleepTime = 10;
+ unsigned m_maxMiningTime = 6000000; // 600 seconds
+ unsigned m_sleepTime = 10; // 10 milliseconds
unsigned m_successfulMineRuns = 0;
std::vector<std::string> m_accounts;
diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp
index d670ebff..094b59c6 100644
--- a/test/TestHelper.cpp
+++ b/test/TestHelper.cpp
@@ -41,8 +41,12 @@ Options::Options()
}
else if (string(suite.argv[i]) == "--optimize")
optimize = true;
+ else if (string(suite.argv[i]) == "--show-messages")
+ showMessages = true;
+ else if (string(suite.argv[i]) == "--no-ipc")
+ disableIPC = true;
- if (ipcPath.empty())
+ if (!disableIPC && ipcPath.empty())
if (auto path = getenv("ETH_TEST_IPC"))
ipcPath = path;
}
diff --git a/test/TestHelper.h b/test/TestHelper.h
index afe4a68f..3e74b54c 100644
--- a/test/TestHelper.h
+++ b/test/TestHelper.h
@@ -106,7 +106,9 @@ namespace test
struct Options: boost::noncopyable
{
std::string ipcPath;
+ bool showMessages = false;
bool optimize = false;
+ bool disableIPC = false;
static Options const& get();
diff --git a/test/boostTest.cpp b/test/boostTest.cpp
index d1d35be3..6fc1c925 100644
--- a/test/boostTest.cpp
+++ b/test/boostTest.cpp
@@ -21,11 +21,9 @@
* Original code taken from boost sources.
*/
-#define BOOST_TEST_MODULE EthereumTests
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
-
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4535) // calling _set_se_translator requires /EHa
@@ -36,3 +34,32 @@
#endif
#pragma GCC diagnostic pop
+
+#include <test/TestHelper.h>
+
+using namespace boost::unit_test;
+
+test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] )
+{
+ master_test_suite_t& master = framework::master_test_suite();
+ master.p_name.value = "SolidityTests";
+ if (dev::test::Options::get().disableIPC)
+ {
+ for (auto suite: {
+ "SolidityAuctionRegistrar",
+ "SolidityFixedFeeRegistrar",
+ "SolidityWallet",
+ "LLLEndToEndTest",
+ "GasMeterTests",
+ "SolidityEndToEndTest",
+ "SolidityOptimizer"
+ })
+ {
+ auto id = master.get(suite);
+ assert(id != INV_TEST_UNIT_ID);
+ master.remove(id);
+ }
+ }
+
+ return 0;
+}
diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh
new file mode 100755
index 00000000..4074ce55
--- /dev/null
+++ b/test/cmdlineTests.sh
@@ -0,0 +1,93 @@
+#!/usr/bin/env bash
+
+#------------------------------------------------------------------------------
+# Bash script to run commandline Solidity tests.
+#
+# The documentation for solidity is hosted at:
+#
+# https://solidity.readthedocs.org
+#
+# ------------------------------------------------------------------------------
+# 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/>
+#
+# (c) 2016 solidity contributors.
+#------------------------------------------------------------------------------
+
+set -e
+
+REPO_ROOT="$(dirname "$0")"/..
+SOLC="$REPO_ROOT/build/solc/solc"
+
+echo "Checking that the bug list is up to date..."
+"$REPO_ROOT"/scripts/update_bugs_by_version.py
+
+echo "Compiling all files in std and examples..."
+
+for f in "$REPO_ROOT"/std/*.sol
+do
+ echo "Compiling $f..."
+ set +e
+ output=$("$SOLC" "$f" 2>&1)
+ failed=$?
+ # Remove the pre-release warning from the compiler output
+ output=$(echo "$output" | grep -v 'pre-release')
+ echo "$output"
+ set -e
+ test -z "$output" -a "$failed" -eq 0
+done
+
+echo "Testing library checksum..."
+echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222
+! echo '' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null
+
+echo "Testing long library names..."
+echo '' | "$SOLC" --link --libraries aveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylonglibraryname:0x90f20564390eAe531E810af625A22f51385Cd222
+
+echo "Testing overwriting files"
+TMPDIR=$(mktemp -d)
+(
+ set -e
+ # First time it works
+ echo 'contract C {} ' | "$SOLC" --bin -o "$TMPDIR/non-existing-stuff-to-create" 2>/dev/null
+ # Second time it fails
+ ! echo 'contract C {} ' | "$SOLC" --bin -o "$TMPDIR/non-existing-stuff-to-create" 2>/dev/null
+ # Unless we force
+ echo 'contract C {} ' | "$SOLC" --overwrite --bin -o "$TMPDIR/non-existing-stuff-to-create" 2>/dev/null
+)
+rm -rf "$TMPDIR"
+
+echo "Testing soljson via the fuzzer..."
+TMPDIR=$(mktemp -d)
+(
+ set -e
+ cd "$REPO_ROOT"
+ REPO_ROOT=$(pwd) # make it absolute
+ cd "$TMPDIR"
+ "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/
+ for f in *.sol
+ do
+ set +e
+ "$REPO_ROOT"/build/test/solfuzzer --quiet < "$f"
+ if [ $? -ne 0 ]; then
+ echo "Fuzzer failed on:"
+ cat "$f"
+ exit 1
+ fi
+ set -e
+ done
+)
+rm -rf "$TMPDIR"
+echo "Done."
diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp
index 39c32eb7..d2904b5f 100644
--- a/test/contracts/FixedFeeRegistrar.cpp
+++ b/test/contracts/FixedFeeRegistrar.cpp
@@ -82,7 +82,7 @@ contract FixedFeeRegistrar is Registrar {
}
}
function disown(string _name, address _refund) onlyrecordowner(_name) {
- delete m_recordData[uint(sha3(_name)) / 8];
+ delete m_recordData[uint(keccak256(_name)) / 8];
if (!_refund.send(c_fee))
throw;
Changed(_name);
@@ -118,7 +118,7 @@ contract FixedFeeRegistrar is Registrar {
Record[2**253] m_recordData;
function m_record(string _name) constant internal returns (Record storage o_record) {
- return m_recordData[uint(sha3(_name)) / 8];
+ return m_recordData[uint(keccak256(_name)) / 8];
}
uint constant c_fee = 69 ether;
}
diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp
index 80f06613..ef345d86 100644
--- a/test/contracts/Wallet.cpp
+++ b/test/contracts/Wallet.cpp
@@ -128,7 +128,7 @@ contract multiowned {
}
// Replaces an owner `_from` with another `_to`.
- function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
+ function changeOwner(address _from, address _to) onlymanyowners(keccak256(msg.data)) external {
if (isOwner(_to)) return;
uint ownerIndex = m_ownerIndex[uint(_from)];
if (ownerIndex == 0) return;
@@ -140,7 +140,7 @@ contract multiowned {
OwnerChanged(_from, _to);
}
- function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
+ function addOwner(address _owner) onlymanyowners(keccak256(msg.data)) external {
if (isOwner(_owner)) return;
clearPending();
@@ -154,7 +154,7 @@ contract multiowned {
OwnerAdded(_owner);
}
- function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
+ function removeOwner(address _owner) onlymanyowners(keccak256(msg.data)) external {
uint ownerIndex = m_ownerIndex[uint(_owner)];
if (ownerIndex == 0) return;
if (m_required > m_numOwners - 1) return;
@@ -166,7 +166,7 @@ contract multiowned {
OwnerRemoved(_owner);
}
- function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
+ function changeRequirement(uint _newRequired) onlymanyowners(keccak256(msg.data)) external {
if (_newRequired > m_numOwners) return;
m_required = _newRequired;
clearPending();
@@ -293,11 +293,11 @@ contract daylimit is multiowned {
m_lastDay = today();
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
- function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
+ function setDailyLimit(uint _newLimit) onlymanyowners(keccak256(msg.data)) external {
m_dailyLimit = _newLimit;
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
- function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
+ function resetSpentToday() onlymanyowners(keccak256(msg.data)) external {
m_spentToday = 0;
}
@@ -374,7 +374,7 @@ contract Wallet is multisig, multiowned, daylimit {
}
// destroys the contract sending everything to `_to`.
- function kill(address _to) onlymanyowners(sha3(msg.data)) external {
+ function kill(address _to) onlymanyowners(keccak256(msg.data)) external {
selfdestruct(_to);
}
@@ -398,7 +398,7 @@ contract Wallet is multisig, multiowned, daylimit {
return 0;
}
// determine our operation hash.
- _r = sha3(msg.data, block.number);
+ _r = keccak256(msg.data, block.number);
if (!confirm(_r) && m_txs[_r].to == 0) {
m_txs[_r].to = _to;
m_txs[_r].value = _value;
diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp
new file mode 100644
index 00000000..cf99755f
--- /dev/null
+++ b/test/fuzzer.cpp
@@ -0,0 +1,223 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Executable for use with AFL <http://lcamtuf.coredump.cx/afl>.
+ */
+
+#include <libevmasm/Assembly.h>
+#include <libevmasm/ConstantOptimiser.h>
+
+#include <json/json.h>
+
+#include <boost/program_options.hpp>
+
+#include <string>
+#include <iostream>
+
+using namespace std;
+using namespace dev;
+using namespace dev::eth;
+namespace po = boost::program_options;
+
+extern "C"
+{
+extern char const* compileJSON(char const* _input, bool _optimize);
+typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error);
+extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback);
+}
+
+bool quiet = false;
+
+string contains(string const& _haystack, vector<string> const& _needles)
+{
+ for (string const& needle: _needles)
+ if (_haystack.find(needle) != string::npos)
+ return needle;
+ return "";
+}
+
+void testConstantOptimizer()
+{
+ if (!quiet)
+ cout << "Testing constant optimizer" << endl;
+ vector<u256> numbers;
+ while (!cin.eof())
+ {
+ h256 data;
+ cin.read(reinterpret_cast<char*>(data.data()), 32);
+ numbers.push_back(u256(data));
+ }
+ if (!quiet)
+ cout << "Got " << numbers.size() << " inputs:" << endl;
+
+ Assembly assembly;
+ for (u256 const& n: numbers)
+ {
+ if (!quiet)
+ cout << n << endl;
+ assembly.append(n);
+ }
+ for (bool isCreation: {false, true})
+ {
+ for (unsigned runs: {1, 2, 3, 20, 40, 100, 200, 400, 1000})
+ {
+ ConstantOptimisationMethod::optimiseConstants(
+ isCreation,
+ runs,
+ assembly,
+ const_cast<AssemblyItems&>(assembly.items())
+ );
+ }
+ }
+}
+
+string readInput()
+{
+ string input;
+ while (!cin.eof())
+ {
+ string s;
+ getline(cin, s);
+ input += s + '\n';
+ }
+ return input;
+}
+
+void testStandardCompiler()
+{
+ if (!quiet)
+ cout << "Testing compiler via JSON interface." << endl;
+ string input = readInput();
+ string outputString(compileStandard(input.c_str(), NULL));
+ Json::Value output;
+ if (!Json::Reader().parse(outputString, output))
+ {
+ cout << "Compiler produced invalid JSON output." << endl;
+ abort();
+ }
+ if (output.isMember("errors"))
+ for (auto const& error: output["errors"])
+ {
+ string invalid = contains(error["type"].asString(), vector<string>{
+ "Exception",
+ "InternalCompilerError"
+ });
+ if (!invalid.empty())
+ {
+ cout << "Invalid error: \"" << error["type"].asString() << "\"" << endl;
+ abort();
+ }
+ }
+}
+
+void testCompiler()
+{
+ if (!quiet)
+ cout << "Testing compiler." << endl;
+ string input = readInput();
+
+ bool optimize = true;
+ string outputString(compileJSON(input.c_str(), optimize));
+ Json::Value outputJson;
+ if (!Json::Reader().parse(outputString, outputJson))
+ {
+ cout << "Compiler produced invalid JSON output." << endl;
+ abort();
+ }
+ if (outputJson.isMember("errors"))
+ {
+ if (!outputJson["errors"].isArray())
+ {
+ cout << "Output JSON has \"errors\" but it is not an array." << endl;
+ abort();
+ }
+ for (Json::Value const& error: outputJson["errors"])
+ {
+ string invalid = contains(error.asString(), vector<string>{
+ "Internal compiler error",
+ "Exception during compilation",
+ "Unknown exception during compilation",
+ "Unknown exception while generating contract data output",
+ "Unknown exception while generating source name output",
+ "Unknown error while generating JSON"
+ });
+ if (!invalid.empty())
+ {
+ cout << "Invalid error: \"" << error.asString() << "\"" << endl;
+ abort();
+ }
+ }
+ }
+ else if (!outputJson.isMember("contracts"))
+ {
+ cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl;
+ abort();
+ }
+}
+
+int main(int argc, char** argv)
+{
+ po::options_description options(
+ R"(solfuzzer, fuzz-testing binary for use with AFL.
+Usage: solfuzzer [Options] < input
+Reads a single source from stdin, compiles it and signals a failure for internal errors.
+
+Allowed options)",
+ po::options_description::m_default_line_length,
+ po::options_description::m_default_line_length - 23);
+ options.add_options()
+ ("help", "Show this help screen.")
+ ("quiet", "Only output errors.")
+ (
+ "standard-json",
+ "Test via the standard-json interface, i.e. "
+ "input is expected to be JSON-encoded instead of "
+ "plain source file."
+ )
+ (
+ "const-opt",
+ "Run the constant optimizer instead of compiling. "
+ "Expects a binary string of up to 32 bytes on stdin."
+ );
+
+ po::variables_map arguments;
+ try
+ {
+ po::command_line_parser cmdLineParser(argc, argv);
+ cmdLineParser.options(options);
+ po::store(cmdLineParser.run(), arguments);
+ }
+ catch (po::error const& _exception)
+ {
+ cerr << _exception.what() << endl;
+ return 1;
+ }
+
+ if (arguments.count("quiet"))
+ quiet = true;
+
+ if (arguments.count("help"))
+ cout << options;
+ else if (arguments.count("const-opt"))
+ testConstantOptimizer();
+ else if (arguments.count("standard-json"))
+ testStandardCompiler();
+ else
+ testCompiler();
+
+ return 0;
+}
diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp
new file mode 100644
index 00000000..17a17d22
--- /dev/null
+++ b/test/libdevcore/Checksum.cpp
@@ -0,0 +1,83 @@
+/*
+ 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/>.
+*/
+/**
+ * Unit tests for the address checksum.
+ */
+
+#include <libdevcore/CommonData.h>
+
+#include "../TestHelper.h"
+
+using namespace std;
+
+namespace dev
+{
+namespace test
+{
+
+BOOST_AUTO_TEST_SUITE(Checksum)
+
+BOOST_AUTO_TEST_CASE(regular)
+{
+ BOOST_CHECK(passesAddressChecksum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true));
+ BOOST_CHECK(passesAddressChecksum("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true));
+ BOOST_CHECK(passesAddressChecksum("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true));
+ BOOST_CHECK(passesAddressChecksum("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true));
+}
+
+BOOST_AUTO_TEST_CASE(regular_negative)
+{
+ BOOST_CHECK(!passesAddressChecksum("0x6aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true));
+ BOOST_CHECK(!passesAddressChecksum("0xeB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true));
+ BOOST_CHECK(!passesAddressChecksum("0xebF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true));
+ BOOST_CHECK(!passesAddressChecksum("0xE1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true));
+}
+
+BOOST_AUTO_TEST_CASE(regular_invalid_length)
+{
+ BOOST_CHECK(passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad60", true));
+ BOOST_CHECK(!passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad6", true));
+ BOOST_CHECK(passesAddressChecksum("0x08A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true));
+ BOOST_CHECK(!passesAddressChecksum("0x8A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true));
+ BOOST_CHECK(passesAddressChecksum("0x00c40cC30cb4675673c9ee382de805c19734986A", true));
+ BOOST_CHECK(!passesAddressChecksum("0xc40cC30cb4675673c9ee382de805c19734986A", true));
+ BOOST_CHECK(passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a00", true));
+ BOOST_CHECK(!passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a", true));
+}
+
+BOOST_AUTO_TEST_CASE(homocaps_valid)
+{
+ BOOST_CHECK(passesAddressChecksum("0x52908400098527886E0F7030069857D2E4169EE7", true));
+ BOOST_CHECK(passesAddressChecksum("0x8617E340B3D01FA5F11F306F4090FD50E238070D", true));
+ BOOST_CHECK(passesAddressChecksum("0xde709f2102306220921060314715629080e2fb77", true));
+ BOOST_CHECK(passesAddressChecksum("0x27b1fdb04752bbc536007a920d24acb045561c26", true));
+}
+
+BOOST_AUTO_TEST_CASE(homocaps_invalid)
+{
+ string upper = "0x00AA0000000012400000000DDEEFF000000000BB";
+ BOOST_CHECK(passesAddressChecksum(upper, false));
+ BOOST_CHECK(!passesAddressChecksum(upper, true));
+ string lower = "0x11aa000000000000000d00cc00000000000000bb";
+ BOOST_CHECK(passesAddressChecksum(lower, false));
+ BOOST_CHECK(!passesAddressChecksum(lower, true));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
diff --git a/test/libdevcore/MiniMoustache.cpp b/test/libdevcore/MiniMoustache.cpp
new file mode 100644
index 00000000..84149173
--- /dev/null
+++ b/test/libdevcore/MiniMoustache.cpp
@@ -0,0 +1,127 @@
+/*
+ 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/>.
+*/
+/**
+ * Unit tests for the mini moustache class.
+ */
+
+#include <libdevcore/Whiskers.h>
+
+#include "../TestHelper.h"
+
+using namespace std;
+
+namespace dev
+{
+namespace test
+{
+
+BOOST_AUTO_TEST_SUITE(WhiskersTest)
+
+BOOST_AUTO_TEST_CASE(no_templates)
+{
+ string templ = "this text does not contain templates";
+ BOOST_CHECK_EQUAL(Whiskers(templ).render(), templ);
+}
+
+BOOST_AUTO_TEST_CASE(basic_replacement)
+{
+ string templ = "a <b> x <c> -> <d>.";
+ string result = Whiskers(templ)
+ ("b", "BE")
+ ("c", "CE")
+ ("d", "DE")
+ .render();
+ BOOST_CHECK_EQUAL(result, "a BE x CE -> DE.");
+}
+
+BOOST_AUTO_TEST_CASE(tag_unavailable)
+{
+ string templ = "<b>";
+ Whiskers m(templ);
+ BOOST_CHECK_THROW(m.render(), WhiskersError);
+}
+
+BOOST_AUTO_TEST_CASE(complicated_replacement)
+{
+ string templ = "a <b> x <complicated> \n <nes<ted>>.";
+ string result = Whiskers(templ)
+ ("b", "BE")
+ ("complicated", "CO<M>PL")
+ ("nes<ted", "NEST")
+ .render();
+ BOOST_CHECK_EQUAL(result, "a BE x CO<M>PL \n NEST>.");
+}
+
+BOOST_AUTO_TEST_CASE(non_existing_list)
+{
+ string templ = "a <#b></b>";
+ Whiskers m(templ);
+ BOOST_CHECK_THROW(m.render(), WhiskersError);
+}
+
+BOOST_AUTO_TEST_CASE(empty_list)
+{
+ string templ = "a <#b></b>x";
+ string result = Whiskers(templ)("b", vector<Whiskers::StringMap>{}).render();
+ BOOST_CHECK_EQUAL(result, "a x");
+}
+
+BOOST_AUTO_TEST_CASE(list)
+{
+ string templ = "a<#b>( <g> - <h> )</b>x";
+ vector<map<string, string>> list(2);
+ list[0]["g"] = "GE";
+ list[0]["h"] = "H";
+ list[1]["g"] = "2GE";
+ list[1]["h"] = "2H";
+ string result = Whiskers(templ)("b", list).render();
+ BOOST_CHECK_EQUAL(result, "a( GE - H )( 2GE - 2H )x");
+}
+
+BOOST_AUTO_TEST_CASE(recursive_list)
+{
+ // Check that templates resulting from lists are not expanded again
+ string templ = "a<#b> 1<g>3 </b><x>";
+ vector<map<string, string>> list(1);
+ list[0]["g"] = "<x>";
+ string result = Whiskers(templ)("x", "X")("b", list).render();
+ BOOST_CHECK_EQUAL(result, "a 1<x>3 X");
+}
+
+BOOST_AUTO_TEST_CASE(list_can_access_upper)
+{
+ string templ = "<#b>(<a>)</b>";
+ vector<map<string, string>> list(2);
+ Whiskers m(templ);
+ string result = m("a", "A")("b", list).render();
+ BOOST_CHECK_EQUAL(result, "(A)(A)");
+}
+
+BOOST_AUTO_TEST_CASE(parameter_collision)
+{
+ string templ = "a <#b></b>";
+ vector<map<string, string>> list(1);
+ list[0]["a"] = "x";
+ Whiskers m(templ);
+ m("a", "X")("b", list);
+ BOOST_CHECK_THROW(m.render(), WhiskersError);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
diff --git a/test/libdevcore/SwarmHash.cpp b/test/libdevcore/SwarmHash.cpp
index 7f3186ac..1ed1da18 100644
--- a/test/libdevcore/SwarmHash.cpp
+++ b/test/libdevcore/SwarmHash.cpp
@@ -1,18 +1,18 @@
/*
- This file is part of cpp-ethereum.
+ This file is part of solidity.
- cpp-ethereum is free software: you can redistribute it and/or modify
+ 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.
- cpp-ethereum is distributed in the hope that it will be useful,
+ 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Unit tests for the swarm hash computation routine.
diff --git a/test/libdevcore/UTF8.cpp b/test/libdevcore/UTF8.cpp
new file mode 100644
index 00000000..719ada72
--- /dev/null
+++ b/test/libdevcore/UTF8.cpp
@@ -0,0 +1,216 @@
+/*
+ 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/>.
+*/
+/**
+ * Unit tests for UTF-8 validation.
+ */
+
+#include <libdevcore/CommonData.h>
+#include <libdevcore/UTF8.h>
+
+#include "../TestHelper.h"
+
+using namespace std;
+
+namespace dev
+{
+namespace test
+{
+
+BOOST_AUTO_TEST_SUITE(UTF8)
+
+namespace {
+
+bool isValidUTF8(string const& _value)
+{
+ size_t pos;
+ return validateUTF8(asString(fromHex(_value)), pos);
+}
+
+bool isInvalidUTF8(string const& _value, size_t _expectedPos)
+{
+ size_t pos;
+ if (validateUTF8(asString(fromHex(_value)), pos))
+ return false;
+ if (pos != _expectedPos)
+ return false;
+ return true;
+}
+
+}
+
+BOOST_AUTO_TEST_CASE(valid)
+{
+ BOOST_CHECK(isValidUTF8("00"));
+ BOOST_CHECK(isValidUTF8("20"));
+ BOOST_CHECK(isValidUTF8("7f"));
+ BOOST_CHECK(isValidUTF8("c281"));
+ BOOST_CHECK(isValidUTF8("df81"));
+ BOOST_CHECK(isValidUTF8("e0a081"));
+ BOOST_CHECK(isValidUTF8("e18081"));
+ BOOST_CHECK(isValidUTF8("ec8081"));
+ BOOST_CHECK(isValidUTF8("ed8081"));
+ BOOST_CHECK(isValidUTF8("ee8081"));
+ BOOST_CHECK(isValidUTF8("ef8081"));
+ BOOST_CHECK(isValidUTF8("f0908081"));
+ BOOST_CHECK(isValidUTF8("f3808081"));
+ BOOST_CHECK(isValidUTF8("f2808081"));
+ BOOST_CHECK(isValidUTF8("f3808081"));
+ BOOST_CHECK(isValidUTF8("f48e8081"));
+}
+
+BOOST_AUTO_TEST_CASE(invalid)
+{
+ // anything between 0x80 and 0xc0 is disallowed
+ BOOST_CHECK(isInvalidUTF8("80", 0)); // invalid per table 3.6
+ BOOST_CHECK(isInvalidUTF8("a0", 0)); // invalid per table 3.6
+ BOOST_CHECK(isInvalidUTF8("c0", 0)); // invalid per table 3.7
+ BOOST_CHECK(isInvalidUTF8("c1", 0)); // invalid per table 3.7
+ BOOST_CHECK(isInvalidUTF8("c2", 0)); // too short (position is reported as the first byte)
+ BOOST_CHECK(isInvalidUTF8("e08081", 2)); // e0 must be followed by >= a0
+ BOOST_CHECK(isInvalidUTF8("e180", 0)); // too short
+ BOOST_CHECK(isInvalidUTF8("ec80", 0)); // too short
+ BOOST_CHECK(isInvalidUTF8("f08f8001", 2)); // f0 must be followed by >= 90
+ BOOST_CHECK(isInvalidUTF8("f18080", 0)); // too short
+ BOOST_CHECK(isInvalidUTF8("f4908081", 2)); // f4 must be followed by < 90
+ // anything above 0xf7 is disallowed
+ BOOST_CHECK(isInvalidUTF8("f8", 0)); // invalid per table 3.7
+ BOOST_CHECK(isInvalidUTF8("f9", 0)); // invalid per table 3.7
+}
+
+BOOST_AUTO_TEST_CASE(corpus)
+{
+ string source = R"(
+κόσμε
+
+hélló
+
+Ā ā Ă ă Ą ą
+
+ƀ Ɓ Ƃ ƃ Ƅ ƅ
+
+ɐ ɑ ɒ ɓ ɔ ɕ
+
+ʰ ʱ ʲ ʳ ʴ ʵ
+
+̀ ́ ̂ ̃ ̄ ̅
+
+ϩ Ϫ ϫ Ϭ ϭ Ϯ
+
+Ё Ђ Ѓ Є Ѕ І
+
+Ա Բ Գ Դ Ե Զ
+
+ ק ר ש ת װ ױ
+
+ځ ڂ ڃ ڄ څ چ
+
+ऑ ऒ ओ औ क ख
+
+ও ঔ ক খ গ ঘ
+
+ਘ ਙ ਚ ਛ ਜ ਝ
+
+ઓ ઔ ક ખ ગ ઘ
+
+ଗ ଘ ଙ ଚ ଛ ଜ
+
+ஔ க ங ச ஜ ஞ
+
+ఎ ఏ ఐ ఒ ఓ ఔ
+
+ಓ ಔ ಕ ಖ ಗ ಘ
+
+ഐ ഒ ഓ ഔ ക
+
+ฒ ณ ด ต ถ ท
+
+ມ ຢ ຣ ລ ວ ສ
+
+༄ ༅ ༆ ༇ ༈ ༉
+
+Ⴑ Ⴒ Ⴓ Ⴔ Ⴕ Ⴖ
+
+ᄌ ᄍ ᄎ ᄏ ᄐ
+
+Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ
+
+ἐ ἑ ἒ ἓ ἔ ἕ
+
+₠ ₡ ₢ ₣ ₤ ₥
+
+⃐ ⃑ ⃒ ⃓ ⃔ ⃕ ⃖ ⃗ ⃘ ⃙ ⃚
+
+ℋ ℌ ℍ ℎ ℏ ℐ ℑ
+
+⅓ ⅔ ⅕ ⅖ ⅗
+
+∬ ∭ ∮ ∯ ∰
+
+⌖ ⌗ ⌘ ⌙ ⌚ ⌛
+
+␀ ␁ ␂ ␃ ␄ ␅
+
+⑀ ⑁ ⑂ ⑃ ⑄
+
+① ② ③ ④ ⑤
+
+╘ ╙ ╚ ╛ ╜ ╝
+
+▁ ▂ ▃ ▄ ▅ ▆
+
+▤ ▥ ▦ ▧ ▨
+
+♔ ♕ ♖ ♗ ♘ ♙
+
+✈ ✉ ✌ ✍ ✎
+
+ぁ あ ぃ い ぅ
+
+ァ ア ィ イ ゥ
+
+ㄅ ㄆ ㄇ ㄈ ㄉ
+
+ㄱ ㄲ ㄳ ㄴ ㄵ
+
+㆚ ㆛ ㆜ ㆝ ㆞
+
+㈀ ㈁ ㈂ ㈃ ㈄
+
+㌀ ㌁ ㌂ ㌃ ㌄
+
+乺 乻 乼 乽 乾
+
+걺 걻 걼 걽 걾
+
+豈 更 車 賈 滑
+
+שּׁ שּׂ אַ אָ אּ
+
+ﮄ ﮅ ﮆ ﮇ ﮈ ﮉ
+
+ ﺵ ﺶ ﺷ ﺸ
+
+「 」 、 ・ ヲ ァ ィ ゥ
+ )";
+ size_t pos;
+ BOOST_CHECK(validateUTF8(source, pos));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp
new file mode 100644
index 00000000..fa7c45ed
--- /dev/null
+++ b/test/libjulia/Parser.cpp
@@ -0,0 +1,231 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Unit tests for parsing Julia.
+ */
+
+#include "../TestHelper.h"
+
+#include <test/libsolidity/ErrorCheck.h>
+
+#include <libsolidity/inlineasm/AsmParser.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+#include <libsolidity/parsing/Scanner.h>
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <boost/optional.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <string>
+#include <memory>
+
+using namespace std;
+
+namespace dev
+{
+namespace solidity
+{
+namespace test
+{
+
+namespace
+{
+
+bool parse(string const& _source, ErrorReporter& errorReporter)
+{
+ try
+ {
+ auto scanner = make_shared<Scanner>(CharStream(_source));
+ auto parserResult = assembly::Parser(errorReporter, true).parse(scanner);
+ if (parserResult)
+ {
+ assembly::AsmAnalysisInfo analysisInfo;
+ return (assembly::AsmAnalyzer(analysisInfo, errorReporter, true)).analyze(*parserResult);
+ }
+ }
+ catch (FatalError const&)
+ {
+ BOOST_FAIL("Fatal error leaked.");
+ }
+ return false;
+}
+
+boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _allowWarnings = true)
+{
+ ErrorList errors;
+ ErrorReporter errorReporter(errors);
+ if (!parse(_source, errorReporter))
+ {
+ BOOST_REQUIRE_EQUAL(errors.size(), 1);
+ return *errors.front();
+ }
+ else
+ {
+ // If success is true, there might still be an error in the assembly stage.
+ if (_allowWarnings && Error::containsOnlyWarnings(errors))
+ return {};
+ else if (!errors.empty())
+ {
+ if (!_allowWarnings)
+ BOOST_CHECK_EQUAL(errors.size(), 1);
+ return *errors.front();
+ }
+ }
+ return {};
+}
+
+bool successParse(std::string const& _source, bool _allowWarnings = true)
+{
+ return !parseAndReturnFirstError(_source, _allowWarnings);
+}
+
+Error expectError(std::string const& _source, bool _allowWarnings = false)
+{
+
+ auto error = parseAndReturnFirstError(_source, _allowWarnings);
+ BOOST_REQUIRE(error);
+ return *error;
+}
+
+}
+
+#define CHECK_ERROR(text, typ, substring) \
+do \
+{ \
+ Error err = expectError((text), false); \
+ BOOST_CHECK(err.type() == (Error::Type::typ)); \
+ BOOST_CHECK(searchErrorMessage(err, (substring))); \
+} while(0)
+
+BOOST_AUTO_TEST_SUITE(JuliaParser)
+
+BOOST_AUTO_TEST_CASE(smoke_test)
+{
+ BOOST_CHECK(successParse("{ }"));
+}
+
+BOOST_AUTO_TEST_CASE(vardecl)
+{
+ BOOST_CHECK(successParse("{ let x:u256 := 7:u256 }"));
+}
+
+BOOST_AUTO_TEST_CASE(vardecl_bool)
+{
+ BOOST_CHECK(successParse("{ let x:bool := true:bool }"));
+ BOOST_CHECK(successParse("{ let x:bool := false:bool }"));
+}
+
+BOOST_AUTO_TEST_CASE(assignment)
+{
+ BOOST_CHECK(successParse("{ let x:u256 := 2:u256 let y:u256 := x }"));
+}
+
+BOOST_AUTO_TEST_CASE(vardecl_complex)
+{
+ BOOST_CHECK(successParse("{ function add(a:u256, b:u256) -> c:u256 {} let y:u256 := 2:u256 let x:u256 := add(7:u256, add(6:u256, y)) }"));
+}
+
+BOOST_AUTO_TEST_CASE(blocks)
+{
+ BOOST_CHECK(successParse("{ let x:u256 := 7:u256 { let y:u256 := 3:u256 } { let z:u256 := 2:u256 } }"));
+}
+
+BOOST_AUTO_TEST_CASE(function_definitions)
+{
+ BOOST_CHECK(successParse("{ function f() { } function g(a:u256) -> x:u256 { } }"));
+}
+
+BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
+{
+ BOOST_CHECK(successParse("{ function f(a:u256, d:u256) { } function g(a:u256, d:u256) -> x:u256, y:u256 { } }"));
+}
+
+BOOST_AUTO_TEST_CASE(function_calls)
+{
+ BOOST_CHECK(successParse("{ function f(a:u256) -> b:u256 {} function g(a:u256, b:u256, c:u256) {} function x() { g(1:u256, 2:u256, f(3:u256)) x() } }"));
+}
+
+BOOST_AUTO_TEST_CASE(tuple_assignment)
+{
+ BOOST_CHECK(successParse("{ function f() -> a:u256, b:u256, c:u256 {} let x:u256, y:u256, z:u256 := f() }"));
+}
+
+BOOST_AUTO_TEST_CASE(label)
+{
+ CHECK_ERROR("{ label: }", ParserError, "Labels are not supported.");
+}
+
+BOOST_AUTO_TEST_CASE(instructions)
+{
+ CHECK_ERROR("{ pop }", ParserError, "Call or assignment expected.");
+}
+
+BOOST_AUTO_TEST_CASE(push)
+{
+ CHECK_ERROR("{ 0x42:u256 }", ParserError, "Call or assignment expected.");
+}
+
+BOOST_AUTO_TEST_CASE(assign_from_stack)
+{
+ CHECK_ERROR("{ =: x:u256 }", ParserError, "Literal or identifier expected.");
+}
+
+BOOST_AUTO_TEST_CASE(empty_call)
+{
+ CHECK_ERROR("{ () }", ParserError, "Literal or identifier expected.");
+}
+
+BOOST_AUTO_TEST_CASE(lacking_types)
+{
+ CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected token Identifier got 'Assign'");
+ CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected token Colon got 'RBrace'");
+ CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected token Colon got 'RParen'");
+ CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected token Colon got 'LBrace'");
+}
+
+BOOST_AUTO_TEST_CASE(invalid_types)
+{
+ /// testing invalid literal
+ /// NOTE: these will need to change when types are compared
+ CHECK_ERROR("{ let x:bool := 1:invalid }", TypeError, "\"invalid\" is not a valid type (user defined types are not yet supported).");
+ /// testing invalid variable declaration
+ CHECK_ERROR("{ let x:invalid := 1:bool }", TypeError, "\"invalid\" is not a valid type (user defined types are not yet supported).");
+ CHECK_ERROR("{ function f(a:invalid) {} }", TypeError, "\"invalid\" is not a valid type (user defined types are not yet supported).");
+}
+
+BOOST_AUTO_TEST_CASE(builtin_types)
+{
+ BOOST_CHECK(successParse("{ let x:bool := true:bool }"));
+ BOOST_CHECK(successParse("{ let x:u8 := 1:u8 }"));
+ BOOST_CHECK(successParse("{ let x:s8 := 1:u8 }"));
+ BOOST_CHECK(successParse("{ let x:u32 := 1:u32 }"));
+ BOOST_CHECK(successParse("{ let x:s32 := 1:s32 }"));
+ BOOST_CHECK(successParse("{ let x:u64 := 1:u64 }"));
+ BOOST_CHECK(successParse("{ let x:s64 := 1:s64 }"));
+ BOOST_CHECK(successParse("{ let x:u128 := 1:u128 }"));
+ BOOST_CHECK(successParse("{ let x:s128 := 1:s128 }"));
+ BOOST_CHECK(successParse("{ let x:u256 := 1:u256 }"));
+ BOOST_CHECK(successParse("{ let x:s256 := 1:s256 }"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
+} // end namespaces
diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp
index b5e32e94..4e896fd0 100644
--- a/test/liblll/EndToEndTest.cpp
+++ b/test/liblll/EndToEndTest.cpp
@@ -43,6 +43,752 @@ BOOST_AUTO_TEST_CASE(smoke_test)
BOOST_CHECK(callFallback() == encodeArgs(string("test", 4)));
}
+BOOST_AUTO_TEST_CASE(bare_panic)
+{
+ char const* sourceCode = "(panic)";
+ compileAndRunWithoutCheck(sourceCode);
+ BOOST_REQUIRE(m_output.empty());
+}
+
+BOOST_AUTO_TEST_CASE(panic)
+{
+ char const* sourceCode = "{ (panic) }";
+ compileAndRunWithoutCheck(sourceCode);
+ BOOST_REQUIRE(m_output.empty());
+}
+
+BOOST_AUTO_TEST_CASE(macro_zeroarg)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (def 'zeroarg () (seq (mstore 0 0x1234) (return 0 32)))
+ (zeroarg)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(0x1234)));
+}
+
+BOOST_AUTO_TEST_CASE(macros)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (def 'x 1)
+ (def 'y () { (def 'x (+ x 2)) })
+ (y)
+ (return x)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(3)));
+}
+
+BOOST_AUTO_TEST_CASE(variables)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (set 'x 1)
+ (set 'y 2)
+ ;; this should equal to 3
+ (set 'z (add (get 'x) (get 'y)))
+ ;; overwriting it here
+ (set 'y 4)
+ ;; each variable has a 32 byte slot, starting from memory location 0x80
+ ;; variable addresses can also be retrieved by x or (ref 'x)
+ (set 'k (add (add (ref 'x) (ref 'y)) z))
+ (return (add (add (get 'x) (add (get 'y) (get 'z))) (get 'k)))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(488)));
+}
+
+BOOST_AUTO_TEST_CASE(when)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= (calldatasize) 0) (return 1))
+ (when (!= (calldatasize) 0) (return 2))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2)));
+ BOOST_CHECK(callFallback() == toBigEndian(u256(1)));
+}
+
+BOOST_AUTO_TEST_CASE(unless)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (unless (!= (calldatasize) 0) (return 1))
+ (unless (= (calldatasize) 0) (return 2))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2)));
+ BOOST_CHECK(callFallback() == toBigEndian(u256(1)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional_literal)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (return (if (= (calldatasize) 0) 1 2))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2)));
+ BOOST_CHECK(callFallback() == toBigEndian(u256(1)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (if (= (calldatasize) 0) (return 1) (return 2))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2)));
+ BOOST_CHECK(callFallback() == toBigEndian(u256(1)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional_seq)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (return (if (= (calldatasize) 0) { 0 2 1 } { 0 1 2 }))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(2)));
+ BOOST_CHECK(callFallback() == toBigEndian(u256(1)));
+}
+
+BOOST_AUTO_TEST_CASE(exp_operator_const)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (exp 2 3)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == toBigEndian(u256(8)));
+}
+
+BOOST_AUTO_TEST_CASE(exp_operator_const_signed)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (exp (- 0 2) 3)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == toBigEndian(u256(-8)));
+}
+
+BOOST_AUTO_TEST_CASE(exp_operator_on_range)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= (div (calldataload 0x00) (exp 2 224)) 0xb3de648b)
+ (return (exp 2 (calldataload 0x04))))
+ (jump 0x02)))
+ )";
+ compileAndRun(sourceCode);
+ testContractAgainstCppOnRange("f(uint256)", [](u256 const& a) -> u256 { return u256(1 << a.convert_to<int>()); }, 0, 16);
+}
+
+BOOST_AUTO_TEST_CASE(constructor_argument_internal_numeric)
+{
+ char const* sourceCode = R"(
+ (seq
+ (sstore 0x00 65535)
+ (returnlll
+ (return @@0x00)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(65535)));
+}
+
+BOOST_AUTO_TEST_CASE(constructor_argument_internal_string)
+{
+ char const* sourceCode = R"(
+ (seq
+ (sstore 0x00 "test")
+ (returnlll
+ (return @@0x00)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs("test"));
+}
+
+BOOST_AUTO_TEST_CASE(constructor_arguments_external)
+{
+ char const* sourceCode = R"(
+ (seq
+ (codecopy 0x00 (bytecodesize) 64)
+ (sstore 0x00 @0x00)
+ (sstore 0x01 @0x20)
+ (returnlll
+ (seq
+ (when (= (div (calldataload 0x00) (exp 2 224)) 0xf2c9ecd8)
+ (return @@0x00))
+ (when (= (div (calldataload 0x00) (exp 2 224)) 0x89ea642f)
+ (return @@0x01)))))
+ )";
+ compileAndRun(sourceCode, 0, "", encodeArgs(u256(65535), "test"));
+ BOOST_CHECK(callContractFunction("getNumber()") == encodeArgs(u256(65535)));
+ BOOST_CHECK(callContractFunction("getString()") == encodeArgs("test"));
+}
+
+BOOST_AUTO_TEST_CASE(fallback_and_invalid_function)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= (div (calldataload 0x00) (exp 2 224)) 0xab5ed150)
+ (return "one"))
+ (when (= (div (calldataload 0x00) (exp 2 224)) 0xee784123)
+ (return "two"))
+ (return "three")))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("getOne()") == encodeArgs("one"));
+ BOOST_CHECK(callContractFunction("getTwo()") == encodeArgs("two"));
+ BOOST_CHECK(callContractFunction("invalidFunction()") == encodeArgs("three"));
+ BOOST_CHECK(callFallback() == encodeArgs("three"));
+}
+
+BOOST_AUTO_TEST_CASE(lit_string)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (lit 0x00 "abcdef")
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(string("abcdef")));
+}
+
+BOOST_AUTO_TEST_CASE(arithmetic)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore8 0x00 (+ 160 22))
+ (mstore8 0x01 (- 223 41))
+ (mstore8 0x02 (* 33 2))
+ (mstore8 0x03 (/ 10 2))
+ (mstore8 0x04 (% 67 2))
+ (mstore8 0x05 (& 15 8))
+ (mstore8 0x06 (| 18 8))
+ (mstore8 0x07 (^ 26 6))
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("b6b6420501081a1c000000000000000000000000000000000000000000000000")));
+}
+
+BOOST_AUTO_TEST_CASE(binary)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore8 0x00 (< 53 87))
+ (mstore8 0x01 (< 73 42))
+ (mstore8 0x02 (<= 37 94))
+ (mstore8 0x03 (<= 37 37))
+ (mstore8 0x04 (<= 183 34))
+ (mstore8 0x05 (S< (- 0 53) 87))
+ (mstore8 0x06 (S< 73 (- 0 42)))
+ (mstore8 0x07 (S<= (- 0 37) 94))
+ (mstore8 0x08 (S<= (- 0 37) (- 0 37)))
+ (mstore8 0x09 (S<= 183 (- 0 34)))
+ (mstore8 0x0a (> 73 42))
+ (mstore8 0x0b (> 53 87))
+ (mstore8 0x0c (>= 94 37))
+ (mstore8 0x0d (>= 94 94))
+ (mstore8 0x0e (>= 34 183))
+ (mstore8 0x0f (S> 73 (- 0 42)))
+ (mstore8 0x10 (S> (- 0 53) 87))
+ (mstore8 0x11 (S>= 94 (- 0 37)))
+ (mstore8 0x12 (S>= (- 0 94) (- 0 94)))
+ (mstore8 0x13 (S>= (- 0 34) 183))
+ (mstore8 0x14 (= 53 53))
+ (mstore8 0x15 (= 73 42))
+ (mstore8 0x16 (!= 37 94))
+ (mstore8 0x17 (!= 37 37))
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0100010100010001010001000101000100010100010001000000000000000000")));
+}
+
+BOOST_AUTO_TEST_CASE(unary)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore8 0x00 (! (< 53 87)))
+ (mstore8 0x01 (! (>= 42 73)))
+ (mstore8 0x02 (~ 0x7f))
+ (mstore8 0x03 (~ 0xaa))
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0001805500000000000000000000000000000000000000000000000000000000")));
+}
+
+BOOST_AUTO_TEST_CASE(assembly_mload_mstore)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (asm
+ 0x07 0x00 mstore
+ "abcdef" 0x20 mstore
+ 0x00 mload 0x40 mstore
+ 0x20 mload 0x60 mstore
+ 0x40 0x40 return))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(7), string("abcdef")));
+}
+
+BOOST_AUTO_TEST_CASE(assembly_sload_sstore)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (asm
+ 0x07 0x00 sstore
+ "abcdef" 0x01 sstore
+ 0x00 sload 0x00 mstore
+ 0x01 sload 0x20 mstore
+ 0x40 0x00 return))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(7), string("abcdef")));
+}
+
+BOOST_AUTO_TEST_CASE(assembly_codecopy)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (lit 0x00 "abcdef")
+ (asm
+ 0x06 6 codesize sub 0x20 codecopy
+ 0x20 0x20 return)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(string("abcdef")));
+}
+
+BOOST_AUTO_TEST_CASE(for_loop)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (for
+ { (set 'i 1) (set 'j 1) } ; INIT
+ (<= @i 10) ; PRED
+ [i]:(+ @i 1) ; POST
+ [j]:(* @j @i)) ; BODY
+ (return j 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(3628800))); // 10!
+}
+
+BOOST_AUTO_TEST_CASE(while_loop)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ ;; Euclid's GCD algorithm
+ (set 'a 1071)
+ (set 'b 462)
+ (while @b
+ [a]:(raw @b [b]:(mod @a @b)))
+ (return a 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(21))); // GCD(1071,462)
+}
+
+BOOST_AUTO_TEST_CASE(keccak256_32bytes)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore 0x00 0x01)
+ (return (keccak256 0x00 0x20))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6")));
+}
+
+// The following tests are for the built-in macros.
+// Note that panic, returnlll and return_one_arg are well covered above.
+
+BOOST_AUTO_TEST_CASE(allgas)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (- (gas) allgas)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(16))); // == 21 - SUB - GAS
+}
+
+BOOST_AUTO_TEST_CASE(send_two_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (send 0xdead 42))
+ )";
+ compileAndRun(sourceCode);
+ callFallbackWithValue(42);
+ BOOST_CHECK(balanceAt(Address(0xdead)) == 42);
+}
+
+BOOST_AUTO_TEST_CASE(send_three_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (send allgas 0xdead 42))
+ )";
+ compileAndRun(sourceCode);
+ callFallbackWithValue(42);
+ BOOST_CHECK(balanceAt(Address(0xdead)) == 42);
+}
+
+// Regression test for edge case that previously failed
+BOOST_AUTO_TEST_CASE(alloc_zero)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore 0x00 (~ 0))
+ (alloc 0)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(-1)));
+}
+
+BOOST_AUTO_TEST_CASE(alloc_size)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore 0x00 0) ; reserve space for the result of the alloc
+ (mstore 0x00 (alloc (calldataload 0x04)))
+ (return (- (msize) (mload 0x00)))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(32)));
+ BOOST_CHECK(callContractFunction("test()", 32) == encodeArgs(u256(32)));
+ BOOST_CHECK(callContractFunction("test()", 33) == encodeArgs(u256(64)));
+}
+
+BOOST_AUTO_TEST_CASE(alloc_start)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore 0x40 0) ; Set initial MSIZE to 0x60
+ (return (alloc 1))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(96));
+}
+
+BOOST_AUTO_TEST_CASE(alloc_with_variable)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (set 'x (alloc 1))
+ (mstore8 @x 42) ; ASCII '*'
+ (return @x 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs("*"));
+}
+
+BOOST_AUTO_TEST_CASE(msg_six_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= 0 (calldatasize))
+ (seq
+ (mstore 0x40 1)
+ (def 'outsize 0x20)
+ (return (msg 1000 (address) 42 0x40 0x20 outsize) outsize)))
+ (when (= 1 (calldataload 0x00))
+ (return (callvalue)))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(msg_five_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= 0 (calldatasize))
+ (seq
+ (mstore 0x20 1)
+ (mstore 0x40 2)
+ (return (msg 1000 (address) 42 0x20 0x40))))
+ (when (= 3 (+ (calldataload 0x00) (calldataload 0x20)))
+ (return (callvalue)))))
+
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(msg_four_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= 0 (calldatasize))
+ (return (msg 1000 (address) 42 0xff)))
+ (return (callvalue))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(msg_three_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= 0 (calldatasize))
+ (return (msg (address) 42 0xff)))
+ (return (callvalue))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(msg_two_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (when (= 0 (calldatasize))
+ (return (msg (address) 0xff)))
+ (return 42)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(create_one_arg)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (call allgas
+ (create (returnlll (return 42)))
+ 0 0 0 0x00 0x20)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(create_two_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (call allgas
+ (create 42 (returnlll (return (balance (address)))))
+ 0 0 0 0x00 0x20)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(sha3_two_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (mstore 0x00 0x01)
+ (return (sha3 0x00 0x20))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6")));
+}
+
+BOOST_AUTO_TEST_CASE(sha3_one_arg)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (sha3 0x01)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6")));
+}
+
+BOOST_AUTO_TEST_CASE(sha3pair)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (sha3pair 0x01 0x02)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0")));
+}
+
+BOOST_AUTO_TEST_CASE(sha3trip)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (sha3trip 0x01 0x02 0x03)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0x6e0c627900b24bd432fe7b1f713f1b0744091a646a9fe4a65a18dfed21f2949c")));
+}
+
+BOOST_AUTO_TEST_CASE(makeperm) // Covers makeperm (implicit), permcount and perm
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (perm 'x) (x (+ 1 x))
+ (perm 'y) (y (+ 10 y))
+ (when (= 2 permcount)
+ (return (+ x y)))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(11)));
+}
+
+BOOST_AUTO_TEST_CASE(ecrecover)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return
+ (ecrecover
+ ; Hash of 'hello world'
+ 0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad
+ ; v = 1 + 27
+ 0x1c
+ ; r
+ 0xdebaaa0cddb321b2dcaaf846d39605de7b97e77ba6106587855b9106cb104215
+ ; s
+ 0x61a22d94fa8b8a687ff9c911c844d1c016d1a685a9166858f9c7c1bc85128aca)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(fromHex("0x8743523d96a1b2cbe0c6909653a56da18ed484af")));
+}
+
+BOOST_AUTO_TEST_CASE(sha256_two_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF")
+ (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!")
+ (sha256 0x20 0x40)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0xcf25a9fe3d86ae228c226c81d2d8c64c687cd6dc4586d10d8e7e4e5b6706d429")));
+}
+
+BOOST_AUTO_TEST_CASE(ripemd160_two_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF")
+ (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!")
+ (ripemd160 0x20 0x40)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0x36c6b90a49e17d4c1e1b0e634ec74124d9b207da")));
+}
+
+BOOST_AUTO_TEST_CASE(sha256_one_arg)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (sha256 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0xcfd2f1fad75a1978da0a444883db7251414b139f31f5a04704c291fdb0e175e6")));
+}
+
+BOOST_AUTO_TEST_CASE(ripemd160_one_arg)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (ripemd160 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(
+ fromHex("0xac5ab22e07b0fb80c69b6207902f725e2507e546")));
+}
+
+BOOST_AUTO_TEST_CASE(wei_szabo_finney_ether)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (+ wei (+ szabo (+ finney ether)))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(1001001000000000001)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_left)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (shl 1 8)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(256)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return (shr 65536 8)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(256)));
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/liblll/Parser.cpp b/test/liblll/Parser.cpp
index 0d5d9ea5..fc977b81 100644
--- a/test/liblll/Parser.cpp
+++ b/test/liblll/Parser.cpp
@@ -171,7 +171,13 @@ BOOST_AUTO_TEST_CASE(list)
BOOST_CHECK_EQUAL(parse(text), R"(( 1234 ))");
BOOST_CHECK(successParse("( 1234 5467 )"));
- BOOST_CHECK(!successParse("()"));
+ BOOST_CHECK(successParse("()"));
+}
+
+BOOST_AUTO_TEST_CASE(macro_with_zero_args)
+{
+ char const* text = "(def 'zeroargs () (asm INVALID))";
+ BOOST_CHECK(successParse(text));
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp
index 0972ce82..4fb4f20c 100644
--- a/test/libsolidity/ASTJSON.cpp
+++ b/test/libsolidity/ASTJSON.cpp
@@ -41,10 +41,10 @@ BOOST_AUTO_TEST_CASE(smoke_test)
{
CompilerStack c;
c.addSource("a", "contract C {}");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
BOOST_CHECK_EQUAL(astJson["name"], "SourceUnit");
}
@@ -52,10 +52,10 @@ BOOST_AUTO_TEST_CASE(source_location)
{
CompilerStack c;
c.addSource("a", "contract C { function f() { var x = 2; x++; } }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
BOOST_CHECK_EQUAL(astJson["name"], "SourceUnit");
BOOST_CHECK_EQUAL(astJson["children"][0]["name"], "ContractDefinition");
BOOST_CHECK_EQUAL(astJson["children"][0]["children"][0]["name"], "FunctionDefinition");
@@ -66,10 +66,10 @@ BOOST_AUTO_TEST_CASE(inheritance_specifier)
{
CompilerStack c;
c.addSource("a", "contract C1 {} contract C2 is C1 {}");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
BOOST_CHECK_EQUAL(astJson["children"][1]["attributes"]["name"], "C2");
BOOST_CHECK_EQUAL(astJson["children"][1]["children"][0]["name"], "InheritanceSpecifier");
BOOST_CHECK_EQUAL(astJson["children"][1]["children"][0]["src"], "30:2:1");
@@ -81,27 +81,27 @@ BOOST_AUTO_TEST_CASE(using_for_directive)
{
CompilerStack c;
c.addSource("a", "library L {} contract C { using L for uint; }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value usingFor = astJson["children"][1]["children"][0];
BOOST_CHECK_EQUAL(usingFor["name"], "UsingForDirective");
BOOST_CHECK_EQUAL(usingFor["src"], "26:17:1");
BOOST_CHECK_EQUAL(usingFor["children"][0]["name"], "UserDefinedTypeName");
BOOST_CHECK_EQUAL(usingFor["children"][0]["attributes"]["name"], "L");
BOOST_CHECK_EQUAL(usingFor["children"][1]["name"], "ElementaryTypeName");
- BOOST_CHECK_EQUAL(usingFor["children"][1]["attributes"]["name"], "uint");
+ BOOST_CHECK_EQUAL(usingFor["children"][1]["attributes"]["name"], "uint");
}
BOOST_AUTO_TEST_CASE(enum_value)
{
CompilerStack c;
c.addSource("a", "contract C { enum E { A, B } }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value enumDefinition = astJson["children"][0]["children"][0];
BOOST_CHECK_EQUAL(enumDefinition["children"][0]["name"], "EnumValue");
BOOST_CHECK_EQUAL(enumDefinition["children"][0]["attributes"]["name"], "A");
@@ -115,10 +115,10 @@ BOOST_AUTO_TEST_CASE(modifier_definition)
{
CompilerStack c;
c.addSource("a", "contract C { modifier M(uint i) { _; } function F() M(1) {} }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value modifier = astJson["children"][0]["children"][0];
BOOST_CHECK_EQUAL(modifier["name"], "ModifierDefinition");
BOOST_CHECK_EQUAL(modifier["attributes"]["name"], "M");
@@ -129,10 +129,10 @@ BOOST_AUTO_TEST_CASE(modifier_invocation)
{
CompilerStack c;
c.addSource("a", "contract C { modifier M(uint i) { _; } function F() M(1) {} }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value modifier = astJson["children"][0]["children"][1]["children"][2];
BOOST_CHECK_EQUAL(modifier["name"], "ModifierInvocation");
BOOST_CHECK_EQUAL(modifier["src"], "52:4:1");
@@ -145,10 +145,10 @@ BOOST_AUTO_TEST_CASE(event_definition)
{
CompilerStack c;
c.addSource("a", "contract C { event E(); }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value event = astJson["children"][0]["children"][0];
BOOST_CHECK_EQUAL(event["name"], "EventDefinition");
BOOST_CHECK_EQUAL(event["attributes"]["name"], "E");
@@ -159,10 +159,10 @@ BOOST_AUTO_TEST_CASE(array_type_name)
{
CompilerStack c;
c.addSource("a", "contract C { uint[] i; }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value array = astJson["children"][0]["children"][0]["children"][0];
BOOST_CHECK_EQUAL(array["name"], "ArrayTypeName");
BOOST_CHECK_EQUAL(array["src"], "13:6:1");
@@ -172,10 +172,10 @@ BOOST_AUTO_TEST_CASE(placeholder_statement)
{
CompilerStack c;
c.addSource("a", "contract C { modifier M { _; } }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value placeholder = astJson["children"][0]["children"][0]["children"][1]["children"][0];
BOOST_CHECK_EQUAL(placeholder["name"], "PlaceholderStatement");
BOOST_CHECK_EQUAL(placeholder["src"], "26:1:1");
@@ -185,14 +185,14 @@ BOOST_AUTO_TEST_CASE(non_utf8)
{
CompilerStack c;
c.addSource("a", "contract C { function f() { var x = hex\"ff\"; } }");
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value literal = astJson["children"][0]["children"][0]["children"][2]["children"][0]["children"][1];
BOOST_CHECK_EQUAL(literal["name"], "Literal");
BOOST_CHECK_EQUAL(literal["attributes"]["hexvalue"], "ff");
- BOOST_CHECK_EQUAL(literal["attributes"]["token"], Json::nullValue);
+ BOOST_CHECK_EQUAL(literal["attributes"]["token"], "string");
BOOST_CHECK_EQUAL(literal["attributes"]["value"], Json::nullValue);
BOOST_CHECK(literal["attributes"]["type"].asString().find("invalid") != string::npos);
}
@@ -204,10 +204,10 @@ BOOST_AUTO_TEST_CASE(function_type)
"contract C { function f(function() external payable returns (uint) x) "
"returns (function() external constant returns (uint)) {} }"
);
- c.parse();
+ c.parseAndAnalyze();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
- Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
+ Json::Value astJson = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
Json::Value fun = astJson["children"][0]["children"][0];
BOOST_CHECK_EQUAL(fun["name"], "FunctionDefinition");
Json::Value argument = fun["children"][0]["children"][0];
@@ -228,6 +228,36 @@ BOOST_AUTO_TEST_CASE(function_type)
BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external");
}
+BOOST_AUTO_TEST_CASE(documentation)
+{
+ CompilerStack c;
+ c.addSource("a", "/**This contract is empty*/ contract C {}");
+ c.addSource("b",
+ "/**This contract is empty"
+ " and has a line-breaking comment.*/"
+ "contract C {}"
+ );
+ c.parseAndAnalyze();
+ map<string, unsigned> sourceIndices;
+ sourceIndices["a"] = 0;
+ sourceIndices["b"] = 1;
+ Json::Value astJsonA = ASTJsonConverter(true, sourceIndices).toJson(c.ast("a"));
+ Json::Value documentationA = astJsonA["children"][0]["attributes"]["documentation"];
+ BOOST_CHECK_EQUAL(documentationA, "This contract is empty");
+ Json::Value astJsonB = ASTJsonConverter(true, sourceIndices).toJson(c.ast("b"));
+ Json::Value documentationB = astJsonB["children"][0]["attributes"]["documentation"];
+ BOOST_CHECK_EQUAL(documentationB, "This contract is empty and has a line-breaking comment.");
+ //same tests for non-legacy mode
+ astJsonA = ASTJsonConverter(false, sourceIndices).toJson(c.ast("a"));
+ documentationA = astJsonA["nodes"][0]["documentation"];
+ BOOST_CHECK_EQUAL(documentationA, "This contract is empty");
+ astJsonB = ASTJsonConverter(false, sourceIndices).toJson(c.ast("b"));
+ documentationB = astJsonB["nodes"][0]["documentation"];
+ BOOST_CHECK_EQUAL(documentationB, "This contract is empty and has a line-breaking comment.");
+
+}
+
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp
index bdbe7dba..99a2996e 100644
--- a/test/libsolidity/Assembly.cpp
+++ b/test/libsolidity/Assembly.cpp
@@ -31,6 +31,7 @@
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h>
+#include <libsolidity/interface/ErrorReporter.h>
using namespace std;
using namespace dev::eth;
@@ -48,27 +49,29 @@ namespace
eth::AssemblyItems compileContract(const string& _sourceCode)
{
ErrorList errors;
- Parser parser(errors);
+ ErrorReporter errorReporter(errors);
+ Parser parser(errorReporter);
ASTPointer<SourceUnit> sourceUnit;
BOOST_REQUIRE_NO_THROW(sourceUnit = parser.parse(make_shared<Scanner>(CharStream(_sourceCode))));
BOOST_CHECK(!!sourceUnit);
- NameAndTypeResolver resolver({}, errors);
- solAssert(Error::containsOnlyWarnings(errors), "");
+ map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
+ NameAndTypeResolver resolver({}, scopes, errorReporter);
+ solAssert(Error::containsOnlyWarnings(errorReporter.errors()), "");
resolver.registerDeclarations(*sourceUnit);
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract));
- if (!Error::containsOnlyWarnings(errors))
+ if (!Error::containsOnlyWarnings(errorReporter.errors()))
return AssemblyItems();
}
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
- TypeChecker checker(errors);
+ TypeChecker checker(errorReporter);
BOOST_REQUIRE_NO_THROW(checker.checkTypeRequirements(*contract));
- if (!Error::containsOnlyWarnings(errors))
+ if (!Error::containsOnlyWarnings(errorReporter.errors()))
return AssemblyItems();
}
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
@@ -116,8 +119,8 @@ BOOST_AUTO_TEST_CASE(location_test)
shared_ptr<string const> n = make_shared<string>("");
AssemblyItems items = compileContract(sourceCode);
vector<SourceLocation> locations =
- vector<SourceLocation>(16, SourceLocation(2, 75, n)) +
- vector<SourceLocation>(27, SourceLocation(20, 72, n)) +
+ vector<SourceLocation>(19, SourceLocation(2, 75, n)) +
+ vector<SourceLocation>(32, SourceLocation(20, 72, n)) +
vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} +
vector<SourceLocation>(2, SourceLocation(58, 67, n)) +
vector<SourceLocation>(3, SourceLocation(20, 72, n));
diff --git a/test/libsolidity/ErrorCheck.cpp b/test/libsolidity/ErrorCheck.cpp
index 75555c9b..9b0f9fb7 100644
--- a/test/libsolidity/ErrorCheck.cpp
+++ b/test/libsolidity/ErrorCheck.cpp
@@ -29,6 +29,15 @@ using namespace std;
bool dev::solidity::searchErrorMessage(Error const& _err, std::string const& _substr)
{
if (string const* errorMessage = boost::get_error_info<dev::errinfo_comment>(_err))
- return errorMessage->find(_substr) != std::string::npos;
+ {
+ if (errorMessage->find(_substr) == std::string::npos)
+ {
+ cout << "Expected message \"" << _substr << "\" but found" << *errorMessage << endl;
+ return false;
+ }
+ return true;
+ }
+ else
+ cout << "Expected error message but found none." << endl;
return _substr.empty();
}
diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp
index 0671fb15..ef560b12 100644
--- a/test/libsolidity/GasMeter.cpp
+++ b/test/libsolidity/GasMeter.cpp
@@ -151,20 +151,20 @@ BOOST_AUTO_TEST_CASE(simple_contract)
contract test {
bytes32 public shaValue;
function f(uint a) {
- shaValue = sha3(a);
+ shaValue = keccak256(a);
}
}
)";
testCreationTimeGas(sourceCode);
}
-BOOST_AUTO_TEST_CASE(store_sha3)
+BOOST_AUTO_TEST_CASE(store_keccak256)
{
char const* sourceCode = R"(
contract test {
bytes32 public shaValue;
function test(uint a) {
- shaValue = sha3(a);
+ shaValue = keccak256(a);
}
}
)";
@@ -248,6 +248,51 @@ BOOST_AUTO_TEST_CASE(multiple_external_functions)
testRunTimeGas("g(uint256)", vector<bytes>{encodeArgs(2)});
}
+BOOST_AUTO_TEST_CASE(exponent_size)
+{
+ char const* sourceCode = R"(
+ contract A {
+ function g(uint x) returns (uint) {
+ return x ** 0x100;
+ }
+ function h(uint x) returns (uint) {
+ return x ** 0x10000;
+ }
+ }
+ )";
+ testCreationTimeGas(sourceCode);
+ testRunTimeGas("g(uint256)", vector<bytes>{encodeArgs(2)});
+ testRunTimeGas("h(uint256)", vector<bytes>{encodeArgs(2)});
+}
+
+BOOST_AUTO_TEST_CASE(balance_gas)
+{
+ char const* sourceCode = R"(
+ contract A {
+ function lookup_balance(address a) returns (uint) {
+ return a.balance;
+ }
+ }
+ )";
+ testCreationTimeGas(sourceCode);
+ testRunTimeGas("lookup_balance(address)", vector<bytes>{encodeArgs(2), encodeArgs(100)});
+}
+
+BOOST_AUTO_TEST_CASE(extcodesize_gas)
+{
+ char const* sourceCode = R"(
+ contract A {
+ function f() returns (uint _s) {
+ assembly {
+ _s := extcodesize(0x30)
+ }
+ }
+ }
+ )";
+ testCreationTimeGas(sourceCode);
+ testRunTimeGas("f()", vector<bytes>{encodeArgs()});
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp
index bc6adc26..6aa96fb8 100644
--- a/test/libsolidity/Imports.cpp
+++ b/test/libsolidity/Imports.cpp
@@ -106,6 +106,7 @@ BOOST_AUTO_TEST_CASE(library_name_clash)
CompilerStack c;
c.addSource("a", "library A {} pragma solidity >=0.0;");
c.addSource("b", "library A {} pragma solidity >=0.0;");
+ c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";");
BOOST_CHECK(!c.compile());
}
@@ -164,6 +165,43 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings)
BOOST_CHECK(c.compile());
}
+BOOST_AUTO_TEST_CASE(filename_with_period)
+{
+ CompilerStack c;
+ c.addSource("a/a.sol", "import \".b.sol\"; contract A is B {} pragma solidity >=0.0;");
+ c.addSource("a/.b.sol", "contract B {} pragma solidity >=0.0;");
+ BOOST_CHECK(!c.compile());
+}
+
+BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_preserved)
+{
+ CompilerStack c;
+ c.setRemappings(vector<string>{"foo=vendor/foo_2.0.0", "vendor/bar:foo=vendor/foo_1.0.0", "bar=vendor/bar"});
+ c.addSource("main.sol", "import \"foo/foo.sol\"; import {Bar} from \"bar/bar.sol\"; contract Main is Foo2, Bar {} pragma solidity >=0.0;");
+ c.addSource("vendor/bar/bar.sol", "import \"foo/foo.sol\"; contract Bar {Foo1 foo;} pragma solidity >=0.0;");
+ c.addSource("vendor/foo_1.0.0/foo.sol", "contract Foo1 {} pragma solidity >=0.0;");
+ c.addSource("vendor/foo_2.0.0/foo.sol", "contract Foo2 {} pragma solidity >=0.0;");
+ BOOST_CHECK(c.compile());
+}
+
+BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent)
+{
+ CompilerStack c;
+ c.setRemappings(vector<string>{"a:x/y/z=d", "a/b:x=e"});
+ c.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;");
+ c.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;");
+ c.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;");
+ c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;");
+ BOOST_CHECK(c.compile());
+ CompilerStack d;
+ d.setRemappings(vector<string>{"a/b:x=e", "a:x/y/z=d"});
+ d.addSource("a/main.sol", "import \"x/y/z/z.sol\"; contract Main is D {} pragma solidity >=0.0;");
+ d.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;");
+ d.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;");
+ d.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;");
+ BOOST_CHECK(d.compile());
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp
index 64073edc..5197f649 100644
--- a/test/libsolidity/InlineAssembly.cpp
+++ b/test/libsolidity/InlineAssembly.cpp
@@ -20,14 +20,20 @@
* Unit tests for inline assembly.
*/
-#include <string>
-#include <memory>
-#include <libevmasm/Assembly.h>
+#include "../TestHelper.h"
+
+#include <libsolidity/interface/AssemblyStack.h>
#include <libsolidity/parsing/Scanner.h>
-#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST.h>
-#include "../TestHelper.h"
+#include <test/libsolidity/ErrorCheck.h>
+#include <libevmasm/Assembly.h>
+
+#include <boost/optional.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <string>
+#include <memory>
using namespace std;
@@ -41,43 +47,103 @@ namespace test
namespace
{
-bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true)
+boost::optional<Error> parseAndReturnFirstError(
+ string const& _source,
+ bool _assemble = false,
+ bool _allowWarnings = true,
+ AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM
+)
{
- assembly::InlineAssemblyStack stack;
+ AssemblyStack stack;
+ bool success = false;
try
{
- if (!stack.parse(std::make_shared<Scanner>(CharStream(_source))))
- return false;
- if (_assemble)
- {
- stack.assemble();
- if (!stack.errors().empty())
- if (!_allowWarnings || !Error::containsOnlyWarnings(stack.errors()))
- return false;
- }
+ success = stack.parseAndAnalyze("", _source);
+ if (success && _assemble)
+ stack.assemble(_machine);
}
catch (FatalError const&)
{
- if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError))
- return false;
+ BOOST_FAIL("Fatal error leaked.");
+ success = false;
}
- if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError))
- return false;
+ shared_ptr<Error const> error;
+ for (auto const& e: stack.errors())
+ {
+ if (_allowWarnings && e->type() == Error::Type::Warning)
+ continue;
+ if (error)
+ BOOST_FAIL("Found more than one error.");
+ error = e;
+ }
+ if (!success)
+ BOOST_REQUIRE(error);
+ if (error)
+ return *error;
+ return {};
+}
- BOOST_CHECK(Error::containsOnlyWarnings(stack.errors()));
- return true;
+bool successParse(
+ string const& _source,
+ bool _assemble = false,
+ bool _allowWarnings = true,
+ AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM
+)
+{
+ return !parseAndReturnFirstError(_source, _assemble, _allowWarnings, _machine);
}
bool successAssemble(string const& _source, bool _allowWarnings = true)
{
- return successParse(_source, true, _allowWarnings);
+ return successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM) &&
+ successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM15);
+}
+
+Error expectError(std::string const& _source, bool _assemble, bool _allowWarnings = false)
+{
+
+ auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings);
+ BOOST_REQUIRE(error);
+ return *error;
+}
+
+void parsePrintCompare(string const& _source, bool _canWarn = false)
+{
+ AssemblyStack stack;
+ BOOST_REQUIRE(stack.parseAndAnalyze("", _source));
+ if (_canWarn)
+ BOOST_REQUIRE(Error::containsOnlyWarnings(stack.errors()));
+ else
+ BOOST_REQUIRE(stack.errors().empty());
+ BOOST_CHECK_EQUAL(stack.print(), _source);
}
}
+#define CHECK_ERROR(text, assemble, typ, substring, warnings) \
+do \
+{ \
+ Error err = expectError((text), (assemble), warnings); \
+ BOOST_CHECK(err.type() == (Error::Type::typ)); \
+ BOOST_CHECK(searchErrorMessage(err, (substring))); \
+} while(0)
+
+#define CHECK_PARSE_ERROR(text, type, substring) \
+CHECK_ERROR(text, false, type, substring, false)
+
+#define CHECK_PARSE_WARNING(text, type, substring) \
+CHECK_ERROR(text, false, type, substring, false)
+
+#define CHECK_ASSEMBLE_ERROR(text, type, substring) \
+CHECK_ERROR(text, true, type, substring, false)
+
+
BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly)
+
+BOOST_AUTO_TEST_SUITE(Parsing)
+
BOOST_AUTO_TEST_CASE(smoke_test)
{
BOOST_CHECK(successParse("{ }"));
@@ -85,22 +151,22 @@ BOOST_AUTO_TEST_CASE(smoke_test)
BOOST_AUTO_TEST_CASE(simple_instructions)
{
- BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub }"));
+ BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub pop }"));
}
BOOST_AUTO_TEST_CASE(suicide_selfdestruct)
{
- BOOST_CHECK(successParse("{ suicide selfdestruct }"));
+ BOOST_CHECK(successParse("{ 0x01 suicide 0x02 selfdestruct }"));
}
BOOST_AUTO_TEST_CASE(keywords)
{
- BOOST_CHECK(successParse("{ byte return address }"));
+ BOOST_CHECK(successParse("{ 1 2 byte 2 return address pop }"));
}
BOOST_AUTO_TEST_CASE(constants)
{
- BOOST_CHECK(successParse("{ 7 8 mul }"));
+ BOOST_CHECK(successParse("{ 7 8 mul pop }"));
}
BOOST_AUTO_TEST_CASE(vardecl)
@@ -108,39 +174,159 @@ BOOST_AUTO_TEST_CASE(vardecl)
BOOST_CHECK(successParse("{ let x := 7 }"));
}
+BOOST_AUTO_TEST_CASE(vardecl_name_clashes)
+{
+ CHECK_PARSE_ERROR("{ let x := 1 let x := 2 }", DeclarationError, "Variable name x already taken in this scope.");
+}
+
+BOOST_AUTO_TEST_CASE(vardecl_multi)
+{
+ BOOST_CHECK(successParse("{ function f() -> x, y {} let x, y := f() }"));
+}
+
+BOOST_AUTO_TEST_CASE(vardecl_multi_conflict)
+{
+ CHECK_PARSE_ERROR("{ function f() -> x, y {} let x, x := f() }", DeclarationError, "Variable name x already taken in this scope.");
+}
+
+BOOST_AUTO_TEST_CASE(vardecl_bool)
+{
+ CHECK_PARSE_ERROR("{ let x := true }", ParserError, "True and false are not valid literals.");
+ CHECK_PARSE_ERROR("{ let x := false }", ParserError, "True and false are not valid literals.");
+}
+
BOOST_AUTO_TEST_CASE(assignment)
{
- BOOST_CHECK(successParse("{ 7 8 add =: x }"));
+ BOOST_CHECK(successParse("{ let x := 2 7 8 add =: x }"));
}
BOOST_AUTO_TEST_CASE(label)
{
- BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump }"));
+ BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump pop }"));
}
BOOST_AUTO_TEST_CASE(label_complex)
{
- BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) }"));
+ BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop }"));
}
BOOST_AUTO_TEST_CASE(functional)
{
- BOOST_CHECK(successParse("{ add(7, mul(6, x)) add mul(7, 8) }"));
+ BOOST_CHECK(successParse("{ let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }"));
+}
+
+BOOST_AUTO_TEST_CASE(functional_partial)
+{
+ CHECK_PARSE_ERROR("{ let x := byte }", ParserError, "Expected token \"(\"");
+}
+
+BOOST_AUTO_TEST_CASE(functional_partial_success)
+{
+ BOOST_CHECK(successParse("{ let x := byte(1, 2) }"));
}
BOOST_AUTO_TEST_CASE(functional_assignment)
{
- BOOST_CHECK(successParse("{ x := 7 }"));
+ BOOST_CHECK(successParse("{ let x := 2 x := 7 }"));
}
BOOST_AUTO_TEST_CASE(functional_assignment_complex)
{
- BOOST_CHECK(successParse("{ x := add(7, mul(6, x)) add mul(7, 8) }"));
+ BOOST_CHECK(successParse("{ let x := 2 x := add(7, mul(6, x)) mul(7, 8) add }"));
}
BOOST_AUTO_TEST_CASE(vardecl_complex)
{
- BOOST_CHECK(successParse("{ let x := add(7, mul(6, x)) add mul(7, 8) }"));
+ BOOST_CHECK(successParse("{ let y := 2 let x := add(7, mul(6, y)) add mul(7, 8) }"));
+}
+
+BOOST_AUTO_TEST_CASE(variable_use_before_decl)
+{
+ CHECK_PARSE_ERROR("{ x := 2 let x := 3 }", DeclarationError, "Variable x used before it was declared.");
+ CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared.");
+}
+
+BOOST_AUTO_TEST_CASE(switch_statement)
+{
+ BOOST_CHECK(successParse("{ switch 42 default {} }"));
+ BOOST_CHECK(successParse("{ switch 42 case 1 {} }"));
+ BOOST_CHECK(successParse("{ switch 42 case 1 {} case 2 {} }"));
+ BOOST_CHECK(successParse("{ switch 42 case 1 {} default {} }"));
+ BOOST_CHECK(successParse("{ switch 42 case 1 {} case 2 {} default {} }"));
+ BOOST_CHECK(successParse("{ switch mul(1, 2) case 1 {} case 2 {} default {} }"));
+ BOOST_CHECK(successParse("{ function f() -> x {} switch f() case 1 {} case 2 {} default {} }"));
+}
+
+BOOST_AUTO_TEST_CASE(switch_no_cases)
+{
+ CHECK_PARSE_ERROR("{ switch 42 }", ParserError, "Switch statement without any cases.");
+}
+
+BOOST_AUTO_TEST_CASE(switch_duplicate_case)
+{
+ CHECK_PARSE_ERROR("{ switch 42 case 1 {} case 1 {} default {} }", DeclarationError, "Duplicate case defined");
+}
+
+BOOST_AUTO_TEST_CASE(switch_invalid_expression)
+{
+ CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected.");
+ CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch.");
+ CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
+}
+
+BOOST_AUTO_TEST_CASE(switch_default_before_case)
+{
+ CHECK_PARSE_ERROR("{ switch 42 default {} case 1 {} }", ParserError, "Case not allowed after default case.");
+}
+
+BOOST_AUTO_TEST_CASE(switch_duplicate_default_case)
+{
+ CHECK_PARSE_ERROR("{ switch 42 default {} default {} }", ParserError, "Only one default case allowed.");
+}
+
+BOOST_AUTO_TEST_CASE(switch_invalid_case)
+{
+ CHECK_PARSE_ERROR("{ switch 42 case mul(1, 2) {} case 2 {} default {} }", ParserError, "Literal expected.");
+}
+
+BOOST_AUTO_TEST_CASE(switch_invalid_body)
+{
+ CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected token LBrace got 'Identifier'");
+}
+
+BOOST_AUTO_TEST_CASE(for_statement)
+{
+ BOOST_CHECK(successParse("{ for {} 1 {} {} }"));
+ BOOST_CHECK(successParse("{ for { let i := 1 } lt(i, 5) { i := add(i, 1) } {} }"));
+}
+
+BOOST_AUTO_TEST_CASE(for_invalid_expression)
+{
+ CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected.");
+ CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected token LBrace got 'Number'");
+ CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected token LBrace got 'Number'");
+ CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected token LBrace got 'Number'");
+ CHECK_PARSE_ERROR("{ for {} calldatasize {} {} }", ParserError, "Instructions are not supported as conditions for the for statement.");
+ CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
+}
+
+BOOST_AUTO_TEST_CASE(for_visibility)
+{
+ BOOST_CHECK(successParse("{ for { let i := 1 } i { pop(i) } { pop(i) } }"));
+ CHECK_PARSE_ERROR("{ for {} i { let i := 1 } {} }", DeclarationError, "Identifier not found");
+ CHECK_PARSE_ERROR("{ for {} 1 { let i := 1 } { pop(i) } }", DeclarationError, "Identifier not found");
+ CHECK_PARSE_ERROR("{ for {} 1 { pop(i) } { let i := 1 } }", DeclarationError, "Identifier not found");
+ CHECK_PARSE_ERROR("{ for { pop(i) } 1 { let i := 1 } {} }", DeclarationError, "Identifier not found");
+ CHECK_PARSE_ERROR("{ for { pop(i) } 1 { } { let i := 1 } }", DeclarationError, "Identifier not found");
+ CHECK_PARSE_ERROR("{ for {} i {} { let i := 1 } }", DeclarationError, "Identifier not found");
+ CHECK_PARSE_ERROR("{ for {} 1 { pop(i) } { let i := 1 } }", DeclarationError, "Identifier not found");
+ CHECK_PARSE_ERROR("{ for { let x := 1 } 1 { let x := 1 } {} }", DeclarationError, "Variable name x already taken in this scope");
+ CHECK_PARSE_ERROR("{ for { let x := 1 } 1 {} { let x := 1 } }", DeclarationError, "Variable name x already taken in this scope");
+ CHECK_PARSE_ERROR("{ let x := 1 for { let x := 1 } 1 {} {} }", DeclarationError, "Variable name x already taken in this scope");
+ CHECK_PARSE_ERROR("{ let x := 1 for {} 1 { let x := 1 } {} }", DeclarationError, "Variable name x already taken in this scope");
+ CHECK_PARSE_ERROR("{ let x := 1 for {} 1 {} { let x := 1 } }", DeclarationError, "Variable name x already taken in this scope");
+ // Check that body and post are not sub-scopes of each other.
+ BOOST_CHECK(successParse("{ for {} 1 { let x := 1 } { let x := 1 } }"));
}
BOOST_AUTO_TEST_CASE(blocks)
@@ -148,6 +334,152 @@ BOOST_AUTO_TEST_CASE(blocks)
BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }"));
}
+BOOST_AUTO_TEST_CASE(function_definitions)
+{
+ BOOST_CHECK(successParse("{ function f() { } function g(a) -> x { } }"));
+}
+
+BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
+{
+ BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> x, y { } }"));
+}
+
+BOOST_AUTO_TEST_CASE(function_calls)
+{
+ BOOST_CHECK(successParse("{ function f(a) -> b {} function g(a, b, c) {} function x() { g(1, 2, f(mul(2, 3))) x() } }"));
+}
+
+BOOST_AUTO_TEST_CASE(opcode_for_functions)
+{
+ CHECK_PARSE_ERROR("{ function gas() { } }", ParserError, "Cannot use instruction names for identifier names.");
+}
+
+BOOST_AUTO_TEST_CASE(opcode_for_function_args)
+{
+ CHECK_PARSE_ERROR("{ function f(gas) { } }", ParserError, "Cannot use instruction names for identifier names.");
+ CHECK_PARSE_ERROR("{ function f() -> gas { } }", ParserError, "Cannot use instruction names for identifier names.");
+}
+
+BOOST_AUTO_TEST_CASE(name_clashes)
+{
+ CHECK_PARSE_ERROR("{ let g := 2 function g() { } }", DeclarationError, "Function name g already taken in this scope");
+}
+
+BOOST_AUTO_TEST_CASE(variable_access_cross_functions)
+{
+ CHECK_PARSE_ERROR("{ let x := 2 function g() { x pop } }", DeclarationError, "Identifier not found.");
+}
+
+BOOST_AUTO_TEST_CASE(invalid_tuple_assignment)
+{
+ /// The push(42) is added here to silence the unbalanced stack error, so that there's only one error reported.
+ CHECK_PARSE_ERROR("{ 42 let x, y := 1 }", DeclarationError, "Variable count mismatch.");
+}
+
+BOOST_AUTO_TEST_CASE(instruction_too_few_arguments)
+{
+ CHECK_PARSE_ERROR("{ mul() }", ParserError, "Expected expression (\"mul\" expects 2 arguments)");
+ CHECK_PARSE_ERROR("{ mul(1) }", ParserError, "Expected comma (\"mul\" expects 2 arguments)");
+}
+
+BOOST_AUTO_TEST_CASE(instruction_too_many_arguments)
+{
+ CHECK_PARSE_ERROR("{ mul(1, 2, 3) }", ParserError, "Expected ')' (\"mul\" expects 2 arguments)");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE(Printing)
+
+BOOST_AUTO_TEST_CASE(print_smoke)
+{
+ parsePrintCompare("{\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_instructions)
+{
+ parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n pop\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_subblock)
+{
+ parsePrintCompare("{\n {\n dup4\n add\n }\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_functional)
+{
+ parsePrintCompare("{\n let x := mul(sload(0x12), 7)\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_label)
+{
+ parsePrintCompare("{\n loop:\n jump(loop)\n}", true);
+}
+
+BOOST_AUTO_TEST_CASE(print_assignments)
+{
+ parsePrintCompare("{\n let x := mul(2, 3)\n 7\n =: x\n x := add(1, 2)\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_multi_assignments)
+{
+ parsePrintCompare("{\n function f() -> x, y\n {\n }\n let x, y := f()\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_string_literals)
+{
+ parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n pop\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
+{
+ string source = "{ let x := \"\\u1bac\" }";
+ string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}";
+ AssemblyStack stack;
+ BOOST_REQUIRE(stack.parseAndAnalyze("", source));
+ BOOST_REQUIRE(stack.errors().empty());
+ BOOST_CHECK_EQUAL(stack.print(), parsed);
+ parsePrintCompare(parsed);
+}
+
+BOOST_AUTO_TEST_CASE(print_switch)
+{
+ parsePrintCompare("{\n switch 42\n case 1 {\n }\n case 2 {\n }\n default {\n }\n}");
+}
+
+BOOST_AUTO_TEST_CASE(print_for)
+{
+ parsePrintCompare("{\n let ret := 5\n for {\n let i := 1\n }\n lt(i, 15)\n {\n i := add(i, 1)\n }\n {\n ret := mul(ret, i)\n }\n}");
+}
+
+BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
+{
+ parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> x, y\n {\n }\n}");
+}
+
+BOOST_AUTO_TEST_CASE(function_calls)
+{
+ string source = R"({
+ function y()
+ {
+ }
+ function f(a) -> b
+ {
+ }
+ function g(a, b, c)
+ {
+ }
+ g(1, mul(2, address), f(mul(2, caller)))
+ y()
+})";
+ boost::replace_all(source, "\t", " ");
+ parsePrintCompare(source);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE(Analysis)
+
BOOST_AUTO_TEST_CASE(string_literals)
{
BOOST_CHECK(successAssemble("{ let x := \"12345678901234567890123456789012\" }"));
@@ -155,33 +487,161 @@ BOOST_AUTO_TEST_CASE(string_literals)
BOOST_AUTO_TEST_CASE(oversize_string_literals)
{
- BOOST_CHECK(!successAssemble("{ let x := \"123456789012345678901234567890123\" }"));
+ CHECK_ASSEMBLE_ERROR("{ let x := \"123456789012345678901234567890123\" }", TypeError, "String literal too long");
}
BOOST_AUTO_TEST_CASE(assignment_after_tag)
{
- BOOST_CHECK(successParse("{ let x := 1 { tag: =: x } }"));
+ BOOST_CHECK(successParse("{ let x := 1 { 7 tag: =: x } }"));
}
BOOST_AUTO_TEST_CASE(magic_variables)
{
- BOOST_CHECK(!successAssemble("{ this }"));
- BOOST_CHECK(!successAssemble("{ ecrecover }"));
- BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }"));
+ CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found");
+ CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found");
+ BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover pop }"));
+}
+
+BOOST_AUTO_TEST_CASE(stack_variables)
+{
+ BOOST_CHECK(successAssemble("{ let y := 3 { 2 { let x := y } pop} }"));
}
BOOST_AUTO_TEST_CASE(imbalanced_stack)
{
BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false));
- BOOST_CHECK(!successAssemble("{ 1 }", false));
+ CHECK_ASSEMBLE_ERROR("{ 1 }", DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s).");
+ CHECK_ASSEMBLE_ERROR("{ pop }", DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s).");
BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false));
}
BOOST_AUTO_TEST_CASE(error_tag)
{
- BOOST_CHECK(successAssemble("{ invalidJumpLabel }"));
+ CHECK_ERROR("{ jump(invalidJumpLabel) }", true, DeclarationError, "Identifier not found", true);
}
+BOOST_AUTO_TEST_CASE(designated_invalid_instruction)
+{
+ BOOST_CHECK(successAssemble("{ invalid }"));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration)
+{
+ CHECK_ASSEMBLE_ERROR("{ let gas := 1 }", ParserError, "Cannot use instruction names for identifier names.");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment)
+{
+ CHECK_ASSEMBLE_ERROR("{ 2 =: gas }", ParserError, "Identifier expected, got instruction name.");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment)
+{
+ CHECK_ASSEMBLE_ERROR("{ gas := 2 }", ParserError, "Label name / variable name must precede \":\"");
+}
+
+BOOST_AUTO_TEST_CASE(revert)
+{
+ BOOST_CHECK(successAssemble("{ revert(0, 0) }"));
+}
+
+BOOST_AUTO_TEST_CASE(function_calls)
+{
+ BOOST_CHECK(successAssemble("{ function f() {} }"));
+ BOOST_CHECK(successAssemble("{ function f() { let y := 2 } }"));
+ BOOST_CHECK(successAssemble("{ function f() -> z { let y := 2 } }"));
+ BOOST_CHECK(successAssemble("{ function f(a) { let y := 2 } }"));
+ BOOST_CHECK(successAssemble("{ function f(a) { let y := a } }"));
+ BOOST_CHECK(successAssemble("{ function f() -> x, y, z {} }"));
+ BOOST_CHECK(successAssemble("{ function f(x, y, z) {} }"));
+ BOOST_CHECK(successAssemble("{ function f(a, b) -> x, y, z { y := a } }"));
+ BOOST_CHECK(successAssemble("{ function f() {} f() }"));
+ BOOST_CHECK(successAssemble("{ function f() -> x, y { x := 1 y := 2} let a, b := f() }"));
+ BOOST_CHECK(successAssemble("{ function f(a, b) -> x, y { x := b y := a } let a, b := f(2, 3) }"));
+ BOOST_CHECK(successAssemble("{ function rec(a) { rec(sub(a, 1)) } rec(2) }"));
+ BOOST_CHECK(successAssemble("{ let r := 2 function f() -> x, y { x := 1 y := 2} let a, b := f() b := r }"));
+ BOOST_CHECK(successAssemble("{ function f() { g() } function g() { f() } }"));
+}
+
+BOOST_AUTO_TEST_CASE(embedded_functions)
+{
+ BOOST_CHECK(successAssemble("{ function f(r, s) -> x { function g(a) -> b { } x := g(2) } let x := f(2, 3) }"));
+}
+
+BOOST_AUTO_TEST_CASE(switch_statement)
+{
+ BOOST_CHECK(successAssemble("{ switch 1 default {} }"));
+ BOOST_CHECK(successAssemble("{ switch 1 case 1 {} default {} }"));
+ BOOST_CHECK(successAssemble("{ switch 1 case 1 {} }"));
+ BOOST_CHECK(successAssemble("{ let a := 3 switch a case 1 { a := 1 } case 2 { a := 5 } a := 9}"));
+ BOOST_CHECK(successAssemble("{ let a := 2 switch calldataload(0) case 1 { a := 1 } case 2 { a := 5 } }"));
+}
+
+BOOST_AUTO_TEST_CASE(for_statement)
+{
+ BOOST_CHECK(successAssemble("{ for {} 1 {} {} }"));
+ BOOST_CHECK(successAssemble("{ let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } }"));
+}
+
+
+BOOST_AUTO_TEST_CASE(large_constant)
+{
+ auto source = R"({
+ switch mul(1, 2)
+ case 0x0000000000000000000000000000000000000000000000000000000026121ff0 {
+ }
+ })";
+ BOOST_CHECK(successAssemble(source));
+}
+
+BOOST_AUTO_TEST_CASE(keccak256)
+{
+ BOOST_CHECK(successAssemble("{ 0 0 keccak256 pop }"));
+ BOOST_CHECK(successAssemble("{ pop(keccak256(0, 0)) }"));
+ BOOST_CHECK(successAssemble("{ 0 0 sha3 pop }"));
+ BOOST_CHECK(successAssemble("{ pop(sha3(0, 0)) }"));
+}
+
+BOOST_AUTO_TEST_CASE(returndatasize)
+{
+ BOOST_CHECK(successAssemble("{ let r := returndatasize }"));
+}
+
+BOOST_AUTO_TEST_CASE(returndatasize_functional)
+{
+ BOOST_CHECK(successAssemble("{ let r := returndatasize() }"));
+}
+
+BOOST_AUTO_TEST_CASE(returndatacopy)
+{
+ BOOST_CHECK(successAssemble("{ 64 32 0 returndatacopy }"));
+}
+
+BOOST_AUTO_TEST_CASE(returndatacopy_functional)
+{
+ BOOST_CHECK(successAssemble("{ returndatacopy(0, 32, 64) }"));
+}
+
+BOOST_AUTO_TEST_CASE(staticcall)
+{
+ BOOST_CHECK(successAssemble("{ pop(staticcall(10000, 0x123, 64, 0x10, 128, 0x10)) }"));
+}
+
+BOOST_AUTO_TEST_CASE(create2)
+{
+ BOOST_CHECK(successAssemble("{ pop(create2(10, 0x123, 32, 64)) }"));
+}
+
+BOOST_AUTO_TEST_CASE(jump_warning)
+{
+ CHECK_PARSE_WARNING("{ 1 jump }", Warning, "Jump instructions");
+ CHECK_PARSE_WARNING("{ 1 2 jumpi }", Warning, "Jump instructions");
+ CHECK_PARSE_WARNING("{ a: jump(a) }", Warning, "Jump instructions");
+ CHECK_PARSE_WARNING("{ a: jumpi(a, 2) }", Warning, "Jump instructions");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp
new file mode 100644
index 00000000..aa690f0b
--- /dev/null
+++ b/test/libsolidity/JSONCompiler.cpp
@@ -0,0 +1,111 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Unit tests for solc/jsonCompiler.cpp.
+ */
+
+#include <string>
+#include <iostream>
+#include <regex>
+#include <boost/test/unit_test.hpp>
+#include <libdevcore/JSON.h>
+
+#include "../Metadata.h"
+#include "../TestHelper.h"
+
+using namespace std;
+
+extern "C"
+{
+extern char const* compileJSONMulti(char const* _input, bool _optimize);
+}
+
+namespace dev
+{
+namespace solidity
+{
+namespace test
+{
+
+namespace
+{
+
+Json::Value compile(string const& _input)
+{
+ string output(compileJSONMulti(_input.c_str(), dev::test::Options::get().optimize));
+ Json::Value ret;
+ BOOST_REQUIRE(Json::Reader().parse(output, ret, false));
+ return ret;
+}
+
+} // end anonymous namespace
+
+BOOST_AUTO_TEST_SUITE(JSONCompiler)
+
+BOOST_AUTO_TEST_CASE(basic_compilation)
+{
+ char const* input = R"(
+ {
+ "sources": {
+ "fileA": "contract A { }"
+ }
+ }
+ )";
+ Json::Value result = compile(input);
+ BOOST_CHECK(result.isObject());
+ BOOST_CHECK(result["contracts"].isObject());
+ BOOST_CHECK(result["contracts"]["fileA:A"].isObject());
+ Json::Value contract = result["contracts"]["fileA:A"];
+ BOOST_CHECK(contract.isObject());
+ BOOST_CHECK(contract["interface"].isString());
+ BOOST_CHECK_EQUAL(contract["interface"].asString(), "[]");
+ BOOST_CHECK(contract["bytecode"].isString());
+ BOOST_CHECK_EQUAL(
+ dev::test::bytecodeSansMetadata(contract["bytecode"].asString()),
+ "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00"
+ );
+ BOOST_CHECK(contract["runtimeBytecode"].isString());
+ BOOST_CHECK_EQUAL(
+ dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()),
+ "60606040525b600080fd00"
+ );
+ BOOST_CHECK(contract["functionHashes"].isObject());
+ BOOST_CHECK(contract["gasEstimates"].isObject());
+ BOOST_CHECK_EQUAL(
+ dev::jsonCompactPrint(contract["gasEstimates"]),
+ "{\"creation\":[62,10800],\"external\":{},\"internal\":{}}"
+ );
+ BOOST_CHECK(contract["metadata"].isString());
+ BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString()));
+ BOOST_CHECK(result["sources"].isObject());
+ BOOST_CHECK(result["sources"]["fileA"].isObject());
+ BOOST_CHECK(result["sources"]["fileA"]["AST"].isObject());
+ BOOST_CHECK_EQUAL(
+ dev::jsonCompactPrint(result["sources"]["fileA"]["AST"]),
+ "{\"attributes\":{\"absolutePath\":\"fileA\",\"exportedSymbols\":{\"A\":[1]}},"
+ "\"children\":[{\"attributes\":{\"baseContracts\":[null],\"contractDependencies\":[null],"
+ "\"contractKind\":\"contract\",\"documentation\":null,\"fullyImplemented\":true,\"linearizedBaseContracts\":[1],"
+ "\"name\":\"A\",\"nodes\":[null],\"scope\":2},\"id\":1,\"name\":\"ContractDefinition\","
+ "\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}"
+ );
+}
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
+} // end namespaces
diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp
new file mode 100644
index 00000000..60bb2e4e
--- /dev/null
+++ b/test/libsolidity/Metadata.cpp
@@ -0,0 +1,63 @@
+/*
+ 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/>.
+ */
+/**
+ * @date 2017
+ * Unit tests for the metadata output.
+ */
+
+#include "../Metadata.h"
+#include "../TestHelper.h"
+#include <libsolidity/interface/CompilerStack.h>
+#include <libdevcore/SwarmHash.h>
+
+namespace dev
+{
+namespace solidity
+{
+namespace test
+{
+
+BOOST_AUTO_TEST_SUITE(Metadata)
+
+BOOST_AUTO_TEST_CASE(metadata_stamp)
+{
+ // Check that the metadata stamp is at the end of the runtime bytecode.
+ char const* sourceCode = R"(
+ pragma solidity >=0.0;
+ contract test {
+ function g(function(uint) external returns (uint) x) {}
+ }
+ )";
+ CompilerStack compilerStack;
+ BOOST_REQUIRE(compilerStack.compile(std::string(sourceCode)));
+ bytes const& bytecode = compilerStack.runtimeObject("test").bytecode;
+ std::string const& metadata = compilerStack.onChainMetadata("test");
+ BOOST_CHECK(dev::test::isValidMetadata(metadata));
+ bytes hash = dev::swarmHash(metadata).asBytes();
+ BOOST_REQUIRE(hash.size() == 32);
+ BOOST_REQUIRE(bytecode.size() >= 2);
+ size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]);
+ BOOST_REQUIRE(metadataCBORSize < bytecode.size() - 2);
+ bytes expectation = bytes{0xa1, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} + hash;
+ BOOST_CHECK(std::equal(expectation.begin(), expectation.end(), bytecode.end() - metadataCBORSize - 2));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
+}
diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp
index 890db241..f87390e1 100644
--- a/test/libsolidity/SolidityABIJSON.cpp
+++ b/test/libsolidity/SolidityABIJSON.cpp
@@ -42,9 +42,9 @@ public:
void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString)
{
- ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing contract failed");
+ ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parseAndAnalyze("pragma solidity >=0.0;\n" + _code), "Parsing contract failed");
- Json::Value generatedInterface = m_compilerStack.metadata("", DocumentationType::ABIInterface);
+ Json::Value generatedInterface = m_compilerStack.contractABI("");
Json::Value expectedInterface;
m_reader.parse(_expectedInterfaceString, expectedInterface);
BOOST_CHECK_MESSAGE(
@@ -63,9 +63,11 @@ BOOST_FIXTURE_TEST_SUITE(SolidityABIJSON, JSONInterfaceChecker)
BOOST_AUTO_TEST_CASE(basic_test)
{
- char const* sourceCode = "contract test {\n"
- " function f(uint a) returns(uint d) { return a * 7; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint a) returns(uint d) { return a * 7; }
+ }
+ )";
char const* interface = R"([
{
@@ -93,8 +95,9 @@ BOOST_AUTO_TEST_CASE(basic_test)
BOOST_AUTO_TEST_CASE(empty_contract)
{
- char const* sourceCode = "contract test {\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test { }
+ )";
char const* interface = "[]";
checkInterface(sourceCode, interface);
@@ -102,10 +105,12 @@ BOOST_AUTO_TEST_CASE(empty_contract)
BOOST_AUTO_TEST_CASE(multiple_methods)
{
- char const* sourceCode = "contract test {\n"
- " function f(uint a) returns(uint d) { return a * 7; }\n"
- " function g(uint b) returns(uint e) { return b * 8; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint a) returns(uint d) { return a * 7; }
+ function g(uint b) returns(uint e) { return b * 8; }
+ }
+ )";
char const* interface = R"([
{
@@ -151,9 +156,11 @@ BOOST_AUTO_TEST_CASE(multiple_methods)
BOOST_AUTO_TEST_CASE(multiple_params)
{
- char const* sourceCode = "contract test {\n"
- " function f(uint a, uint b) returns(uint d) { return a + b; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint a, uint b) returns(uint d) { return a + b; }
+ }
+ )";
char const* interface = R"([
{
@@ -186,10 +193,12 @@ BOOST_AUTO_TEST_CASE(multiple_params)
BOOST_AUTO_TEST_CASE(multiple_methods_order)
{
// methods are expected to be in alpabetical order
- char const* sourceCode = "contract test {\n"
- " function f(uint a) returns(uint d) { return a * 7; }\n"
- " function c(uint b) returns(uint e) { return b * 8; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint a) returns(uint d) { return a * 7; }
+ function c(uint b) returns(uint e) { return b * 8; }
+ }
+ )";
char const* interface = R"([
{
@@ -235,10 +244,12 @@ BOOST_AUTO_TEST_CASE(multiple_methods_order)
BOOST_AUTO_TEST_CASE(const_function)
{
- char const* sourceCode = "contract test {\n"
- " function foo(uint a, uint b) returns(uint d) { return a + b; }\n"
- " function boo(uint32 a) constant returns(uint b) { return a * 4; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function foo(uint a, uint b) returns(uint d) { return a + b; }
+ function boo(uint32 a) constant returns(uint b) { return a * 4; }
+ }
+ )";
char const* interface = R"([
{
@@ -286,11 +297,13 @@ BOOST_AUTO_TEST_CASE(const_function)
BOOST_AUTO_TEST_CASE(events)
{
- char const* sourceCode = "contract test {\n"
- " function f(uint a) returns(uint d) { return a * 7; }\n"
- " event e1(uint b, address indexed c); \n"
- " event e2(); \n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint a) returns(uint d) { return a * 7; }
+ event e1(uint b, address indexed c);
+ event e2();
+ }
+ )";
char const* interface = R"([
{
"name": "f",
@@ -341,9 +354,11 @@ BOOST_AUTO_TEST_CASE(events)
BOOST_AUTO_TEST_CASE(events_anonymous)
{
- char const* sourceCode = "contract test {\n"
- " event e() anonymous; \n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ event e() anonymous;
+ }
+ )";
char const* interface = R"([
{
"name": "e",
@@ -359,15 +374,16 @@ BOOST_AUTO_TEST_CASE(events_anonymous)
BOOST_AUTO_TEST_CASE(inherited)
{
- char const* sourceCode =
- " contract Base { \n"
- " function baseFunction(uint p) returns (uint i) { return p; } \n"
- " event baseEvent(bytes32 indexed evtArgBase); \n"
- " } \n"
- " contract Derived is Base { \n"
- " function derivedFunction(bytes32 p) returns (bytes32 i) { return p; } \n"
- " event derivedEvent(uint indexed evtArgDerived); \n"
- " }";
+ char const* sourceCode = R"(
+ contract Base {
+ function baseFunction(uint p) returns (uint i) { return p; }
+ event baseEvent(bytes32 indexed evtArgBase);
+ }
+ contract Derived is Base {
+ function derivedFunction(bytes32 p) returns (bytes32 i) { return p; }
+ event derivedEvent(uint indexed evtArgDerived);
+ }
+ )";
char const* interface = R"([
{
@@ -431,13 +447,14 @@ BOOST_AUTO_TEST_CASE(inherited)
BOOST_AUTO_TEST_CASE(empty_name_input_parameter_with_named_one)
{
char const* sourceCode = R"(
- contract test {
- function f(uint, uint k) returns(uint ret_k, uint ret_g){
- uint g = 8;
- ret_k = k;
- ret_g = g;
+ contract test {
+ function f(uint, uint k) returns(uint ret_k, uint ret_g) {
+ uint g = 8;
+ ret_k = k;
+ ret_g = g;
+ }
}
- })";
+ )";
char const* interface = R"([
{
@@ -475,10 +492,11 @@ BOOST_AUTO_TEST_CASE(empty_name_return_parameter)
{
char const* sourceCode = R"(
contract test {
- function f(uint k) returns(uint){
- return k;
+ function f(uint k) returns(uint) {
+ return k;
+ }
}
- })";
+ )";
char const* interface = R"([
{
@@ -542,7 +560,7 @@ BOOST_AUTO_TEST_CASE(return_param_in_abi)
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
function test(ActionChoices param) {}
- function ret() returns(ActionChoices){
+ function ret() returns(ActionChoices) {
ActionChoices action = ActionChoices.GoLeft;
return action;
}
@@ -612,7 +630,7 @@ BOOST_AUTO_TEST_CASE(library_function)
char const* sourceCode = R"(
library test {
struct StructType { uint a; }
- function f(StructType storage b, uint[] storage c, test d) returns (uint[] e, StructType storage f){}
+ function f(StructType storage b, uint[] storage c, test d) returns (uint[] e, StructType storage f) {}
}
)";
@@ -734,26 +752,6 @@ BOOST_AUTO_TEST_CASE(function_type)
checkInterface(sourceCode, interface);
}
-BOOST_AUTO_TEST_CASE(metadata_stamp)
-{
- // Check that the metadata stamp is at the end of the runtime bytecode.
- char const* sourceCode = R"(
- pragma solidity >=0.0;
- contract test {
- function g(function(uint) external returns (uint) x) {}
- }
- )";
- BOOST_REQUIRE(m_compilerStack.compile(std::string(sourceCode)));
- bytes const& bytecode = m_compilerStack.runtimeObject("test").bytecode;
- bytes hash = dev::swarmHash(m_compilerStack.onChainMetadata("test")).asBytes();
- BOOST_REQUIRE(hash.size() == 32);
- BOOST_REQUIRE(bytecode.size() >= 2);
- size_t metadataCBORSize = (size_t(bytecode.end()[-2]) << 8) + size_t(bytecode.end()[-1]);
- BOOST_REQUIRE(metadataCBORSize < bytecode.size() - 2);
- bytes expectation = bytes{0xa1, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} + hash;
- BOOST_CHECK(std::equal(expectation.begin(), expectation.end(), bytecode.end() - metadataCBORSize - 2));
-}
-
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp
index aa1eb20a..c9771fbd 100644
--- a/test/libsolidity/SolidityEndToEndTest.cpp
+++ b/test/libsolidity/SolidityEndToEndTest.cpp
@@ -1355,7 +1355,7 @@ BOOST_AUTO_TEST_CASE(multiple_elementary_accessors)
function test() {
data = 8;
name = "Celina";
- a_hash = sha3(123);
+ a_hash = keccak256(123);
an_address = address(0x1337);
super_secret_data = 42;
}
@@ -1482,9 +1482,15 @@ BOOST_AUTO_TEST_CASE(now)
}
}
)";
- m_rpc.test_modifyTimestamp(0x776347e2);
compileAndRun(sourceCode);
- BOOST_CHECK(callContractFunction("someInfo()") == encodeArgs(true, 0x776347e3));
+ u256 startBlock = m_blockNumber;
+ size_t startTime = blockTimestamp(startBlock);
+ auto ret = callContractFunction("someInfo()");
+ u256 endBlock = m_blockNumber;
+ size_t endTime = blockTimestamp(endBlock);
+ BOOST_CHECK(startBlock != endBlock);
+ BOOST_CHECK(startTime != endTime);
+ BOOST_CHECK(ret == encodeArgs(true, endTime));
}
BOOST_AUTO_TEST_CASE(type_conversions_cleanup)
@@ -1675,6 +1681,42 @@ BOOST_AUTO_TEST_CASE(send_ether)
BOOST_CHECK_EQUAL(balanceAt(address), amount);
}
+BOOST_AUTO_TEST_CASE(transfer_ether)
+{
+ char const* sourceCode = R"(
+ contract A {
+ function A() payable {}
+ function a(address addr, uint amount) returns (uint) {
+ addr.transfer(amount);
+ return this.balance;
+ }
+ function b(address addr, uint amount) {
+ addr.transfer(amount);
+ }
+ }
+
+ contract B {
+ }
+
+ contract C {
+ function () payable {
+ throw;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "B");
+ u160 const nonPayableRecipient = m_contractAddress;
+ compileAndRun(sourceCode, 0, "C");
+ u160 const oogRecipient = m_contractAddress;
+ compileAndRun(sourceCode, 20, "A");
+ u160 payableRecipient(23);
+ BOOST_CHECK(callContractFunction("a(address,uint256)", payableRecipient, 10) == encodeArgs(10));
+ BOOST_CHECK_EQUAL(balanceAt(payableRecipient), 10);
+ BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10);
+ BOOST_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10) == encodeArgs());
+ BOOST_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10) == encodeArgs());
+}
+
BOOST_AUTO_TEST_CASE(log0)
{
char const* sourceCode = R"(
@@ -1822,12 +1864,12 @@ BOOST_AUTO_TEST_CASE(selfdestruct)
BOOST_CHECK_EQUAL(balanceAt(address), amount);
}
-BOOST_AUTO_TEST_CASE(sha3)
+BOOST_AUTO_TEST_CASE(keccak256)
{
char const* sourceCode = R"(
contract test {
- function a(bytes32 input) returns (bytes32 sha3hash) {
- return sha3(input);
+ function a(bytes32 input) returns (bytes32 hash) {
+ return keccak256(input);
}
}
)";
@@ -1841,6 +1883,23 @@ BOOST_AUTO_TEST_CASE(sha3)
testContractAgainstCpp("a(bytes32)", f, u256(-1));
}
+BOOST_AUTO_TEST_CASE(sha3)
+{
+ char const* sourceCode = R"(
+ contract test {
+ // to confuse the optimiser
+ function b(bytes32 input) returns (bytes32) {
+ return sha3(input);
+ }
+ function a(bytes32 input) returns (bool) {
+ return keccak256(input) == b(input);
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+ BOOST_REQUIRE(callContractFunction("a(bytes32)", u256(42)) == encodeArgs(true));
+}
+
BOOST_AUTO_TEST_CASE(sha256)
{
char const* sourceCode = R"(
@@ -2505,6 +2564,16 @@ BOOST_AUTO_TEST_CASE(constructor_argument_overriding)
BOOST_CHECK(callContractFunction("getA()") == encodeArgs(3));
}
+BOOST_AUTO_TEST_CASE(internal_constructor)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function C() internal {}
+ }
+ )";
+ BOOST_CHECK(compileAndRunWithoutCheck(sourceCode, 0, "C").empty());
+}
+
BOOST_AUTO_TEST_CASE(function_modifier)
{
char const* sourceCode = R"(
@@ -2761,6 +2830,7 @@ BOOST_AUTO_TEST_CASE(event_no_arguments)
}
}
)";
+
compileAndRun(sourceCode);
callContractFunction("deposit()");
BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
@@ -2770,6 +2840,126 @@ BOOST_AUTO_TEST_CASE(event_no_arguments)
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()")));
}
+BOOST_AUTO_TEST_CASE(event_access_through_base_name)
+{
+ char const* sourceCode = R"(
+ contract A {
+ event x();
+ }
+ contract B is A {
+ function f() returns (uint) {
+ A.x();
+ return 1;
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+ callContractFunction("f()");
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data.empty());
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("x()")));
+}
+
+BOOST_AUTO_TEST_CASE(events_with_same_name)
+{
+ char const* sourceCode = R"(
+ contract ClientReceipt {
+ event Deposit;
+ event Deposit(address _addr);
+ event Deposit(address _addr, uint _amount);
+ function deposit() returns (uint) {
+ Deposit();
+ return 1;
+ }
+ function deposit(address _addr) returns (uint) {
+ Deposit(_addr);
+ return 1;
+ }
+ function deposit(address _addr, uint _amount) returns (uint) {
+ Deposit(_addr, _amount);
+ return 1;
+ }
+ }
+ )";
+ u160 const c_loggedAddress = m_contractAddress;
+
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("deposit()") == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data.empty());
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()")));
+
+ BOOST_CHECK(callContractFunction("deposit(address)", c_loggedAddress) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)")));
+
+ BOOST_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)")));
+}
+
+BOOST_AUTO_TEST_CASE(events_with_same_name_inherited)
+{
+ char const* sourceCode = R"(
+ contract A {
+ event Deposit;
+ }
+
+ contract B {
+ event Deposit(address _addr);
+ }
+
+ contract ClientReceipt is A, B {
+ event Deposit(address _addr, uint _amount);
+ function deposit() returns (uint) {
+ Deposit();
+ return 1;
+ }
+ function deposit(address _addr) returns (uint) {
+ Deposit(_addr);
+ return 1;
+ }
+ function deposit(address _addr, uint _amount) returns (uint) {
+ Deposit(_addr, _amount);
+ return 1;
+ }
+ }
+ )";
+ u160 const c_loggedAddress = m_contractAddress;
+
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("deposit()") == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data.empty());
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()")));
+
+ BOOST_CHECK(callContractFunction("deposit(address)", c_loggedAddress) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)")));
+
+ BOOST_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)")));
+}
+
BOOST_AUTO_TEST_CASE(event_anonymous)
{
char const* sourceCode = R"(
@@ -2937,13 +3127,13 @@ BOOST_AUTO_TEST_CASE(empty_name_return_parameter)
BOOST_CHECK(callContractFunction("f(uint256)", 9) == encodeArgs(9));
}
-BOOST_AUTO_TEST_CASE(sha3_multiple_arguments)
+BOOST_AUTO_TEST_CASE(keccak256_multiple_arguments)
{
char const* sourceCode = R"(
contract c {
function foo(uint a, uint b, uint c) returns (bytes32 d)
{
- d = sha3(a, b, c);
+ d = keccak256(a, b, c);
}
}
)";
@@ -2956,13 +3146,13 @@ BOOST_AUTO_TEST_CASE(sha3_multiple_arguments)
toBigEndian(u256(13)))));
}
-BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_numeric_literals)
+BOOST_AUTO_TEST_CASE(keccak256_multiple_arguments_with_numeric_literals)
{
char const* sourceCode = R"(
contract c {
function foo(uint a, uint16 b) returns (bytes32 d)
{
- d = sha3(a, b, 145);
+ d = keccak256(a, b, 145);
}
}
)";
@@ -2975,17 +3165,17 @@ BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_numeric_literals)
bytes(1, 0x91))));
}
-BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_string_literals)
+BOOST_AUTO_TEST_CASE(keccak256_multiple_arguments_with_string_literals)
{
char const* sourceCode = R"(
contract c {
function foo() returns (bytes32 d)
{
- d = sha3("foo");
+ d = keccak256("foo");
}
function bar(uint a, uint16 b) returns (bytes32 d)
{
- d = sha3(a, b, 145, "foo");
+ d = keccak256(a, b, 145, "foo");
}
}
)";
@@ -3001,7 +3191,7 @@ BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_string_literals)
bytes{0x66, 0x6f, 0x6f})));
}
-BOOST_AUTO_TEST_CASE(sha3_with_bytes)
+BOOST_AUTO_TEST_CASE(keccak256_with_bytes)
{
char const* sourceCode = R"(
contract c {
@@ -3012,7 +3202,7 @@ BOOST_AUTO_TEST_CASE(sha3_with_bytes)
data[0] = "f";
data[1] = "o";
data[2] = "o";
- return sha3(data) == sha3("foo");
+ return keccak256(data) == keccak256("foo");
}
}
)";
@@ -3020,7 +3210,7 @@ BOOST_AUTO_TEST_CASE(sha3_with_bytes)
BOOST_CHECK(callContractFunction("foo()") == encodeArgs(true));
}
-BOOST_AUTO_TEST_CASE(iterated_sha3_with_bytes)
+BOOST_AUTO_TEST_CASE(iterated_keccak256_with_bytes)
{
char const* sourceCode = R"(
contract c {
@@ -3031,7 +3221,7 @@ BOOST_AUTO_TEST_CASE(iterated_sha3_with_bytes)
data[0] = "x";
data[1] = "y";
data[2] = "z";
- return sha3("b", sha3(data), "a");
+ return keccak256("b", keccak256(data), "a");
}
}
)";
@@ -3041,13 +3231,13 @@ BOOST_AUTO_TEST_CASE(iterated_sha3_with_bytes)
));
}
-BOOST_AUTO_TEST_CASE(keccak256_multiple_arguments)
+BOOST_AUTO_TEST_CASE(sha3_multiple_arguments)
{
char const* sourceCode = R"(
contract c {
function foo(uint a, uint b, uint c) returns (bytes32 d)
{
- d = keccak256(a, b, c);
+ d = sha3(a, b, c);
}
})";
compileAndRun(sourceCode);
@@ -3072,7 +3262,7 @@ BOOST_AUTO_TEST_CASE(generic_call)
function sender() payable {}
function doSend(address rec) returns (uint d)
{
- bytes4 signature = bytes4(bytes32(sha3("receive(uint256)")));
+ bytes4 signature = bytes4(bytes32(keccak256("receive(uint256)")));
rec.call.value(2)(signature, 23);
return receiver(rec).received();
}
@@ -3097,7 +3287,7 @@ BOOST_AUTO_TEST_CASE(generic_callcode)
function Sender() payable { }
function doSend(address rec) returns (uint d)
{
- bytes4 signature = bytes4(bytes32(sha3("receive(uint256)")));
+ bytes4 signature = bytes4(bytes32(keccak256("receive(uint256)")));
rec.callcode.value(2)(signature, 23);
return Receiver(rec).received();
}
@@ -3134,7 +3324,7 @@ BOOST_AUTO_TEST_CASE(generic_delegatecall)
function Sender() payable {}
function doSend(address rec) payable
{
- bytes4 signature = bytes4(bytes32(sha3("receive(uint256)")));
+ bytes4 signature = bytes4(bytes32(keccak256("receive(uint256)")));
if (rec.delegatecall(signature, 23)) {}
}
}
@@ -3199,7 +3389,7 @@ BOOST_AUTO_TEST_CASE(bytes_from_calldata_to_memory)
char const* sourceCode = R"(
contract C {
function f() returns (bytes32) {
- return sha3("abc", msg.data);
+ return keccak256("abc", msg.data);
}
}
)";
@@ -3512,6 +3702,50 @@ BOOST_AUTO_TEST_CASE(enum_explicit_overflow)
BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 0) == encodeArgs(0));
}
+BOOST_AUTO_TEST_CASE(storing_invalid_boolean)
+{
+ char const* sourceCode = R"(
+ contract C {
+ event Ev(bool);
+ bool public perm;
+ function set() returns(uint) {
+ bool tmp;
+ assembly {
+ tmp := 5
+ }
+ perm = tmp;
+ return 1;
+ }
+ function ret() returns(bool) {
+ bool tmp;
+ assembly {
+ tmp := 5
+ }
+ return tmp;
+ }
+ function ev() returns(uint) {
+ bool tmp;
+ assembly {
+ tmp := 5
+ }
+ Ev(tmp);
+ return 1;
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("set()") == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("perm()") == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("ret()") == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("ev()") == encodeArgs(1));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(1));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Ev(bool)")));
+}
+
+
BOOST_AUTO_TEST_CASE(using_contract_enums_with_explicit_contract_name)
{
char const* sourceCode = R"(
@@ -4293,6 +4527,38 @@ BOOST_AUTO_TEST_CASE(array_copy_including_mapping)
BOOST_CHECK(storageEmpty(m_contractAddress));
}
+BOOST_AUTO_TEST_CASE(swap_in_storage_overwrite)
+{
+ // This tests a swap in storage which does not work as one
+ // might expect because we do not have temporary storage.
+ // (x, y) = (y, x) is the same as
+ // y = x;
+ // x = y;
+ char const* sourceCode = R"(
+ contract c {
+ struct S { uint a; uint b; }
+ S public x;
+ S public y;
+ function set() {
+ x.a = 1; x.b = 2;
+ y.a = 3; y.b = 4;
+ }
+ function swap() {
+ (x, y) = (y, x);
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0), u256(0)));
+ BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(0), u256(0)));
+ BOOST_CHECK(callContractFunction("set()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1), u256(2)));
+ BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(3), u256(4)));
+ BOOST_CHECK(callContractFunction("swap()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(1), u256(2)));
+ BOOST_CHECK(callContractFunction("y()") == encodeArgs(u256(1), u256(2)));
+}
+
BOOST_AUTO_TEST_CASE(pass_dynamic_arguments_to_the_base)
{
char const* sourceCode = R"(
@@ -4369,7 +4635,6 @@ BOOST_AUTO_TEST_CASE(simple_constant_variables_test)
BOOST_AUTO_TEST_CASE(constant_variables)
{
- //for now constant specifier is valid only for uint, bytesXX, string and enums
char const* sourceCode = R"(
contract Foo {
uint constant x = 56;
@@ -4380,6 +4645,58 @@ BOOST_AUTO_TEST_CASE(constant_variables)
compileAndRun(sourceCode);
}
+BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_expression)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint constant x = 0x123 + 0x456;
+ function f() returns (uint) { return x + 1; }
+ }
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(0x123 + 0x456 + 1));
+}
+
+BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_keccak)
+{
+ char const* sourceCode = R"(
+ contract C {
+ bytes32 constant x = keccak256("abc");
+ function f() returns (bytes32) { return x; }
+ }
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(dev::keccak256("abc")));
+}
+
+// Disabled until https://github.com/ethereum/solidity/issues/715 is implemented
+//BOOST_AUTO_TEST_CASE(assignment_to_const_array_vars)
+//{
+// char const* sourceCode = R"(
+// contract C {
+// uint[3] constant x = [uint(1), 2, 3];
+// uint constant y = x[0] + x[1] + x[2];
+// function f() returns (uint) { return y; }
+// }
+// )";
+// compileAndRun(sourceCode);
+// BOOST_CHECK(callContractFunction("f()") == encodeArgs(1 + 2 + 3));
+//}
+
+// Disabled until https://github.com/ethereum/solidity/issues/715 is implemented
+//BOOST_AUTO_TEST_CASE(constant_struct)
+//{
+// char const* sourceCode = R"(
+// contract C {
+// struct S { uint x; uint[] y; }
+// S constant x = S(5, new uint[](4));
+// function f() returns (uint) { return x.x; }
+// }
+// )";
+// compileAndRun(sourceCode);
+// BOOST_CHECK(callContractFunction("f()") == encodeArgs(5));
+//}
+
BOOST_AUTO_TEST_CASE(packed_storage_structs_uint)
{
char const* sourceCode = R"(
@@ -4591,6 +4908,34 @@ BOOST_AUTO_TEST_CASE(super_overload)
BOOST_CHECK(callContractFunction("h()") == encodeArgs(2));
}
+BOOST_AUTO_TEST_CASE(bool_conversion)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(bool _b) returns(uint) {
+ if (_b)
+ return 1;
+ else
+ return 0;
+ }
+ function g(bool _in) returns (bool _out) {
+ _out = _in;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(bool)", 0) == encodeArgs(0));
+ BOOST_CHECK(callContractFunction("f(bool)", 1) == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("f(bool)", 2) == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("f(bool)", 3) == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("f(bool)", 255) == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("g(bool)", 0) == encodeArgs(0));
+ BOOST_CHECK(callContractFunction("g(bool)", 1) == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("g(bool)", 2) == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("g(bool)", 3) == encodeArgs(1));
+ BOOST_CHECK(callContractFunction("g(bool)", 255) == encodeArgs(1));
+}
+
BOOST_AUTO_TEST_CASE(packed_storage_signed)
{
char const* sourceCode = R"(
@@ -4819,60 +5164,6 @@ BOOST_AUTO_TEST_CASE(proper_order_of_overwriting_of_attributes)
BOOST_CHECK(callContractFunction("ok()") == encodeArgs(false));
}
-BOOST_AUTO_TEST_CASE(proper_overwriting_accessor_by_function)
-{
- // bug #1798
- char const* sourceCode = R"(
- contract attribute {
- bool ok = false;
- }
- contract func {
- function ok() returns (bool) { return true; }
- }
-
- contract attr_func is attribute, func {
- function checkOk() returns (bool) { return ok(); }
- }
- contract func_attr is func, attribute {
- function checkOk() returns (bool) { return ok; }
- }
- )";
- compileAndRun(sourceCode, 0, "attr_func");
- BOOST_CHECK(callContractFunction("ok()") == encodeArgs(true));
- compileAndRun(sourceCode, 0, "func_attr");
- BOOST_CHECK(callContractFunction("checkOk()") == encodeArgs(false));
-}
-
-
-BOOST_AUTO_TEST_CASE(overwriting_inheritance)
-{
- // bug #1798
- char const* sourceCode = R"(
- contract A {
- function ok() returns (uint) { return 1; }
- }
- contract B {
- function ok() returns (uint) { return 2; }
- }
- contract C {
- uint ok = 6;
- }
- contract AB is A, B {
- function ok() returns (uint) { return 4; }
- }
- contract reversedE is C, AB {
- function checkOk() returns (uint) { return ok(); }
- }
- contract E is AB, C {
- function checkOk() returns (uint) { return ok; }
- }
- )";
- compileAndRun(sourceCode, 0, "reversedE");
- BOOST_CHECK(callContractFunction("checkOk()") == encodeArgs(4));
- compileAndRun(sourceCode, 0, "E");
- BOOST_CHECK(callContractFunction("checkOk()") == encodeArgs(6));
-}
-
BOOST_AUTO_TEST_CASE(struct_assign_reference_to_struct)
{
char const* sourceCode = R"(
@@ -5096,7 +5387,7 @@ BOOST_AUTO_TEST_CASE(reusing_memory)
mapping(uint => uint) map;
function f(uint x) returns (uint) {
map[x] = x;
- return (new Helper(uint(sha3(this.g(map[x]))))).flag();
+ return (new Helper(uint(keccak256(this.g(map[x]))))).flag();
}
function g(uint a) returns (uint)
{
@@ -7113,6 +7404,20 @@ BOOST_AUTO_TEST_CASE(inline_array_return)
BOOST_CHECK(callContractFunction("f()") == encodeArgs(1, 2, 3, 4, 5));
}
+BOOST_AUTO_TEST_CASE(inline_array_singleton)
+{
+ // This caused a failure since the type was not converted to its mobile type.
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (uint) {
+ return [4][0];
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(4)));
+}
+
BOOST_AUTO_TEST_CASE(inline_long_string_return)
{
char const* sourceCode = R"(
@@ -7214,18 +7519,79 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access)
uint16 x;
uint16 public y;
uint public z;
- function f() {
- // we know that z is aligned because it is too large, so we just discard its
- // intra-slot offset value
- assembly { 7 z pop sstore }
+ function f() returns (bool) {
+ uint off1;
+ uint off2;
+ assembly {
+ sstore(z_slot, 7)
+ off1 := z_offset
+ off2 := y_offset
+ }
+ assert(off1 == 0);
+ assert(off2 == 2);
+ return true;
}
}
)";
compileAndRun(sourceCode, 0, "C");
- BOOST_CHECK(callContractFunction("f()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
}
+BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_inside_function)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint16 x;
+ uint16 public y;
+ uint public z;
+ function f() returns (bool) {
+ uint off1;
+ uint off2;
+ assembly {
+ function f() -> o1 {
+ sstore(z_slot, 7)
+ o1 := y_offset
+ }
+ off2 := f()
+ }
+ assert(off2 == 2);
+ return true;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
+ BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7)));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer)
+{
+ char const* sourceCode = R"(
+ contract C {
+ struct Data { uint contents; }
+ uint public separator;
+ Data public a;
+ uint public separator2;
+ function f() returns (bool) {
+ Data x = a;
+ uint off;
+ assembly {
+ sstore(x_slot, 7)
+ off := x_offset
+ }
+ assert(off == 0);
+ return true;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
+ BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(7)));
+ BOOST_CHECK(callContractFunction("separator()") == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("separator2()") == encodeArgs(u256(0)));
+}
+
BOOST_AUTO_TEST_CASE(inline_assembly_jumps)
{
char const* sourceCode = R"(
@@ -7262,6 +7628,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access)
assembly {
_x
jump(g)
+ pop
}
}
}
@@ -7271,6 +7638,178 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access)
BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(10)));
}
+BOOST_AUTO_TEST_CASE(inline_assembly_function_call)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() {
+ assembly {
+ function asmfun(a, b, c) -> x, y, z {
+ x := a
+ y := b
+ z := 7
+ }
+ let a1, b1, c1 := asmfun(1, 2, 3)
+ mstore(0x00, a1)
+ mstore(0x20, b1)
+ mstore(0x40, c1)
+ return(0, 0x60)
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7)));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_function_call2)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() {
+ assembly {
+ let d := 0x10
+ function asmfun(a, b, c) -> x, y, z {
+ x := a
+ y := b
+ z := 7
+ }
+ let a1, b1, c1 := asmfun(1, 2, 3)
+ mstore(0x00, a1)
+ mstore(0x20, b1)
+ mstore(0x40, c1)
+ mstore(0x60, d)
+ return(0, 0x80)
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7), u256(0x10)));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() {
+ assembly {
+ let d := 0x10
+ function asmfun(a, b, c) -> x, y, z {
+ x := g(a)
+ function g(r) -> s { s := mul(r, r) }
+ y := g(b)
+ z := 7
+ }
+ let a1, b1, c1 := asmfun(1, 2, 3)
+ mstore(0x00, a1)
+ mstore(0x20, b1)
+ mstore(0x40, c1)
+ mstore(0x60, d)
+ return(0, 0x80)
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(4), u256(7), u256(0x10)));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_switch)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a) returns (uint b) {
+ assembly {
+ switch a
+ case 1 { b := 8 }
+ case 2 { b := 9 }
+ default { b := 2 }
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(2)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(8)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(9)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(2)));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_recursion)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a) returns (uint b) {
+ assembly {
+ function fac(n) -> nf {
+ switch n
+ case 0 { nf := 1 }
+ case 1 { nf := 1 }
+ default { nf := mul(n, fac(sub(n, 1))) }
+ }
+ b := fac(a)
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(1)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(2)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(6)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(4)) == encodeArgs(u256(24)));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_for)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a) returns (uint b) {
+ assembly {
+ function fac(n) -> nf {
+ nf := 1
+ for { let i := n } gt(i, 0) { i := sub(i, 1) } {
+ nf := mul(nf, i)
+ }
+ }
+ b := fac(a)
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(1)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(2)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(3)) == encodeArgs(u256(6)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(4)) == encodeArgs(u256(24)));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_for2)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint st;
+ function f(uint a) returns (uint b, uint c, uint d) {
+ st = 0;
+ assembly {
+ function sideeffect(r) -> x { sstore(0, add(sload(0), r)) x := 1}
+ for { let i := a } eq(i, sideeffect(2)) { d := add(d, 3) } {
+ b := i
+ i := 0
+ }
+ }
+ c = st;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(0)) == encodeArgs(u256(0), u256(2), u256(0)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(1)) == encodeArgs(u256(1), u256(4), u256(3)));
+ BOOST_CHECK(callContractFunction("f(uint256)", u256(2)) == encodeArgs(u256(0), u256(2), u256(0)));
+}
+
BOOST_AUTO_TEST_CASE(index_access_with_type_conversion)
{
// Test for a bug where higher order bits cleanup was not done for array index access.
@@ -8357,6 +8896,25 @@ BOOST_AUTO_TEST_CASE(function_array_cross_calls)
BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(5), u256(6), u256(7)));
}
+BOOST_AUTO_TEST_CASE(external_function_to_address)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (bool) {
+ return address(this.f) == address(this);
+ }
+ function g(function() external cb) returns (address) {
+ return address(cb);
+ }
+ }
+ )";
+
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(true));
+ BOOST_CHECK(callContractFunction("g(function)", fromHex("00000000000000000000000000000000000004226121ff00000000000000000")) == encodeArgs(u160(0x42)));
+}
+
+
BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage)
{
char const* sourceCode = R"(
@@ -8428,6 +8986,396 @@ BOOST_AUTO_TEST_CASE(shift_negative_constant_right)
BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(-0x42)));
}
+BOOST_AUTO_TEST_CASE(shift_left)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a, uint b) returns (uint) {
+ return a << b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x426600)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0x42660000)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0x84cc0000)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(240)) == fromHex("4266000000000000000000000000000000000000000000000000000000000000"));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(256)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_left_uint32)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint32 a, uint32 b) returns (uint) {
+ return a << b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266)));
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(8)) == encodeArgs(u256(0x426600)));
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(16)) == encodeArgs(u256(0x42660000)));
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(17)) == encodeArgs(u256(0x84cc0000)));
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(32)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_left_uint8)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint8 a, uint8 b) returns (uint) {
+ return a << b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(0)) == encodeArgs(u256(0x66)));
+ BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(8)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_left_larger_type)
+{
+ // This basically tests proper cleanup and conversion. It should not convert x to int8.
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (int8) {
+ uint8 x = 254;
+ int8 y = 1;
+ return y << x;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_left_assignment)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a, uint b) returns (uint) {
+ a <<= b;
+ return a;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x426600)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0x42660000)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0x84cc0000)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(240)) == fromHex("4266000000000000000000000000000000000000000000000000000000000000"));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(256)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_left_assignment_different_type)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a, uint8 b) returns (uint) {
+ a <<= b;
+ return a;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256,uint8)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint8)", u256(0x4266), u256(8)) == encodeArgs(u256(0x426600)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint8)", u256(0x4266), u256(16)) == encodeArgs(u256(0x42660000)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint8)", u256(0x4266), u256(17)) == encodeArgs(u256(0x84cc0000)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint8)", u256(0x4266), u256(240)) == fromHex("4266000000000000000000000000000000000000000000000000000000000000"));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a, uint b) returns (uint) {
+ return a >> b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x42)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right_garbled)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint8 a, uint8 b) returns (uint) {
+ assembly {
+ a := 0xffffffff
+ }
+ // Higher bits should be cleared before the shift
+ return a >> b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x0), u256(4)) == encodeArgs(u256(0xf)));
+ BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x0), u256(0x1004)) == encodeArgs(u256(0xf)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right_uint32)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint32 a, uint32 b) returns (uint) {
+ return a >> b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266)));
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(8)) == encodeArgs(u256(0x42)));
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(16)) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(17)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right_uint8)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint8 a, uint8 b) returns (uint) {
+ return a >> b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(0)) == encodeArgs(u256(0x66)));
+ BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(8)) == encodeArgs(u256(0x0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right_assignment)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a, uint b) returns (uint) {
+ a >>= b;
+ return a;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x42)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(int a, int b) returns (int) {
+ return a >> b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(0)) == encodeArgs(u256(-4266)));
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(8)) == encodeArgs(u256(-16)));
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(16)) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(17)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_assignment)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(int a, int b) returns (int) {
+ a >>= b;
+ return a;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(0)) == encodeArgs(u256(-4266)));
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(8)) == encodeArgs(u256(-16)));
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(16)) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(17)) == encodeArgs(u256(0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_negative_rvalue)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(int a, int b) returns (int) {
+ return a << b;
+ }
+ function g(int a, int b) returns (int) {
+ return a >> b;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(1), u256(-1)) == encodeArgs());
+ BOOST_CHECK(callContractFunction("g(int256,int256)", u256(1), u256(-1)) == encodeArgs());
+}
+
+BOOST_AUTO_TEST_CASE(shift_negative_rvalue_assignment)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(int a, int b) returns (int) {
+ a <<= b;
+ return a;
+ }
+ function g(int a, int b) returns (int) {
+ a >>= b;
+ return a;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(int256,int256)", u256(1), u256(-1)) == encodeArgs());
+ BOOST_CHECK(callContractFunction("g(int256,int256)", u256(1), u256(-1)) == encodeArgs());
+}
+
+BOOST_AUTO_TEST_CASE(shift_constant_left_assignment)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (uint a) {
+ a = 0x42;
+ a <<= 8;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x4200)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_constant_right_assignment)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (uint a) {
+ a = 0x4200;
+ a >>= 8;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x42)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_cleanup)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (uint16 x) {
+ x = 0xffff;
+ x += 32;
+ x <<= 8;
+ x >>= 16;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_cleanup_garbled)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (uint8 x) {
+ assembly {
+ x := 0xffff
+ }
+ x >>= 8;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x0)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_overflow)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function leftU(uint8 x, uint8 y) returns (uint8) {
+ return x << y;
+ }
+ function leftS(int8 x, int8 y) returns (int8) {
+ return x << y;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("leftU(uint8,uint8)", 255, 8) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("leftU(uint8,uint8)", 255, 1) == encodeArgs(u256(254)));
+ BOOST_CHECK(callContractFunction("leftU(uint8,uint8)", 255, 0) == encodeArgs(u256(255)));
+
+ // Result is -128 and output is sign-extended, not zero-padded.
+ BOOST_CHECK(callContractFunction("leftS(int8,int8)", 1, 7) == encodeArgs(u256(0) - 128));
+ BOOST_CHECK(callContractFunction("leftS(int8,int8)", 1, 6) == encodeArgs(u256(64)));
+}
+
+BOOST_AUTO_TEST_CASE(shift_bytes)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function left(bytes20 x, uint8 y) returns (bytes20) {
+ return x << y;
+ }
+ function right(bytes20 x, uint8 y) returns (bytes20) {
+ return x >> y;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("left(bytes20,uint8)", "12345678901234567890", 8 * 8) == encodeArgs("901234567890" + string(8, 0)));
+ BOOST_CHECK(callContractFunction("right(bytes20,uint8)", "12345678901234567890", 8 * 8) == encodeArgs(string(8, 0) + "123456789012"));
+}
+
+BOOST_AUTO_TEST_CASE(shift_bytes_cleanup)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function left(uint8 y) returns (bytes20) {
+ bytes20 x;
+ assembly { x := "12345678901234567890abcde" }
+ return x << y;
+ }
+ function right(uint8 y) returns (bytes20) {
+ bytes20 x;
+ assembly { x := "12345678901234567890abcde" }
+ return x >> y;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("left(uint8)", 8 * 8) == encodeArgs("901234567890" + string(8, 0)));
+ BOOST_CHECK(callContractFunction("right(uint8)", 8 * 8) == encodeArgs(string(8, 0) + "123456789012"));
+}
+
+BOOST_AUTO_TEST_CASE(cleanup_in_compound_assign)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function test() returns (uint, uint) {
+ uint32 a = 0xffffffff;
+ uint16 x = uint16(a);
+ uint16 y = x;
+ x /= 0x100;
+ y = y / 0x100;
+ return (x, y);
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(0xff), u256(0xff)));
+}
+
BOOST_AUTO_TEST_CASE(inline_assembly_in_modifiers)
{
char const* sourceCode = R"(
@@ -8470,13 +9418,76 @@ BOOST_AUTO_TEST_CASE(packed_storage_overflow)
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x1234), u256(0), u256(0), u256(0xfffe)));
}
-BOOST_AUTO_TEST_CASE(inline_assembly_invalidjumplabel)
+BOOST_AUTO_TEST_CASE(contracts_separated_with_comment)
+{
+ char const* sourceCode = R"(
+ contract C1 {}
+ /**
+ **/
+ contract C2 {}
+ )";
+ compileAndRun(sourceCode, 0, "C1");
+ compileAndRun(sourceCode, 0, "C2");
+}
+
+BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once)
+{
+ char const* sourceCode = R"(
+ contract D {
+ bytes a = hex"1237651237125387136581271652831736512837126583171583712358126123765123712538713658127165283173651283712658317158371235812612376512371253871365812716528317365128371265831715837123581261237651237125387136581271652831736512837126583171583712358126";
+ bytes b = hex"1237651237125327136581271252831736512837126583171383712358126123765125712538713658127165253173651283712658357158371235812612376512371a5387136581271652a317365128371265a317158371235812612a765123712538a13658127165a83173651283712a58317158371235a126";
+ function D(uint) {}
+ }
+ contract Double {
+ function f() {
+ new D(2);
+ }
+ function g() {
+ new D(3);
+ }
+ }
+ contract Single {
+ function f() {
+ new D(2);
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK_LE(
+ double(m_compiler.object("Double").bytecode.size()),
+ 1.1 * double(m_compiler.object("Single").bytecode.size())
+ );
+}
+
+BOOST_AUTO_TEST_CASE(recursive_structs)
+{
+ char const* sourceCode = R"(
+ contract C {
+ struct S {
+ S[] x;
+ }
+ S sstorage;
+ function f() returns (uint) {
+ S memory s;
+ s.x = new S[](10);
+ delete s;
+ sstorage.x.length++;
+ delete sstorage;
+ return 1;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1)));
+}
+
+BOOST_AUTO_TEST_CASE(invalid_instruction)
{
char const* sourceCode = R"(
contract C {
function f() {
assembly {
- jump(invalidJumpLabel)
+ invalid
}
}
}
@@ -8485,16 +9496,231 @@ BOOST_AUTO_TEST_CASE(inline_assembly_invalidjumplabel)
BOOST_CHECK(callContractFunction("f()") == encodeArgs());
}
-BOOST_AUTO_TEST_CASE(contracts_separated_with_comment)
+BOOST_AUTO_TEST_CASE(assert_require)
{
char const* sourceCode = R"(
- contract C1 {}
- /**
- **/
- contract C2 {}
+ contract C {
+ function f() {
+ assert(false);
+ }
+ function g(bool val) returns (bool) {
+ assert(val == true);
+ return true;
+ }
+ function h(bool val) returns (bool) {
+ require(val);
+ return true;
+ }
+ }
)";
- compileAndRun(sourceCode, 0, "C1");
- compileAndRun(sourceCode, 0, "C2");
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("g(bool)", false) == encodeArgs());
+ BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true));
+ BOOST_CHECK(callContractFunction("h(bool)", false) == encodeArgs());
+ BOOST_CHECK(callContractFunction("h(bool)", true) == encodeArgs(true));
+}
+
+BOOST_AUTO_TEST_CASE(revert)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint public a = 42;
+ function f() {
+ a = 1;
+ revert();
+ }
+ function g() {
+ a = 1;
+ assembly {
+ revert(0, 0)
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(42)));
+ BOOST_CHECK(callContractFunction("g()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(negative_stack_height)
+{
+ // This code was causing negative stack height during code generation
+ // because the stack height was not adjusted at the beginning of functions.
+ char const* sourceCode = R"(
+ contract C {
+ mapping(uint => Invoice) public invoices;
+ struct Invoice {
+ uint AID;
+ bool Aboola;
+ bool Aboolc;
+ bool exists;
+ }
+ function nredit(uint startindex) public constant returns(uint[500] CIDs, uint[500] dates, uint[500] RIDs, bool[500] Cboolas, uint[500] amounts){}
+ function return500InvoicesByDates(uint begindate, uint enddate, uint startindex) public constant returns(uint[500] AIDs, bool[500] Aboolas, uint[500] dates, bytes32[3][500] Abytesas, bytes32[3][500] bytesbs, bytes32[2][500] bytescs, uint[500] amounts, bool[500] Aboolbs, bool[500] Aboolcs){}
+ function return500PaymentsByDates(uint begindate, uint enddate, uint startindex) public constant returns(uint[500] BIDs, uint[500] dates, uint[500] RIDs, bool[500] Bboolas, bytes32[3][500] bytesbs,bytes32[2][500] bytescs, uint[500] amounts, bool[500] Bboolbs){}
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+}
+
+BOOST_AUTO_TEST_CASE(literal_empty_string)
+{
+ char const* sourceCode = R"(
+ contract C {
+ bytes32 public x;
+ uint public a;
+ function f(bytes32 _x, uint _a) {
+ x = _x;
+ a = _a;
+ }
+ function g() {
+ this.f("", 2);
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("g()") == encodeArgs());
+ BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(2)));
+}
+
+BOOST_AUTO_TEST_CASE(scientific_notation)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (uint) {
+ return 2e10 wei;
+ }
+ function g() returns (uint) {
+ return 200e-2 wei;
+ }
+ function h() returns (uint) {
+ return 2.5e1;
+ }
+ function i() returns (int) {
+ return -2e10;
+ }
+ function j() returns (int) {
+ return -200e-2;
+ }
+ function k() returns (int) {
+ return -2.5e1;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(20000000000)));
+ BOOST_CHECK(callContractFunction("g()") == encodeArgs(u256(2)));
+ BOOST_CHECK(callContractFunction("h()") == encodeArgs(u256(25)));
+ BOOST_CHECK(callContractFunction("i()") == encodeArgs(u256(-20000000000)));
+ BOOST_CHECK(callContractFunction("j()") == encodeArgs(u256(-2)));
+ BOOST_CHECK(callContractFunction("k()") == encodeArgs(u256(-25)));
+}
+
+BOOST_AUTO_TEST_CASE(interface)
+{
+ char const* sourceCode = R"(
+ interface I {
+ event A();
+ function f() returns (bool);
+ function() payable;
+ }
+
+ contract A is I {
+ function f() returns (bool) {
+ return g();
+ }
+
+ function g() returns (bool) {
+ return true;
+ }
+
+ function() payable {
+ }
+ }
+
+ contract C {
+ function f(address _interfaceAddress) returns (bool) {
+ I i = I(_interfaceAddress);
+ return i.f();
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "A");
+ u160 const recipient = m_contractAddress;
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f(address)", recipient) == encodeArgs(true));
+}
+
+BOOST_AUTO_TEST_CASE(keccak256_assembly)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() returns (bytes32 ret) {
+ assembly {
+ ret := keccak256(0, 0)
+ }
+ }
+ function g() returns (bytes32 ret) {
+ assembly {
+ 0
+ 0
+ keccak256
+ =: ret
+ }
+ }
+ function h() returns (bytes32 ret) {
+ assembly {
+ ret := sha3(0, 0)
+ }
+ }
+ function i() returns (bytes32 ret) {
+ assembly {
+ 0
+ 0
+ sha3
+ =: ret
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"));
+ BOOST_CHECK(callContractFunction("g()") == fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"));
+ BOOST_CHECK(callContractFunction("h()") == fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"));
+ BOOST_CHECK(callContractFunction("i()") == fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"));
+}
+
+BOOST_AUTO_TEST_CASE(multi_modifiers)
+{
+ // This triggered a bug in some version because the variable in the modifier was not
+ // unregistered correctly.
+ char const* sourceCode = R"(
+ contract C {
+ uint public x;
+ modifier m1 {
+ address a1 = msg.sender;
+ x++;
+ _;
+ }
+ function f1() m1() {
+ x += 7;
+ }
+ function f2() m1() {
+ x += 3;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f1()") == bytes());
+ BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(8)));
+ BOOST_CHECK(callContractFunction("f2()") == bytes());
+ BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(12)));
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp
index cab9f09f..58efa0a2 100644
--- a/test/libsolidity/SolidityExpressionCompiler.cpp
+++ b/test/libsolidity/SolidityExpressionCompiler.cpp
@@ -29,6 +29,7 @@
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include "../TestHelper.h"
using namespace std;
@@ -98,7 +99,8 @@ bytes compileFirstExpression(
try
{
ErrorList errors;
- sourceUnit = Parser(errors).parse(make_shared<Scanner>(CharStream(_sourceCode)));
+ ErrorReporter errorReporter(errors);
+ sourceUnit = Parser(errorReporter).parse(make_shared<Scanner>(CharStream(_sourceCode)));
if (!sourceUnit)
return bytes();
}
@@ -114,7 +116,9 @@ bytes compileFirstExpression(
declarations.push_back(variable.get());
ErrorList errors;
- NameAndTypeResolver resolver(declarations, errors);
+ ErrorReporter errorReporter(errors);
+ map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
+ NameAndTypeResolver resolver(declarations, scopes, errorReporter);
resolver.registerDeclarations(*sourceUnit);
vector<ContractDefinition const*> inheritanceHierarchy;
@@ -127,7 +131,8 @@ bytes compileFirstExpression(
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
- TypeChecker typeChecker(errors);
+ ErrorReporter errorReporter(errors);
+ TypeChecker typeChecker(errorReporter);
BOOST_REQUIRE(typeChecker.checkTypeRequirements(*contract));
}
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
@@ -168,9 +173,11 @@ BOOST_AUTO_TEST_SUITE(SolidityExpressionCompiler)
BOOST_AUTO_TEST_CASE(literal_true)
{
- char const* sourceCode = "contract test {\n"
- " function f() { var x = true; }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { var x = true; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH1), 0x1});
@@ -179,9 +186,11 @@ BOOST_AUTO_TEST_CASE(literal_true)
BOOST_AUTO_TEST_CASE(literal_false)
{
- char const* sourceCode = "contract test {\n"
- " function f() { var x = false; }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { var x = false; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH1), 0x0});
@@ -190,9 +199,11 @@ BOOST_AUTO_TEST_CASE(literal_false)
BOOST_AUTO_TEST_CASE(int_literal)
{
- char const* sourceCode = "contract test {\n"
- " function f() { var x = 0x12345678901234567890; }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { var x = 0x12345678901234567890; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH10), 0x12, 0x34, 0x56, 0x78, 0x90,
@@ -204,11 +215,11 @@ BOOST_AUTO_TEST_CASE(int_with_wei_ether_subdenomination)
{
char const* sourceCode = R"(
contract test {
- function test ()
- {
+ function test () {
var x = 1 wei;
}
- })";
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH1), 0x1});
@@ -219,11 +230,11 @@ BOOST_AUTO_TEST_CASE(int_with_szabo_ether_subdenomination)
{
char const* sourceCode = R"(
contract test {
- function test ()
- {
+ function test () {
var x = 1 szabo;
}
- })";
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH5), 0xe8, 0xd4, 0xa5, 0x10, 0x00});
@@ -249,11 +260,11 @@ BOOST_AUTO_TEST_CASE(int_with_ether_ether_subdenomination)
{
char const* sourceCode = R"(
contract test {
- function test ()
- {
+ function test () {
var x = 1 ether;
}
- })";
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH8), 0xd, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00});
@@ -262,9 +273,11 @@ BOOST_AUTO_TEST_CASE(int_with_ether_ether_subdenomination)
BOOST_AUTO_TEST_CASE(comparison)
{
- char const* sourceCode = "contract test {\n"
- " function f() { var x = (0x10aa < 0x11aa) != true; }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { var x = (0x10aa < 0x11aa) != true; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH1), 0x1, byte(Instruction::ISZERO), byte(Instruction::ISZERO),
@@ -278,9 +291,11 @@ BOOST_AUTO_TEST_CASE(comparison)
BOOST_AUTO_TEST_CASE(short_circuiting)
{
- char const* sourceCode = "contract test {\n"
- " function f() { var x = true != (4 <= 8 + 10 || 9 != 2); }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { var x = true != (4 <= 8 + 10 || 9 != 2); }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation({byte(Instruction::PUSH1), 0x12, // 8 + 10
@@ -305,9 +320,11 @@ BOOST_AUTO_TEST_CASE(short_circuiting)
BOOST_AUTO_TEST_CASE(arithmetics)
{
- char const* sourceCode = "contract test {\n"
- " function f(uint y) { var x = ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint y) { var x = ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}});
bytes expectation({byte(Instruction::PUSH1), 0x1,
byte(Instruction::PUSH1), 0x2,
@@ -325,13 +342,19 @@ BOOST_AUTO_TEST_CASE(arithmetics)
byte(Instruction::ADD),
byte(Instruction::DUP2),
byte(Instruction::ISZERO),
- byte(Instruction::PUSH1), 0x0,
+ byte(Instruction::ISZERO),
+ byte(Instruction::PUSH1), 0x1d,
byte(Instruction::JUMPI),
+ byte(Instruction::INVALID),
+ byte(Instruction::JUMPDEST),
byte(Instruction::MOD),
byte(Instruction::DUP2),
byte(Instruction::ISZERO),
- byte(Instruction::PUSH1), 0x0,
+ byte(Instruction::ISZERO),
+ byte(Instruction::PUSH1), 0x26,
byte(Instruction::JUMPI),
+ byte(Instruction::INVALID),
+ byte(Instruction::JUMPDEST),
byte(Instruction::DIV),
byte(Instruction::MUL)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
@@ -339,9 +362,11 @@ BOOST_AUTO_TEST_CASE(arithmetics)
BOOST_AUTO_TEST_CASE(unary_operators)
{
- char const* sourceCode = "contract test {\n"
- " function f(int y) { var x = !(~+- y == 2); }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(int y) { var x = !(~+- y == 2); }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}});
bytes expectation({byte(Instruction::PUSH1), 0x2,
@@ -356,9 +381,11 @@ BOOST_AUTO_TEST_CASE(unary_operators)
BOOST_AUTO_TEST_CASE(unary_inc_dec)
{
- char const* sourceCode = "contract test {\n"
- " function f(uint a) { var x = --a ^ (a-- ^ (++a ^ a++)); }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint a) { var x = --a ^ (a-- ^ (++a ^ a++)); }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}});
// Stack: a, x
@@ -406,9 +433,11 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec)
BOOST_AUTO_TEST_CASE(assignment)
{
- char const* sourceCode = "contract test {\n"
- " function f(uint a, uint b) { (a += b) * 2; }"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f(uint a, uint b) { (a += b) * 2; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}});
// Stack: a, b
@@ -427,9 +456,11 @@ BOOST_AUTO_TEST_CASE(assignment)
BOOST_AUTO_TEST_CASE(negative_literals_8bits)
{
- char const* sourceCode = "contract test {\n"
- " function f() { int8 x = -0x80; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { int8 x = -0x80; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation(bytes({byte(Instruction::PUSH32)}) + bytes(31, 0xff) + bytes(1, 0x80));
@@ -438,9 +469,11 @@ BOOST_AUTO_TEST_CASE(negative_literals_8bits)
BOOST_AUTO_TEST_CASE(negative_literals_16bits)
{
- char const* sourceCode = "contract test {\n"
- " function f() { int64 x = ~0xabc; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { int64 x = ~0xabc; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation(bytes({byte(Instruction::PUSH32)}) + bytes(30, 0xff) + bytes{0xf5, 0x43});
@@ -451,9 +484,11 @@ BOOST_AUTO_TEST_CASE(intermediately_overflowing_literals)
{
// first literal itself is too large for 256 bits but it fits after all constant operations
// have been applied
- char const* sourceCode = "contract test {\n"
- " function f() { var x = (0xffffffffffffffffffffffffffffffffffffffff * 0xffffffffffffffffffffffffff01) & 0xbf; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() { var x = (0xffffffffffffffffffffffffffffffffffffffff * 0xffffffffffffffffffffffffff01) & 0xbf; }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode);
bytes expectation(bytes({byte(Instruction::PUSH1), 0xbf}));
@@ -462,11 +497,13 @@ BOOST_AUTO_TEST_CASE(intermediately_overflowing_literals)
BOOST_AUTO_TEST_CASE(blockhash)
{
- char const* sourceCode = "contract test {\n"
- " function f() {\n"
- " block.blockhash(3);\n"
- " }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function f() {
+ block.blockhash(3);
+ }
+ }
+ )";
bytes code = compileFirstExpression(sourceCode, {}, {},
{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block))});
diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp
index 627aaa2f..637ff5cc 100644
--- a/test/libsolidity/SolidityNameAndTypeResolution.cpp
+++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp
@@ -20,18 +20,23 @@
* Unit tests for the name and type resolution of the solidity parser.
*/
-#include <string>
+#include <test/libsolidity/ErrorCheck.h>
+
+#include <test/TestHelper.h>
-#include <libdevcore/SHA3.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/parsing/Parser.h>
#include <libsolidity/analysis/NameAndTypeResolver.h>
+#include <libsolidity/analysis/StaticAnalyzer.h>
+#include <libsolidity/analysis/PostTypeChecker.h>
#include <libsolidity/analysis/SyntaxChecker.h>
-#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/analysis/GlobalContext.h>
#include <libsolidity/analysis/TypeChecker.h>
-#include "../TestHelper.h"
-#include "ErrorCheck.h"
+
+#include <libdevcore/SHA3.h>
+
+#include <string>
using namespace std;
@@ -46,12 +51,13 @@ namespace
{
pair<ASTPointer<SourceUnit>, std::shared_ptr<Error const>>
-parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true)
+parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true, bool _allowMultipleErrors = false)
{
// Silence compiler version warning
string source = _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source;
ErrorList errors;
- Parser parser(errors);
+ ErrorReporter errorReporter(errors);
+ Parser parser(errorReporter);
ASTPointer<SourceUnit> sourceUnit;
// catch exceptions for a transition period
try
@@ -60,13 +66,14 @@ parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false,
if(!sourceUnit)
BOOST_FAIL("Parsing failed in type checker test.");
- SyntaxChecker syntaxChecker(errors);
+ SyntaxChecker syntaxChecker(errorReporter);
if (!syntaxChecker.checkSyntax(*sourceUnit))
- return make_pair(sourceUnit, errors.at(0));
+ return make_pair(sourceUnit, errorReporter.errors().at(0));
std::shared_ptr<GlobalContext> globalContext = make_shared<GlobalContext>();
- NameAndTypeResolver resolver(globalContext->declarations(), errors);
- solAssert(Error::containsOnlyWarnings(errors), "");
+ map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
+ NameAndTypeResolver resolver(globalContext->declarations(), scopes, errorReporter);
+ solAssert(Error::containsOnlyWarnings(errorReporter.errors()), "");
resolver.registerDeclarations(*sourceUnit);
bool success = true;
@@ -86,19 +93,32 @@ parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false,
globalContext->setCurrentContract(*contract);
resolver.updateDeclaration(*globalContext->currentThis());
- TypeChecker typeChecker(errors);
+ TypeChecker typeChecker(errorReporter);
bool success = typeChecker.checkTypeRequirements(*contract);
- BOOST_CHECK(success || !errors.empty());
-
+ BOOST_CHECK(success || !errorReporter.errors().empty());
}
- for (auto const& currentError: errors)
+ if (success)
+ if (!PostTypeChecker(errorReporter).check(*sourceUnit))
+ success = false;
+ if (success)
+ if (!StaticAnalyzer(errorReporter).analyze(*sourceUnit))
+ success = false;
+ std::shared_ptr<Error const> error;
+ for (auto const& currentError: errorReporter.errors())
{
if (
(_reportWarnings && currentError->type() == Error::Type::Warning) ||
(!_reportWarnings && currentError->type() != Error::Type::Warning)
)
- return make_pair(sourceUnit, currentError);
+ {
+ if (error && !_allowMultipleErrors)
+ BOOST_FAIL("Multiple errors found");
+ if (!error)
+ error = currentError;
+ }
}
+ if (error)
+ return make_pair(sourceUnit, error);
}
catch (InternalCompilerError const& _e)
{
@@ -131,9 +151,9 @@ bool success(string const& _source)
return !parseAnalyseAndReturnError(_source).second;
}
-Error expectError(std::string const& _source, bool _warning = false)
+Error expectError(std::string const& _source, bool _warning = false, bool _allowMultiple = false)
{
- auto sourceAndError = parseAnalyseAndReturnError(_source, _warning);
+ auto sourceAndError = parseAnalyseAndReturnError(_source, _warning, true, _allowMultiple);
BOOST_REQUIRE(!!sourceAndError.second);
BOOST_REQUIRE(!!sourceAndError.first);
return *sourceAndError.second;
@@ -161,124 +181,167 @@ static FunctionTypePointer retrieveFunctionBySignature(
}
-#define CHECK_ERROR_OR_WARNING(text, typ, substring, warning) \
+#define CHECK_ERROR_OR_WARNING(text, typ, substring, warning, allowMulti) \
do \
{ \
- Error err = expectError((text), (warning)); \
+ Error err = expectError((text), (warning), (allowMulti)); \
BOOST_CHECK(err.type() == (Error::Type::typ)); \
- BOOST_CHECK(searchErrorMessage(err, substring)); \
+ BOOST_CHECK(searchErrorMessage(err, (substring))); \
} while(0)
// [checkError(text, type, substring)] asserts that the compilation down to typechecking
// emits an error of type [type] and with a message containing [substring].
#define CHECK_ERROR(text, type, substring) \
-CHECK_ERROR_OR_WARNING(text, type, substring, false)
+CHECK_ERROR_OR_WARNING(text, type, substring, false, false)
+
+// [checkError(text, type, substring)] asserts that the compilation down to typechecking
+// emits an error of type [type] and with a message containing [substring].
+#define CHECK_ERROR_ALLOW_MULTI(text, type, substring) \
+CHECK_ERROR_OR_WARNING(text, type, substring, false, true)
-// [checkWarning(text, type, substring)] asserts that the compilation down to typechecking
-// emits a warning of type [type] and with a message containing [substring].
+// [checkWarning(text, substring)] asserts that the compilation down to typechecking
+// emits a warning and with a message containing [substring].
#define CHECK_WARNING(text, substring) \
-CHECK_ERROR_OR_WARNING(text, Warning, substring, true)
+CHECK_ERROR_OR_WARNING(text, Warning, substring, true, false)
+
+// [checkWarningAllowMulti(text, substring)] aserts that the compilation down to typechecking
+// emits a warning and with a message containing [substring].
+#define CHECK_WARNING_ALLOW_MULTI(text, substring) \
+CHECK_ERROR_OR_WARNING(text, Warning, substring, true, true)
// [checkSuccess(text)] asserts that the compilation down to typechecking succeeds.
#define CHECK_SUCCESS(text) do { BOOST_CHECK(success((text))); } while(0)
+#define CHECK_SUCCESS_NO_WARNINGS(text) \
+do \
+{ \
+ auto sourceAndError = parseAnalyseAndReturnError((text), true); \
+ BOOST_CHECK(sourceAndError.second == nullptr); \
+} \
+while(0)
+
BOOST_AUTO_TEST_SUITE(SolidityNameAndTypeResolution)
BOOST_AUTO_TEST_CASE(smoke_test)
{
- char const* text = "contract test {\n"
- " uint256 stateVariable1;\n"
- " function fun(uint256 arg1) { uint256 y; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ uint256 stateVariable1;
+ function fun(uint256 arg1) { uint256 y; y = arg1; }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(double_stateVariable_declaration)
{
- char const* text = "contract test {\n"
- " uint256 variable;\n"
- " uint128 variable;\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ uint256 variable;
+ uint128 variable;
+ }
+ )";
CHECK_ERROR(text, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(double_function_declaration)
{
- char const* text = "contract test {\n"
- " function fun() { uint x; }\n"
- " function fun() { uint x; }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function fun() { }
+ function fun() { }
+ }
+ )";
CHECK_ERROR(text, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(double_variable_declaration)
{
- char const* text = "contract test {\n"
- " function f() { uint256 x; if (true) { uint256 x; } }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() {
+ uint256 x;
+ if (true) { uint256 x; }
+ }
+ }
+ )";
CHECK_ERROR(text, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(name_shadowing)
{
- char const* text = "contract test {\n"
- " uint256 variable;\n"
- " function f() { uint32 variable ; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ uint256 variable;
+ function f() { uint32 variable; variable = 2; }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(name_references)
{
- char const* text = "contract test {\n"
- " uint256 variable;\n"
- " function f(uint256 arg) returns (uint out) { f(variable); test; out; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ uint256 variable;
+ function f(uint256) returns (uint out) { f(variable); test; out; }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(undeclared_name)
{
- char const* text = "contract test {\n"
- " uint256 variable;\n"
- " function f(uint256 arg) { f(notfound); }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ uint256 variable;
+ function f(uint256 arg) {
+ f(notfound);
+ }
+ }
+ )";
CHECK_ERROR(text, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(reference_to_later_declaration)
{
- char const* text = "contract test {\n"
- " function g() { f(); }"
- " function f() { }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function g() { f(); }
+ function f() {}
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(struct_definition_directly_recursive)
{
- char const* text = "contract test {\n"
- " struct MyStructName {\n"
- " address addr;\n"
- " MyStructName x;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ struct MyStructName {
+ address addr;
+ MyStructName x;
+ }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(struct_definition_indirectly_recursive)
{
- char const* text = "contract test {\n"
- " struct MyStructName1 {\n"
- " address addr;\n"
- " uint256 count;\n"
- " MyStructName2 x;\n"
- " }\n"
- " struct MyStructName2 {\n"
- " MyStructName1 x;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ struct MyStructName1 {
+ address addr;
+ uint256 count;
+ MyStructName2 x;
+ }
+ struct MyStructName2 {
+ MyStructName1 x;
+ }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -295,179 +358,261 @@ BOOST_AUTO_TEST_CASE(struct_definition_not_really_recursive)
BOOST_AUTO_TEST_CASE(struct_definition_recursion_via_mapping)
{
- char const* text = "contract test {\n"
- " struct MyStructName1 {\n"
- " address addr;\n"
- " uint256 count;\n"
- " mapping(uint => MyStructName1) x;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ struct MyStructName1 {
+ address addr;
+ uint256 count;
+ mapping(uint => MyStructName1) x;
+ }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(type_inference_smoke_test)
{
- char const* text = "contract test {\n"
- " function f(uint256 arg1, uint32 arg2) returns (bool ret) { var x = arg1 + arg2 == 8; ret = x; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f(uint256 arg1, uint32 arg2) returns (bool ret) {
+ var x = arg1 + arg2 == 8; ret = x;
+ }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(type_checking_return)
{
- char const* text = "contract test {\n"
- " function f() returns (bool r) { return 1 >= 2; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() returns (bool r) { return 1 >= 2; }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(type_checking_return_wrong_number)
{
- char const* text = "contract test {\n"
- " function f() returns (bool r1, bool r2) { return 1 >= 2; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() returns (bool r1, bool r2) { return 1 >= 2; }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(type_checking_return_wrong_type)
{
- char const* text = "contract test {\n"
- " function f() returns (uint256 r) { return 1 >= 2; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() returns (uint256 r) { return 1 >= 2; }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(type_checking_function_call)
{
- char const* text = "contract test {\n"
- " function f() returns (bool r) { return g(12, true) == 3; }\n"
- " function g(uint256 a, bool b) returns (uint256 r) { }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() returns (bool) { return g(12, true) == 3; }
+ function g(uint256, bool) returns (uint256) { }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(type_conversion_for_comparison)
{
- char const* text = "contract test {\n"
- " function f() { uint32(2) == int64(2); }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() { uint32(2) == int64(2); }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(type_conversion_for_comparison_invalid)
{
- char const* text = "contract test {\n"
- " function f() { int32(2) == uint64(2); }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() { int32(2) == uint64(2); }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(type_inference_explicit_conversion)
{
- char const* text = "contract test {\n"
- " function f() returns (int256 r) { var x = int256(uint32(2)); return x; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() returns (int256 r) { var x = int256(uint32(2)); return x; }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(large_string_literal)
{
- char const* text = "contract test {\n"
- " function f() { var x = \"123456789012345678901234567890123\"; }"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function f() { var x = "123456789012345678901234567890123"; }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(balance)
{
- char const* text = "contract test {\n"
- " function fun() {\n"
- " uint256 x = address(0).balance;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function fun() {
+ uint256 x = address(0).balance;
+ }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(balance_invalid)
{
- char const* text = "contract test {\n"
- " function fun() {\n"
- " address(0).balance = 7;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function fun() {
+ address(0).balance = 7;
+ }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(assignment_to_mapping)
{
- char const* text = "contract test {\n"
- " struct str {\n"
- " mapping(uint=>uint) map;\n"
- " }\n"
- " str data;"
- " function fun() {\n"
- " var a = data.map;\n"
- " data.map = a;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ struct str {
+ mapping(uint=>uint) map;
+ }
+ str data;
+ function fun() {
+ var a = data.map;
+ data.map = a;
+ }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(assignment_to_struct)
{
- char const* text = "contract test {\n"
- " struct str {\n"
- " mapping(uint=>uint) map;\n"
- " }\n"
- " str data;"
- " function fun() {\n"
- " var a = data;\n"
- " data = a;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ struct str {
+ mapping(uint=>uint) map;
+ }
+ str data;
+ function fun() {
+ var a = data;
+ data = a;
+ }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(returns_in_constructor)
{
- char const* text = "contract test {\n"
- " function test() returns (uint a) {\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function test() returns (uint a) { }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(forward_function_reference)
{
- char const* text = "contract First {\n"
- " function fun() returns (bool ret) {\n"
- " return Second(1).fun(1, true, 3) > 0;\n"
- " }\n"
- "}\n"
- "contract Second {\n"
- " function fun(uint a, bool b, uint c) returns (uint ret) {\n"
- " if (First(2).fun() == true) return 1;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract First {
+ function fun() returns (bool) {
+ return Second(1).fun(1, true, 3) > 0;
+ }
+ }
+ contract Second {
+ function fun(uint, bool, uint) returns (uint) {
+ if (First(2).fun() == true) return 1;
+ }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(comparison_bitop_precedence)
{
- char const* text = "contract First {\n"
- " function fun() returns (bool ret) {\n"
- " return 1 & 2 == 8 & 9 && 1 ^ 2 < 4 | 6;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract First {
+ function fun() returns (bool ret) {
+ return 1 & 2 == 8 & 9 && 1 ^ 2 < 4 | 6;
+ }
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(comparison_of_function_types)
+{
+ char const* text = R"(
+ contract C {
+ function f() returns (bool ret) {
+ return this.f < this.f;
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Operator < not compatible");
+ text = R"(
+ contract C {
+ function f() returns (bool ret) {
+ return f < f;
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Operator < not compatible");
+ text = R"(
+ contract C {
+ function f() returns (bool ret) {
+ return f == f;
+ }
+ function g() returns (bool ret) {
+ return f != f;
+ }
+ }
+ )";
CHECK_SUCCESS(text);
}
+BOOST_AUTO_TEST_CASE(comparison_of_mapping_types)
+{
+ char const* text = R"(
+ contract C {
+ mapping(uint => uint) x;
+ function f() returns (bool ret) {
+ var y = x;
+ return x == y;
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Operator == not compatible");
+}
+
BOOST_AUTO_TEST_CASE(function_no_implementation)
{
ASTPointer<SourceUnit> sourceUnit;
- char const* text = "contract test {\n"
- " function functionName(bytes32 input) returns (bytes32 out);\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function functionName(bytes32 input) returns (bytes32 out);
+ }
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed");
std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes();
ContractDefinition* contract = dynamic_cast<ContractDefinition*>(nodes[1].get());
@@ -482,7 +627,7 @@ BOOST_AUTO_TEST_CASE(abstract_contract)
char const* text = R"(
contract base { function foo(); }
contract derived is base { function foo() {} }
- )";
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed");
std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes();
ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get());
@@ -501,7 +646,7 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload)
char const* text = R"(
contract base { function foo(bool); }
contract derived is base { function foo(uint) {} }
- )";
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed");
std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes();
ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get());
@@ -519,9 +664,9 @@ BOOST_AUTO_TEST_CASE(create_abstract_contract)
contract base { function foo(); }
contract derived {
base b;
- function foo() { b = new base();}
- }
- )";
+ function foo() { b = new base(); }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -535,21 +680,26 @@ BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_optional)
function derived(uint i) BaseBase(i){}
function foo() {}
}
- )";
- ETH_TEST_REQUIRE_NO_THROW(parseAndAnalyse(text), "Parsing and name resolving failed");
+ )";
+ ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed");
+ std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes();
+ BOOST_CHECK_EQUAL(nodes.size(), 4);
+ ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[3].get());
+ BOOST_REQUIRE(derived);
+ BOOST_CHECK(!derived->annotation().isFullyImplemented);
}
BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_not_provided)
{
ASTPointer<SourceUnit> sourceUnit;
char const* text = R"(
- contract BaseBase { function BaseBase(uint j); }
+ contract BaseBase { function BaseBase(uint); }
contract base is BaseBase { function foo(); }
contract derived is base {
- function derived(uint i) {}
+ function derived(uint) {}
function foo() {}
}
- )";
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed");
std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes();
BOOST_CHECK_EQUAL(nodes.size(), 4);
@@ -565,7 +715,7 @@ BOOST_AUTO_TEST_CASE(redeclare_implemented_abstract_function_as_abstract)
contract base { function foo(); }
contract derived is base { function foo() {} }
contract wrong is derived { function foo(); }
- )";
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -587,11 +737,13 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor)
BOOST_AUTO_TEST_CASE(function_canonical_signature)
{
ASTPointer<SourceUnit> sourceUnit;
- char const* text = "contract Test {\n"
- " function foo(uint256 arg1, uint64 arg2, bool arg3) returns (uint256 ret) {\n"
- " ret = arg1 + arg2;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract Test {
+ function foo(uint256 arg1, uint64 arg2, bool arg3) returns (uint256 ret) {
+ ret = arg1 + arg2;
+ }
+ }
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed");
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
@@ -604,11 +756,13 @@ BOOST_AUTO_TEST_CASE(function_canonical_signature)
BOOST_AUTO_TEST_CASE(function_canonical_signature_type_aliases)
{
ASTPointer<SourceUnit> sourceUnit;
- char const* text = "contract Test {\n"
- " function boo(uint arg1, bytes32 arg2, address arg3) returns (uint ret) {\n"
- " ret = 5;\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract Test {
+ function boo(uint, bytes32, address) returns (uint ret) {
+ ret = 5;
+ }
+ }
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed");
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
@@ -628,10 +782,11 @@ BOOST_AUTO_TEST_CASE(function_external_types)
uint a;
}
contract Test {
- function boo(uint arg2, bool arg3, bytes8 arg4, bool[2] pairs, uint[] dynamic, C carg, address[] addresses) external returns (uint ret) {
- ret = 5;
+ function boo(uint, bool, bytes8, bool[2], uint[], C, address[]) external returns (uint ret) {
+ ret = 5;
}
- })";
+ }
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed");
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
@@ -653,7 +808,8 @@ BOOST_AUTO_TEST_CASE(enum_external_type)
function boo(ActionChoices enumArg) external returns (uint ret) {
ret = 5;
}
- })";
+ }
+ )";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed");
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
@@ -670,12 +826,13 @@ BOOST_AUTO_TEST_CASE(function_external_call_allowed_conversion)
char const* text = R"(
contract C {}
contract Test {
- function externalCall() {
+ function externalCall() {
C arg;
this.g(arg);
}
function g (C c) external {}
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -684,12 +841,13 @@ BOOST_AUTO_TEST_CASE(function_external_call_not_allowed_conversion)
char const* text = R"(
contract C {}
contract Test {
- function externalCall() {
+ function externalCall() {
address arg;
this.g(arg);
}
function g (C c) external {}
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -705,7 +863,8 @@ BOOST_AUTO_TEST_CASE(function_internal_allowed_conversion)
function internalCall() {
g(a);
}
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -721,18 +880,19 @@ BOOST_AUTO_TEST_CASE(function_internal_not_allowed_conversion)
function internalCall() {
g(a);
}
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(hash_collision_in_interface)
{
- char const* text = "contract test {\n"
- " function gsf() {\n"
- " }\n"
- " function tgeo() {\n"
- " }\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function gsf() { }
+ function tgeo() { }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -767,7 +927,7 @@ BOOST_AUTO_TEST_CASE(cyclic_inheritance)
contract A is B { }
contract B is A { }
)";
- CHECK_ERROR(text, TypeError, "");
+ CHECK_ERROR_ALLOW_MULTI(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(legal_override_direct)
@@ -811,7 +971,7 @@ BOOST_AUTO_TEST_CASE(complex_inheritance)
{
char const* text = R"(
contract A { function f() { uint8 x = C(0).g(); } }
- contract B { function f() {} function g() returns (uint8 r) {} }
+ contract B { function f() {} function g() returns (uint8) {} }
contract C is A, B { }
)";
CHECK_SUCCESS(text);
@@ -940,6 +1100,28 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables)
CHECK_SUCCESS(text);
}
+BOOST_AUTO_TEST_CASE(function_modifier_double_invocation)
+{
+ char const* text = R"(
+ contract B {
+ function f(uint x) mod(x) mod(2) { }
+ modifier mod(uint a) { if (a > 0) _; }
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Modifier already used for this function");
+}
+
+BOOST_AUTO_TEST_CASE(base_constructor_double_invocation)
+{
+ char const* text = R"(
+ contract C { function C(uint a) {} }
+ contract B is C {
+ function B() C(2) C(2) {}
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Base constructor already provided");
+}
+
BOOST_AUTO_TEST_CASE(legal_modifier_override)
{
char const* text = R"(
@@ -964,7 +1146,9 @@ BOOST_AUTO_TEST_CASE(modifier_overrides_function)
contract A { modifier mod(uint a) { _; } }
contract B is A { function mod(uint a) { } }
)";
- CHECK_ERROR(text, TypeError, "");
+ // Error: Identifier already declared.
+ // Error: Override changes modifier to function.
+ CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, "Identifier already declared");
}
BOOST_AUTO_TEST_CASE(function_overrides_modifier)
@@ -973,7 +1157,9 @@ BOOST_AUTO_TEST_CASE(function_overrides_modifier)
contract A { function mod(uint a) { } }
contract B is A { modifier mod(uint a) { _; } }
)";
- CHECK_ERROR(text, TypeError, "");
+ // Error: Identifier already declared.
+ // Error: Override changes function to modifier.
+ CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(modifier_returns_value)
@@ -984,19 +1170,21 @@ BOOST_AUTO_TEST_CASE(modifier_returns_value)
modifier mod(uint a) { _; return 7; }
}
)";
- CHECK_ERROR(text, TypeError, "");
+ CHECK_ERROR(text, TypeError, "Return arguments not allowed.");
}
BOOST_AUTO_TEST_CASE(state_variable_accessors)
{
- char const* text = "contract test {\n"
- " function fun() {\n"
- " uint64(2);\n"
- " }\n"
- "uint256 public foo;\n"
- "mapping(uint=>bytes4) public map;\n"
- "mapping(uint=>mapping(uint=>bytes4)) public multiple_map;\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function fun() {
+ uint64(2);
+ }
+ uint256 public foo;
+ mapping(uint=>bytes4) public map;
+ mapping(uint=>mapping(uint=>bytes4)) public multiple_map;
+ }
+ )";
ASTPointer<SourceUnit> source;
ContractDefinition const* contract;
@@ -1004,49 +1192,53 @@ BOOST_AUTO_TEST_CASE(state_variable_accessors)
BOOST_REQUIRE((contract = retrieveContract(source, 0)) != nullptr);
FunctionTypePointer function = retrieveFunctionBySignature(*contract, "foo()");
BOOST_REQUIRE(function && function->hasDeclaration());
- auto returnParams = function->returnParameterTypeNames(false);
- BOOST_CHECK_EQUAL(returnParams.at(0), "uint256");
+ auto returnParams = function->returnParameterTypes();
+ BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "uint256");
BOOST_CHECK(function->isConstant());
function = retrieveFunctionBySignature(*contract, "map(uint256)");
BOOST_REQUIRE(function && function->hasDeclaration());
- auto params = function->parameterTypeNames(false);
- BOOST_CHECK_EQUAL(params.at(0), "uint256");
- returnParams = function->returnParameterTypeNames(false);
- BOOST_CHECK_EQUAL(returnParams.at(0), "bytes4");
+ auto params = function->parameterTypes();
+ BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256");
+ returnParams = function->returnParameterTypes();
+ BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4");
BOOST_CHECK(function->isConstant());
function = retrieveFunctionBySignature(*contract, "multiple_map(uint256,uint256)");
BOOST_REQUIRE(function && function->hasDeclaration());
- params = function->parameterTypeNames(false);
- BOOST_CHECK_EQUAL(params.at(0), "uint256");
- BOOST_CHECK_EQUAL(params.at(1), "uint256");
- returnParams = function->returnParameterTypeNames(false);
- BOOST_CHECK_EQUAL(returnParams.at(0), "bytes4");
+ params = function->parameterTypes();
+ BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256");
+ BOOST_CHECK_EQUAL(params.at(1)->canonicalName(false), "uint256");
+ returnParams = function->returnParameterTypes();
+ BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4");
BOOST_CHECK(function->isConstant());
}
BOOST_AUTO_TEST_CASE(function_clash_with_state_variable_accessor)
{
- char const* text = "contract test {\n"
- " function fun() {\n"
- " uint64(2);\n"
- " }\n"
- "uint256 foo;\n"
- " function foo() {}\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function fun() {
+ uint64(2);
+ }
+ uint256 foo;
+ function foo() {}
+ }
+ )";
CHECK_ERROR(text, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(private_state_variable)
{
- char const* text = "contract test {\n"
- " function fun() {\n"
- " uint64(2);\n"
- " }\n"
- "uint256 private foo;\n"
- "uint256 internal bar;\n"
- "}\n";
+ char const* text = R"(
+ contract test {
+ function fun() {
+ uint64(2);
+ }
+ uint256 private foo;
+ uint256 internal bar;
+ }
+ )";
ASTPointer<SourceUnit> source;
ContractDefinition const* contract;
@@ -1075,12 +1267,14 @@ BOOST_AUTO_TEST_CASE(missing_state_variable)
BOOST_AUTO_TEST_CASE(base_class_state_variable_accessor)
{
// test for issue #1126 https://github.com/ethereum/cpp-ethereum/issues/1126
- char const* text = "contract Parent {\n"
- " uint256 public m_aMember;\n"
- "}\n"
- "contract Child is Parent{\n"
- " function foo() returns (uint256) { return Parent.m_aMember; }\n"
- "}\n";
+ char const* text = R"(
+ contract Parent {
+ uint256 public m_aMember;
+ }
+ contract Child is Parent {
+ function foo() returns (uint256) { return Parent.m_aMember; }
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1088,7 +1282,7 @@ BOOST_AUTO_TEST_CASE(struct_accessor_one_array_only)
{
char const* sourceCode = R"(
contract test {
- struct Data { uint[15] m_array; }
+ struct Data { uint[15] m_array; }
Data public data;
}
)";
@@ -1097,41 +1291,47 @@ BOOST_AUTO_TEST_CASE(struct_accessor_one_array_only)
BOOST_AUTO_TEST_CASE(base_class_state_variable_internal_member)
{
- char const* text = "contract Parent {\n"
- " uint256 internal m_aMember;\n"
- "}\n"
- "contract Child is Parent{\n"
- " function foo() returns (uint256) { return Parent.m_aMember; }\n"
- "}\n";
+ char const* text = R"(
+ contract Parent {
+ uint256 internal m_aMember;
+ }
+ contract Child is Parent{
+ function foo() returns (uint256) { return Parent.m_aMember; }
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(state_variable_member_of_wrong_class1)
{
- char const* text = "contract Parent1 {\n"
- " uint256 internal m_aMember1;\n"
- "}\n"
- "contract Parent2 is Parent1{\n"
- " uint256 internal m_aMember2;\n"
- "}\n"
- "contract Child is Parent2{\n"
- " function foo() returns (uint256) { return Parent2.m_aMember1; }\n"
- "}\n";
+ char const* text = R"(
+ contract Parent1 {
+ uint256 internal m_aMember1;
+ }
+ contract Parent2 is Parent1{
+ uint256 internal m_aMember2;
+ }
+ contract Child is Parent2{
+ function foo() returns (uint256) { return Parent2.m_aMember1; }
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(state_variable_member_of_wrong_class2)
{
- char const* text = "contract Parent1 {\n"
- " uint256 internal m_aMember1;\n"
- "}\n"
- "contract Parent2 is Parent1{\n"
- " uint256 internal m_aMember2;\n"
- "}\n"
- "contract Child is Parent2{\n"
- " function foo() returns (uint256) { return Child.m_aMember2; }\n"
- " uint256 public m_aMember3;\n"
- "}\n";
+ char const* text = R"(
+ contract Parent1 {
+ uint256 internal m_aMember1;
+ }
+ contract Parent2 is Parent1 {
+ uint256 internal m_aMember2;
+ }
+ contract Child is Parent2 {
+ function foo() returns (uint256) { return Child.m_aMember2; }
+ uint256 public m_aMember3;
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1197,7 +1397,7 @@ BOOST_AUTO_TEST_CASE(fallback_function_twice)
function() { x = 3; }
}
)";
- CHECK_ERROR(text, DeclarationError, "");
+ CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(fallback_function_inheritance)
@@ -1220,7 +1420,8 @@ BOOST_AUTO_TEST_CASE(event)
contract c {
event e(uint indexed a, bytes3 indexed s, bool indexed b);
function f() { e(2, "abc", true); }
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1229,7 +1430,8 @@ BOOST_AUTO_TEST_CASE(event_too_many_indexed)
char const* text = R"(
contract c {
event e(uint indexed a, bytes3 indexed b, bool indexed c, uint indexed d);
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1238,7 +1440,8 @@ BOOST_AUTO_TEST_CASE(anonymous_event_four_indexed)
char const* text = R"(
contract c {
event e(uint indexed a, bytes3 indexed b, bool indexed c, uint indexed d) anonymous;
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1247,20 +1450,80 @@ BOOST_AUTO_TEST_CASE(anonymous_event_too_many_indexed)
char const* text = R"(
contract c {
event e(uint indexed a, bytes3 indexed b, bool indexed c, uint indexed d, uint indexed e) anonymous;
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(events_with_same_name)
+{
+ char const* text = R"(
+ contract TestIt {
+ event A();
+ event A(uint i);
+ }
+ )";
+ BOOST_CHECK(success(text));
+}
+
BOOST_AUTO_TEST_CASE(event_call)
{
char const* text = R"(
contract c {
event e(uint a, bytes3 indexed s, bool indexed b);
function f() { e(2, "abc", true); }
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
+BOOST_AUTO_TEST_CASE(event_function_inheritance_clash)
+{
+ char const* text = R"(
+ contract A {
+ function dup() returns (uint) {
+ return 1;
+ }
+ }
+ contract B {
+ event dup();
+ }
+ contract C is A, B {
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Identifier already declared.");
+}
+
+BOOST_AUTO_TEST_CASE(function_event_inheritance_clash)
+{
+ char const* text = R"(
+ contract B {
+ event dup();
+ }
+ contract A {
+ function dup() returns (uint) {
+ return 1;
+ }
+ }
+ contract C is B, A {
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Identifier already declared.");
+}
+
+BOOST_AUTO_TEST_CASE(function_event_in_contract_clash)
+{
+ char const* text = R"(
+ contract A {
+ event dup();
+ function dup() returns (uint) {
+ return 1;
+ }
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Identifier already declared.");
+}
+
BOOST_AUTO_TEST_CASE(event_inheritance)
{
char const* text = R"(
@@ -1269,7 +1532,8 @@ BOOST_AUTO_TEST_CASE(event_inheritance)
}
contract c is base {
function f() { e(2, "abc", true); }
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1279,7 +1543,8 @@ BOOST_AUTO_TEST_CASE(multiple_events_argument_clash)
contract c {
event e1(uint a, uint e1, uint e2);
event e2(uint a, uint e1, uint e2);
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1291,7 +1556,8 @@ BOOST_AUTO_TEST_CASE(access_to_default_function_visibility)
}
contract d {
function g() { c(0).f(); }
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1303,7 +1569,8 @@ BOOST_AUTO_TEST_CASE(access_to_internal_function)
}
contract d {
function g() { c(0).f(); }
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1315,7 +1582,8 @@ BOOST_AUTO_TEST_CASE(access_to_default_state_variable_visibility)
}
contract d {
function g() { c(0).a(); }
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1327,43 +1595,68 @@ BOOST_AUTO_TEST_CASE(access_to_internal_state_variable)
}
contract d {
function g() { c(0).a(); }
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(error_count_in_named_args)
{
- char const* sourceCode = "contract test {\n"
- " function a(uint a, uint b) returns (uint r) { r = a + b; }\n"
- " function b() returns (uint r) { r = a({a: 1}); }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function a(uint a, uint b) returns (uint r) {
+ r = a + b;
+ }
+ function b() returns (uint r) {
+ r = a({a: 1});
+ }
+ }
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
BOOST_AUTO_TEST_CASE(empty_in_named_args)
{
- char const* sourceCode = "contract test {\n"
- " function a(uint a, uint b) returns (uint r) { r = a + b; }\n"
- " function b() returns (uint r) { r = a({}); }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function a(uint a, uint b) returns (uint r) {
+ r = a + b;
+ }
+ function b() returns (uint r) {
+ r = a({});
+ }
+ }
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
BOOST_AUTO_TEST_CASE(duplicate_parameter_names_in_named_args)
{
- char const* sourceCode = "contract test {\n"
- " function a(uint a, uint b) returns (uint r) { r = a + b; }\n"
- " function b() returns (uint r) { r = a({a: 1, a: 2}); }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function a(uint a, uint b) returns (uint r) {
+ r = a + b;
+ }
+ function b() returns (uint r) {
+ r = a({a: 1, a: 2});
+ }
+ }
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
BOOST_AUTO_TEST_CASE(invalid_parameter_names_in_named_args)
{
- char const* sourceCode = "contract test {\n"
- " function a(uint a, uint b) returns (uint r) { r = a + b; }\n"
- " function b() returns (uint r) { r = a({a: 1, c: 2}); }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function a(uint a, uint b) returns (uint r) {
+ r = a + b;
+ }
+ function b() returns (uint r) {
+ r = a({a: 1, c: 2});
+ }
+ }
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1371,19 +1664,29 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter)
{
char const* text = R"(
contract test {
- function f(uint){
+ function f(uint) { }
}
- })";
+ )";
CHECK_SUCCESS(text);
}
+BOOST_AUTO_TEST_CASE(constant_input_parameter)
+{
+ char const* text = R"(
+ contract test {
+ function f(uint[] constant a) { }
+ }
+ )";
+ CHECK_ERROR_ALLOW_MULTI(text, TypeError, "Illegal use of \"constant\" specifier.");
+}
+
BOOST_AUTO_TEST_CASE(empty_name_return_parameter)
{
char const* text = R"(
contract test {
- function f() returns(bool){
+ function f() returns(bool) { }
}
- })";
+ )";
CHECK_SUCCESS(text);
}
@@ -1391,10 +1694,11 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter_with_named_one)
{
char const* text = R"(
contract test {
- function f(uint, uint k) returns(uint ret_k){
+ function f(uint, uint k) returns(uint ret_k) {
return k;
+ }
}
- })";
+ )";
CHECK_SUCCESS(text);
}
@@ -1402,16 +1706,21 @@ BOOST_AUTO_TEST_CASE(empty_name_return_parameter_with_named_one)
{
char const* text = R"(
contract test {
- function f() returns(uint ret_k, uint){
+ function f() returns(uint ret_k, uint) {
return 5;
+ }
}
- })";
+ )";
CHECK_ERROR(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(disallow_declaration_of_void_type)
{
- char const* sourceCode = "contract c { function f() { var (x) = f(); } }";
+ char const* sourceCode = R"(
+ contract c {
+ function f() { var (x) = f(); }
+ }
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1419,22 +1728,22 @@ BOOST_AUTO_TEST_CASE(overflow_caused_by_ether_units)
{
char const* sourceCodeFine = R"(
contract c {
- function c ()
- {
- a = 115792089237316195423570985008687907853269984665640564039458;
+ function c () {
+ a = 115792089237316195423570985008687907853269984665640564039458;
}
uint256 a;
- })";
+ }
+ )";
ETH_TEST_CHECK_NO_THROW(parseAndAnalyse(sourceCodeFine),
"Parsing and Resolving names failed");
char const* sourceCode = R"(
contract c {
- function c ()
- {
+ function c () {
a = 115792089237316195423570985008687907853269984665640564039458 ether;
}
uint256 a;
- })";
+ }
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1443,21 +1752,92 @@ BOOST_AUTO_TEST_CASE(exp_operator_exponent_too_big)
char const* sourceCode = R"(
contract test {
function f() returns(uint d) { return 2 ** 10000000000; }
- })";
+ }
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(exp_warn_literal_base)
+{
+ char const* sourceCode = R"(
+ contract test {
+ function f() returns(uint) {
+ uint8 x = 100;
+ return 10**x;
+ }
+ }
+ )";
+ CHECK_WARNING(sourceCode, "might overflow");
+ sourceCode = R"(
+ contract test {
+ function f() returns(uint) {
+ uint8 x = 100;
+ return uint8(10)**x;
+ }
+ }
+ )";
+ CHECK_SUCCESS(sourceCode);
+ sourceCode = R"(
+ contract test {
+ function f() returns(uint) {
+ return 2**80;
+ }
+ }
+ )";
+ CHECK_SUCCESS(sourceCode);
+}
+
+
+BOOST_AUTO_TEST_CASE(warn_var_from_zero)
+{
+ char const* sourceCode = R"(
+ contract test {
+ function f() returns (uint) {
+ var i = 1;
+ return i;
+ }
+ }
+ )";
+ CHECK_WARNING(sourceCode, "uint8, which can hold values between 0 and 255");
+ sourceCode = R"(
+ contract test {
+ function f() {
+ var i = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
+ i;
+ }
+ }
+ )";
+ CHECK_WARNING(sourceCode, "uint256, which can hold values between 0 and 115792089237316195423570985008687907853269984665640564039457584007913129639935");
+ sourceCode = R"(
+ contract test {
+ function f() {
+ var i = -2;
+ i;
+ }
+ }
+ )";
+ CHECK_WARNING(sourceCode, "int8, which can hold values between -128 and 127");
+ sourceCode = R"(
+ contract test {
+ function f() {
+ for (var i = 0; i < msg.data.length; i++) { }
+ }
+ }
+ )";
+ CHECK_WARNING(sourceCode, "uint8, which can hold");
+}
+
BOOST_AUTO_TEST_CASE(enum_member_access)
{
char const* text = R"(
- contract test {
- enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
- function test()
- {
- choices = ActionChoices.GoStraight;
- }
- ActionChoices choices;
+ contract test {
+ enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
+ function test()
+ {
+ choices = ActionChoices.GoStraight;
}
+ ActionChoices choices;
+ }
)";
CHECK_SUCCESS(text);
}
@@ -1465,14 +1845,14 @@ BOOST_AUTO_TEST_CASE(enum_member_access)
BOOST_AUTO_TEST_CASE(enum_member_access_accross_contracts)
{
char const* text = R"(
- contract Interface {
- enum MyEnum { One, Two }
- }
- contract Impl {
- function test() returns (Interface.MyEnum) {
- return Interface.MyEnum.One;
- }
+ contract Interface {
+ enum MyEnum { One, Two }
+ }
+ contract Impl {
+ function test() returns (Interface.MyEnum) {
+ return Interface.MyEnum.One;
}
+ }
)";
CHECK_SUCCESS(text);
}
@@ -1480,14 +1860,13 @@ BOOST_AUTO_TEST_CASE(enum_member_access_accross_contracts)
BOOST_AUTO_TEST_CASE(enum_invalid_member_access)
{
char const* text = R"(
- contract test {
- enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
- function test()
- {
- choices = ActionChoices.RunAroundWavingYourHands;
- }
- ActionChoices choices;
+ contract test {
+ enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
+ function test() {
+ choices = ActionChoices.RunAroundWavingYourHands;
}
+ ActionChoices choices;
+ }
)";
CHECK_ERROR(text, TypeError, "");
}
@@ -1495,14 +1874,13 @@ BOOST_AUTO_TEST_CASE(enum_invalid_member_access)
BOOST_AUTO_TEST_CASE(enum_invalid_direct_member_access)
{
char const* text = R"(
- contract test {
- enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
- function test()
- {
- choices = Sit;
- }
- ActionChoices choices;
+ contract test {
+ enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
+ function test() {
+ choices = Sit;
}
+ ActionChoices choices;
+ }
)";
CHECK_ERROR(text, DeclarationError, "");
}
@@ -1510,16 +1888,15 @@ BOOST_AUTO_TEST_CASE(enum_invalid_direct_member_access)
BOOST_AUTO_TEST_CASE(enum_explicit_conversion_is_okay)
{
char const* text = R"(
- contract test {
- enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
- function test()
- {
- a = uint256(ActionChoices.GoStraight);
- b = uint64(ActionChoices.Sit);
- }
- uint256 a;
- uint64 b;
+ contract test {
+ enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
+ function test() {
+ a = uint256(ActionChoices.GoStraight);
+ b = uint64(ActionChoices.Sit);
}
+ uint256 a;
+ uint64 b;
+ }
)";
CHECK_SUCCESS(text);
}
@@ -1527,33 +1904,43 @@ BOOST_AUTO_TEST_CASE(enum_explicit_conversion_is_okay)
BOOST_AUTO_TEST_CASE(int_to_enum_explicit_conversion_is_okay)
{
char const* text = R"(
- contract test {
- enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
- function test()
- {
- a = 2;
- b = ActionChoices(a);
- }
- uint256 a;
- ActionChoices b;
+ contract test {
+ enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
+ function test() {
+ a = 2;
+ b = ActionChoices(a);
}
+ uint256 a;
+ ActionChoices b;
+ }
)";
CHECK_SUCCESS(text);
}
-BOOST_AUTO_TEST_CASE(enum_implicit_conversion_is_not_okay)
+BOOST_AUTO_TEST_CASE(enum_implicit_conversion_is_not_okay_256)
{
char const* text = R"(
- contract test {
- enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
- function test()
- {
- a = ActionChoices.GoStraight;
- b = ActionChoices.Sit;
- }
- uint256 a;
- uint64 b;
+ contract test {
+ enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
+ function test() {
+ a = ActionChoices.GoStraight;
+ }
+ uint256 a;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "");
+}
+
+BOOST_AUTO_TEST_CASE(enum_implicit_conversion_is_not_okay_64)
+{
+ char const* text = R"(
+ contract test {
+ enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
+ function test() {
+ b = ActionChoices.Sit;
}
+ uint64 b;
+ }
)";
CHECK_ERROR(text, TypeError, "");
}
@@ -1564,8 +1951,7 @@ BOOST_AUTO_TEST_CASE(enum_to_enum_conversion_is_not_okay)
contract test {
enum Paper { Up, Down, Left, Right }
enum Ground { North, South, West, East }
- function test()
- {
+ function test() {
Ground(Paper.Up);
}
}
@@ -1609,7 +1995,7 @@ BOOST_AUTO_TEST_CASE(private_visibility)
contract derived is base {
function g() { f(); }
}
- )";
+ )";
CHECK_ERROR(sourceCode, DeclarationError, "");
}
@@ -1622,7 +2008,7 @@ BOOST_AUTO_TEST_CASE(private_visibility_via_explicit_base_access)
contract derived is base {
function g() { base.f(); }
}
- )";
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1633,7 +2019,7 @@ BOOST_AUTO_TEST_CASE(external_visibility)
function f() external {}
function g() { f(); }
}
- )";
+ )";
CHECK_ERROR(sourceCode, DeclarationError, "");
}
@@ -1646,7 +2032,7 @@ BOOST_AUTO_TEST_CASE(external_base_visibility)
contract derived is base {
function g() { base.f(); }
}
- )";
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1656,7 +2042,7 @@ BOOST_AUTO_TEST_CASE(external_argument_assign)
contract c {
function f(uint a) external { a = 1; }
}
- )";
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1666,7 +2052,7 @@ BOOST_AUTO_TEST_CASE(external_argument_increment)
contract c {
function f(uint a) external { a++; }
}
- )";
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1676,7 +2062,7 @@ BOOST_AUTO_TEST_CASE(external_argument_delete)
contract c {
function f(uint a) external { delete a; }
}
- )";
+ )";
CHECK_ERROR(sourceCode, TypeError, "");
}
@@ -1684,12 +2070,12 @@ BOOST_AUTO_TEST_CASE(test_for_bug_override_function_with_bytearray_type)
{
char const* sourceCode = R"(
contract Vehicle {
- function f(bytes _a) external returns (uint256 r) {r = 1;}
+ function f(bytes) external returns (uint256 r) {r = 1;}
}
contract Bike is Vehicle {
- function f(bytes _a) external returns (uint256 r) {r = 42;}
+ function f(bytes) external returns (uint256 r) {r = 42;}
}
- )";
+ )";
ETH_TEST_CHECK_NO_THROW(parseAndAnalyse(sourceCode), "Parsing and Name Resolving failed");
}
@@ -1698,10 +2084,21 @@ BOOST_AUTO_TEST_CASE(array_with_nonconstant_length)
char const* text = R"(
contract c {
function f(uint a) { uint8[a] x; }
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(array_with_negative_length)
+{
+ char const* text = R"(
+ contract c {
+ function f(uint a) { uint8[-1] x; }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Array with negative length specified");
+}
+
BOOST_AUTO_TEST_CASE(array_copy_with_different_types1)
{
char const* text = R"(
@@ -1709,7 +2106,8 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types1)
bytes a;
uint[] b;
function f() { b = a; }
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1720,7 +2118,8 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types2)
uint32[] a;
uint8[] b;
function f() { b = a; }
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1731,7 +2130,8 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types_conversion_possible)
uint32[] a;
uint8[] b;
function f() { a = b; }
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1742,7 +2142,8 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types_static_dynamic)
uint32[] a;
uint8[80] b;
function f() { a = b; }
- })";
+ }
+ )";
CHECK_SUCCESS(text);
}
@@ -1753,7 +2154,8 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types_dynamic_static)
uint[] a;
uint[80] b;
function f() { b = a; }
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1762,7 +2164,8 @@ BOOST_AUTO_TEST_CASE(storage_variable_initialization_with_incorrect_type_int)
char const* text = R"(
contract c {
uint8 a = 1000;
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1771,7 +2174,8 @@ BOOST_AUTO_TEST_CASE(storage_variable_initialization_with_incorrect_type_string)
char const* text = R"(
contract c {
uint a = "abc";
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -1887,28 +2291,150 @@ BOOST_AUTO_TEST_CASE(test_byte_is_alias_of_byte1)
contract c {
bytes arr;
function f() { byte a = arr[0];}
- })";
+ }
+ )";
ETH_TEST_REQUIRE_NO_THROW(parseAndAnalyse(text), "Type resolving failed");
}
+BOOST_AUTO_TEST_CASE(warns_assigning_decimal_to_bytesxx)
+{
+ char const* text = R"(
+ contract Foo {
+ bytes32 a = 7;
+ }
+ )";
+ CHECK_WARNING(text, "Decimal literal assigned to bytesXX variable will be left-aligned.");
+}
+
+BOOST_AUTO_TEST_CASE(does_not_warn_assigning_hex_number_to_bytesxx)
+{
+ char const* text = R"(
+ contract Foo {
+ bytes32 a = 0x1234;
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(explicit_conversion_from_decimal_to_bytesxx)
+{
+ char const* text = R"(
+ contract Foo {
+ bytes32 a = bytes32(7);
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
BOOST_AUTO_TEST_CASE(assigning_value_to_const_variable)
{
char const* text = R"(
contract Foo {
function changeIt() { x = 9; }
uint constant x = 56;
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
-BOOST_AUTO_TEST_CASE(complex_const_variable)
+BOOST_AUTO_TEST_CASE(assigning_state_to_const_variable)
{
- //for now constant specifier is valid only for uint bytesXX and enums
char const* text = R"(
- contract Foo {
- mapping(uint => bool) constant mapVar;
- })";
- CHECK_ERROR(text, TypeError, "");
+ contract C {
+ address constant x = msg.sender;
+ }
+ )";
+ // Change to TypeError for 0.5.0.
+ CHECK_WARNING(text, "Initial value for constant variable has to be compile-time constant.");
+}
+
+BOOST_AUTO_TEST_CASE(constant_string_literal_disallows_assignment)
+{
+ char const* text = R"(
+ contract Test {
+ string constant x = "abefghijklmnopqabcdefghijklmnopqabcdefghijklmnopqabca";
+ function f() {
+ x[0] = "f";
+ }
+ }
+ )";
+
+ // Even if this is made possible in the future, we should not allow assignment
+ // to elements of constant arrays.
+ CHECK_ERROR(text, TypeError, "Index access for string is not possible.");
+}
+
+BOOST_AUTO_TEST_CASE(assign_constant_function_value_to_constant)
+{
+ char const* text = R"(
+ contract C {
+ function () constant returns (uint) x;
+ uint constant y = x();
+ }
+ )";
+ // Change to TypeError for 0.5.0.
+ CHECK_WARNING(text, "Initial value for constant variable has to be compile-time constant.");
+}
+
+BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_conversion)
+{
+ char const* text = R"(
+ contract C {
+ C constant x = C(0x123);
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_expression)
+{
+ char const* text = R"(
+ contract C {
+ uint constant x = 0x123 + 0x456;
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_keccak)
+{
+ char const* text = R"(
+ contract C {
+ bytes32 constant x = keccak256("abc");
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(assignment_to_const_array_vars)
+{
+ char const* text = R"(
+ contract C {
+ uint[3] constant x = [uint(1), 2, 3];
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "implemented");
+}
+
+BOOST_AUTO_TEST_CASE(constant_struct)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint x; uint[] y; }
+ S constant x = S(5, new uint[](4));
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "implemented");
+}
+
+BOOST_AUTO_TEST_CASE(address_is_constant)
+{
+ char const* text = R"(
+ contract C {
+ address constant x = 0x1212121212121212121212121212121212121212;
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(uninitialized_const_variable)
@@ -1916,7 +2442,8 @@ BOOST_AUTO_TEST_CASE(uninitialized_const_variable)
char const* text = R"(
contract Foo {
uint constant y;
- })";
+ }
+ )";
CHECK_ERROR(text, TypeError, "");
}
@@ -2009,12 +2536,12 @@ BOOST_AUTO_TEST_CASE(multiple_constructors)
BOOST_AUTO_TEST_CASE(equal_overload)
{
char const* sourceCode = R"(
- contract test {
+ contract C {
function test(uint a) returns (uint b) { }
function test(uint a) external {}
}
)";
- CHECK_ERROR(sourceCode, DeclarationError, "");
+ CHECK_ERROR_ALLOW_MULTI(sourceCode, DeclarationError, "");
}
BOOST_AUTO_TEST_CASE(uninitialized_var)
@@ -2058,6 +2585,16 @@ BOOST_AUTO_TEST_CASE(invalid_utf8_explicit)
CHECK_ERROR(sourceCode, TypeError, "Explicit type conversion not allowed");
}
+BOOST_AUTO_TEST_CASE(large_utf8_codepoint)
+{
+ char const* sourceCode = R"(
+ contract C {
+ string s = "\xf0\x9f\xa6\x84";
+ }
+ )";
+ CHECK_SUCCESS(sourceCode);
+}
+
BOOST_AUTO_TEST_CASE(string_index)
{
char const* sourceCode = R"(
@@ -2212,6 +2749,7 @@ BOOST_AUTO_TEST_CASE(storage_location_local_variables)
uint[] storage x;
uint[] memory y;
uint[] memory z;
+ x;y;z;
}
}
)";
@@ -2261,6 +2799,32 @@ BOOST_AUTO_TEST_CASE(storage_assign_to_different_local_variable)
CHECK_ERROR(sourceCode, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(uninitialized_mapping_variable)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() {
+ mapping(uint => uint) x;
+ x;
+ }
+ }
+ )";
+ CHECK_ERROR(sourceCode, TypeError, "Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable");
+}
+
+BOOST_AUTO_TEST_CASE(uninitialized_mapping_array_variable)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() {
+ mapping(uint => uint)[] storage x;
+ x;
+ }
+ }
+ )";
+ CHECK_WARNING(sourceCode, "Uninitialized storage pointer");
+}
+
BOOST_AUTO_TEST_CASE(no_delete_on_storage_pointers)
{
char const* sourceCode = R"(
@@ -2407,24 +2971,13 @@ BOOST_AUTO_TEST_CASE(literal_strings)
function f() {
string memory long = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
string memory short = "123";
+ long; short;
}
}
)";
CHECK_SUCCESS(text);
}
-BOOST_AUTO_TEST_CASE(invalid_integer_literal_exp)
-{
- char const* text = R"(
- contract Foo {
- function f() {
- var x = 1e2;
- }
- }
- )";
- CHECK_ERROR(text, TypeError, "");
-}
-
BOOST_AUTO_TEST_CASE(memory_structs_with_mappings)
{
char const* text = R"(
@@ -2495,7 +3048,7 @@ BOOST_AUTO_TEST_CASE(call_to_library_function)
{
char const* text = R"(
library Lib {
- function min(uint x, uint y) returns (uint);
+ function min(uint, uint) returns (uint);
}
contract Test {
function f() {
@@ -2550,7 +3103,7 @@ BOOST_AUTO_TEST_CASE(non_initialized_references)
}
function f()
{
- s x;
+ s storage x;
x.a = 2;
}
}
@@ -2559,12 +3112,12 @@ BOOST_AUTO_TEST_CASE(non_initialized_references)
CHECK_WARNING(text, "Uninitialized storage pointer");
}
-BOOST_AUTO_TEST_CASE(sha3_with_large_integer_constant)
+BOOST_AUTO_TEST_CASE(keccak256_with_large_integer_constant)
{
char const* text = R"(
contract c
{
- function f() { sha3(2**500); }
+ function f() { keccak256(2**500); }
}
)";
CHECK_ERROR(text, TypeError, "");
@@ -2593,9 +3146,9 @@ BOOST_AUTO_TEST_CASE(cyclic_binary_dependency_via_inheritance)
BOOST_AUTO_TEST_CASE(multi_variable_declaration_fail)
{
char const* text = R"(
- contract C { function f() { var (x,y); } }
+ contract C { function f() { var (x,y); x = 1; y = 1;} }
)";
- CHECK_ERROR(text, TypeError, "");
+ CHECK_ERROR(text, TypeError, "Assignment necessary for type detection.");
}
BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fine)
@@ -2612,6 +3165,7 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fine)
var (,e,g) = two();
var (,,) = three();
var () = none();
+ a;b;c;d;e;g;
}
}
)";
@@ -2667,9 +3221,10 @@ BOOST_AUTO_TEST_CASE(tuples)
contract C {
function f() {
uint a = (1);
- var (b,) = (1,);
- var (c,d) = (1, 2 + a);
- var (e,) = (1, 2, b);
+ var (b,) = (uint8(1),);
+ var (c,d) = (uint32(1), 2 + a);
+ var (e,) = (uint64(1), 2, b);
+ a;b;c;d;e;
}
}
)";
@@ -2710,6 +3265,31 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_6)
CHECK_ERROR(text, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(tuple_assignment_from_void_function)
+{
+ char const* text = R"(
+ contract C {
+ function f() { }
+ function g() {
+ var (x,) = (f(), f());
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Cannot declare variable with void (empty tuple) type.");
+}
+
+BOOST_AUTO_TEST_CASE(tuple_compound_assignment)
+{
+ char const* text = R"(
+ contract C {
+ function f() returns (uint a, uint b) {
+ (a, b) += (1, 1);
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Compound assignment is not allowed for tuple types.");
+}
+
BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity)
{
char const* text = R"(
@@ -2802,7 +3382,7 @@ BOOST_AUTO_TEST_CASE(using_for_overload)
library D {
struct s { uint a; }
function mul(s storage self, uint x) returns (uint) { return self.a *= x; }
- function mul(s storage self, bytes32 x) returns (bytes32) { }
+ function mul(s storage, bytes32) returns (bytes32) { }
}
contract C {
using D for D.s;
@@ -2914,6 +3494,7 @@ BOOST_AUTO_TEST_CASE(create_memory_arrays)
L.S[][] memory x = new L.S[][](10);
var y = new uint[](20);
var z = new bytes(size);
+ x;y;z;
}
}
)";
@@ -2960,8 +3541,8 @@ BOOST_AUTO_TEST_CASE(function_overload_array_type)
{
char const* text = R"(
contract M {
- function f(uint[] values);
- function f(int[] values);
+ function f(uint[]);
+ function f(int[]);
}
)";
CHECK_SUCCESS(text);
@@ -3165,7 +3746,7 @@ BOOST_AUTO_TEST_CASE(left_value_in_conditional_expression_not_supported_yet)
}
}
)";
- CHECK_ERROR(text, TypeError, "");
+ CHECK_ERROR_ALLOW_MULTI(text, TypeError, "");
}
BOOST_AUTO_TEST_CASE(conditional_expression_with_different_struct)
@@ -3179,8 +3760,8 @@ BOOST_AUTO_TEST_CASE(conditional_expression_with_different_struct)
uint x;
}
function f() {
- s1 x;
- s2 y;
+ s1 memory x;
+ s2 memory y;
true ? x : y;
}
}
@@ -3258,55 +3839,65 @@ BOOST_AUTO_TEST_CASE(conditional_with_all_types)
// integers
uint x;
uint y;
- true ? x : y;
+ uint g = true ? x : y;
+ g += 1; // Avoid unused var warning
// integer constants
- true ? 1 : 3;
+ uint h = true ? 1 : 3;
+ h += 1; // Avoid unused var warning
// string literal
- true ? "hello" : "world";
-
+ var i = true ? "hello" : "world";
+ i = "used"; //Avoid unused var warning
+ }
+ function f2() {
// bool
- true ? true : false;
+ bool j = true ? true : false;
+ j = j && true; // Avoid unused var warning
// real is not there yet.
// array
byte[2] memory a;
byte[2] memory b;
- true ? a : b;
+ var k = true ? a : b;
+ k[0] = byte(0); //Avoid unused var warning
bytes memory e;
bytes memory f;
- true ? e : f;
+ var l = true ? e : f;
+ l[0] = byte(0); // Avoid unused var warning
// fixed bytes
bytes2 c;
bytes2 d;
- true ? c : d;
+ var m = true ? c : d;
+ m &= m;
+ }
+ function f3() {
// contract doesn't fit in here
// struct
- true ? struct_x : struct_y;
+ struct_x = true ? struct_x : struct_y;
// function
- true ? fun_x : fun_y;
-
+ var r = true ? fun_x : fun_y;
+ r(); // Avoid unused var warning
// enum
small enum_x;
small enum_y;
- true ? enum_x : enum_y;
+ enum_x = true ? enum_x : enum_y;
// tuple
- true ? (1, 2) : (3, 4);
-
+ var (n, o) = true ? (1, 2) : (3, 4);
+ (n, o) = (o, n); // Avoid unused var warning
// mapping
- true ? table1 : table2;
-
+ var p = true ? table1 : table2;
+ p[0] = 0; // Avoid unused var warning
// typetype
- true ? uint32(1) : uint32(2);
-
+ var q = true ? uint32(1) : uint32(2);
+ q += 1; // Avoid unused var warning
// modifier doesn't fit in here
// magic doesn't fit in here
@@ -3356,6 +3947,7 @@ BOOST_AUTO_TEST_CASE(uint7_and_uintM_as_identifier)
uint7 = 5;
string memory intM;
uint bytesM = 21;
+ intM; bytesM;
}
}
)";
@@ -3407,6 +3999,7 @@ BOOST_AUTO_TEST_CASE(int10abc_is_identifier)
function f() {
uint uint10abc = 3;
int int10abc = 4;
+ uint10abc; int10abc;
}
}
)";
@@ -3491,6 +4084,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_int_conversion)
int128 b = 4;
fixed c = b;
ufixed d = a;
+ c; d;
}
}
)";
@@ -3504,6 +4098,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_rational_int_conversion)
function f() {
fixed c = 3;
ufixed d = 4;
+ c; d;
}
}
)";
@@ -3517,6 +4112,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_rational_fraction_conversion)
function f() {
fixed a = 4.5;
ufixed d = 2.5;
+ a; d;
}
}
)";
@@ -3530,6 +4126,7 @@ BOOST_AUTO_TEST_CASE(invalid_int_implicit_conversion_from_fixed)
function f() {
fixed a = 4.5;
int b = a;
+ a; b;
}
}
)";
@@ -3541,12 +4138,33 @@ BOOST_AUTO_TEST_CASE(rational_unary_operation)
char const* text = R"(
contract test {
function f() {
+ ufixed8x16 a = 3.25;
+ fixed8x16 b = -3.25;
+ a;
+ b;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract test {
+ function f() {
ufixed8x16 a = +3.25;
fixed8x16 b = -3.25;
+ a; b;
}
}
)";
- CHECK_SUCCESS(text);
+ CHECK_WARNING(text,"Use of unary + is deprecated");
+ text = R"(
+ contract test {
+ function f(uint x) {
+ uint y = +x;
+ y;
+ }
+ }
+ )";
+ CHECK_WARNING(text,"Use of unary + is deprecated");
}
BOOST_AUTO_TEST_CASE(leading_zero_rationals_convert)
@@ -3558,6 +4176,7 @@ BOOST_AUTO_TEST_CASE(leading_zero_rationals_convert)
ufixed0x56 b = 0.0000000000000006661338147750939242541790008544921875;
fixed0x8 c = -0.5;
fixed0x56 d = -0.0000000000000006661338147750939242541790008544921875;
+ a; b; c; d;
}
}
)";
@@ -3575,6 +4194,7 @@ BOOST_AUTO_TEST_CASE(size_capabilities_of_fixed_point_types)
fixed248x8 d = -123456781234567979695948382928485849359686494864095409282048094275023098123.5;
fixed0x256 e = -0.93322335481643744342575580035176794825198893968114429702091846411734101080123092162893656820177312738451291806995868682861328125;
fixed0x256 g = -0.00011788606643744342575580035176794825198893968114429702091846411734101080123092162893656820177312738451291806995868682861328125;
+ a; b; c; d; e; g;
}
}
)";
@@ -3614,6 +4234,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_valid_explicit_conversions)
ufixed0x256 a = ufixed0x256(1/3);
ufixed0x248 b = ufixed0x248(1/3);
ufixed0x8 c = ufixed0x8(1/3);
+ a; b; c;
}
}
)";
@@ -3661,7 +4282,7 @@ BOOST_AUTO_TEST_CASE(fixed_to_bytes_implicit_conversion)
char const* text = R"(
contract test {
function f() {
- fixed a = 3.2;
+ fixed a = 3.25;
bytes32 c = a;
}
}
@@ -3745,20 +4366,54 @@ BOOST_AUTO_TEST_CASE(rational_to_fixed_literal_expression)
ufixed8x248 e = ufixed8x248(35.245 % 12.9);
ufixed8x248 f = ufixed8x248(1.2 % 2);
fixed g = 2 ** -2;
+ a; b; c; d; e; f; g;
}
}
)";
CHECK_SUCCESS(text);
}
-BOOST_AUTO_TEST_CASE(rational_as_exponent_value)
+BOOST_AUTO_TEST_CASE(rational_as_exponent_value_neg_decimal)
{
char const* text = R"(
contract test {
function f() {
fixed g = 2 ** -2.2;
+ }
+ }
+ )";
+ BOOST_CHECK(!success(text));
+}
+
+BOOST_AUTO_TEST_CASE(rational_as_exponent_value_pos_decimal)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
ufixed b = 3 ** 2.5;
+ }
+ }
+ )";
+ BOOST_CHECK(!success(text));
+}
+
+BOOST_AUTO_TEST_CASE(rational_as_exponent_half)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
ufixed24x24 b = 2 ** (1/2);
+ }
+ }
+ )";
+ BOOST_CHECK(!success(text));
+}
+
+BOOST_AUTO_TEST_CASE(rational_as_exponent_value_neg_quarter)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
fixed40x40 c = 42 ** (-1/4);
}
}
@@ -3766,15 +4421,48 @@ BOOST_AUTO_TEST_CASE(rational_as_exponent_value)
BOOST_CHECK(!success(text));
}
-BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents)
+BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents_15)
{
char const* text = R"(
contract test {
function f() {
ufixed a = 3 ** ufixed(1.5);
+ }
+ }
+ )";
+ BOOST_CHECK(!success(text));
+}
+
+BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents_half)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
ufixed b = 2 ** ufixed(1/2);
+ }
+ }
+ )";
+ BOOST_CHECK(!success(text));
+}
+
+BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents_neg)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
fixed c = 42 ** fixed(-1/4);
- fixed d = 16 ** fixed(-0.33);
+ }
+ }
+ )";
+ BOOST_CHECK(!success(text));
+}
+
+BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents_neg_decimal)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
+ fixed d = 16 ** fixed(-0.5);
}
}
)";
@@ -3789,6 +4477,7 @@ BOOST_AUTO_TEST_CASE(var_capable_of_holding_constant_rationals)
var a = 0.12345678;
var b = 12345678.352;
var c = 0.00000009;
+ a; b; c;
}
}
)";
@@ -3801,6 +4490,7 @@ BOOST_AUTO_TEST_CASE(var_and_rational_with_tuple)
contract test {
function f() {
var (a, b) = (.5, 1/3);
+ a; b;
}
}
)";
@@ -3824,7 +4514,7 @@ BOOST_AUTO_TEST_CASE(rational_bitnot_unary_operation)
char const* text = R"(
contract test {
function f() {
- fixed a = ~3.56;
+ fixed a = ~3.5;
}
}
)";
@@ -3836,7 +4526,7 @@ BOOST_AUTO_TEST_CASE(rational_bitor_binary_operation)
char const* text = R"(
contract test {
function f() {
- fixed a = 1.56 | 3;
+ fixed a = 1.5 | 3;
}
}
)";
@@ -3848,7 +4538,7 @@ BOOST_AUTO_TEST_CASE(rational_bitxor_binary_operation)
char const* text = R"(
contract test {
function f() {
- fixed a = 1.56 ^ 3;
+ fixed a = 1.75 ^ 3;
}
}
)";
@@ -3860,7 +4550,7 @@ BOOST_AUTO_TEST_CASE(rational_bitand_binary_operation)
char const* text = R"(
contract test {
function f() {
- fixed a = 1.56 & 3;
+ fixed a = 1.75 & 3;
}
}
)";
@@ -3874,6 +4564,7 @@ BOOST_AUTO_TEST_CASE(zero_handling)
function f() {
fixed8x8 a = 0;
ufixed8x8 b = 0;
+ a; b;
}
}
)";
@@ -3952,7 +4643,7 @@ BOOST_AUTO_TEST_CASE(unused_return_value_send)
}
}
)";
- CHECK_WARNING(text, "Return value of low-level calls not used");
+ CHECK_WARNING(text, "Failure condition of 'send' ignored. Consider using 'transfer' instead.");
}
BOOST_AUTO_TEST_CASE(unused_return_value_call)
@@ -3988,7 +4679,7 @@ BOOST_AUTO_TEST_CASE(unused_return_value_callcode)
}
}
)";
- CHECK_WARNING(text, "Return value of low-level calls not used");
+ CHECK_WARNING_ALLOW_MULTI(text, "Return value of low-level calls not used");
}
BOOST_AUTO_TEST_CASE(unused_return_value_delegatecall)
@@ -4003,6 +4694,31 @@ BOOST_AUTO_TEST_CASE(unused_return_value_delegatecall)
CHECK_WARNING(text, "Return value of low-level calls not used");
}
+BOOST_AUTO_TEST_CASE(warn_about_callcode)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
+ var x = address(0x12).callcode;
+ x;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "\"callcode\" has been deprecated in favour");
+}
+
+BOOST_AUTO_TEST_CASE(no_warn_about_callcode_as_local)
+{
+ char const* text = R"(
+ contract test {
+ function callcode() {
+ var x = this.callcode;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
BOOST_AUTO_TEST_CASE(modifier_without_underscore)
{
char const* text = R"(
@@ -4071,6 +4787,25 @@ BOOST_AUTO_TEST_CASE(illegal_override_payable_nonpayable)
CHECK_ERROR(text, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(function_variable_mixin)
+{
+ // bug #1798 (cpp-ethereum), related to #1286 (solidity)
+ char const* text = R"(
+ contract attribute {
+ bool ok = false;
+ }
+ contract func {
+ function ok() returns (bool) { return true; }
+ }
+
+ contract attr_func is attribute, func {
+ function checkOk() returns (bool) { return ok(); }
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "");
+}
+
+
BOOST_AUTO_TEST_CASE(payable_constant_conflict)
{
char const* text = R"(
@@ -4161,7 +4896,7 @@ BOOST_AUTO_TEST_CASE(invalid_array_as_statement)
char const* text = R"(
contract test {
struct S { uint x; }
- function test(uint k) { S[k]; }
+ function test(uint k) { S[k]; }
}
)";
CHECK_ERROR(text, TypeError, "");
@@ -4360,6 +5095,7 @@ BOOST_AUTO_TEST_CASE(function_type_arrays)
function(uint) returns (uint)[10] storage b = y;
function(uint) external returns (uint)[] memory c;
c = new function(uint) external returns (uint)[](200);
+ a; b;
}
}
)";
@@ -4411,16 +5147,77 @@ BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid)
CHECK_ERROR(text, TypeError, "");
}
-BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal)
+BOOST_AUTO_TEST_CASE(external_function_to_function_type_calldata_parameter)
{
+ // This is a test that checks that the type of the `bytes` parameter is
+ // correctly changed from its own type `bytes calldata` to `bytes memory`
+ // when converting to a function type.
char const* text = R"(
- contract A {
- function a() {
- .8E0;
+ contract C {
+ function f(function(bytes memory) external g) { }
+ function callback(bytes) external {}
+ function g() {
+ f(this.callback);
}
}
)";
- CHECK_ERROR(text, TypeError, "");
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(external_function_type_to_address)
+{
+ char const* text = R"(
+ contract C {
+ function f() returns (address) {
+ return address(this.f);
+ }
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(internal_function_type_to_address)
+{
+ char const* text = R"(
+ contract C {
+ function f() returns (address) {
+ return address(f);
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Explicit type conversion not allowed");
+}
+
+BOOST_AUTO_TEST_CASE(external_function_type_to_uint)
+{
+ char const* text = R"(
+ contract C {
+ function f() returns (uint) {
+ return uint(this.f);
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Explicit type conversion not allowed");
+}
+
+BOOST_AUTO_TEST_CASE(warn_function_type_parameters_with_names)
+{
+ char const* text = R"(
+ contract C {
+ function(uint a) f;
+ }
+ )";
+ CHECK_WARNING(text, "Naming function type parameters is deprecated.");
+}
+
+BOOST_AUTO_TEST_CASE(warn_function_type_return_parameters_with_names)
+{
+ char const* text = R"(
+ contract C {
+ function(uint) returns(bool ret) f;
+ }
+ )";
+ CHECK_WARNING(text, "Naming function type return parameters is deprecated.");
}
BOOST_AUTO_TEST_CASE(shift_constant_left_negative_rvalue)
@@ -4474,7 +5271,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_positive_stack)
}
}
)";
- CHECK_WARNING(text, "Inline assembly block is not balanced");
+ CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s).");
}
BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack)
@@ -4488,7 +5285,20 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack)
}
}
)";
- CHECK_WARNING(text, "Inline assembly block is not balanced");
+ CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s).");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load)
+{
+ char const* text = R"(
+ contract c {
+ uint8 x;
+ function f() {
+ assembly { x pop }
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
}
BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier)
@@ -4521,7 +5331,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage)
}
}
)";
- CHECK_ERROR(text, DeclarationError, "not found, not unique or not lvalue.");
+ CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
}
BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
@@ -4539,7 +5349,83 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers)
}
}
)";
- CHECK_ERROR(text, DeclarationError, "");
+ CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign)
+{
+ char const* text = R"(
+ contract test {
+ uint constant x = 1;
+ function f() {
+ assembly {
+ x := 2
+ }
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_constant_access)
+{
+ char const* text = R"(
+ contract test {
+ uint constant x = 1;
+ function f() {
+ assembly {
+ let y := x
+ }
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions)
+{
+ char const* text = R"(
+ contract test {
+ function f() {
+ uint a;
+ assembly {
+ function g() -> x { x := a }
+ }
+ }
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Cannot access local Solidity variables from inside an inline assembly function.");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions_storage_ptr)
+{
+ char const* text = R"(
+ contract test {
+ uint[] r;
+ function f() {
+ uint[] storage a = r;
+ assembly {
+ function g() -> x { x := a_offset }
+ }
+ }
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Cannot access local Solidity variables from inside an inline assembly function.");
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_storage_variable_access_out_of_functions)
+{
+ char const* text = R"(
+ contract test {
+ uint a;
+ function f() {
+ assembly {
+ function g() -> x { x := a_slot }
+ }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(invalid_mobile_type)
@@ -4555,6 +5441,760 @@ BOOST_AUTO_TEST_CASE(invalid_mobile_type)
CHECK_ERROR(text, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(warns_msg_value_in_non_payable_public_function)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ msg.value;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?");
+}
+
+BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_payable_function)
+{
+ char const* text = R"(
+ contract C {
+ function f() payable {
+ msg.value;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_internal_function)
+{
+ char const* text = R"(
+ contract C {
+ function f() internal {
+ msg.value;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_library)
+{
+ char const* text = R"(
+ library C {
+ function f() {
+ msg.value;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(does_not_warn_non_magic_msg_value)
+{
+ char const* text = R"(
+ contract C {
+ struct msg {
+ uint256 value;
+ }
+
+ function f() {
+ msg.value;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_modifier_following_non_payable_public_function)
+{
+ char const* text = R"(
+ contract c {
+ function f() { }
+ modifier m() { msg.value; _; }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(assignment_to_constant)
+{
+ char const* text = R"(
+ contract c {
+ uint constant a = 1;
+ function f() { a = 2; }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Cannot assign to a constant variable.");
+}
+
+BOOST_AUTO_TEST_CASE(inconstructible_internal_constructor)
+{
+ char const* text = R"(
+ contract C {
+ function C() internal {}
+ }
+ contract D {
+ function f() { var x = new C(); }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Contract with internal constructor cannot be created directly.");
+}
+
+BOOST_AUTO_TEST_CASE(inconstructible_internal_constructor_inverted)
+{
+ // Previously, the type information for A was not yet available at the point of
+ // "new A".
+ char const* text = R"(
+ contract B {
+ A a;
+ function B() {
+ a = new A(this);
+ }
+ }
+ contract A {
+ function A(address a) internal {}
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Contract with internal constructor cannot be created directly.");
+}
+
+BOOST_AUTO_TEST_CASE(constructible_internal_constructor)
+{
+ char const* text = R"(
+ contract C {
+ function C() internal {}
+ }
+ contract D is C {
+ function D() { }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(address_checksum_type_deduction)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ var x = 0xfA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ x.send(2);
+ }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_address_checksum)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ address x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ x;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "checksum");
+}
+
+BOOST_AUTO_TEST_CASE(invalid_address_no_checksum)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ address x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e;
+ x;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "checksum");
+}
+
+BOOST_AUTO_TEST_CASE(invalid_address_length)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ address x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ x;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "checksum");
+}
+
+BOOST_AUTO_TEST_CASE(address_test_for_bug_in_implementation)
+{
+ // A previous implementation claimed the string would be an address
+ char const* text = R"(
+ contract AddrString {
+ address public test = "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c";
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "is not implicitly convertible to expected type address");
+ text = R"(
+ contract AddrString {
+ function f() returns (address) {
+ return "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c";
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "is not implicitly convertible to expected type");
+}
+
+BOOST_AUTO_TEST_CASE(early_exit_on_fatal_errors)
+{
+ // This tests a crash that occured because we did not stop for fatal errors.
+ char const* text = R"(
+ contract C {
+ struct S {
+ ftring a;
+ }
+ S public s;
+ function s() s {
+ }
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique");
+}
+
+BOOST_AUTO_TEST_CASE(address_methods)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ address addr;
+ uint balance = addr.balance;
+ bool callRet = addr.call();
+ bool callcodeRet = addr.callcode();
+ bool delegatecallRet = addr.delegatecall();
+ bool sendRet = addr.send(1);
+ addr.transfer(1);
+ callRet; callcodeRet; delegatecallRet; sendRet;
+ }
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(cyclic_dependency_for_constants)
+{
+ char const* text = R"(
+ contract C {
+ uint constant a = a;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "cyclic dependency via a");
+ text = R"(
+ contract C {
+ uint constant a = b * c;
+ uint constant b = 7;
+ uint constant c = b + uint(keccak256(d));
+ uint constant d = 2 + a;
+ }
+ )";
+ CHECK_ERROR_ALLOW_MULTI(text, TypeError, "a has a cyclic dependency via c");
+ text = R"(
+ contract C {
+ uint constant a = b * c;
+ uint constant b = 7;
+ uint constant c = 4 + uint(keccak256(d));
+ uint constant d = 2 + b;
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(interface)
+{
+ char const* text = R"(
+ interface I {
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(interface_constructor)
+{
+ char const* text = R"(
+ interface I {
+ function I();
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Constructor cannot be defined in interfaces");
+}
+
+BOOST_AUTO_TEST_CASE(interface_functions)
+{
+ char const* text = R"(
+ interface I {
+ function();
+ function f();
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(interface_function_bodies)
+{
+ char const* text = R"(
+ interface I {
+ function f() {
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Functions in interfaces cannot have an implementation");
+}
+
+BOOST_AUTO_TEST_CASE(interface_function_internal)
+{
+ char const* text = R"(
+ interface I {
+ function f() internal;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private.");
+}
+
+BOOST_AUTO_TEST_CASE(interface_function_private)
+{
+ char const* text = R"(
+ interface I {
+ function f() private;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private.");
+}
+
+BOOST_AUTO_TEST_CASE(interface_events)
+{
+ char const* text = R"(
+ interface I {
+ event E();
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(interface_inheritance)
+{
+ char const* text = R"(
+ interface A {
+ }
+ interface I is A {
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Interfaces cannot inherit");
+}
+
+
+BOOST_AUTO_TEST_CASE(interface_structs)
+{
+ char const* text = R"(
+ interface I {
+ struct A {
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Structs cannot be defined in interfaces");
+}
+
+BOOST_AUTO_TEST_CASE(interface_variables)
+{
+ char const* text = R"(
+ interface I {
+ uint a;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Variables cannot be declared in interfaces");
+}
+
+BOOST_AUTO_TEST_CASE(interface_function_parameters)
+{
+ char const* text = R"(
+ interface I {
+ function f(uint a) returns(bool);
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(interface_enums)
+{
+ char const* text = R"(
+ interface I {
+ enum A { B, C }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Enumerable cannot be declared in interfaces");
+}
+
+BOOST_AUTO_TEST_CASE(using_interface)
+{
+ char const* text = R"(
+ interface I {
+ function f();
+ }
+ contract C is I {
+ function f() {
+ }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(using_interface_complex)
+{
+ char const* text = R"(
+ interface I {
+ event A();
+ function f();
+ function g();
+ function();
+ }
+ contract C is I {
+ function f() {
+ }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(warn_about_throw)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ throw;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "\"throw\" is deprecated");
+}
+
+BOOST_AUTO_TEST_CASE(bare_revert)
+{
+ char const* text = R"(
+ contract C {
+ function f(uint x) {
+ if (x > 7)
+ revert;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Statement has no effect.");
+}
+
+BOOST_AUTO_TEST_CASE(bare_others)
+{
+ CHECK_WARNING("contract C { function f() { selfdestruct; } }", "Statement has no effect.");
+ CHECK_WARNING("contract C { function f() { assert; } }", "Statement has no effect.");
+ CHECK_WARNING("contract C { function f() { require; } }", "Statement has no effect.");
+ CHECK_WARNING("contract C { function f() { suicide; } }", "Statement has no effect.");
+}
+
+BOOST_AUTO_TEST_CASE(pure_statement_in_for_loop)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ for (uint x = 0; x < 10; true)
+ x++;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Statement has no effect.");
+}
+
+BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ for (uint x = 0; true; x++)
+ {}
+ }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint a; uint b; }
+ S x; S y;
+ function f() {
+ (x, y) = (y, x);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "This assignment performs two copies to storage.");
+}
+
+BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_right)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint a; uint b; }
+ S x; S y;
+ function f() {
+ (x, y, ) = (y, x, 1, 2);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "This assignment performs two copies to storage.");
+}
+
+BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_left)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint a; uint b; }
+ S x; S y;
+ function f() {
+ (,x, y) = (1, 2, y, x);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "This assignment performs two copies to storage.");
+}
+
+BOOST_AUTO_TEST_CASE(nowarn_swap_memory)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint a; uint b; }
+ function f() {
+ S memory x;
+ S memory y;
+ (x, y) = (y, x);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(nowarn_swap_storage_pointers)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint a; uint b; }
+ S x; S y;
+ function f() {
+ S storage x_local = x;
+ S storage y_local = y;
+ S storage z_local = x;
+ (x, y_local, x_local, z_local) = (y, x_local, y_local, y);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(warn_unused_local)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ uint a;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Unused");
+}
+
+BOOST_AUTO_TEST_CASE(warn_unused_local_assigned)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ uint a = 1;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Unused");
+}
+
+BOOST_AUTO_TEST_CASE(warn_unused_param)
+{
+ char const* text = R"(
+ contract C {
+ function f(uint a) {
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Unused");
+ text = R"(
+ contract C {
+ function f(uint a) {
+ }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(warn_unused_return_param)
+{
+ char const* text = R"(
+ contract C {
+ function f() returns (uint a) {
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Unused");
+ text = R"(
+ contract C {
+ function f() returns (uint a) {
+ return;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Unused");
+ text = R"(
+ contract C {
+ function f() returns (uint) {
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f() returns (uint a) {
+ a = 1;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f() returns (uint a) {
+ return 1;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(no_unused_warnings)
+{
+ char const* text = R"(
+ contract C {
+ function f(uint a) returns (uint b) {
+ uint c = 1;
+ b = a + c;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(no_unused_dec_after_use)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ a = 7;
+ uint a;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(no_unused_inline_asm)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ uint a;
+ assembly {
+ a := 1
+ }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(callable_crash)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint a; bool x; }
+ S public s;
+ function C() {
+ 3({a: 1, x: true});
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Type is not callable");
+}
+
+BOOST_AUTO_TEST_CASE(returndatacopy_as_variable)
+{
+ char const* text = R"(
+ contract c { function f() { uint returndatasize; assembly { returndatasize }}}
+ )";
+ CHECK_WARNING_ALLOW_MULTI(text, "Variable is shadowed in inline assembly by an instruction of the same name");
+}
+
+BOOST_AUTO_TEST_CASE(create2_as_variable)
+{
+ char const* text = R"(
+ contract c { function f() { uint create2; assembly { create2(0, 0, 0, 0) }}}
+ )";
+ CHECK_WARNING_ALLOW_MULTI(text, "Variable is shadowed in inline assembly by an instruction of the same name");
+}
+
+BOOST_AUTO_TEST_CASE(shadowing_warning_can_be_removed)
+{
+ char const* text = R"(
+ contract C {function f() {assembly {}}}
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(warn_unspecified_storage)
+{
+ char const* text = R"(
+ contract C {
+ struct S { uint a; string b; }
+ S x;
+ function f() {
+ S storage y = x;
+ y;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ struct S { uint a; }
+ S x;
+ function f() {
+ S y = x;
+ y;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "is declared as a storage pointer. Use an explicit \"storage\" keyword to silence this warning");
+}
+
+BOOST_AUTO_TEST_CASE(implicit_conversion_disallowed)
+{
+ char const* text = R"(
+ contract C {
+ function f() returns (bytes4) {
+ uint32 tmp = 1;
+ return tmp;
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Return argument type uint32 is not implicitly convertible to expected type (type of first return variable) bytes4.");
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp
index 85bc2277..2a7376b9 100644
--- a/test/libsolidity/SolidityNatspecJSON.cpp
+++ b/test/libsolidity/SolidityNatspecJSON.cpp
@@ -45,25 +45,25 @@ public:
bool _userDocumentation
)
{
- ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing failed");
+ ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parseAndAnalyze("pragma solidity >=0.0;\n" + _code), "Parsing failed");
Json::Value generatedDocumentation;
if (_userDocumentation)
- generatedDocumentation = m_compilerStack.metadata("", DocumentationType::NatspecUser);
+ generatedDocumentation = m_compilerStack.natspec("", DocumentationType::NatspecUser);
else
- generatedDocumentation = m_compilerStack.metadata("", DocumentationType::NatspecDev);
+ generatedDocumentation = m_compilerStack.natspec("", DocumentationType::NatspecDev);
Json::Value expectedDocumentation;
m_reader.parse(_expectedDocumentationString, expectedDocumentation);
BOOST_CHECK_MESSAGE(
expectedDocumentation == generatedDocumentation,
- "Expected " << expectedDocumentation.toStyledString() <<
+ "Expected:\n" << expectedDocumentation.toStyledString() <<
"\n but got:\n" << generatedDocumentation.toStyledString()
);
}
void expectNatspecError(std::string const& _code)
{
- BOOST_CHECK(!m_compilerStack.parse(_code));
+ BOOST_CHECK(!m_compilerStack.parseAndAnalyze(_code));
BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError));
}
@@ -76,10 +76,12 @@ BOOST_FIXTURE_TEST_SUITE(SolidityNatspecJSON, DocumentationChecker)
BOOST_AUTO_TEST_CASE(user_basic_test)
{
- char const* sourceCode = "contract test {\n"
- " /// @notice Multiplies `a` by 7\n"
- " function mul(uint a) returns(uint d) { return a * 7; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @notice Multiplies `a` by 7
+ function mul(uint a) returns(uint d) { return a * 7; }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -91,11 +93,13 @@ BOOST_AUTO_TEST_CASE(user_basic_test)
BOOST_AUTO_TEST_CASE(dev_and_user_basic_test)
{
- char const* sourceCode = "contract test {\n"
- " /// @notice Multiplies `a` by 7\n"
- " /// @dev Multiplies a number by 7\n"
- " function mul(uint a) returns(uint d) { return a * 7; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @notice Multiplies `a` by 7
+ /// @dev Multiplies a number by 7
+ function mul(uint a) returns(uint d) { return a * 7; }
+ }
+ )";
char const* devNatspec = "{"
"\"methods\":{"
@@ -116,14 +120,15 @@ BOOST_AUTO_TEST_CASE(dev_and_user_basic_test)
BOOST_AUTO_TEST_CASE(user_multiline_comment)
{
- char const* sourceCode = "contract test {\n"
- " /// @notice Multiplies `a` by 7\n"
- " /// and then adds `b`\n"
- " function mul_and_add(uint a, uint256 b) returns(uint256 d)\n"
- " {\n"
- " return (a * 7) + b;\n"
- " }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @notice Multiplies `a` by 7
+ /// and then adds `b`
+ function mul_and_add(uint a, uint256 b) returns(uint256 d) {
+ return (a * 7) + b;
+ }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -135,24 +140,24 @@ BOOST_AUTO_TEST_CASE(user_multiline_comment)
BOOST_AUTO_TEST_CASE(user_multiple_functions)
{
- char const* sourceCode = "contract test {\n"
- " /// @notice Multiplies `a` by 7 and then adds `b`\n"
- " function mul_and_add(uint a, uint256 b) returns(uint256 d)\n"
- " {\n"
- " return (a * 7) + b;\n"
- " }\n"
- "\n"
- " /// @notice Divides `input` by `div`\n"
- " function divide(uint input, uint div) returns(uint d)\n"
- " {\n"
- " return input / div;\n"
- " }\n"
- " /// @notice Subtracts 3 from `input`\n"
- " function sub(int input) returns(int d)\n"
- " {\n"
- " return input - 3;\n"
- " }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @notice Multiplies `a` by 7 and then adds `b`
+ function mul_and_add(uint a, uint256 b) returns(uint256 d) {
+ return (a * 7) + b;
+ }
+
+ /// @notice Divides `input` by `div`
+ function divide(uint input, uint div) returns(uint d) {
+ return input / div;
+ }
+
+ /// @notice Subtracts 3 from `input`
+ function sub(int input) returns(int d) {
+ return input - 3;
+ }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -166,8 +171,9 @@ BOOST_AUTO_TEST_CASE(user_multiple_functions)
BOOST_AUTO_TEST_CASE(user_empty_contract)
{
- char const* sourceCode = "contract test {\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test { }
+ )";
char const* natspec = "{\"methods\":{} }";
@@ -176,13 +182,16 @@ BOOST_AUTO_TEST_CASE(user_empty_contract)
BOOST_AUTO_TEST_CASE(dev_and_user_no_doc)
{
- char const* sourceCode = "contract test {\n"
- " function mul(uint a) returns(uint d) { return a * 7; }\n"
- " function sub(int input) returns(int d)\n"
- " {\n"
- " return input - 3;\n"
- " }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ function mul(uint a) returns(uint d) {
+ return a * 7;
+ }
+ function sub(int input) returns(int d) {
+ return input - 3;
+ }
+ }
+ )";
char const* devNatspec = "{\"methods\":{}}";
char const* userNatspec = "{\"methods\":{}}";
@@ -193,18 +202,20 @@ BOOST_AUTO_TEST_CASE(dev_and_user_no_doc)
BOOST_AUTO_TEST_CASE(dev_desc_after_nl)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev\n"
- " /// Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter\n"
- " /// @param second Documentation for the second parameter\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev
+ /// Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param second Documentation for the second parameter
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
" \"mul(uint256,uint256)\":{ \n"
- " \"details\": \" Multiplies a number by 7 and adds second parameter\",\n"
+ " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
" \"params\": {\n"
" \"a\": \"Documentation for the first parameter\",\n"
" \"second\": \"Documentation for the second parameter\"\n"
@@ -217,10 +228,35 @@ BOOST_AUTO_TEST_CASE(dev_desc_after_nl)
BOOST_AUTO_TEST_CASE(dev_multiple_params)
{
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param second Documentation for the second parameter
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
+
+ char const* natspec = "{"
+ "\"methods\":{"
+ " \"mul(uint256,uint256)\":{ \n"
+ " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
+ " \"params\": {\n"
+ " \"a\": \"Documentation for the first parameter\",\n"
+ " \"second\": \"Documentation for the second parameter\"\n"
+ " }\n"
+ " }\n"
+ "}}";
+
+ checkNatspec(sourceCode, natspec, false);
+}
+
+BOOST_AUTO_TEST_CASE(dev_multiple_params_mixed_whitespace)
+{
char const* sourceCode = "contract test {\n"
- " /// @dev Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter\n"
- " /// @param second Documentation for the second parameter\n"
+ " /// @dev Multiplies a number by 7 and adds second parameter\n"
+ " /// @param a Documentation for the first parameter\n"
+ " /// @param second Documentation for the second parameter\n"
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
"}\n";
@@ -240,13 +276,15 @@ BOOST_AUTO_TEST_CASE(dev_multiple_params)
BOOST_AUTO_TEST_CASE(dev_mutiline_param_description)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter starts here.\n"
- " /// Since it's a really complicated parameter we need 2 lines\n"
- " /// @param second Documentation for the second parameter\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter starts here.
+ /// Since it's a really complicated parameter we need 2 lines
+ /// @param second Documentation for the second parameter
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -264,26 +302,27 @@ BOOST_AUTO_TEST_CASE(dev_mutiline_param_description)
BOOST_AUTO_TEST_CASE(dev_multiple_functions)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter\n"
- " /// @param second Documentation for the second parameter\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- " \n"
- " /// @dev Divides 2 numbers\n"
- " /// @param input Documentation for the input parameter\n"
- " /// @param div Documentation for the div parameter\n"
- " function divide(uint input, uint div) returns(uint d)\n"
- " {\n"
- " return input / div;\n"
- " }\n"
- " /// @dev Subtracts 3 from `input`\n"
- " /// @param input Documentation for the input parameter\n"
- " function sub(int input) returns(int d)\n"
- " {\n"
- " return input - 3;\n"
- " }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param second Documentation for the second parameter
+ function mul(uint a, uint second) returns(uint d) {
+ return a * 7 + second;
+ }
+ /// @dev Divides 2 numbers
+ /// @param input Documentation for the input parameter
+ /// @param div Documentation for the div parameter
+ function divide(uint input, uint div) returns(uint d) {
+ return input / div;
+ }
+ /// @dev Subtracts 3 from `input`
+ /// @param input Documentation for the input parameter
+ function sub(int input) returns(int d) {
+ return input - 3;
+ }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -314,14 +353,16 @@ BOOST_AUTO_TEST_CASE(dev_multiple_functions)
BOOST_AUTO_TEST_CASE(dev_return)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter starts here.\n"
- " /// Since it's a really complicated parameter we need 2 lines\n"
- " /// @param second Documentation for the second parameter\n"
- " /// @return The result of the multiplication\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter starts here.
+ /// Since it's a really complicated parameter we need 2 lines
+ /// @param second Documentation for the second parameter
+ /// @return The result of the multiplication
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -339,15 +380,19 @@ BOOST_AUTO_TEST_CASE(dev_return)
}
BOOST_AUTO_TEST_CASE(dev_return_desc_after_nl)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter starts here.\n"
- " /// Since it's a really complicated parameter we need 2 lines\n"
- " /// @param second Documentation for the second parameter\n"
- " /// @return\n"
- " /// The result of the multiplication\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter starts here.
+ /// Since it's a really complicated parameter we need 2 lines
+ /// @param second Documentation for the second parameter
+ /// @return
+ /// The result of the multiplication
+ function mul(uint a, uint second) returns(uint d) {
+ return a * 7 + second;
+ }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -357,7 +402,7 @@ BOOST_AUTO_TEST_CASE(dev_return_desc_after_nl)
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n"
" \"second\": \"Documentation for the second parameter\"\n"
" },\n"
- " \"return\": \" The result of the multiplication\"\n"
+ " \"return\": \"The result of the multiplication\"\n"
" }\n"
"}}";
@@ -367,15 +412,19 @@ BOOST_AUTO_TEST_CASE(dev_return_desc_after_nl)
BOOST_AUTO_TEST_CASE(dev_multiline_return)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter starts here.\n"
- " /// Since it's a really complicated parameter we need 2 lines\n"
- " /// @param second Documentation for the second parameter\n"
- " /// @return The result of the multiplication\n"
- " /// and cookies with nutella\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter starts here.
+ /// Since it's a really complicated parameter we need 2 lines
+ /// @param second Documentation for the second parameter
+ /// @return The result of the multiplication
+ /// and cookies with nutella
+ function mul(uint a, uint second) returns(uint d) {
+ return a * 7 + second;
+ }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -394,17 +443,21 @@ BOOST_AUTO_TEST_CASE(dev_multiline_return)
BOOST_AUTO_TEST_CASE(dev_multiline_comment)
{
- char const* sourceCode = "contract test {\n"
- " /**\n"
- " * @dev Multiplies a number by 7 and adds second parameter\n"
- " * @param a Documentation for the first parameter starts here.\n"
- " * Since it's a really complicated parameter we need 2 lines\n"
- " * @param second Documentation for the second parameter\n"
- " * @return The result of the multiplication\n"
- " * and cookies with nutella\n"
- " */"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /**
+ * @dev Multiplies a number by 7 and adds second parameter
+ * @param a Documentation for the first parameter starts here.
+ * Since it's a really complicated parameter we need 2 lines
+ * @param second Documentation for the second parameter
+ * @return The result of the multiplication
+ * and cookies with nutella
+ */
+ function mul(uint a, uint second) returns(uint d) {
+ return a * 7 + second;
+ }
+ }
+ )";
char const* natspec = "{"
"\"methods\":{"
@@ -423,10 +476,12 @@ BOOST_AUTO_TEST_CASE(dev_multiline_comment)
BOOST_AUTO_TEST_CASE(dev_contract_no_doc)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev Mul function\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Mul function
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
char const* natspec = "{"
" \"methods\":{"
@@ -441,12 +496,14 @@ BOOST_AUTO_TEST_CASE(dev_contract_no_doc)
BOOST_AUTO_TEST_CASE(dev_contract_doc)
{
- char const* sourceCode = " /// @author Lefteris\n"
- " /// @title Just a test contract\n"
- "contract test {\n"
- " /// @dev Mul function\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ /// @author Lefteris
+ /// @title Just a test contract
+ contract test {
+ /// @dev Mul function
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
char const* natspec = "{"
" \"author\": \"Lefteris\","
@@ -463,13 +520,15 @@ BOOST_AUTO_TEST_CASE(dev_contract_doc)
BOOST_AUTO_TEST_CASE(dev_author_at_function)
{
- char const* sourceCode = " /// @author Lefteris\n"
- " /// @title Just a test contract\n"
- "contract test {\n"
- " /// @dev Mul function\n"
- " /// @author John Doe\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ /// @author Lefteris
+ /// @title Just a test contract
+ contract test {
+ /// @dev Mul function
+ /// @author John Doe
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
char const* natspec = "{"
" \"author\": \"Lefteris\","
@@ -549,25 +608,71 @@ BOOST_AUTO_TEST_CASE(empty_comment)
BOOST_AUTO_TEST_CASE(dev_title_at_function_error)
{
- char const* sourceCode = " /// @author Lefteris\n"
- " /// @title Just a test contract\n"
- "contract test {\n"
- " /// @dev Mul function\n"
- " /// @title I really should not be here\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ /// @author Lefteris
+ /// @title Just a test contract
+ contract test {
+ /// @dev Mul function
+ /// @title I really should not be here
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
expectNatspecError(sourceCode);
}
BOOST_AUTO_TEST_CASE(dev_documenting_nonexistent_param)
{
- char const* sourceCode = "contract test {\n"
- " /// @dev Multiplies a number by 7 and adds second parameter\n"
- " /// @param a Documentation for the first parameter\n"
- " /// @param not_existing Documentation for the second parameter\n"
- " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
- "}\n";
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param not_existing Documentation for the second parameter
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
+
+ expectNatspecError(sourceCode);
+}
+
+BOOST_AUTO_TEST_CASE(dev_documenting_no_paramname)
+{
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
+
+ expectNatspecError(sourceCode);
+}
+
+BOOST_AUTO_TEST_CASE(dev_documenting_no_paramname_end)
+{
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param se
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
+
+ expectNatspecError(sourceCode);
+}
+
+BOOST_AUTO_TEST_CASE(dev_documenting_no_param_description)
+{
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param second
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
expectNatspecError(sourceCode);
}
diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp
index 2e2e0c6c..a4d80c99 100644
--- a/test/libsolidity/SolidityOptimizer.cpp
+++ b/test/libsolidity/SolidityOptimizer.cpp
@@ -31,6 +31,7 @@
#include <boost/test/unit_test.hpp>
#include <boost/lexical_cast.hpp>
+#include <chrono>
#include <string>
#include <tuple>
#include <memory>
@@ -73,16 +74,17 @@ public:
void compileBothVersions(
std::string const& _sourceCode,
u256 const& _value = 0,
- std::string const& _contractName = ""
+ std::string const& _contractName = "",
+ unsigned const _optimizeRuns = 200
)
{
- bytes nonOptimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, false);
+ bytes nonOptimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, false, _optimizeRuns);
m_nonOptimizedContract = m_contractAddress;
- bytes optimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, true);
+ bytes optimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, true, _optimizeRuns);
size_t nonOptimizedSize = numInstructions(nonOptimizedBytecode);
size_t optimizedSize = numInstructions(optimizedBytecode);
BOOST_CHECK_MESSAGE(
- optimizedSize < nonOptimizedSize,
+ _optimizeRuns < 50 || optimizedSize < nonOptimizedSize,
string("Optimizer did not reduce bytecode size. Non-optimized size: ") +
std::to_string(nonOptimizedSize) + " - optimized size: " +
std::to_string(optimizedSize)
@@ -320,18 +322,18 @@ BOOST_AUTO_TEST_CASE(storage_write_in_loops)
// Information in joining branches is not retained anymore.
BOOST_AUTO_TEST_CASE(retain_information_in_branches)
{
- // This tests that the optimizer knows that we already have "z == sha3(y)" inside both branches.
+ // This tests that the optimizer knows that we already have "z == keccak256(y)" inside both branches.
char const* sourceCode = R"(
contract c {
bytes32 d;
uint a;
function f(uint x, bytes32 y) returns (uint r_a, bytes32 r_d) {
- bytes32 z = sha3(y);
+ bytes32 z = keccak256(y);
if (x > 8) {
- z = sha3(y);
+ z = keccak256(y);
a = x;
} else {
- z = sha3(y);
+ z = keccak256(y);
a = x;
}
r_a = a;
@@ -347,7 +349,7 @@ BOOST_AUTO_TEST_CASE(retain_information_in_branches)
bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "c", true);
size_t numSHA3s = 0;
eachInstruction(optimizedBytecode, [&](Instruction _instr, u256 const&) {
- if (_instr == Instruction::SHA3)
+ if (_instr == Instruction::KECCAK256)
numSHA3s++;
});
// TEST DISABLED - OPTIMIZER IS NOT EFFECTIVE ON THIS ONE ANYMORE
@@ -356,7 +358,7 @@ BOOST_AUTO_TEST_CASE(retain_information_in_branches)
BOOST_AUTO_TEST_CASE(store_tags_as_unions)
{
- // This calls the same function from two sources and both calls have a certain sha3 on
+ // This calls the same function from two sources and both calls have a certain Keccak-256 on
// the stack at the same position.
// Without storing tags as unions, the return from the shared function would not know where to
// jump and thus all jumpdests are forced to clear their state and we do not know about the
@@ -368,19 +370,19 @@ BOOST_AUTO_TEST_CASE(store_tags_as_unions)
contract test {
bytes32 data;
function f(uint x, bytes32 y) external returns (uint r_a, bytes32 r_d) {
- r_d = sha3(y);
+ r_d = keccak256(y);
shared(y);
- r_d = sha3(y);
+ r_d = keccak256(y);
r_a = 5;
}
function g(uint x, bytes32 y) external returns (uint r_a, bytes32 r_d) {
- r_d = sha3(y);
+ r_d = keccak256(y);
shared(y);
- r_d = bytes32(uint(sha3(y)) + 2);
+ r_d = bytes32(uint(keccak256(y)) + 2);
r_a = 7;
}
function shared(bytes32 y) internal {
- data = sha3(y);
+ data = keccak256(y);
}
}
)";
@@ -390,7 +392,7 @@ BOOST_AUTO_TEST_CASE(store_tags_as_unions)
bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "test", true);
size_t numSHA3s = 0;
eachInstruction(optimizedBytecode, [&](Instruction _instr, u256 const&) {
- if (_instr == Instruction::SHA3)
+ if (_instr == Instruction::KECCAK256)
numSHA3s++;
});
// TEST DISABLED UNTIL 93693404 IS IMPLEMENTED
@@ -399,8 +401,8 @@ BOOST_AUTO_TEST_CASE(store_tags_as_unions)
BOOST_AUTO_TEST_CASE(incorrect_storage_access_bug)
{
- // This bug appeared because a sha3 operation with too low sequence number was used,
- // resulting in memory not being rewritten before the sha3. The fix was to
+ // This bug appeared because a Keccak-256 operation with too low sequence number was used,
+ // resulting in memory not being rewritten before the Keccak-256. The fix was to
// take the max of the min sequence numbers when merging the states.
char const* sourceCode = R"(
contract C
@@ -695,59 +697,6 @@ BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location_offset)
});
}
-BOOST_AUTO_TEST_CASE(cse_interleaved_memory_at_known_location_offset)
-{
- // stores and reads to/from two locations which are known to be different,
- // should not optimize away the first store, because the location overlaps with the load,
- // but it should optimize away the second, because we know that the location is different by 32
- AssemblyItems input{
- u256(0x50),
- Instruction::DUP2,
- u256(2),
- Instruction::ADD,
- Instruction::MSTORE, // ["DUP1"+2] = 0x50
- u256(0x60),
- Instruction::DUP2,
- u256(32),
- Instruction::ADD,
- Instruction::MSTORE, // ["DUP1"+32] = 0x60
- Instruction::DUP1,
- Instruction::MLOAD, // read from "DUP1"
- u256(0x70),
- Instruction::DUP3,
- u256(32),
- Instruction::ADD,
- Instruction::MSTORE, // ["DUP1"+32] = 0x70
- u256(0x80),
- Instruction::DUP3,
- u256(2),
- Instruction::ADD,
- Instruction::MSTORE, // ["DUP1"+2] = 0x80
- };
- // If the actual code changes too much, we could also simply check that the output contains
- // exactly 3 MSTORE and exactly 1 MLOAD instruction.
- checkCSE(input, {
- u256(0x50),
- u256(2),
- Instruction::DUP3,
- Instruction::ADD,
- Instruction::SWAP1,
- Instruction::DUP2,
- Instruction::MSTORE, // ["DUP1"+2] = 0x50
- Instruction::DUP2,
- Instruction::MLOAD, // read from "DUP1"
- u256(0x70),
- u256(32),
- Instruction::DUP5,
- Instruction::ADD,
- Instruction::MSTORE, // ["DUP1"+32] = 0x70
- u256(0x80),
- Instruction::SWAP1,
- Instruction::SWAP2,
- Instruction::MSTORE // ["DUP1"+2] = 0x80
- });
-}
-
BOOST_AUTO_TEST_CASE(cse_deep_stack)
{
AssemblyItems input{
@@ -819,19 +768,19 @@ BOOST_AUTO_TEST_CASE(cse_jumpi_jump)
});
}
-BOOST_AUTO_TEST_CASE(cse_empty_sha3)
+BOOST_AUTO_TEST_CASE(cse_empty_keccak256)
{
AssemblyItems input{
u256(0),
Instruction::DUP2,
- Instruction::SHA3
+ Instruction::KECCAK256
};
checkCSE(input, {
u256(dev::keccak256(bytesConstRef()))
});
}
-BOOST_AUTO_TEST_CASE(cse_partial_sha3)
+BOOST_AUTO_TEST_CASE(cse_partial_keccak256)
{
AssemblyItems input{
u256(0xabcd) << (256 - 16),
@@ -839,7 +788,7 @@ BOOST_AUTO_TEST_CASE(cse_partial_sha3)
Instruction::MSTORE,
u256(2),
u256(0),
- Instruction::SHA3
+ Instruction::KECCAK256
};
checkCSE(input, {
u256(0xabcd) << (256 - 16),
@@ -849,19 +798,19 @@ BOOST_AUTO_TEST_CASE(cse_partial_sha3)
});
}
-BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_location)
+BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_location)
{
- // sha3 twice from same dynamic location
+ // Keccak-256 twice from same dynamic location
AssemblyItems input{
Instruction::DUP2,
Instruction::DUP1,
Instruction::MSTORE,
u256(64),
Instruction::DUP2,
- Instruction::SHA3,
+ Instruction::KECCAK256,
u256(64),
Instruction::DUP3,
- Instruction::SHA3
+ Instruction::KECCAK256
};
checkCSE(input, {
Instruction::DUP2,
@@ -869,27 +818,27 @@ BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_location)
Instruction::MSTORE,
u256(64),
Instruction::DUP2,
- Instruction::SHA3,
+ Instruction::KECCAK256,
Instruction::DUP1
});
}
-BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content)
+BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content)
{
- // sha3 twice from different dynamic location but with same content
+ // Keccak-256 twice from different dynamic location but with same content
AssemblyItems input{
Instruction::DUP1,
u256(0x80),
Instruction::MSTORE, // m[128] = DUP1
u256(0x20),
u256(0x80),
- Instruction::SHA3, // sha3(m[128..(128+32)])
+ Instruction::KECCAK256, // keccak256(m[128..(128+32)])
Instruction::DUP2,
u256(12),
Instruction::MSTORE, // m[12] = DUP1
u256(0x20),
u256(12),
- Instruction::SHA3 // sha3(m[12..(12+32)])
+ Instruction::KECCAK256 // keccak256(m[12..(12+32)])
};
checkCSE(input, {
u256(0x80),
@@ -898,7 +847,7 @@ BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content)
Instruction::MSTORE,
u256(0x20),
Instruction::SWAP1,
- Instruction::SHA3,
+ Instruction::KECCAK256,
u256(12),
Instruction::DUP3,
Instruction::SWAP1,
@@ -907,10 +856,10 @@ BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content)
});
}
-BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_dynamic_store_in_between)
+BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_dynamic_store_in_between)
{
- // sha3 twice from different dynamic location but with same content,
- // dynamic mstore in between, which forces us to re-calculate the sha3
+ // Keccak-256 twice from different dynamic location but with same content,
+ // dynamic mstore in between, which forces us to re-calculate the hash
AssemblyItems input{
u256(0x80),
Instruction::DUP2,
@@ -919,7 +868,7 @@ BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_dynamic_store_in_between)
u256(0x20),
Instruction::DUP1,
Instruction::DUP3,
- Instruction::SHA3, // sha3(m[128..(128+32)])
+ Instruction::KECCAK256, // keccak256(m[128..(128+32)])
u256(12),
Instruction::DUP5,
Instruction::DUP2,
@@ -930,15 +879,15 @@ BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_dynamic_store_in_between)
Instruction::SWAP2,
Instruction::SWAP1,
Instruction::SWAP2,
- Instruction::SHA3 // sha3(m[12..(12+32)])
+ Instruction::KECCAK256 // keccak256(m[12..(12+32)])
};
checkCSE(input, input);
}
-BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_noninterfering_store_in_between)
+BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_noninterfering_store_in_between)
{
- // sha3 twice from different dynamic location but with same content,
- // dynamic mstore in between, but does not force us to re-calculate the sha3
+ // Keccak-256 twice from different dynamic location but with same content,
+ // dynamic mstore in between, but does not force us to re-calculate the hash
AssemblyItems input{
u256(0x80),
Instruction::DUP2,
@@ -947,7 +896,7 @@ BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_noninterfering_store_in_between
u256(0x20),
Instruction::DUP1,
Instruction::DUP3,
- Instruction::SHA3, // sha3(m[128..(128+32)])
+ Instruction::KECCAK256, // keccak256(m[128..(128+32)])
u256(12),
Instruction::DUP5,
Instruction::DUP2,
@@ -960,12 +909,12 @@ BOOST_AUTO_TEST_CASE(cse_sha3_twice_same_content_noninterfering_store_in_between
Instruction::MSTORE, // does not destoy memory knowledge
u256(0x20),
u256(12),
- Instruction::SHA3 // sha3(m[12..(12+32)])
+ Instruction::KECCAK256 // keccak256(m[12..(12+32)])
};
// if this changes too often, only count the number of SHA3 and MSTORE instructions
AssemblyItems output = CSE(input);
BOOST_CHECK_EQUAL(4, count(output.begin(), output.end(), AssemblyItem(Instruction::MSTORE)));
- BOOST_CHECK_EQUAL(1, count(output.begin(), output.end(), AssemblyItem(Instruction::SHA3)));
+ BOOST_CHECK_EQUAL(1, count(output.begin(), output.end(), AssemblyItem(Instruction::KECCAK256)));
}
BOOST_AUTO_TEST_CASE(cse_with_initially_known_stack)
@@ -1187,34 +1136,71 @@ BOOST_AUTO_TEST_CASE(clear_unreachable_code)
);
}
+BOOST_AUTO_TEST_CASE(peephole_double_push)
+{
+ AssemblyItems items{
+ u256(0),
+ u256(0),
+ u256(5),
+ u256(5),
+ u256(4),
+ u256(5)
+ };
+ AssemblyItems expectation{
+ u256(0),
+ Instruction::DUP1,
+ u256(5),
+ Instruction::DUP1,
+ u256(4),
+ u256(5)
+ };
+ PeepholeOptimiser peepOpt(items);
+ BOOST_REQUIRE(peepOpt.optimise());
+ BOOST_CHECK_EQUAL_COLLECTIONS(
+ items.begin(), items.end(),
+ expectation.begin(), expectation.end()
+ );
+}
+
BOOST_AUTO_TEST_CASE(computing_constants)
{
char const* sourceCode = R"(
- contract c {
- uint a;
- uint b;
- uint c;
- function set() returns (uint a, uint b, uint c) {
- a = 0x77abc0000000000000000000000000000000000000000000000000000000001;
- b = 0x817416927846239487123469187231298734162934871263941234127518276;
+ contract C {
+ uint m_a;
+ uint m_b;
+ uint m_c;
+ uint m_d;
+ function C() {
+ set();
+ }
+ function set() returns (uint) {
+ m_a = 0x77abc0000000000000000000000000000000000000000000000000000000001;
+ m_b = 0x817416927846239487123469187231298734162934871263941234127518276;
g();
+ return 1;
}
function g() {
- b = 0x817416927846239487123469187231298734162934871263941234127518276;
- c = 0x817416927846239487123469187231298734162934871263941234127518276;
+ m_b = 0x817416927846239487123469187231298734162934871263941234127518276;
+ m_c = 0x817416927846239487123469187231298734162934871263941234127518276;
+ h();
+ }
+ function h() {
+ m_d = 0xff05694900000000000000000000000000000000000000000000000000000000;
}
- function get() returns (uint ra, uint rb, uint rc) {
- ra = a;
- rb = b;
- rc = c ;
+ function get() returns (uint ra, uint rb, uint rc, uint rd) {
+ ra = m_a;
+ rb = m_b;
+ rc = m_c;
+ rd = m_d;
}
}
)";
- compileBothVersions(sourceCode);
+ compileBothVersions(sourceCode, 0, "C", 1);
+ compareVersions("get()");
compareVersions("set()");
compareVersions("get()");
- bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "c", true, 1);
+ bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "C", true, 1);
bytes complicatedConstant = toBigEndian(u256("0x817416927846239487123469187231298734162934871263941234127518276"));
unsigned occurrences = 0;
for (auto iter = optimizedBytecode.cbegin(); iter < optimizedBytecode.cend(); ++occurrences)
@@ -1234,6 +1220,67 @@ BOOST_AUTO_TEST_CASE(computing_constants)
) == optimizedBytecode.cend());
}
+
+BOOST_AUTO_TEST_CASE(constant_optimization_early_exit)
+{
+ // This tests that the constant optimizer does not try to find the best representation
+ // indefinitely but instead stops after some number of iterations.
+ char const* sourceCode = R"(
+ pragma solidity ^0.4.0;
+
+ contract HexEncoding {
+ function hexEncodeTest(address addr) returns (bytes32 ret) {
+ uint x = uint(addr) / 2**32;
+
+ // Nibble interleave
+ x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
+ x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
+ x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
+ x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
+ x = (x | (x * 2** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
+ x = (x | (x * 2** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
+
+ // Hex encode
+ uint h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
+ uint i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
+ uint j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
+ x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;
+
+ // Store and load next batch
+ assembly {
+ mstore(0, x)
+ }
+ x = uint(addr) * 2**96;
+
+ // Nibble interleave
+ x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
+ x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
+ x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
+ x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
+ x = (x | (x * 2** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
+ x = (x | (x * 2** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
+
+ // Hex encode
+ h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
+ i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
+ j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
+ x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;
+
+ // Store and hash
+ assembly {
+ mstore(32, x)
+ ret := keccak256(0, 40)
+ }
+ }
+ }
+ )";
+ auto start = std::chrono::steady_clock::now();
+ compileBothVersions(sourceCode);
+ double duration = std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
+ BOOST_CHECK_MESSAGE(duration < 20, "Compilation of constants took longer than 20 seconds.");
+ compareVersions("hexEncodeTest(address)", u256(0x123456789));
+}
+
BOOST_AUTO_TEST_CASE(inconsistency)
{
// This is a test of a bug in the optimizer.
diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp
index 5e3c69d2..78edd4d1 100644
--- a/test/libsolidity/SolidityParser.cpp
+++ b/test/libsolidity/SolidityParser.cpp
@@ -24,8 +24,9 @@
#include <memory>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/parsing/Parser.h>
-#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include "../TestHelper.h"
+#include "ErrorCheck.h"
using namespace std;
@@ -40,7 +41,8 @@ namespace
{
ASTPointer<ContractDefinition> parseText(std::string const& _source, ErrorList& _errors)
{
- ASTPointer<SourceUnit> sourceUnit = Parser(_errors).parse(std::make_shared<Scanner>(CharStream(_source)));
+ ErrorReporter errorReporter(_errors);
+ ASTPointer<SourceUnit> sourceUnit = Parser(errorReporter).parse(std::make_shared<Scanner>(CharStream(_source)));
if (!sourceUnit)
return ASTPointer<ContractDefinition>();
for (ASTPointer<ASTNode> const& node: sourceUnit->nodes())
@@ -71,6 +73,22 @@ bool successParse(std::string const& _source)
return true;
}
+Error getError(std::string const& _source)
+{
+ ErrorList errors;
+ try
+ {
+ parseText(_source, errors);
+ }
+ catch (FatalError const& /*_exception*/)
+ {
+ // no-op
+ }
+ Error const* error = Error::containsErrorOfType(errors, Error::Type::ParserError);
+ BOOST_REQUIRE(error);
+ return *error;
+}
+
void checkFunctionNatspec(
FunctionDefinition const* _function,
std::string const& _expectedDoc
@@ -83,6 +101,14 @@ void checkFunctionNatspec(
}
+#define CHECK_PARSE_ERROR(source, substring) \
+do \
+{\
+ Error err = getError((source)); \
+ BOOST_CHECK(searchErrorMessage(err, (substring))); \
+}\
+while(0)
+
BOOST_AUTO_TEST_SUITE(SolidityParser)
@@ -103,7 +129,7 @@ BOOST_AUTO_TEST_CASE(missing_variable_name_in_declaration)
uint256 ;
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected identifier");
}
BOOST_AUTO_TEST_CASE(empty_function)
@@ -159,7 +185,7 @@ BOOST_AUTO_TEST_CASE(missing_parameter_name_in_named_args)
function b() returns (uint r) { r = a({: 1, : 2, : 3}); }
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected identifier");
}
BOOST_AUTO_TEST_CASE(missing_argument_in_named_args)
@@ -170,7 +196,18 @@ BOOST_AUTO_TEST_CASE(missing_argument_in_named_args)
function b() returns (uint r) { r = a({a: , b: , c: }); }
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected primary expression");
+}
+
+BOOST_AUTO_TEST_CASE(trailing_comma_in_named_args)
+{
+ char const* text = R"(
+ contract test {
+ function a(uint a, uint b, uint c) returns (uint r) { r = a * 100 + b * 10 + c * 1; }
+ function b() returns (uint r) { r = a({a: 1, b: 2, c: 3, }); }
+ }
+ )";
+ CHECK_PARSE_ERROR(text, "Unexpected trailing comma");
}
BOOST_AUTO_TEST_CASE(two_exact_functions)
@@ -463,7 +500,7 @@ BOOST_AUTO_TEST_CASE(variable_definition_in_function_parameter)
function fun(var a) {}
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected explicit type name");
}
BOOST_AUTO_TEST_CASE(variable_definition_in_mapping)
@@ -475,7 +512,7 @@ BOOST_AUTO_TEST_CASE(variable_definition_in_mapping)
}
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected elementary type name for mapping key type");
}
BOOST_AUTO_TEST_CASE(variable_definition_in_function_return)
@@ -487,7 +524,7 @@ BOOST_AUTO_TEST_CASE(variable_definition_in_function_return)
}
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected explicit type name");
}
BOOST_AUTO_TEST_CASE(operator_expression)
@@ -777,14 +814,14 @@ BOOST_AUTO_TEST_CASE(modifier_without_semicolon)
modifier mod { if (msg.sender == 0) _ }
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected token Semicolon got");
}
BOOST_AUTO_TEST_CASE(modifier_arguments)
{
char const* text = R"(
contract c {
- modifier mod(uint a) { if (msg.sender == a) _; }
+ modifier mod(address a) { if (msg.sender == a) _; }
}
)";
BOOST_CHECK(successParse(text));
@@ -861,7 +898,25 @@ BOOST_AUTO_TEST_CASE(multiple_visibility_specifiers)
contract c {
uint private internal a;
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Visibility already specified");
+}
+
+BOOST_AUTO_TEST_CASE(multiple_payable_specifiers)
+{
+ char const* text = R"(
+ contract c {
+ function f() payable payable {}
+ })";
+ CHECK_PARSE_ERROR(text, "Multiple \"payable\" specifiers.");
+}
+
+BOOST_AUTO_TEST_CASE(multiple_constant_specifiers)
+{
+ char const* text = R"(
+ contract c {
+ function f() constant constant {}
+ })";
+ CHECK_PARSE_ERROR(text, "Multiple \"constant\" specifiers.");
}
BOOST_AUTO_TEST_CASE(literal_constants_with_ether_subdenominations)
@@ -916,7 +971,7 @@ BOOST_AUTO_TEST_CASE(empty_enum_declaration)
contract c {
enum foo { }
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "enum with no members is not allowed");
}
BOOST_AUTO_TEST_CASE(malformed_enum_declaration)
@@ -925,7 +980,7 @@ BOOST_AUTO_TEST_CASE(malformed_enum_declaration)
contract c {
enum foo { WARNING,}
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected Identifier after");
}
BOOST_AUTO_TEST_CASE(external_function)
@@ -943,7 +998,7 @@ BOOST_AUTO_TEST_CASE(external_variable)
contract c {
uint external x;
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected identifier");
}
BOOST_AUTO_TEST_CASE(arrays_in_storage)
@@ -991,7 +1046,7 @@ BOOST_AUTO_TEST_CASE(constant_is_keyword)
contract Foo {
uint constant = 4;
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected identifier");
}
BOOST_AUTO_TEST_CASE(var_array)
@@ -1000,7 +1055,7 @@ BOOST_AUTO_TEST_CASE(var_array)
contract Foo {
function f() { var[] a; }
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected identifier");
}
BOOST_AUTO_TEST_CASE(location_specifiers_for_params)
@@ -1032,7 +1087,7 @@ BOOST_AUTO_TEST_CASE(location_specifiers_for_state)
contract Foo {
uint[] memory x;
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected identifier");
}
BOOST_AUTO_TEST_CASE(location_specifiers_with_var)
@@ -1041,7 +1096,7 @@ BOOST_AUTO_TEST_CASE(location_specifiers_with_var)
contract Foo {
function f() { var memory x; }
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Location specifier needs explicit type name");
}
BOOST_AUTO_TEST_CASE(empty_comment)
@@ -1088,7 +1143,7 @@ BOOST_AUTO_TEST_CASE(local_const_variable)
return local;
}
})";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected token Semicolon");
}
BOOST_AUTO_TEST_CASE(multi_variable_declaration)
@@ -1207,7 +1262,7 @@ BOOST_AUTO_TEST_CASE(inline_array_empty_cells_check_lvalue)
}
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected expression");
}
BOOST_AUTO_TEST_CASE(inline_array_empty_cells_check_without_lvalue)
@@ -1220,7 +1275,7 @@ BOOST_AUTO_TEST_CASE(inline_array_empty_cells_check_without_lvalue)
}
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected expression");
}
BOOST_AUTO_TEST_CASE(conditional_true_false_literal)
@@ -1321,7 +1376,7 @@ BOOST_AUTO_TEST_CASE(no_double_radix_in_fixed_literal)
fixed40x40 pi = 3.14.15;
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected token Semicolon");
}
BOOST_AUTO_TEST_CASE(invalid_fixed_conversion_leading_zeroes_check)
@@ -1333,7 +1388,7 @@ BOOST_AUTO_TEST_CASE(invalid_fixed_conversion_leading_zeroes_check)
}
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected primary expression");
}
BOOST_AUTO_TEST_CASE(payable_accessor)
@@ -1343,7 +1398,7 @@ BOOST_AUTO_TEST_CASE(payable_accessor)
uint payable x;
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected identifier");
}
BOOST_AUTO_TEST_CASE(function_type_in_expression)
@@ -1376,7 +1431,7 @@ BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_modifiers)
function (uint, uint) modifier1() returns (uint) f1;
}
)";
- BOOST_CHECK(!successParse(text));
+ CHECK_PARSE_ERROR(text, "Expected token LBrace");
}
BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_assignment)
@@ -1454,6 +1509,29 @@ BOOST_AUTO_TEST_CASE(function_type_state_variable)
BOOST_CHECK(successParse(text));
}
+BOOST_AUTO_TEST_CASE(scientific_notation)
+{
+ char const* text = R"(
+ contract test {
+ uint256 a = 2e10;
+ uint256 b = 2E10;
+ uint256 c = 200e-2;
+ uint256 d = 2E10 wei;
+ uint256 e = 2.5e10;
+ }
+ )";
+ BOOST_CHECK(successParse(text));
+}
+
+BOOST_AUTO_TEST_CASE(interface)
+{
+ char const* text = R"(
+ interface Interface {
+ function f();
+ }
+ )";
+ BOOST_CHECK(successParse(text));
+}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/libsolidity/SolidityScanner.cpp b/test/libsolidity/SolidityScanner.cpp
index eb2f042c..020bce7f 100644
--- a/test/libsolidity/SolidityScanner.cpp
+++ b/test/libsolidity/SolidityScanner.cpp
@@ -97,9 +97,39 @@ BOOST_AUTO_TEST_CASE(hex_numbers)
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
}
+BOOST_AUTO_TEST_CASE(octal_numbers)
+{
+ Scanner scanner(CharStream("07"));
+ BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Illegal);
+ scanner.reset(CharStream("007"), "");
+ BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Illegal);
+ scanner.reset(CharStream("-07"), "");
+ BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Sub);
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Illegal);
+ scanner.reset(CharStream("-.07"), "");
+ BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Sub);
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
+ scanner.reset(CharStream("0"), "");
+ BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number);
+ scanner.reset(CharStream("0.1"), "");
+ BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Number);
+}
+
+BOOST_AUTO_TEST_CASE(scientific_notation)
+{
+ Scanner scanner(CharStream("var x = 2e10;"));
+ BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var);
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Assign);
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
+ BOOST_CHECK_EQUAL(scanner.currentLiteral(), "2e10");
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon);
+ BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
+}
+
BOOST_AUTO_TEST_CASE(negative_numbers)
{
- Scanner scanner(CharStream("var x = -.2 + -0x78 + -7.3 + 8.9;"));
+ Scanner scanner(CharStream("var x = -.2 + -0x78 + -7.3 + 8.9 + 2e-2;"));
BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Var);
BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier);
BOOST_CHECK_EQUAL(scanner.next(), Token::Assign);
@@ -117,6 +147,9 @@ BOOST_AUTO_TEST_CASE(negative_numbers)
BOOST_CHECK_EQUAL(scanner.next(), Token::Add);
BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
BOOST_CHECK_EQUAL(scanner.currentLiteral(), "8.9");
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Add);
+ BOOST_CHECK_EQUAL(scanner.next(), Token::Number);
+ BOOST_CHECK_EQUAL(scanner.currentLiteral(), "2e-2");
BOOST_CHECK_EQUAL(scanner.next(), Token::Semicolon);
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
}
diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp
index dc3143c8..0b5ab516 100644
--- a/test/libsolidity/SolidityTypes.cpp
+++ b/test/libsolidity/SolidityTypes.cpp
@@ -21,6 +21,8 @@
*/
#include <libsolidity/ast/Types.h>
+#include <libsolidity/ast/AST.h>
+#include <libdevcore/SHA3.h>
#include <boost/test/unit_test.hpp>
using namespace std;
@@ -86,6 +88,71 @@ BOOST_AUTO_TEST_CASE(storage_layout_arrays)
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(32), 9).storageSize() == 9);
}
+BOOST_AUTO_TEST_CASE(type_identifiers)
+{
+ ASTNode::resetID();
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint128")->identifier(), "t_uint128");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("int128")->identifier(), "t_int128");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("address")->identifier(), "t_address");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint8")->identifier(), "t_uint8");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("ufixed8x64")->identifier(), "t_ufixed8x64");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("fixed128x8")->identifier(), "t_fixed128x8");
+ BOOST_CHECK_EQUAL(RationalNumberType(rational(7, 1)).identifier(), "t_rational_7_by_1");
+ BOOST_CHECK_EQUAL(RationalNumberType(rational(200, 77)).identifier(), "t_rational_200_by_77");
+ BOOST_CHECK_EQUAL(RationalNumberType(rational(2 * 200, 2 * 77)).identifier(), "t_rational_200_by_77");
+ BOOST_CHECK_EQUAL(
+ StringLiteralType(Literal(SourceLocation{}, Token::StringLiteral, make_shared<string>("abc - def"))).identifier(),
+ "t_stringliteral_196a9142ee0d40e274a6482393c762b16dd8315713207365e1e13d8d85b74fc4"
+ );
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes8")->identifier(), "t_bytes8");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes32")->identifier(), "t_bytes32");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bool")->identifier(), "t_bool");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes")->identifier(), "t_bytes_storage_ptr");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string")->identifier(), "t_string_storage_ptr");
+ ArrayType largeintArray(DataLocation::Memory, Type::fromElementaryTypeName("int128"), u256("2535301200456458802993406410752"));
+ BOOST_CHECK_EQUAL(largeintArray.identifier(), "t_array$_t_int128_$2535301200456458802993406410752_memory_ptr");
+ TypePointer stringArray = make_shared<ArrayType>(DataLocation::Storage, Type::fromElementaryTypeName("string"), u256("20"));
+ TypePointer multiArray = make_shared<ArrayType>(DataLocation::Storage, stringArray);
+ BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr");
+
+ ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, ContractDefinition::ContractKind::Contract);
+ BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$");
+ BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2");
+
+ StructDefinition s({}, make_shared<string>("Struct"), {});
+ BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type$_t_struct$_Struct_$3_storage_ptr_$");
+
+ EnumDefinition e({}, make_shared<string>("Enum"), {});
+ BOOST_CHECK_EQUAL(e.type()->identifier(), "t_type$_t_enum$_Enum_$4_$");
+
+ TupleType t({e.type(), s.type(), stringArray, nullptr});
+ BOOST_CHECK_EQUAL(t.identifier(), "t_tuple$_t_type$_t_enum$_Enum_$4_$_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_t_array$_t_string_storage_$20_storage_ptr_$__$");
+
+ TypePointer sha3fun = make_shared<FunctionType>(strings{}, strings{}, FunctionType::Kind::SHA3);
+ BOOST_CHECK_EQUAL(sha3fun->identifier(), "t_function_sha3$__$returns$__$");
+
+ FunctionType metaFun(TypePointers{sha3fun}, TypePointers{s.type()});
+ BOOST_CHECK_EQUAL(metaFun.identifier(), "t_function_internal$_t_function_sha3$__$returns$__$_$returns$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$");
+
+ TypePointer m = make_shared<MappingType>(Type::fromElementaryTypeName("bytes32"), s.type());
+ MappingType m2(Type::fromElementaryTypeName("uint64"), m);
+ BOOST_CHECK_EQUAL(m2.identifier(), "t_mapping$_t_uint64_$_t_mapping$_t_bytes32_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_$");
+
+ // TypeType is tested with contract
+
+ auto emptyParams = make_shared<ParameterList>(SourceLocation(), std::vector<ASTPointer<VariableDeclaration>>());
+ ModifierDefinition mod(SourceLocation{}, make_shared<string>("modif"), {}, emptyParams, {});
+ BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier$__$");
+
+ SourceUnit su({}, {});
+ BOOST_CHECK_EQUAL(ModuleType(su).identifier(), "t_module_7");
+ BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Block).identifier(), "t_magic_block");
+ BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Message).identifier(), "t_magic_message");
+ BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Transaction).identifier(), "t_magic_transaction");
+
+ BOOST_CHECK_EQUAL(InaccessibleDynamicType().identifier(), "t_inaccessible");
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp
new file mode 100644
index 00000000..be13d46b
--- /dev/null
+++ b/test/libsolidity/StandardCompiler.cpp
@@ -0,0 +1,235 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Unit tests for interface/StandardCompiler.h.
+ */
+
+#include <string>
+#include <iostream>
+#include <regex>
+#include <boost/test/unit_test.hpp>
+#include <libsolidity/interface/StandardCompiler.h>
+#include <libdevcore/JSON.h>
+
+#include "../Metadata.h"
+
+using namespace std;
+using namespace dev::eth;
+
+namespace dev
+{
+namespace solidity
+{
+namespace test
+{
+
+namespace
+{
+
+/// Helper to match a specific error type and message
+bool containsError(Json::Value const& _compilerResult, string const& _type, string const& _message)
+{
+ if (!_compilerResult.isMember("errors"))
+ return false;
+
+ for (auto const& error: _compilerResult["errors"])
+ {
+ BOOST_REQUIRE(error.isObject());
+ BOOST_REQUIRE(error["type"].isString());
+ BOOST_REQUIRE(error["message"].isString());
+ if ((error["type"].asString() == _type) && (error["message"].asString() == _message))
+ return true;
+ }
+
+ return false;
+}
+
+bool containsAtMostWarnings(Json::Value const& _compilerResult)
+{
+ if (!_compilerResult.isMember("errors"))
+ return true;
+
+ for (auto const& error: _compilerResult["errors"])
+ {
+ BOOST_REQUIRE(error.isObject());
+ BOOST_REQUIRE(error["severity"].isString());
+ if (error["severity"].asString() != "warning")
+ return false;
+ }
+
+ return true;
+}
+
+Json::Value getContractResult(Json::Value const& _compilerResult, string const& _file, string const& _name)
+{
+ if (
+ !_compilerResult["contracts"].isObject() ||
+ !_compilerResult["contracts"][_file].isObject() ||
+ !_compilerResult["contracts"][_file][_name].isObject()
+ )
+ return Json::Value();
+ return _compilerResult["contracts"][_file][_name];
+}
+
+Json::Value compile(string const& _input)
+{
+ StandardCompiler compiler;
+ string output = compiler.compile(_input);
+ Json::Value ret;
+ BOOST_REQUIRE(Json::Reader().parse(output, ret, false));
+ return ret;
+}
+
+} // end anonymous namespace
+
+BOOST_AUTO_TEST_SUITE(StandardCompiler)
+
+BOOST_AUTO_TEST_CASE(assume_object_input)
+{
+ Json::Value result;
+
+ /// Use the native JSON interface of StandardCompiler to trigger these
+ solidity::StandardCompiler compiler;
+ result = compiler.compile(Json::Value());
+ BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object."));
+ result = compiler.compile(Json::Value("INVALID"));
+ BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object."));
+
+ /// Use the string interface of StandardCompiler to trigger these
+ result = compile("");
+ BOOST_CHECK(containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
+ result = compile("invalid");
+ BOOST_CHECK(containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
+ result = compile("\"invalid\"");
+ BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object."));
+ BOOST_CHECK(!containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
+ result = compile("{}");
+ BOOST_CHECK(!containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n"));
+ BOOST_CHECK(!containsAtMostWarnings(result));
+}
+
+BOOST_AUTO_TEST_CASE(invalid_language)
+{
+ char const* input = R"(
+ {
+ "language": "INVALID"
+ }
+ )";
+ Json::Value result = compile(input);
+ BOOST_CHECK(containsError(result, "JSONError", "Only \"Solidity\" is supported as a language."));
+}
+
+BOOST_AUTO_TEST_CASE(valid_language)
+{
+ char const* input = R"(
+ {
+ "language": "Solidity"
+ }
+ )";
+ Json::Value result = compile(input);
+ BOOST_CHECK(!containsError(result, "JSONError", "Only \"Solidity\" is supported as a language."));
+}
+
+BOOST_AUTO_TEST_CASE(no_sources)
+{
+ char const* input = R"(
+ {
+ "language": "Solidity"
+ }
+ )";
+ Json::Value result = compile(input);
+ BOOST_CHECK(containsError(result, "JSONError", "No input sources specified."));
+}
+
+BOOST_AUTO_TEST_CASE(smoke_test)
+{
+ char const* input = R"(
+ {
+ "language": "Solidity",
+ "sources": {
+ "empty": {
+ "content": ""
+ }
+ }
+ }
+ )";
+ Json::Value result = compile(input);
+ BOOST_CHECK(containsAtMostWarnings(result));
+}
+
+BOOST_AUTO_TEST_CASE(basic_compilation)
+{
+ char const* input = R"(
+ {
+ "language": "Solidity",
+ "sources": {
+ "fileA": {
+ "content": "contract A { }"
+ }
+ }
+ }
+ )";
+ Json::Value result = compile(input);
+ BOOST_CHECK(containsAtMostWarnings(result));
+ Json::Value contract = getContractResult(result, "fileA", "A");
+ BOOST_CHECK(contract.isObject());
+ BOOST_CHECK(contract["abi"].isArray());
+ BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["abi"]), "[]");
+ BOOST_CHECK(contract["devdoc"].isObject());
+ BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["devdoc"]), "{\"methods\":{}}");
+ BOOST_CHECK(contract["userdoc"].isObject());
+ BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["userdoc"]), "{\"methods\":{}}");
+ BOOST_CHECK(contract["evm"].isObject());
+ /// @TODO check evm.methodIdentifiers, legacyAssembly, bytecode, deployedBytecode
+ BOOST_CHECK(contract["evm"]["bytecode"].isObject());
+ BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString());
+ BOOST_CHECK_EQUAL(
+ dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()),
+ "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00"
+ );
+ BOOST_CHECK(contract["evm"]["assembly"].isString());
+ BOOST_CHECK(contract["evm"]["assembly"].asString().find(
+ " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x60)\n jumpi(tag_1, iszero(callvalue))\n"
+ " 0x0\n dup1\n revert\ntag_1:\ntag_2:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n"
+ " return\nstop\n\nsub_0: assembly {\n /* \"fileA\":0:14 contract A { } */\n"
+ " mstore(0x40, 0x60)\n tag_1:\n 0x0\n dup1\n revert\n\n"
+ " auxdata: 0xa165627a7a7230582") == 0);
+ BOOST_CHECK(contract["evm"]["gasEstimates"].isObject());
+ BOOST_CHECK_EQUAL(
+ dev::jsonCompactPrint(contract["evm"]["gasEstimates"]),
+ "{\"creation\":{\"codeDepositCost\":\"10800\",\"executionCost\":\"62\",\"totalCost\":\"10862\"}}"
+ );
+ BOOST_CHECK(contract["metadata"].isString());
+ BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString()));
+ BOOST_CHECK(result["sources"].isObject());
+ BOOST_CHECK(result["sources"]["fileA"].isObject());
+ BOOST_CHECK(result["sources"]["fileA"]["legacyAST"].isObject());
+ BOOST_CHECK_EQUAL(
+ dev::jsonCompactPrint(result["sources"]["fileA"]["legacyAST"]),
+ "{\"attributes\":{\"absolutePath\":\"fileA\",\"exportedSymbols\":{\"A\":[1]}},\"children\":"
+ "[{\"attributes\":{\"baseContracts\":[null],\"contractDependencies\":[null],\"contractKind\":\"contract\","
+ "\"documentation\":null,\"fullyImplemented\":true,\"linearizedBaseContracts\":[1],\"name\":\"A\",\"nodes\":[null],\"scope\":2},"
+ "\"id\":1,\"name\":\"ContractDefinition\",\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}"
+ );
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
+} // end namespaces