aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.editorconfig18
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml1
-rw-r--r--CMakeLists.txt11
-rw-r--r--Changelog.md38
-rw-r--r--README.md6
-rw-r--r--appveyor.yml3
-rw-r--r--cmake/EthCompilerSettings.cmake8
-rw-r--r--cmake/UseDev.cmake1
-rw-r--r--cmake/templates/license.h.in75
m---------deps0
-rw-r--r--docs/abi-spec.rst345
-rw-r--r--docs/assembly.rst68
-rw-r--r--docs/bugs.json7
-rw-r--r--docs/bugs_by_version.json37
-rw-r--r--docs/contracts.rst61
-rw-r--r--docs/contributing.rst21
-rw-r--r--docs/control-structures.rst65
-rw-r--r--docs/grammar.txt34
-rw-r--r--docs/index.rst9
-rw-r--r--docs/installing-solidity.rst8
-rw-r--r--docs/introduction-to-smart-contracts.rst24
-rw-r--r--docs/layout-of-source-files.rst4
-rw-r--r--docs/logo.svg27
-rw-r--r--docs/miscellaneous.rst8
-rw-r--r--docs/solidity-by-example.rst24
-rw-r--r--docs/types.rst25
-rw-r--r--docs/units-and-global-variables.rst21
-rw-r--r--docs/using-the-compiler.rst2
-rw-r--r--docs/utils/SolidityLexer.py2
-rw-r--r--libdevcore/Assertions.h34
-rw-r--r--libdevcore/Common.h2
-rw-r--r--libdevcore/CommonIO.cpp23
-rw-r--r--libdevcore/Exceptions.h4
-rw-r--r--libdevcore/SHA3.h16
-rw-r--r--libdevcore/UTF8.cpp84
-rw-r--r--libdevcore/Whiskers.cpp127
-rw-r--r--libdevcore/Whiskers.h87
-rw-r--r--libdevcore/debugbreak.h128
-rw-r--r--libevmasm/Assembly.cpp34
-rw-r--r--libevmasm/Assembly.h24
-rw-r--r--libevmasm/AssemblyItem.h8
-rw-r--r--libevmasm/CommonSubexpressionEliminator.cpp4
-rw-r--r--libevmasm/ConstantOptimiser.cpp5
-rw-r--r--libevmasm/EVMSchedule.h4
-rw-r--r--libevmasm/GasMeter.cpp15
-rw-r--r--libevmasm/GasMeter.h4
-rw-r--r--libevmasm/Instruction.cpp16
-rw-r--r--libevmasm/Instruction.h15
-rw-r--r--libevmasm/KnownState.cpp20
-rw-r--r--libevmasm/KnownState.h8
-rw-r--r--libevmasm/PeepholeOptimiser.cpp23
-rw-r--r--libevmasm/SemanticInformation.cpp7
-rw-r--r--libevmasm/SimplificationRules.cpp25
-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.cpp62
-rw-r--r--liblll/Compiler.cpp5
-rw-r--r--liblll/CompilerState.cpp16
-rw-r--r--liblll/Parser.cpp2
-rw-r--r--libsolidity/CMakeLists.txt4
-rw-r--r--libsolidity/analysis/DocStringAnalyser.cpp19
-rw-r--r--libsolidity/analysis/DocStringAnalyser.h8
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp153
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.h43
-rw-r--r--libsolidity/analysis/PostTypeChecker.cpp19
-rw-r--r--libsolidity/analysis/PostTypeChecker.h8
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp36
-rw-r--r--libsolidity/analysis/ReferencesResolver.h15
-rw-r--r--libsolidity/analysis/StaticAnalyzer.cpp47
-rw-r--r--libsolidity/analysis/StaticAnalyzer.h7
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp53
-rw-r--r--libsolidity/analysis/SyntaxChecker.h9
-rw-r--r--libsolidity/analysis/TypeChecker.cpp477
-rw-r--r--libsolidity/analysis/TypeChecker.h17
-rw-r--r--libsolidity/ast/AST.cpp20
-rw-r--r--libsolidity/ast/AST.h15
-rw-r--r--libsolidity/ast/ASTAnnotations.h13
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp881
-rw-r--r--libsolidity/ast/ASTJsonConverter.h114
-rw-r--r--libsolidity/ast/Types.cpp13
-rw-r--r--libsolidity/ast/Types.h9
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp6
-rw-r--r--libsolidity/codegen/CompilerContext.cpp63
-rw-r--r--libsolidity/codegen/CompilerContext.h18
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp124
-rw-r--r--libsolidity/codegen/CompilerUtils.h24
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp100
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp37
-rw-r--r--libsolidity/codegen/ExpressionCompiler.h2
-rw-r--r--libsolidity/codegen/LValue.cpp6
-rw-r--r--libsolidity/formal/Why3Translator.cpp902
-rw-r--r--libsolidity/formal/Why3Translator.h149
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp368
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.h58
-rw-r--r--libsolidity/inlineasm/AsmAnalysisInfo.h19
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.cpp272
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.h16
-rw-r--r--libsolidity/inlineasm/AsmData.h41
-rw-r--r--libsolidity/inlineasm/AsmDataForward.h (renamed from libsolidity/interface/Utils.h)39
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp282
-rw-r--r--libsolidity/inlineasm/AsmParser.h10
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp91
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h24
-rw-r--r--libsolidity/inlineasm/AsmScope.cpp27
-rw-r--r--libsolidity/inlineasm/AsmScope.h38
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.cpp100
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.h31
-rw-r--r--libsolidity/inlineasm/AsmStack.cpp95
-rw-r--r--libsolidity/inlineasm/AsmStack.h92
-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.cpp129
-rw-r--r--libsolidity/interface/CompilerStack.h84
-rw-r--r--libsolidity/interface/ErrorReporter.cpp167
-rw-r--r--libsolidity/interface/ErrorReporter.h102
-rw-r--r--libsolidity/interface/Exceptions.cpp4
-rw-r--r--libsolidity/interface/Exceptions.h12
-rw-r--r--libsolidity/interface/InterfaceHandler.cpp197
-rw-r--r--libsolidity/interface/Natspec.cpp130
-rw-r--r--libsolidity/interface/Natspec.h (renamed from libsolidity/interface/InterfaceHandler.h)9
-rw-r--r--libsolidity/interface/StandardCompiler.cpp32
-rw-r--r--libsolidity/interface/Version.cpp2
-rw-r--r--libsolidity/parsing/DocStringParser.cpp11
-rw-r--r--libsolidity/parsing/DocStringParser.h7
-rw-r--r--libsolidity/parsing/Parser.cpp111
-rw-r--r--libsolidity/parsing/Parser.h7
-rw-r--r--libsolidity/parsing/ParserBase.cpp87
-rw-r--r--libsolidity/parsing/ParserBase.h15
-rw-r--r--libsolidity/parsing/Scanner.cpp2
-rw-r--r--libsolidity/parsing/Token.h1
-rwxr-xr-xscripts/bytecodecompare/prepare_report.py2
-rw-r--r--scripts/bytecodecompare/storebytecode.bat13
-rwxr-xr-xscripts/bytecodecompare/storebytecode.sh19
-rwxr-xr-xscripts/create_source_tarball.sh2
-rwxr-xr-xscripts/docker_deploy.sh2
-rwxr-xr-xscripts/get_version.sh31
-rwxr-xr-xscripts/release_ppa.sh10
-rwxr-xr-xscripts/test_emscripten.sh53
-rwxr-xr-xscripts/travis-emscripten/build_emscripten.sh2
-rw-r--r--solc/CommandLineInterface.cpp276
-rw-r--r--solc/CommandLineInterface.h12
-rw-r--r--solc/jsonCompiler.cpp219
-rw-r--r--test/CMakeLists.txt4
-rw-r--r--test/Metadata.cpp80
-rw-r--r--test/Metadata.h37
-rw-r--r--test/RPCSession.cpp4
-rwxr-xr-xtest/cmdlineTests.sh7
-rw-r--r--test/contracts/FixedFeeRegistrar.cpp4
-rw-r--r--test/contracts/Wallet.cpp16
-rw-r--r--test/fuzzer.cpp1
-rw-r--r--test/libdevcore/MiniMoustache.cpp127
-rw-r--r--test/libdevcore/UTF8.cpp216
-rw-r--r--test/libjulia/Parser.cpp231
-rw-r--r--test/liblll/EndToEndTest.cpp426
-rw-r--r--test/liblll/Parser.cpp8
-rw-r--r--test/libsolidity/ASTJSON.cpp56
-rw-r--r--test/libsolidity/Assembly.cpp18
-rw-r--r--test/libsolidity/ErrorCheck.cpp11
-rw-r--r--test/libsolidity/GasMeter.cpp6
-rw-r--r--test/libsolidity/InlineAssembly.cpp328
-rw-r--r--test/libsolidity/JSONCompiler.cpp111
-rw-r--r--test/libsolidity/Metadata.cpp63
-rw-r--r--test/libsolidity/SolidityABIJSON.cpp22
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp434
-rw-r--r--test/libsolidity/SolidityExpressionCompiler.cpp10
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp486
-rw-r--r--test/libsolidity/SolidityNatspecJSON.cpp4
-rw-r--r--test/libsolidity/SolidityOptimizer.cpp159
-rw-r--r--test/libsolidity/SolidityParser.cpp34
-rw-r--r--test/libsolidity/StandardCompiler.cpp86
177 files changed, 8549 insertions, 4407 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 47739568..114420c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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 a7b3f5be..34b1152c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -191,6 +191,7 @@ before_script:
&& scripts/create_source_tarball.sh)
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)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 931a8a0f..d6ed6643 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.11")
+set(PROJECT_VERSION "0.4.12")
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 9c69fb57..b41852df 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,41 @@
+### 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:
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/appveyor.yml b/appveyor.yml
index 3e3cebc1..3d4d65bb 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -65,7 +65,8 @@ build_script:
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
- cd %APPVEYOR_BUILD_FOLDER%
- scripts\release.bat %CONFIGURATION%
- - scripts\bytecodecompare\storebytecode.bat %CONFIGURATION% %APPVEYOR_REPO_COMMIT%
+ - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short
+ - ps: scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir
test_script:
- cd %APPVEYOR_BUILD_FOLDER%
diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake
index 97db9168..ea3b185a 100644
--- a/cmake/EthCompilerSettings.cmake
+++ b/cmake/EthCompilerSettings.cmake
@@ -65,7 +65,7 @@ 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)
@@ -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/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
index 420cea17..83643634 100644
--- a/docs/assembly.rst
+++ b/docs/assembly.rst
@@ -13,6 +13,8 @@ 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
===============
@@ -28,11 +30,8 @@ arising when writing manual assembly by the following features:
* 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))) } }``
-
-.. note::
- Of the above, loops, function calls and switch statements are not yet implemented.
+* 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.
@@ -182,6 +181,8 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+------+-----------------------------------------------------------------+
| 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 |
@@ -232,9 +233,17 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+------+-----------------------------------------------------------------+
| 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) |
@@ -246,6 +255,9 @@ In the grammar, opcodes are represented as pre-defined identifiers.
| 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)) |
@@ -312,8 +324,10 @@ would be written as follows
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).
+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.
@@ -431,11 +445,6 @@ As an example how this can be done in extreme cases, please see the following.
pop // We have to pop the manually pushed value here again.
}
-.. 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
----------------------------------
@@ -491,9 +500,6 @@ is performed by replacing the variable's value on the stack by the new value.
Switch
------
-.. note::
- Switch is not yet implemented.
-
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
@@ -506,10 +512,10 @@ case called ``default``.
assembly {
let x := 0
switch calldataload(4)
- case 0: {
+ case 0 {
x := calldataload(0x24)
}
- default: {
+ default {
x := calldataload(0x44)
}
sstore(0, div(x, 2))
@@ -521,13 +527,10 @@ case does require them.
Loops
-----
-.. note::
- Loops are not yet implemented.
-
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 can also be blocks. If the initializing part is a block that
+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).
@@ -545,9 +548,6 @@ The following example computes the sum of an area in memory.
Functions
---------
-.. note::
- Functions are not yet implemented.
-
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
@@ -559,7 +559,7 @@ 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)``.
+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.
@@ -568,12 +568,12 @@ The following example implements the power function by square-and-multiply.
assembly {
function power(base, exponent) -> result {
switch exponent
- 0: { result := 1 }
- 1: { result := base }
- default: {
+ case 0 { result := 1 }
+ case 1 { result := base }
+ default {
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
- 1: { result := mul(base, result) }
+ case 1 { result := mul(base, result) }
}
}
}
@@ -693,13 +693,13 @@ 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: {
+ case 0xb3de648b {
let (r) = f(calldataload(4))
let ret := $allocate(0x20)
mstore(ret, r)
return(ret, 0x20)
}
- default: { jump(invalidJumpLabel) }
+ default { revert(0, 0) }
// memory allocator
function $allocate(size) -> pos {
pos := mload(0x40)
@@ -744,7 +744,7 @@ After the desugaring phase it looks as follows::
}
$caseDefault:
{
- jump(invalidJumpLabel)
+ revert(0, 0)
jump($endswitch)
}
$endswitch:
@@ -850,8 +850,8 @@ Grammar::
AssemblyAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
- ( 'default' ':' AssemblyBlock )?
- AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock
+ ( 'default' AssemblyBlock )?
+ AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock
AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
( '->' '(' IdentifierList ')' )? AssemblyBlock
AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression)
diff --git a/docs/bugs.json b/docs/bugs.json
index 1a67d626..a0c0e7c4 100644
--- a/docs/bugs.json
+++ b/docs/bugs.json
@@ -1,5 +1,12 @@
[
{
+ "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).",
diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json
index 0f7346b4..cab79f05 100644
--- a/docs/bugs_by_version.json
+++ b/docs/bugs_by_version.json
@@ -1,6 +1,7 @@
{
"0.1.0": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@@ -15,6 +16,7 @@
},
"0.1.1": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@@ -29,6 +31,7 @@
},
"0.1.2": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@@ -43,6 +46,7 @@
},
"0.1.3": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@@ -57,6 +61,7 @@
},
"0.1.4": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@@ -71,6 +76,7 @@
},
"0.1.5": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@@ -85,6 +91,7 @@
},
"0.1.6": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -100,6 +107,7 @@
},
"0.1.7": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -115,6 +123,7 @@
},
"0.2.0": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -130,6 +139,7 @@
},
"0.2.1": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -145,6 +155,7 @@
},
"0.2.2": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -160,6 +171,7 @@
},
"0.3.0": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -174,6 +186,7 @@
},
"0.3.1": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -187,6 +200,7 @@
},
"0.3.2": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -200,6 +214,7 @@
},
"0.3.3": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -212,6 +227,7 @@
},
"0.3.4": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -224,6 +240,7 @@
},
"0.3.5": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -236,6 +253,7 @@
},
"0.3.6": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -246,6 +264,7 @@
},
"0.4.0": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -256,6 +275,7 @@
},
"0.4.1": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -266,16 +286,24 @@
},
"0.4.10": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction"
],
"released": "2017-03-15"
},
"0.4.11": {
- "bugs": [],
+ "bugs": [
+ "SkipEmptyStringLiteral"
+ ],
"released": "2017-05-03"
},
+ "0.4.12": {
+ "bugs": [],
+ "released": "2017-07-03"
+ },
"0.4.2": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@@ -285,6 +313,7 @@
},
"0.4.3": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage"
@@ -293,6 +322,7 @@
},
"0.4.4": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored"
],
@@ -300,6 +330,7 @@
},
"0.4.5": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStateKnowledgeNotResetForJumpdest"
@@ -308,6 +339,7 @@
},
"0.4.6": {
"bugs": [
+ "SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored"
],
@@ -315,18 +347,21 @@
},
"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"
diff --git a/docs/contracts.rst b/docs/contracts.rst
index 8d7af2c1..e9ea1b3b 100644
--- a/docs/contracts.rst
+++ b/docs/contracts.rst
@@ -20,7 +20,7 @@ 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
@@ -244,6 +244,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
}
.. index:: ! getter;function, ! function;getter
+.. _getter_functions:
Getter Functions
================
@@ -273,7 +274,7 @@ be done at declaration.
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,8 +322,8 @@ 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.
::
@@ -405,8 +406,8 @@ inheritable properties of contracts and may be overridden by derived contracts.
}
}
-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
@@ -441,7 +442,7 @@ 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
+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
@@ -458,11 +459,13 @@ value types and strings.
}
+.. _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.
::
@@ -491,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
@@ -509,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.
@@ -567,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
@@ -590,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.
@@ -605,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.
@@ -679,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>`_,
@@ -818,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
@@ -834,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;
@@ -849,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
@@ -865,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
@@ -937,7 +940,7 @@ Interfaces are similar to abstract contracts, but they cannot have any functions
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
+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:
@@ -976,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
@@ -1041,8 +1044,8 @@ more advanced example to implement a set).
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``
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 42204d5c..559f9f6a 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 `Moustache <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 Moustache: 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 variales can also be used
+inside such areas. \ No newline at end of file
diff --git a/docs/control-structures.rst b/docs/control-structures.rst
index a2d34274..03787c20 100644
--- a/docs/control-structures.rst
+++ b/docs/control-structures.rst
@@ -361,55 +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 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).
+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()``.
+
+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, Solidity automatically generates a runtime exception in the following situations:
+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 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 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 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 you call a zero-initialized variable of internal function type.
-#. If a ``.transfer()`` fails.
#. If you call ``assert`` with an argument that evaluates to false.
-While a user-provided exception is generated in the following situations:
+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``) when a user-provided exception is thrown or the condition of
-a ``require`` call is not met. In contrast, it performs an invalid operation
-(instruction ``0xfe``) if a runtime exception is encountered or the condition of an ``assert`` call is not met. In both cases, this 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
+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.
-
-If contracts are written so that ``assert`` is only used to test internal conditions and ``require``
-is used in case of malformed input, a formal analysis tool that verifies that the invalid
-opcode can never be reached can be used to check for the absence of errors assuming valid inputs.
+(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/grammar.txt b/docs/grammar.txt
index dc188572..6c041460 100644
--- a/docs/grammar.txt
+++ b/docs/grammar.txt
@@ -7,7 +7,7 @@ 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* '}'
@@ -20,9 +20,12 @@ StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' )? Ident
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' )*
+ ( ModifierInvocation | 'constant' | 'payable' | 'external' | 'public' | 'internal' | 'private' )*
( 'returns' ParameterList )? ( ';' | Block )
EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';'
@@ -62,7 +65,7 @@ WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
-InlineAssemblyStatement = 'assembly' InlineAssemblyBlock
+InlineAssemblyStatement = 'assembly' StringLiteral? InlineAssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
@@ -72,8 +75,13 @@ VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expressi
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
@@ -88,20 +96,20 @@ Expression =
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
- | Expression? (',' Expression)
| PrimaryExpression
-PrimaryExpression = Identifier
- | BooleanLiteral
+PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
+ | TupleExpression
+ | Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
-FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' FunctionCallArguments ')'
+FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
@@ -120,6 +128,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'
@@ -137,7 +148,8 @@ Ufixed = 'ufixed' | 'ufixed0x8' | 'ufixed0x16' | 'ufixed0x24' | 'ufixed0x32' | '
InlineAssemblyBlock = '{' AssemblyItem* '}'
-AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | NumberLiteral | StringLiteral | HexLiteral
+AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
AssemblyLocalBinding = 'let' Identifier ':=' FunctionalAssemblyExpression
-AssemblyAssignment = Identifier ':=' FunctionalAssemblyExpression | '=:' Identifier
+AssemblyAssignment = ( Identifier ':=' FunctionalAssemblyExpression ) | ( '=:' Identifier )
+AssemblyLabel = Identifier ':'
FunctionalAssemblyExpression = Identifier '(' AssemblyItem? ( ',' AssemblyItem )* ')'
diff --git a/docs/index.rst b/docs/index.rst
index 1312864a..3cdda62d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,6 +1,11 @@
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 (EVM).
@@ -54,6 +59,9 @@ Available Solidity Integrations
* `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>`_.
@@ -137,6 +145,7 @@ Contents
solidity-in-depth.rst
security-considerations.rst
using-the-compiler.rst
+ abi-spec.rst
style-guide.rst
common-patterns.rst
bugs.rst
diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst
index a2a3c3da..9b5ba9f2 100644
--- a/docs/installing-solidity.rst
+++ b/docs/installing-solidity.rst
@@ -119,6 +119,12 @@ Install it using ``brew``:
# 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:
Building from Source
@@ -261,7 +267,7 @@ 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 relase example: ``0.4.8+commit.60cc1668.Emscripten.clang``.
+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``
diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst
index 9001a08c..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
@@ -124,7 +126,7 @@ 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
@@ -193,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?
@@ -402,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
@@ -423,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
@@ -436,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 715b29ae..e4b403f6 100644
--- a/docs/layout-of-source-files.rst
+++ b/docs/layout-of-source-files.rst
@@ -33,7 +33,7 @@ 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
@@ -185,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 914dfacd..182de33a 100644
--- a/docs/miscellaneous.rst
+++ b/docs/miscellaneous.rst
@@ -371,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-getters>` 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});``
@@ -394,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 | ``++``, ``--`` |
@@ -462,7 +464,7 @@ Global Variables
- ``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)
+- ``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()`
diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst
index 4ed474df..450b0286 100644
--- a/docs/solidity-by-example.rst
+++ b/docs/solidity-by-example.rst
@@ -94,7 +94,7 @@ of votes.
// 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);
+ require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0));
voters[voter].weight = 1;
}
@@ -165,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
@@ -273,7 +273,7 @@ activate themselves.
// 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
@@ -296,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;
@@ -316,7 +316,7 @@ 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.
@@ -566,9 +566,9 @@ Safe Remote Purchase
_;
}
- 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
@@ -577,7 +577,7 @@ Safe Remote Purchase
onlySeller
inState(State.Created)
{
- aborted();
+ Aborted();
state = State.Inactive;
seller.transfer(this.balance);
}
@@ -591,7 +591,7 @@ Safe Remote Purchase
condition(msg.value == (2 * value))
payable
{
- purchaseConfirmed();
+ PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
@@ -602,7 +602,7 @@ 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.
@@ -612,7 +612,7 @@ Safe Remote Purchase
// block the refund - the withdraw pattern should be used.
buyer.transfer(value);
- seller.transfer(this.balance));
+ seller.transfer(this.balance);
}
}
diff --git a/docs/types.rst b/docs/types.rst
index c400aecb..319701c7 100644
--- a/docs/types.rst
+++ b/docs/types.rst
@@ -54,13 +54,13 @@ Operators:
* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation)
* 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 a runtime exception.
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
+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.
@@ -77,7 +77,7 @@ sign extends. Shifting by a negative amount throws a runtime exception.
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:
@@ -125,7 +125,7 @@ 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.
@@ -387,10 +387,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++) {
@@ -472,19 +472,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.
::
@@ -655,7 +655,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
@@ -795,7 +796,7 @@ 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.
diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst
index 246cc564..7d21f065 100644
--- a/docs/units-and-global-variables.rst
+++ b/docs/units-and-global-variables.rst
@@ -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,13 +79,23 @@ Block and Transaction Properties
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
-.. index:: assert, revert, 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
----------------------------------------
-``assert(bool condition)``:
- throws if the condition is not met.
``addmod(uint x, uint y, uint k) returns (uint)``:
compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``.
``mulmod(uint x, uint y, uint k) returns (uint)``:
@@ -101,8 +111,6 @@ Mathematical and Cryptographic Functions
``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>`_)
-``revert()``:
- abort execution and revert state changes
In the above, "tightly packed" means that the arguments are concatenated without padding.
This means that the following are all identical::
@@ -122,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
diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst
index 7aa997f8..7f82df70 100644
--- a/docs/using-the-compiler.rst
+++ b/docs/using-the-compiler.rst
@@ -119,7 +119,7 @@ Input Description
//
// The available output types are as follows:
// abi - ABI
- // ast - AST of all source files (not supported atm)
+ // ast - AST of all source files
// legacyAST - legacy AST of all source files
// devdoc - Developer documentation (natspec)
// userdoc - User documentation (natspec)
diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py
index 1dc99159..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': [
diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h
index e54b9d55..729ffb05 100644
--- a/libdevcore/Assertions.h
+++ b/libdevcore/Assertions.h
@@ -25,7 +25,6 @@
#pragma once
#include "Exceptions.h"
-#include "debugbreak.h"
namespace dev
{
@@ -38,37 +37,6 @@ 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.
@@ -86,6 +54,4 @@ inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char con
} \
while (false)
-using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
-
}
diff --git a/libdevcore/Common.h b/libdevcore/Common.h
index dc981ff6..c5b09a80 100644
--- a/libdevcore/Common.h
+++ b/libdevcore/Common.h
@@ -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/CommonIO.cpp b/libdevcore/CommonIO.cpp
index 8dbcb00a..52829455 100644
--- a/libdevcore/CommonIO.cpp
+++ b/libdevcore/CommonIO.cpp
@@ -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/Exceptions.h b/libdevcore/Exceptions.h
index 37cdbed9..4817e9e3 100644
--- a/libdevcore/Exceptions.h
+++ b/libdevcore/Exceptions.h
@@ -56,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/SHA3.h b/libdevcore/SHA3.h
index ce075521..1a561066 100644
--- a/libdevcore/SHA3.h
+++ b/libdevcore/SHA3.h
@@ -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/UTF8.cpp b/libdevcore/UTF8.cpp
index 9fbf4b45..2ae720ec 100644
--- a/libdevcore/UTF8.cpp
+++ b/libdevcore/UTF8.cpp
@@ -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/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 ea061a30..597fdae1 100644
--- a/libevmasm/Assembly.cpp
+++ b/libevmasm/Assembly.cpp
@@ -40,7 +40,7 @@ void Assembly::append(Assembly const& _a)
auto newDeposit = m_deposit + _a.deposit();
for (AssemblyItem i: _a.m_items)
{
- if (i.type() == Tag || (i.type() == PushTag && i != errorTag()))
+ if (i.type() == Tag || i.type() == PushTag)
i.setData(i.data() + m_usedTags);
else if (i.type() == PushSub || i.type() == PushSubSize)
i.setData(i.data() + m_subs.size());
@@ -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
@@ -216,6 +203,9 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con
}
}
+ if (m_auxiliaryData.size() > 0)
+ _out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl;
+
return _out;
}
@@ -315,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;
}
@@ -333,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())
diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h
index 528c9e74..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,7 +115,6 @@ protected:
/// returns the replaced tags.
std::map<u256, u256> optimiseInternal(bool _enable, bool _isCreation, size_t _runs);
- void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
unsigned bytesRequired(unsigned subTagSize) const;
private:
@@ -141,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.h b/libevmasm/AssemblyItem.h
index 464368fb..d38db927 100644
--- a/libevmasm/AssemblyItem.h
+++ b/libevmasm/AssemblyItem.h
@@ -148,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 fd4fffa6..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());
diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp
index 0c093ebf..2ecbfa7f 100644
--- a/libevmasm/ConstantOptimiser.cpp
+++ b/libevmasm/ConstantOptimiser.cpp
@@ -99,10 +99,7 @@ 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(
diff --git a/libevmasm/EVMSchedule.h b/libevmasm/EVMSchedule.h
index 65d307ae..1695a59c 100644
--- a/libevmasm/EVMSchedule.h
+++ b/libevmasm/EVMSchedule.h
@@ -32,8 +32,8 @@ struct EVMSchedule
unsigned stackLimit = 1024;
unsigned expGas = 10;
unsigned expByteGas = 10;
- unsigned sha3Gas = 30;
- unsigned sha3WordGas = 6;
+ unsigned keccak256Gas = 30;
+ unsigned keccak256WordGas = 6;
unsigned sloadGas = 200;
unsigned sstoreSetGas = 20000;
unsigned sstoreResetGas = 5000;
diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp
index f5fd00ea..c96c6ca5 100644
--- a/libevmasm/GasMeter.cpp
+++ b/libevmasm/GasMeter.cpp
@@ -96,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;
@@ -128,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.
@@ -141,8 +143,10 @@ 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);
@@ -154,6 +158,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
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();
diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h
index 3169ff2a..2c3ecf5a 100644
--- a/libevmasm/GasMeter.h
+++ b/libevmasm/GasMeter.h
@@ -48,8 +48,8 @@ namespace GasCosts
static unsigned const balanceGas = 400;
static unsigned const expGas = 10;
static unsigned const expByteGas = 50;
- static unsigned const sha3Gas = 30;
- static unsigned const sha3WordGas = 6;
+ 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;
diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp
index 5e92c6e6..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,8 +159,10 @@ 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 },
+ { "CREATE2", Instruction::CREATE2 },
{ "REVERT", Instruction::REVERT },
{ "INVALID", Instruction::INVALID },
{ "SELFDESTRUCT", Instruction::SELFDESTRUCT }
@@ -189,7 +193,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ 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::SHA3, { "SHA3", 0, 2, 1, false, Tier::Special } },
+ { 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 } },
@@ -203,6 +207,8 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ 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 } },
@@ -210,7 +216,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ 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, false, Tier::VeryLow } },
+ { 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 } },
@@ -295,7 +301,9 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ 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::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } },
+ { 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 } }
};
diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h
index 192fe090..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,6 +187,8 @@ 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
+ 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)
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/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp
index 6c92d76b..e94a8ba4 100644
--- a/libevmasm/PeepholeOptimiser.cpp
+++ b/libevmasm/PeepholeOptimiser.cpp
@@ -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(
@@ -235,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 61586e7b..f63f0c61 100644
--- a/libevmasm/SemanticInformation.cpp
+++ b/libevmasm/SemanticInformation.cpp
@@ -137,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;
@@ -156,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;
@@ -175,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
index 2976d95f..e6c51f95 100644
--- a/libevmasm/SimplificationRules.cpp
+++ b/libevmasm/SimplificationRules.cpp
@@ -103,7 +103,7 @@ Rules::Rules()
{{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::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; }},
@@ -124,23 +124,26 @@ Rules::Rules()
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
}},
- // invariants involving known constants
+ // 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::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::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::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; }},
@@ -153,6 +156,7 @@ Rules::Rules()
{{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; }},
@@ -160,6 +164,7 @@ Rules::Rules()
{{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,
@@ -172,14 +177,17 @@ Rules::Rules()
{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>()},
@@ -210,6 +218,7 @@ Rules::Rules()
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
}});
}
+
// move constants across subtractions
addRules(vector<pair<Pattern, function<Pattern()>>>{
{
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 2b8822a6..56c1e26a 100644
--- a/liblll/CodeFragment.cpp
+++ b/liblll/CodeFragment.cpp
@@ -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")
{
@@ -515,8 +540,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 +595,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/Compiler.cpp b/liblll/Compiler.cpp
index ea8b27af..05376cd5 100644
--- a/liblll/Compiler.cpp
+++ b/liblll/Compiler.cpp
@@ -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/CompilerState.cpp b/liblll/CompilerState.cpp
index 88e43e18..5d38bb8c 100644
--- a/liblll/CompilerState.cpp
+++ b/liblll/CompilerState.cpp
@@ -45,26 +45,29 @@ CodeFragment const& CompilerState::getDef(std::string const& _s)
void CompilerState::populateStandard()
{
static const string s = "{"
+ "(def 'panic () (asm INVALID))"
"(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 +76,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/Parser.cpp b/liblll/Parser.cpp
index 44d2a2ae..a3962df4 100644
--- a/liblll/Parser.cpp
+++ b/liblll/Parser.cpp
@@ -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/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/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/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 336dc894..aac90311 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -24,7 +24,9 @@
#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;
@@ -36,10 +38,10 @@ namespace solidity
NameAndTypeResolver::NameAndTypeResolver(
vector<Declaration const*> const& _globals,
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
- ErrorList& _errors
+ ErrorReporter& _errorReporter
) :
m_scopes(_scopes),
- m_errors(_errors)
+ m_errorReporter(_errorReporter)
{
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer());
@@ -52,11 +54,11 @@ bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode con
// The helper registers all declarations in m_scopes as a side-effect of its construction.
try
{
- DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors, _currentScope);
+ 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;
}
@@ -73,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."
);
@@ -88,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() +
@@ -106,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."
);
@@ -119,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."
);
@@ -137,7 +139,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi
}
catch (FatalError const&)
{
- if (m_errors.empty())
+ if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return false;
}
@@ -152,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;
}
@@ -214,7 +216,7 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
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(),
@@ -232,6 +234,26 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
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))
@@ -290,7 +312,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
{
if (m_scopes.count(&_node))
m_currentScope = m_scopes[&_node].get();
- return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node);
+ return ReferencesResolver(m_errorReporter, *this, _resolveInsideCode).resolve(_node);
}
}
@@ -328,11 +350,10 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
secondDeclarationLocation = declaration->location();
}
- reportDeclarationError(
+ m_errorReporter.declarationError(
secondDeclarationLocation,
- "Identifier already declared.",
- firstDeclarationLocation,
- "The previous declaration is here:"
+ SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
+ "Identifier already declared."
);
}
}
@@ -347,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());
}
@@ -415,61 +436,15 @@ 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(_currentScope),
- m_errors(_errors)
+ m_errorReporter(_errorReporter)
{
_astRoot.accept(*this);
solAssert(m_currentScope == _currentScope, "Scopes not correctly closed.");
@@ -633,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."
);
}
@@ -665,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 038a887b..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.
@@ -48,7 +50,7 @@ public:
NameAndTypeResolver(
std::vector<Declaration const*> const& _globals,
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
- ErrorList& _errors
+ ErrorReporter& _errorReporter
);
/// Registers all declarations found in the AST node, usually a source unit.
/// @returns false in case of error.
@@ -88,6 +90,9 @@ public:
std::vector<Declaration const*> const& _declarations
);
+ /// Generate and store warnings about variables that are named like instructions.
+ void warnVariablesNamedLikeInstructions();
+
private:
/// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors.
bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true);
@@ -103,24 +108,6 @@ 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.
@@ -128,7 +115,7 @@ private:
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
DeclarationContainer* m_currentScope = nullptr;
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
};
/**
@@ -145,7 +132,7 @@ public:
DeclarationRegistrationHelper(
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
- ErrorList& _errors,
+ ErrorReporter& _errorReporter,
ASTNode const* _currentScope = nullptr
);
@@ -175,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
index e8da3ca4..fbc72e52 100644
--- a/libsolidity/analysis/PostTypeChecker.cpp
+++ b/libsolidity/analysis/PostTypeChecker.cpp
@@ -18,6 +18,7 @@
#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>
@@ -32,17 +33,7 @@ using namespace dev::solidity;
bool PostTypeChecker::check(ASTNode const& _astRoot)
{
_astRoot.accept(*this);
- return Error::containsOnlyWarnings(m_errors);
-}
-
-void PostTypeChecker::typeError(SourceLocation const& _location, std::string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::TypeError);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
-
- m_errors.push_back(err);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool PostTypeChecker::visit(ContractDefinition const&)
@@ -57,7 +48,11 @@ void PostTypeChecker::endVisit(ContractDefinition const&)
solAssert(!m_currentConstVariable, "");
for (auto declaration: m_constVariables)
if (auto identifier = findCycle(declaration))
- typeError(declaration->location(), "The value of the constant " + declaration->name() + " has a cyclic dependency via " + identifier->name() + ".");
+ 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();
diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h
index 8774f413..dbdf50e0 100644
--- a/libsolidity/analysis/PostTypeChecker.h
+++ b/libsolidity/analysis/PostTypeChecker.h
@@ -28,6 +28,8 @@ 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
@@ -37,7 +39,7 @@ class PostTypeChecker: private ASTConstVisitor
{
public:
/// @param _errors the reference to the list of errors and warnings to add them found during type checking.
- PostTypeChecker(ErrorList& _errors): m_errors(_errors) {}
+ PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool check(ASTNode const& _astRoot);
@@ -55,10 +57,10 @@ private:
VariableDeclaration const* findCycle(
VariableDeclaration const* _startingFrom,
- std::set<VariableDeclaration const*> const& _seen = {}
+ std::set<VariableDeclaration const*> const& _seen = std::set<VariableDeclaration const*>{}
);
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
VariableDeclaration const* m_currentConstVariable = nullptr;
std::vector<VariableDeclaration const*> m_constVariables; ///< Required for determinism.
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index 9433976a..2a5f27df 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -28,6 +28,7 @@
#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>
@@ -111,7 +112,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
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)
@@ -161,13 +162,16 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
{
+ 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.
// The only purpose of this step is to fill the inline assembly annotation with
// external references.
- ErrorList errorsIgnored;
- assembly::ExternalIdentifierAccess::Resolver resolver =
- [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) {
+ 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");
@@ -186,6 +190,12 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
}
if (declarations.size() != 1)
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();
@@ -194,7 +204,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information
assembly::AsmAnalysisInfo analysisInfo;
- assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations());
+ assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations());
return false;
}
@@ -303,29 +313,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 dce343d3..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,11 +44,11 @@ class ReferencesResolver: private ASTConstVisitor
{
public:
ReferencesResolver(
- ErrorList& _errors,
+ ErrorReporter& _errorReporter,
NameAndTypeResolver& _resolver,
bool _resolveInsideCode = false
):
- m_errors(_errors),
+ m_errorReporter(_errorReporter),
m_resolver(_resolver),
m_resolveInsideCode(_resolveInsideCode)
{}
@@ -74,16 +75,16 @@ 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;
/// Stack of return parameters.
std::vector<ParameterList const*> m_returnParameters;
diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp
index 369376fa..b1b31163 100644
--- a/libsolidity/analysis/StaticAnalyzer.cpp
+++ b/libsolidity/analysis/StaticAnalyzer.cpp
@@ -21,18 +21,18 @@
*/
#include <libsolidity/analysis/StaticAnalyzer.h>
-#include <memory>
#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_errors);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool StaticAnalyzer::visit(ContractDefinition const& _contract)
@@ -63,7 +63,7 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
m_nonPayablePublic = false;
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
- warning(var.first->location(), "Unused local variable");
+ m_errorReporter.warning(var.first->location(), "Unused local variable");
m_localVarUseCount.clear();
}
@@ -105,7 +105,11 @@ bool StaticAnalyzer::visit(Return const& _return)
bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
{
if (_statement.expression().annotation().isPure)
- warning(_statement.location(), "Statement has no effect.");
+ m_errorReporter.warning(
+ _statement.location(),
+ "Statement has no effect."
+ );
+
return true;
}
@@ -114,17 +118,36 @@ 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")
- warning(_memberAccess.location(), "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?");
+ 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;
}
-void StaticAnalyzer::warning(SourceLocation const& _location, string const& _description)
+bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
{
- auto err = make_shared<Error>(Error::Type::Warning);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
+ if (!m_currentFunction)
+ return true;
- m_errors.push_back(err);
+ 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
index ab72e7d9..cd6913b5 100644
--- a/libsolidity/analysis/StaticAnalyzer.h
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -44,15 +44,13 @@ 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(ErrorList& _errors): m_errors(_errors) {}
+ 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:
- /// Adds a new warning to the list of errors.
- void warning(SourceLocation const& _location, std::string const& _description);
virtual bool visit(ContractDefinition const& _contract) override;
virtual void endVisit(ContractDefinition const& _contract) override;
@@ -65,8 +63,9 @@ private:
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;
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
/// Flag that indicates whether the current contract definition is a library.
bool m_library = false;
diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp
index 94e82a87..02e2fdcf 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;
@@ -29,27 +30,7 @@ using namespace dev::solidity;
bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
{
_astRoot.accept(*this);
- return Error::containsOnlyWarnings(m_errors);
-}
-
-void SyntaxChecker::warning(SourceLocation const& _location, string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::Warning);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
-
- m_errors.push_back(err);
-}
-
-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);
+ return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool SyntaxChecker::visit(SourceUnit const&)
@@ -74,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);
}
}
@@ -87,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());
@@ -96,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 "
@@ -116,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;
}
@@ -146,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;
}
@@ -154,14 +131,14 @@ 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(UnaryOperation const& _operation)
{
if (_operation.getOperator() == Token::Add)
- warning(_operation.location(), "Use of unary + is deprecated.");
+ m_errorReporter.warning(_operation.location(), "Use of unary + is deprecated.");
return true;
}
@@ -171,3 +148,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 8d7dcdd3..ec6ac434 100644
--- a/libsolidity/analysis/SyntaxChecker.h
+++ b/libsolidity/analysis/SyntaxChecker.h
@@ -38,14 +38,11 @@ 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(ASTNode const& _astRoot);
private:
- /// Adds a new error to the list of errors.
- void warning(SourceLocation const& _location, std::string const& _description);
- void syntaxError(SourceLocation const& _location, std::string const& _description);
virtual bool visit(SourceUnit const& _sourceUnit) override;
virtual void endVisit(SourceUnit const& _sourceUnit) override;
@@ -66,7 +63,9 @@ private:
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 38cdc1f8..1563467c 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -22,11 +22,13 @@
#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 <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;
@@ -43,10 +45,10 @@ bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
{
// 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
@@ -81,11 +83,11 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
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;
@@ -95,21 +97,19 @@ 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())
@@ -127,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()
);
@@ -156,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)
{
@@ -170,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."
+ );
}
}
}
@@ -213,7 +213,7 @@ 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;
@@ -285,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])
@@ -299,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);
}
@@ -310,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.");
}
}
}
@@ -347,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."
);
@@ -357,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)
@@ -370,16 +399,16 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
solAssert(base, "Base contract not available.");
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
- typeError(_inheritance.location(), "Interfaces cannot inherit.");
+ 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()) +
@@ -392,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 " +
@@ -409,17 +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)
- typeError(_struct.location(), "Structs cannot be defined in interfaces.");
+ 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*;
@@ -427,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())
@@ -452,34 +481,49 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
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())
- typeError(_function.location(), "Functions in interfaces cannot have an implementation.");
+ m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation.");
if (_function.visibility() < FunctionDefinition::Visibility::Public)
- typeError(_function.location(), "Functions in interfaces cannot be internal or private.");
+ m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot be internal or private.");
if (_function.isConstructor())
- typeError(_function.location(), "Constructor cannot be defined in interfaces.");
+ m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces.");
}
if (_function.isImplemented())
_function.body().accept(*this);
@@ -488,8 +532,13 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
bool TypeChecker::visit(VariableDeclaration const& _variable)
{
- if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
- typeError(_variable.location(), "Variables cannot be declared in interfaces.");
+ // 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.
@@ -505,19 +554,19 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (_variable.isConstant())
{
if (!_variable.isStateVariable())
- typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
+ m_errorReporter.typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
if (!_variable.type()->isValueType())
{
bool allowed = false;
if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type().get()))
allowed = arrayType->isString();
if (!allowed)
- typeError(_variable.location(), "Constants of non-value type not yet implemented.");
+ m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented.");
}
if (!_variable.value())
- typeError(_variable.location(), "Uninitialized \"constant\" variable.");
+ m_errorReporter.typeError(_variable.location(), "Uninitialized \"constant\" variable.");
else if (!_variable.value()->annotation().isPure)
- warning(
+ 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."
@@ -527,20 +576,20 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
{
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)
- typeError(_enum.location(), "Enumerable cannot be declared in interfaces.");
+ m_errorReporter.typeError(_enum.location(), "Enumerable cannot be declared in interfaces.");
return false;
}
@@ -572,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()) +
@@ -589,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 " +
@@ -608,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;
}
@@ -624,16 +673,17 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
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)
{
// External references have already been resolved in a prior stage and stored in the annotation.
// We run the resolve step again regardless.
- assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&](
+ julia::ExternalIdentifierAccess::Resolver identifierAccess = [&](
assembly::Identifier const& _identifier,
- assembly::IdentifierContext _context
+ julia::IdentifierContext _context,
+ bool
)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
@@ -647,43 +697,43 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
{
- typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
+ m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
return size_t(-1);
}
- else if (_context != assembly::IdentifierContext::RValue)
+ else if (_context != julia::IdentifierContext::RValue)
{
- typeError(_identifier.location, "Storage variables cannot be assigned to.");
+ m_errorReporter.typeError(_identifier.location, "Storage variables cannot be assigned to.");
return size_t(-1);
}
}
else if (var->isConstant())
{
- typeError(_identifier.location, "Constant variables not supported by inline assembly.");
+ m_errorReporter.typeError(_identifier.location, "Constant variables not supported by inline assembly.");
return size_t(-1);
}
else if (!var->isLocalVariable())
{
- typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
+ 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))
{
- typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables.");
+ 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)
{
- typeError(_identifier.location, "Only types that use one stack slot are supported.");
+ m_errorReporter.typeError(_identifier.location, "Only types that use one stack slot are supported.");
return size_t(-1);
}
}
- else if (_context == assembly::IdentifierContext::LValue)
+ else if (_context == julia::IdentifierContext::LValue)
{
- typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
+ m_errorReporter.typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
return size_t(-1);
}
- if (_context == assembly::IdentifierContext::RValue)
+ if (_context == julia::IdentifierContext::RValue)
{
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
if (dynamic_cast<FunctionDefinition const*>(declaration))
@@ -696,7 +746,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
if (!contract->isLibrary())
{
- typeError(_identifier.location, "Expected a library.");
+ m_errorReporter.typeError(_identifier.location, "Expected a library.");
return size_t(-1);
}
}
@@ -710,7 +760,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
_inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
assembly::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
- m_errors,
+ m_errorReporter,
+ false,
identifierAccess
);
if (!analyzer.analyze(_inlineAssembly.operations()))
@@ -753,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;
@@ -762,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() +
@@ -774,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() +
@@ -796,20 +847,20 @@ 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(
+ m_errorReporter.warning(
varDecl.location(),
"Uninitialized storage pointer. Did you mean '<type> memory " + varDecl.name() + "'?"
);
}
else if (dynamic_cast<MappingType const*>(type(varDecl).get()))
- typeError(
+ m_errorReporter.typeError(
varDecl.location(),
"Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
);
@@ -835,7 +886,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()) +
@@ -843,7 +894,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."
@@ -852,7 +903,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()) +
@@ -860,7 +911,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()) +
@@ -891,7 +942,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() +
@@ -901,10 +952,42 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
solAssert(false, "");
}
else if (*var.annotation().type == TupleType())
- typeError(
+ 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
@@ -917,7 +1000,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() &&
valueComponentType->mobileType()
)
- typeError(
+ m_errorReporter.typeError(
_statement.location(),
"Type " +
valueComponentType->toString() +
@@ -928,7 +1011,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
" or use an explicit conversion."
);
else
- typeError(
+ m_errorReporter.typeError(
_statement.location(),
"Type " +
valueComponentType->toString() +
@@ -946,7 +1029,7 @@ 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()))
{
@@ -958,9 +1041,9 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
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)
- warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead.");
+ m_errorReporter.warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead.");
}
}
}
@@ -975,14 +1058,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() +
@@ -1002,7 +1085,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
_conditional.falseExpression().annotation().isPure;
if (_conditional.annotation().lValueRequested)
- typeError(
+ m_errorReporter.typeError(
_conditional.location(),
"Conditional expression as left value is not supported yet."
);
@@ -1018,17 +1101,19 @@ bool TypeChecker::visit(Assignment const& _assignment)
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get()))
{
if (_assignment.assignmentOperator() != Token::Assign)
- typeError(
+ 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)
@@ -1042,7 +1127,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())) +
@@ -1063,7 +1148,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)
{
@@ -1087,7 +1172,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
{
// 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);
@@ -1097,7 +1182,7 @@ 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]->mobileType();
@@ -1114,7 +1199,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
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
@@ -1146,7 +1231,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)) +
@@ -1167,7 +1252,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())) +
@@ -1200,7 +1285,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
commonType->category() == Type::Category::FixedPoint &&
dynamic_cast<FixedPointType const&>(*commonType).numBits() != 256
))
- warning(
+ 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 "
@@ -1238,20 +1323,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());
@@ -1260,7 +1349,7 @@ 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;
@@ -1274,7 +1363,7 @@ 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());
@@ -1282,18 +1371,15 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
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;
}
@@ -1312,13 +1398,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)
{
@@ -1330,10 +1416,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 " +
@@ -1349,14 +1435,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
@@ -1366,7 +1452,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
@@ -1380,7 +1466,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 " +
@@ -1393,7 +1479,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
if (!found)
- typeError(
+ m_errorReporter.typeError(
_functionCall.location(),
"Named argument does not match function declaration."
);
@@ -1414,11 +1500,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())
- typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
+ m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
solAssert(!!m_scope, "");
m_scope->annotation().contractDependencies.insert(contract);
@@ -1427,7 +1513,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)."
);
@@ -1437,12 +1523,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."
);
@@ -1457,7 +1543,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
_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)
@@ -1488,13 +1574,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() +
@@ -1502,7 +1588,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() +
@@ -1515,7 +1601,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() + ")"
@@ -1563,10 +1649,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
@@ -1576,7 +1662,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();
@@ -1587,7 +1673,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();
@@ -1609,7 +1695,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;
}
@@ -1617,20 +1703,20 @@ 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() + ")"
);
@@ -1660,14 +1746,14 @@ bool TypeChecker::visit(Identifier const& _identifier)
candidates.push_back(declaration);
}
if (candidates.empty())
- fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup.");
+ m_errorReporter.fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup.");
else if (candidates.size() == 1)
annotation.referencedDeclaration = candidates.front();
else
- fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup.");
+ m_errorReporter.fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup.");
}
else if (annotation.overloadedDeclarations.empty())
- fatalTypeError(_identifier.location(), "No candidates for overload resolution found.");
+ m_errorReporter.fatalTypeError(_identifier.location(), "No candidates for overload resolution found.");
else if (annotation.overloadedDeclarations.size() == 1)
annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin();
else
@@ -1683,11 +1769,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(
@@ -1697,7 +1783,7 @@ 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))
@@ -1717,21 +1803,21 @@ void TypeChecker::endVisit(Literal const& _literal)
if (_literal.looksLikeAddress())
{
if (_literal.passesAddressChecksum())
- {
_literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address);
- return;
- }
else
- warning(
+ 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'."
);
}
- _literal.annotation().type = Type::forLiteral(_literal);
- _literal.annotation().isPure = true;
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(
@@ -1772,7 +1858,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() +
@@ -1783,7 +1869,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() +
@@ -1791,7 +1877,23 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte
_expectedType.toString() +
"."
);
- }
+ }
+
+ if (
+ type(_expression)->category() == Type::Category::RationalNumber &&
+ _expectedType.category() == Type::Category::FixedBytes
+ )
+ {
+ auto literal = dynamic_cast<Literal const*>(&_expression);
+
+ 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."
+ );
+ }
+
}
void TypeChecker::requireLValue(Expression const& _expression)
@@ -1800,33 +1902,8 @@ void TypeChecker::requireLValue(Expression const& _expression)
_expression.accept(*this);
if (_expression.annotation().isConstant)
- typeError(_expression.location(), "Cannot assign to a constant variable.");
+ m_errorReporter.typeError(_expression.location(), "Cannot assign to a constant variable.");
else if (!_expression.annotation().isLValue)
- typeError(_expression.location(), "Expression has to be an lvalue.");
+ m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue.");
}
-void TypeChecker::typeError(SourceLocation const& _location, string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::TypeError);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
-
- m_errors.push_back(err);
-}
-
-void TypeChecker::warning(SourceLocation const& _location, string const& _description)
-{
- auto err = make_shared<Error>(Error::Type::Warning);
- *err <<
- errinfo_sourceLocation(_location) <<
- errinfo_comment(_description);
-
- m_errors.push_back(err);
-}
-
-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 88559f44..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,7 +43,7 @@ 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
@@ -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,6 +69,9 @@ 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;
@@ -127,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 03112d2d..403f4b79 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -20,10 +20,8 @@
* Solidity abstract syntax tree.
*/
-#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>
@@ -532,18 +530,26 @@ IdentifierAnnotation& Identifier::annotation() const
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;
- string lit = value();
- return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1;
+ if (!isHexNumber())
+ return false;
+
+ return abs(int(value().length()) - 42) <= 1;
}
bool Literal::passesAddressChecksum() const
{
- string lit = value();
- solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix");
- return dev::passesAddressChecksum(lit, true);
+ solAssert(isHexNumber(), "Expected hex number");
+ return dev::passesAddressChecksum(value(), true);
}
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 02234ffc..e8831dc0 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>
@@ -583,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();
@@ -874,6 +872,8 @@ 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
{
@@ -1314,7 +1314,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.
@@ -1327,8 +1327,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;
@@ -1590,6 +1590,9 @@ 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.
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index a7d89248..45a6dd1a 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -200,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 9ea23687..a90debb2 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -24,7 +24,8 @@
#include <boost/algorithm/string/join.hpp>
#include <libdevcore/UTF8.h>
#include <libsolidity/ast/AST.h>
-#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmPrinter.h>
using namespace std;
@@ -33,49 +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
)
{
- ASTJsonConverter::addJsonNode(
+ ASTJsonConverter::setJsonNode(
_node,
_nodeName,
- std::vector<pair<string const, Json::Value const>>(_attributes),
- _hasChildren
+ std::vector<pair<string, Json::Value>>(std::move(_attributes))
);
}
-
-void ASTJsonConverter::addJsonNode(
+
+void ASTJsonConverter::setJsonNode(
ASTNode const& _node,
- string const& _nodeName,
- std::vector<pair<string const, Json::Value const>> const& _attributes,
- bool _hasChildren = false
+ string const& _nodeType,
+ std::vector<pair<string, Json::Value>>&& _attributes
)
{
- Json::Value node;
-
- node["id"] = Json::UInt64(_node.id());
- node["src"] = sourceLocationToString(_node.location());
- node["name"] = _nodeName;
- if (_attributes.size() != 0)
+ 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);
}
}
@@ -90,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, ".");
+}
+
+Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp)
{
+ 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;
}
-void ASTJsonConverter::print(ostream& _stream)
+void ASTJsonConverter::appendExpressionAttributes(
+ std::vector<pair<string, Json::Value>>& _attributes,
+ ExpressionAnnotation const& _annotation
+)
{
- process();
- _stream << 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;
}
-Json::Value const& ASTJsonConverter::json()
+Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<assembly::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info)
{
- process();
- return m_astJson;
+ 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;
}
-bool ASTJsonConverter::visit(SourceUnit const&)
+void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node)
{
- Json::Value children(Json::arrayValue);
+ _stream << toJson(_node);
+}
- m_astJson["name"] = "SourceUnit";
- m_astJson["children"] = children;
- m_jsonNodePtrs.push(&m_astJson["children"]);
+Json::Value ASTJsonConverter::toJson(ASTNode const& _node)
+{
+ _node.accept(*this);
+ return std::move(m_currentValue);
+}
- return true;
+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)
@@ -125,556 +218,516 @@ 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(Json::UInt64(baseContract->id()));
- 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("constant", _node.isDeclaredConst()),
+ make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()),
make_pair("payable", _node.isPayable()),
- make_pair("visibility", visibility(_node.visibility()))
- }, true);
- return true;
+ 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)
{
- std::vector<pair<string const, Json::Value const>> attributes = {
+ std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
- make_pair("type", type(_node)),
+ 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("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()));
- addJsonNode(_node, "VariableDeclaration", attributes, true);
- return true;
-
+ 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)
{
m_inEvent = true;
- addJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()) }, true);
- return 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)
{
- addJsonNode(_node, "FunctionTypeName", {
+ setJsonNode(_node, "FunctionTypeName", {
make_pair("payable", _node.isPayable()),
make_pair("visibility", visibility(_node.visibility())),
- make_pair("constant", _node.isDeclaredConst())
- }, true);
- return true;
+ 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, "VariableDeclarationStatement", {}, 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;
+ )
+ };
+ appendExpressionAttributes(attributes, _node.annotation());
+ setJsonNode(_node, "Literal", std::move(attributes));
+ return false;
}
-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();
-}
-
-void ASTJsonConverter::endVisit(VariableDeclaration const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(ModifierDefinition const&)
-{
- goUp();
-}
-
-void ASTJsonConverter::endVisit(ModifierInvocation const&)
-{
- goUp();
-}
void ASTJsonConverter::endVisit(EventDefinition const&)
{
m_inEvent = false;
- 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&)
-{
-}
-
-void ASTJsonConverter::endVisit(Break const&)
-{
-}
-
-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&)
-{
-}
-
-void ASTJsonConverter::endVisit(ElementaryTypeNameExpression const&)
-{
-}
-
-void ASTJsonConverter::endVisit(Literal const&)
-{
-}
-
-void ASTJsonConverter::process()
-{
- if (!processed)
- m_ast->accept(*this);
- processed = true;
}
string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility)
@@ -709,6 +762,52 @@ string ASTJsonConverter::location(VariableDeclaration::Location _location)
}
}
+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."));
+ }
+}
+
+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 ."));
+ }
+}
+
+string ASTJsonConverter::literalTokenKind(Token::Value _token)
+{
+ 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)
{
return _expression.annotation().type ? _expression.annotation().type->toString() : "Unknown";
diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h
index bd5e6560..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,82 +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 addJsonNode(
+ void setJsonNode(
ASTNode const& _node,
std::string const& _nodeName,
- std::vector<std::pair<std::string const, Json::Value const>> const& _attributes,
- bool _hasChildren
+ 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();
}
-
+ 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
- bool processed = false;
- Json::Value m_astJson;
- std::stack<Json::Value*> m_jsonNodePtrs;
- ASTNode const* m_ast;
+ Json::Value m_currentValue;
std::map<std::string, unsigned> m_sourceIndices;
};
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 41ee6498..7dc6c4a6 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -22,7 +22,6 @@
#include <libsolidity/ast/Types.h>
-#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libdevcore/CommonIO.h>
@@ -2183,6 +2182,8 @@ string FunctionType::identifier() const
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())
@@ -2247,6 +2248,16 @@ 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_kind == Kind::External, "");
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index c4ffc44c..f7a73ab5 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -933,6 +933,7 @@ public:
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;
@@ -1038,6 +1039,7 @@ public:
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);
@@ -1116,11 +1118,7 @@ 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; }
@@ -1178,6 +1176,7 @@ public:
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 bdd29abd..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;
@@ -449,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;
@@ -731,7 +731,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
_context << Instruction::POP;
}
- // Change of length for a regular array (i.e. length at location, data at sha3(location)).
+ // 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;
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index 51dd9fd2..6875bda1 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -25,8 +25,12 @@
#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>
@@ -120,6 +124,7 @@ 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;
}
@@ -240,6 +245,20 @@ CompilerContext& CompilerContext::appendConditionalInvalid()
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;
@@ -264,12 +283,13 @@ void CompilerContext::appendInlineAssembly(
assembly = &replacedAssembly;
}
- unsigned startStackHeight = stackHeight();
+ int startStackHeight = stackHeight();
- assembly::ExternalIdentifierAccess identifierAccess;
+ julia::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](
assembly::Identifier const& _identifier,
- assembly::IdentifierContext
+ julia::IdentifierContext,
+ bool
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
@@ -277,31 +297,42 @@ void CompilerContext::appendInlineAssembly(
};
identifierAccess.generateCode = [&](
assembly::Identifier const& _identifier,
- assembly::IdentifierContext _context,
- eth::Assembly& _assembly
+ julia::IdentifierContext _context,
+ julia::AbstractAssembly& _assembly
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
solAssert(it != _localVariables.end(), "");
- unsigned stackDepth = _localVariables.end() - it;
- int stackDiff = _assembly.deposit() - startStackHeight + stackDepth;
- if (_context == assembly::IdentifierContext::LValue)
+ 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::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);
}
};
- solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block.");
+ 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 c37142c9..1968c1e1 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -136,13 +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);
+ CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
/// Appends an INVALID instruction
- CompilerContext& appendInvalid();
+ CompilerContext& appendInvalid();
/// Appends a conditional INVALID instruction
- CompilerContext& appendConditionalInvalid();
- /// Returns an "ErrorTag"
- eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
+ 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.
@@ -151,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
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index dc0b340c..7067ddd5 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -128,7 +128,7 @@ 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;
@@ -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
@@ -299,40 +302,15 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
m_context << Instruction::SWAP1 << Instruction::POP;
}
-void CompilerUtils::memoryCopyPrecompile()
-{
- // Stack here: size target source
-
- m_context.appendInlineAssembly(R"(
- {
- let words := div(add(len, 31), 32)
- let cost := add(15, mul(3, words))
- jumpi(invalidJumpLabel, iszero(call(cost, $identityContractAddress, 0, src, len, dst, len)))
- }
- )",
- { "len", "dst", "src" },
- map<string, string> {
- { "$identityContractAddress", toString(identityContractAddress) }
- }
- );
- m_context << Instruction::POP << Instruction::POP << Instruction::POP;
-}
-
void CompilerUtils::memoryCopy32()
{
// Stack here: size target source
m_context.appendInlineAssembly(R"(
{
- jumpi(end, eq(len, 0))
- start:
- mstore(dst, mload(src))
- jumpi(end, iszero(gt(len, 32)))
- dst := add(dst, 32)
- src := add(src, 32)
- len := sub(len, 32)
- jump(start)
- end:
+ for { let i := 0 } lt(i, len) { i := add(i, 32) } {
+ mstore(add(dst, i), mload(add(src, i)))
+ }
}
)",
{ "len", "dst", "src" }
@@ -346,21 +324,22 @@ void CompilerUtils::memoryCopy()
m_context.appendInlineAssembly(R"(
{
- // copy 32 bytes at once
- start32:
- jumpi(end32, lt(len, 32))
- mstore(dst, mload(src))
- dst := add(dst, 32)
- src := add(src, 32)
- len := sub(len, 32)
- jump(start32)
- end32:
+ // 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))
+ // 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" }
@@ -374,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;
@@ -392,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)
@@ -404,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.
@@ -440,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);
}
@@ -469,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.appendConditionalInvalid();
+ if (_asPartOfArgumentDecoding)
+ m_context.appendConditionalRevert();
+ else
+ m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
break;
@@ -488,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)
{
@@ -953,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)
@@ -998,13 +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);
+ convertType(_type, _type, true, false, true);
return numBytes;
}
@@ -1019,6 +1011,18 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack)
m_context << ((u256(1) << _typeOnStack.numBits()) - 1) << Instruction::AND;
}
+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(_padToWords);
@@ -1031,7 +1035,7 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
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 b9ed6757..fb169463 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -109,15 +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.
- /// Stack pre: <size> <target> <source>
- /// Stack post:
- void memoryCopyPrecompile();
/// 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();
@@ -139,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.
@@ -170,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.
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 34ef13c0..c358a519 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;
@@ -106,7 +111,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
- m_context.appendConditionalInvalid();
+ 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.appendInvalid();
+ 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());
}
@@ -519,40 +543,42 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
- ErrorList errors;
- assembly::CodeGenerator codeGen(errors);
unsigned startStackHeight = m_context.stackHeight();
- assembly::ExternalIdentifierAccess identifierAccess;
- identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext)
+ 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, assembly::IdentifierContext _context, eth::Assembly& _assembly)
+ 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 == assembly::IdentifierContext::RValue)
+ if (_context == julia::IdentifierContext::RValue)
{
- int const depositBefore = _assembly.deposit();
+ 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(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
- _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
+ 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())
{
- _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))
@@ -570,7 +596,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
}
else if (m_context.isLocalVariable(decl))
{
- int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
+ int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable);
if (ref->second.isSlot || ref->second.isOffset)
{
solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
@@ -587,7 +613,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
// only slot, offset is zero
if (ref->second.isOffset)
{
- _assembly.append(u256(0));
+ _assembly.appendConstant(u256(0));
return;
}
}
@@ -601,7 +627,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
errinfo_comment("Stack too deep, try removing local variables.")
);
solAssert(variable->type()->sizeOnStack() == 1, "");
- _assembly.append(dupInstruction(stackDiff));
+ _assembly.appendInstruction(dupInstruction(stackDiff));
}
else
solAssert(false, "");
@@ -610,11 +636,11 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
solAssert(contract->isLibrary(), "");
- _assembly.appendLibraryAddress(contract->fullyQualifiedName());
+ _assembly.appendLinkerSymbol(contract->fullyQualifiedName());
}
else
solAssert(false, "Invalid declaration type.");
- solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), "");
+ solAssert(_assembly.stackHeight() - depositBefore == int(ref->second.valueSize), "");
}
else
{
@@ -626,25 +652,24 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
"Can only assign to stack variables in inline assembly."
);
solAssert(variable->type()->sizeOnStack() == 1, "");
- int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 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, try removing local variables.")
+ errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
);
- _assembly.append(swapInstruction(stackDiff));
- _assembly.append(Instruction::POP);
+ _assembly.appendInstruction(swapInstruction(stackDiff));
+ _assembly.appendInstruction(Instruction::POP);
}
};
solAssert(_inlineAssembly.annotation().analysisInfo, "");
- codeGen.assemble(
+ 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;
}
@@ -799,8 +824,7 @@ bool ContractCompiler::visit(Throw const& _throw)
{
CompilerContext::LocationSetter locationSetter(m_context, _throw);
// Do not send back an error detail.
- m_context << u256(0) << u256(0);
- m_context << Instruction::REVERT;
+ m_context.appendRevert();
return false;
}
@@ -873,6 +897,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
solAssert(m_currentFunction, "");
unsigned stackSurplus = 0;
Block const* codeBlock = nullptr;
+ vector<VariableDeclaration const*> addedVariables;
m_modifierDepth++;
@@ -896,6 +921,7 @@ 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
@@ -922,6 +948,8 @@ void ContractCompiler::appendModifierOrFunctionCode()
m_returnTags.pop_back();
CompilerUtils(m_context).popStackSlots(stackSurplus);
+ for (auto var: addedVariables)
+ m_context.removeVariable(*var);
}
m_modifierDepth--;
}
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index f018b311..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();
@@ -434,7 +436,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
- if (_functionCall.annotation().isTypeConversion)
+ if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
{
solAssert(_functionCall.arguments().size() == 1, "");
solAssert(_functionCall.names().empty(), "");
@@ -445,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());
@@ -476,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());
@@ -524,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;
@@ -586,7 +588,7 @@ 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.appendConditionalInvalid();
+ m_context.appendConditionalRevert();
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
break;
@@ -650,7 +652,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
// Check if zero (out of stack or not enough balance).
m_context << Instruction::ISZERO;
- m_context.appendConditionalInvalid();
+ m_context.appendConditionalRevert();
}
break;
case FunctionType::Kind::Selfdestruct:
@@ -659,9 +661,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::SELFDESTRUCT;
break;
case FunctionType::Kind::Revert:
- // memory offset returned - zero length
- m_context << u256(0) << u256(0);
- m_context << Instruction::REVERT;
+ m_context.appendRevert();
break;
case FunctionType::Kind::SHA3:
{
@@ -674,7 +674,7 @@ 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 FunctionType::Kind::Log0:
@@ -721,7 +721,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
true
);
utils().toSizeAfterFreeMemoryPointer();
- m_context << Instruction::SHA3;
+ m_context << Instruction::KECCAK256;
}
else
utils().convertType(
@@ -887,9 +887,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
auto success = m_context.appendConditionalJump();
if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error
- m_context << Instruction::INVALID;
+ m_context.appendInvalid();
else
- m_context << u256(0) << u256(0) << Instruction::REVERT;
+ m_context.appendRevert();
// the success branch
m_context << success;
break;
@@ -1214,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);
}
@@ -1269,7 +1269,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
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)
{
@@ -1367,6 +1367,7 @@ 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))
@@ -1694,7 +1695,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
- m_context.appendConditionalInvalid();
+ m_context.appendConditionalRevert();
existenceChecked = true;
}
@@ -1730,7 +1731,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
- m_context.appendConditionalInvalid();
+ m_context.appendConditionalRevert();
}
utils().popStackSlots(remainsSize);
diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h
index d0a8ac15..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
diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp
index a74a3d74..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 (
@@ -267,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 b6f17907..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.kind())
- {
- case FunctionType::Kind::AddMod:
- case FunctionType::Kind::MulMod:
- {
- //@todo require that third parameter is not zero
- add("(of_int (mod (Int.(");
- add(function.kind() == FunctionType::Kind::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::Kind::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::Kind::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::Kind::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
index dad05a78..7e00ffae 100644
--- a/libsolidity/inlineasm/AsmAnalysis.cpp
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -25,10 +25,10 @@
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
-#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/interface/Utils.h>
+#include <libsolidity/interface/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp>
+#include <boost/algorithm/string.hpp>
#include <memory>
#include <functional>
@@ -38,18 +38,15 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
-AsmAnalyzer::AsmAnalyzer(
- AsmAnalysisInfo& _analysisInfo,
- ErrorList& _errors,
- ExternalIdentifierAccess::Resolver const& _resolver
-):
- m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors)
-{
+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.scopes, m_errors))(_block))
+ if (!(ScopeFiller(m_info, m_errorReporter))(_block))
return false;
return (*this)(_block);
@@ -57,28 +54,31 @@ bool AsmAnalyzer::analyze(Block const& _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.isNumber && _literal.value.size() > 32)
+ if (_literal.kind == assembly::LiteralKind::String && _literal.value.size() > 32)
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
- "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)",
- _literal.location
- ));
+ 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;
@@ -87,18 +87,17 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
- size_t numErrorsBefore = m_errors.size();
+ size_t numErrorsBefore = m_errorReporter.errors().size();
bool success = true;
if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
[&](Scope::Variable const& _var)
{
- if (!_var.active)
+ if (!m_activeVariables.count(&_var))
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Variable " + _identifier.name + " used before it was declared.",
- _identifier.location
- ));
+ m_errorReporter.declarationError(
+ _identifier.location,
+ "Variable " + _identifier.name + " used before it was declared."
+ );
success = false;
}
++m_stackHeight;
@@ -109,11 +108,10 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
},
[&](Scope::Function const&)
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
- "Function " + _identifier.name + " used without being called.",
- _identifier.location
- ));
+ m_errorReporter.typeError(
+ _identifier.location,
+ "Function " + _identifier.name + " used without being called."
+ );
success = false;
}
)))
@@ -123,16 +121,15 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
size_t stackSize(-1);
if (m_resolver)
- stackSize = m_resolver(_identifier, IdentifierContext::RValue);
+ {
+ 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_errors.size())
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Identifier not found.",
- _identifier.location
- ));
+ if (numErrorsBefore == m_errorReporter.errors().size())
+ m_errorReporter.declarationError(_identifier.location, "Identifier not found.");
success = false;
}
m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
@@ -143,15 +140,11 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{
+ solAssert(!m_julia, "");
bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
- {
- int const stackHeight = m_stackHeight;
- if (!boost::apply_visitor(*this, arg))
+ if (!expectExpression(arg))
success = false;
- if (!expectDeposit(1, stackHeight, locationOf(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))
@@ -160,15 +153,15 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
return success;
}
-bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
+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()(FunctionalAssignment const& _assignment)
+bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{
int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_assignment.value);
@@ -181,23 +174,37 @@ bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
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);
- solAssert(m_stackHeight - stackHeight == 1, "Invalid value size.");
- boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true;
+ 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)
{
- Scope& bodyScope = scope(&_funDef.body);
+ Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
+ solAssert(virtualBlock, "");
+ Scope& varScope = scope(virtualBlock);
for (auto const& var: _funDef.arguments + _funDef.returns)
- boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true;
+ {
+ 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();
- m_virtualVariablesInNextBlock = m_stackHeight;
bool success = (*this)(_funDef.body);
@@ -214,141 +221,213 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor(
[&](Scope::Variable const&)
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
- "Attempt to call variable instead of function.",
- _funCall.functionName.location
- ));
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Attempt to call variable instead of function."
+ );
success = false;
},
[&](Scope::Label const&)
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
- "Attempt to call label instead of function.",
- _funCall.functionName.location
- ));
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Attempt to call label instead of function."
+ );
success = false;
},
[&](Scope::Function const& _fun)
{
- arguments = _fun.arguments;
- returns = _fun.returns;
+ /// TODO: compare types too
+ arguments = _fun.arguments.size();
+ returns = _fun.returns.size();
}
)))
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Function not found.",
- _funCall.functionName.location
- ));
+ m_errorReporter.declarationError(_funCall.functionName.location, "Function not found.");
success = false;
}
if (success)
{
if (_funCall.arguments.size() != arguments)
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
- "Expected " +
- boost::lexical_cast<string>(arguments) +
- " arguments but got " +
- boost::lexical_cast<string>(_funCall.arguments.size()) +
- ".",
- _funCall.functionName.location
- ));
+ 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)
- {
- int const stackHeight = m_stackHeight;
- if (!boost::apply_visitor(*this, arg))
- success = false;
- if (!expectDeposit(1, stackHeight, locationOf(arg)))
+ 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 - m_virtualVariablesInNextBlock;
- m_virtualVariablesInNextBlock = 0;
+ int const initialStackHeight = m_stackHeight;
for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s))
success = false;
- for (auto const& identifier: scope(&_block).identifiers)
- if (identifier.second.type() == typeid(Scope::Variable))
- --m_stackHeight;
+ m_stackHeight -= scope(&_block).numberOfVariables();
int const stackDiff = m_stackHeight - initialStackHeight;
if (stackDiff != 0)
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
+ 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).")
- ),
- _block.location
- ));
+ )
+ );
success = false;
}
- m_currentScope = m_currentScope->superScope;
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_errors.size();
+ 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_errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
- "Assignment requires variable.",
- _variable.location
- ));
+ m_errorReporter.typeError(_variable.location, "Assignment requires variable.");
success = false;
}
- else if (!boost::get<Scope::Variable>(*var).active)
+ else if (!m_activeVariables.count(&boost::get<Scope::Variable>(*var)))
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Variable " + _variable.name + " used before it was declared.",
- _variable.location
- ));
+ m_errorReporter.declarationError(
+ _variable.location,
+ "Variable " + _variable.name + " used before it was declared."
+ );
success = false;
}
variableSize = 1;
}
else if (m_resolver)
- variableSize = m_resolver(_variable, IdentifierContext::LValue);
+ {
+ 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_errors.size())
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Variable not found or variable not lvalue.",
- _variable.location
- ));
+ 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))
@@ -358,43 +437,60 @@ bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t
if (_valueSize != variableSize && variableSize != size_t(-1))
{
- m_errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
+ m_errorReporter.typeError(
+ _variable.location,
"Variable size (" +
to_string(variableSize) +
") and value size (" +
to_string(_valueSize) +
- ") do not match.",
- _variable.location
- ));
+ ") do not match."
+ );
success = false;
}
return success;
}
-bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location)
-{
- int stackDiff = m_stackHeight - _oldHeight;
- if (stackDiff != _deposit)
- {
- m_errors.push_back(make_shared<Error>(
- 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>(stackDiff) +
- " item(s).",
- _location
- ));
- return false;
- }
- else
- return true;
-}
-
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
index 426ee0d2..9b2a8f9c 100644
--- a/libsolidity/inlineasm/AsmAnalysis.h
+++ b/libsolidity/inlineasm/AsmAnalysis.h
@@ -20,10 +20,14 @@
#pragma once
-#include <libsolidity/inlineasm/AsmStack.h>
-
#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>
@@ -33,23 +37,10 @@ namespace dev
{
namespace solidity
{
+class ErrorReporter;
namespace assembly
{
-struct Literal;
-struct Block;
-struct Label;
-struct FunctionalInstruction;
-struct FunctionalAssignment;
-struct VariableDeclaration;
-struct Instruction;
-struct Identifier;
-struct Assignment;
-struct FunctionDefinition;
-struct FunctionCall;
-
-struct Scope;
-
struct AsmAnalysisInfo;
/**
@@ -60,11 +51,12 @@ struct AsmAnalysisInfo;
class AsmAnalyzer: public boost::static_visitor<bool>
{
public:
- AsmAnalyzer(
+ explicit AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo,
- ErrorList& _errors,
- ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver()
- );
+ 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);
@@ -73,29 +65,37 @@ public:
bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
bool operator()(assembly::Label const& _label);
- bool operator()(assembly::Assignment const&);
- bool operator()(assembly::FunctionalAssignment const& _functionalAssignment);
+ 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));
- bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
+
Scope& scope(assembly::Block const* _block);
+ void expectValidType(std::string const& type, SourceLocation const& _location);
+ void warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location);
- /// This is used when we enter the body of a function definition. There, the parameters
- /// and return parameters appear as variables which are already on the stack before
- /// we enter the block.
- int m_virtualVariablesInNextBlock = 0;
int m_stackHeight = 0;
- ExternalIdentifierAccess::Resolver const& m_resolver;
+ 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;
- ErrorList& m_errors;
+ ErrorReporter& m_errorReporter;
+ bool m_julia = false;
};
}
diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libsolidity/inlineasm/AsmAnalysisInfo.h
index e21eb2c5..bd3b28c4 100644
--- a/libsolidity/inlineasm/AsmAnalysisInfo.h
+++ b/libsolidity/inlineasm/AsmAnalysisInfo.h
@@ -20,10 +20,13 @@
#pragma once
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
#include <boost/variant.hpp>
#include <map>
#include <memory>
+#include <vector>
namespace dev
{
@@ -32,28 +35,16 @@ namespace solidity
namespace assembly
{
-struct Literal;
-struct Block;
-struct Label;
-struct FunctionalInstruction;
-struct FunctionalAssignment;
-struct VariableDeclaration;
-struct Instruction;
-struct Identifier;
-struct Assignment;
-struct FunctionDefinition;
-struct FunctionCall;
-
struct Scope;
-using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>;
-
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 9ef3e6e7..74743737 100644
--- a/libsolidity/inlineasm/AsmCodeGen.cpp
+++ b/libsolidity/inlineasm/AsmCodeGen.cpp
@@ -32,6 +32,9 @@
#include <libevmasm/SourceLocation.h>
#include <libevmasm/Instruction.h>
+#include <libjulia/backends/evm/AbstractAssembly.h>
+#include <libjulia/backends/evm/EVMCodeTransform.h>
+
#include <libdevcore/CommonIO.h>
#include <boost/range/adaptor/reversed.hpp>
@@ -46,268 +49,101 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
-struct GeneratorState
-{
- GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly):
- errors(_errors), info(_analysisInfo), assembly(_assembly) {}
-
- size_t newLabelId()
- {
- return assemblyTagToIdentifier(assembly.newTag());
- }
-
- size_t assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
- {
- u256 id = _tag.data();
- solAssert(id <= std::numeric_limits<size_t>::max(), "Tag id too large.");
- return size_t(id);
- }
-
- ErrorList& errors;
- AsmAnalysisInfo info;
- eth::Assembly& assembly;
-};
-
-class CodeTransform: public boost::static_visitor<>
+class EthAssemblyAdapter: public julia::AbstractAssembly
{
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::Block const& _block,
- assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess()
- ): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit())
+ EthAssemblyAdapter(eth::Assembly& _assembly):
+ m_assembly(_assembly)
{
}
-
-private:
- CodeTransform(
- GeneratorState& _state,
- assembly::Block const& _block,
- assembly::ExternalIdentifierAccess const& _identifierAccess,
- int _initialDeposit
- ):
- m_state(_state),
- m_scope(*m_state.info.scopes.at(&_block)),
- m_identifierAccess(_identifierAccess),
- m_initialDeposit(_initialDeposit)
+ virtual void setSourceLocation(SourceLocation const& _location) override
{
- int blockStartDeposit = m_state.assembly.deposit();
- std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
-
- m_state.assembly.setSourceLocation(_block.location);
-
- // pop variables
- for (auto const& identifier: m_scope.identifiers)
- if (identifier.second.type() == typeid(Scope::Variable))
- m_state.assembly.append(solidity::Instruction::POP);
-
- int deposit = m_state.assembly.deposit() - blockStartDeposit;
- solAssert(deposit == 0, "Invalid stack height at end of block.");
+ m_assembly.setSourceLocation(_location);
}
-
-public:
- void operator()(assembly::Instruction const& _instruction)
- {
- m_state.assembly.setSourceLocation(_instruction.location);
- m_state.assembly.append(_instruction.instruction);
- checkStackHeight(&_instruction);
- }
- void operator()(assembly::Literal const& _literal)
+ virtual int stackHeight() const override { return m_assembly.deposit(); }
+ virtual void appendInstruction(solidity::Instruction _instruction) override
{
- m_state.assembly.setSourceLocation(_literal.location);
- if (_literal.isNumber)
- m_state.assembly.append(u256(_literal.value));
- else
- {
- solAssert(_literal.value.size() <= 32, "");
- m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
- }
- checkStackHeight(&_literal);
+ m_assembly.append(_instruction);
}
- void operator()(assembly::Identifier const& _identifier)
+ virtual void appendConstant(u256 const& _constant) override
{
- m_state.assembly.setSourceLocation(_identifier.location);
- // First search internals, then externals.
- if (m_scope.lookup(_identifier.name, Scope::NonconstVisitor(
- [=](Scope::Variable& _var)
- {
- if (int heightDiff = variableHeightDiff(_var, _identifier.location, false))
- m_state.assembly.append(solidity::dupInstruction(heightDiff));
- else
- // Store something to balance the stack
- m_state.assembly.append(u256(0));
- },
- [=](Scope::Label& _label)
- {
- assignLabelIdIfUnset(_label);
- m_state.assembly.append(eth::AssemblyItem(eth::PushTag, _label.id));
- },
- [=](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_state.assembly);
- checkStackHeight(&_identifier);
+ m_assembly.append(_constant);
}
- void operator()(FunctionalInstruction const& _instr)
+ /// Append a label.
+ virtual void appendLabel(LabelID _labelId) 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);
- }
- (*this)(_instr.instruction);
- checkStackHeight(&_instr);
+ m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId));
}
- void operator()(assembly::FunctionCall const&)
+ /// Append a label reference.
+ virtual void appendLabelReference(LabelID _labelId) override
{
- solAssert(false, "Function call not removed during desugaring phase.");
+ m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId));
}
- void operator()(Label const& _label)
+ virtual size_t newLabelId() override
{
- m_state.assembly.setSourceLocation(_label.location);
- solAssert(m_scope.identifiers.count(_label.name), "");
- Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name));
- assignLabelIdIfUnset(label);
- m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id));
- checkStackHeight(&_label);
+ return assemblyTagToIdentifier(m_assembly.newTag());
}
- void operator()(assembly::Assignment const& _assignment)
+ virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override
{
- m_state.assembly.setSourceLocation(_assignment.location);
- generateAssignment(_assignment.variableName, _assignment.location);
- checkStackHeight(&_assignment);
+ m_assembly.appendLibraryAddress(_linkerSymbol);
}
- void operator()(FunctionalAssignment const& _assignment)
+ virtual void appendJump(int _stackDiffAfter) override
{
- int height = m_state.assembly.deposit();
- boost::apply_visitor(*this, *_assignment.value);
- expectDeposit(1, height);
- m_state.assembly.setSourceLocation(_assignment.location);
- generateAssignment(_assignment.variableName, _assignment.location);
- checkStackHeight(&_assignment);
+ appendInstruction(solidity::Instruction::JUMP);
+ m_assembly.adjustDeposit(_stackDiffAfter);
}
- void operator()(assembly::VariableDeclaration const& _varDecl)
+ virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override
{
- int height = m_state.assembly.deposit();
- boost::apply_visitor(*this, *_varDecl.value);
- expectDeposit(1, height);
- auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name));
- var.stackHeight = height;
- var.active = true;
+ appendLabelReference(_labelId);
+ appendJump(_stackDiffAfter);
}
- void operator()(assembly::Block const& _block)
+ virtual void appendJumpToIf(LabelID _labelId) override
{
- CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit);
- checkStackHeight(&_block);
+ appendLabelReference(_labelId);
+ appendInstruction(solidity::Instruction::JUMPI);
}
- void operator()(assembly::FunctionDefinition const&)
+ virtual void appendBeginsub(LabelID, int) override
{
- solAssert(false, "Function definition not removed during desugaring phase.");
+ // TODO we could emulate that, though
+ solAssert(false, "BEGINSUB not implemented for EVM 1.0");
}
-
-private:
- void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location)
+ /// Call a subroutine.
+ virtual void appendJumpsub(LabelID, int, int) override
{
- auto var = m_scope.lookup(_variableName.name);
- if (var)
- {
- Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
- if (int heightDiff = variableHeightDiff(_var, _location, true))
- m_state.assembly.append(solidity::swapInstruction(heightDiff - 1));
- m_state.assembly.append(solidity::Instruction::POP);
- }
- else
- {
- solAssert(
- m_identifierAccess.generateCode,
- "Identifier not found and no external access available."
- );
- m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly);
- }
+ // TODO we could emulate that, though
+ solAssert(false, "JUMPSUB not implemented for EVM 1.0");
}
- /// Determines the stack height difference to the given variables. Automatically generates
- /// errors if it is not yet in scope or the height difference is too large. Returns 0 on
- /// errors and the (positive) stack height difference otherwise.
- int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap)
+ /// Return from a subroutine.
+ virtual void appendReturnsub(int, int) override
{
- int heightDiff = m_state.assembly.deposit() - _var.stackHeight;
- if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
- {
- //@TODO move this to analysis phase.
- m_state.errors.push_back(make_shared<Error>(
- Error::Type::TypeError,
- "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
- _location
- ));
- return 0;
- }
- else
- return heightDiff;
+ // TODO we could emulate that, though
+ solAssert(false, "RETURNSUB not implemented for EVM 1.0");
}
- void expectDeposit(int _deposit, int _oldHeight)
+ virtual void appendAssemblySize() override
{
- solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit.");
+ m_assembly.appendProgramSize();
}
- void checkStackHeight(void const* _astElement)
- {
- solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
- solAssert(
- m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit,
- "Stack height mismatch between analysis and code generation phase."
- );
- }
-
- /// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
- void assignLabelIdIfUnset(Scope::Label& _label)
+private:
+ LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
{
- if (_label.id == Scope::Label::unassignedLabelId)
- _label.id = m_state.newLabelId();
- else if (_label.id == Scope::Label::errorLabelId)
- _label.id = size_t(m_state.assembly.errorTag().data());
+ u256 id = _tag.data();
+ solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large.");
+ return LabelID(id);
}
-
- GeneratorState& m_state;
- Scope& m_scope;
- ExternalIdentifierAccess m_identifierAccess;
- int const m_initialDeposit;
+ eth::Assembly& m_assembly;
};
-eth::Assembly assembly::CodeGenerator::assemble(
- Block const& _parsedData,
- AsmAnalysisInfo& _analysisInfo,
- ExternalIdentifierAccess const& _identifierAccess
-)
-{
- eth::Assembly assembly;
- GeneratorState state(m_errors, _analysisInfo, assembly);
- CodeTransform(state, _parsedData, _identifierAccess);
- return assembly;
-}
-
void assembly::CodeGenerator::assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
eth::Assembly& _assembly,
- ExternalIdentifierAccess const& _identifierAccess
+ julia::ExternalIdentifierAccess const& _identifierAccess
)
{
- GeneratorState state(m_errors, _analysisInfo, _assembly);
- CodeTransform(state, _parsedData, _identifierAccess);
+ EthAssemblyAdapter assemblyAdapter(_assembly);
+ julia::CodeTransform(assemblyAdapter, _analysisInfo, false, false, _identifierAccess)(_parsedData);
}
diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h
index e830e047..2a36a590 100644
--- a/libsolidity/inlineasm/AsmCodeGen.h
+++ b/libsolidity/inlineasm/AsmCodeGen.h
@@ -23,7 +23,6 @@
#pragma once
#include <libsolidity/inlineasm/AsmAnalysis.h>
-#include <libsolidity/interface/Exceptions.h>
#include <functional>
@@ -42,24 +41,13 @@ struct Block;
class CodeGenerator
{
public:
- CodeGenerator(ErrorList& _errors):
- m_errors(_errors) {}
- /// Performs code generation and @returns the result.
- eth::Assembly assemble(
- Block const& _parsedData,
- AsmAnalysisInfo& _analysisInfo,
- ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
- );
/// Performs code generation and appends generated to to _assembly.
- void assemble(
+ static void assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
eth::Assembly& _assembly,
- ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
+ julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess()
);
-
-private:
- ErrorList& m_errors;
};
}
diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h
index d61b5803..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,37 +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 FunctionDefinition;
-struct FunctionCall;
-struct Block;
-using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, 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; };
struct FunctionCall { SourceLocation location; Identifier functionName; 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; };
+/// 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; std::vector<std::string> arguments; std::vector<std::string> returns; Block body; };
+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 d7f78958..d282a30d 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -21,10 +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>
-#include <libsolidity/interface/Exceptions.h>
using namespace std;
using namespace dev;
@@ -40,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;
@@ -50,16 +50,16 @@ 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();
@@ -67,23 +67,43 @@ assembly::Statement Parser::parseStatement()
return parseFunctionDefinition();
case Token::LBrace:
return parseBlock();
+ case Token::Switch:
+ {
+ 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::Assignment assignment = createWithLocation<assembly::Assignment>();
- m_scanner->next();
+ 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;
}
@@ -91,50 +111,100 @@ 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);
+ assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location);
if (!m_julia && instructions().count(identifier.name))
fatalParserError("Cannot use instruction names for identifier names.");
- m_scanner->next();
- funAss.variableName = identifier;
- funAss.value.reset(new Statement(parseExpression()));
- funAss.location.end = locationOf(*funAss.value).end;
- return funAss;
+ 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;
}
@@ -159,14 +229,30 @@ std::map<string, dev::solidity::Instruction> const& Parser::instructions()
// 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:
@@ -174,14 +260,14 @@ 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 (!m_julia && instructions().count(literal))
{
@@ -190,28 +276,62 @@ assembly::Statement Parser::parseElementaryOperation(bool _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;
}
@@ -219,7 +339,14 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
{
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let);
- varDecl.name = expectAsmIdentifier();
+ 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()));
@@ -233,22 +360,22 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::Function);
funDef.name = expectAsmIdentifier();
expectToken(Token::LParen);
- while (m_scanner->currentToken() != Token::RParen)
+ while (currentToken() != Token::RParen)
{
- funDef.arguments.push_back(expectAsmIdentifier());
- if (m_scanner->currentToken() == Token::RParen)
+ funDef.arguments.emplace_back(parseTypedName());
+ if (currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
}
expectToken(Token::RParen);
- if (m_scanner->currentToken() == Token::Sub)
+ if (currentToken() == Token::Sub)
{
expectToken(Token::Sub);
expectToken(Token::GreaterThan);
while (true)
{
- funDef.returns.push_back(expectAsmIdentifier());
- if (m_scanner->currentToken() == Token::LBrace)
+ funDef.returns.emplace_back(parseTypedName());
+ if (currentToken() == Token::LBrace)
break;
expectToken(Token::Comma);
}
@@ -258,7 +385,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
return funDef;
}
-assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _instruction)
+assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
{
if (_instruction.type() == typeid(Instruction))
{
@@ -276,26 +403,40 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in
unsigned args = unsigned(instrInfo.args);
for (unsigned i = 0; i < args; ++i)
{
+ /// check for premature closing parentheses
+ if (currentToken() == Token::RParen)
+ fatalParserError(string(
+ "Expected expression (\"" +
+ instructionNames().at(instr) +
+ "\" expects " +
+ boost::lexical_cast<string>(args) +
+ " arguments)"
+ ));
+
ret.arguments.emplace_back(parseExpression());
if (i != args - 1)
{
- if (m_scanner->currentToken() != Token::Comma)
+ if (currentToken() != Token::Comma)
fatalParserError(string(
- "Expected comma (" +
- instrInfo.name +
- " expects " +
+ "Expected comma (\"" +
+ instructionNames().at(instr) +
+ "\" expects " +
boost::lexical_cast<string>(args) +
" arguments)"
));
else
- m_scanner->next();
+ advance();
}
}
ret.location.end = endPosition();
- if (m_scanner->currentToken() == Token::Comma)
- fatalParserError(
- string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)")
- );
+ if (currentToken() == Token::Comma)
+ fatalParserError(string(
+ "Expected ')' (\"" +
+ instructionNames().at(instr) +
+ "\" expects " +
+ boost::lexical_cast<string>(args) +
+ " arguments)"
+ ));
expectToken(Token::RParen);
return ret;
}
@@ -305,10 +446,10 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in
ret.functionName = std::move(boost::get<Identifier>(_instruction));
ret.location = ret.functionName.location;
expectToken(Token::LParen);
- while (m_scanner->currentToken() != Token::RParen)
+ while (currentToken() != Token::RParen)
{
ret.arguments.emplace_back(parseExpression());
- if (m_scanner->currentToken() == Token::RParen)
+ if (currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
}
@@ -317,15 +458,40 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in
return ret;
}
else
- fatalParserError("Assembly instruction or function name required in front of \"(\")");
+ fatalParserError(
+ m_julia ?
+ "Function name expected." :
+ "Assembly instruction or function name required in front of \"(\")"
+ );
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 = m_scanner->currentLiteral();
- if (!m_julia && instructions().count(name))
+ 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 c55fd2ac..45708afd 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -37,7 +37,7 @@ namespace assembly
class Parser: public ParserBase
{
public:
- explicit Parser(ErrorList& _errors, bool _julia = false): ParserBase(_errors), m_julia(_julia) {}
+ 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,13 +62,17 @@ protected:
Block parseBlock();
Statement parseStatement();
+ Case parseCase();
+ ForLoop parseForLoop();
/// Parses a functional expression that has to push exactly one stack element
Statement parseExpression();
- std::map<std::string, dev::solidity::Instruction> const& instructions();
+ 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();
FunctionDefinition parseFunctionDefinition();
- Statement parseFunctionalInstruction(Statement&& _instruction);
+ Statement parseCall(Statement&& _instruction);
+ TypedName parseTypedName();
std::string expectAsmIdentifier();
private:
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index 252e91f9..062ff453 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -21,8 +21,8 @@
*/
#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>
@@ -40,13 +40,22 @@ using namespace dev::solidity::assembly;
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)
{
- if (_literal.isNumber)
- return _literal.value;
+ 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 == '\\')
@@ -73,7 +82,7 @@ string AsmPrinter::operator()(assembly::Literal const& _literal)
}
else
out += c;
- return "\"" + out + "\"";
+ return "\"" + out + "\"" + appendTypeName(_literal.type);
}
string AsmPrinter::operator()(assembly::Identifier const& _identifier)
@@ -83,6 +92,7 @@ string AsmPrinter::operator()(assembly::Identifier const& _identifier)
string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functionalInstruction)
{
+ solAssert(!m_julia, "");
return
(*this)(_functionalInstruction.instruction) +
"(" +
@@ -94,29 +104,56 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional
string AsmPrinter::operator()(assembly::Label const& _label)
{
+ solAssert(!m_julia, "");
return _label.name + ":";
}
-string AsmPrinter::operator()(assembly::Assignment const& _assignment)
+string AsmPrinter::operator()(assembly::StackAssignment const& _assignment)
{
+ solAssert(!m_julia, "");
return "=: " + (*this)(_assignment.variableName);
}
-string AsmPrinter::operator()(assembly::FunctionalAssignment const& _functionalAssignment)
+string AsmPrinter::operator()(assembly::Assignment const& _assignment)
{
- return (*this)(_functionalAssignment.variableName) + " := " + boost::apply_visitor(*this, *_functionalAssignment.value);
+ return (*this)(_assignment.variableName) + " := " + boost::apply_visitor(*this, *_assignment.value);
}
string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration)
{
- return "let " + _variableDeclaration.name + " := " + boost::apply_visitor(*this, *_variableDeclaration.value);
+ 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 + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")";
+ 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 += " -> " + boost::algorithm::join(_functionDefinition.returns, ", ");
+ {
+ 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);
}
@@ -130,6 +167,33 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
")";
}
+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())
@@ -141,3 +205,10 @@ string AsmPrinter::operator()(Block const& _block)
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
index a7a1de0a..f57dddc8 100644
--- a/libsolidity/inlineasm/AsmPrinter.h
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -22,6 +22,8 @@
#pragma once
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
#include <boost/variant.hpp>
namespace dev
@@ -30,32 +32,30 @@ namespace solidity
{
namespace assembly
{
-struct Instruction;
-struct Literal;
-struct Identifier;
-struct FunctionalInstruction;
-struct Label;
-struct Assignment;
-struct FunctionalAssignment;
-struct VariableDeclaration;
-struct FunctionDefinition;
-struct FunctionCall;
-struct Block;
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::FunctionalAssignment const& _functionalAssignment);
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
index 609dca16..315d5953 100644
--- a/libsolidity/inlineasm/AsmScope.cpp
+++ b/libsolidity/inlineasm/AsmScope.cpp
@@ -32,19 +32,21 @@ bool Scope::registerLabel(string const& _name)
return true;
}
-bool Scope::registerVariable(string const& _name)
+bool Scope::registerVariable(string const& _name, JuliaType const& _type)
{
if (exists(_name))
return false;
- identifiers[_name] = Variable();
+ Variable variable;
+ variable.type = _type;
+ identifiers[_name] = variable;
return true;
}
-bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns)
+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);
+ identifiers[_name] = Function{_arguments, _returns};
return true;
}
@@ -77,3 +79,20 @@ bool Scope::exists(string const& _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
index 37e0f0b8..cc240565 100644
--- a/libsolidity/inlineasm/AsmScope.h
+++ b/libsolidity/inlineasm/AsmScope.h
@@ -23,6 +23,7 @@
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
+#include <boost/optional.hpp>
#include <functional>
#include <memory>
@@ -61,36 +62,28 @@ struct GenericVisitor<>: public boost::static_visitor<> {
struct Scope
{
- struct Variable
- {
- /// Used during code generation to store the stack height. @todo move there.
- int stackHeight = 0;
- /// Used during analysis to check whether we already passed the declaration inside the block.
- /// @todo move there.
- bool active = false;
- };
-
- struct Label
- {
- size_t id = unassignedLabelId;
- static const size_t errorLabelId = -1;
- static const size_t unassignedLabelId = 0;
- };
+ using JuliaType = std::string;
+ using LabelID = size_t;
+ struct Variable { JuliaType type; };
+ struct Label { };
struct Function
{
- Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {}
- size_t arguments = 0;
- size_t returns = 0;
+ 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);
+ bool registerVariable(std::string const& _name, JuliaType const& _type);
bool registerLabel(std::string const& _name);
- bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns);
+ 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
@@ -116,6 +109,11 @@ struct Scope
/// 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.
diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp
index de6fbdaa..5b3174b8 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.cpp
+++ b/libsolidity/inlineasm/AsmScopeFiller.cpp
@@ -22,9 +22,10 @@
#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 <libsolidity/interface/Utils.h>
#include <boost/range/adaptor/reversed.hpp>
@@ -36,13 +37,9 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
-ScopeFiller::ScopeFiller(ScopeFiller::Scopes& _scopes, ErrorList& _errors):
- m_scopes(_scopes), m_errors(_errors)
+ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter):
+ m_info(_info), m_errorReporter(_errorReporter)
{
- // Make the Solidity ErrorTag available to inline assembly
- Scope::Label errorLabel;
- errorLabel.id = Scope::Label::errorLabelId;
- scope(nullptr).identifiers["invalidJumpLabel"] = errorLabel;
m_currentScope = &scope(nullptr);
}
@@ -51,11 +48,10 @@ bool ScopeFiller::operator()(Label const& _item)
if (!m_currentScope->registerLabel(_item.name))
{
//@TODO secondary location
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Label name " + _item.name + " already taken in this scope.",
- _item.location
- ));
+ m_errorReporter.declarationError(
+ _item.location,
+ "Label name " + _item.name + " already taken in this scope."
+ );
return false;
}
return true;
@@ -63,32 +59,75 @@ bool ScopeFiller::operator()(Label const& _item)
bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
{
- return registerVariable(_varDecl.name, _varDecl.location, *m_currentScope);
+ 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;
- if (!m_currentScope->registerFunction(_funDef.name, _funDef.arguments.size(), _funDef.returns.size()))
+ 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_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Function name " + _funDef.name + " already taken in this scope.",
- _funDef.location
- ));
+ m_errorReporter.declarationError(
+ _funDef.location,
+ "Function name " + _funDef.name + " already taken in this scope."
+ );
success = false;
}
- Scope& body = scope(&_funDef.body);
- body.superScope = m_currentScope;
- body.functionScope = true;
+
+ 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, body))
+ 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;
}
@@ -106,16 +145,15 @@ bool ScopeFiller::operator()(Block const& _block)
return success;
}
-bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope)
+bool ScopeFiller::registerVariable(TypedName const& _name, SourceLocation const& _location, Scope& _scope)
{
- if (!_scope.registerVariable(_name))
+ if (!_scope.registerVariable(_name.name, _name.type))
{
//@TODO secondary location
- m_errors.push_back(make_shared<Error>(
- Error::Type::DeclarationError,
- "Variable name " + _name + " already taken in this scope.",
- _location
- ));
+ m_errorReporter.declarationError(
+ _location,
+ "Variable name " + _name.name + " already taken in this scope."
+ );
return false;
}
return true;
@@ -123,7 +161,7 @@ bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _l
Scope& ScopeFiller::scope(Block const* _block)
{
- auto& scope = m_scopes[_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
index bb62948b..80c03d2c 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.h
+++ b/libsolidity/inlineasm/AsmScopeFiller.h
@@ -20,7 +20,7 @@
#pragma once
-#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/inlineasm/AsmDataForward.h>
#include <boost/variant.hpp>
@@ -29,24 +29,16 @@
namespace dev
{
+struct SourceLocation;
namespace solidity
{
+class ErrorReporter;
namespace assembly
{
-struct Literal;
-struct Block;
-struct Label;
-struct FunctionalInstruction;
-struct FunctionalAssignment;
-struct VariableDeclaration;
-struct Instruction;
-struct Identifier;
-struct Assignment;
-struct FunctionDefinition;
-struct FunctionCall;
-
+struct TypedName;
struct Scope;
+struct AsmAnalysisInfo;
/**
* Fills scopes with identifiers and checks for name clashes.
@@ -55,24 +47,25 @@ struct Scope;
class ScopeFiller: public boost::static_visitor<bool>
{
public:
- using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
- ScopeFiller(Scopes& _scopes, ErrorList& _errors);
+ 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::FunctionalAssignment 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(
- std::string const& _name,
+ TypedName const& _name,
SourceLocation const& _location,
Scope& _scope
);
@@ -80,8 +73,8 @@ private:
Scope& scope(assembly::Block const* _block);
Scope* m_currentScope = nullptr;
- Scopes& m_scopes;
- ErrorList& m_errors;
+ AsmAnalysisInfo& m_info;
+ ErrorReporter& m_errorReporter;
};
}
diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp
deleted file mode 100644
index c2a7d8ea..00000000
--- a/libsolidity/inlineasm/AsmStack.cpp
+++ /dev/null
@@ -1,95 +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 <libsolidity/inlineasm/AsmParser.h>
-#include <libsolidity/inlineasm/AsmCodeGen.h>
-#include <libsolidity/inlineasm/AsmPrinter.h>
-#include <libsolidity/inlineasm/AsmAnalysis.h>
-#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
-
-#include <libsolidity/parsing/Scanner.h>
-
-#include <libevmasm/Assembly.h>
-#include <libevmasm/SourceLocation.h>
-
-#include <memory>
-
-using namespace std;
-using namespace dev;
-using namespace dev::solidity;
-using namespace dev::solidity::assembly;
-
-bool InlineAssemblyStack::parse(
- shared_ptr<Scanner> const& _scanner,
- ExternalIdentifierAccess::Resolver const& _resolver
-)
-{
- m_parserResult = make_shared<Block>();
- Parser parser(m_errors);
- auto result = parser.parse(_scanner);
- if (!result)
- return false;
-
- *m_parserResult = std::move(*result);
- AsmAnalysisInfo analysisInfo;
- return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult);
-}
-
-string InlineAssemblyStack::toString()
-{
- return AsmPrinter()(*m_parserResult);
-}
-
-eth::Assembly InlineAssemblyStack::assemble()
-{
- AsmAnalysisInfo analysisInfo;
- AsmAnalyzer analyzer(analysisInfo, m_errors);
- solAssert(analyzer.analyze(*m_parserResult), "");
- CodeGenerator codeGen(m_errors);
- return codeGen.assemble(*m_parserResult, analysisInfo);
-}
-
-bool InlineAssemblyStack::parseAndAssemble(
- string const& _input,
- eth::Assembly& _assembly,
- ExternalIdentifierAccess const& _identifierAccess
-)
-{
- ErrorList errors;
- auto scanner = make_shared<Scanner>(CharStream(_input), "--CODEGEN--");
- auto parserResult = Parser(errors).parse(scanner);
- if (!errors.empty())
- return false;
- solAssert(parserResult, "");
-
- AsmAnalysisInfo analysisInfo;
- AsmAnalyzer analyzer(analysisInfo, errors, _identifierAccess.resolve);
- solAssert(analyzer.analyze(*parserResult), "");
- CodeGenerator(errors).assemble(*parserResult, analysisInfo, _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 77a7e02a..00000000
--- a/libsolidity/inlineasm/AsmStack.h
+++ /dev/null
@@ -1,92 +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 <libsolidity/interface/Exceptions.h>
-
-#include <string>
-#include <functional>
-
-namespace dev
-{
-namespace eth
-{
-class Assembly;
-}
-namespace solidity
-{
-class Scanner;
-namespace assembly
-{
-struct Block;
-struct Identifier;
-
-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(assembly::Identifier const&, IdentifierContext)>;
- /// 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(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>;
- /// 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;
-};
-
-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,
- ExternalIdentifierAccess::Resolver const& _externalIdentifierResolver = ExternalIdentifierAccess::Resolver()
- );
- /// Converts the parser result back into a string form (not necessarily the same form
- /// as the source form, but it should parse into the same parsed form again).
- std::string toString();
-
- 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,
- ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
- );
-
- 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 9c9c9614..e2507821 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -37,9 +37,9 @@
#include <libsolidity/analysis/PostTypeChecker.h>
#include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/codegen/Compiler.h>
-#include <libsolidity/interface/InterfaceHandler.h>
+#include <libsolidity/interface/ABI.h>
+#include <libsolidity/interface/Natspec.h>
#include <libsolidity/interface/GasEstimator.h>
-#include <libsolidity/formal/Why3Translator.h>
#include <libevmasm/Exceptions.h>
@@ -56,9 +56,6 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
-CompilerStack::CompilerStack(ReadFile::Callback const& _readFile):
- m_readFile(_readFile) {}
-
void CompilerStack::setRemappings(vector<string> const& _remappings)
{
vector<Remapping> remappings;
@@ -87,6 +84,7 @@ void CompilerStack::reset(bool _keepSources)
}
else
{
+ m_stackState = Empty;
m_sources.clear();
}
m_optimize = false;
@@ -95,8 +93,7 @@ void CompilerStack::reset(bool _keepSources)
m_scopes.clear();
m_sourceOrder.clear();
m_contracts.clear();
- m_errors.clear();
- m_stackState = Empty;
+ m_errorReporter.clear();
}
bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary)
@@ -120,15 +117,11 @@ bool CompilerStack::parse()
//reset
if(m_stackState != SourcesSet)
return false;
- m_errors.clear();
+ 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)
@@ -138,9 +131,9 @@ bool CompilerStack::parse()
string const& path = sourcesToParse[i];
Source& source = m_sources[path];
source.scanner->reset();
- source.ast = Parser(m_errors).parse(source.scanner);
+ 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;
@@ -153,7 +146,7 @@ bool CompilerStack::parse()
}
}
}
- if (Error::containsOnlyWarnings(m_errors))
+ if (Error::containsOnlyWarnings(m_errorReporter.errors()))
{
m_stackState = ParsingSuccessful;
return true;
@@ -169,18 +162,18 @@ bool CompilerStack::analyze()
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_scopes, m_errors);
+ NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!resolver.registerDeclarations(*source->ast))
return false;
@@ -216,11 +209,11 @@ bool CompilerStack::analyze()
{
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;
@@ -236,7 +229,7 @@ bool CompilerStack::analyze()
if (noErrors)
{
- PostTypeChecker postTypeChecker(m_errors);
+ PostTypeChecker postTypeChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!postTypeChecker.check(*source->ast))
noErrors = false;
@@ -244,7 +237,7 @@ bool CompilerStack::analyze()
if (noErrors)
{
- StaticAnalyzer staticAnalyzer(m_errors);
+ StaticAnalyzer staticAnalyzer(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!staticAnalyzer.analyze(*source->ast))
noErrors = false;
@@ -322,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);
@@ -406,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);
@@ -444,20 +414,31 @@ map<string, unsigned> CompilerStack::sourceIndices() const
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_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
+{
+ 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."));
@@ -474,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_stackState != CompilationSuccessful)
- BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
+ 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;
}
@@ -564,11 +559,10 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
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.contentsOrErrorMessage);
- m_errors.push_back(std::move(err));
+ m_errorReporter.parserError(
+ import->location(),
+ string("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage)
+ );
continue;
}
}
@@ -734,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())
@@ -821,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);
}
diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h
index c1d344ca..03a1b806 100644
--- a/libsolidity/interface/CompilerStack.h
+++ b/libsolidity/interface/CompilerStack.h
@@ -35,7 +35,7 @@
#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
@@ -59,15 +59,14 @@ 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
};
/**
@@ -78,10 +77,21 @@ enum class DocumentationType: uint8_t
class CompilerStack: boost::noncopyable
{
public:
+ enum State {
+ Empty,
+ SourcesSet,
+ ParsingSuccessful,
+ AnalysisSuccessful,
+ CompilationSuccessful
+ };
+
/// Creates a new compiler stack.
/// @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());
+ 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);
@@ -115,7 +125,6 @@ public:
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.
@@ -128,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.
@@ -157,11 +160,6 @@ public:
/// @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;
- /// @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;
-
/// Streams a verbose version of the assembly to @a _outStream.
/// @arg _sourceCodes is the map of input files to source code strings
/// @arg _inJsonFromat shows whether the out should be in Json format
@@ -173,14 +171,18 @@ 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; }
@@ -191,16 +193,6 @@ public:
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:
@@ -208,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:
/**
@@ -230,20 +224,12 @@ 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;
};
- enum State {
- Empty,
- SourcesSet,
- ParsingSuccessful,
- AnalysisSuccessful,
- CompilationSuccessful
- };
-
/// 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.
@@ -265,9 +251,21 @@ private:
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
{
@@ -288,8 +286,8 @@ private:
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 c09180de..9f2a2d06 100644
--- a/libsolidity/interface/Exceptions.cpp
+++ b/libsolidity/interface/Exceptions.cpp
@@ -21,7 +21,6 @@
*/
#include <libsolidity/interface/Exceptions.h>
-#include <libsolidity/interface/Utils.h>
using namespace std;
using namespace dev;
@@ -47,9 +46,6 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
case Type::TypeError:
m_typeName = "TypeError";
break;
- case Type::Why3TranslatorError:
- m_typeName = "Why3TranslatorError";
- break;
case Type::Warning:
m_typeName = "Warning";
break;
diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h
index 0803d8cc..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,7 +60,6 @@ public:
ParserError,
TypeError,
SyntaxError,
- Why3TranslatorError,
Warning
};
diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp
deleted file mode 100644
index 6c1bb0c4..00000000
--- a/libsolidity/interface/InterfaceHandler.cpp
+++ /dev/null
@@ -1,197 +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);
-
- 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 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;
-}
-
-Json::Value InterfaceHandler::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;
-}
-
-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 56927d44..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
@@ -59,7 +58,7 @@ enum class CommentOwner
Function
};
-class InterfaceHandler
+class Natspec
{
public:
/// Get the given type of documentation
@@ -71,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
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
index 223cc15d..e677afc8 100644
--- a/libsolidity/interface/StandardCompiler.cpp
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -115,14 +115,6 @@ StringMap createSourceList(Json::Value const& _input)
return sources;
}
-Json::Value methodIdentifiers(ContractDefinition const& _contract)
-{
- Json::Value methodIdentifiers(Json::objectValue);
- for (auto const& it: _contract.interfaceFunctions())
- methodIdentifiers[it.second->externalSignature()] = toHex(it.first.ref());
- return methodIdentifiers;
-}
-
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
{
Json::Value ret(Json::objectValue);
@@ -273,11 +265,9 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compilerStack.scanner(_sourceName); };
- bool success = false;
-
try
{
- success = m_compilerStack.compile(optimize, optimizeRuns, libraries);
+ m_compilerStack.compile(optimize, optimizeRuns, libraries);
for (auto const& error: m_compilerStack.errors())
{
@@ -367,22 +357,26 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
if (errors.size() > 0)
output["errors"] = errors;
+ bool parsingSuccess = m_compilerStack.state() >= CompilerStack::State::ParsingSuccessful;
+ bool compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful;
+
/// Inconsistent state - stop here to receive error reports from users
- if (!success && (errors.size() == 0))
+ 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: m_compilerStack.sourceNames())
+ for (auto const& source: parsingSuccess ? m_compilerStack.sourceNames() : vector<string>())
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++;
- sourceResult["legacyAST"] = ASTJsonConverter(m_compilerStack.ast(source), m_compilerStack.sourceIndices()).json();
+ 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: success ? m_compilerStack.contractNames() : vector<string>())
+ for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>())
{
size_t colon = contractName.find(':');
solAssert(colon != string::npos, "");
@@ -391,10 +385,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
// ABI, documentation and metadata
Json::Value contractData(Json::objectValue);
- contractData["abi"] = m_compilerStack.metadata(contractName, DocumentationType::ABIInterface);
+ contractData["abi"] = m_compilerStack.contractABI(contractName);
contractData["metadata"] = m_compilerStack.onChainMetadata(contractName);
- contractData["userdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecUser);
- contractData["devdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecDev);
+ contractData["userdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecUser);
+ contractData["devdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecDev);
// EVM
Json::Value evmData(Json::objectValue);
@@ -403,7 +397,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), false);
evmData["assembly"] = tmp.str();
evmData["legacyAssembly"] = m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), true);
- evmData["methodIdentifiers"] = methodIdentifiers(m_compilerStack.contractDefinition(contractName));
+ evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
evmData["bytecode"] = collectEVMObject(
diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp
index 0d23f9c3..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;
diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp
index 8e912126..0409de72 100644
--- a/libsolidity/parsing/DocStringParser.cpp
+++ b/libsolidity/parsing/DocStringParser.cpp
@@ -1,6 +1,7 @@
#include <libsolidity/parsing/DocStringParser.h>
-#include <libsolidity/interface/Utils.h>
+#include <libsolidity/interface/ErrorReporter.h>
+#include <libsolidity/interface/Exceptions.h>
#include <boost/range/irange.hpp>
#include <boost/range/algorithm.hpp>
@@ -51,9 +52,9 @@ string::const_iterator skipWhitespace(
}
-bool DocStringParser::parse(string const& _docString, ErrorList& _errors)
+bool DocStringParser::parse(string const& _docString, ErrorReporter& _errorReporter)
{
- m_errors = &_errors;
+ m_errorReporter = &_errorReporter;
m_errorsOccurred = false;
m_lastTag = nullptr;
@@ -172,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 b5130c8a..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;
@@ -95,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;
}
@@ -324,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();
}
@@ -348,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;
@@ -502,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
{
@@ -514,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;
@@ -703,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)
@@ -866,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);
@@ -1329,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);
}
@@ -1438,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 282617ab..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);
@@ -154,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 0e60fd0b..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;
diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h
index 9a557ebd..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>
diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py
index 5a770981..427724b7 100755
--- a/scripts/bytecodecompare/prepare_report.py
+++ b/scripts/bytecodecompare/prepare_report.py
@@ -6,7 +6,7 @@ import subprocess
import json
solc = sys.argv[1]
-report = open("report.txt", "w")
+report = open("report.txt", "wb")
for optimize in [False, True]:
for f in sorted(glob.glob("*.sol")):
diff --git a/scripts/bytecodecompare/storebytecode.bat b/scripts/bytecodecompare/storebytecode.bat
index 969a42a4..e64e9276 100644
--- a/scripts/bytecodecompare/storebytecode.bat
+++ b/scripts/bytecodecompare/storebytecode.bat
@@ -20,22 +20,23 @@ REM Copyright (c) 2017 solidity contributors.
REM ---------------------------------------------------------------------------
set CONFIGURATION=%1
-set COMMIT=%2
+set DIRECTORY=%2
mkdir bytecode
cd bytecode
..\scripts\isolate_tests.py ..\test\
..\scripts\bytecodecompare\prepare_report.py ..\build\solc\%CONFIGURATION%\solc.exe
-git clone --depth 2 git@github.com:ethereum/solidity-test-bytecode.git
+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
+git clean -f -d -x 2>&1
-mkdir %COMMIT%
-set REPORT=%COMMIT%/windows.txt
+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
+git push origin 2>&1
diff --git a/scripts/bytecodecompare/storebytecode.sh b/scripts/bytecodecompare/storebytecode.sh
index 9a40bc6d..564de3f8 100755
--- a/scripts/bytecodecompare/storebytecode.sh
+++ b/scripts/bytecodecompare/storebytecode.sh
@@ -93,12 +93,19 @@ EOF
git config user.email "chris@ethereum.org"
git clean -f -d -x
- mkdir -p "$TRAVIS_COMMIT"
- REPORT="$TRAVIS_COMMIT/$ZIP_SUFFIX.txt"
+ DIRNAME=$(cd "$REPO_ROOT" && git show -s --format="%cd-%H" --date=short)
+ mkdir -p "$DIRNAME"
+ REPORT="$DIRNAME/$ZIP_SUFFIX.txt"
cp ../report.txt "$REPORT"
- git add "$REPORT"
- git commit -a -m "Added report $REPORT"
- git push origin
+ # Only push if adding actually worked, i.e. there were changes.
+ if git add "$REPORT"
+ then
+ git commit -a -m "Added report $REPORT"
+ git pull --rebase
+ git push origin
+ else
+ echo "Adding report failed, it might already exist in the repository."
+ fi
fi
)
-rm -rf "$TMPDIR" \ No newline at end of file
+rm -rf "$TMPDIR"
diff --git a/scripts/create_source_tarball.sh b/scripts/create_source_tarball.sh
index 553786ba..9e66799a 100755
--- a/scripts/create_source_tarball.sh
+++ b/scripts/create_source_tarball.sh
@@ -6,7 +6,7 @@ set -e
REPO_ROOT="$(dirname "$0")"/..
(
cd "$REPO_ROOT"
- version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? CMakeLists.txt)
+ 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?/./')
diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh
index 0abde840..00705725 100755
--- a/scripts/docker_deploy.sh
+++ b/scripts/docker_deploy.sh
@@ -3,7 +3,7 @@
set -e
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
-version=$(grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? $(dirname "$0")/../CMakeLists.txt)
+version=$($(dirname "$0")/get_version.sh)
if [ "$TRAVIS_BRANCH" = "develop" ]
then
docker tag ethereum/solc:build ethereum/solc:nightly;
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/release_ppa.sh b/scripts/release_ppa.sh
index 86288c47..7c9abecb 100755
--- a/scripts/release_ppa.sh
+++ b/scripts/release_ppa.sh
@@ -39,7 +39,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 +55,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 ]
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/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh
index 02740e6c..f92b3c44 100755
--- a/scripts/travis-emscripten/build_emscripten.sh
+++ b/scripts/travis-emscripten/build_emscripten.sh
@@ -99,6 +99,6 @@ 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/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp
index 63d41cdf..0222ccb0 100644
--- a/solc/CommandLineInterface.cpp
+++ b/solc/CommandLineInterface.cpp
@@ -23,6 +23,7 @@
#include "CommandLineInterface.h"
#include "solidity/BuildInfo.h"
+#include "license.h"
#include <libsolidity/interface/Version.h>
#include <libsolidity/parsing/Scanner.h>
@@ -35,7 +36,7 @@
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/SourceReferenceFormatter.h>
#include <libsolidity/interface/GasEstimator.h>
-#include <libsolidity/formal/Why3Translator.h>
+#include <libsolidity/interface/AssemblyStack.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/GasMeter.h>
@@ -68,26 +69,36 @@ namespace dev
namespace solidity
{
+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";
@@ -100,30 +111,33 @@ 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_strVersion = "version";
-static string const g_stdinFileNameStr = "<stdin>";
-static string const g_strMetadataLiteral = "metadata-literal";
-static string const g_strAllowPaths = "allow-paths";
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;
@@ -131,29 +145,38 @@ 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;
-static string const g_argMetadataLiteral = g_strMetadataLiteral;
-static string const g_argAllowPaths = g_strAllowPaths;
-static string const g_argStandardJSON = g_strStandardJSON;
/// Possible arguments to for --combined-json
-static set<string> const g_combinedJsonArgs{
+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()
{
cout <<
@@ -165,6 +188,14 @@ 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))
@@ -250,9 +281,10 @@ 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(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contract) + ".signatures", out);
@@ -266,24 +298,31 @@ void CommandLineInterface::handleOnChainMetadata(string const& _contract)
return;
string data = m_compiler->onChainMetadata(_contract);
- if (m_args.count("output-dir"))
+ 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_argAbi;
- suffix = ".abi";
- title = "Contract JSON ABI";
- break;
case DocumentationType::NatspecUser:
argName = g_argNatspecUser;
suffix = ".docuser";
@@ -301,11 +340,7 @@ 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(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contract) + suffix, output);
@@ -358,17 +393,6 @@ void CommandLineInterface::handleGasEstimation(string const& _contract)
}
}
-void CommandLineInterface::handleFormal()
-{
- if (!m_args.count(g_argFormal))
- return;
-
- if (m_args.count(g_argOutputDir))
- createFile("solidity.mlw", m_compiler->formalTranslation());
- else
- cout << "Formal version:" << endl << m_compiler->formalTranslation() << endl;
-}
-
void CommandLineInterface::readInputFilesAndConfigureRemappings()
{
bool addStdin = false;
@@ -486,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
@@ -499,10 +527,12 @@ 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()
(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.")
(
g_argOptimizeRuns.c_str(),
@@ -536,7 +566,16 @@ Allowed options)",
)
(
g_argAssemble.c_str(),
- "Switch to assembly mode, ignoring all options and assumes input is assembly."
+ "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(),
@@ -553,6 +592,7 @@ Allowed options)",
outputComponents.add_options()
(g_argAst.c_str(), "AST of all source files.")
(g_argAstJson.c_str(), "AST of all source files in JSON format.")
+ (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.")
@@ -599,6 +639,12 @@ Allowed options)",
return false;
}
+ if (m_args.count(g_strLicense))
+ {
+ license();
+ return false;
+ }
+
if (m_args.count(g_argCombinedJson))
{
vector<string> requests;
@@ -686,11 +732,30 @@ bool CommandLineInterface::processInput()
if (!parseLibraryOption(library))
return false;
- if (m_args.count(g_argAssemble))
+ 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(g_argLink))
{
@@ -714,10 +779,6 @@ bool CommandLineInterface::processInput()
unsigned runs = m_args[g_argOptimizeRuns].as<unsigned>();
bool successful = m_compiler->compile(optimize, runs, m_libraries);
- if (successful && m_args.count(g_argFormal))
- if (!m_compiler->prepareFormalAnalysis())
- successful = false;
-
for (auto const& error: m_compiler->errors())
SourceReferenceFormatter::printExceptionInformation(
cerr,
@@ -787,7 +848,7 @@ void CommandLineInterface::handleCombinedJSON()
{
Json::Value contractData(Json::objectValue);
if (requests.count(g_strAbi))
- contractData[g_strAbi] = dev::jsonCompactPrint(m_compiler->interface(contractName));
+ contractData[g_strAbi] = dev::jsonCompactPrint(m_compiler->contractABI(contractName));
if (requests.count("metadata"))
contractData["metadata"] = m_compiler->onChainMetadata(contractName);
if (requests.count(g_strBinary))
@@ -813,10 +874,12 @@ void CommandLineInterface::handleCombinedJSON()
auto map = m_compiler->runtimeSourceMapping(contractName);
contractData[g_strSrcMapRuntime] = map ? *map : "";
}
+ if (requests.count(g_strSignatureHashes))
+ contractData[g_strSignatureHashes] = m_compiler->methodIdentifiers(contractName);
if (requests.count(g_strNatspecDev))
- contractData[g_strNatspecDev] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecDev));
+ contractData[g_strNatspecDev] = dev::jsonCompactPrint(m_compiler->natspec(contractName, DocumentationType::NatspecDev));
if (requests.count(g_strNatspecUser))
- contractData[g_strNatspecUser] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecUser));
+ contractData[g_strNatspecUser] = dev::jsonCompactPrint(m_compiler->natspec(contractName, DocumentationType::NatspecUser));
output[g_strContracts][contractName] = contractData;
}
@@ -832,12 +895,13 @@ void CommandLineInterface::handleCombinedJSON()
if (requests.count(g_strAst))
{
+ 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());
+ ASTJsonConverter converter(legacyFormat, m_compiler->sourceIndices());
output[g_strSources][sourceCode.first] = Json::Value(Json::objectValue);
- output[g_strSources][sourceCode.first]["AST"] = converter.json();
+ output[g_strSources][sourceCode.first]["AST"] = converter.toJson(m_compiler->ast(sourceCode.first));
}
}
cout << dev::jsonCompactPrint(output) << endl;
@@ -851,6 +915,8 @@ void CommandLineInterface::handleAst(string const& _argStr)
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"));
@@ -867,6 +933,7 @@ void CommandLineInterface::handleAst(string const& _argStr)
asts
);
+ bool legacyFormat = !m_args.count(g_argAstCompactJson);
if (m_args.count(g_argOutputDir))
{
for (auto const& sourceCode: m_sourceCodes)
@@ -880,8 +947,7 @@ void CommandLineInterface::handleAst(string const& _argStr)
}
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);
@@ -904,10 +970,7 @@ 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));
}
}
}
@@ -915,10 +978,9 @@ void CommandLineInterface::handleAst(string const& _argStr)
bool CommandLineInterface::actOnInput()
{
- if (m_args.count(g_argStandardJSON))
+ if (m_args.count(g_argStandardJSON) || m_onlyAssemble)
+ // Already done in "processInput" phase.
return true;
- else if (m_onlyAssemble)
- outputAssembly();
else if (m_onlyLink)
writeLinkedFiles();
else
@@ -979,45 +1041,91 @@ void CommandLineInterface::writeLinkedFiles()
writeFile(src.first, src.second);
}
-bool CommandLineInterface::assemble()
+bool CommandLineInterface::assemble(
+ AssemblyStack::Language _language,
+ AssemblyStack::Machine _targetMachine
+)
{
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& sourceAndStack: assemblyStacks)
{
- for (auto const& error: stack.second.errors())
+ 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.second.errors()))
+ 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()
@@ -1027,6 +1135,7 @@ void CommandLineInterface::outputCompilationResults()
// do we need AST output?
handleAst(g_argAst);
handleAst(g_argAstJson);
+ handleAst(g_argAstCompactJson);
vector<string> contracts = m_compiler->contractNames();
for (string const& contract: contracts)
@@ -1056,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 f52a03c7..b482c20b 100644
--- a/solc/CommandLineInterface.h
+++ b/solc/CommandLineInterface.h
@@ -22,7 +22,7 @@
#pragma once
#include <libsolidity/interface/CompilerStack.h>
-#include <libsolidity/inlineasm/AsmStack.h>
+#include <libsolidity/interface/AssemblyStack.h>
#include <boost/program_options.hpp>
#include <boost/filesystem/path.hpp>
@@ -54,9 +54,7 @@ private:
bool link();
void writeLinkedFiles();
- /// Parse assembly input.
- bool assemble();
- void outputAssembly();
+ bool assemble(AssemblyStack::Language _language, AssemblyStack::Machine _targetMachine);
void outputCompilationResults();
@@ -67,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();
@@ -85,6 +84,7 @@ private:
bool m_error = false; ///< If true, some error occurred.
bool m_onlyAssemble = false;
+
bool m_onlyLink = false;
/// Compiler arguments variable map
@@ -97,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 42c25de0..de797b3c 100644
--- a/solc/jsonCompiler.cpp
+++ b/solc/jsonCompiler.cpp
@@ -21,26 +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/StandardCompiler.h>
-#include <libsolidity/interface/SourceReferenceFormatter.h>
-#include <libsolidity/ast/ASTJsonConverter.h>
#include <libsolidity/interface/Version.h>
+#include "license.h"
+
using namespace std;
using namespace dev;
using namespace solidity;
@@ -86,14 +73,6 @@ ReadFile::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullp
return readCallback;
}
-Json::Value functionHashes(ContractDefinition const& _contract)
-{
- Json::Value functionHashes(Json::objectValue);
- for (auto const& it: _contract.interfaceFunctions())
- functionHashes[it.second->externalSignature()] = toHex(it.first.ref());
- return functionHashes;
-}
-
/// Translates a gas value as a string to a JSON number or null
Json::Value gasToJson(Json::Value const& _value)
{
@@ -115,9 +94,8 @@ Json::Value gasToJson(Json::Value const& _value)
return Json::Value(Json::LargestUInt(value));
}
-Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract)
+Json::Value translateGasEstimates(Json::Value const& estimates)
{
- Json::Value estimates = _compiler.gasEstimates(_contract);
Json::Value output(Json::objectValue);
if (estimates["creation"].isObject())
@@ -137,120 +115,102 @@ Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract)
string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback)
{
- Json::Value output(Json::objectValue);
- Json::Value errors(Json::arrayValue);
- CompilerStack compiler(wrapReadCallback(_readCallback));
- auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return compiler.scanner(_sourceName); };
- bool success = false;
- try
+ /// create new JSON input format
+ Json::Value input = Json::objectValue;
+ input["language"] = "Solidity";
+ input["sources"] = Json::objectValue;
+ for (auto const& source: _sources)
{
- compiler.addSources(_sources);
- bool succ = compiler.compile(_optimize);
- for (auto const& error: compiler.errors())
- {
- auto err = dynamic_pointer_cast<Error const>(error);
- errors.append(SourceReferenceFormatter::formatExceptionInformation(
- *error,
- (err->type() == Error::Type::Warning) ? "Warning" : "Error",
- scannerFromSourceName
- ));
- }
- success = succ; // keep success false on exception
+ input["sources"][source.first] = Json::objectValue;
+ input["sources"][source.first]["content"] = source.second;
}
- catch (Error const& error)
- {
- errors.append(SourceReferenceFormatter::formatExceptionInformation(error, error.typeName(), scannerFromSourceName));
- }
- catch (CompilerError const& exception)
- {
- errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Compiler error (" + exception.lineInfo() + ")", scannerFromSourceName));
- }
- catch (InternalCompilerError const& exception)
- {
- errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Internal compiler error (" + exception.lineInfo() + ")", scannerFromSourceName));
- }
- catch (UnimplementedFeatureError const& exception)
- {
- errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Unimplemented feature (" + exception.lineInfo() + ")", scannerFromSourceName));
- }
- catch (Exception const& exception)
- {
- errors.append("Exception during compilation: " + boost::diagnostic_information(exception));
- }
- 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(SourceReferenceFormatter::formatExceptionInformation(
- *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
@@ -304,6 +264,11 @@ 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();
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 01a9a188..8e7b8916 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -4,8 +4,10 @@ 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)
list(REMOVE_ITEM SRC_LIST "./fuzzer.cpp")
@@ -18,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})
add_executable(solfuzzer fuzzer.cpp)
target_link_libraries(solfuzzer soljson ${Boost_PROGRAM_OPTIONS_LIBRARIES})
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 3ea3b1fe..c4fbfefb 100644
--- a/test/RPCSession.cpp
+++ b/test/RPCSession.cpp
@@ -316,9 +316,9 @@ 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;
BOOST_REQUIRE(Json::Reader().parse(reply, result, false));
diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh
index 6dc3f77f..4074ce55 100755
--- a/test/cmdlineTests.sh
+++ b/test/cmdlineTests.sh
@@ -79,7 +79,14 @@ TMPDIR=$(mktemp -d)
"$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"
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
index 053d880f..cf99755f 100644
--- a/test/fuzzer.cpp
+++ b/test/fuzzer.cpp
@@ -152,7 +152,6 @@ void testCompiler()
"Exception during compilation",
"Unknown exception during compilation",
"Unknown exception while generating contract data output",
- "Unknown exception while generating formal method output",
"Unknown exception while generating source name output",
"Unknown error while generating JSON"
});
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/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 c7c1fd3b..f3bfb438 100644
--- a/test/liblll/EndToEndTest.cpp
+++ b/test/liblll/EndToEndTest.cpp
@@ -57,6 +57,114 @@ BOOST_AUTO_TEST_CASE(panic)
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"(
@@ -272,13 +380,329 @@ BOOST_AUTO_TEST_CASE(assembly_codecopy)
(seq
(lit 0x00 "abcdef")
(asm
- 0x06 0x16 0x20 codecopy
+ 0x06 6 codesize sub 0x20 codecopy
0x20 0x20 return)))
)";
compileAndRun(sourceCode);
BOOST_CHECK(callFallback() == encodeArgs(string("abcdef")));
}
+
+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);
+}
+
+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 d23815b4..4fb4f20c 100644
--- a/test/libsolidity/ASTJSON.cpp
+++ b/test/libsolidity/ASTJSON.cpp
@@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(smoke_test)
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");
}
@@ -55,7 +55,7 @@ BOOST_AUTO_TEST_CASE(source_location)
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");
@@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(inheritance_specifier)
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");
@@ -84,7 +84,7 @@ BOOST_AUTO_TEST_CASE(using_for_directive)
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");
@@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(enum_value)
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");
@@ -118,7 +118,7 @@ BOOST_AUTO_TEST_CASE(modifier_definition)
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");
@@ -132,7 +132,7 @@ BOOST_AUTO_TEST_CASE(modifier_invocation)
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");
@@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(event_definition)
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");
@@ -162,7 +162,7 @@ BOOST_AUTO_TEST_CASE(array_type_name)
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");
@@ -175,7 +175,7 @@ BOOST_AUTO_TEST_CASE(placeholder_statement)
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");
@@ -188,11 +188,11 @@ BOOST_AUTO_TEST_CASE(non_utf8)
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);
}
@@ -207,7 +207,7 @@ BOOST_AUTO_TEST_CASE(function_type)
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 c4ec0d20..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,28 +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);
map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
- NameAndTypeResolver resolver({}, scopes, errors);
- solAssert(Error::containsOnlyWarnings(errors), "");
+ 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())
@@ -117,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>(17, SourceLocation(2, 75, n)) +
- vector<SourceLocation>(30, 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 f90cb105..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);
}
}
)";
diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp
index 8bf4df8e..5197f649 100644
--- a/test/libsolidity/InlineAssembly.cpp
+++ b/test/libsolidity/InlineAssembly.cpp
@@ -22,7 +22,7 @@
#include "../TestHelper.h"
-#include <libsolidity/inlineasm/AsmStack.h>
+#include <libsolidity/interface/AssemblyStack.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST.h>
@@ -47,49 +47,56 @@ namespace test
namespace
{
-boost::optional<Error> parseAndReturnFirstError(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
{
- success = stack.parse(std::make_shared<Scanner>(CharStream(_source)));
+ success = stack.parseAndAnalyze("", _source);
if (success && _assemble)
- stack.assemble();
+ stack.assemble(_machine);
}
catch (FatalError const&)
{
BOOST_FAIL("Fatal error leaked.");
success = false;
}
- if (!success)
- {
- BOOST_REQUIRE_EQUAL(stack.errors().size(), 1);
- return *stack.errors().front();
- }
- else
+ shared_ptr<Error const> error;
+ for (auto const& e: stack.errors())
{
- // If success is true, there might still be an error in the assembly stage.
- if (_allowWarnings && Error::containsOnlyWarnings(stack.errors()))
- return {};
- else if (!stack.errors().empty())
- {
- if (!_allowWarnings)
- BOOST_CHECK_EQUAL(stack.errors().size(), 1);
- return *stack.errors().front();
- }
+ 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 {};
}
-bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true)
+bool successParse(
+ string const& _source,
+ bool _assemble = false,
+ bool _allowWarnings = true,
+ AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM
+)
{
- return !parseAndReturnFirstError(_source, _assemble, _allowWarnings);
+ 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)
@@ -100,29 +107,35 @@ Error expectError(std::string const& _source, bool _assemble, bool _allowWarning
return *error;
}
-void parsePrintCompare(string const& _source)
+void parsePrintCompare(string const& _source, bool _canWarn = false)
{
- assembly::InlineAssemblyStack stack;
- BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(_source))));
- BOOST_REQUIRE(stack.errors().empty());
- BOOST_CHECK_EQUAL(stack.toString(), _source);
+ 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) \
+#define CHECK_ERROR(text, assemble, typ, substring, warnings) \
do \
{ \
- Error err = expectError((text), (assemble), false); \
+ 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)
+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)
+CHECK_ERROR(text, true, type, substring, false)
@@ -161,6 +174,27 @@ 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("{ let x := 2 7 8 add =: x }"));
@@ -181,6 +215,16 @@ BOOST_AUTO_TEST_CASE(functional)
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("{ let x := 2 x := 7 }"));
@@ -202,6 +246,89 @@ BOOST_AUTO_TEST_CASE(variable_use_before_decl)
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)
{
BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }"));
@@ -243,6 +370,23 @@ 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)
@@ -269,7 +413,7 @@ BOOST_AUTO_TEST_CASE(print_functional)
BOOST_AUTO_TEST_CASE(print_label)
{
- parsePrintCompare("{\n loop:\n jump(loop)\n}");
+ parsePrintCompare("{\n loop:\n jump(loop)\n}", true);
}
BOOST_AUTO_TEST_CASE(print_assignments)
@@ -277,6 +421,11 @@ 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}");
@@ -286,13 +435,23 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
{
string source = "{ let x := \"\\u1bac\" }";
string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}";
- assembly::InlineAssemblyStack stack;
- BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(source))));
+ AssemblyStack stack;
+ BOOST_REQUIRE(stack.parseAndAnalyze("", source));
BOOST_REQUIRE(stack.errors().empty());
- BOOST_CHECK_EQUAL(stack.toString(), parsed);
+ 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}");
@@ -358,7 +517,7 @@ BOOST_AUTO_TEST_CASE(imbalanced_stack)
BOOST_AUTO_TEST_CASE(error_tag)
{
- BOOST_CHECK(successAssemble("{ jump(invalidJumpLabel) }"));
+ CHECK_ERROR("{ jump(invalidJumpLabel) }", true, DeclarationError, "Identifier not found", true);
}
BOOST_AUTO_TEST_CASE(designated_invalid_instruction)
@@ -386,6 +545,101 @@ 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 bdcc5b10..f87390e1 100644
--- a/test/libsolidity/SolidityABIJSON.cpp
+++ b/test/libsolidity/SolidityABIJSON.cpp
@@ -44,7 +44,7 @@ public:
{
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(
@@ -752,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 f2f4b8b0..a6c01283 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;
}
@@ -1864,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);
}
}
)";
@@ -1883,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"(
@@ -3110,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);
}
}
)";
@@ -3129,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);
}
}
)";
@@ -3148,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");
}
}
)";
@@ -3174,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 {
@@ -3185,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");
}
}
)";
@@ -3193,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 {
@@ -3204,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");
}
}
)";
@@ -3214,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);
@@ -3245,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();
}
@@ -3270,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();
}
@@ -3307,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)) {}
}
}
@@ -3372,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);
}
}
)";
@@ -3685,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"(
@@ -4466,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"(
@@ -5294,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)
{
@@ -7445,6 +7538,33 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access)
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"(
@@ -7518,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.
@@ -9126,21 +9418,6 @@ 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)
-{
- char const* sourceCode = R"(
- contract C {
- function f() {
- assembly {
- jump(invalidJumpLabel)
- }
- }
- }
- )";
- compileAndRun(sourceCode, 0, "C");
- BOOST_CHECK(callContractFunction("f()") == encodeArgs());
-}
-
BOOST_AUTO_TEST_CASE(contracts_separated_with_comment)
{
char const* sourceCode = R"(
@@ -9268,6 +9545,50 @@ BOOST_AUTO_TEST_CASE(revert)
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"(
@@ -9336,6 +9657,45 @@ BOOST_AUTO_TEST_CASE(interface)
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_SUITE_END()
}
diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp
index 3116aea8..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,8 +116,9 @@ bytes compileFirstExpression(
declarations.push_back(variable.get());
ErrorList errors;
+ ErrorReporter errorReporter(errors);
map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
- NameAndTypeResolver resolver(declarations, scopes, errors);
+ NameAndTypeResolver resolver(declarations, scopes, errorReporter);
resolver.registerDeclarations(*sourceUnit);
vector<ContractDefinition const*> inheritanceHierarchy;
@@ -128,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())
diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp
index 3a9f7295..d0aee3d0 100644
--- a/test/libsolidity/SolidityNameAndTypeResolution.cpp
+++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp
@@ -30,7 +30,7 @@
#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>
@@ -56,7 +56,8 @@ parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = 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
@@ -65,14 +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>();
map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes;
- NameAndTypeResolver resolver(globalContext->declarations(), scopes, errors);
- solAssert(Error::containsOnlyWarnings(errors), "");
+ NameAndTypeResolver resolver(globalContext->declarations(), scopes, errorReporter);
+ solAssert(Error::containsOnlyWarnings(errorReporter.errors()), "");
resolver.registerDeclarations(*sourceUnit);
bool success = true;
@@ -92,26 +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());
}
if (success)
- if (!PostTypeChecker(errors).check(*sourceUnit))
+ if (!PostTypeChecker(errorReporter).check(*sourceUnit))
success = false;
if (success)
- if (!StaticAnalyzer(errors).analyze(*sourceUnit))
+ if (!StaticAnalyzer(errorReporter).analyze(*sourceUnit))
success = false;
- if (errors.size() > 1 && !_allowMultipleErrors)
- BOOST_FAIL("Multiple errors found");
- for (auto const& currentError: errors)
+ 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)
{
@@ -192,11 +199,16 @@ CHECK_ERROR_OR_WARNING(text, type, substring, false, false)
#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, 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)
@@ -548,6 +560,51 @@ BOOST_AUTO_TEST_CASE(comparison_bitop_precedence)
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;
@@ -1043,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"(
@@ -1069,7 +1148,7 @@ BOOST_AUTO_TEST_CASE(modifier_overrides_function)
)";
// Error: Identifier already declared.
// Error: Override changes modifier to function.
- CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, "");
+ CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, "Identifier already declared");
}
BOOST_AUTO_TEST_CASE(function_overrides_modifier)
@@ -1591,6 +1670,16 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter)
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"(
@@ -1698,6 +1787,46 @@ BOOST_AUTO_TEST_CASE(exp_warn_literal_base)
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"(
@@ -2167,6 +2296,36 @@ BOOST_AUTO_TEST_CASE(test_byte_is_alias_of_byte1)
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"(
@@ -2268,6 +2427,16 @@ BOOST_AUTO_TEST_CASE(constant_struct)
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)
{
char const* text = R"(
@@ -2416,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"(
@@ -2933,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, "");
@@ -3042,9 +3221,9 @@ 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;
}
}
@@ -3682,12 +3861,12 @@ BOOST_AUTO_TEST_CASE(conditional_with_all_types)
byte[2] memory a;
byte[2] memory b;
var k = true ? a : b;
- k[0] = 0; //Avoid unused var warning
+ k[0] = byte(0); //Avoid unused var warning
bytes memory e;
bytes memory f;
var l = true ? e : f;
- l[0] = 0; // Avoid unused var warning
+ l[0] = byte(0); // Avoid unused var warning
// fixed bytes
bytes2 c;
@@ -4500,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)
@@ -4515,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"(
@@ -4996,6 +5200,26 @@ BOOST_AUTO_TEST_CASE(external_function_type_to_uint)
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)
{
char const* text = R"(
@@ -5158,6 +5382,52 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_access)
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)
{
char const* text = R"(
@@ -5319,7 +5589,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_checksum)
char const* text = R"(
contract C {
function f() {
- var x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ address x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
x;
}
}
@@ -5332,7 +5602,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_no_checksum)
char const* text = R"(
contract C {
function f() {
- var x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e;
+ address x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e;
x;
}
}
@@ -5345,7 +5615,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_length)
char const* text = R"(
contract C {
function f() {
- var x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ address x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
x;
}
}
@@ -5353,6 +5623,25 @@ BOOST_AUTO_TEST_CASE(invalid_address_length)
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.
@@ -5400,7 +5689,7 @@ BOOST_AUTO_TEST_CASE(cyclic_dependency_for_constants)
contract C {
uint constant a = b * c;
uint constant b = 7;
- uint constant c = b + uint(sha3(d));
+ uint constant c = b + uint(keccak256(d));
uint constant d = 2 + a;
}
)";
@@ -5409,7 +5698,7 @@ BOOST_AUTO_TEST_CASE(cyclic_dependency_for_constants)
contract C {
uint constant a = b * c;
uint constant b = 7;
- uint constant c = 4 + uint(sha3(d));
+ uint constant c = 4 + uint(keccak256(d));
uint constant d = 2 + b;
}
)";
@@ -5520,6 +5809,16 @@ BOOST_AUTO_TEST_CASE(interface_variables)
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"(
@@ -5608,6 +5907,80 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop)
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"(
@@ -5625,7 +5998,7 @@ BOOST_AUTO_TEST_CASE(warn_unused_local_assigned)
char const* text = R"(
contract C {
function f() {
- var a = 1;
+ uint a = 1;
}
}
)";
@@ -5718,7 +6091,58 @@ BOOST_AUTO_TEST_CASE(no_unused_dec_after_use)
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_SUITE_END()
diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp
index 78c1a0ee..2a7376b9 100644
--- a/test/libsolidity/SolidityNatspecJSON.cpp
+++ b/test/libsolidity/SolidityNatspecJSON.cpp
@@ -49,9 +49,9 @@ public:
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(
diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp
index d705e3c8..a4d80c99 100644
--- a/test/libsolidity/SolidityOptimizer.cpp
+++ b/test/libsolidity/SolidityOptimizer.cpp
@@ -322,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;
@@ -349,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
@@ -358,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
@@ -370,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);
}
}
)";
@@ -392,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
@@ -401,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
@@ -697,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{
@@ -821,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),
@@ -841,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),
@@ -851,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,
@@ -871,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),
@@ -900,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,
@@ -909,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,
@@ -921,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,
@@ -932,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,
@@ -949,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,
@@ -962,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)
@@ -1189,6 +1136,32 @@ 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"(
@@ -1296,7 +1269,7 @@ BOOST_AUTO_TEST_CASE(constant_optimization_early_exit)
// Store and hash
assembly {
mstore(32, x)
- ret := sha3(0, 40)
+ ret := keccak256(0, 40)
}
}
}
diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp
index 6e33aba5..78edd4d1 100644
--- a/test/libsolidity/SolidityParser.cpp
+++ b/test/libsolidity/SolidityParser.cpp
@@ -24,7 +24,7 @@
#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"
@@ -41,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())
@@ -198,6 +199,17 @@ BOOST_AUTO_TEST_CASE(missing_argument_in_named_args)
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)
{
char const* text = R"(
@@ -889,6 +901,24 @@ BOOST_AUTO_TEST_CASE(multiple_visibility_specifiers)
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)
{
char const* text = R"(
diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp
index ffb0e2c6..be13d46b 100644
--- a/test/libsolidity/StandardCompiler.cpp
+++ b/test/libsolidity/StandardCompiler.cpp
@@ -26,6 +26,7 @@
#include <libsolidity/interface/StandardCompiler.h>
#include <libdevcore/JSON.h>
+#include "../Metadata.h"
using namespace std;
using namespace dev::eth;
@@ -68,60 +69,12 @@ bool containsAtMostWarnings(Json::Value const& _compilerResult)
BOOST_REQUIRE(error.isObject());
BOOST_REQUIRE(error["severity"].isString());
if (error["severity"].asString() != "warning")
- {
- cout << error << std::endl;
return false;
- }
}
return true;
}
-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;
-}
-
Json::Value getContractResult(Json::Value const& _compilerResult, string const& _file, string const& _name)
{
if (
@@ -236,34 +189,43 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
Json::Value contract = getContractResult(result, "fileA", "A");
BOOST_CHECK(contract.isObject());
BOOST_CHECK(contract["abi"].isArray());
- BOOST_CHECK(dev::jsonCompactPrint(contract["abi"]) == "[]");
+ BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["abi"]), "[]");
BOOST_CHECK(contract["devdoc"].isObject());
- BOOST_CHECK(dev::jsonCompactPrint(contract["devdoc"]) == "{\"methods\":{}}");
+ BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["devdoc"]), "{\"methods\":{}}");
BOOST_CHECK(contract["userdoc"].isObject());
- BOOST_CHECK(dev::jsonCompactPrint(contract["userdoc"]) == "{\"methods\":{}}");
+ 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(bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()) ==
- "60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00");
+ BOOST_CHECK_EQUAL(
+ dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()),
+ "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00"
+ );
BOOST_CHECK(contract["evm"]["assembly"].isString());
- BOOST_CHECK(contract["evm"]["assembly"].asString() ==
+ BOOST_CHECK(contract["evm"]["assembly"].asString().find(
" /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x60)\n jumpi(tag_1, iszero(callvalue))\n"
- " invalid\ntag_1:\ntag_2:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\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 invalid\n}\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(dev::jsonCompactPrint(contract["evm"]["gasEstimates"]) ==
- "{\"creation\":{\"codeDepositCost\":\"10200\",\"executionCost\":\"62\",\"totalCost\":\"10262\"}}");
+ BOOST_CHECK_EQUAL(
+ dev::jsonCompactPrint(contract["evm"]["gasEstimates"]),
+ "{\"creation\":{\"codeDepositCost\":\"10800\",\"executionCost\":\"62\",\"totalCost\":\"10862\"}}"
+ );
BOOST_CHECK(contract["metadata"].isString());
- BOOST_CHECK(isValidMetadata(contract["metadata"].asString()));
+ 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(dev::jsonCompactPrint(result["sources"]["fileA"]["legacyAST"]) ==
- "{\"children\":[{\"attributes\":{\"fullyImplemented\":true,\"isLibrary\":false,\"linearizedBaseContracts\":[1],"
- "\"name\":\"A\"},\"children\":[],\"id\":1,\"name\":\"ContractDefinition\",\"src\":\"0:14:0\"}],\"name\":\"SourceUnit\"}");
+ 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()