Merge pull request #2219 from ethereum/develop

Release for version 0.4.11
This commit is contained in:
chriseth 2017-05-03 14:36:32 +02:00 committed by GitHub
commit 68ef581059
121 changed files with 5012 additions and 1404 deletions

4
.gitignore vendored
View File

@ -1,5 +1,5 @@
.commit_hash.txt commit_hash.txt
.prerelease.txt prerelease.txt
# Compiled Object files # Compiled Object files
*.slo *.slo

View File

@ -44,6 +44,7 @@ env:
- SOLC_INSTALL_DEPS_TRAVIS=On - SOLC_INSTALL_DEPS_TRAVIS=On
- SOLC_RELEASE=On - SOLC_RELEASE=On
- SOLC_TESTS=On - SOLC_TESTS=On
- SOLC_STOREBYTECODE=Off
- SOLC_DOCKER=Off - SOLC_DOCKER=Off
matrix: matrix:
@ -61,6 +62,7 @@ matrix:
compiler: gcc compiler: gcc
env: env:
- ZIP_SUFFIX=ubuntu-trusty - ZIP_SUFFIX=ubuntu-trusty
- SOLC_STOREBYTECODE=On
- os: linux - os: linux
dist: trusty dist: trusty
@ -68,6 +70,7 @@ matrix:
compiler: clang compiler: clang
env: env:
- ZIP_SUFFIX=ubuntu-trusty-clang - ZIP_SUFFIX=ubuntu-trusty-clang
- SOLC_STOREBYTECODE=On
# Documentation target, which generates documentation using Phoenix / ReadTheDocs. # Documentation target, which generates documentation using Phoenix / ReadTheDocs.
- os: linux - os: linux
@ -113,6 +116,8 @@ matrix:
- SOLC_INSTALL_DEPS_TRAVIS=Off - SOLC_INSTALL_DEPS_TRAVIS=Off
- SOLC_RELEASE=Off - SOLC_RELEASE=Off
- SOLC_TESTS=Off - SOLC_TESTS=Off
- ZIP_SUFFIX=emscripten
- SOLC_STOREBYTECODE=On
# OS X Mavericks (10.9) # OS X Mavericks (10.9)
# https://en.wikipedia.org/wiki/OS_X_Mavericks # https://en.wikipedia.org/wiki/OS_X_Mavericks
@ -175,11 +180,12 @@ cache:
install: install:
- test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh) - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh)
- test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh) - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh)
- if [ "$TRAVIS_BRANCH" = release ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
- echo -n "$TRAVIS_COMMIT" > commit_hash.txt - echo -n "$TRAVIS_COMMIT" > commit_hash.txt
- test $SOLC_DOCKER != On || (docker build -t ethereum/solc:build -f scripts/Dockerfile .)
before_script: before_script:
- test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh) - test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh)
- test $SOLC_DOCKER != On || (scripts/docker_build.sh)
- test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE
&& scripts/release.sh $ZIP_SUFFIX && scripts/release.sh $ZIP_SUFFIX
&& scripts/create_source_tarball.sh) && scripts/create_source_tarball.sh)
@ -187,6 +193,7 @@ before_script:
script: script:
- test $SOLC_DOCS != On || (scripts/docs.sh) - test $SOLC_DOCS != On || (scripts/docs.sh)
- test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh)
- test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh)
deploy: deploy:
# This is the deploy target for the Emscripten build. # This is the deploy target for the Emscripten build.
@ -223,11 +230,8 @@ deploy:
overwrite: true overwrite: true
file_glob: true file_glob: true
file: file: $TRAVIS_BUILD_DIR/upload/*
- $TRAVIS_BUILD_DIR/solidity*.zip
- $TRAVIS_BUILD_DIR/solidity*tar.gz
skip_cleanup: true skip_cleanup: true
on: on:
all_branches: true all_branches: true
tags: true tags: true
condition: $SOLC_RELEASE == On

View File

@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.4.10") set(PROJECT_VERSION "0.4.11")
project(solidity VERSION ${PROJECT_VERSION}) project(solidity VERSION ${PROJECT_VERSION})
# Let's find our dependencies # Let's find our dependencies

View File

@ -1,3 +1,27 @@
### 0.4.11 (2017-05-03)
Features:
* Implement the Standard JSON Input / Output API
* Support ``interface`` contracts.
* C API (``jsonCompiler``): Add the ``compileStandard()`` method to process a Standard JSON I/O.
* Commandline interface: Add the ``--standard-json`` parameter to process a Standard JSON I/O.
* Commandline interface: Support ``--allow-paths`` to define trusted import paths. Note: the
path(s) of the supplied source file(s) is always trusted.
* Inline Assembly: Storage variable access using ``_slot`` and ``_offset`` suffixes.
* Inline Assembly: Disallow blocks with unbalanced stack.
* Static analyzer: Warn about statements without effects.
* Static analyzer: Warn about unused local variables, parameters, and return parameters.
* Syntax checker: issue deprecation warning for unary '+'
Bugfixes:
* Assembly output: Implement missing AssemblyItem types.
* Compiler interface: Fix a bug where source indexes could be inconsistent between Solidity compiled
with different compilers (clang vs. gcc) or compiler settings. The bug was visible in AST
and source mappings.
* Gas Estimator: Reflect the most recent fee schedule.
* Type system: Contract inheriting from base with unimplemented constructor should be abstract.
* Optimizer: Number representation bug in the constant optimizer fixed.
### 0.4.10 (2017-03-15) ### 0.4.10 (2017-03-15)
Features: Features:

View File

@ -34,6 +34,10 @@ branches:
os: Visual Studio 2015 os: Visual Studio 2015
configuration: configuration:
- RelWithDebInfo - RelWithDebInfo
environment:
# This is used for pushing to solidity-test-bytecodes
priv_key:
secure: yYGwg4rhCdHfwuv2mFjaNEDwAx3IKUbp0D5fMGpaKefnfk+BiMS5bqSHRiOj91PZ91P9pUk2Vu+eNuS4hTFCf1zFGfrOhlJ4Ij0xSyU5m/LQr590Mo+f7W94Xc8ubgo6j2hp9qH/szTqTzmAkmxKO5TLlWjVzVny2t/s5o5UprLS1/MdzDNLjpVNXR03oKfdWUV9a2l6+PejXCbqyUCagh6BByZqeAPbDcil6eAfxu4EPX83Fuurof+KqFzIWycBG5qK1pTipn2pxiA0QKuUrD8y8VNL0S23NTgxoxSp7nPVMd3K0qRSzPM5lrqS7Z8i3evkVwPbuhu0gSiV08jGVahH2snQ3JGYsH2D4KmVn/xiVBeJ0lRplYlfZF0GUu7iJ+DDxi6wBPhW9A25/NyD/mx7Ub2dLheyWi8AjdSCzhfRD+4We8FQQeHRo3Q0kAohFmlCXdXhrcwOOloId8r6xYwg+hWxHTt2Oe9CKwXfmiPjgl/Gd6lYgLpyyfJ8drQ6tjO/pybLEa10v74qYNdVW5LaLIsRUM9Jm/FDVTrOGYtPndi87mF+/tBJIaXXNz0EMl5xvsKW0SBfUMV49zoDDKZZgWyO9U/cfViEUi7Sdn9QLsBWLZfSgBQNkq3WGZVKPq58OxEWT9dUghQHlSVh2qWF/NUx0TRBjiJl9JM56ENTMD00y18eDcXNCeLLVYB+R1axabUPdXivrO+BrWQK94IWxKEJ+YYN8WVJWAO5T/EBDKwgiXGneePwJ75WP7XCLtuYxqjC+CeW3xBVCzCEeZB/VVBvt7fhmtcoeZZ6tAS10h0yY5WWZ/EUVorj+c/FrMm7Nlpcrd1p4hciffePSLVg+yvy9/xTuM9trYWMgj4xcDQbYsaeItHO2Z3EiUoCgNdUw6rONiNwS/XBApWhCcklWm0/g62h2gOa7/hnKG6p2omQzYw+cOzWbF9+DBzoTSXXZXqbUshVee+CD+iYJKleGYSdbMdM89HW4HyskHk6HgM1ggE8CsgD1pMhXtqLTYZBlvsZCBkHPkD9NhGD2DtrNOmJOW8xwkL2/Il6roDF4n856XNdsjvd++rvQoKr58SkyApCJeCo3sfVres0W22g+7If2b2kWC4/DphrFkeaceFzJOctBUrwstvQBXIVOcadU978A3E7jvTaMR4JL9kC/iPOUVNjNRNM/gNvTlf3CIyMMszFeftjEBGnCZaSpht2RtNapRQQb6QPkOP88nufQVZq/TP1ECmvdTUWJ7kSnAupu6u8oH2x2IIm/KKeIwSYU5rGxjRb36DwgXCHcwfRYo3VNorwTeZGj4q1TSM9PuvgzNg//gKZW6VRa+HdNm/40ZGpDsOrr55tOBqfpq9k5RmevqW/OMZS3xUuArKdYLQY75t9eWcbHSgFN2ZY1KEdyEEvVKgs6Q4lEnSSulGxroRxTU5BOoA0V4tCeCUoSPD3FB93WsO9fBPzNsqOuBtDdIkApefzc1pT38uKpmVfggKUsoWUdqMXAWqCDWr2uw9EE900RJpEY6mIEWhkcro5LAMwaqByOGpqFFUkH+UWTC102eVHEmjxKpC6c6cSzoKKU6Ckd+jVRFO7TvmVe1MKCwjXj8lcAfAM2gQ+XehtrQdIBhAmCrnzurfz2u9tKVdpiADC1ig+kMs1/HX2713LYVXzDKdk+duQ94SVtGv9F2Iv+KN5oq4UFgll6VGt7GHsJOrYYf/wrOfB09IkpmjNygvcpmmSdcXXF8ulDD6KHTGEGUlFwLOpEwKx+zX2ZvviStHhN8KsoTKSVSueDmSSI63HdTS7FxfrHJc1yAzsdqEN5g5eV/z2Fn34qy64mdFSAZMF5zsbWZYFpc9ef3llF5aRcuD90JWT2VC7rB2jeGEtiwGkDlqKzxqRvJk06wTK6+n5RncN66bDaksulOPJMAR/bRW7dinV8T6yIvybuhqDetxJQP6eyAnW4xr1YxIAG4BXGZV6XAPTgOG2oGvMdncxkcLQHXVu07x39ySqP/m2MBxn0zF3DmaqrSPIRMhS8gG3d/23Jux3YHDEOBHjdJSdwqs5F5+QBFPV2rmJnpcSoW4d3M119XI20L914c62R7wY4e6+qmi3ydQU9g6p8psZgaE3TuMsyzX3k4C30nC/3gWT+zl253NjZwfbzIdHu5LWNDY9kEHtKzLP
# NB: Appveyor cache is disabled, because it is proving very unreliable. # NB: Appveyor cache is disabled, because it is proving very unreliable.
# We can re-enable it when we find a way to mitigate the unreliability # We can re-enable it when we find a way to mitigate the unreliability
# issues. Have automated builds be reliable is the more important thing. # issues. Have automated builds be reliable is the more important thing.
@ -43,7 +47,14 @@ configuration:
#init: #init:
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
install: install:
- ps: $fileContent = "-----BEGIN RSA PRIVATE KEY-----`n"
- ps: $fileContent += $env:priv_key.Replace(' ', "`n")
- ps: $fileContent += "`n-----END RSA PRIVATE KEY-----`n"
- ps: Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent
- git submodule update --init --recursive - git submodule update --init --recursive
- ps: $prerelease = "nightly."
- ps: $prerelease += Get-Date -format "yyyy.M.d"
- ps: Set-Content prerelease.txt $prerelease
- scripts/install_deps.bat - scripts/install_deps.bat
- set ETHEREUM_DEPS_PATH=%APPVEYOR_BUILD_FOLDER%\deps\install - set ETHEREUM_DEPS_PATH=%APPVEYOR_BUILD_FOLDER%\deps\install
before_build: before_build:
@ -54,15 +65,12 @@ build_script:
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal - msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
- cd %APPVEYOR_BUILD_FOLDER% - cd %APPVEYOR_BUILD_FOLDER%
- scripts\release.bat %CONFIGURATION% - scripts\release.bat %CONFIGURATION%
- scripts\bytecodecompare\storebytecode.bat %CONFIGURATION% %APPVEYOR_REPO_COMMIT%
test_script: test_script:
- cd %APPVEYOR_BUILD_FOLDER% - cd %APPVEYOR_BUILD_FOLDER%
- cd deps\install\x64\eth
- ps: $ethProc = Start-Process eth.exe --test
- ps: Start-Sleep -s 100
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
- copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" . - soltest.exe --show-progress -- --no-ipc
- soltest.exe --show-progress -- --ipcpath \\.\pipe\geth.ipc
artifacts: artifacts:
- path: solidity-windows.zip - path: solidity-windows.zip

View File

@ -11,7 +11,7 @@ differs from standalone assembly and then specify assembly itself.
TODO: Write about how scoping rules of inline assembly are a bit different TODO: Write about how scoping rules of inline assembly are a bit different
and the complications that arise when for example using internal functions and the complications that arise when for example using internal functions
of libraries. Furhermore, write about the symbols defined by the compiler. of libraries. Furthermore, write about the symbols defined by the compiler.
Inline Assembly Inline Assembly
=============== ===============
@ -29,7 +29,7 @@ arising when writing manual assembly by the following features:
* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`` * 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) }`` * 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 }`` * 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))) } }`` * function calls: ``function f(x) -> y { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }``
.. note:: .. note::
Of the above, loops, function calls and switch statements are not yet implemented. Of the above, loops, function calls and switch statements are not yet implemented.
@ -323,9 +323,12 @@ Access to External Variables and Functions
------------------------------------------ ------------------------------------------
Solidity variables and other identifiers can be accessed by simply using their name. Solidity variables and other identifiers can be accessed by simply using their name.
For storage and memory variables, this will push the address and not the value onto the For memory variables, this will push the address and not the value onto the
stack. Also note that non-struct and non-array storage variable addresses occupy two slots stack. Storage variables are different: Values in storage might not occupy a
on the stack: One for the address and one for the byte offset inside the storage slot. full storage slot, so their "address" is composed of a slot and a byte-offset
inside that slot. To retrieve the slot pointed to by the variable ``x``, you
used ``x_slot`` and to retrieve the byte-offset you used ``x_offset``.
In assignments (see below), we can even use local Solidity variables to assign to. In assignments (see below), we can even use local Solidity variables to assign to.
Functions external to inline assembly can also be accessed: The assembly will Functions external to inline assembly can also be accessed: The assembly will
@ -340,17 +343,13 @@ changes during the call, and thus references to local variables will be wrong.
.. code:: .. code::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract C { contract C {
uint b; uint b;
function f(uint x) returns (uint r) { function f(uint x) returns (uint r) {
assembly { assembly {
b pop // remove the offset, we know it is zero r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero
sload
x
mul
=: r // assign to return variable r
} }
} }
} }
@ -567,7 +566,7 @@ The following example implements the power function by square-and-multiply.
.. code:: .. code::
assembly { assembly {
function power(base, exponent) -> (result) { function power(base, exponent) -> result {
switch exponent switch exponent
0: { result := 1 } 0: { result := 1 }
1: { result := base } 1: { result := base }
@ -702,12 +701,12 @@ The following assembly will be generated::
} }
default: { jump(invalidJumpLabel) } default: { jump(invalidJumpLabel) }
// memory allocator // memory allocator
function $allocate(size) -> (pos) { function $allocate(size) -> pos {
pos := mload(0x40) pos := mload(0x40)
mstore(0x40, add(pos, size)) mstore(0x40, add(pos, size))
} }
// the contract function // the contract function
function f(x) -> (y) { function f(x) -> y {
y := 1 y := 1
for { let i := 0 } lt(i, x) { i := add(i, 1) } { for { let i := 0 } lt(i, x) { i := add(i, 1) } {
y := mul(2, y) y := mul(2, y)

103
docs/bugs.json Normal file
View File

@ -0,0 +1,103 @@
[
{
"name": "ConstantOptimizerSubtraction",
"summary": "In some situations, the optimizer replaces certain numbers in the code with routines that compute different numbers.",
"description": "The optimizer tries to represent any number in the bytecode by routines that compute them with less gas. For some special numbers, an incorrect routine is generated. This could allow an attacker to e.g. trick victims about a specific amount of ether, or function calls to call different functions (or none at all).",
"link": "https://blog.ethereum.org/2017/05/03/solidity-optimizer-bug/",
"fixed": "0.4.11",
"severity": "low",
"conditions": {
"optimizer": true
}
},
{
"name": "IdentityPrecompileReturnIgnored",
"summary": "Failure of the identity precompile was ignored.",
"description": "Calls to the identity contract, which is used for copying memory, ignored its return value. On the public chain, calls to the identity precompile can be made in a way that they never fail, but this might be different on private chains.",
"severity": "low",
"fixed": "0.4.7"
},
{
"name": "OptimizerStateKnowledgeNotResetForJumpdest",
"summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
"description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was simplified to just use the empty state, but this implementation was not done properly. This bug can cause data corruption.",
"severity": "medium",
"introduced": "0.4.5",
"fixed": "0.4.6",
"conditions": {
"optimizer": true
}
},
{
"name": "HighOrderByteCleanStorage",
"summary": "For short types, the high order bytes were not cleaned properly and could overwrite existing data.",
"description": "Types shorter than 32 bytes are packed together into the same 32 byte storage slot, but storage writes always write 32 bytes. For some types, the higher order bytes were not cleaned properly, which made it sometimes possible to overwrite a variable in storage when writing to another one.",
"link": "https://blog.ethereum.org/2016/11/01/security-alert-solidity-variables-can-overwritten-storage/",
"severity": "high",
"introduced": "0.1.6",
"fixed": "0.4.4"
},
{
"name": "OptimizerStaleKnowledgeAboutSHA3",
"summary": "The optimizer did not properly reset its knowledge about SHA3 operations resulting in some hashes (also used for storage variable positions) not being calculated correctly.",
"description": "The optimizer performs symbolic execution in order to save re-evaluating expressions whose value is already known. This knowledge was not properly reset across control flow paths and thus the optimizer sometimes thought that the result of a SHA3 operation is already present on the stack. This could result in data corruption by accessing the wrong storage slot.",
"severity": "medium",
"fixed": "0.4.3",
"conditions": {
"optimizer": true
}
},
{
"name": "LibrariesNotCallableFromPayableFunctions",
"summary": "Library functions threw an exception when called from a call that received Ether.",
"description": "Library functions are protected against sending them Ether through a call. Since the DELEGATECALL opcode forwards the information about how much Ether was sent with a call, the library function incorrectly assumed that Ether was sent to the library and threw an exception.",
"severity": "low",
"introduced": "0.4.0",
"fixed": "0.4.2"
},
{
"name": "SendFailsForZeroEther",
"summary": "The send function did not provide enough gas to the recipient if no Ether was sent with it.",
"description": "The recipient of an Ether transfer automatically receives a certain amount of gas from the EVM to handle the transfer. In the case of a zero-transfer, this gas is not provided which causes the recipient to throw an exception.",
"severity": "low",
"fixed": "0.4.0"
},
{
"name": "DynamicAllocationInfiniteLoop",
"summary": "Dynamic allocation of an empty memory array caused an infinite loop and thus an exception.",
"description": "Memory arrays can be created provided a length. If this length is zero, code was generated that did not terminate and thus consumed all gas.",
"severity": "low",
"fixed": "0.3.6"
},
{
"name": "OptimizerClearStateOnCodePathJoin",
"summary": "The optimizer did not properly reset its internal state at jump destinations, which could lead to data corruption.",
"description": "The optimizer performs symbolic execution at certain stages. At jump destinations, multiple code paths join and thus it has to compute a common state from the incoming edges. Computing this common state was not done correctly. This bug can cause data corruption, but it is probably quite hard to use for targeted attacks.",
"severity": "low",
"fixed": "0.3.6",
"conditions": {
"optimizer": true
}
},
{
"name": "CleanBytesHigherOrderBits",
"summary": "The higher order bits of short bytesNN types were not cleaned before comparison.",
"description": "Two variables of type bytesNN were considered different if their higher order bits, which are not part of the actual value, were different. An attacker might use this to reach seemingly unreachable code paths by providing incorrectly formatted input data.",
"severity": "medium/high",
"fixed": "0.3.3"
},
{
"name": "ArrayAccessCleanHigherOrderBits",
"summary": "Access to array elements for arrays of types with less than 32 bytes did not correctly clean the higher order bits, causing corruption in other array elements.",
"description": "Multiple elements of an array of values that are shorter than 17 bytes are packed into the same storage slot. Writing to a single element of such an array did not properly clean the higher order bytes and thus could lead to data corruption.",
"severity": "medium/high",
"fixed": "0.3.1"
},
{
"name": "AncientCompiler",
"summary": "This compiler version is ancient and might contain several undocumented or undiscovered bugs.",
"description": "The list of bugs is only kept for compiler versions starting from 0.3.0, so older versions might contain undocumented bugs.",
"severity": "high",
"fixed": "0.3.0"
}
]

61
docs/bugs.rst Normal file
View File

@ -0,0 +1,61 @@
.. index:: Bugs
.. _known_bugs:
##################
List of Known Bugs
##################
Below, you can find a JSON-formatted list of some of the known security-relevant bugs in the
Solidity compiler. The file itself is hosted in the `Github repository
<https://github.com/ethereum/solidity/blob/develop/docs/bugs.json>`_.
The list stretches back as far as version 0.3.0, bugs known to be present only
in versions preceding that are not listed.
There is another file called `bugs_by_version.json
<https://github.com/ethereum/solidity/blob/develop/docs/bugs_by_version.json>`_,
which can be used to check which bugs affect a specific version of the compiler.
Contract source verification tools and also other tools interacting with
contracts should consult this list according to the following criteria:
- It is mildly suspicious if a contract was compiled with a nightly
compiler version instead of a released version. This list does not keep
track of unreleased or nightly versions.
- It is also mildly suspicious if a contract was compiled with a version that was
not the most recent at the time the contract was created. For contracts
created from other contracts, you have to follow the creation chain
back to a transaction and use the date of that transaction as creation date.
- It is highly suspicious if a contract was compiled with a compiler that
contains a known bug and the contract was created at a time where a newer
compiler version containing a fix was already released.
The JSON file of known bugs below is an array of objects, one for each bug,
with the following keys:
name
Unique name given to the bug
summary
Short description of the bug
description
Detailed description of the bug
link
URL of a website with more detailed information, optional
introduced
The first published compiler version that contained the bug, optional
fixed
The first published compiler version that did not contain the bug anymore
publish
The date at which the bug became known publicly, optional
severity
Severity of the bug: low, medium, high. Takes into account
discoverability in contract tests, likelihood of occurrence and
potential damage by exploits.
conditions
Conditions that have to be met to trigger the bug. Currently, this
is an object that can contain a boolean value ``optimizer``, which
means that the optimizer has to be switched on to enable the bug.
If no conditions are given, assume that the bug is present.
.. literalinclude:: bugs.json
:language: js

334
docs/bugs_by_version.json Normal file
View File

@ -0,0 +1,334 @@
{
"0.1.0": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-07-10"
},
"0.1.1": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-08-04"
},
"0.1.2": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-08-20"
},
"0.1.3": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-09-25"
},
"0.1.4": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-09-30"
},
"0.1.5": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-10-07"
},
"0.1.6": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-10-16"
},
"0.1.7": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-11-17"
},
"0.2.0": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2015-12-02"
},
"0.2.1": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2016-01-30"
},
"0.2.2": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits",
"AncientCompiler"
],
"released": "2016-02-17"
},
"0.3.0": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits",
"ArrayAccessCleanHigherOrderBits"
],
"released": "2016-03-11"
},
"0.3.1": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits"
],
"released": "2016-03-31"
},
"0.3.2": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin",
"CleanBytesHigherOrderBits"
],
"released": "2016-04-18"
},
"0.3.3": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin"
],
"released": "2016-05-27"
},
"0.3.4": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin"
],
"released": "2016-05-31"
},
"0.3.5": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther",
"DynamicAllocationInfiniteLoop",
"OptimizerClearStateOnCodePathJoin"
],
"released": "2016-06-10"
},
"0.3.6": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"SendFailsForZeroEther"
],
"released": "2016-08-10"
},
"0.4.0": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"LibrariesNotCallableFromPayableFunctions"
],
"released": "2016-09-08"
},
"0.4.1": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3",
"LibrariesNotCallableFromPayableFunctions"
],
"released": "2016-09-09"
},
"0.4.10": {
"bugs": [
"ConstantOptimizerSubtraction"
],
"released": "2017-03-15"
},
"0.4.11": {
"bugs": [],
"released": "2017-05-03"
},
"0.4.2": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
"OptimizerStaleKnowledgeAboutSHA3"
],
"released": "2016-09-17"
},
"0.4.3": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage"
],
"released": "2016-10-25"
},
"0.4.4": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored"
],
"released": "2016-10-31"
},
"0.4.5": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStateKnowledgeNotResetForJumpdest"
],
"released": "2016-11-21"
},
"0.4.6": {
"bugs": [
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored"
],
"released": "2016-11-22"
},
"0.4.7": {
"bugs": [
"ConstantOptimizerSubtraction"
],
"released": "2016-12-15"
},
"0.4.8": {
"bugs": [
"ConstantOptimizerSubtraction"
],
"released": "2017-01-13"
},
"0.4.9": {
"bugs": [
"ConstantOptimizerSubtraction"
],
"released": "2017-01-31"
}
}

View File

@ -23,12 +23,12 @@ contract in order to become the "richest", inspired by
`King of the Ether <https://www.kingoftheether.com/>`_. `King of the Ether <https://www.kingoftheether.com/>`_.
In the following contract, if you are usurped as the richest, In the following contract, if you are usurped as the richest,
you will recieve the funds of the person who has gone on to you will receive the funds of the person who has gone on to
become the new richest. become the new richest.
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract WithdrawalContract { contract WithdrawalContract {
address public richest; address public richest;
@ -52,17 +52,12 @@ become the new richest.
} }
} }
function withdraw() returns (bool) { function withdraw() {
uint amount = pendingWithdrawals[msg.sender]; uint amount = pendingWithdrawals[msg.sender];
// Remember to zero the pending refund before // Remember to zero the pending refund before
// sending to prevent re-entrancy attacks // sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0; pendingWithdrawals[msg.sender] = 0;
if (msg.sender.send(amount)) { msg.sender.transfer(amount);
return true;
} else {
pendingWithdrawals[msg.sender] = amount;
return false;
}
} }
} }
@ -70,7 +65,7 @@ This is as opposed to the more intuitive sending pattern:
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract SendContract { contract SendContract {
address public richest; address public richest;
@ -83,12 +78,8 @@ This is as opposed to the more intuitive sending pattern:
function becomeRichest() payable returns (bool) { function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) { if (msg.value > mostSent) {
// Check if call succeeds to prevent an attacker // This line can cause problems (explained below).
// from trapping the previous person's funds in richest.transfer(msg.value);
// this contract through a callstack attack
if (!richest.send(msg.value)) {
throw;
}
richest = msg.sender; richest = msg.sender;
mostSent = msg.value; mostSent = msg.value;
return true; return true;
@ -101,11 +92,15 @@ This is as opposed to the more intuitive sending pattern:
Notice that, in this example, an attacker could trap the Notice that, in this example, an attacker could trap the
contract into an unusable state by causing ``richest`` to be contract into an unusable state by causing ``richest`` to be
the address of a contract that has a fallback function the address of a contract that has a fallback function
which consumes more than the 2300 gas stipend. That way, which fails (e.g. by using ``revert()`` or by just
whenever ``send`` is called to deliver funds to the conssuming more than the 2300 gas stipend). That way,
"poisoned" contract, it will cause execution to always fail whenever ``transfer`` is called to deliver funds to the
because there will not be enough gas to finish the execution "poisoned" contract, it will fail and thus also ``becomeRichest``
of the fallback function. will fail, with the contract being stuck forever.
In contrast, if you use the "withdraw" pattern from the first example,
the attacker can only cause his or her own withdraw to fail and not the
rest of the contract's workings.
.. index:: access;restricting .. index:: access;restricting
@ -135,7 +130,7 @@ restrictions highly readable.
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract AccessRestriction { contract AccessRestriction {
// These will be assigned at the construction // These will be assigned at the construction
@ -152,8 +147,7 @@ restrictions highly readable.
// a certain address. // a certain address.
modifier onlyBy(address _account) modifier onlyBy(address _account)
{ {
if (msg.sender != _account) require(msg.sender == _account);
throw;
// Do not forget the "_;"! It will // Do not forget the "_;"! It will
// be replaced by the actual function // be replaced by the actual function
// body when the modifier is used. // body when the modifier is used.
@ -169,7 +163,7 @@ restrictions highly readable.
} }
modifier onlyAfter(uint _time) { modifier onlyAfter(uint _time) {
if (now < _time) throw; require(now >= _time);
_; _;
} }
@ -190,8 +184,7 @@ restrictions highly readable.
// This was dangerous before Solidity version 0.4.0, // This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`. // where it was possible to skip the part after `_;`.
modifier costs(uint _amount) { modifier costs(uint _amount) {
if (msg.value < _amount) require(msg.value >= _amount);
throw;
_; _;
if (msg.value > _amount) if (msg.value > _amount)
msg.sender.send(msg.value - _amount); msg.sender.send(msg.value - _amount);
@ -232,7 +225,7 @@ reached at a certain point in **time**.
An example for this is a blind auction contract which An example for this is a blind auction contract which
starts in the stage "accepting blinded bids", then starts in the stage "accepting blinded bids", then
transitions to "revealing bids" which is ended by transitions to "revealing bids" which is ended by
"determine auction autcome". "determine auction outcome".
.. index:: function;modifier .. index:: function;modifier
@ -276,7 +269,7 @@ function finishes.
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract StateMachine { contract StateMachine {
enum Stages { enum Stages {
@ -293,7 +286,7 @@ function finishes.
uint public creationTime = now; uint public creationTime = now;
modifier atStage(Stages _stage) { modifier atStage(Stages _stage) {
if (stage != _stage) throw; require(stage == _stage);
_; _;
} }

View File

@ -27,7 +27,7 @@ From ``web3.js``, i.e. the JavaScript
API, this is done as follows:: API, this is done as follows::
// Need to specify some source including contract name for the data param below // Need to specify some source including contract name for the data param below
var source = "contract CONTRACT_NAME { function CONTRACT_NAME(unit a, uint b) {} }"; var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }";
// The json abi array generated by the compiler // The json abi array generated by the compiler
var abiArray = [ var abiArray = [
@ -327,7 +327,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract owned { contract owned {
function owned() { owner = msg.sender; } function owned() { owner = msg.sender; }
@ -341,8 +341,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
// function is executed and otherwise, an exception is // function is executed and otherwise, an exception is
// thrown. // thrown.
modifier onlyOwner { modifier onlyOwner {
if (msg.sender != owner) require(msg.sender == owner);
throw;
_; _;
} }
} }
@ -390,7 +389,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
contract Mutex { contract Mutex {
bool locked; bool locked;
modifier noReentrancy() { modifier noReentrancy() {
if (locked) throw; require(!locked);
locked = true; locked = true;
_; _;
locked = false; locked = false;
@ -401,7 +400,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
/// The `return 7` statement assigns 7 to the return value but still /// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier. /// executes the statement `locked = false` in the modifier.
function f() noReentrancy returns (uint) { function f() noReentrancy returns (uint) {
if (!msg.sender.call()) throw; require(msg.sender.call());
return 7; return 7;
} }
} }
@ -436,7 +435,7 @@ execution data (``msg.gas``) or make calls to external contracts are disallowed.
that might have a side-effect on memory allocation are allowed, but those that that might have a side-effect on memory allocation are allowed, but those that
might have a side-effect on other memory objects are not. The built-in functions might have a side-effect on other memory objects are not. The built-in functions
``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod`` ``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod``
are allowed (ever though they do call external contracts). are allowed (even though they do call external contracts).
The reason behind allowing side-effects on the memory allocator is that it 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. should be possible to construct complex objects like e.g. lookup-tables.
@ -922,6 +921,35 @@ Such contracts cannot be compiled (even if they contain implemented functions al
If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract. If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract.
.. index:: ! contract;interface, ! interface contract
**********
Interfaces
**********
Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions:
#. Cannot inherit other contracts or interfaces.
#. Cannot define constructor.
#. Cannot define variables.
#. Cannot define structs.
#. Cannot define enums.
Some of these restrictions might be lifted in the future.
Interfaces are basically limited to what the Contract ABI can represent and the conversion between the ABI and
an Interface should be possible without any information loss.
Interfaces are denoted by their own keyword:
::
interface Token {
function transfer(address recipient, uint amount);
}
Contracts can inherit interfaces as they would inherit other contracts.
.. index:: ! library, callcode, delegatecall .. index:: ! library, callcode, delegatecall
.. _libraries: .. _libraries:
@ -960,7 +988,7 @@ more advanced example to implement a set).
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
library Set { library Set {
// We define a new struct datatype that will be used to // We define a new struct datatype that will be used to
@ -1006,8 +1034,7 @@ more advanced example to implement a set).
// The library functions can be called without a // The library functions can be called without a
// specific instance of the library, since the // specific instance of the library, since the
// "instance" will be the current contract. // "instance" will be the current contract.
if (!Set.insert(knownValues, value)) require(Set.insert(knownValues, value));
throw;
} }
// In this contract, we can also directly access knownValues.flags, if we want. // In this contract, we can also directly access knownValues.flags, if we want.
} }
@ -1101,7 +1128,7 @@ Restrictions for libraries in comparison to contracts:
- No state variables - No state variables
- Cannot inherit nor be inherited - Cannot inherit nor be inherited
- Cannot recieve Ether - Cannot receive Ether
(These might be lifted at a later point.) (These might be lifted at a later point.)
@ -1137,7 +1164,7 @@ available without having to add further code.
Let us rewrite the set example from the Let us rewrite the set example from the
:ref:`libraries` in this way:: :ref:`libraries` in this way::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
// This is the same code as before, just without comments // This is the same code as before, just without comments
library Set { library Set {
@ -1178,8 +1205,7 @@ Let us rewrite the set example from the
// corresponding member functions. // corresponding member functions.
// The following function call is identical to // The following function call is identical to
// Set.insert(knownValues, value) // Set.insert(knownValues, value)
if (!knownValues.insert(value)) require(knownValues.insert(value));
throw;
} }
} }

View File

@ -113,8 +113,8 @@ actual contract has not been created yet.
Functions of other contracts have to be called externally. For an external call, Functions of other contracts have to be called externally. For an external call,
all function arguments have to be copied to memory. all function arguments have to be copied to memory.
When calling functions When calling functions of other contracts, the amount of Wei sent with the call and
of other contracts, the amount of Wei sent with the call and the gas can be specified:: the gas can be specified with special options ``.value()`` and ``.gas()``, respectively::
contract InfoFeed { contract InfoFeed {
function info() payable returns (uint ret) { return 42; } function info() payable returns (uint ret) { return 42; }
@ -127,8 +127,8 @@ of other contracts, the amount of Wei sent with the call and the gas can be spec
function callFeed() { feed.info.value(10).gas(800)(); } function callFeed() { feed.info.value(10).gas(800)(); }
} }
The modifier ``payable`` has to be used for ``info``, because otherwise, The modifier ``payable`` has to be used for ``info``, because otherwise, the `.value()`
we would not be able to send Ether to it in the call ``feed.info.value(10).gas(800)()``. option would not be available.
Note that the expression ``InfoFeed(addr)`` performs an explicit type conversion stating Note that the expression ``InfoFeed(addr)`` performs an explicit type conversion stating
that "we know that the type of the contract at the given address is ``InfoFeed``" and that "we know that the type of the contract at the given address is ``InfoFeed``" and
@ -235,7 +235,7 @@ creation-dependencies are not possible.
} }
} }
As seen in the example, it is possible to forward Ether to the creation, As seen in the example, it is possible to forward Ether to the creation using the ``.value()`` option,
but it is not possible to limit the amount of gas. If the creation fails but it is not possible to limit the amount of gas. If the creation fails
(due to out-of-stack, not enough balance or other problems), an exception (due to out-of-stack, not enough balance or other problems), an exception
is thrown. is thrown.
@ -399,6 +399,7 @@ Currently, Solidity automatically generates a runtime exception in the following
#. If you call ``assert`` with an argument that evaluates to false. #. If you call ``assert`` with an argument that evaluates to false.
While a user-provided exception is generated in the following situations: While a user-provided exception is generated in the following situations:
#. Calling ``throw``. #. Calling ``throw``.
#. Calling ``require`` with an argument that evaluates to ``false``. #. Calling ``require`` with an argument that evaluates to ``false``.

View File

@ -68,7 +68,7 @@ creator. Save it. Then ``selfdestruct(creator);`` to kill and return funds.
Note that if you ``import "mortal"`` at the top of your contracts and declare Note that if you ``import "mortal"`` at the top of your contracts and declare
``contract SomeContract is mortal { ...`` and compile with a compiler that already ``contract SomeContract is mortal { ...`` and compile with a compiler that already
has it (which includes `browser-solidity <https://ethereum.github.io/browser-solidity/>`_), then has it (which includes `Remix <https://remix.ethereum.org/>`_), then
``kill()`` is taken care of for you. Once a contract is "mortal", then you can ``kill()`` is taken care of for you. Once a contract is "mortal", then you can
``contractname.kill.sendTransaction({from:eth.coinbase})``, just the same as my ``contractname.kill.sendTransaction({from:eth.coinbase})``, just the same as my
examples. examples.
@ -665,8 +665,7 @@ What does the following strange check do in the Custom Token contract?
:: ::
if (balanceOf[_to] + _value < balanceOf[_to]) require((balanceOf[_to] + _value) >= balanceOf[_to]);
throw;
Integers in Solidity (and most other machine-related programming languages) are restricted to a certain range. Integers in Solidity (and most other machine-related programming languages) are restricted to a certain range.
For ``uint256``, this is ``0`` up to ``2**256 - 1``. If the result of some operation on those numbers For ``uint256``, this is ``0`` up to ``2**256 - 1``. If the result of some operation on those numbers

View File

@ -2,7 +2,7 @@ Solidity
======== ========
Solidity is a contract-oriented, high-level language whose syntax is similar to that of JavaScript Solidity is a contract-oriented, high-level language whose syntax is similar to that of JavaScript
and it is designed to target the Ethereum Virtual Machine. and it is designed to target the Ethereum Virtual Machine (EVM).
Solidity is statically typed, supports inheritance, libraries and complex Solidity is statically typed, supports inheritance, libraries and complex
user-defined types among other features. user-defined types among other features.
@ -11,8 +11,8 @@ As you will see, it is possible to create contracts for voting,
crowdfunding, blind auctions, multi-signature wallets and more. crowdfunding, blind auctions, multi-signature wallets and more.
.. note:: .. note::
The best way to try out Solidity right now is using the The best way to try out Solidity right now is using
`Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_ `Remix <https://remix.ethereum.org/>`_
(it can take a while to load, please be patient). (it can take a while to load, please be patient).
Useful links Useful links
@ -33,7 +33,7 @@ Useful links
Available Solidity Integrations Available Solidity Integrations
------------------------------- -------------------------------
* `Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_ * `Remix <https://remix.ethereum.org/>`_
Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components.
* `Ethereum Studio <https://live.ether.camp/>`_ * `Ethereum Studio <https://live.ether.camp/>`_
@ -48,8 +48,8 @@ Available Solidity Integrations
* `Package for SublimeText — Solidity language syntax <https://packagecontrol.io/packages/Ethereum/>`_ * `Package for SublimeText — Solidity language syntax <https://packagecontrol.io/packages/Ethereum/>`_
Solidity syntax highlighting for SublimeText editor. Solidity syntax highlighting for SublimeText editor.
* `Atom Ethereum interface <https://github.com/gmtDevs/atom-ethereum-interface>`_ * `Etheratom <https://github.com/0mkara/etheratom>`_
Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (requires backend node). Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (Backend node & VM compatible).
* `Atom Solidity Linter <https://atom.io/packages/linter-solidity>`_ * `Atom Solidity Linter <https://atom.io/packages/linter-solidity>`_
Plugin for the Atom editor that provides Solidity linting. Plugin for the Atom editor that provides Solidity linting.
@ -78,8 +78,8 @@ Discontinued:
Solidity Tools Solidity Tools
-------------- --------------
* `Dapple <https://github.com/nexusdev/dapple>`_ * `Dapp <https://dapp.readthedocs.io>`_
Package and deployment manager for Solidity. Build tool, package manager, and deployment assistant for Solidity.
* `Solidity REPL <https://github.com/raineorshine/solidity-repl>`_ * `Solidity REPL <https://github.com/raineorshine/solidity-repl>`_
Try Solidity instantly with a command-line Solidity console. Try Solidity instantly with a command-line Solidity console.
@ -90,6 +90,9 @@ Solidity Tools
* `evmdis <https://github.com/Arachnid/evmdis>`_ * `evmdis <https://github.com/Arachnid/evmdis>`_
EVM Disassembler that performs static analysis on the bytecode to provide a higher level of abstraction than raw EVM operations. EVM Disassembler that performs static analysis on the bytecode to provide a higher level of abstraction than raw EVM operations.
* `Doxity <https://github.com/DigixGlobal/doxity>`_
Documentation Generator for Solidity.
Third-Party Solidity Parsers and Grammars Third-Party Solidity Parsers and Grammars
----------------------------------------- -----------------------------------------
@ -109,7 +112,7 @@ and the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`.
The next section will explain several *features* of Solidity by giving The next section will explain several *features* of Solidity by giving
useful :ref:`example contracts <voting>` useful :ref:`example contracts <voting>`
Remember that you can always try out the contracts Remember that you can always try out the contracts
`in your browser <https://ethereum.github.io/browser-solidity>`_! `in your browser <https://remix.ethereum.org>`_!
The last and most extensive section will cover all aspects of Solidity in depth. The last and most extensive section will cover all aspects of Solidity in depth.
@ -136,5 +139,6 @@ Contents
using-the-compiler.rst using-the-compiler.rst
style-guide.rst style-guide.rst
common-patterns.rst common-patterns.rst
bugs.rst
contributing.rst contributing.rst
frequently-asked-questions.rst frequently-asked-questions.rst

View File

@ -15,11 +15,11 @@ are not guaranteed to be working and despite best efforts they might contain und
and/or broken changes. We recommend using the latest release. Package installers below and/or broken changes. We recommend using the latest release. Package installers below
will use the latest release. will use the latest release.
Browser-Solidity Remix
================ =====
If you just want to try Solidity for small contracts, you If you just want to try Solidity for small contracts, you
can try `browser-solidity <https://ethereum.github.io/browser-solidity>`_ can try `Remix <https://remix.ethereum.org/>`_
which does not need any installation. If you want to use it which does not need any installation. If you want to use it
without connection to the Internet, you can go to without connection to the Internet, you can go to
https://github.com/ethereum/browser-solidity/tree/gh-pages and https://github.com/ethereum/browser-solidity/tree/gh-pages and
@ -31,7 +31,7 @@ npm / Node.js
This is probably the most portable and most convenient way to install Solidity locally. This is probably the most portable and most convenient way to install Solidity locally.
A platform-independent JavaScript library is provided by compiling the C++ source A platform-independent JavaScript library is provided by compiling the C++ source
into JavaScript using Emscripten. It can be used in projects directly (such as Browser-Solidity). into JavaScript using Emscripten. It can be used in projects directly (such as Remix).
Please refer to the `solc-js <https://github.com/ethereum/solc-js>`_ repository for instructions. Please refer to the `solc-js <https://github.com/ethereum/solc-js>`_ repository for instructions.
It also contains a commandline tool called `solcjs`, which can be installed via npm: It also contains a commandline tool called `solcjs`, which can be installed via npm:
@ -250,6 +250,7 @@ The version string in detail
============================ ============================
The Solidity version string contains four parts: The Solidity version string contains four parts:
- the version number - the version number
- pre-release tag, usually set to ``develop.YYYY.MM.DD`` or ``nightly.YYYY.MM.DD`` - pre-release tag, usually set to ``develop.YYYY.MM.DD`` or ``nightly.YYYY.MM.DD``
- commit in the format of ``commit.GITHASH`` - commit in the format of ``commit.GITHASH``
@ -280,4 +281,4 @@ Example:
3. a breaking change is introduced - version is bumped to 0.5.0 3. a breaking change is introduced - version is bumped to 0.5.0
4. the 0.5.0 release is made 4. the 0.5.0 release is made
This behaviour works well with the version pragma. This behaviour works well with the :ref:`version pragma <version_pragma>`.

View File

@ -146,7 +146,7 @@ single account.
The line ``event Sent(address from, address to, uint amount);`` declares The line ``event Sent(address from, address to, uint amount);`` declares
a so-called "event" which is fired in the last line of the function a so-called "event" which is fired in the last line of the function
``send``. User interfaces (as well as server appliances of course) can ``send``. User interfaces (as well as server applications of course) can
listen for those events being fired on the blockchain without much listen for those events being fired on the blockchain without much
cost. As soon as it is fired, the listener will also receive the cost. As soon as it is fired, the listener will also receive the
arguments ``from``, ``to`` and ``amount``, which makes it easy to track arguments ``from``, ``to`` and ``amount``, which makes it easy to track
@ -161,7 +161,7 @@ transactions. In order to listen for this event, you would use ::
"Sender: " + Coin.balances.call(result.args.from) + "Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to)); "Receiver: " + Coin.balances.call(result.args.to));
} }
} })
Note how the automatically generated function ``balances`` is called from Note how the automatically generated function ``balances`` is called from
the user interface. the user interface.

View File

@ -7,6 +7,8 @@ and pragma directives.
.. index:: ! pragma, version .. index:: ! pragma, version
.. _version_pragma:
Version Pragma Version Pragma
============== ==============
@ -24,7 +26,7 @@ The version pragma is used as follows::
pragma solidity ^0.4.0; pragma solidity ^0.4.0;
Such a source file will not compile with a compiler earlier than version 0.4.0 Such a source file will not compile with a compiler earlier than version 0.4.0
and it will also not work on a compiler starting form version 0.5.0 (this and it will also not work on a compiler starting from version 0.5.0 (this
second condition is added by using ``^``). The idea behind this is that second condition is added by using ``^``). The idea behind this is that
there will be no breaking changes until version ``0.5.0``, so we can always there will be no breaking changes until version ``0.5.0``, so we can always
be sure that our code will compile the way we intended it to. We do not fix be sure that our code will compile the way we intended it to. We do not fix
@ -151,9 +153,9 @@ remapping ``=/``.
If there are multiple remappings that lead to a valid file, the remapping If there are multiple remappings that lead to a valid file, the remapping
with the longest common prefix is chosen. with the longest common prefix is chosen.
**browser-solidity**: **Remix**:
The `browser-based compiler <https://ethereum.github.io/browser-solidity>`_ `Remix <https://remix.ethereum.org/>`_
provides an automatic remapping for github and will also automatically retrieve provides an automatic remapping for github and will also automatically retrieve
the file over the network: the file over the network:
You can import the iterable mapping by e.g. You can import the iterable mapping by e.g.

View File

@ -314,6 +314,14 @@ Comments are of course also not permitted and used here only for explanatory pur
} }
} }
.. note::
Note the ABI definition above has no fixed order. It can change with compiler versions.
.. note::
Since the bytecode of the resulting contract contains the metadata hash, any change to
the metadata will result in a change of the bytecode. Furthermore, since the metadata
includes a hash of all the sources used, a single whitespace change in any of the source
codes will result in a different metadata, and subsequently a different bytecode.
Encoding of the Metadata Hash in the Bytecode Encoding of the Metadata Hash in the Bytecode
============================================= =============================================

View File

@ -22,7 +22,11 @@ you should be more careful.
This section will list some pitfalls and general security recommendations but This section will list some pitfalls and general security recommendations but
can, of course, never be complete. Also, keep in mind that even if your can, of course, never be complete. Also, keep in mind that even if your
smart contract code is bug-free, the compiler or the platform itself might smart contract code is bug-free, the compiler or the platform itself might
have a bug. have a bug. A list of some publicly known security-relevant bugs of the compiler
can be found in the
:ref:`list of known bugs<known_bugs>`, which is also machine-readable. Note
that there is a bug bounty program that covers the code generator of the
Solidity compiler.
As always, with open source documentation, please help us extend this section As always, with open source documentation, please help us extend this section
(especially, some examples would not hurt)! (especially, some examples would not hurt)!
@ -75,7 +79,7 @@ outlined further below:
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract Fund { contract Fund {
/// Mapping of ether shares of the contract. /// Mapping of ether shares of the contract.
@ -84,8 +88,7 @@ outlined further below:
function withdraw() { function withdraw() {
var share = shares[msg.sender]; var share = shares[msg.sender];
shares[msg.sender] = 0; shares[msg.sender] = 0;
if (!msg.sender.send(share)) msg.sender.transfer(share);
throw;
} }
} }
@ -117,25 +120,27 @@ Sending and Receiving Ether
During the execution of the fallback function, the contract can only rely During the execution of the fallback function, the contract can only rely
on the "gas stipend" (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way. on the "gas stipend" (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way.
To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function
(for example in the "details" section in browser-solidity). (for example in the "details" section in Remix).
- There is a way to forward more gas to the receiving contract using - There is a way to forward more gas to the receiving contract using
``addr.call.value(x)()``. This is essentially the same as ``addr.send(x)``, ``addr.call.value(x)()``. This is essentially the same as ``addr.transfer(x)``,
only that it forwards all remaining gas and opens up the ability for the only that it forwards all remaining gas and opens up the ability for the
recipient to perform more expensive actions. This might include calling back recipient to perform more expensive actions (and it only returns a failure code
and does not automatically propagate the error). This might include calling back
into the sending contract or other state changes you might not have thought of. into the sending contract or other state changes you might not have thought of.
So it allows for great flexibility for honest users but also for malicious actors. So it allows for great flexibility for honest users but also for malicious actors.
- If you want to send Ether using ``address.send``, there are certain details to be aware of: - If you want to send Ether using ``address.transfer``, there are certain details to be aware of:
1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract. 1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract.
2. Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call 2. Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call
depth, they can force the transfer to fail; make sure to always check the return value of ``send``. Better yet, depth, they can force the transfer to fail; take this possibility into account or use ``send`` and make sure to always check its return value. Better yet,
write your contract using a pattern where the recipient can withdraw Ether instead. write your contract using a pattern where the recipient can withdraw Ether instead.
3. Sending Ether can also fail because the execution of the recipient contract 3. Sending Ether can also fail because the execution of the recipient contract
requires more than the allotted amount of gas (explicitly by using ``throw`` or requires more than the allotted amount of gas (explicitly by using ``require``,
``assert``, ``revert``, ``throw`` or
because the operation is just too expensive) - it "runs out of gas" (OOG). because the operation is just too expensive) - it "runs out of gas" (OOG).
If the return value of ``send`` is checked, this might provide a If you use ``transfer`` or ``send`` with a return value check, this might provide a
means for the recipient to block progress in the sending contract. Again, the best practice here is to use means for the recipient to block progress in the sending contract. Again, the best practice here is to use
a :ref:`"withdraw" pattern instead of a "send" pattern <withdrawal_pattern>`. a :ref:`"withdraw" pattern instead of a "send" pattern <withdrawal_pattern>`.
@ -158,7 +163,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE // THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet { contract TxUserWallet {
@ -168,9 +173,9 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
owner = msg.sender; owner = msg.sender;
} }
function transfer(address dest, uint amount) { function transferTo(address dest, uint amount) {
if (tx.origin != owner) { throw; } require(tx.origin == owner);
if (!dest.call.value(amount)()) throw; dest.transfer(amount);
} }
} }
@ -188,7 +193,7 @@ Now someone tricks you into sending ether to the address of this attack wallet:
} }
function() { function() {
TxUserWallet(msg.sender).transfer(owner, msg.sender.balance); TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
} }
} }

View File

@ -36,7 +36,7 @@ of votes.
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
/// @title Voting with delegation. /// @title Voting with delegation.
contract Ballot { contract Ballot {
@ -51,8 +51,7 @@ of votes.
} }
// This is a type for a single proposal. // This is a type for a single proposal.
struct Proposal struct Proposal {
{
bytes32 name; // short name (up to 32 bytes) bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes uint voteCount; // number of accumulated votes
} }
@ -88,14 +87,14 @@ of votes.
// Give `voter` the right to vote on this ballot. // Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`. // May only be called by `chairperson`.
function giveRightToVote(address voter) { function giveRightToVote(address voter) {
if (msg.sender != chairperson || voters[voter].voted) { // If the argument of `require` evaluates to `false`,
// `throw` terminates and reverts all changes to // it terminates and reverts all changes to
// the state and to Ether balances. It is often // the state and to Ether balances. It is often
// a good idea to use this if functions are // a good idea to use this if functions are
// called incorrectly. But watch out, this // called incorrectly. But watch out, this
// will also consume all provided gas. // will currently also consume all provided gas
throw; // (this is planned to change in the future).
} require((msg.sender == chairperson) && !voters[voter].voted);
voters[voter].weight = 1; voters[voter].weight = 1;
} }
@ -103,12 +102,10 @@ of votes.
function delegate(address to) { function delegate(address to) {
// assigns reference // assigns reference
Voter sender = voters[msg.sender]; Voter sender = voters[msg.sender];
if (sender.voted) require(!sender.voted);
throw;
// Self-delegation is not allowed. // Self-delegation is not allowed.
if (to == msg.sender) require(to != msg.sender);
throw;
// Forward the delegation as long as // Forward the delegation as long as
// `to` also delegated. // `to` also delegated.
@ -122,8 +119,7 @@ of votes.
to = voters[to].delegate; to = voters[to].delegate;
// We found a loop in the delegation, not allowed. // We found a loop in the delegation, not allowed.
if (to == msg.sender) require(to != msg.sender);
throw;
} }
// Since `sender` is a reference, this // Since `sender` is a reference, this
@ -146,8 +142,7 @@ of votes.
/// to proposal `proposals[proposal].name`. /// to proposal `proposals[proposal].name`.
function vote(uint proposal) { function vote(uint proposal) {
Voter sender = voters[msg.sender]; Voter sender = voters[msg.sender];
if (sender.voted) require(!sender.voted);
throw;
sender.voted = true; sender.voted = true;
sender.vote = proposal; sender.vote = proposal;
@ -219,7 +214,7 @@ activate themselves.
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract SimpleAuction { contract SimpleAuction {
// Parameters of the auction. Times are either // Parameters of the auction. Times are either
@ -270,22 +265,21 @@ activate themselves.
// the transaction. The keyword payable // the transaction. The keyword payable
// is required for the function to // is required for the function to
// be able to receive Ether. // be able to receive Ether.
if (now > auctionStart + biddingTime) {
// Revert the call if the bidding // Revert the call if the bidding
// period is over. // period is over.
throw; require(now <= (auctionStart + biddingTime));
}
if (msg.value <= highestBid) {
// If the bid is not higher, send the // If the bid is not higher, send the
// money back. // money back.
throw; require(msg.value > highestBid);
}
if (highestBidder != 0) { if (highestBidder != 0) {
// Sending back the money by simply using // Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk // highestBidder.send(highestBid) is a security risk
// because it can be prevented by the caller by e.g. // because it can be prevented by the caller by e.g.
// raising the call stack to 1023. It is always safer // raising the call stack to 1023. It is always safer
// to let the recipient withdraw their money themselves. // to let the recipients withdraw their money themselves.
pendingReturns[highestBidder] += highestBid; pendingReturns[highestBidder] += highestBid;
} }
highestBidder = msg.sender; highestBidder = msg.sender;
@ -328,18 +322,15 @@ activate themselves.
// external contracts. // external contracts.
// 1. Conditions // 1. Conditions
if (now <= auctionStart + biddingTime) require(now >= (auctionStart + biddingTime)); // auction did not yet end
throw; // auction did not yet end require(!ended); // this function has already been called
if (ended)
throw; // this function has already been called
// 2. Effects // 2. Effects
ended = true; ended = true;
AuctionEnded(highestBidder, highestBid); AuctionEnded(highestBidder, highestBid);
// 3. Interaction // 3. Interaction
if (!beneficiary.send(highestBid)) beneficiary.transfer(highestBid);
throw;
} }
} }
@ -382,7 +373,7 @@ high or low invalid bids.
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract BlindAuction { contract BlindAuction {
struct Bid { struct Bid {
@ -410,8 +401,8 @@ high or low invalid bids.
/// functions. `onlyBefore` is applied to `bid` below: /// functions. `onlyBefore` is applied to `bid` below:
/// The new function body is the modifier's body where /// The new function body is the modifier's body where
/// `_` is replaced by the old function body. /// `_` is replaced by the old function body.
modifier onlyBefore(uint _time) { if (now >= _time) throw; _; } modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { if (now <= _time) throw; _; } modifier onlyAfter(uint _time) { require(now > _time); _; }
function BlindAuction( function BlindAuction(
uint _biddingTime, uint _biddingTime,
@ -455,13 +446,9 @@ high or low invalid bids.
onlyBefore(revealEnd) onlyBefore(revealEnd)
{ {
uint length = bids[msg.sender].length; uint length = bids[msg.sender].length;
if ( require(_values.length == length);
_values.length != length || require(_fake.length == length);
_fake.length != length || require(_secret.length == length);
_secret.length != length
) {
throw;
}
uint refund; uint refund;
for (uint i = 0; i < length; i++) { for (uint i = 0; i < length; i++) {
@ -482,8 +469,7 @@ high or low invalid bids.
// the same deposit. // the same deposit.
bid.blindedBid = 0; bid.blindedBid = 0;
} }
if (!msg.sender.send(refund)) msg.sender.transfer(refund);
throw;
} }
// This is an "internal" function which means that it // This is an "internal" function which means that it
@ -528,14 +514,12 @@ high or low invalid bids.
function auctionEnd() function auctionEnd()
onlyAfter(revealEnd) onlyAfter(revealEnd)
{ {
if (ended) require(!ended);
throw;
AuctionEnded(highestBidder, highestBid); AuctionEnded(highestBidder, highestBid);
ended = true; ended = true;
// We send all the money we have, because some // We send all the money we have, because some
// of the refunds might have failed. // of the refunds might have failed.
if (!beneficiary.send(this.balance)) beneficiary.transfer(this.balance);
throw;
} }
} }
@ -547,7 +531,7 @@ Safe Remote Purchase
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract Purchase { contract Purchase {
uint public value; uint public value;
@ -559,26 +543,26 @@ Safe Remote Purchase
function Purchase() payable { function Purchase() payable {
seller = msg.sender; seller = msg.sender;
value = msg.value / 2; value = msg.value / 2;
if (2 * value != msg.value) throw; require((2 * value) == msg.value);
} }
modifier require(bool _condition) { modifier condition(bool _condition) {
if (!_condition) throw; require(_condition);
_; _;
} }
modifier onlyBuyer() { modifier onlyBuyer() {
if (msg.sender != buyer) throw; require(msg.sender == buyer);
_; _;
} }
modifier onlySeller() { modifier onlySeller() {
if (msg.sender != seller) throw; require(msg.sender == seller);
_; _;
} }
modifier inState(State _state) { modifier inState(State _state) {
if (state != _state) throw; require(state == _state);
_; _;
} }
@ -595,8 +579,7 @@ Safe Remote Purchase
{ {
aborted(); aborted();
state = State.Inactive; state = State.Inactive;
if (!seller.send(this.balance)) seller.transfer(this.balance);
throw;
} }
/// Confirm the purchase as buyer. /// Confirm the purchase as buyer.
@ -605,7 +588,7 @@ Safe Remote Purchase
/// is called. /// is called.
function confirmPurchase() function confirmPurchase()
inState(State.Created) inState(State.Created)
require(msg.value == 2 * value) condition(msg.value == (2 * value))
payable payable
{ {
purchaseConfirmed(); purchaseConfirmed();
@ -624,10 +607,12 @@ Safe Remote Purchase
// otherwise, the contracts called using `send` below // otherwise, the contracts called using `send` below
// can call in again here. // can call in again here.
state = State.Inactive; state = State.Inactive;
// This actually allows both the buyer and the seller to
// block the refund. // NOTE: This actually allows both the buyer and the seller to
if (!buyer.send(value) || !seller.send(this.balance)) // block the refund - the withdraw pattern should be used.
throw;
buyer.transfer(value);
seller.transfer(this.balance));
} }
} }

View File

@ -62,13 +62,13 @@ Function modifiers can be used to amend the semantics of functions in a declarat
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract Purchase { contract Purchase {
address public seller; address public seller;
modifier onlySeller() { // Modifier modifier onlySeller() { // Modifier
if (msg.sender != seller) throw; require(msg.sender == seller);
_; _;
} }

View File

@ -164,7 +164,7 @@ Functions should be grouped according to their visibility and ordered:
- internal - internal
- private - private
Within a grouping, place the `constant` functions last. Within a grouping, place the ``constant`` functions last.
Yes:: Yes::

View File

@ -64,6 +64,12 @@ expression ``x << y`` is equivalent to ``x * 2**y`` and ``x >> y`` is
equivalent to ``x / 2**y``. This means that shifting negative numbers equivalent to ``x / 2**y``. This means that shifting negative numbers
sign extends. Shifting by a negative amount throws a runtime exception. sign extends. Shifting by a negative amount throws a runtime exception.
.. warning::
The results produced by shift right of negative values of signed integer types is different from those produced
by other programming languages. In Solidity, shift right maps to division so the shifted negative values
are going to be rounded towards zero (truncated). In other programming languages the shift right of negative values
works like division with rounding down (towards negative infinity).
.. index:: address, balance, send, call, callcode, delegatecall, transfer .. index:: address, balance, send, call, callcode, delegatecall, transfer
.. _address: .. _address:
@ -123,6 +129,8 @@ In a similar way, the function ``delegatecall`` can be used: The difference is t
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. All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``.
.. note:: .. note::
All contracts inherit the members of address, so it is possible to query the balance of the All contracts inherit the members of address, so it is possible to query the balance of the
current contract using ``this.balance``. current contract using ``this.balance``.
@ -232,7 +240,7 @@ a non-rational number).
Integer literals and rational number literals belong to number literal types. Integer literals and rational number literals belong to number literal types.
Moreover, all number literal expressions (i.e. the expressions that Moreover, all number literal expressions (i.e. the expressions that
contain only number literals and operators) belong to number literal contain only number literals and operators) belong to number literal
types. So the number literal expressions `1 + 2` and `2 + 1` both types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both
belong to the same number literal type for the rational number three. belong to the same number literal type for the rational number three.
.. note:: .. note::
@ -261,7 +269,7 @@ a non-rational number).
String Literals String Literals
--------------- ---------------
String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; `"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``.
String literals support escape characters, such as ``\n``, ``\xNN`` and ``\uNNNN``. ``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence. String literals support escape characters, such as ``\n``, ``\xNN`` and ``\uNNNN``. ``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence.
@ -412,7 +420,7 @@ Example that shows how to use internal function types::
Another example that uses external function types:: Another example that uses external function types::
pragma solidity ^0.4.5; pragma solidity ^0.4.11;
contract Oracle { contract Oracle {
struct Request { struct Request {
@ -437,7 +445,7 @@ Another example that uses external function types::
oracle.query("USD", this.oracleResponse); oracle.query("USD", this.oracleResponse);
} }
function oracleResponse(bytes response) { function oracleResponse(bytes response) {
if (msg.sender != address(oracle)) throw; require(msg.sender == address(oracle));
// Use the data // Use the data
} }
} }
@ -714,7 +722,7 @@ shown in the following example:
:: ::
pragma solidity ^0.4.0; pragma solidity ^0.4.11;
contract CrowdFunding { contract CrowdFunding {
// Defines a new type with two fields. // Defines a new type with two fields.
@ -755,8 +763,7 @@ shown in the following example:
return false; return false;
uint amount = c.amount; uint amount = c.amount;
c.amount = 0; c.amount = 0;
if (!c.beneficiary.send(amount)) c.beneficiary.transfer(amount);
throw;
return true; return true;
} }
} }

View File

@ -22,8 +22,8 @@ unit and units are considered naively in the following way:
* ``1 minutes == 60 seconds`` * ``1 minutes == 60 seconds``
* ``1 hours == 60 minutes`` * ``1 hours == 60 minutes``
* ``1 days == 24 hours`` * ``1 days == 24 hours``
* ``1 weeks = 7 days`` * ``1 weeks == 7 days``
* ``1 years = 365 days`` * ``1 years == 365 days``
Take care if you perform calendar calculations using these units, because Take care if you perform calendar calculations using these units, because
not every year equals 365 days and not even every day has 24 hours not every year equals 365 days and not even every day has 24 hours
@ -93,13 +93,14 @@ Mathematical and Cryptographic Functions
``keccak256(...) returns (bytes32)``: ``keccak256(...) returns (bytes32)``:
compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
``sha3(...) returns (bytes32)``: ``sha3(...) returns (bytes32)``:
alias to `keccak256()` alias to ``keccak256()``
``sha256(...) returns (bytes32)``: ``sha256(...) returns (bytes32)``:
compute the SHA-256 hash of the (tightly packed) arguments compute the SHA-256 hash of the (tightly packed) arguments
``ripemd160(...) returns (bytes20)``: ``ripemd160(...) returns (bytes20)``:
compute RIPEMD-160 hash of the (tightly packed) arguments compute RIPEMD-160 hash of the (tightly packed) arguments
``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: ``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 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()``: ``revert()``:
abort execution and revert state changes abort execution and revert state changes
@ -128,17 +129,23 @@ Address Related
``<address>.balance`` (``uint256``): ``<address>.balance`` (``uint256``):
balance of the :ref:`address` in Wei balance of the :ref:`address` in Wei
``<address>.send(uint256 amount) returns (bool)``:
send given amount of Wei to :ref:`address`, returns ``false`` on failure
``<address>.transfer(uint256 amount)``: ``<address>.transfer(uint256 amount)``:
send given amount of Wei to :ref:`address`, throws on failure send given amount of Wei to :ref:`address`, throws on failure
``<address>.send(uint256 amount) returns (bool)``:
send given amount of Wei to :ref:`address`, returns ``false`` on failure
``<address>.call(...) returns (bool)``:
issue low-level ``CALL``, returns ``false`` on failure
``<address>.callcode(...) returns (bool)``:
issue low-level ``CALLCODE``, returns ``false`` on failure
``<address>.delegatecall(...) returns (bool)``:
issue low-level ``DELEGATECALL``, returns ``false`` on failure
For more information, see the section on :ref:`address`. For more information, see the section on :ref:`address`.
.. warning:: .. warning::
There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024
(this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order
to make safe Ether transfers, always check the return value of ``send`` or even better: to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better:
Use a pattern where the recipient withdraws the money. Use a pattern where the recipient withdraws the money.
.. index:: this, selfdestruct .. index:: this, selfdestruct

View File

@ -29,21 +29,21 @@ files reside, so things like ``import "/etc/passwd";`` only work if you add ``=/
If there are multiple matches due to remappings, the one with the longest common prefix is selected. If there are multiple matches due to remappings, the one with the longest common prefix is selected.
For security reasons the compiler has restrictions what directories it can access. Paths (and their subdirectories) of source files specified on the commandline and paths defined by remappings are allowed for import statements, but everything else is rejected. Additional paths (and their subdirectories) can be allowed via the ``--allow-paths /sample/path,/another/sample/path`` switch.
If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points: If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points:
Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``. Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``.
If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case. If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case.
If ``solc`` is called with the option ``--standard-json``, it will expect a JSON input (as explained below) on the standard input, and return a JSON output on the standard output.
.. _compiler-api: .. _compiler-api:
Compiler Input and Output JSON Description Compiler Input and Output JSON Description
****************************************** ******************************************
.. warning::
This JSON interface is not yet supported by the Solidity compiler, but will be released in a future version.
These JSON formats are used by the compiler API as well as are available through ``solc``. These are subject to change, These JSON formats are used by the compiler API as well as are available through ``solc``. These are subject to change,
some fields are optional (as noted), but it is aimed at to only make backwards compatible changes. some fields are optional (as noted), but it is aimed at to only make backwards compatible changes.
@ -119,22 +119,27 @@ Input Description
// //
// The available output types are as follows: // The available output types are as follows:
// abi - ABI // abi - ABI
// ast - AST of all source files // ast - AST of all source files (not supported atm)
// why3 - Why3 translated output // legacyAST - legacy AST of all source files
// devdoc - Developer documentation (natspec) // devdoc - Developer documentation (natspec)
// userdoc - User documentation (natspec) // userdoc - User documentation (natspec)
// metadata - Metadata // metadata - Metadata
// evm.ir - New assembly format before desugaring // ir - New assembly format before desugaring
// evm.assembly - New assembly format after desugaring // evm.assembly - New assembly format after desugaring
// evm.legacyAssemblyJSON - Old-style assembly format in JSON // evm.legacyAssembly - Old-style assembly format in JSON
// evm.opcodes - Opcodes list // evm.bytecode.object - Bytecode object
// evm.bytecode.opcodes - Opcodes list
// evm.bytecode.sourceMap - Source mapping (useful for debugging)
// evm.bytecode.linkReferences - Link references (if unlinked object)
// evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode)
// evm.methodIdentifiers - The list of function hashes // evm.methodIdentifiers - The list of function hashes
// evm.gasEstimates - Function gas estimates // evm.gasEstimates - Function gas estimates
// evm.bytecode - Bytecode
// evm.deployedBytecode - Deployed bytecode
// evm.sourceMap - Source mapping (useful for debugging)
// ewasm.wast - eWASM S-expressions format (not supported atm) // ewasm.wast - eWASM S-expressions format (not supported atm)
// ewasm.wasm - eWASM binary format (not supported atm) // ewasm.wasm - eWASM binary format (not supported atm)
//
// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
// target part of that output.
//
outputSelection: { outputSelection: {
// Enable the metadata and bytecode outputs of every single contract. // Enable the metadata and bytecode outputs of every single contract.
"*": { "*": {
@ -148,9 +153,9 @@ Input Description
"*": { "*": {
"*": [ "evm.sourceMap" ] "*": [ "evm.sourceMap" ]
}, },
// Enable the AST and Why3 output of every single file. // Enable the legacy AST output of every single file.
"*": { "*": {
"": [ "ast", "why3" ] "": [ "legacyAST" ]
} }
} }
} }
@ -174,12 +179,14 @@ Output Description
], ],
// Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc
type: "TypeError", type: "TypeError",
// Mandatory: Component where the error originated, such as "general", "why3", "ewasm", etc. // Mandatory: Component where the error originated, such as "general", "ewasm", etc.
component: "general", component: "general",
// Mandatory ("error" or "warning") // Mandatory ("error" or "warning")
severity: "error", severity: "error",
// Mandatory // Mandatory
message: "Invalid keyword" message: "Invalid keyword"
// Optional: the message formatted with source location
formattedMessage: "sourceFile.sol:100: Invalid keyword"
} }
], ],
// This contains the file-level outputs. In can be limited/filtered by the outputSelection settings. // This contains the file-level outputs. In can be limited/filtered by the outputSelection settings.
@ -188,7 +195,9 @@ Output Description
// Identifier (used in source maps) // Identifier (used in source maps)
id: 1, id: 1,
// The AST object // The AST object
ast: {} ast: {},
// The legacy AST object
legacyAST: {}
} }
}, },
// This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings. // This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings.
@ -199,17 +208,26 @@ Output Description
// The Ethereum Contract ABI. If empty, it is represented as an empty array. // The Ethereum Contract ABI. If empty, it is represented as an empty array.
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
abi: [], abi: [],
evm: { // See the Metadata Output documentation (serialised JSON string)
metadata: "{...}",
// User documentation (natspec)
userdoc: {},
// Developer documentation (natspec)
devdoc: {},
// Intermediate representation (string) // Intermediate representation (string)
ir: "", ir: "",
// EVM-related outputs
evm: {
// Assembly (string) // Assembly (string)
assembly: "", assembly: "",
// Old-style assembly (string) // Old-style assembly (object)
legacyAssemblyJSON: [], legacyAssembly: {},
// Bytecode and related details. // Bytecode and related details.
bytecode: { bytecode: {
// The bytecode as a hex string. // The bytecode as a hex string.
object: "00fe", object: "00fe",
// Opcodes list (string)
opcodes: "",
// The source mapping as a string. See the source mapping definition. // The source mapping as a string. See the source mapping definition.
sourceMap: "", sourceMap: "",
// If given, this is an unlinked object. // If given, this is an unlinked object.
@ -222,45 +240,36 @@ Output Description
] ]
} }
} }
} },
// The same layout as above. // The same layout as above.
deployedBytecode: { }, deployedBytecode: { },
// Opcodes list (string)
opcodes: "",
// The list of function hashes // The list of function hashes
methodIdentifiers: { methodIdentifiers: {
"5c19a95c": "delegate(address)", "delegate(address)": "5c19a95c"
}, },
// Function gas estimates // Function gas estimates
gasEstimates: { gasEstimates: {
creation: { creation: {
dataCost: 420000, codeDepositCost: "420000",
// -1 means infinite (aka. unknown) executionCost: "infinite",
executionCost: -1 totalCost: "infinite"
}, },
external: { external: {
"delegate(address)": 25000 "delegate(address)": "25000"
}, },
internal: { internal: {
"heavyLifting()": -1 "heavyLifting()": "infinite"
} }
} }
}, },
// See the Metadata Output documentation // eWASM related outputs
metadata: {},
ewasm: { ewasm: {
// S-expressions format // S-expressions format
wast: "", wast: "",
// Binary format (hex string) // Binary format (hex string)
wasm: "" wasm: ""
},
// User documentation (natspec)
userdoc: {},
// Developer documentation (natspec)
devdoc: {}
} }
} }
}, }
// Why3 output (string) }
why3: ""
} }

View File

@ -205,7 +205,8 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con
{ {
_out << _prefix << "stop" << endl; _out << _prefix << "stop" << endl;
for (auto const& i: m_data) for (auto const& i: m_data)
assertThrow(u256(i.first) < m_subs.size(), AssemblyException, "Data not yet implemented."); if (u256(i.first) >= m_subs.size())
_out << _prefix << "data_" << toHex(u256(i.first)) << " " << toHex(i.second) << endl;
for (size_t i = 0; i < m_subs.size(); ++i) for (size_t i = 0; i < m_subs.size(); ++i)
{ {

View File

@ -159,18 +159,25 @@ string AssemblyItem::toAssemblyText() const
text = toHex(toCompactBigEndian(data(), 1), 1, HexPrefix::Add); text = toHex(toCompactBigEndian(data(), 1), 1, HexPrefix::Add);
break; break;
case PushString: case PushString:
assertThrow(false, AssemblyException, "Push string assembly output not implemented."); text = string("data_") + toHex(data());
break; break;
case PushTag: case PushTag:
assertThrow(data() < 0x10000, AssemblyException, "Sub-assembly tags not yet implemented."); {
text = string("tag_") + to_string(size_t(data())); size_t sub{0};
size_t tag{0};
tie(sub, tag) = splitForeignPushTag();
if (sub == size_t(-1))
text = string("tag_") + to_string(tag);
else
text = string("tag_") + to_string(sub) + "_" + to_string(tag);
break; break;
}
case Tag: case Tag:
assertThrow(data() < 0x10000, AssemblyException, "Sub-assembly tags not yet implemented."); assertThrow(data() < 0x10000, AssemblyException, "Declaration of sub-assembly tag.");
text = string("tag_") + to_string(size_t(data())) + ":"; text = string("tag_") + to_string(size_t(data())) + ":";
break; break;
case PushData: case PushData:
assertThrow(false, AssemblyException, "Push data not implemented."); text = string("data_") + toHex(data());
break; break;
case PushSub: case PushSub:
text = string("dataOffset(sub_") + to_string(size_t(data())) + ")"; text = string("dataOffset(sub_") + to_string(size_t(data())) + ")";

View File

@ -203,8 +203,13 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
u256 powerOfTwo = u256(1) << bits; u256 powerOfTwo = u256(1) << bits;
u256 upperPart = _value >> bits; u256 upperPart = _value >> bits;
bigint lowerPart = _value & (powerOfTwo - 1); bigint lowerPart = _value & (powerOfTwo - 1);
if (abs(powerOfTwo - lowerPart) < lowerPart) if ((powerOfTwo - lowerPart) < lowerPart)
{
lowerPart = lowerPart - powerOfTwo; // make it negative lowerPart = lowerPart - powerOfTwo; // make it negative
upperPart++;
}
if (upperPart == 0)
continue;
if (abs(lowerPart) >= (powerOfTwo >> 8)) if (abs(lowerPart) >= (powerOfTwo >> 8))
continue; continue;
@ -212,7 +217,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
if (lowerPart != 0) if (lowerPart != 0)
newRoutine += findRepresentation(u256(abs(lowerPart))); newRoutine += findRepresentation(u256(abs(lowerPart)));
newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP}; newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
if (upperPart != 1 && upperPart != 0) if (upperPart != 1)
newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
if (lowerPart > 0) if (lowerPart > 0)
newRoutine += AssemblyItems{Instruction::ADD}; newRoutine += AssemblyItems{Instruction::ADD};
@ -232,6 +237,54 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
} }
} }
bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine)
{
// This is a tiny EVM that can only evaluate some instructions.
vector<u256> stack;
for (AssemblyItem const& item: _routine)
{
switch (item.type())
{
case Operation:
{
if (stack.size() < size_t(item.arguments()))
return false;
u256* sp = &stack.back();
switch (item.instruction())
{
case Instruction::MUL:
sp[-1] = sp[0] * sp[-1];
break;
case Instruction::EXP:
if (sp[-1] > 0xff)
return false;
sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1]));
break;
case Instruction::ADD:
sp[-1] = sp[0] + sp[-1];
break;
case Instruction::SUB:
sp[-1] = sp[0] - sp[-1];
break;
case Instruction::NOT:
sp[0] = ~sp[0];
break;
default:
return false;
}
stack.resize(stack.size() + item.deposit());
break;
}
case Push:
stack.push_back(item.data());
break;
default:
return false;
}
}
return stack.size() == 1 && stack.front() == _value;
}
bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine)
{ {
size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP); size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP);

View File

@ -21,10 +21,14 @@
#pragma once #pragma once
#include <vector> #include <libevmasm/Exceptions.h>
#include <libdevcore/Assertions.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libdevcore/CommonIO.h> #include <libdevcore/CommonIO.h>
#include <vector>
namespace dev namespace dev
{ {
namespace eth namespace eth
@ -130,6 +134,11 @@ public:
ConstantOptimisationMethod(_params, _value) ConstantOptimisationMethod(_params, _value)
{ {
m_routine = findRepresentation(m_value); m_routine = findRepresentation(m_value);
assertThrow(
checkRepresentation(m_value, m_routine),
OptimizerException,
"Invalid constant expression created."
);
} }
virtual bigint gasNeeded() override { return gasNeeded(m_routine); } virtual bigint gasNeeded() override { return gasNeeded(m_routine); }
@ -141,6 +150,8 @@ public:
protected: protected:
/// Tries to recursively find a way to compute @a _value. /// Tries to recursively find a way to compute @a _value.
AssemblyItems findRepresentation(u256 const& _value); AssemblyItems findRepresentation(u256 const& _value);
/// Recomputes the value from the calculated representation and checks for correctness.
bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine);
bigint gasNeeded(AssemblyItems const& _routine); bigint gasNeeded(AssemblyItems const& _routine);
/// Counter for the complexity of optimization, will stop when it reaches zero. /// Counter for the complexity of optimization, will stop when it reaches zero.

View File

@ -34,7 +34,7 @@ struct EVMSchedule
unsigned expByteGas = 10; unsigned expByteGas = 10;
unsigned sha3Gas = 30; unsigned sha3Gas = 30;
unsigned sha3WordGas = 6; unsigned sha3WordGas = 6;
unsigned sloadGas = 50; unsigned sloadGas = 200;
unsigned sstoreSetGas = 20000; unsigned sstoreSetGas = 20000;
unsigned sstoreResetGas = 5000; unsigned sstoreResetGas = 5000;
unsigned sstoreRefundGas = 15000; unsigned sstoreRefundGas = 15000;

View File

@ -149,6 +149,10 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
} }
break; break;
} }
case Instruction::SELFDESTRUCT:
gas = GasCosts::selfdestructGas;
gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists.
break;
case Instruction::CREATE: case Instruction::CREATE:
if (_includeExternalCosts) if (_includeExternalCosts)
// We assume that we do not know the target contract and thus, the consumption is infinite. // We assume that we do not know the target contract and thus, the consumption is infinite.
@ -232,6 +236,8 @@ unsigned GasMeter::runGas(Instruction _instruction)
case Tier::High: return GasCosts::tier5Gas; case Tier::High: return GasCosts::tier5Gas;
case Tier::Ext: return GasCosts::tier6Gas; case Tier::Ext: return GasCosts::tier6Gas;
case Tier::Special: return GasCosts::tier7Gas; case Tier::Special: return GasCosts::tier7Gas;
case Tier::ExtCode: return GasCosts::extCodeGas;
case Tier::Balance: return GasCosts::balanceGas;
default: break; default: break;
} }
assertThrow(false, OptimizerException, "Invalid gas tier."); assertThrow(false, OptimizerException, "Invalid gas tier.");

View File

@ -44,11 +44,13 @@ namespace GasCosts
static unsigned const tier5Gas = 10; static unsigned const tier5Gas = 10;
static unsigned const tier6Gas = 20; static unsigned const tier6Gas = 20;
static unsigned const tier7Gas = 0; static unsigned const tier7Gas = 0;
static unsigned const extCodeGas = 700;
static unsigned const balanceGas = 400;
static unsigned const expGas = 10; static unsigned const expGas = 10;
static unsigned const expByteGas = 10; static unsigned const expByteGas = 50;
static unsigned const sha3Gas = 30; static unsigned const sha3Gas = 30;
static unsigned const sha3WordGas = 6; static unsigned const sha3WordGas = 6;
static unsigned const sloadGas = 50; static unsigned const sloadGas = 200;
static unsigned const sstoreSetGas = 20000; static unsigned const sstoreSetGas = 20000;
static unsigned const sstoreResetGas = 5000; static unsigned const sstoreResetGas = 5000;
static unsigned const sstoreRefundGas = 15000; static unsigned const sstoreRefundGas = 15000;
@ -57,10 +59,11 @@ namespace GasCosts
static unsigned const logDataGas = 8; static unsigned const logDataGas = 8;
static unsigned const logTopicGas = 375; static unsigned const logTopicGas = 375;
static unsigned const createGas = 32000; static unsigned const createGas = 32000;
static unsigned const callGas = 40; static unsigned const callGas = 700;
static unsigned const callStipend = 2300; static unsigned const callStipend = 2300;
static unsigned const callValueTransferGas = 9000; static unsigned const callValueTransferGas = 9000;
static unsigned const callNewAccountGas = 25000; static unsigned const callNewAccountGas = 25000;
static unsigned const selfdestructGas = 5000;
static unsigned const selfdestructRefundGas = 24000; static unsigned const selfdestructRefundGas = 24000;
static unsigned const memoryGas = 3; static unsigned const memoryGas = 3;
static unsigned const quadCoeffDiv = 512; static unsigned const quadCoeffDiv = 512;

View File

@ -191,7 +191,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } }, { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } },
{ Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::Special } }, { Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::Special } },
{ Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } }, { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } },
{ Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Ext } }, { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } },
{ Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } }, { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } },
{ Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::Base } }, { Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::Base } },
{ Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::Base } }, { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::Base } },
@ -201,8 +201,8 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::Base } }, { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::Base } },
{ Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } }, { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } },
{ Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } }, { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } },
{ Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::Ext } }, { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::ExtCode } },
{ Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::Ext } }, { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtCode } },
{ Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } }, { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } },
{ Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } }, { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } },
{ Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } }, { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } },
@ -297,7 +297,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } },
{ Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } }, { Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } },
{ Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } },
{ Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } } { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Special } }
}; };
void dev::solidity::eachInstruction( void dev::solidity::eachInstruction(

View File

@ -237,6 +237,8 @@ enum class Tier : unsigned
Mid, // 8, Mid Mid, // 8, Mid
High, // 10, Slow High, // 10, Slow
Ext, // 20, Ext Ext, // 20, Ext
ExtCode, // 700, Extcode
Balance, // 400, Balance
Special, // multiparam or otherwise special Special, // multiparam or otherwise special
Invalid // Invalid. Invalid // Invalid.
}; };

View File

@ -39,39 +39,39 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)),
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)), make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
make_shared<MagicVariableDeclaration>("suicide", make_shared<MagicVariableDeclaration>("suicide",
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)), make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<MagicVariableDeclaration>("selfdestruct",
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)), make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("addmod", make_shared<MagicVariableDeclaration>("addmod",
make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::AddMod)), make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod)),
make_shared<MagicVariableDeclaration>("mulmod", make_shared<MagicVariableDeclaration>("mulmod",
make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::MulMod)), make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod)),
make_shared<MagicVariableDeclaration>("sha3", make_shared<MagicVariableDeclaration>("sha3",
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)),
make_shared<MagicVariableDeclaration>("keccak256", make_shared<MagicVariableDeclaration>("keccak256",
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)),
make_shared<MagicVariableDeclaration>("log0", make_shared<MagicVariableDeclaration>("log0",
make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)), make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)),
make_shared<MagicVariableDeclaration>("log1", make_shared<MagicVariableDeclaration>("log1",
make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)), make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)),
make_shared<MagicVariableDeclaration>("log2", make_shared<MagicVariableDeclaration>("log2",
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)),
make_shared<MagicVariableDeclaration>("log3", make_shared<MagicVariableDeclaration>("log3",
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)),
make_shared<MagicVariableDeclaration>("log4", make_shared<MagicVariableDeclaration>("log4",
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)), make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)),
make_shared<MagicVariableDeclaration>("sha256", make_shared<MagicVariableDeclaration>("sha256",
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)), make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true)),
make_shared<MagicVariableDeclaration>("ecrecover", make_shared<MagicVariableDeclaration>("ecrecover",
make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover)),
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<MagicVariableDeclaration>("ripemd160",
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true)),
make_shared<MagicVariableDeclaration>("assert", make_shared<MagicVariableDeclaration>("assert",
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert)), make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert)),
make_shared<MagicVariableDeclaration>("require", make_shared<MagicVariableDeclaration>("require",
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Require)), make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require)),
make_shared<MagicVariableDeclaration>("revert", make_shared<MagicVariableDeclaration>("revert",
make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Revert))}) make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert))})
{ {
} }

View File

@ -58,6 +58,9 @@ void PostTypeChecker::endVisit(ContractDefinition const&)
for (auto declaration: m_constVariables) for (auto declaration: m_constVariables)
if (auto identifier = findCycle(declaration)) if (auto identifier = findCycle(declaration))
typeError(declaration->location(), "The value of the constant " + declaration->name() + " has a cyclic dependency via " + identifier->name() + "."); typeError(declaration->location(), "The value of the constant " + declaration->name() + " has a cyclic dependency via " + identifier->name() + ".");
m_constVariables.clear();
m_constVariableDependencies.clear();
} }
bool PostTypeChecker::visit(VariableDeclaration const& _variable) bool PostTypeChecker::visit(VariableDeclaration const& _variable)

View File

@ -25,9 +25,12 @@
#include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <libsolidity/analysis/ConstantEvaluator.h> #include <libsolidity/analysis/ConstantEvaluator.h>
#include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmData.h>
#include <boost/algorithm/string.hpp>
using namespace std; using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
@ -158,21 +161,40 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
{ {
// We need to perform a full code generation pass here as inline assembly does not distinguish
// reference resolution and code generation.
// Errors created in this stage are completely ignored because we do not yet know // 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 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; ErrorList errorsIgnored;
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored); assembly::ExternalIdentifierAccess::Resolver resolver =
codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) { [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name); auto declarations = m_resolver.nameFromCurrentScope(_identifier.name);
bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset");
if (isSlot || isOffset)
{
// special mode to access storage variables
if (!declarations.empty())
// the special identifier exists itself, we should not allow that.
return size_t(-1);
string realName = _identifier.name.substr(0, _identifier.name.size() - (
isSlot ?
string("_slot").size() :
string("_offset").size()
));
declarations = m_resolver.nameFromCurrentScope(realName);
}
if (declarations.size() != 1) if (declarations.size() != 1)
return false; return size_t(-1);
_inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front(); _inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
// At this stage we neither know the code to generate nor the stack size of the identifier, _inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
// so we do not modify assembly. _inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
return true; return size_t(1);
}); };
// Will be re-generated later with correct information
assembly::AsmAnalysisInfo analysisInfo;
assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations());
return false; return false;
} }

View File

@ -48,13 +48,65 @@ void StaticAnalyzer::endVisit(ContractDefinition const&)
bool StaticAnalyzer::visit(FunctionDefinition const& _function) bool StaticAnalyzer::visit(FunctionDefinition const& _function)
{ {
if (_function.isImplemented())
m_currentFunction = &_function;
else
solAssert(!m_currentFunction, "");
solAssert(m_localVarUseCount.empty(), "");
m_nonPayablePublic = _function.isPublic() && !_function.isPayable(); m_nonPayablePublic = _function.isPublic() && !_function.isPayable();
return true; return true;
} }
void StaticAnalyzer::endVisit(FunctionDefinition const&) void StaticAnalyzer::endVisit(FunctionDefinition const&)
{ {
m_currentFunction = nullptr;
m_nonPayablePublic = false; m_nonPayablePublic = false;
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
warning(var.first->location(), "Unused local variable");
m_localVarUseCount.clear();
}
bool StaticAnalyzer::visit(Identifier const& _identifier)
{
if (m_currentFunction)
if (auto var = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
{
solAssert(!var->name().empty(), "");
if (var->isLocalVariable())
m_localVarUseCount[var] += 1;
}
return true;
}
bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
{
if (m_currentFunction)
{
solAssert(_variable.isLocalVariable(), "");
if (_variable.name() != "")
// This is not a no-op, the entry might pre-exist.
m_localVarUseCount[&_variable] += 0;
}
return true;
}
bool StaticAnalyzer::visit(Return const& _return)
{
// If the return has an expression, it counts as
// a "use" of the return parameters.
if (m_currentFunction && _return.expression())
for (auto const& var: m_currentFunction->returnParameters())
if (!var->name().empty())
m_localVarUseCount[var.get()] += 1;
return true;
}
bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
{
if (_statement.expression().annotation().isPure)
warning(_statement.location(), "Statement has no effect.");
return true;
} }
bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)

View File

@ -60,6 +60,10 @@ private:
virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(FunctionDefinition const& _function) override;
virtual void endVisit(FunctionDefinition const& _function) override; virtual void endVisit(FunctionDefinition const& _function) override;
virtual bool visit(ExpressionStatement const& _statement) override;
virtual bool visit(VariableDeclaration const& _variable) override;
virtual bool visit(Identifier const& _identifier) override;
virtual bool visit(Return const& _return) override;
virtual bool visit(MemberAccess const& _memberAccess) override; virtual bool visit(MemberAccess const& _memberAccess) override;
ErrorList& m_errors; ErrorList& m_errors;
@ -69,6 +73,11 @@ private:
/// Flag that indicates whether a public function does not contain the "payable" modifier. /// Flag that indicates whether a public function does not contain the "payable" modifier.
bool m_nonPayablePublic = false; bool m_nonPayablePublic = false;
/// Number of uses of each (named) local variable in a function, counter is initialized with zero.
std::map<VariableDeclaration const*, int> m_localVarUseCount;
FunctionDefinition const* m_currentFunction = nullptr;
}; };
} }

View File

@ -32,6 +32,16 @@ bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
return Error::containsOnlyWarnings(m_errors); 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) void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string const& _description)
{ {
auto err = make_shared<Error>(Error::Type::SyntaxError); auto err = make_shared<Error>(Error::Type::SyntaxError);
@ -148,6 +158,13 @@ bool SyntaxChecker::visit(Break const& _breakStatement)
return true; return true;
} }
bool SyntaxChecker::visit(UnaryOperation const& _operation)
{
if (_operation.getOperator() == Token::Add)
warning(_operation.location(), "Use of unary + is deprecated.");
return true;
}
bool SyntaxChecker::visit(PlaceholderStatement const&) bool SyntaxChecker::visit(PlaceholderStatement const&)
{ {
m_placeholderFound = true; m_placeholderFound = true;

View File

@ -32,6 +32,7 @@ namespace solidity
* The module that performs syntax analysis on the AST: * The module that performs syntax analysis on the AST:
* - whether continue/break is in a for/while loop. * - whether continue/break is in a for/while loop.
* - whether a modifier contains at least one '_' * - whether a modifier contains at least one '_'
* - issues deprecation warnings for unary '+'
*/ */
class SyntaxChecker: private ASTConstVisitor class SyntaxChecker: private ASTConstVisitor
{ {
@ -43,6 +44,7 @@ public:
private: private:
/// Adds a new error to the list of errors. /// 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); void syntaxError(SourceLocation const& _location, std::string const& _description);
virtual bool visit(SourceUnit const& _sourceUnit) override; virtual bool visit(SourceUnit const& _sourceUnit) override;
@ -60,6 +62,8 @@ private:
virtual bool visit(Continue const& _continueStatement) override; virtual bool visit(Continue const& _continueStatement) override;
virtual bool visit(Break const& _breakStatement) override; virtual bool visit(Break const& _breakStatement) override;
virtual bool visit(UnaryOperation const& _operation) override;
virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; virtual bool visit(PlaceholderStatement const& _placeholderStatement) override;
ErrorList& m_errors; ErrorList& m_errors;

View File

@ -24,8 +24,9 @@
#include <memory> #include <memory>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libevmasm/Assembly.h> // needed for inline assembly #include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
using namespace std; using namespace std;
using namespace dev; using namespace dev;
@ -64,8 +65,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
{ {
m_scope = &_contract; m_scope = &_contract;
// We force our own visiting order here. // We force our own visiting order here. The structs have to be excluded below.
//@TODO structs will be visited again below, but it is probably fine. set<ASTNode const*> visited;
for (auto const& s: _contract.definedStructs())
visited.insert(s);
ASTNode::listAccept(_contract.definedStructs(), *this); ASTNode::listAccept(_contract.definedStructs(), *this);
ASTNode::listAccept(_contract.baseContracts(), *this); ASTNode::listAccept(_contract.baseContracts(), *this);
@ -113,7 +116,9 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
_contract.annotation().isFullyImplemented = false; _contract.annotation().isFullyImplemented = false;
} }
ASTNode::listAccept(_contract.subNodes(), *this); for (auto const& n: _contract.subNodes())
if (!visited.count(n.get()))
n->accept(*this);
checkContractExternalTypeClashes(_contract); checkContractExternalTypeClashes(_contract);
// check for hash collisions in function signatures // check for hash collisions in function signatures
@ -183,13 +188,20 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>; using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>;
map<string, vector<FunTypeAndFlag>> functions; map<string, vector<FunTypeAndFlag>> functions;
bool allBaseConstructorsImplemented = true;
// Search from base to derived // Search from base to derived
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
for (FunctionDefinition const* function: contract->definedFunctions()) for (FunctionDefinition const* function: contract->definedFunctions())
{ {
// Take constructors out of overload hierarchy // Take constructors out of overload hierarchy
if (function->isConstructor()) if (function->isConstructor())
{
if (!function->isImplemented())
// Base contract's constructor is not fully implemented, no way to get
// out of this.
allBaseConstructorsImplemented = false;
continue; continue;
}
auto& overloads = functions[function->name()]; auto& overloads = functions[function->name()];
FunctionTypePointer funType = make_shared<FunctionType>(*function); FunctionTypePointer funType = make_shared<FunctionType>(*function);
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
@ -207,6 +219,9 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
it->second = true; it->second = true;
} }
if (!allBaseConstructorsImplemented)
_contract.annotation().isFullyImplemented = false;
// Set to not fully implemented if at least one flag is false. // Set to not fully implemented if at least one flag is false.
for (auto const& it: functions) for (auto const& it: functions)
for (auto const& funAndFlag: it.second) for (auto const& funAndFlag: it.second)
@ -354,6 +369,9 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
solAssert(base, "Base contract not available."); solAssert(base, "Base contract not available.");
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
typeError(_inheritance.location(), "Interfaces cannot inherit.");
if (base->isLibrary()) if (base->isLibrary())
typeError(_inheritance.location(), "Libraries cannot be inherited from."); typeError(_inheritance.location(), "Libraries cannot be inherited from.");
@ -396,6 +414,9 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
bool TypeChecker::visit(StructDefinition const& _struct) bool TypeChecker::visit(StructDefinition const& _struct)
{ {
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
typeError(_struct.location(), "Structs cannot be defined in interfaces.");
for (ASTPointer<VariableDeclaration> const& member: _struct.members()) for (ASTPointer<VariableDeclaration> const& member: _struct.members())
if (!type(*member)->canBeStored()) if (!type(*member)->canBeStored())
typeError(member->location(), "Type cannot be used in struct."); typeError(member->location(), "Type cannot be used in struct.");
@ -451,6 +472,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts : dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts :
vector<ContractDefinition const*>() vector<ContractDefinition const*>()
); );
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
{
if (_function.isImplemented())
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.");
if (_function.isConstructor())
typeError(_function.location(), "Constructor cannot be defined in interfaces.");
}
if (_function.isImplemented()) if (_function.isImplemented())
_function.body().accept(*this); _function.body().accept(*this);
return false; return false;
@ -458,6 +488,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
bool TypeChecker::visit(VariableDeclaration const& _variable) bool TypeChecker::visit(VariableDeclaration const& _variable)
{ {
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
typeError(_variable.location(), "Variables cannot be declared in interfaces.");
// Variables can be declared without type (with "var"), in which case the first assignment // Variables can be declared without type (with "var"), in which case the first assignment
// sets the type. // sets the type.
// Note that assignments before the first declaration are legal because of the special scoping // Note that assignments before the first declaration are legal because of the special scoping
@ -504,6 +537,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
return false; return false;
} }
bool TypeChecker::visit(EnumDefinition const& _enum)
{
if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
typeError(_enum.location(), "Enumerable cannot be declared in interfaces.");
return false;
}
void TypeChecker::visitManually( void TypeChecker::visitManually(
ModifierInvocation const& _modifier, ModifierInvocation const& _modifier,
vector<ContractDefinition const*> const& _bases vector<ContractDefinition const*> const& _bases
@ -582,72 +622,98 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
void TypeChecker::endVisit(FunctionTypeName const& _funType) void TypeChecker::endVisit(FunctionTypeName const& _funType)
{ {
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type); FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
if (fun.location() == FunctionType::Location::External) if (fun.kind() == FunctionType::Kind::External)
if (!fun.canBeUsedExternally(false)) if (!fun.canBeUsedExternally(false))
typeError(_funType.location(), "External function type uses internal types."); typeError(_funType.location(), "External function type uses internal types.");
} }
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{ {
// Inline assembly does not have its own type-checking phase, so we just run the
// code-generator and see whether it produces any errors.
// External references have already been resolved in a prior stage and stored in the annotation. // External references have already been resolved in a prior stage and stored in the annotation.
auto identifierAccess = [&]( // We run the resolve step again regardless.
assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&](
assembly::Identifier const& _identifier, assembly::Identifier const& _identifier,
eth::Assembly& _assembly, assembly::IdentifierContext _context
assembly::CodeGenerator::IdentifierContext _context
) )
{ {
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end()) if (ref == _inlineAssembly.annotation().externalReferences.end())
return false; return size_t(-1);
Declaration const* declaration = ref->second; Declaration const* declaration = ref->second.declaration;
solAssert(!!declaration, ""); solAssert(!!declaration, "");
if (_context == assembly::CodeGenerator::IdentifierContext::RValue) if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (ref->second.isSlot || ref->second.isOffset)
{
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
{
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)
{
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.");
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.");
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.");
return size_t(-1);
}
else if (var->type()->sizeOnStack() != 1)
{
typeError(_identifier.location, "Only types that use one stack slot are supported.");
return size_t(-1);
}
}
else if (_context == assembly::IdentifierContext::LValue)
{
typeError(_identifier.location, "Only local variables can be assigned to in inline assembly.");
return size_t(-1);
}
if (_context == assembly::IdentifierContext::RValue)
{ {
solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
unsigned pushes = 0;
if (dynamic_cast<FunctionDefinition const*>(declaration)) if (dynamic_cast<FunctionDefinition const*>(declaration))
pushes = 1;
else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{ {
if (var->isConstant()) }
fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); else if (dynamic_cast<VariableDeclaration const*>(declaration))
if (var->isLocalVariable()) {
pushes = var->type()->sizeOnStack();
else if (!var->type()->isValueType())
pushes = 1;
else
pushes = 2; // slot number, intra slot offset
} }
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
{ {
if (!contract->isLibrary()) if (!contract->isLibrary())
return false;
pushes = 1;
}
else
return false;
for (unsigned i = 0; i < pushes; ++i)
_assembly.append(u256(0)); // just to verify the stack height
}
else
{ {
// lvalue context typeError(_identifier.location, "Expected a library.");
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) return size_t(-1);
{ }
if (!varDecl->isLocalVariable())
return false; // only local variables are inline-assemlby lvalues
for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i)
_assembly.append(Instruction::POP); // remove value just to verify the stack height
} }
else else
return false; return size_t(-1);
} }
return true; ref->second.valueSize = 1;
return size_t(1);
}; };
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors); solAssert(!_inlineAssembly.annotation().analysisInfo, "");
if (!codeGen.typeCheck(identifierAccess)) _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
assembly::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
m_errors,
identifierAccess
);
if (!analyzer.analyze(_inlineAssembly.operations()))
return false; return false;
return true; return true;
} }
@ -886,15 +952,14 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
{ {
if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression()).get())) if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression()).get()))
{ {
using Location = FunctionType::Location; auto kind = callType->kind();
Location location = callType->location();
if ( if (
location == Location::Bare || kind == FunctionType::Kind::Bare ||
location == Location::BareCallCode || kind == FunctionType::Kind::BareCallCode ||
location == Location::BareDelegateCall kind == FunctionType::Kind::BareDelegateCall
) )
warning(_statement.location(), "Return value of low-level calls not used."); warning(_statement.location(), "Return value of low-level calls not used.");
else if (location == Location::Send) else if (kind == FunctionType::Kind::Send)
warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead."); warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead.");
} }
} }
@ -1387,7 +1452,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
TypePointers{type}, TypePointers{type},
strings(), strings(),
strings(), strings(),
FunctionType::Location::ObjectCreation FunctionType::Kind::ObjectCreation
); );
_newExpression.annotation().isPure = true; _newExpression.annotation().isPure = true;
} }
@ -1636,8 +1701,8 @@ bool TypeChecker::visit(Identifier const& _identifier)
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration)) if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration)) else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
if (auto functionType = dynamic_cast<FunctionType const*>(annotation.type.get())) if (dynamic_cast<FunctionType const*>(annotation.type.get()))
annotation.isPure = functionType->isPure(); annotation.isPure = true;
return false; return false;
} }

View File

@ -83,6 +83,7 @@ private:
virtual bool visit(StructDefinition const& _struct) override; virtual bool visit(StructDefinition const& _struct) override;
virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(FunctionDefinition const& _function) override;
virtual bool visit(VariableDeclaration const& _variable) override; virtual bool visit(VariableDeclaration const& _variable) override;
virtual bool visit(EnumDefinition const& _enum) override;
/// We need to do this manually because we want to pass the bases of the current contract in /// We need to do this manually because we want to pass the bases of the current contract in
/// case this is a base constructor call. /// case this is a base constructor call.
void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases); void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases);

View File

@ -316,19 +316,21 @@ protected:
class ContractDefinition: public Declaration, public Documented class ContractDefinition: public Declaration, public Documented
{ {
public: public:
enum class ContractKind { Interface, Contract, Library };
ContractDefinition( ContractDefinition(
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation, ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<ASTNode>> const& _subNodes, std::vector<ASTPointer<ASTNode>> const& _subNodes,
bool _isLibrary ContractKind _contractKind = ContractKind::Contract
): ):
Declaration(_location, _name), Declaration(_location, _name),
Documented(_documentation), Documented(_documentation),
m_baseContracts(_baseContracts), m_baseContracts(_baseContracts),
m_subNodes(_subNodes), m_subNodes(_subNodes),
m_isLibrary(_isLibrary) m_contractKind(_contractKind)
{} {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
@ -344,7 +346,7 @@ public:
std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); } std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); }
std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); } std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); }
std::vector<EventDefinition const*> const& interfaceEvents() const; std::vector<EventDefinition const*> const& interfaceEvents() const;
bool isLibrary() const { return m_isLibrary; } bool isLibrary() const { return m_contractKind == ContractKind::Library; }
/// @returns a map of canonical function signatures to FunctionDefinitions /// @returns a map of canonical function signatures to FunctionDefinitions
/// as intended for use by the ABI. /// as intended for use by the ABI.
@ -371,10 +373,12 @@ public:
virtual ContractDefinitionAnnotation& annotation() const override; virtual ContractDefinitionAnnotation& annotation() const override;
ContractKind contractKind() const { return m_contractKind; }
private: private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts; std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<ASTNode>> m_subNodes; std::vector<ASTPointer<ASTNode>> m_subNodes;
bool m_isLibrary; ContractKind m_contractKind;
// parsed Natspec documentation of the contract. // parsed Natspec documentation of the contract.
Json::Value m_userDocumentation; Json::Value m_userDocumentation;

View File

@ -22,11 +22,12 @@
#pragma once #pragma once
#include <libsolidity/ast/ASTForward.h>
#include <map> #include <map>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <set> #include <set>
#include <libsolidity/ast/ASTForward.h>
namespace dev namespace dev
{ {
@ -112,13 +113,24 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
namespace assembly namespace assembly
{ {
struct Identifier; // forward struct AsmAnalysisInfo;
struct Identifier;
} }
struct InlineAssemblyAnnotation: StatementAnnotation struct InlineAssemblyAnnotation: StatementAnnotation
{ {
/// Mapping containing resolved references to external identifiers. struct ExternalIdentifierInfo
std::map<assembly::Identifier const*, Declaration const*> externalReferences; {
Declaration const* declaration = nullptr;
bool isSlot = false; ///< Whether the storage slot of a variable is queried.
bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried.
size_t valueSize = size_t(-1);
};
/// Mapping containing resolved references to external identifiers and their value size
std::map<assembly::Identifier const*, ExternalIdentifierInfo> externalReferences;
/// Information generated during analysis phase.
std::shared_ptr<assembly::AsmAnalysisInfo> analysisInfo;
}; };
struct ReturnAnnotation: StatementAnnotation struct ReturnAnnotation: StatementAnnotation

View File

@ -39,6 +39,21 @@ void ASTJsonConverter::addJsonNode(
initializer_list<pair<string const, Json::Value const>> _attributes, initializer_list<pair<string const, Json::Value const>> _attributes,
bool _hasChildren = false bool _hasChildren = false
) )
{
ASTJsonConverter::addJsonNode(
_node,
_nodeName,
std::vector<pair<string const, Json::Value const>>(_attributes),
_hasChildren
);
}
void ASTJsonConverter::addJsonNode(
ASTNode const& _node,
string const& _nodeName,
std::vector<pair<string const, Json::Value const>> const& _attributes,
bool _hasChildren = false
)
{ {
Json::Value node; Json::Value node;
@ -183,11 +198,18 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
bool ASTJsonConverter::visit(VariableDeclaration const& _node) bool ASTJsonConverter::visit(VariableDeclaration const& _node)
{ {
addJsonNode(_node, "VariableDeclaration", { std::vector<pair<string const, Json::Value const>> attributes = {
make_pair("name", _node.name()), make_pair("name", _node.name()),
make_pair("type", type(_node)) make_pair("type", type(_node)),
}, true); make_pair("constant", _node.isConstant()),
make_pair("storageLocation", location(_node.referenceLocation())),
make_pair("visibility", visibility(_node.visibility()))
};
if (m_inEvent)
attributes.push_back(make_pair("indexed", _node.isIndexed()));
addJsonNode(_node, "VariableDeclaration", attributes, true);
return true; return true;
} }
bool ASTJsonConverter::visit(ModifierDefinition const& _node) bool ASTJsonConverter::visit(ModifierDefinition const& _node)
@ -209,6 +231,7 @@ bool ASTJsonConverter::visit(TypeName const&)
bool ASTJsonConverter::visit(EventDefinition const& _node) bool ASTJsonConverter::visit(EventDefinition const& _node)
{ {
m_inEvent = true;
addJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()) }, true); addJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()) }, true);
return true; return true;
} }
@ -502,6 +525,7 @@ void ASTJsonConverter::endVisit(ModifierInvocation const&)
void ASTJsonConverter::endVisit(EventDefinition const&) void ASTJsonConverter::endVisit(EventDefinition const&)
{ {
m_inEvent = false;
goUp(); goUp();
} }
@ -670,6 +694,21 @@ string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility)
} }
} }
string ASTJsonConverter::location(VariableDeclaration::Location _location)
{
switch (_location)
{
case VariableDeclaration::Location::Default:
return "default";
case VariableDeclaration::Location::Storage:
return "storage";
case VariableDeclaration::Location::Memory:
return "memory";
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown declaration location."));
}
}
string ASTJsonConverter::type(Expression const& _expression) string ASTJsonConverter::type(Expression const& _expression)
{ {
return _expression.annotation().type ? _expression.annotation().type->toString() : "Unknown"; return _expression.annotation().type ? _expression.annotation().type->toString() : "Unknown";

View File

@ -151,8 +151,15 @@ private:
std::initializer_list<std::pair<std::string const, Json::Value const>> _attributes, std::initializer_list<std::pair<std::string const, Json::Value const>> _attributes,
bool _hasChildren bool _hasChildren
); );
void addJsonNode(
ASTNode const& _node,
std::string const& _nodeName,
std::vector<std::pair<std::string const, Json::Value const>> const& _attributes,
bool _hasChildren
);
std::string sourceLocationToString(SourceLocation const& _location) const; std::string sourceLocationToString(SourceLocation const& _location) const;
std::string visibility(Declaration::Visibility const& _visibility); std::string visibility(Declaration::Visibility const& _visibility);
std::string location(VariableDeclaration::Location _location);
std::string type(Expression const& _expression); std::string type(Expression const& _expression);
std::string type(VariableDeclaration const& _varDecl); std::string type(VariableDeclaration const& _varDecl);
inline void goUp() inline void goUp()
@ -161,6 +168,7 @@ private:
m_jsonNodePtrs.pop(); m_jsonNodePtrs.pop();
} }
bool m_inEvent = false; ///< whether we are currently inside an event or not
bool processed = false; bool processed = false;
Json::Value m_astJson; Json::Value m_astJson;
std::stack<Json::Value*> m_jsonNodePtrs; std::stack<Json::Value*> m_jsonNodePtrs;

View File

@ -462,11 +462,11 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
if (isAddress()) if (isAddress())
return { return {
{"balance", make_shared<IntegerType >(256)}, {"balance", make_shared<IntegerType >(256)},
{"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true, false, true)}, {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::Bare, true, false, true)},
{"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true, false, true)}, {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)},
{"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)}, {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)},
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}, {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)},
{"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Location::Transfer)} {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}
}; };
else else
return MemberList::MemberMap(); return MemberList::MemberMap();
@ -1466,7 +1466,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
TypePointers{make_shared<IntegerType>(256)}, TypePointers{make_shared<IntegerType>(256)},
strings{string()}, strings{string()},
strings{string()}, strings{string()},
isByteArray() ? FunctionType::Location::ByteArrayPush : FunctionType::Location::ArrayPush isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
)}); )});
} }
return members; return members;
@ -1766,7 +1766,7 @@ FunctionTypePointer StructType::constructorType() const
TypePointers{copyForLocation(DataLocation::Memory, false)}, TypePointers{copyForLocation(DataLocation::Memory, false)},
paramNames, paramNames,
strings(), strings(),
FunctionType::Location::Internal FunctionType::Kind::Internal
); );
} }
@ -1967,7 +1967,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons
} }
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
m_location(_isInternal ? Location::Internal : Location::External), m_kind(_isInternal ? Kind::Internal : Kind::External),
m_isConstant(_function.isDeclaredConst()), m_isConstant(_function.isDeclaredConst()),
m_isPayable(_isInternal ? false : _function.isPayable()), m_isPayable(_isInternal ? false : _function.isPayable()),
m_declaration(&_function) m_declaration(&_function)
@ -1998,7 +1998,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal
} }
FunctionType::FunctionType(VariableDeclaration const& _varDecl): FunctionType::FunctionType(VariableDeclaration const& _varDecl):
m_location(Location::External), m_isConstant(true), m_declaration(&_varDecl) m_kind(Kind::External), m_isConstant(true), m_declaration(&_varDecl)
{ {
TypePointers paramTypes; TypePointers paramTypes;
vector<string> paramNames; vector<string> paramNames;
@ -2058,7 +2058,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
} }
FunctionType::FunctionType(EventDefinition const& _event): FunctionType::FunctionType(EventDefinition const& _event):
m_location(Location::Event), m_isConstant(true), m_declaration(&_event) m_kind(Kind::Event), m_isConstant(true), m_declaration(&_event)
{ {
TypePointers params; TypePointers params;
vector<string> paramNames; vector<string> paramNames;
@ -2074,19 +2074,19 @@ FunctionType::FunctionType(EventDefinition const& _event):
} }
FunctionType::FunctionType(FunctionTypeName const& _typeName): FunctionType::FunctionType(FunctionTypeName const& _typeName):
m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal), m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal),
m_isConstant(_typeName.isDeclaredConst()), m_isConstant(_typeName.isDeclaredConst()),
m_isPayable(_typeName.isPayable()) m_isPayable(_typeName.isPayable())
{ {
if (_typeName.isPayable()) if (_typeName.isPayable())
{ {
solAssert(m_location == Location::External, "Internal payable function type used."); solAssert(m_kind == Kind::External, "Internal payable function type used.");
solAssert(!m_isConstant, "Payable constant function"); solAssert(!m_isConstant, "Payable constant function");
} }
for (auto const& t: _typeName.parameterTypes()) for (auto const& t: _typeName.parameterTypes())
{ {
solAssert(t->annotation().type, "Type not set for parameter."); solAssert(t->annotation().type, "Type not set for parameter.");
if (m_location == Location::External) if (m_kind == Kind::External)
solAssert( solAssert(
t->annotation().type->canBeUsedExternally(false), t->annotation().type->canBeUsedExternally(false),
"Internal type used as parameter for external function." "Internal type used as parameter for external function."
@ -2096,7 +2096,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName):
for (auto const& t: _typeName.returnParameterTypes()) for (auto const& t: _typeName.returnParameterTypes())
{ {
solAssert(t->annotation().type, "Type not set for return parameter."); solAssert(t->annotation().type, "Type not set for return parameter.");
if (m_location == Location::External) if (m_kind == Kind::External)
solAssert( solAssert(
t->annotation().type->canBeUsedExternally(false), t->annotation().type->canBeUsedExternally(false),
"Internal type used as return parameter for external function." "Internal type used as return parameter for external function."
@ -2126,7 +2126,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c
TypePointers{make_shared<ContractType>(_contract)}, TypePointers{make_shared<ContractType>(_contract)},
parameterNames, parameterNames,
strings{""}, strings{""},
Location::Creation, Kind::Creation,
false, false,
nullptr, nullptr,
false, false,
@ -2151,38 +2151,38 @@ TypePointers FunctionType::parameterTypes() const
string FunctionType::identifier() const string FunctionType::identifier() const
{ {
string id = "t_function_"; string id = "t_function_";
switch (location()) switch (m_kind)
{ {
case Location::Internal: id += "internal"; break; case Kind::Internal: id += "internal"; break;
case Location::External: id += "external"; break; case Kind::External: id += "external"; break;
case Location::CallCode: id += "callcode"; break; case Kind::CallCode: id += "callcode"; break;
case Location::DelegateCall: id += "delegatecall"; break; case Kind::DelegateCall: id += "delegatecall"; break;
case Location::Bare: id += "bare"; break; case Kind::Bare: id += "bare"; break;
case Location::BareCallCode: id += "barecallcode"; break; case Kind::BareCallCode: id += "barecallcode"; break;
case Location::BareDelegateCall: id += "baredelegatecall"; break; case Kind::BareDelegateCall: id += "baredelegatecall"; break;
case Location::Creation: id += "creation"; break; case Kind::Creation: id += "creation"; break;
case Location::Send: id += "send"; break; case Kind::Send: id += "send"; break;
case Location::Transfer: id += "transfer"; break; case Kind::Transfer: id += "transfer"; break;
case Location::SHA3: id += "sha3"; break; case Kind::SHA3: id += "sha3"; break;
case Location::Selfdestruct: id += "selfdestruct"; break; case Kind::Selfdestruct: id += "selfdestruct"; break;
case Location::Revert: id += "revert"; break; case Kind::Revert: id += "revert"; break;
case Location::ECRecover: id += "ecrecover"; break; case Kind::ECRecover: id += "ecrecover"; break;
case Location::SHA256: id += "sha256"; break; case Kind::SHA256: id += "sha256"; break;
case Location::RIPEMD160: id += "ripemd160"; break; case Kind::RIPEMD160: id += "ripemd160"; break;
case Location::Log0: id += "log0"; break; case Kind::Log0: id += "log0"; break;
case Location::Log1: id += "log1"; break; case Kind::Log1: id += "log1"; break;
case Location::Log2: id += "log2"; break; case Kind::Log2: id += "log2"; break;
case Location::Log3: id += "log3"; break; case Kind::Log3: id += "log3"; break;
case Location::Log4: id += "log4"; break; case Kind::Log4: id += "log4"; break;
case Location::Event: id += "event"; break; case Kind::Event: id += "event"; break;
case Location::SetGas: id += "setgas"; break; case Kind::SetGas: id += "setgas"; break;
case Location::SetValue: id += "setvalue"; break; case Kind::SetValue: id += "setvalue"; break;
case Location::BlockHash: id += "blockhash"; break; case Kind::BlockHash: id += "blockhash"; break;
case Location::AddMod: id += "addmod"; break; case Kind::AddMod: id += "addmod"; break;
case Location::MulMod: id += "mulmod"; break; case Kind::MulMod: id += "mulmod"; break;
case Location::ArrayPush: id += "arraypush"; break; case Kind::ArrayPush: id += "arraypush"; break;
case Location::ByteArrayPush: id += "bytearraypush"; break; case Kind::ByteArrayPush: id += "bytearraypush"; break;
case Location::ObjectCreation: id += "objectcreation"; break; case Kind::ObjectCreation: id += "objectcreation"; break;
default: solAssert(false, "Unknown function location."); break; default: solAssert(false, "Unknown function location."); break;
} }
if (isConstant()) if (isConstant())
@ -2203,7 +2203,7 @@ bool FunctionType::operator==(Type const& _other) const
return false; return false;
FunctionType const& other = dynamic_cast<FunctionType const&>(_other); FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
if (m_location != other.m_location) if (m_kind != other.m_kind)
return false; return false;
if (m_isConstant != other.isConstant()) if (m_isConstant != other.isConstant())
return false; return false;
@ -2231,7 +2231,7 @@ bool FunctionType::operator==(Type const& _other) const
bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (m_location == Location::External && _convertTo.category() == Category::Integer) if (m_kind == Kind::External && _convertTo.category() == Category::Integer)
{ {
IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
if (convertTo.isAddress()) if (convertTo.isAddress())
@ -2249,7 +2249,7 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
string FunctionType::canonicalName(bool) const string FunctionType::canonicalName(bool) const
{ {
solAssert(m_location == Location::External, ""); solAssert(m_kind == Kind::External, "");
return "function"; return "function";
} }
@ -2263,7 +2263,7 @@ string FunctionType::toString(bool _short) const
name += " constant"; name += " constant";
if (m_isPayable) if (m_isPayable)
name += " payable"; name += " payable";
if (m_location == Location::External) if (m_kind == Kind::External)
name += " external"; name += " external";
if (!m_returnParameterTypes.empty()) if (!m_returnParameterTypes.empty())
{ {
@ -2285,7 +2285,7 @@ unsigned FunctionType::calldataEncodedSize(bool _padded) const
u256 FunctionType::storageSize() const u256 FunctionType::storageSize() const
{ {
if (m_location == Location::External || m_location == Location::Internal) if (m_kind == Kind::External || m_kind == Kind::Internal)
return 1; return 1;
else else
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
@ -2295,9 +2295,9 @@ u256 FunctionType::storageSize() const
unsigned FunctionType::storageBytes() const unsigned FunctionType::storageBytes() const
{ {
if (m_location == Location::External) if (m_kind == Kind::External)
return 20 + 4; return 20 + 4;
else if (m_location == Location::Internal) else if (m_kind == Kind::Internal)
return 8; // it should really not be possible to create larger programs return 8; // it should really not be possible to create larger programs
else else
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
@ -2307,21 +2307,21 @@ unsigned FunctionType::storageBytes() const
unsigned FunctionType::sizeOnStack() const unsigned FunctionType::sizeOnStack() const
{ {
Location location = m_location; Kind kind = m_kind;
if (m_location == Location::SetGas || m_location == Location::SetValue) if (m_kind == Kind::SetGas || m_kind == Kind::SetValue)
{ {
solAssert(m_returnParameterTypes.size() == 1, ""); solAssert(m_returnParameterTypes.size() == 1, "");
location = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_location; kind = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_kind;
} }
unsigned size = 0; unsigned size = 0;
if (location == Location::External || location == Location::CallCode || location == Location::DelegateCall) if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall)
size = 2; size = 2;
else if (location == Location::Bare || location == Location::BareCallCode || location == Location::BareDelegateCall) else if (kind == Kind::Bare || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall)
size = 1; size = 1;
else if (location == Location::Internal) else if (kind == Kind::Internal)
size = 1; size = 1;
else if (location == Location::ArrayPush || location == Location::ByteArrayPush) else if (kind == Kind::ArrayPush || kind == Kind::ByteArrayPush)
size = 1; size = 1;
if (m_gasSet) if (m_gasSet)
size++; size++;
@ -2362,26 +2362,26 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
return make_shared<FunctionType>( return make_shared<FunctionType>(
paramTypes, retParamTypes, paramTypes, retParamTypes,
m_parameterNames, m_returnParameterNames, m_parameterNames, m_returnParameterNames,
m_location, m_arbitraryParameters, m_kind, m_arbitraryParameters,
m_declaration, m_isConstant, m_isPayable m_declaration, m_isConstant, m_isPayable
); );
} }
MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) const MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) const
{ {
switch (m_location) switch (m_kind)
{ {
case Location::External: case Kind::External:
case Location::Creation: case Kind::Creation:
case Location::ECRecover: case Kind::ECRecover:
case Location::SHA256: case Kind::SHA256:
case Location::RIPEMD160: case Kind::RIPEMD160:
case Location::Bare: case Kind::Bare:
case Location::BareCallCode: case Kind::BareCallCode:
case Location::BareDelegateCall: case Kind::BareDelegateCall:
{ {
MemberList::MemberMap members; MemberList::MemberMap members;
if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall) if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
{ {
if (m_isPayable) if (m_isPayable)
members.push_back(MemberList::Member( members.push_back(MemberList::Member(
@ -2391,7 +2391,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
TypePointers{copyAndSetGasOrValue(false, true)}, TypePointers{copyAndSetGasOrValue(false, true)},
strings(), strings(),
strings(), strings(),
Location::SetValue, Kind::SetValue,
false, false,
nullptr, nullptr,
false, false,
@ -2401,7 +2401,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
) )
)); ));
} }
if (m_location != Location::Creation) if (m_kind != Kind::Creation)
members.push_back(MemberList::Member( members.push_back(MemberList::Member(
"gas", "gas",
make_shared<FunctionType>( make_shared<FunctionType>(
@ -2409,7 +2409,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
TypePointers{copyAndSetGasOrValue(true, false)}, TypePointers{copyAndSetGasOrValue(true, false)},
strings(), strings(),
strings(), strings(),
Location::SetGas, Kind::SetGas,
false, false,
nullptr, nullptr,
false, false,
@ -2428,7 +2428,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
TypePointer FunctionType::encodingType() const TypePointer FunctionType::encodingType() const
{ {
// Only external functions can be encoded, internal functions cannot leave code boundaries. // Only external functions can be encoded, internal functions cannot leave code boundaries.
if (m_location == Location::External) if (m_kind == Kind::External)
return shared_from_this(); return shared_from_this();
else else
return TypePointer(); return TypePointer();
@ -2436,7 +2436,7 @@ TypePointer FunctionType::encodingType() const
TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const
{ {
if (m_location == Location::External) if (m_kind == Kind::External)
return shared_from_this(); return shared_from_this();
else else
return TypePointer(); return TypePointer();
@ -2478,14 +2478,14 @@ bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const
bool FunctionType::isBareCall() const bool FunctionType::isBareCall() const
{ {
switch (m_location) switch (m_kind)
{ {
case Location::Bare: case Kind::Bare:
case Location::BareCallCode: case Kind::BareCallCode:
case Location::BareDelegateCall: case Kind::BareDelegateCall:
case Location::ECRecover: case Kind::ECRecover:
case Location::SHA256: case Kind::SHA256:
case Location::RIPEMD160: case Kind::RIPEMD160:
return true; return true;
default: default:
return false; return false;
@ -2520,13 +2520,13 @@ u256 FunctionType::externalIdentifier() const
bool FunctionType::isPure() const bool FunctionType::isPure() const
{ {
return return
m_location == Location::SHA3 || m_kind == Kind::SHA3 ||
m_location == Location::ECRecover || m_kind == Kind::ECRecover ||
m_location == Location::SHA256 || m_kind == Kind::SHA256 ||
m_location == Location::RIPEMD160 || m_kind == Kind::RIPEMD160 ||
m_location == Location::AddMod || m_kind == Kind::AddMod ||
m_location == Location::MulMod || m_kind == Kind::MulMod ||
m_location == Location::ObjectCreation; m_kind == Kind::ObjectCreation;
} }
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
@ -2545,7 +2545,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con
m_returnParameterTypes, m_returnParameterTypes,
m_parameterNames, m_parameterNames,
m_returnParameterNames, m_returnParameterNames,
m_location, m_kind,
m_arbitraryParameters, m_arbitraryParameters,
m_declaration, m_declaration,
m_isConstant, m_isConstant,
@ -2571,18 +2571,18 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
parameterTypes.push_back(t); parameterTypes.push_back(t);
} }
Location location = m_location; Kind kind = m_kind;
if (_inLibrary) if (_inLibrary)
{ {
solAssert(!!m_declaration, "Declaration has to be available."); solAssert(!!m_declaration, "Declaration has to be available.");
if (!m_declaration->isPublic()) if (!m_declaration->isPublic())
location = Location::Internal; // will be inlined kind = Kind::Internal; // will be inlined
else else
location = Location::DelegateCall; kind = Kind::DelegateCall;
} }
TypePointers returnParameterTypes = m_returnParameterTypes; TypePointers returnParameterTypes = m_returnParameterTypes;
if (location != Location::Internal) if (kind != Kind::Internal)
{ {
// Alter dynamic types to be non-accessible. // Alter dynamic types to be non-accessible.
for (auto& param: returnParameterTypes) for (auto& param: returnParameterTypes)
@ -2595,7 +2595,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
returnParameterTypes, returnParameterTypes,
m_parameterNames, m_parameterNames,
m_returnParameterNames, m_returnParameterNames,
location, kind,
m_arbitraryParameters, m_arbitraryParameters,
m_declaration, m_declaration,
m_isConstant, m_isConstant,
@ -2821,7 +2821,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
return MemberList::MemberMap({ return MemberList::MemberMap({
{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, {"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"timestamp", make_shared<IntegerType>(256)}, {"timestamp", make_shared<IntegerType>(256)},
{"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)}, {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash)},
{"difficulty", make_shared<IntegerType>(256)}, {"difficulty", make_shared<IntegerType>(256)},
{"number", make_shared<IntegerType>(256)}, {"number", make_shared<IntegerType>(256)},
{"gaslimit", make_shared<IntegerType>(256)} {"gaslimit", make_shared<IntegerType>(256)}

View File

@ -817,8 +817,7 @@ class FunctionType: public Type
{ {
public: public:
/// How this function is invoked on the EVM. /// How this function is invoked on the EVM.
/// @todo This documentation is outdated, and Location should rather be named "Type" enum class Kind
enum class Location
{ {
Internal, ///< stack-call using plain JUMP Internal, ///< stack-call using plain JUMP
External, ///< external call using CALL External, ///< external call using CALL
@ -868,7 +867,7 @@ public:
FunctionType( FunctionType(
strings const& _parameterTypes, strings const& _parameterTypes,
strings const& _returnParameterTypes, strings const& _returnParameterTypes,
Location _location = Location::Internal, Kind _kind = Kind::Internal,
bool _arbitraryParameters = false, bool _arbitraryParameters = false,
bool _constant = false, bool _constant = false,
bool _payable = false bool _payable = false
@ -877,7 +876,7 @@ public:
parseElementaryTypeVector(_returnParameterTypes), parseElementaryTypeVector(_returnParameterTypes),
strings(), strings(),
strings(), strings(),
_location, _kind,
_arbitraryParameters, _arbitraryParameters,
nullptr, nullptr,
_constant, _constant,
@ -895,7 +894,7 @@ public:
TypePointers const& _returnParameterTypes, TypePointers const& _returnParameterTypes,
strings _parameterNames = strings(), strings _parameterNames = strings(),
strings _returnParameterNames = strings(), strings _returnParameterNames = strings(),
Location _location = Location::Internal, Kind _kind = Kind::Internal,
bool _arbitraryParameters = false, bool _arbitraryParameters = false,
Declaration const* _declaration = nullptr, Declaration const* _declaration = nullptr,
bool _isConstant = false, bool _isConstant = false,
@ -908,7 +907,7 @@ public:
m_returnParameterTypes(_returnParameterTypes), m_returnParameterTypes(_returnParameterTypes),
m_parameterNames(_parameterNames), m_parameterNames(_parameterNames),
m_returnParameterNames(_returnParameterNames), m_returnParameterNames(_returnParameterNames),
m_location(_location), m_kind(_kind),
m_arbitraryParameters(_arbitraryParameters), m_arbitraryParameters(_arbitraryParameters),
m_gasSet(_gasSet), m_gasSet(_gasSet),
m_valueSet(_valueSet), m_valueSet(_valueSet),
@ -937,11 +936,11 @@ public:
virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string canonicalName(bool /*_addDataLocation*/) const override;
virtual std::string toString(bool _short) const override; virtual std::string toString(bool _short) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override; virtual unsigned calldataEncodedSize(bool _padded) const override;
virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
virtual u256 storageSize() const override; virtual u256 storageSize() const override;
virtual unsigned storageBytes() const override; virtual unsigned storageBytes() const override;
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
virtual unsigned sizeOnStack() const override; virtual unsigned sizeOnStack() const override;
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override; virtual TypePointer encodingType() const override;
@ -964,7 +963,7 @@ public:
/// @returns true if the ABI is used for this call (only meaningful for external calls) /// @returns true if the ABI is used for this call (only meaningful for external calls)
bool isBareCall() const; bool isBareCall() const;
Location const& location() const { return m_location; } Kind const& kind() const { return m_kind; }
/// @returns the external signature of this function type given the function name /// @returns the external signature of this function type given the function name
std::string externalSignature() const; std::string externalSignature() const;
/// @returns the external identifier of this function (the hash of the signature). /// @returns the external identifier of this function (the hash of the signature).
@ -986,7 +985,7 @@ public:
ASTPointer<ASTString> documentation() const; ASTPointer<ASTString> documentation() const;
/// true iff arguments are to be padded to multiples of 32 bytes for external calls /// true iff arguments are to be padded to multiples of 32 bytes for external calls
bool padArguments() const { return !(m_location == Location::SHA3 || m_location == Location::SHA256 || m_location == Location::RIPEMD160); } bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160); }
bool takesArbitraryParameters() const { return m_arbitraryParameters; } bool takesArbitraryParameters() const { return m_arbitraryParameters; }
bool gasSet() const { return m_gasSet; } bool gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; } bool valueSet() const { return m_valueSet; }
@ -1012,7 +1011,7 @@ private:
TypePointers m_returnParameterTypes; TypePointers m_returnParameterTypes;
std::vector<std::string> m_parameterNames; std::vector<std::string> m_parameterNames;
std::vector<std::string> m_returnParameterNames; std::vector<std::string> m_returnParameterNames;
Location const m_location; Kind const m_kind;
/// true if the function takes an arbitrary number of arguments of arbitrary types /// true if the function takes an arbitrary number of arguments of arbitrary types
bool const m_arbitraryParameters = false; bool const m_arbitraryParameters = false;
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack

View File

@ -265,31 +265,40 @@ void CompilerContext::appendInlineAssembly(
} }
unsigned startStackHeight = stackHeight(); unsigned startStackHeight = stackHeight();
auto identifierAccess = [&](
assembly::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](
assembly::Identifier const& _identifier, assembly::Identifier const& _identifier,
eth::Assembly& _assembly, assembly::IdentifierContext
assembly::CodeGenerator::IdentifierContext _context )
) { {
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
if (it == _localVariables.end()) return it == _localVariables.end() ? size_t(-1) : 1;
return false; };
identifierAccess.generateCode = [&](
assembly::Identifier const& _identifier,
assembly::IdentifierContext _context,
eth::Assembly& _assembly
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
solAssert(it != _localVariables.end(), "");
unsigned stackDepth = _localVariables.end() - it; unsigned stackDepth = _localVariables.end() - it;
int stackDiff = _assembly.deposit() - startStackHeight + stackDepth; int stackDiff = _assembly.deposit() - startStackHeight + stackDepth;
if (_context == assembly::CodeGenerator::IdentifierContext::LValue) if (_context == assembly::IdentifierContext::LValue)
stackDiff -= 1; stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16) if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << CompilerError() <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );
if (_context == assembly::CodeGenerator::IdentifierContext::RValue) if (_context == assembly::IdentifierContext::RValue)
_assembly.append(dupInstruction(stackDiff)); _assembly.append(dupInstruction(stackDiff));
else else
{ {
_assembly.append(swapInstruction(stackDiff)); _assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP); _assembly.append(Instruction::POP);
} }
return true;
}; };
solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block.");

View File

@ -135,7 +135,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
} }
else if ( else if (
_type.category() == Type::Category::Function && _type.category() == Type::Category::Function &&
dynamic_cast<FunctionType const&>(_type).location() == FunctionType::Location::External dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
) )
{ {
solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented.");
@ -795,7 +795,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType); IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType);
solAssert(targetType.isAddress(), "Function type can only be converted to address."); solAssert(targetType.isAddress(), "Function type can only be converted to address.");
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack); FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
solAssert(typeOnStack.location() == FunctionType::Location::External, "Only external function type can be converted."); solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted.");
// stack: <address> <function_id> // stack: <address> <function_id>
m_context << Instruction::POP; m_context << Instruction::POP;
@ -820,7 +820,7 @@ void CompilerUtils::pushZeroValue(Type const& _type)
{ {
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
{ {
if (funType->location() == FunctionType::Location::Internal) if (funType->kind() == FunctionType::Kind::Internal)
{ {
m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
_context.appendInvalid(); _context.appendInvalid();
@ -983,7 +983,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
unsigned numBytes = _type.calldataEncodedSize(_padToWords); unsigned numBytes = _type.calldataEncodedSize(_padToWords);
bool isExternalFunctionType = false; bool isExternalFunctionType = false;
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
if (funType->location() == FunctionType::Location::External) if (funType->kind() == FunctionType::Kind::External)
isExternalFunctionType = true; isExternalFunctionType = true;
if (numBytes == 0) if (numBytes == 0)
{ {

View File

@ -520,21 +520,29 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{ {
ErrorList errors; ErrorList errors;
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); assembly::CodeGenerator codeGen(errors);
unsigned startStackHeight = m_context.stackHeight(); unsigned startStackHeight = m_context.stackHeight();
codeGen.assemble( assembly::ExternalIdentifierAccess identifierAccess;
m_context.nonConstAssembly(), identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext)
[&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { {
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end()) if (ref == _inlineAssembly.annotation().externalReferences.end())
return false; return size_t(-1);
Declaration const* decl = ref->second; return ref->second.valueSize;
solAssert(!!decl, ""); };
if (_context == assembly::CodeGenerator::IdentifierContext::RValue) identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _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)
{
int const depositBefore = _assembly.deposit();
solAssert(!!decl->type(), "Type of declaration required but not yet determined."); solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{ {
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef); functionDef = &m_context.resolveVirtualFunction(*functionDef);
_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
// If there is a runtime context, we have to merge both labels into the same // If there is a runtime context, we have to merge both labels into the same
@ -550,63 +558,91 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
{ {
solAssert(!variable->isConstant(), ""); solAssert(!variable->isConstant(), "");
if (m_context.isLocalVariable(variable)) if (m_context.isStateVariable(decl))
{
auto const& location = m_context.storageLocationOfVariable(*decl);
if (ref->second.isSlot)
m_context << location.first;
else if (ref->second.isOffset)
m_context << u256(location.second);
else
solAssert(false, "");
}
else if (m_context.isLocalVariable(decl))
{ {
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
if (ref->second.isSlot || ref->second.isOffset)
{
solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
unsigned size = variable->type()->sizeOnStack();
if (size == 2)
{
// slot plus offset
if (ref->second.isOffset)
stackDiff--;
}
else
{
solAssert(size == 1, "");
// only slot, offset is zero
if (ref->second.isOffset)
{
_assembly.append(u256(0));
return;
}
}
}
else
solAssert(variable->type()->sizeOnStack() == 1, "");
if (stackDiff < 1 || stackDiff > 16) if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );
for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) solAssert(variable->type()->sizeOnStack() == 1, "");
_assembly.append(dupInstruction(stackDiff)); _assembly.append(dupInstruction(stackDiff));
} }
else else
{ solAssert(false, "");
solAssert(m_context.isStateVariable(variable), "Invalid variable type.");
auto const& location = m_context.storageLocationOfVariable(*variable);
if (!variable->type()->isValueType())
{
solAssert(location.second == 0, "Intra-slot offest assumed to be zero.");
_assembly.append(location.first);
}
else
{
_assembly.append(location.first);
_assembly.append(u256(location.second));
}
}
} }
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
{ {
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
solAssert(contract->isLibrary(), ""); solAssert(contract->isLibrary(), "");
_assembly.appendLibraryAddress(contract->fullyQualifiedName()); _assembly.appendLibraryAddress(contract->fullyQualifiedName());
} }
else else
solAssert(false, "Invalid declaration type."); solAssert(false, "Invalid declaration type.");
} else { solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), "");
}
else
{
// lvalue context // lvalue context
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
auto variable = dynamic_cast<VariableDeclaration const*>(decl); auto variable = dynamic_cast<VariableDeclaration const*>(decl);
solAssert( solAssert(
!!variable && m_context.isLocalVariable(variable), !!variable && m_context.isLocalVariable(variable),
"Can only assign to stack variables in inline assembly." "Can only assign to stack variables in inline assembly."
); );
unsigned size = variable->type()->sizeOnStack(); solAssert(variable->type()->sizeOnStack() == 1, "");
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size; int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1;
if (stackDiff > 16 || stackDiff < 1) if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
CompilerError() << CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.") errinfo_comment("Stack too deep, try removing local variables.")
); );
for (unsigned i = 0; i < size; ++i) {
_assembly.append(swapInstruction(stackDiff)); _assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP); _assembly.append(Instruction::POP);
} }
} };
return true; solAssert(_inlineAssembly.annotation().analysisInfo, "");
} codeGen.assemble(
_inlineAssembly.operations(),
*_inlineAssembly.annotation().analysisInfo,
m_context.nonConstAssembly(),
identifierAccess
); );
solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested."); solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested.");
m_context.setStackOffset(startStackHeight); m_context.setStackOffset(startStackHeight);

View File

@ -434,7 +434,6 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool ExpressionCompiler::visit(FunctionCall const& _functionCall) bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _functionCall); CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
using Location = FunctionType::Location;
if (_functionCall.annotation().isTypeConversion) if (_functionCall.annotation().isTypeConversion)
{ {
solAssert(_functionCall.arguments().size() == 1, ""); solAssert(_functionCall.arguments().size() == 1, "");
@ -499,10 +498,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
FunctionType const& function = *functionType; FunctionType const& function = *functionType;
if (function.bound()) if (function.bound())
// Only delegatecall and internal functions can be bound, this might be lifted later. // Only delegatecall and internal functions can be bound, this might be lifted later.
solAssert(function.location() == Location::DelegateCall || function.location() == Location::Internal, ""); solAssert(function.kind() == FunctionType::Kind::DelegateCall || function.kind() == FunctionType::Kind::Internal, "");
switch (function.location()) switch (function.kind())
{ {
case Location::Internal: case FunctionType::Kind::Internal:
{ {
// Calling convention: Caller pushes return address and arguments // Calling convention: Caller pushes return address and arguments
// Callee removes them and pushes return values // Callee removes them and pushes return values
@ -538,16 +537,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1); m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
break; break;
} }
case Location::External: case FunctionType::Kind::External:
case Location::CallCode: case FunctionType::Kind::CallCode:
case Location::DelegateCall: case FunctionType::Kind::DelegateCall:
case Location::Bare: case FunctionType::Kind::Bare:
case Location::BareCallCode: case FunctionType::Kind::BareCallCode:
case Location::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments); appendExternalFunctionCall(function, arguments);
break; break;
case Location::Creation: case FunctionType::Kind::Creation:
{ {
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
solAssert(!function.gasSet(), "Gas limit set for contract creation."); solAssert(!function.gasSet(), "Gas limit set for contract creation.");
@ -592,7 +591,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << swapInstruction(1) << Instruction::POP; m_context << swapInstruction(1) << Instruction::POP;
break; break;
} }
case Location::SetGas: case FunctionType::Kind::SetGas:
{ {
// stack layout: contract_address function_id [gas] [value] // stack layout: contract_address function_id [gas] [value]
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
@ -608,7 +607,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::POP; m_context << Instruction::POP;
break; break;
} }
case Location::SetValue: case FunctionType::Kind::SetValue:
// stack layout: contract_address function_id [gas] [value] // stack layout: contract_address function_id [gas] [value]
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
// Note that function is not the original function, but the ".value" function. // Note that function is not the original function, but the ".value" function.
@ -617,8 +616,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::POP; m_context << Instruction::POP;
arguments.front()->accept(*this); arguments.front()->accept(*this);
break; break;
case Location::Send: case FunctionType::Kind::Send:
case Location::Transfer: case FunctionType::Kind::Transfer:
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
// Provide the gas stipend manually at first because we may send zero ether. // Provide the gas stipend manually at first because we may send zero ether.
// Will be zeroed if we send more than zero ether. // Will be zeroed if we send more than zero ether.
@ -637,7 +636,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
TypePointers{}, TypePointers{},
strings(), strings(),
strings(), strings(),
Location::Bare, FunctionType::Kind::Bare,
false, false,
nullptr, nullptr,
false, false,
@ -647,24 +646,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
), ),
{} {}
); );
if (function.location() == Location::Transfer) if (function.kind() == FunctionType::Kind::Transfer)
{ {
// Check if zero (out of stack or not enough balance). // Check if zero (out of stack or not enough balance).
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalInvalid(); m_context.appendConditionalInvalid();
} }
break; break;
case Location::Selfdestruct: case FunctionType::Kind::Selfdestruct:
arguments.front()->accept(*this); arguments.front()->accept(*this);
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true);
m_context << Instruction::SELFDESTRUCT; m_context << Instruction::SELFDESTRUCT;
break; break;
case Location::Revert: case FunctionType::Kind::Revert:
// memory offset returned - zero length // memory offset returned - zero length
m_context << u256(0) << u256(0); m_context << u256(0) << u256(0);
m_context << Instruction::REVERT; m_context << Instruction::REVERT;
break; break;
case Location::SHA3: case FunctionType::Kind::SHA3:
{ {
TypePointers argumentTypes; TypePointers argumentTypes;
for (auto const& arg: arguments) for (auto const& arg: arguments)
@ -678,13 +677,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::SHA3; m_context << Instruction::SHA3;
break; break;
} }
case Location::Log0: case FunctionType::Kind::Log0:
case Location::Log1: case FunctionType::Kind::Log1:
case Location::Log2: case FunctionType::Kind::Log2:
case Location::Log3: case FunctionType::Kind::Log3:
case Location::Log4: case FunctionType::Kind::Log4:
{ {
unsigned logNumber = int(function.location()) - int(Location::Log0); unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0);
for (unsigned arg = logNumber; arg > 0; --arg) for (unsigned arg = logNumber; arg > 0; --arg)
{ {
arguments[arg]->accept(*this); arguments[arg]->accept(*this);
@ -701,7 +700,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << logInstruction(logNumber); m_context << logInstruction(logNumber);
break; break;
} }
case Location::Event: case FunctionType::Kind::Event:
{ {
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
auto const& event = dynamic_cast<EventDefinition const&>(function.declaration()); auto const& event = dynamic_cast<EventDefinition const&>(function.declaration());
@ -755,50 +754,50 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << logInstruction(numIndexed); m_context << logInstruction(numIndexed);
break; break;
} }
case Location::BlockHash: case FunctionType::Kind::BlockHash:
{ {
arguments[0]->accept(*this); arguments[0]->accept(*this);
utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true); utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true);
m_context << Instruction::BLOCKHASH; m_context << Instruction::BLOCKHASH;
break; break;
} }
case Location::AddMod: case FunctionType::Kind::AddMod:
case Location::MulMod: case FunctionType::Kind::MulMod:
{ {
for (unsigned i = 0; i < 3; i ++) for (unsigned i = 0; i < 3; i ++)
{ {
arguments[2 - i]->accept(*this); arguments[2 - i]->accept(*this);
utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256)); utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256));
} }
if (function.location() == Location::AddMod) if (function.kind() == FunctionType::Kind::AddMod)
m_context << Instruction::ADDMOD; m_context << Instruction::ADDMOD;
else else
m_context << Instruction::MULMOD; m_context << Instruction::MULMOD;
break; break;
} }
case Location::ECRecover: case FunctionType::Kind::ECRecover:
case Location::SHA256: case FunctionType::Kind::SHA256:
case Location::RIPEMD160: case FunctionType::Kind::RIPEMD160:
{ {
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, static const map<FunctionType::Kind, u256> contractAddresses{{FunctionType::Kind::ECRecover, 1},
{Location::SHA256, 2}, {FunctionType::Kind::SHA256, 2},
{Location::RIPEMD160, 3}}; {FunctionType::Kind::RIPEMD160, 3}};
m_context << contractAddresses.find(function.location())->second; m_context << contractAddresses.find(function.kind())->second;
for (unsigned i = function.sizeOnStack(); i > 0; --i) for (unsigned i = function.sizeOnStack(); i > 0; --i)
m_context << swapInstruction(i); m_context << swapInstruction(i);
appendExternalFunctionCall(function, arguments); appendExternalFunctionCall(function, arguments);
break; break;
} }
case Location::ByteArrayPush: case FunctionType::Kind::ByteArrayPush:
case Location::ArrayPush: case FunctionType::Kind::ArrayPush:
{ {
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
solAssert(function.parameterTypes().size() == 1, ""); solAssert(function.parameterTypes().size() == 1, "");
solAssert(!!function.parameterTypes()[0], ""); solAssert(!!function.parameterTypes()[0], "");
TypePointer paramType = function.parameterTypes()[0]; TypePointer paramType = function.parameterTypes()[0];
shared_ptr<ArrayType> arrayType = shared_ptr<ArrayType> arrayType =
function.location() == Location::ArrayPush ? function.kind() == FunctionType::Kind::ArrayPush ?
make_shared<ArrayType>(DataLocation::Storage, paramType) : make_shared<ArrayType>(DataLocation::Storage, paramType) :
make_shared<ArrayType>(DataLocation::Storage); make_shared<ArrayType>(DataLocation::Storage);
// get the current length // get the current length
@ -822,13 +821,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack());
utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack());
// stack: newLength argValue storageSlot slotOffset // stack: newLength argValue storageSlot slotOffset
if (function.location() == Location::ArrayPush) if (function.kind() == FunctionType::Kind::ArrayPush)
StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true);
else else
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
break; break;
} }
case Location::ObjectCreation: case FunctionType::Kind::ObjectCreation:
{ {
// Will allocate at the end of memory (MSIZE) and not write at all unless the base // Will allocate at the end of memory (MSIZE) and not write at all unless the base
// type is dynamically sized. // type is dynamically sized.
@ -878,15 +877,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::POP; m_context << Instruction::POP;
break; break;
} }
case Location::Assert: case FunctionType::Kind::Assert:
case Location::Require: case FunctionType::Kind::Require:
{ {
arguments.front()->accept(*this); arguments.front()->accept(*this);
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false);
// jump if condition was met // jump if condition was met
m_context << Instruction::ISZERO << Instruction::ISZERO; m_context << Instruction::ISZERO << Instruction::ISZERO;
auto success = m_context.appendConditionalJump(); auto success = m_context.appendConditionalJump();
if (function.location() == Location::Assert) if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error // condition was not met, flag an error
m_context << Instruction::INVALID; m_context << Instruction::INVALID;
else else
@ -922,7 +921,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
*funType->selfType(), *funType->selfType(),
true true
); );
if (funType->location() == FunctionType::Location::Internal) if (funType->kind() == FunctionType::Kind::Internal)
{ {
FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration()); FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration());
utils().pushCombinedFunctionEntryLabel(funDef); utils().pushCombinedFunctionEntryLabel(funDef);
@ -930,7 +929,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
} }
else else
{ {
solAssert(funType->location() == FunctionType::Location::DelegateCall, ""); solAssert(funType->kind() == FunctionType::Kind::DelegateCall, "");
auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope()); auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope());
solAssert(contract && contract->isLibrary(), ""); solAssert(contract && contract->isLibrary(), "");
m_context.appendLibraryAddress(contract->fullyQualifiedName()); m_context.appendLibraryAddress(contract->fullyQualifiedName());
@ -949,9 +948,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(_memberAccess.annotation().type, "_memberAccess has no type"); solAssert(_memberAccess.annotation().type, "_memberAccess has no type");
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
{ {
switch (funType->location()) switch (funType->kind())
{ {
case FunctionType::Location::Internal: case FunctionType::Kind::Internal:
// We do not visit the expression here on purpose, because in the case of an // We do not visit the expression here on purpose, because in the case of an
// internal library function call, this would push the library address forcing // internal library function call, this would push the library address forcing
// us to link against it although we actually do not need it. // us to link against it although we actually do not need it.
@ -960,31 +959,31 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
else else
solAssert(false, "Function not found in member access"); solAssert(false, "Function not found in member access");
break; break;
case FunctionType::Location::Event: case FunctionType::Kind::Event:
if (!dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration)) if (!dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration))
solAssert(false, "event not found"); solAssert(false, "event not found");
// no-op, because the parent node will do the job // no-op, because the parent node will do the job
break; break;
case FunctionType::Location::External: case FunctionType::Kind::External:
case FunctionType::Location::Creation: case FunctionType::Kind::Creation:
case FunctionType::Location::DelegateCall: case FunctionType::Kind::DelegateCall:
case FunctionType::Location::CallCode: case FunctionType::Kind::CallCode:
case FunctionType::Location::Send: case FunctionType::Kind::Send:
case FunctionType::Location::Bare: case FunctionType::Kind::Bare:
case FunctionType::Location::BareCallCode: case FunctionType::Kind::BareCallCode:
case FunctionType::Location::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
case FunctionType::Location::Transfer: case FunctionType::Kind::Transfer:
_memberAccess.expression().accept(*this); _memberAccess.expression().accept(*this);
m_context << funType->externalIdentifier(); m_context << funType->externalIdentifier();
break; break;
case FunctionType::Location::Log0: case FunctionType::Kind::Log0:
case FunctionType::Location::Log1: case FunctionType::Kind::Log1:
case FunctionType::Location::Log2: case FunctionType::Kind::Log2:
case FunctionType::Location::Log3: case FunctionType::Kind::Log3:
case FunctionType::Location::Log4: case FunctionType::Kind::Log4:
case FunctionType::Location::ECRecover: case FunctionType::Kind::ECRecover:
case FunctionType::Location::SHA256: case FunctionType::Kind::SHA256:
case FunctionType::Location::RIPEMD160: case FunctionType::Kind::RIPEMD160:
default: default:
solAssert(false, "unsupported member function"); solAssert(false, "unsupported member function");
} }
@ -1372,7 +1371,7 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type
{ {
if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type)) if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type))
{ {
if (funType->location() == FunctionType::Location::Internal) if (funType->kind() == FunctionType::Kind::Internal)
{ {
// We have to remove the upper bits (construction time value) because they might // We have to remove the upper bits (construction time value) because they might
// be "unknown" in one of the operands and not in the other. // be "unknown" in one of the operands and not in the other.
@ -1555,11 +1554,10 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (_functionType.bound()) if (_functionType.bound())
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
using FunctionKind = FunctionType::Location; auto funKind = _functionType.kind();
FunctionKind funKind = _functionType.location(); bool returnSuccessCondition = funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode;
bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode;
bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool isDelegateCall = funKind == FunctionKind::BareDelegateCall || funKind == FunctionKind::DelegateCall;
unsigned retSize = 0; unsigned retSize = 0;
if (returnSuccessCondition) if (returnSuccessCondition)
@ -1576,7 +1574,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
TypePointers parameterTypes = _functionType.parameterTypes(); TypePointers parameterTypes = _functionType.parameterTypes();
bool manualFunctionId = false; bool manualFunctionId = false;
if ( if (
(funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode || funKind == FunctionKind::BareDelegateCall) && (funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) &&
!_arguments.empty() !_arguments.empty()
) )
{ {
@ -1611,7 +1609,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
argumentTypes.push_back(_arguments[i]->annotation().type); argumentTypes.push_back(_arguments[i]->annotation().type);
} }
if (funKind == FunctionKind::ECRecover) if (funKind == FunctionType::Kind::ECRecover)
{ {
// Clears 32 bytes of currently free memory and advances free memory pointer. // Clears 32 bytes of currently free memory and advances free memory pointer.
// Output area will be "start of input area" - 32. // Output area will be "start of input area" - 32.
@ -1667,7 +1665,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
// put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input> // put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input>
m_context << u256(retSize); m_context << u256(retSize);
utils().fetchFreeMemoryPointer(); // This is the start of input utils().fetchFreeMemoryPointer(); // This is the start of input
if (funKind == FunctionKind::ECRecover) if (funKind == FunctionType::Kind::ECRecover)
{ {
// In this case, output is 32 bytes before input and has already been cleared. // In this case, output is 32 bytes before input and has already been cleared.
m_context << u256(32) << Instruction::DUP2 << Instruction::SUB << Instruction::SWAP1; m_context << u256(32) << Instruction::DUP2 << Instruction::SUB << Instruction::SWAP1;
@ -1693,7 +1691,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
bool existenceChecked = false; bool existenceChecked = false;
// Check the the target contract exists (has code) for non-low-level calls. // Check the the target contract exists (has code) for non-low-level calls.
if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall) if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{ {
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
m_context.appendConditionalInvalid(); m_context.appendConditionalInvalid();
@ -1741,14 +1739,14 @@ void ExpressionCompiler::appendExternalFunctionCall(
{ {
// already there // already there
} }
else if (funKind == FunctionKind::RIPEMD160) else if (funKind == FunctionType::Kind::RIPEMD160)
{ {
// fix: built-in contract returns right-aligned data // fix: built-in contract returns right-aligned data
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); utils().loadFromMemoryDynamic(IntegerType(160), false, true, false);
utils().convertType(IntegerType(160), FixedBytesType(20)); utils().convertType(IntegerType(160), FixedBytesType(20));
} }
else if (funKind == FunctionKind::ECRecover) else if (funKind == FunctionType::Kind::ECRecover)
{ {
// Output is 32 bytes before input / free mem pointer. // Output is 32 bytes before input / free mem pointer.
// Failing ecrecover cannot be detected, so we clear output before the call. // Failing ecrecover cannot be detected, so we clear output before the call.

View File

@ -199,7 +199,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
} }
else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType))
{ {
if (fun->location() == FunctionType::Location::External) if (fun->kind() == FunctionType::Kind::External)
{ {
CompilerUtils(m_context).splitExternalFunctionType(false); CompilerUtils(m_context).splitExternalFunctionType(false);
cleaned = true; cleaned = true;
@ -256,7 +256,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType))
{ {
solAssert(_sourceType == *m_dataType, "function item stored but target is not equal to source"); solAssert(_sourceType == *m_dataType, "function item stored but target is not equal to source");
if (fun->location() == FunctionType::Location::External) if (fun->kind() == FunctionType::Kind::External)
// Combine the two-item function type into a single stack slot. // Combine the two-item function type into a single stack slot.
utils.combineExternalFunctionType(false); utils.combineExternalFunctionType(false);
else else

View File

@ -588,14 +588,14 @@ bool Why3Translator::visit(FunctionCall const& _node)
return true; return true;
} }
FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type); FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type);
switch (function.location()) switch (function.kind())
{ {
case FunctionType::Location::AddMod: case FunctionType::Kind::AddMod:
case FunctionType::Location::MulMod: case FunctionType::Kind::MulMod:
{ {
//@todo require that third parameter is not zero //@todo require that third parameter is not zero
add("(of_int (mod (Int.("); add("(of_int (mod (Int.(");
add(function.location() == FunctionType::Location::AddMod ? "+" : "*"); add(function.kind() == FunctionType::Kind::AddMod ? "+" : "*");
add(") (to_int "); add(") (to_int ");
_node.arguments().at(0)->accept(*this); _node.arguments().at(0)->accept(*this);
add(") (to_int "); add(") (to_int ");
@ -605,7 +605,7 @@ bool Why3Translator::visit(FunctionCall const& _node)
add(")))"); add(")))");
return false; return false;
} }
case FunctionType::Location::Internal: case FunctionType::Kind::Internal:
{ {
if (!_node.names().empty()) if (!_node.names().empty())
{ {
@ -626,7 +626,7 @@ bool Why3Translator::visit(FunctionCall const& _node)
add(")"); add(")");
return false; return false;
} }
case FunctionType::Location::Bare: case FunctionType::Kind::Bare:
{ {
if (!_node.arguments().empty()) if (!_node.arguments().empty())
{ {
@ -654,7 +654,7 @@ bool Why3Translator::visit(FunctionCall const& _node)
add(")"); add(")");
return false; return false;
} }
case FunctionType::Location::SetValue: case FunctionType::Kind::SetValue:
{ {
add("let amount = "); add("let amount = ");
solAssert(_node.arguments().size() == 1, ""); solAssert(_node.arguments().size() == 1, "");

View File

@ -21,6 +21,9 @@
#include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmScopeFiller.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Utils.h> #include <libsolidity/interface/Utils.h>
@ -35,146 +38,363 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
using namespace dev::solidity::assembly; using namespace dev::solidity::assembly;
AsmAnalyzer::AsmAnalyzer(
bool Scope::registerLabel(string const& _name) AsmAnalysisInfo& _analysisInfo,
ErrorList& _errors,
ExternalIdentifierAccess::Resolver const& _resolver
):
m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors)
{ {
if (exists(_name)) }
bool AsmAnalyzer::analyze(Block const& _block)
{
if (!(ScopeFiller(m_info.scopes, m_errors))(_block))
return false; return false;
identifiers[_name] = Label();
return (*this)(_block);
}
bool AsmAnalyzer::operator()(Label const& _label)
{
m_info.stackHeightInfo[&_label] = m_stackHeight;
return true; return true;
} }
bool Scope::registerVariable(string const& _name) bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{ {
if (exists(_name)) auto const& info = instructionInfo(_instruction.instruction);
return false; m_stackHeight += info.ret - info.args;
identifiers[_name] = Variable(); m_info.stackHeightInfo[&_instruction] = m_stackHeight;
return true; return true;
} }
bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns)
{
if (exists(_name))
return false;
identifiers[_name] = Function(_arguments, _returns);
return true;
}
Scope::Identifier* Scope::lookup(string const& _name)
{
if (identifiers.count(_name))
return &identifiers[_name];
else if (superScope && !closedScope)
return superScope->lookup(_name);
else
return nullptr;
}
bool Scope::exists(string const& _name)
{
if (identifiers.count(_name))
return true;
else if (superScope)
return superScope->exists(_name);
else
return false;
}
AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::Scopes& _scopes, ErrorList& _errors):
m_scopes(_scopes), m_errors(_errors)
{
// Make the Solidity ErrorTag available to inline assembly
m_scopes[nullptr] = make_shared<Scope>();
Scope::Label errorLabel;
errorLabel.id = Scope::Label::errorLabelId;
m_scopes[nullptr]->identifiers["invalidJumpLabel"] = errorLabel;
m_currentScope = m_scopes[nullptr].get();
}
bool AsmAnalyzer::operator()(assembly::Literal const& _literal) bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
{ {
++m_stackHeight;
if (!_literal.isNumber && _literal.value.size() > 32) if (!_literal.isNumber && _literal.value.size() > 32)
{ {
m_errors.push_back(make_shared<Error>( m_errors.push_back(make_shared<Error>(
Error::Type::TypeError, Error::Type::TypeError,
"String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)" "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)",
_literal.location
)); ));
return false; return false;
} }
m_info.stackHeightInfo[&_literal] = m_stackHeight;
return true; return true;
} }
bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
size_t numErrorsBefore = m_errors.size();
bool success = true;
if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
[&](Scope::Variable const& _var)
{
if (!_var.active)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable " + _identifier.name + " used before it was declared.",
_identifier.location
));
success = false;
}
++m_stackHeight;
},
[&](Scope::Label const&)
{
++m_stackHeight;
},
[&](Scope::Function const&)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Function " + _identifier.name + " used without being called.",
_identifier.location
));
success = false;
}
)))
{
}
else
{
size_t stackSize(-1);
if (m_resolver)
stackSize = m_resolver(_identifier, IdentifierContext::RValue);
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
));
success = false;
}
m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
}
m_info.stackHeightInfo[&_identifier] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{ {
bool success = true; bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed) for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
{
int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg)) if (!boost::apply_visitor(*this, arg))
success = false; 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)) if (!(*this)(_instr.instruction))
success = false; success = false;
m_info.stackHeightInfo[&_instr] = m_stackHeight;
return success; return success;
} }
bool AsmAnalyzer::operator()(Label const& _item) bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{ {
if (!m_currentScope->registerLabel(_item.name)) bool success = checkAssignment(_assignment.variableName, size_t(-1));
{ m_info.stackHeightInfo[&_assignment] = m_stackHeight;
//@TODO secondary location return success;
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Label name " + _item.name + " already taken in this scope.",
_item.location
));
return false;
}
return true;
} }
bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment) bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
{ {
return boost::apply_visitor(*this, *_assignment.value); int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_assignment.value);
solAssert(m_stackHeight >= stackHeight, "Negative value size.");
if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight))
success = false;
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
{ {
int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_varDecl.value); bool success = boost::apply_visitor(*this, *_varDecl.value);
if (!m_currentScope->registerVariable(_varDecl.name)) solAssert(m_stackHeight - stackHeight == 1, "Invalid value size.");
{ boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true;
//@TODO secondary location m_info.stackHeightInfo[&_varDecl] = m_stackHeight;
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable name " + _varDecl.name + " already taken in this scope.",
_varDecl.location
));
success = false;
}
return success; return success;
} }
bool AsmAnalyzer::operator()(assembly::FunctionDefinition const&) bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
{ {
// TODO - we cannot throw an exception here because of some tests. Scope& bodyScope = scope(&_funDef.body);
return true; for (auto const& var: _funDef.arguments + _funDef.returns)
boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true;
int const stackHeight = m_stackHeight;
m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
m_virtualVariablesInNextBlock = m_stackHeight;
bool success = (*this)(_funDef.body);
m_stackHeight = stackHeight;
m_info.stackHeightInfo[&_funDef] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(assembly::FunctionCall const&) bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
{ {
// TODO - we cannot throw an exception here because of some tests. bool success = true;
return true; size_t arguments = 0;
size_t returns = 0;
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
));
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
));
success = false;
},
[&](Scope::Function const& _fun)
{
arguments = _fun.arguments;
returns = _fun.returns;
}
)))
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Function not found.",
_funCall.functionName.location
));
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
));
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)))
success = false;
}
m_stackHeight += int(returns) - int(arguments);
m_info.stackHeightInfo[&_funCall] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(Block const& _block) bool AsmAnalyzer::operator()(Block const& _block)
{ {
bool success = true; bool success = true;
auto scope = make_shared<Scope>(); m_currentScope = &scope(&_block);
scope->superScope = m_currentScope;
m_scopes[&_block] = scope; int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock;
m_currentScope = scope.get(); m_virtualVariablesInNextBlock = 0;
for (auto const& s: _block.statements) for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s)) if (!boost::apply_visitor(*this, s))
success = false; success = false;
for (auto const& identifier: scope(&_block).identifiers)
if (identifier.second.type() == typeid(Scope::Variable))
--m_stackHeight;
int const stackDiff = m_stackHeight - initialStackHeight;
if (stackDiff != 0)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"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_currentScope = m_currentScope->superScope;
m_info.stackHeightInfo[&_block] = m_stackHeight;
return success; return success;
} }
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
{
bool success = true;
size_t numErrorsBefore = m_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
));
success = false;
}
else if (!boost::get<Scope::Variable>(*var).active)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable " + _variable.name + " used before it was declared.",
_variable.location
));
success = false;
}
variableSize = 1;
}
else if (m_resolver)
variableSize = m_resolver(_variable, IdentifierContext::LValue);
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
));
success = false;
}
if (_valueSize == size_t(-1))
_valueSize = variableSize == size_t(-1) ? 1 : variableSize;
m_stackHeight -= _valueSize;
if (_valueSize != variableSize && variableSize != size_t(-1))
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Variable size (" +
to_string(variableSize) +
") and value size (" +
to_string(_valueSize) +
") do not match.",
_variable.location
));
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)
{
auto scopePtr = m_info.scopes.at(_block);
solAssert(scopePtr, "Scope requested but not present.");
return *scopePtr;
}

View File

@ -20,6 +20,8 @@
#pragma once #pragma once
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp> #include <boost/variant.hpp>
@ -46,101 +48,32 @@ struct Assignment;
struct FunctionDefinition; struct FunctionDefinition;
struct FunctionCall; struct FunctionCall;
template <class...> struct Scope;
struct GenericVisitor{};
template <class Visitable, class... Others>
struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
{
using GenericVisitor<Others...>::operator ();
explicit GenericVisitor(
std::function<void(Visitable&)> _visitor,
std::function<void(Others&)>... _otherVisitors
):
GenericVisitor<Others...>(_otherVisitors...),
m_visitor(_visitor)
{}
void operator()(Visitable& _v) const { m_visitor(_v); }
std::function<void(Visitable&)> m_visitor;
};
template <>
struct GenericVisitor<>: public boost::static_visitor<> {
void operator()() const {}
};
struct Scope
{
struct Variable
{
int stackHeight = 0;
bool active = false;
};
struct Label
{
size_t id = unassignedLabelId;
static const size_t errorLabelId = -1;
static const size_t unassignedLabelId = 0;
};
struct Function
{
Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {}
size_t arguments = 0;
size_t returns = 0;
};
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 registerLabel(std::string const& _name);
bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns);
/// Looks up the identifier in this or super scopes (stops and function and assembly boundaries)
/// and returns a valid pointer if found or a nullptr if not found.
/// The pointer will be invalidated if the scope is modified.
Identifier* lookup(std::string const& _name);
/// Looks up the identifier in this and super scopes (stops and function and assembly boundaries)
/// and calls the visitor, returns false if not found.
template <class V>
bool lookup(std::string const& _name, V const& _visitor)
{
if (Identifier* id = lookup(_name))
{
boost::apply_visitor(_visitor, *id);
return true;
}
else
return false;
}
/// @returns true if the name exists in this scope or in super scopes (also searches
/// across function and assembly boundaries).
bool exists(std::string const& _name);
Scope* superScope = nullptr;
/// If true, identifiers from the super scope are not visible here, but they are still
/// taken into account to prevent shadowing.
bool closedScope = false;
std::map<std::string, Identifier> identifiers;
};
struct AsmAnalysisInfo;
/**
* Performs the full analysis stage, calls the ScopeFiller internally, then resolves
* references and performs other checks.
* If all these checks pass, code generation should not throw errors.
*/
class AsmAnalyzer: public boost::static_visitor<bool> class AsmAnalyzer: public boost::static_visitor<bool>
{ {
public: public:
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; AsmAnalyzer(
AsmAnalyzer(Scopes& _scopes, ErrorList& _errors); AsmAnalysisInfo& _analysisInfo,
ErrorList& _errors,
ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver()
);
bool operator()(assembly::Instruction const&) { return true; } bool analyze(assembly::Block const& _block);
bool operator()(assembly::Instruction const&);
bool operator()(assembly::Literal const& _literal); bool operator()(assembly::Literal const& _literal);
bool operator()(assembly::Identifier const&) { return true; } bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
bool operator()(assembly::Label const& _label); bool operator()(assembly::Label const& _label);
bool operator()(assembly::Assignment const&) { return true; } bool operator()(assembly::Assignment const&);
bool operator()(assembly::FunctionalAssignment const& _functionalAssignment); bool operator()(assembly::FunctionalAssignment const& _functionalAssignment);
bool operator()(assembly::VariableDeclaration const& _variableDeclaration); bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition); bool operator()(assembly::FunctionDefinition const& _functionDefinition);
@ -148,8 +81,20 @@ public:
bool operator()(assembly::Block const& _block); bool operator()(assembly::Block const& _block);
private: private:
/// 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);
/// 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;
Scope* m_currentScope = nullptr; Scope* m_currentScope = nullptr;
Scopes& m_scopes; AsmAnalysisInfo& m_info;
ErrorList& m_errors; ErrorList& m_errors;
}; };

View File

@ -0,0 +1,26 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Information generated during analyzer part of inline assembly.
*/
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <ostream>

View File

@ -0,0 +1,61 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Information generated during analyzer part of inline assembly.
*/
#pragma once
#include <boost/variant.hpp>
#include <map>
#include <memory>
namespace dev
{
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;
};
}
}
}

View File

@ -24,7 +24,9 @@
#include <libsolidity/inlineasm/AsmParser.h> #include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libevmasm/Assembly.h> #include <libevmasm/Assembly.h>
#include <libevmasm/SourceLocation.h> #include <libevmasm/SourceLocation.h>
@ -46,13 +48,8 @@ using namespace dev::solidity::assembly;
struct GeneratorState struct GeneratorState
{ {
GeneratorState(ErrorList& _errors, eth::Assembly& _assembly): GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly):
errors(_errors), assembly(_assembly) {} errors(_errors), info(_analysisInfo), assembly(_assembly) {}
void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation())
{
errors.push_back(make_shared<Error>(_type, _description, _location));
}
size_t newLabelId() size_t newLabelId()
{ {
@ -66,8 +63,8 @@ struct GeneratorState
return size_t(id); return size_t(id);
} }
std::map<assembly::Block const*, shared_ptr<Scope>> scopes;
ErrorList& errors; ErrorList& errors;
AsmAnalysisInfo info;
eth::Assembly& assembly; eth::Assembly& assembly;
}; };
@ -80,13 +77,24 @@ public:
explicit CodeTransform( explicit CodeTransform(
GeneratorState& _state, GeneratorState& _state,
assembly::Block const& _block, assembly::Block const& _block,
assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess() assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess()
): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit())
{
}
private:
CodeTransform(
GeneratorState& _state,
assembly::Block const& _block,
assembly::ExternalIdentifierAccess const& _identifierAccess,
int _initialDeposit
): ):
m_state(_state), m_state(_state),
m_scope(*m_state.scopes.at(&_block)), m_scope(*m_state.info.scopes.at(&_block)),
m_initialDeposit(m_state.assembly.deposit()), m_identifierAccess(_identifierAccess),
m_identifierAccess(_identifierAccess) m_initialDeposit(_initialDeposit)
{ {
int blockStartDeposit = m_state.assembly.deposit();
std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this));
m_state.assembly.setSourceLocation(_block.location); m_state.assembly.setSourceLocation(_block.location);
@ -96,31 +104,16 @@ public:
if (identifier.second.type() == typeid(Scope::Variable)) if (identifier.second.type() == typeid(Scope::Variable))
m_state.assembly.append(solidity::Instruction::POP); m_state.assembly.append(solidity::Instruction::POP);
int deposit = m_state.assembly.deposit() - m_initialDeposit; int deposit = m_state.assembly.deposit() - blockStartDeposit;
solAssert(deposit == 0, "Invalid stack height at end of block.");
// issue warnings for stack height discrepancies
if (deposit < 0)
{
m_state.addError(
Error::Type::Warning,
"Inline assembly block is not balanced. It takes " + toString(-deposit) + " item(s) from the stack.",
_block.location
);
}
else if (deposit > 0)
{
m_state.addError(
Error::Type::Warning,
"Inline assembly block is not balanced. It leaves " + toString(deposit) + " item(s) on the stack.",
_block.location
);
}
} }
public:
void operator()(assembly::Instruction const& _instruction) void operator()(assembly::Instruction const& _instruction)
{ {
m_state.assembly.setSourceLocation(_instruction.location); m_state.assembly.setSourceLocation(_instruction.location);
m_state.assembly.append(_instruction.instruction); m_state.assembly.append(_instruction.instruction);
checkStackHeight(&_instruction);
} }
void operator()(assembly::Literal const& _literal) void operator()(assembly::Literal const& _literal)
{ {
@ -130,8 +123,9 @@ public:
else else
{ {
solAssert(_literal.value.size() <= 32, ""); solAssert(_literal.value.size() <= 32, "");
m_state.assembly.append(_literal.value); m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
} }
checkStackHeight(&_literal);
} }
void operator()(assembly::Identifier const& _identifier) void operator()(assembly::Identifier const& _identifier)
{ {
@ -153,20 +147,18 @@ public:
}, },
[=](Scope::Function&) [=](Scope::Function&)
{ {
solAssert(false, "Not yet implemented"); solAssert(false, "Function not removed during desugaring.");
} }
))) )))
{ {
return;
} }
else if (!m_identifierAccess || !m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue)) solAssert(
{ m_identifierAccess.generateCode,
m_state.addError( "Identifier not found and no external access available."
Error::Type::DeclarationError,
"Identifier not found or not unique",
_identifier.location
); );
m_state.assembly.append(u256(0)); m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly);
} checkStackHeight(&_identifier);
} }
void operator()(FunctionalInstruction const& _instr) void operator()(FunctionalInstruction const& _instr)
{ {
@ -174,9 +166,10 @@ public:
{ {
int height = m_state.assembly.deposit(); int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *it); boost::apply_visitor(*this, *it);
expectDeposit(1, height, locationOf(*it)); expectDeposit(1, height);
} }
(*this)(_instr.instruction); (*this)(_instr.instruction);
checkStackHeight(&_instr);
} }
void operator()(assembly::FunctionCall const&) void operator()(assembly::FunctionCall const&)
{ {
@ -186,36 +179,39 @@ public:
{ {
m_state.assembly.setSourceLocation(_label.location); m_state.assembly.setSourceLocation(_label.location);
solAssert(m_scope.identifiers.count(_label.name), ""); solAssert(m_scope.identifiers.count(_label.name), "");
Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers[_label.name]); Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name));
assignLabelIdIfUnset(label); assignLabelIdIfUnset(label);
m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id)); m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id));
checkStackHeight(&_label);
} }
void operator()(assembly::Assignment const& _assignment) void operator()(assembly::Assignment const& _assignment)
{ {
m_state.assembly.setSourceLocation(_assignment.location); m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location); generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
} }
void operator()(FunctionalAssignment const& _assignment) void operator()(FunctionalAssignment const& _assignment)
{ {
int height = m_state.assembly.deposit(); int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_assignment.value); boost::apply_visitor(*this, *_assignment.value);
expectDeposit(1, height, locationOf(*_assignment.value)); expectDeposit(1, height);
m_state.assembly.setSourceLocation(_assignment.location); m_state.assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName, _assignment.location); generateAssignment(_assignment.variableName, _assignment.location);
checkStackHeight(&_assignment);
} }
void operator()(assembly::VariableDeclaration const& _varDecl) void operator()(assembly::VariableDeclaration const& _varDecl)
{ {
int height = m_state.assembly.deposit(); int height = m_state.assembly.deposit();
boost::apply_visitor(*this, *_varDecl.value); boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(1, height, locationOf(*_varDecl.value)); expectDeposit(1, height);
solAssert(m_scope.identifiers.count(_varDecl.name), ""); auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name));
auto& var = boost::get<Scope::Variable>(m_scope.identifiers[_varDecl.name]);
var.stackHeight = height; var.stackHeight = height;
var.active = true; var.active = true;
} }
void operator()(assembly::Block const& _block) void operator()(assembly::Block const& _block)
{ {
CodeTransform(m_state, _block, m_identifierAccess); CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit);
checkStackHeight(&_block);
} }
void operator()(assembly::FunctionDefinition const&) void operator()(assembly::FunctionDefinition const&)
{ {
@ -225,35 +221,22 @@ public:
private: private:
void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location) void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location)
{ {
if (m_scope.lookup(_variableName.name, Scope::Visitor( auto var = m_scope.lookup(_variableName.name);
[=](Scope::Variable const& _var) if (var)
{ {
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
if (int heightDiff = variableHeightDiff(_var, _location, true)) if (int heightDiff = variableHeightDiff(_var, _location, true))
m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); m_state.assembly.append(solidity::swapInstruction(heightDiff - 1));
m_state.assembly.append(solidity::Instruction::POP); m_state.assembly.append(solidity::Instruction::POP);
},
[=](Scope::Label const&)
{
m_state.addError(
Error::Type::DeclarationError,
"Label \"" + string(_variableName.name) + "\" used as variable."
);
},
[=](Scope::Function const&)
{
m_state.addError(
Error::Type::DeclarationError,
"Function \"" + string(_variableName.name) + "\" used as variable."
);
} }
))) else
{ {
} solAssert(
else if (!m_identifierAccess || !m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue)) m_identifierAccess.generateCode,
m_state.addError( "Identifier not found and no external access available."
Error::Type::DeclarationError,
"Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue."
); );
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly);
}
} }
/// Determines the stack height difference to the given variables. Automatically generates /// Determines the stack height difference to the given variables. Automatically generates
@ -261,35 +244,32 @@ private:
/// errors and the (positive) stack height difference otherwise. /// errors and the (positive) stack height difference otherwise.
int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap) int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap)
{ {
if (!_var.active)
{
m_state.addError( Error::Type::TypeError, "Variable used before it was declared", _location);
return 0;
}
int heightDiff = m_state.assembly.deposit() - _var.stackHeight; int heightDiff = m_state.assembly.deposit() - _var.stackHeight;
if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16)) if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
{ {
m_state.addError( //@TODO move this to analysis phase.
m_state.errors.push_back(make_shared<Error>(
Error::Type::TypeError, Error::Type::TypeError,
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")", "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")",
_location _location
); ));
return 0; return 0;
} }
else else
return heightDiff; return heightDiff;
} }
void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location) void expectDeposit(int _deposit, int _oldHeight)
{ {
if (m_state.assembly.deposit() != _oldHeight + 1) solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit.");
m_state.addError(Error::Type::TypeError, }
"Expected instruction(s) to deposit " +
boost::lexical_cast<string>(_deposit) + void checkStackHeight(void const* _astElement)
" item(s) to the stack, but did deposit " + {
boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) + solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
" item(s).", solAssert(
_location m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit,
"Stack height mismatch between analysis and code generation phase."
); );
} }
@ -305,35 +285,29 @@ private:
GeneratorState& m_state; GeneratorState& m_state;
Scope& m_scope; Scope& m_scope;
ExternalIdentifierAccess m_identifierAccess;
int const m_initialDeposit; int const m_initialDeposit;
assembly::CodeGenerator::IdentifierAccess m_identifierAccess;
}; };
bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) eth::Assembly assembly::CodeGenerator::assemble(
{ Block const& _parsedData,
size_t initialErrorLen = m_errors.size(); AsmAnalysisInfo& _analysisInfo,
eth::Assembly assembly; ExternalIdentifierAccess const& _identifierAccess
GeneratorState state(m_errors, assembly); )
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData))
return false;
CodeTransform(state, m_parsedData, _identifierAccess);
return m_errors.size() == initialErrorLen;
}
eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess)
{ {
eth::Assembly assembly; eth::Assembly assembly;
GeneratorState state(m_errors, assembly); GeneratorState state(m_errors, _analysisInfo, assembly);
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) CodeTransform(state, _parsedData, _identifierAccess);
solAssert(false, "Assembly error");
CodeTransform(state, m_parsedData, _identifierAccess);
return assembly; return assembly;
} }
void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) void assembly::CodeGenerator::assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
eth::Assembly& _assembly,
ExternalIdentifierAccess const& _identifierAccess
)
{ {
GeneratorState state(m_errors, _assembly); GeneratorState state(m_errors, _analysisInfo, _assembly);
if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) CodeTransform(state, _parsedData, _identifierAccess);
solAssert(false, "Assembly error");
CodeTransform(state, m_parsedData, _identifierAccess);
} }

View File

@ -22,9 +22,11 @@
#pragma once #pragma once
#include <functional> #include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <functional>
namespace dev namespace dev
{ {
namespace eth namespace eth
@ -36,30 +38,27 @@ namespace solidity
namespace assembly namespace assembly
{ {
struct Block; struct Block;
struct Identifier;
class CodeGenerator class CodeGenerator
{ {
public: public:
enum class IdentifierContext { LValue, RValue }; CodeGenerator(ErrorList& _errors):
/// Function type that is called for external identifiers. Such a function should search for m_errors(_errors) {}
/// the identifier and append appropriate assembly items to the assembly. If in lvalue context,
/// the value to assign is assumed to be on the stack and an assignment is to be performed.
/// If in rvalue context, the function is assumed to append instructions to
/// push the value of the identifier onto the stack. On error, the function should return false.
using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>;
CodeGenerator(Block const& _parsedData, ErrorList& _errors):
m_parsedData(_parsedData), m_errors(_errors) {}
/// Performs type checks and @returns false on error.
/// Actually runs the full code generation but discards the result.
bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess());
/// Performs code generation and @returns the result. /// Performs code generation and @returns the result.
eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess()); eth::Assembly assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
);
/// Performs code generation and appends generated to to _assembly. /// Performs code generation and appends generated to to _assembly.
void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess()); void assemble(
Block const& _parsedData,
AsmAnalysisInfo& _analysisInfo,
eth::Assembly& _assembly,
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
);
private: private:
Block const& m_parsedData;
ErrorList& m_errors; ErrorList& m_errors;
}; };

View File

@ -24,6 +24,7 @@
#include <ctype.h> #include <ctype.h>
#include <algorithm> #include <algorithm>
#include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Scanner.h>
#include <libsolidity/interface/Exceptions.h>
using namespace std; using namespace std;
using namespace dev; using namespace dev;
@ -68,12 +69,14 @@ assembly::Statement Parser::parseStatement()
return parseBlock(); return parseBlock();
case Token::Assign: case Token::Assign:
{ {
if (m_julia)
break;
assembly::Assignment assignment = createWithLocation<assembly::Assignment>(); assembly::Assignment assignment = createWithLocation<assembly::Assignment>();
m_scanner->next(); m_scanner->next();
expectToken(Token::Colon); expectToken(Token::Colon);
assignment.variableName.location = location(); assignment.variableName.location = location();
assignment.variableName.name = m_scanner->currentLiteral(); assignment.variableName.name = m_scanner->currentLiteral();
if (instructions().count(assignment.variableName.name)) if (!m_julia && instructions().count(assignment.variableName.name))
fatalParserError("Identifier expected, got instruction name."); fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition(); assignment.location.end = endPosition();
expectToken(Token::Identifier); expectToken(Token::Identifier);
@ -105,7 +108,7 @@ assembly::Statement Parser::parseStatement()
{ {
// functional assignment // functional assignment
FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location); FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location);
if (instructions().count(identifier.name)) if (!m_julia && instructions().count(identifier.name))
fatalParserError("Cannot use instruction names for identifier names."); fatalParserError("Cannot use instruction names for identifier names.");
m_scanner->next(); m_scanner->next();
funAss.variableName = identifier; funAss.variableName = identifier;
@ -180,7 +183,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
else else
literal = m_scanner->currentLiteral(); literal = m_scanner->currentLiteral();
// first search the set of instructions. // first search the set of instructions.
if (instructions().count(literal)) if (!m_julia && instructions().count(literal))
{ {
dev::solidity::Instruction const& instr = instructions().at(literal); dev::solidity::Instruction const& instr = instructions().at(literal);
if (_onlySinglePusher) if (_onlySinglePusher)
@ -242,15 +245,13 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
{ {
expectToken(Token::Sub); expectToken(Token::Sub);
expectToken(Token::GreaterThan); expectToken(Token::GreaterThan);
expectToken(Token::LParen);
while (true) while (true)
{ {
funDef.returns.push_back(expectAsmIdentifier()); funDef.returns.push_back(expectAsmIdentifier());
if (m_scanner->currentToken() == Token::RParen) if (m_scanner->currentToken() == Token::LBrace)
break; break;
expectToken(Token::Comma); expectToken(Token::Comma);
} }
expectToken(Token::RParen);
} }
funDef.body = parseBlock(); funDef.body = parseBlock();
funDef.location.end = funDef.body.location.end; funDef.location.end = funDef.body.location.end;
@ -261,6 +262,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in
{ {
if (_instruction.type() == typeid(Instruction)) if (_instruction.type() == typeid(Instruction))
{ {
solAssert(!m_julia, "Instructions are invalid in JULIA");
FunctionalInstruction ret; FunctionalInstruction ret;
ret.instruction = std::move(boost::get<Instruction>(_instruction)); ret.instruction = std::move(boost::get<Instruction>(_instruction));
ret.location = ret.instruction.location; ret.location = ret.instruction.location;
@ -323,7 +325,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in
string Parser::expectAsmIdentifier() string Parser::expectAsmIdentifier()
{ {
string name = m_scanner->currentLiteral(); string name = m_scanner->currentLiteral();
if (instructions().count(name)) if (!m_julia && instructions().count(name))
fatalParserError("Cannot use instruction names for identifier names."); fatalParserError("Cannot use instruction names for identifier names.");
expectToken(Token::Identifier); expectToken(Token::Identifier);
return name; return name;

View File

@ -37,7 +37,7 @@ namespace assembly
class Parser: public ParserBase class Parser: public ParserBase
{ {
public: public:
Parser(ErrorList& _errors): ParserBase(_errors) {} explicit Parser(ErrorList& _errors, bool _julia = false): ParserBase(_errors), m_julia(_julia) {}
/// Parses an inline assembly block starting with `{` and ending with `}`. /// Parses an inline assembly block starting with `{` and ending with `}`.
/// @returns an empty shared pointer on error. /// @returns an empty shared pointer on error.
@ -70,6 +70,9 @@ protected:
FunctionDefinition parseFunctionDefinition(); FunctionDefinition parseFunctionDefinition();
Statement parseFunctionalInstruction(Statement&& _instruction); Statement parseFunctionalInstruction(Statement&& _instruction);
std::string expectAsmIdentifier(); std::string expectAsmIdentifier();
private:
bool m_julia = false;
}; };
} }

View File

@ -116,7 +116,7 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin
{ {
string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")"; string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")";
if (!_functionDefinition.returns.empty()) if (!_functionDefinition.returns.empty())
out += " -> (" + boost::algorithm::join(_functionDefinition.returns, ", ") + ")"; out += " -> " + boost::algorithm::join(_functionDefinition.returns, ", ");
return out + "\n" + (*this)(_functionDefinition.body); return out + "\n" + (*this)(_functionDefinition.body);
} }

View File

@ -0,0 +1,79 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Scopes for identifiers.
*/
#include <libsolidity/inlineasm/AsmScope.h>
using namespace std;
using namespace dev::solidity::assembly;
bool Scope::registerLabel(string const& _name)
{
if (exists(_name))
return false;
identifiers[_name] = Label();
return true;
}
bool Scope::registerVariable(string const& _name)
{
if (exists(_name))
return false;
identifiers[_name] = Variable();
return true;
}
bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns)
{
if (exists(_name))
return false;
identifiers[_name] = Function(_arguments, _returns);
return true;
}
Scope::Identifier* Scope::lookup(string const& _name)
{
bool crossedFunctionBoundary = false;
for (Scope* s = this; s; s = s->superScope)
{
auto id = s->identifiers.find(_name);
if (id != s->identifiers.end())
{
if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable))
return nullptr;
else
return &id->second;
}
if (s->functionScope)
crossedFunctionBoundary = true;
}
return nullptr;
}
bool Scope::exists(string const& _name)
{
if (identifiers.count(_name))
return true;
else if (superScope)
return superScope->exists(_name);
else
return false;
}

View File

@ -0,0 +1,128 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Scopes for identifiers.
*/
#pragma once
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
#include <functional>
#include <memory>
namespace dev
{
namespace solidity
{
namespace assembly
{
template <class...>
struct GenericVisitor{};
template <class Visitable, class... Others>
struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...>
{
using GenericVisitor<Others...>::operator ();
explicit GenericVisitor(
std::function<void(Visitable&)> _visitor,
std::function<void(Others&)>... _otherVisitors
):
GenericVisitor<Others...>(_otherVisitors...),
m_visitor(_visitor)
{}
void operator()(Visitable& _v) const { m_visitor(_v); }
std::function<void(Visitable&)> m_visitor;
};
template <>
struct GenericVisitor<>: public boost::static_visitor<> {
void operator()() const {}
};
struct Scope
{
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;
};
struct Function
{
Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {}
size_t arguments = 0;
size_t returns = 0;
};
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 registerLabel(std::string const& _name);
bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns);
/// Looks up the identifier in this or super scopes and returns a valid pointer if found
/// or a nullptr if not found. Variable lookups up across function boundaries will fail, as
/// will any lookups across assembly boundaries.
/// The pointer will be invalidated if the scope is modified.
/// @param _crossedFunction if true, we already crossed a function boundary during recursive lookup
Identifier* lookup(std::string const& _name);
/// Looks up the identifier in this and super scopes (will not find variables across function
/// boundaries and generally stops at assembly boundaries) and calls the visitor, returns
/// false if not found.
template <class V>
bool lookup(std::string const& _name, V const& _visitor)
{
if (Identifier* id = lookup(_name))
{
boost::apply_visitor(_visitor, *id);
return true;
}
else
return false;
}
/// @returns true if the name exists in this scope or in super scopes (also searches
/// across function and assembly boundaries).
bool exists(std::string const& _name);
Scope* superScope = nullptr;
/// If true, variables from the super scope are not visible here (other identifiers are),
/// but they are still taken into account to prevent shadowing.
bool functionScope = false;
std::map<std::string, Identifier> identifiers;
};
}
}
}

View File

@ -0,0 +1,130 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Module responsible for registering identifiers inside their scopes.
*/
#include <libsolidity/inlineasm/AsmScopeFiller.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Utils.h>
#include <boost/range/adaptor/reversed.hpp>
#include <memory>
#include <functional>
using namespace std;
using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
ScopeFiller::ScopeFiller(ScopeFiller::Scopes& _scopes, ErrorList& _errors):
m_scopes(_scopes), m_errors(_errors)
{
// 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);
}
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
));
return false;
}
return true;
}
bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
{
return registerVariable(_varDecl.name, _varDecl.location, *m_currentScope);
}
bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
{
bool success = true;
if (!m_currentScope->registerFunction(_funDef.name, _funDef.arguments.size(), _funDef.returns.size()))
{
//@TODO secondary location
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Function name " + _funDef.name + " already taken in this scope.",
_funDef.location
));
success = false;
}
Scope& body = scope(&_funDef.body);
body.superScope = m_currentScope;
body.functionScope = true;
for (auto const& var: _funDef.arguments + _funDef.returns)
if (!registerVariable(var, _funDef.location, body))
success = false;
if (!(*this)(_funDef.body))
success = false;
return success;
}
bool ScopeFiller::operator()(Block const& _block)
{
bool success = true;
scope(&_block).superScope = m_currentScope;
m_currentScope = &scope(&_block);
for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s))
success = false;
m_currentScope = m_currentScope->superScope;
return success;
}
bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope)
{
if (!_scope.registerVariable(_name))
{
//@TODO secondary location
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable name " + _name + " already taken in this scope.",
_location
));
return false;
}
return true;
}
Scope& ScopeFiller::scope(Block const* _block)
{
auto& scope = m_scopes[_block];
if (!scope)
scope = make_shared<Scope>();
return *scope;
}

View File

@ -0,0 +1,89 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Module responsible for registering identifiers inside their scopes.
*/
#pragma once
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
#include <functional>
#include <memory>
namespace dev
{
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;
/**
* Fills scopes with identifiers and checks for name clashes.
* Does not resolve references.
*/
class ScopeFiller: public boost::static_visitor<bool>
{
public:
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
ScopeFiller(Scopes& _scopes, ErrorList& _errors);
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::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::Block const& _block);
private:
bool registerVariable(
std::string const& _name,
SourceLocation const& _location,
Scope& _scope
);
Scope& scope(assembly::Block const* _block);
Scope* m_currentScope = nullptr;
Scopes& m_scopes;
ErrorList& m_errors;
};
}
}
}

View File

@ -26,6 +26,7 @@
#include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/inlineasm/AsmPrinter.h> #include <libsolidity/inlineasm/AsmPrinter.h>
#include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Scanner.h>
@ -39,7 +40,10 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
using namespace dev::solidity::assembly; using namespace dev::solidity::assembly;
bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner) bool InlineAssemblyStack::parse(
shared_ptr<Scanner> const& _scanner,
ExternalIdentifierAccess::Resolver const& _resolver
)
{ {
m_parserResult = make_shared<Block>(); m_parserResult = make_shared<Block>();
Parser parser(m_errors); Parser parser(m_errors);
@ -48,8 +52,8 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner)
return false; return false;
*m_parserResult = std::move(*result); *m_parserResult = std::move(*result);
AsmAnalyzer::Scopes scopes; AsmAnalysisInfo analysisInfo;
return (AsmAnalyzer(scopes, m_errors))(*m_parserResult); return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult);
} }
string InlineAssemblyStack::toString() string InlineAssemblyStack::toString()
@ -59,14 +63,17 @@ string InlineAssemblyStack::toString()
eth::Assembly InlineAssemblyStack::assemble() eth::Assembly InlineAssemblyStack::assemble()
{ {
CodeGenerator codeGen(*m_parserResult, m_errors); AsmAnalysisInfo analysisInfo;
return codeGen.assemble(); AsmAnalyzer analyzer(analysisInfo, m_errors);
solAssert(analyzer.analyze(*m_parserResult), "");
CodeGenerator codeGen(m_errors);
return codeGen.assemble(*m_parserResult, analysisInfo);
} }
bool InlineAssemblyStack::parseAndAssemble( bool InlineAssemblyStack::parseAndAssemble(
string const& _input, string const& _input,
eth::Assembly& _assembly, eth::Assembly& _assembly,
CodeGenerator::IdentifierAccess const& _identifierAccess ExternalIdentifierAccess const& _identifierAccess
) )
{ {
ErrorList errors; ErrorList errors;
@ -74,8 +81,12 @@ bool InlineAssemblyStack::parseAndAssemble(
auto parserResult = Parser(errors).parse(scanner); auto parserResult = Parser(errors).parse(scanner);
if (!errors.empty()) if (!errors.empty())
return false; return false;
solAssert(parserResult, "");
CodeGenerator(*parserResult, errors).assemble(_assembly, _identifierAccess); 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 // At this point, the assembly might be messed up, but we should throw an
// internal compiler error anyway. // internal compiler error anyway.

View File

@ -22,10 +22,10 @@
#pragma once #pragma once
#include <libsolidity/interface/Exceptions.h>
#include <string> #include <string>
#include <functional> #include <functional>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
namespace dev namespace dev
{ {
@ -39,13 +39,34 @@ class Scanner;
namespace assembly namespace assembly
{ {
struct Block; 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 class InlineAssemblyStack
{ {
public: public:
/// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`. /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`.
/// @return false or error. /// @return false or error.
bool parse(std::shared_ptr<Scanner> const& _scanner); 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 /// 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). /// as the source form, but it should parse into the same parsed form again).
std::string toString(); std::string toString();
@ -56,7 +77,7 @@ public:
bool parseAndAssemble( bool parseAndAssemble(
std::string const& _input, std::string const& _input,
eth::Assembly& _assembly, eth::Assembly& _assembly,
CodeGenerator::IdentifierAccess const& _identifierAccess = CodeGenerator::IdentifierAccess() ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
); );
ErrorList const& errors() const { return m_errors; } ErrorList const& errors() const { return m_errors; }

View File

@ -38,6 +38,7 @@
#include <libsolidity/analysis/SyntaxChecker.h> #include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/codegen/Compiler.h> #include <libsolidity/codegen/Compiler.h>
#include <libsolidity/interface/InterfaceHandler.h> #include <libsolidity/interface/InterfaceHandler.h>
#include <libsolidity/interface/GasEstimator.h>
#include <libsolidity/formal/Why3Translator.h> #include <libsolidity/formal/Why3Translator.h>
#include <libevmasm/Exceptions.h> #include <libevmasm/Exceptions.h>
@ -55,8 +56,8 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
CompilerStack::CompilerStack(ReadFileCallback const& _readFile): CompilerStack::CompilerStack(ReadFile::Callback const& _readFile):
m_readFile(_readFile), m_parseSuccessful(false) {} m_readFile(_readFile) {}
void CompilerStack::setRemappings(vector<string> const& _remappings) void CompilerStack::setRemappings(vector<string> const& _remappings)
{ {
@ -78,10 +79,12 @@ void CompilerStack::setRemappings(vector<string> const& _remappings)
void CompilerStack::reset(bool _keepSources) void CompilerStack::reset(bool _keepSources)
{ {
m_parseSuccessful = false;
if (_keepSources) if (_keepSources)
{
m_stackState = SourcesSet;
for (auto sourcePair: m_sources) for (auto sourcePair: m_sources)
sourcePair.second.reset(); sourcePair.second.reset();
}
else else
{ {
m_sources.clear(); m_sources.clear();
@ -93,6 +96,7 @@ void CompilerStack::reset(bool _keepSources)
m_sourceOrder.clear(); m_sourceOrder.clear();
m_contracts.clear(); m_contracts.clear();
m_errors.clear(); m_errors.clear();
m_stackState = Empty;
} }
bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary) bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary)
@ -101,6 +105,7 @@ bool CompilerStack::addSource(string const& _name, string const& _content, bool
reset(true); reset(true);
m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name); m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name);
m_sources[_name].isLibrary = _isLibrary; m_sources[_name].isLibrary = _isLibrary;
m_stackState = SourcesSet;
return existed; return existed;
} }
@ -113,9 +118,10 @@ void CompilerStack::setSource(string const& _sourceCode)
bool CompilerStack::parse() bool CompilerStack::parse()
{ {
//reset //reset
if(m_stackState != SourcesSet)
return false;
m_errors.clear(); m_errors.clear();
ASTNode::resetID(); ASTNode::resetID();
m_parseSuccessful = false;
if (SemVerVersion{string(VersionString)}.isPrerelease()) if (SemVerVersion{string(VersionString)}.isPrerelease())
{ {
@ -127,14 +133,12 @@ bool CompilerStack::parse()
vector<string> sourcesToParse; vector<string> sourcesToParse;
for (auto const& s: m_sources) for (auto const& s: m_sources)
sourcesToParse.push_back(s.first); sourcesToParse.push_back(s.first);
map<string, SourceUnit const*> sourceUnitsByName;
for (size_t i = 0; i < sourcesToParse.size(); ++i) for (size_t i = 0; i < sourcesToParse.size(); ++i)
{ {
string const& path = sourcesToParse[i]; string const& path = sourcesToParse[i];
Source& source = m_sources[path]; Source& source = m_sources[path];
source.scanner->reset(); source.scanner->reset();
source.ast = Parser(m_errors).parse(source.scanner); source.ast = Parser(m_errors).parse(source.scanner);
sourceUnitsByName[path] = source.ast.get();
if (!source.ast) if (!source.ast)
solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error."); solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error.");
else else
@ -149,10 +153,19 @@ bool CompilerStack::parse()
} }
} }
} }
if (!Error::containsOnlyWarnings(m_errors)) if (Error::containsOnlyWarnings(m_errors))
// errors while parsing. should stop before type checking {
m_stackState = ParsingSuccessful;
return true;
}
else
return false; return false;
}
bool CompilerStack::analyze()
{
if (m_stackState != ParsingSuccessful)
return false;
resolveImports(); resolveImports();
bool noErrors = true; bool noErrors = true;
@ -172,6 +185,9 @@ bool CompilerStack::parse()
if (!resolver.registerDeclarations(*source->ast)) if (!resolver.registerDeclarations(*source->ast))
return false; return false;
map<string, SourceUnit const*> sourceUnitsByName;
for (auto& source: m_sources)
sourceUnitsByName[source.first] = source.second.ast.get();
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
if (!resolver.performImports(*source->ast, sourceUnitsByName)) if (!resolver.performImports(*source->ast, sourceUnitsByName))
return false; return false;
@ -234,8 +250,13 @@ bool CompilerStack::parse()
noErrors = false; noErrors = false;
} }
m_parseSuccessful = noErrors; if (noErrors)
return m_parseSuccessful; {
m_stackState = AnalysisSuccessful;
return true;
}
else
return false;
} }
bool CompilerStack::parse(string const& _sourceCode) bool CompilerStack::parse(string const& _sourceCode)
@ -244,9 +265,20 @@ bool CompilerStack::parse(string const& _sourceCode)
return parse(); return parse();
} }
bool CompilerStack::parseAndAnalyze()
{
return parse() && analyze();
}
bool CompilerStack::parseAndAnalyze(std::string const& _sourceCode)
{
setSource(_sourceCode);
return parseAndAnalyze();
}
vector<string> CompilerStack::contractNames() const vector<string> CompilerStack::contractNames() const
{ {
if (!m_parseSuccessful) if (m_stackState < AnalysisSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
vector<string> contractNames; vector<string> contractNames;
for (auto const& contract: m_contracts) for (auto const& contract: m_contracts)
@ -257,8 +289,8 @@ vector<string> CompilerStack::contractNames() const
bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> const& _libraries) bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> const& _libraries)
{ {
if (!m_parseSuccessful) if (m_stackState < AnalysisSuccessful)
if (!parse()) if (!parseAndAnalyze())
return false; return false;
m_optimize = _optimize; m_optimize = _optimize;
@ -271,12 +303,13 @@ bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> co
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
compileContract(*contract, compiledContracts); compileContract(*contract, compiledContracts);
this->link(); this->link();
m_stackState = CompilationSuccessful;
return true; return true;
} }
bool CompilerStack::compile(string const& _sourceCode, bool _optimize, unsigned _runs) bool CompilerStack::compile(string const& _sourceCode, bool _optimize, unsigned _runs)
{ {
return parse(_sourceCode) && compile(_optimize, _runs); return parseAndAnalyze(_sourceCode) && compile(_optimize, _runs);
} }
void CompilerStack::link() void CompilerStack::link()
@ -405,8 +438,9 @@ vector<string> CompilerStack::sourceNames() const
map<string, unsigned> CompilerStack::sourceIndices() const map<string, unsigned> CompilerStack::sourceIndices() const
{ {
map<string, unsigned> indices; map<string, unsigned> indices;
unsigned index = 0;
for (auto const& s: m_sources) for (auto const& s: m_sources)
indices[s.first] = indices.size(); indices[s.first] = index++;
return indices; return indices;
} }
@ -417,7 +451,7 @@ Json::Value const& CompilerStack::interface(string const& _contractName) const
Json::Value const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const Json::Value const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const
{ {
if (!m_parseSuccessful) if (m_stackState < AnalysisSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
return metadata(contract(_contractName), _type); return metadata(contract(_contractName), _type);
@ -425,7 +459,7 @@ Json::Value const& CompilerStack::metadata(string const& _contractName, Document
Json::Value const& CompilerStack::metadata(Contract const& _contract, DocumentationType _type) const Json::Value const& CompilerStack::metadata(Contract const& _contract, DocumentationType _type) const
{ {
if (!m_parseSuccessful) if (m_stackState < AnalysisSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
solAssert(_contract.contract, ""); solAssert(_contract.contract, "");
@ -456,7 +490,7 @@ Json::Value const& CompilerStack::metadata(Contract const& _contract, Documentat
string const& CompilerStack::onChainMetadata(string const& _contractName) const string const& CompilerStack::onChainMetadata(string const& _contractName) const
{ {
if (!m_parseSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
return contract(_contractName).onChainMetadata; return contract(_contractName).onChainMetadata;
@ -522,18 +556,18 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
if (m_sources.count(importPath) || newSources.count(importPath)) if (m_sources.count(importPath) || newSources.count(importPath))
continue; continue;
ReadFileResult result{false, string("File not supplied initially.")}; ReadFile::Result result{false, string("File not supplied initially.")};
if (m_readFile) if (m_readFile)
result = m_readFile(importPath); result = m_readFile(importPath);
if (result.success) if (result.success)
newSources[importPath] = result.contentsOrErrorMesage; newSources[importPath] = result.contentsOrErrorMessage;
else else
{ {
auto err = make_shared<Error>(Error::Type::ParserError); auto err = make_shared<Error>(Error::Type::ParserError);
*err << *err <<
errinfo_sourceLocation(import->location()) << errinfo_sourceLocation(import->location()) <<
errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMesage); errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage);
m_errors.push_back(std::move(err)); m_errors.push_back(std::move(err));
continue; continue;
} }
@ -655,8 +689,33 @@ void CompilerStack::compileContract(
cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2);
compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata);
compiledContract.compiler = compiler; compiledContract.compiler = compiler;
try
{
compiledContract.object = compiler->assembledObject(); compiledContract.object = compiler->assembledObject();
}
catch(eth::OptimizerException const&)
{
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for bytecode"));
}
catch(eth::AssemblyException const&)
{
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for bytecode"));
}
try
{
compiledContract.runtimeObject = compiler->runtimeObject(); compiledContract.runtimeObject = compiler->runtimeObject();
}
catch(eth::OptimizerException const&)
{
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for deployed bytecode"));
}
catch(eth::AssemblyException const&)
{
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for deployed bytecode"));
}
compiledContract.onChainMetadata = onChainMetadata; compiledContract.onChainMetadata = onChainMetadata;
_compiledContracts[compiledContract.contract] = &compiler->assembly(); _compiledContracts[compiledContract.contract] = &compiler->assembly();
@ -841,3 +900,88 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con
} }
return ret; return ret;
} }
namespace
{
Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
{
if (_gas.isInfinite)
return Json::Value("infinite");
else
return Json::Value(toString(_gas.value));
}
}
Json::Value CompilerStack::gasEstimates(string const& _contractName) const
{
if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName))
return Json::Value();
using Gas = GasEstimator::GasConsumption;
Json::Value output(Json::objectValue);
if (eth::AssemblyItems const* items = assemblyItems(_contractName))
{
Gas executionGas = GasEstimator::functionalEstimation(*items);
u256 bytecodeSize(runtimeObject(_contractName).bytecode.size());
Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas;
Json::Value creation(Json::objectValue);
creation["codeDepositCost"] = gasToJson(codeDepositGas);
creation["executionCost"] = gasToJson(executionGas);
/// TODO: implement + overload to avoid the need of +=
executionGas += codeDepositGas;
creation["totalCost"] = gasToJson(executionGas);
output["creation"] = creation;
}
if (eth::AssemblyItems const* items = runtimeAssemblyItems(_contractName))
{
/// External functions
ContractDefinition const& contract = contractDefinition(_contractName);
Json::Value externalFunctions(Json::objectValue);
for (auto it: contract.interfaceFunctions())
{
string sig = it.second->externalSignature();
externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig));
}
if (contract.fallbackFunction())
/// This needs to be set to an invalid signature in order to trigger the fallback,
/// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound.
/// An empty string ("") would work to trigger the shortcut only.
externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID"));
if (!externalFunctions.empty())
output["external"] = externalFunctions;
/// Internal functions
Json::Value internalFunctions(Json::objectValue);
for (auto const& it: contract.definedFunctions())
{
/// Exclude externally visible functions, constructor and the fallback function
if (it->isPartOfExternalInterface() || it->isConstructor() || it->name().empty())
continue;
size_t entry = functionEntryPoint(_contractName, *it);
GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite();
if (entry > 0)
gas = GasEstimator::functionalEstimation(*items, entry, *it);
FunctionType type(*it);
string sig = it->name() + "(";
auto paramTypes = type.parameterTypes();
for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it)
sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ",");
sig += ")";
internalFunctions[sig] = gasToJson(gas);
}
if (!internalFunctions.empty())
output["internal"] = internalFunctions;
}
return output;
}

View File

@ -36,6 +36,7 @@
#include <libevmasm/SourceLocation.h> #include <libevmasm/SourceLocation.h>
#include <libevmasm/LinkerObject.h> #include <libevmasm/LinkerObject.h>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/ReadFile.h>
namespace dev namespace dev
{ {
@ -77,18 +78,10 @@ enum class DocumentationType: uint8_t
class CompilerStack: boost::noncopyable class CompilerStack: boost::noncopyable
{ {
public: public:
struct ReadFileResult
{
bool success;
std::string contentsOrErrorMesage;
};
/// File reading callback.
using ReadFileCallback = std::function<ReadFileResult(std::string const&)>;
/// Creates a new compiler stack. /// Creates a new compiler stack.
/// @param _readFile callback to used to read files for import statements. Should return /// @param _readFile callback to used to read files for import statements. Must return
explicit CompilerStack(ReadFileCallback const& _readFile = ReadFileCallback()); /// and must not emit exceptions.
explicit CompilerStack(ReadFile::Callback const& _readFile = ReadFile::Callback());
/// Sets path remappings in the format "context:prefix=target" /// Sets path remappings in the format "context:prefix=target"
void setRemappings(std::vector<std::string> const& _remappings); void setRemappings(std::vector<std::string> const& _remappings);
@ -110,6 +103,16 @@ public:
/// Sets the given source code as the only source unit apart from standard sources and parses it. /// Sets the given source code as the only source unit apart from standard sources and parses it.
/// @returns false on error. /// @returns false on error.
bool parse(std::string const& _sourceCode); bool parse(std::string const& _sourceCode);
/// performs the analyisis steps (imports, scopesetting, syntaxCheck, referenceResolving,
/// typechecking, staticAnalysis) on previously set sources
/// @returns false on error.
bool analyze();
/// Parses and analyzes all source units that were added
/// @returns false on error.
bool parseAndAnalyze();
/// Sets the given source code as the only source unit apart from standard sources and parses and analyzes it.
/// @returns false on error.
bool parseAndAnalyze(std::string const& _sourceCode);
/// @returns a list of the contract names in the sources. /// @returns a list of the contract names in the sources.
std::vector<std::string> contractNames() const; std::vector<std::string> contractNames() const;
std::string defaultContractName() const; std::string defaultContractName() const;
@ -181,6 +184,9 @@ public:
std::string const& onChainMetadata(std::string const& _contractName) const; std::string const& onChainMetadata(std::string const& _contractName) const;
void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; }
/// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
Json::Value gasEstimates(std::string const& _contractName) const;
/// @returns the previously used scanner, useful for counting lines during error reporting. /// @returns the previously used scanner, useful for counting lines during error reporting.
Scanner const& scanner(std::string const& _sourceName = "") const; Scanner const& scanner(std::string const& _sourceName = "") const;
/// @returns the parsed source unit with the supplied name. /// @returns the parsed source unit with the supplied name.
@ -230,6 +236,13 @@ private:
mutable std::unique_ptr<std::string const> sourceMapping; mutable std::unique_ptr<std::string const> sourceMapping;
mutable std::unique_ptr<std::string const> runtimeSourceMapping; 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 /// 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. /// @a m_readFile and stores the absolute paths of all imports in the AST annotations.
@ -263,14 +276,13 @@ private:
std::string target; std::string target;
}; };
ReadFileCallback m_readFile; ReadFile::Callback m_readFile;
bool m_optimize = false; bool m_optimize = false;
unsigned m_optimizeRuns = 200; unsigned m_optimizeRuns = 200;
std::map<std::string, h160> m_libraries; std::map<std::string, h160> m_libraries;
/// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum
/// "context:prefix=target" /// "context:prefix=target"
std::vector<Remapping> m_remappings; std::vector<Remapping> m_remappings;
bool m_parseSuccessful;
std::map<std::string const, Source> m_sources; std::map<std::string const, Source> m_sources;
std::shared_ptr<GlobalContext> m_globalContext; std::shared_ptr<GlobalContext> m_globalContext;
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes;
@ -279,6 +291,7 @@ private:
std::string m_formalTranslation; std::string m_formalTranslation;
ErrorList m_errors; ErrorList m_errors;
bool m_metadataLiteralSources = false; bool m_metadataLiteralSources = false;
State m_stackState = Empty;
}; };
} }

View File

@ -33,22 +33,22 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
switch(m_type) switch(m_type)
{ {
case Type::DeclarationError: case Type::DeclarationError:
m_typeName = "Declaration Error"; m_typeName = "DeclarationError";
break; break;
case Type::DocstringParsingError: case Type::DocstringParsingError:
m_typeName = "Docstring Parsing Error"; m_typeName = "DocstringParsingError";
break; break;
case Type::ParserError: case Type::ParserError:
m_typeName = "Parser Error"; m_typeName = "ParserError";
break; break;
case Type::SyntaxError: case Type::SyntaxError:
m_typeName = "Syntax Error"; m_typeName = "SyntaxError";
break; break;
case Type::TypeError: case Type::TypeError:
m_typeName = "Type Error"; m_typeName = "TypeError";
break; break;
case Type::Why3TranslatorError: case Type::Why3TranslatorError:
m_typeName = "Why3 Translator Error"; m_typeName = "Why3TranslatorError";
break; break;
case Type::Warning: case Type::Warning:
m_typeName = "Warning"; m_typeName = "Warning";

View File

@ -0,0 +1,45 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
#include <functional>
#include <boost/noncopyable.hpp>
namespace dev
{
namespace solidity
{
class ReadFile: boost::noncopyable
{
public:
/// File reading result.
struct Result
{
bool success;
std::string contentsOrErrorMessage;
};
/// File reading callback.
using Callback = std::function<Result(std::string const&)>;
};
}
}

View File

@ -23,6 +23,7 @@
#pragma once #pragma once
#include <ostream> #include <ostream>
#include <sstream>
#include <functional> #include <functional>
#include <libevmasm/SourceLocation.h> #include <libevmasm/SourceLocation.h>
@ -53,6 +54,16 @@ public:
std::string const& _name, std::string const& _name,
ScannerFromSourceNameFun const& _scannerFromSourceName ScannerFromSourceNameFun const& _scannerFromSourceName
); );
static std::string formatExceptionInformation(
Exception const& _exception,
std::string const& _name,
ScannerFromSourceNameFun const& _scannerFromSourceName
)
{
std::ostringstream errorOutput;
printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName);
return errorOutput.str();
}
private: private:
/// Prints source name if location is given. /// Prints source name if location is given.
static void printSourceName( static void printSourceName(

View File

@ -0,0 +1,482 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Alex Beregszaszi
* @date 2016
* Standard JSON compiler interface.
*/
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/SourceReferenceFormatter.h>
#include <libsolidity/ast/ASTJsonConverter.h>
#include <libevmasm/Instruction.h>
#include <libdevcore/JSON.h>
#include <libdevcore/SHA3.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
namespace {
Json::Value formatError(
bool _warning,
string const& _type,
string const& _component,
string const& _message,
string const& _formattedMessage = "",
Json::Value const& _sourceLocation = Json::Value()
)
{
Json::Value error = Json::objectValue;
error["type"] = _type;
error["component"] = _component;
error["severity"] = _warning ? "warning" : "error";
error["message"] = _message;
error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message;
if (_sourceLocation.isObject())
error["sourceLocation"] = _sourceLocation;
return error;
}
Json::Value formatFatalError(string const& _type, string const& _message)
{
Json::Value output = Json::objectValue;
output["errors"] = Json::arrayValue;
output["errors"].append(formatError(false, _type, "general", _message));
return output;
}
Json::Value formatErrorWithException(
Exception const& _exception,
bool const& _warning,
string const& _type,
string const& _component,
string const& _message,
function<Scanner const&(string const&)> const& _scannerFromSourceName
)
{
string message;
string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _message, _scannerFromSourceName);
// NOTE: the below is partially a copy from SourceReferenceFormatter
SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception);
if (string const* description = boost::get_error_info<errinfo_comment>(_exception))
message = ((_message.length() > 0) ? (_message + ":") : "") + *description;
else
message = _message;
if (location && location->sourceName)
{
Json::Value sourceLocation = Json::objectValue;
sourceLocation["file"] = *location->sourceName;
sourceLocation["start"] = location->start;
sourceLocation["end"] = location->end;
}
return formatError(_warning, _type, _component, message, formattedMessage, location);
}
/// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content.
bool hashMatchesContent(string const& _hash, string const& _content)
{
try
{
return dev::h256(_hash) == dev::keccak256(_content);
}
catch (dev::BadHexCharacter)
{
return false;
}
}
StringMap createSourceList(Json::Value const& _input)
{
StringMap sources;
Json::Value const& jsonSources = _input["sources"];
if (jsonSources.isObject())
for (auto const& sourceName: jsonSources.getMemberNames())
sources[sourceName] = jsonSources[sourceName]["content"].asString();
return sources;
}
Json::Value 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);
for (auto const& ref: linkReferences)
{
string const& fullname = ref.second;
size_t colon = fullname.find(':');
solAssert(colon != string::npos, "");
string file = fullname.substr(0, colon);
string name = fullname.substr(colon + 1);
Json::Value fileObject = ret.get(file, Json::objectValue);
Json::Value libraryArray = fileObject.get(name, Json::arrayValue);
Json::Value entry = Json::objectValue;
entry["start"] = Json::UInt(ref.first);
entry["length"] = 20;
libraryArray.append(entry);
fileObject[name] = libraryArray;
ret[file] = fileObject;
}
return ret;
}
Json::Value collectEVMObject(eth::LinkerObject const& _object, string const* _sourceMap)
{
Json::Value output = Json::objectValue;
output["object"] = _object.toHex();
output["opcodes"] = solidity::disassemble(_object.bytecode);
output["sourceMap"] = _sourceMap ? *_sourceMap : "";
output["linkReferences"] = formatLinkReferences(_object.linkReferences);
return output;
}
}
Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
{
m_compilerStack.reset(false);
if (!_input.isObject())
return formatFatalError("JSONError", "Input is not a JSON object.");
if (_input["language"] != "Solidity")
return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language.");
Json::Value const& sources = _input["sources"];
if (!sources)
return formatFatalError("JSONError", "No input sources specified.");
Json::Value errors = Json::arrayValue;
for (auto const& sourceName: sources.getMemberNames())
{
string hash;
if (!sources[sourceName].isObject())
return formatFatalError("JSONError", "Source input is not a JSON object.");
if (sources[sourceName]["keccak256"].isString())
hash = sources[sourceName]["keccak256"].asString();
if (sources[sourceName]["content"].isString())
{
string content = sources[sourceName]["content"].asString();
if (!hash.empty() && !hashMatchesContent(hash, content))
errors.append(formatError(
false,
"IOError",
"general",
"Mismatch between content and supplied hash for \"" + sourceName + "\""
));
else
m_compilerStack.addSource(sourceName, content);
}
else if (sources[sourceName]["urls"].isArray())
{
if (!m_readFile)
return formatFatalError("JSONError", "No import callback supplied, but URL is requested.");
bool found = false;
vector<string> failures;
for (auto const& url: sources[sourceName]["urls"])
{
ReadFile::Result result = m_readFile(url.asString());
if (result.success)
{
if (!hash.empty() && !hashMatchesContent(hash, result.contentsOrErrorMessage))
errors.append(formatError(
false,
"IOError",
"general",
"Mismatch between content and supplied hash for \"" + sourceName + "\" at \"" + url.asString() + "\""
));
else
{
m_compilerStack.addSource(sourceName, result.contentsOrErrorMessage);
found = true;
break;
}
}
else
failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.contentsOrErrorMessage);
}
for (auto const& failure: failures)
{
/// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors.
errors.append(formatError(
found ? true : false,
"IOError",
"general",
failure
));
}
}
else
return formatFatalError("JSONError", "Invalid input source specified.");
}
Json::Value const& settings = _input.get("settings", Json::Value());
vector<string> remappings;
for (auto const& remapping: settings.get("remappings", Json::Value()))
remappings.push_back(remapping.asString());
m_compilerStack.setRemappings(remappings);
Json::Value optimizerSettings = settings.get("optimizer", Json::Value());
bool optimize = optimizerSettings.get("enabled", Json::Value(false)).asBool();
unsigned optimizeRuns = optimizerSettings.get("runs", Json::Value(200u)).asUInt();
map<string, h160> libraries;
Json::Value jsonLibraries = settings.get("libraries", Json::Value());
for (auto const& sourceName: jsonLibraries.getMemberNames())
{
auto const& jsonSourceName = jsonLibraries[sourceName];
for (auto const& library: jsonSourceName.getMemberNames())
// @TODO use libraries only for the given source
libraries[library] = h160(jsonSourceName[library].asString());
}
Json::Value metadataSettings = settings.get("metadata", Json::Value());
m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool());
auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compilerStack.scanner(_sourceName); };
bool success = false;
try
{
success = m_compilerStack.compile(optimize, optimizeRuns, libraries);
for (auto const& error: m_compilerStack.errors())
{
auto err = dynamic_pointer_cast<Error const>(error);
errors.append(formatErrorWithException(
*error,
err->type() == Error::Type::Warning,
err->typeName(),
"general",
"",
scannerFromSourceName
));
}
}
catch (Error const& _error)
{
if (_error.type() == Error::Type::DocstringParsingError)
errors.append(formatError(
false,
"DocstringParsingError",
"general",
"Documentation parsing error: " + *boost::get_error_info<errinfo_comment>(_error)
));
else
errors.append(formatErrorWithException(
_error,
false,
_error.typeName(),
"general",
"",
scannerFromSourceName
));
}
catch (CompilerError const& _exception)
{
errors.append(formatErrorWithException(
_exception,
false,
"CompilerError",
"general",
"Compiler error (" + _exception.lineInfo() + ")",
scannerFromSourceName
));
}
catch (InternalCompilerError const& _exception)
{
errors.append(formatErrorWithException(
_exception,
false,
"InternalCompilerError",
"general",
"Internal compiler error (" + _exception.lineInfo() + ")", scannerFromSourceName
));
}
catch (UnimplementedFeatureError const& _exception)
{
errors.append(formatErrorWithException(
_exception,
false,
"UnimplementedFeatureError",
"general",
"Unimplemented feature (" + _exception.lineInfo() + ")",
scannerFromSourceName));
}
catch (Exception const& _exception)
{
errors.append(formatError(
false,
"Exception",
"general",
"Exception during compilation: " + boost::diagnostic_information(_exception)
));
}
catch (...)
{
errors.append(formatError(
false,
"Exception",
"general",
"Unknown exception during compilation."
));
}
Json::Value output = Json::objectValue;
if (errors.size() > 0)
output["errors"] = errors;
/// Inconsistent state - stop here to receive error reports from users
if (!success && (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())
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++;
sourceResult["legacyAST"] = ASTJsonConverter(m_compilerStack.ast(source), m_compilerStack.sourceIndices()).json();
output["sources"][source] = sourceResult;
}
Json::Value contractsOutput = Json::objectValue;
for (string const& contractName: success ? m_compilerStack.contractNames() : vector<string>())
{
size_t colon = contractName.find(':');
solAssert(colon != string::npos, "");
string file = contractName.substr(0, colon);
string name = contractName.substr(colon + 1);
// ABI, documentation and metadata
Json::Value contractData(Json::objectValue);
contractData["abi"] = m_compilerStack.metadata(contractName, DocumentationType::ABIInterface);
contractData["metadata"] = m_compilerStack.onChainMetadata(contractName);
contractData["userdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecUser);
contractData["devdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecDev);
// EVM
Json::Value evmData(Json::objectValue);
// @TODO: add ir
ostringstream tmp;
m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), false);
evmData["assembly"] = tmp.str();
evmData["legacyAssembly"] = m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), true);
evmData["methodIdentifiers"] = methodIdentifiers(m_compilerStack.contractDefinition(contractName));
evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
evmData["bytecode"] = collectEVMObject(
m_compilerStack.object(contractName),
m_compilerStack.sourceMapping(contractName)
);
evmData["deployedBytecode"] = collectEVMObject(
m_compilerStack.runtimeObject(contractName),
m_compilerStack.runtimeSourceMapping(contractName)
);
contractData["evm"] = evmData;
if (!contractsOutput.isMember(file))
contractsOutput[file] = Json::objectValue;
contractsOutput[file][name] = contractData;
}
output["contracts"] = contractsOutput;
return output;
}
Json::Value StandardCompiler::compile(Json::Value const& _input)
{
try
{
return compileInternal(_input);
}
catch (Json::LogicError const& _exception)
{
return formatFatalError("InternalCompilerError", string("JSON logic exception: ") + _exception.what());
}
catch (Json::RuntimeError const& _exception)
{
return formatFatalError("InternalCompilerError", string("JSON runtime exception: ") + _exception.what());
}
catch (Exception const& _exception)
{
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception));
}
catch (...)
{
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal");
}
}
string StandardCompiler::compile(string const& _input)
{
Json::Value input;
Json::Reader reader;
try
{
if (!reader.parse(_input, input, false))
return jsonCompactPrint(formatFatalError("JSONError", reader.getFormattedErrorMessages()));
}
catch(...)
{
return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}";
}
// cout << "Input: " << input.toStyledString() << endl;
Json::Value output = compile(input);
// cout << "Output: " << output.toStyledString() << endl;
try
{
return jsonCompactPrint(output);
}
catch(...)
{
return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}";
}
}

View File

@ -0,0 +1,63 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Alex Beregszaszi
* @date 2016
* Standard JSON compiler interface.
*/
#pragma once
#include <libsolidity/interface/CompilerStack.h>
namespace dev
{
namespace solidity
{
/**
* Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput.
* See docs/using-the-compiler#compiler-input-and-output-json-description.
*/
class StandardCompiler: boost::noncopyable
{
public:
/// Creates a new StandardCompiler.
/// @param _readFile callback to used to read files for import statements. Must return
/// and must not emit exceptions.
StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback())
: m_compilerStack(_readFile), m_readFile(_readFile)
{
}
/// Sets all input parameters according to @a _input which conforms to the standardized input
/// format, performs compilation and returns a standardized output.
Json::Value compile(Json::Value const& _input);
/// Parses input as JSON and peforms the above processing steps, returning a serialized JSON
/// output. Parsing errors are returned as regular errors.
std::string compile(std::string const& _input);
private:
Json::Value compileInternal(Json::Value const& _input);
CompilerStack m_compilerStack;
ReadFile::Callback m_readFile;
};
}
}

View File

@ -82,9 +82,10 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
case Token::Import: case Token::Import:
nodes.push_back(parseImportDirective()); nodes.push_back(parseImportDirective());
break; break;
case Token::Interface:
case Token::Contract: case Token::Contract:
case Token::Library: case Token::Library:
nodes.push_back(parseContractDefinition(token == Token::Library)); nodes.push_back(parseContractDefinition(token));
break; break;
default: default:
fatalParserError(string("Expected import directive or contract definition.")); fatalParserError(string("Expected import directive or contract definition."));
@ -193,13 +194,30 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases)); return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases));
} }
ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token)
{
switch(_token)
{
case Token::Interface:
return ContractDefinition::ContractKind::Interface;
case Token::Contract:
return ContractDefinition::ContractKind::Contract;
case Token::Library:
return ContractDefinition::ContractKind::Library;
default:
fatalParserError("Unsupported contract type.");
}
// FIXME: fatalParserError is not considered as throwing here
return ContractDefinition::ContractKind::Contract;
}
ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind)
{ {
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docString; ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "") if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral()); docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
expectToken(_isLibrary ? Token::Library : Token::Contract); expectToken(_expectedKind);
ASTPointer<ASTString> name = expectIdentifierToken(); ASTPointer<ASTString> name = expectIdentifierToken();
vector<ASTPointer<InheritanceSpecifier>> baseContracts; vector<ASTPointer<InheritanceSpecifier>> baseContracts;
if (m_scanner->currentToken() == Token::Is) if (m_scanner->currentToken() == Token::Is)
@ -252,7 +270,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary)
docString, docString,
baseContracts, baseContracts,
subNodes, subNodes,
_isLibrary tokenToContractKind(_expectedKind)
); );
} }

View File

@ -69,7 +69,8 @@ private:
///@name Parsing functions for the AST nodes ///@name Parsing functions for the AST nodes
ASTPointer<PragmaDirective> parsePragmaDirective(); ASTPointer<PragmaDirective> parsePragmaDirective();
ASTPointer<ImportDirective> parseImportDirective(); ASTPointer<ImportDirective> parseImportDirective();
ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary); ContractDefinition::ContractKind tokenToContractKind(Token::Value _token);
ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind);
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token);
FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers);

View File

@ -157,6 +157,7 @@ namespace solidity
K(Hex, "hex", 0) \ K(Hex, "hex", 0) \
K(If, "if", 0) \ K(If, "if", 0) \
K(Indexed, "indexed", 0) \ K(Indexed, "indexed", 0) \
K(Interface, "interface", 0) \
K(Internal, "internal", 0) \ K(Internal, "internal", 0) \
K(Import, "import", 0) \ K(Import, "import", 0) \
K(Is, "is", 0) \ K(Is, "is", 0) \
@ -225,7 +226,6 @@ namespace solidity
K(Final, "final", 0) \ K(Final, "final", 0) \
K(In, "in", 0) \ K(In, "in", 0) \
K(Inline, "inline", 0) \ K(Inline, "inline", 0) \
K(Interface, "interface", 0) \
K(Let, "let", 0) \ K(Let, "let", 0) \
K(Match, "match", 0) \ K(Match, "match", 0) \
K(NullLiteral, "null", 0) \ K(NullLiteral, "null", 0) \

View File

View File

@ -14,3 +14,5 @@ make solc && install -s solc/solc /usr/bin &&\
cd / && rm -rf solidity &&\ cd / && rm -rf solidity &&\
apk del sed build-base git make cmake gcc g++ musl-dev curl-dev boost-dev &&\ apk del sed build-base git make cmake gcc g++ musl-dev curl-dev boost-dev &&\
rm -rf /var/cache/apk/* rm -rf /var/cache/apk/*
ENTRYPOINT ["/usr/bin/solc"]

View File

@ -29,12 +29,6 @@
set -e set -e
if [[ "$OSTYPE" != "darwin"* ]]; then if [[ "$OSTYPE" != "darwin"* ]]; then
if [ "$TRAVIS_BRANCH" = release ]
then
echo -n > prerelease.txt
else
date -u +"nightly.%Y.%-m.%-d" > prerelease.txt
fi
./scripts/travis-emscripten/install_deps.sh ./scripts/travis-emscripten/install_deps.sh
docker run -v $(pwd):/src trzeci/emscripten:sdk-tag-1.35.4-64bit ./scripts/travis-emscripten/build_emscripten.sh docker run -v $(pwd):/src trzeci/emscripten:sdk-tag-1.35.4-64bit ./scripts/travis-emscripten/build_emscripten.sh
fi fi

Binary file not shown.

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python
import sys
import glob
import subprocess
import json
solc = sys.argv[1]
report = open("report.txt", "w")
for optimize in [False, True]:
for f in sorted(glob.glob("*.sol")):
args = [solc, '--combined-json', 'bin,metadata', f]
if optimize:
args += ['--optimize']
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = proc.communicate()
try:
result = json.loads(out.strip())
for contractName in sorted(result['contracts'].keys()):
report.write(contractName + ' ' + result['contracts'][contractName]['bin'] + '\n')
report.write(contractName + ' ' + result['contracts'][contractName]['metadata'] + '\n')
except:
report.write(f + ": ERROR\n")

View File

@ -0,0 +1,41 @@
@ECHO OFF
REM ---------------------------------------------------------------------------
REM This file is part of solidity.
REM
REM solidity is free software: you can redistribute it and/or modify
REM it under the terms of the GNU General Public License as published by
REM the Free Software Foundation, either version 3 of the License, or
REM (at your option) any later version.
REM
REM solidity is distributed in the hope that it will be useful,
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
REM GNU General Public License for more details.
REM
REM You should have received a copy of the GNU General Public License
REM along with solidity. If not, see <http://www.gnu.org/licenses/>
REM
REM Copyright (c) 2017 solidity contributors.
REM ---------------------------------------------------------------------------
set CONFIGURATION=%1
set COMMIT=%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
cd solidity-test-bytecode
git config user.name "travis"
git config user.email "chris@ethereum.org"
git clean -f -d -x
mkdir %COMMIT%
set REPORT=%COMMIT%/windows.txt
cp ../report.txt %REPORT%
git add %REPORT%
git commit -a -m "Added report."
git push origin

View File

@ -0,0 +1,104 @@
#!/usr/bin/env bash
#------------------------------------------------------------------------------
# Script used for cross-platform comparison as part of the travis automation.
# Splits all test source code into multiple files, generates bytecode and
# uploads the bytecode into github.com/ethereum/solidity-test-bytecode where
# another travis job is triggered to do the actual comparison.
#
# ------------------------------------------------------------------------------
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2017 solidity contributors.
#------------------------------------------------------------------------------
set -e
REPO_ROOT="$(dirname "$0")"/../..
echo "Compiling all test contracts into bytecode..."
TMPDIR=$(mktemp -d)
(
cd "$REPO_ROOT"
REPO_ROOT=$(pwd) # make it absolute
cd "$TMPDIR"
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/
if [[ "$SOLC_EMSCRIPTEN" = "On" ]]
then
cp "$REPO_ROOT/build/solc/soljson.js" .
npm install solc
cat > solc <<EOF
#!/usr/bin/env node
var process = require('process')
var fs = require('fs')
var compiler = require('solc/wrapper.js')(require('./soljson.js'))
for (var optimize of [false, true])
{
for (var filename of process.argv.slice(2))
{
if (filename !== undefined)
{
var inputs = {}
inputs[filename] = fs.readFileSync(filename).toString()
var result = compiler.compile({sources: inputs}, optimize)
if (!('contracts' in result) || Object.keys(result['contracts']).length === 0)
{
console.log(filename + ': ERROR')
}
else
{
for (var contractName in result['contracts'])
{
console.log(contractName + ' ' + result['contracts'][contractName].bytecode)
console.log(contractName + ' ' + result['contracts'][contractName].metadata)
}
}
}
}
}
EOF
chmod +x solc
./solc *.sol > report.txt
else
$REPO_ROOT/scripts/bytecodecompare/prepare_report.py $REPO_ROOT/build/solc/solc
fi
if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]
then
openssl aes-256-cbc -K $encrypted_60701c962b9c_key -iv $encrypted_60701c962b9c_iv -in "$REPO_ROOT"/scripts/bytecodecompare/deploy_key.enc -out deploy_key -d
chmod 600 deploy_key
eval `ssh-agent -s`
ssh-add deploy_key
git clone --depth 2 git@github.com:ethereum/solidity-test-bytecode.git
cd solidity-test-bytecode
git config user.name "travis"
git config user.email "chris@ethereum.org"
git clean -f -d -x
mkdir -p "$TRAVIS_COMMIT"
REPORT="$TRAVIS_COMMIT/$ZIP_SUFFIX.txt"
cp ../report.txt "$REPORT"
git add "$REPORT"
git commit -a -m "Added report $REPORT"
git push origin
fi
)
rm -rf "$TMPDIR"

View File

@ -15,7 +15,7 @@ REPO_ROOT="$(dirname "$0")"/..
then then
versionstring="$version" versionstring="$version"
else else
versionstring="$version-develop-$commitdate-$commithash" versionstring="$version-nightly-$commitdate-$commithash"
fi fi
TEMPDIR=$(mktemp -d) TEMPDIR=$(mktemp -d)
@ -29,6 +29,7 @@ REPO_ROOT="$(dirname "$0")"/..
# Add dependencies # Add dependencies
mkdir -p "$SOLDIR/deps/downloads/" 2>/dev/null || true mkdir -p "$SOLDIR/deps/downloads/" 2>/dev/null || true
wget -O "$SOLDIR/deps/downloads/jsoncpp-1.7.7.tar.gz" https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz wget -O "$SOLDIR/deps/downloads/jsoncpp-1.7.7.tar.gz" https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz
tar czf "$REPO_ROOT/solidity_$versionstring.tar.gz" -C "$TEMPDIR" "solidity_$versionstring" mkdir -p "$REPO_ROOT/upload"
tar czf "$REPO_ROOT/upload/solidity_$versionstring.tar.gz" -C "$TEMPDIR" "solidity_$versionstring"
rm -r "$TEMPDIR" rm -r "$TEMPDIR"
) )

8
scripts/docker_build.sh Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env sh
set -e
docker build -t ethereum/solc:build -f scripts/Dockerfile .
tmp_container=$(docker create ethereum/solc:build sh)
mkdir -p upload
docker cp ${tmp_container}:/usr/bin/solc upload/solc-static-linux

View File

@ -10,13 +10,11 @@ then
docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT" docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT"
docker push ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT"; docker push ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT";
docker push ethereum/solc:nightly; docker push ethereum/solc:nightly;
elif [ "$TRAVIS_BRANCH" = "release" ]
then
docker tag ethereum/solc:build ethereum/solc:stable;
docker push ethereum/solc:stable;
elif [ "$TRAVIS_TAG" = v"$version" ] elif [ "$TRAVIS_TAG" = v"$version" ]
then then
docker tag ethereum/solc:build ethereum/solc:stable;
docker tag ethereum/solc:build ethereum/solc:"$version"; docker tag ethereum/solc:build ethereum/solc:"$version";
docker push ethereum/solc:stable;
docker push ethereum/solc:"$version"; docker push ethereum/solc:"$version";
else else
echo "Not publishing docker image from branch $TRAVIS_BRANCH or tag $TRAVIS_TAG" echo "Not publishing docker image from branch $TRAVIS_BRANCH or tag $TRAVIS_TAG"

View File

@ -59,4 +59,3 @@ REM Copyright (c) 2016 solidity contributors.
REM --------------------------------------------------------------------------- REM ---------------------------------------------------------------------------
cmake -P deps\install_deps.cmake cmake -P deps\install_deps.cmake
cmake -P scripts\install_eth.cmake

View File

@ -96,7 +96,7 @@ case $(uname -s) in
brew update brew update
brew install boost brew install boost
brew install cmake brew install cmake
if ["$CI" = true]; then if [ "$CI" = true ]; then
brew upgrade cmake brew upgrade cmake
brew tap ethereum/ethereum brew tap ethereum/ethereum
brew install cpp-ethereum brew install cpp-ethereum
@ -314,12 +314,12 @@ case $(uname -s) in
libboost-all-dev libboost-all-dev
if [ "$CI" = true ]; then if [ "$CI" = true ]; then
# Install 'eth', for use in the Solidity Tests-over-IPC. # Install 'eth', for use in the Solidity Tests-over-IPC.
# We will not use this 'eth', but its dependencies
sudo add-apt-repository -y ppa:ethereum/ethereum sudo add-apt-repository -y ppa:ethereum/ethereum
sudo add-apt-repository -y ppa:ethereum/ethereum-dev sudo add-apt-repository -y ppa:ethereum/ethereum-dev
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install eth sudo apt-get -y install eth
fi fi
;; ;;
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------

View File

@ -1,76 +0,0 @@
#------------------------------------------------------------------------------
# Cmake script for installing pre-requisite package eth for solidity.
#
# The aim of this script is to simply download and unpack eth binaries to the deps folder.
#
# The documentation for solidity is hosted at:
#
# http://solidity.readthedocs.io/
#
# ------------------------------------------------------------------------------
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016 solidity contributors.
#------------------------------------------------------------------------------
function(download URL DST_FILE STATUS)
set(TMP_FILE "${DST_FILE}.part")
get_filename_component(FILE_NAME ${DST_FILE} NAME)
if (NOT EXISTS ${DST_FILE})
message("Downloading ${FILE_NAME}")
file(DOWNLOAD ${URL} ${TMP_FILE} SHOW_PROGRESS STATUS DOWNLOAD_STATUS)
list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
if (STATUS_CODE EQUAL 0)
file(RENAME ${TMP_FILE} ${DST_FILE})
else()
file(REMOVE ${TMP_FILE})
list(GET DOWNLOAD_STATUS 1 ERROR_MSG)
message("ERROR! Downloading '${FILE_NAME}' failed.")
message(STATUS "URL: ${URL}")
message(STATUS "Error: ${STATUS_CODE} ${ERROR_MSG}")
set(STATUS FALSE PARENT_SCOPE)
return()
endif()
else()
message("Using cached ${FILE_NAME}")
endif()
set(STATUS TRUE PARENT_SCOPE)
endfunction(download)
function(download_and_unpack PACKAGE_URL DST_DIR)
get_filename_component(FILE_NAME ${PACKAGE_URL} NAME)
set(DST_FILE "${CACHE_DIR}/${FILE_NAME}")
set(TMP_FILE "${DST_FILE}.part")
file(MAKE_DIRECTORY ${CACHE_DIR})
file(MAKE_DIRECTORY ${DST_DIR})
download(${PACKAGE_URL} ${DST_FILE} STATUS)
if (STATUS)
message("Unpacking ${FILE_NAME} to ${DST_DIR}")
execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${DST_FILE}
WORKING_DIRECTORY ${DST_DIR})
endif()
endfunction(download_and_unpack)
get_filename_component(ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(CACHE_DIR "${ROOT_DIR}/deps/cache")
set(INSTALL_DIR "${ROOT_DIR}/deps/install/x64/eth")
download_and_unpack("https://github.com/bobsummerwill/cpp-ethereum/releases/download/develop-v1.3.0.401/cpp-ethereum-develop-windows.zip" ${INSTALL_DIR})

View File

@ -7,38 +7,42 @@
# scripts/isolate_tests.py test/libsolidity/* # scripts/isolate_tests.py test/libsolidity/*
import sys import sys
import re
import os
import hashlib
from os.path import join
def extract_cases(path): def extract_cases(path):
lines = open(path).read().splitlines() lines = open(path, 'rb').read().splitlines()
inside = False inside = False
delimiter = ''
tests = [] tests = []
for l in lines: for l in lines:
if inside: if inside:
if l.strip().endswith(')";'): if l.strip().endswith(')' + delimiter + '";'):
inside = False inside = False
else: else:
tests[-1] += l + '\n' tests[-1] += l + '\n'
else: else:
if l.strip().endswith('R"('): m = re.search(r'R"([^(]*)\($', l.strip())
if m:
inside = True inside = True
delimiter = m.group(1)
tests += [''] tests += ['']
return tests return tests
def write_cases(tests, start=0): def write_cases(tests):
for i, test in enumerate(tests, start=start): for test in tests:
open('test%d.sol' % i, 'w').write(test) open('test_%s.sol' % hashlib.sha256(test).hexdigest(), 'wb').write(test)
if __name__ == '__main__': if __name__ == '__main__':
files = sys.argv[1:] path = sys.argv[1]
i = 0 for root, dir, files in os.walk(path):
for path in files: for f in files:
cases = extract_cases(path) cases = extract_cases(join(root, f))
write_cases(cases, start=i) write_cases(cases)
i += len(cases)

View File

@ -88,5 +88,5 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
fi fi
# And ZIP it all up, with a filename suffix passed in on the command-line. # And ZIP it all up, with a filename suffix passed in on the command-line.
mkdir -p $REPO_ROOT/upload
zip -j $REPO_ROOT/solidity-$ZIP_SUFFIX.zip $ZIP_TEMP_DIR/* zip -j $REPO_ROOT/upload/solidity-$ZIP_SUFFIX.zip $ZIP_TEMP_DIR/*

View File

@ -42,8 +42,16 @@ test "${output//[[:blank:]]/}" = "3"
# instead. This will go away soon. # instead. This will go away soon.
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
ETH_PATH="$REPO_ROOT/eth" ETH_PATH="$REPO_ROOT/eth"
else elif [ -z $CI ]; then
ETH_PATH="eth" ETH_PATH="eth"
else
mkdir -p /tmp/test
wget -O /tmp/test/eth https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth
test "$(shasum /tmp/test/eth)" = "c132e8989229e4840831a4fb1a1d058b732a11d5 /tmp/test/eth"
sync
chmod +x /tmp/test/eth
sync # Otherwise we might get a "text file busy" error
ETH_PATH="/tmp/test/eth"
fi fi
# This trailing ampersand directs the shell to run the command in the background, # This trailing ampersand directs the shell to run the command in the background,

View File

@ -94,6 +94,8 @@ emmake make -j 4
cd .. cd ..
cp build/solc/soljson.js ./ cp build/solc/soljson.js ./
mkdir -p upload
cp soljson.js upload/
OUTPUT_SIZE=`ls -la build/solc/soljson.js` OUTPUT_SIZE=`ls -la build/solc/soljson.js`

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python
#
# This script is used to generate the list of bugs per compiler version
# from the list of bugs.
# It updates the list in place and signals failure if there were changes.
# This makes it possible to use this script as part of CI to check
# that the list is up to date.
import os
import json
import re
import sys
def comp(version_string):
return [int(c) for c in version_string.split('.')]
path = os.path.dirname(os.path.realpath(__file__))
with open(path + '/../docs/bugs.json') as bugsFile:
bugs = json.load(bugsFile)
versions = {}
with open(path + '/../Changelog.md') as changelog:
for line in changelog:
m = re.search(r'^### (\S+) \((\d+-\d+-\d+)\)$', line)
if m:
versions[m.group(1)] = {}
versions[m.group(1)]['released'] = m.group(2)
for v in versions:
versions[v]['bugs'] = []
for bug in bugs:
if 'introduced' in bug and comp(bug['introduced']) > comp(v):
continue
if comp(bug['fixed']) <= comp(v):
continue
versions[v]['bugs'] += [bug['name']]
with open(path + '/../docs/bugs_by_version.json', 'r+') as bugs_by_version:
old_contents = bugs_by_version.read()
new_contents = json.dumps(versions, sort_keys=True, indent=4)
bugs_by_version.seek(0)
bugs_by_version.write(new_contents)
sys.exit(old_contents != new_contents)

View File

@ -18,7 +18,7 @@ else()
endif() endif()
if (EMSCRIPTEN) if (EMSCRIPTEN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\"]' -s RESERVED_FUNCTION_POINTERS=20") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\",\"_compileStandard\"]' -s RESERVED_FUNCTION_POINTERS=20")
add_executable(soljson jsonCompiler.cpp ${HEADERS}) add_executable(soljson jsonCompiler.cpp ${HEADERS})
eth_use(soljson REQUIRED Solidity::solidity) eth_use(soljson REQUIRED Solidity::solidity)
else() else()

View File

@ -32,6 +32,7 @@
#include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/SourceReferenceFormatter.h> #include <libsolidity/interface/SourceReferenceFormatter.h>
#include <libsolidity/interface/GasEstimator.h> #include <libsolidity/interface/GasEstimator.h>
#include <libsolidity/formal/Why3Translator.h> #include <libsolidity/formal/Why3Translator.h>
@ -102,6 +103,8 @@ static string const g_strSrcMapRuntime = "srcmap-runtime";
static string const g_strVersion = "version"; static string const g_strVersion = "version";
static string const g_stdinFileNameStr = "<stdin>"; static string const g_stdinFileNameStr = "<stdin>";
static string const g_strMetadataLiteral = "metadata-literal"; 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_argAbi = g_strAbi; static string const g_argAbi = g_strAbi;
static string const g_argAddStandard = g_strAddStandard; static string const g_argAddStandard = g_strAddStandard;
@ -131,6 +134,8 @@ static string const g_argSignatureHashes = g_strSignatureHashes;
static string const g_argVersion = g_strVersion; static string const g_argVersion = g_strVersion;
static string const g_stdinFileName = g_stdinFileNameStr; static string const g_stdinFileName = g_stdinFileNameStr;
static string const g_argMetadataLiteral = g_strMetadataLiteral; 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 /// Possible arguments to for --combined-json
static set<string> const g_combinedJsonArgs{ static set<string> const g_combinedJsonArgs{
@ -315,49 +320,40 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co
void CommandLineInterface::handleGasEstimation(string const& _contract) void CommandLineInterface::handleGasEstimation(string const& _contract)
{ {
using Gas = GasEstimator::GasConsumption; Json::Value estimates = m_compiler->gasEstimates(_contract);
if (!m_compiler->assemblyItems(_contract) && !m_compiler->runtimeAssemblyItems(_contract))
return;
cout << "Gas estimation:" << endl; cout << "Gas estimation:" << endl;
if (eth::AssemblyItems const* items = m_compiler->assemblyItems(_contract))
if (estimates["creation"].isObject())
{ {
Gas gas = GasEstimator::functionalEstimation(*items); Json::Value creation = estimates["creation"];
u256 bytecodeSize(m_compiler->runtimeObject(_contract).bytecode.size());
cout << "construction:" << endl; cout << "construction:" << endl;
cout << " " << gas << " + " << (bytecodeSize * eth::GasCosts::createDataGas) << " = "; cout << " " << creation["executionCost"].asString();
gas += bytecodeSize * eth::GasCosts::createDataGas; cout << " + " << creation["codeDepositCost"].asString();
cout << gas << endl; cout << " = " << creation["totalCost"].asString() << endl;
} }
if (eth::AssemblyItems const* items = m_compiler->runtimeAssemblyItems(_contract))
if (estimates["external"].isObject())
{ {
ContractDefinition const& contract = m_compiler->contractDefinition(_contract); Json::Value externalFunctions = estimates["external"];
cout << "external:" << endl; cout << "external:" << endl;
for (auto it: contract.interfaceFunctions()) for (auto const& name: externalFunctions.getMemberNames())
{ {
string sig = it.second->externalSignature(); if (name.empty())
GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig); cout << " fallback:\t";
cout << " " << sig << ":\t" << gas << endl; else
cout << " " << name << ":\t";
cout << externalFunctions[name].asString() << endl;
} }
if (contract.fallbackFunction()) }
if (estimates["internal"].isObject())
{ {
GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, "INVALID"); Json::Value internalFunctions = estimates["internal"];
cout << " fallback:\t" << gas << endl;
}
cout << "internal:" << endl; cout << "internal:" << endl;
for (auto const& it: contract.definedFunctions()) for (auto const& name: internalFunctions.getMemberNames())
{ {
if (it->isPartOfExternalInterface() || it->isConstructor()) cout << " " << name << ":\t";
continue; cout << internalFunctions[name].asString() << endl;
size_t entry = m_compiler->functionEntryPoint(_contract, *it);
GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite();
if (entry > 0)
gas = GasEstimator::functionalEstimation(*items, entry, *it);
FunctionType type(*it);
cout << " " << it->name() << "(";
auto paramTypes = type.parameterTypes();
for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it)
cout << (*it)->toString() << (it + 1 == paramTypes.end() ? "" : ",");
cout << "):\t" << gas << endl;
} }
} }
} }
@ -533,6 +529,11 @@ Allowed options)",
"Output a single json document containing the specified information." "Output a single json document containing the specified information."
) )
(g_argGas.c_str(), "Print an estimate of the maximal gas usage for each function.") (g_argGas.c_str(), "Print an estimate of the maximal gas usage for each function.")
(
g_argStandardJSON.c_str(),
"Switch to Standard JSON input / output mode, ignoring all options. "
"It reads from standard input and provides the result on the standard output."
)
( (
g_argAssemble.c_str(), g_argAssemble.c_str(),
"Switch to assembly mode, ignoring all options and assumes input is assembly." "Switch to assembly mode, ignoring all options and assumes input is assembly."
@ -542,7 +543,12 @@ Allowed options)",
"Switch to linker mode, ignoring all options apart from --libraries " "Switch to linker mode, ignoring all options apart from --libraries "
"and modify binaries in place." "and modify binaries in place."
) )
(g_argMetadataLiteral.c_str(), "Store referenced sources are literal data in the metadata output."); (g_argMetadataLiteral.c_str(), "Store referenced sources are literal data in the metadata output.")
(
g_argAllowPaths.c_str(),
po::value<string>()->value_name("path(s)"),
"Allow a given path for imports. A list of paths can be supplied by separating them with a comma."
);
po::options_description outputComponents("Output Components"); po::options_description outputComponents("Output Components");
outputComponents.add_options() outputComponents.add_options()
(g_argAst.c_str(), "AST of all source files.") (g_argAst.c_str(), "AST of all source files.")
@ -610,6 +616,69 @@ Allowed options)",
bool CommandLineInterface::processInput() bool CommandLineInterface::processInput()
{ {
ReadFile::Callback fileReader = [this](string const& _path)
{
try
{
auto path = boost::filesystem::path(_path);
auto canonicalPath = boost::filesystem::canonical(path);
bool isAllowed = false;
for (auto const& allowedDir: m_allowedDirectories)
{
// If dir is a prefix of boostPath, we are fine.
if (
std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) &&
std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin())
)
{
isAllowed = true;
break;
}
}
if (!isAllowed)
return ReadFile::Result{false, "File outside of allowed directories."};
else if (!boost::filesystem::exists(path))
return ReadFile::Result{false, "File not found."};
else if (!boost::filesystem::is_regular_file(canonicalPath))
return ReadFile::Result{false, "Not a valid file."};
else
{
auto contents = dev::contentsString(canonicalPath.string());
m_sourceCodes[path.string()] = contents;
return ReadFile::Result{true, contents};
}
}
catch (Exception const& _exception)
{
return ReadFile::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)};
}
catch (...)
{
return ReadFile::Result{false, "Unknown exception in read callback."};
}
};
if (m_args.count(g_argAllowPaths))
{
vector<string> paths;
for (string const& path: boost::split(paths, m_args[g_argAllowPaths].as<string>(), boost::is_any_of(",")))
m_allowedDirectories.push_back(boost::filesystem::path(path));
}
if (m_args.count(g_argStandardJSON))
{
string input;
while (!cin.eof())
{
string tmp;
getline(cin, tmp);
input.append(tmp + "\n");
}
StandardCompiler compiler(fileReader);
cout << compiler.compile(input) << endl;
return true;
}
readInputFilesAndConfigureRemappings(); readInputFilesAndConfigureRemappings();
if (m_args.count(g_argLibraries)) if (m_args.count(g_argLibraries))
@ -630,37 +699,6 @@ bool CommandLineInterface::processInput()
return link(); return link();
} }
CompilerStack::ReadFileCallback fileReader = [this](string const& _path)
{
auto path = boost::filesystem::path(_path);
if (!boost::filesystem::exists(path))
return CompilerStack::ReadFileResult{false, "File not found."};
auto canonicalPath = boost::filesystem::canonical(path);
bool isAllowed = false;
for (auto const& allowedDir: m_allowedDirectories)
{
// If dir is a prefix of boostPath, we are fine.
if (
std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) &&
std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin())
)
{
isAllowed = true;
break;
}
}
if (!isAllowed)
return CompilerStack::ReadFileResult{false, "File outside of allowed directories."};
else if (!boost::filesystem::is_regular_file(canonicalPath))
return CompilerStack::ReadFileResult{false, "Not a valid file."};
else
{
auto contents = dev::contentsString(canonicalPath.string());
m_sourceCodes[path.string()] = contents;
return CompilerStack::ReadFileResult{true, contents};
}
};
m_compiler.reset(new CompilerStack(fileReader)); m_compiler.reset(new CompilerStack(fileReader));
auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); }; auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); };
try try
@ -877,7 +915,9 @@ void CommandLineInterface::handleAst(string const& _argStr)
bool CommandLineInterface::actOnInput() bool CommandLineInterface::actOnInput()
{ {
if (m_onlyAssemble) if (m_args.count(g_argStandardJSON))
return true;
else if (m_onlyAssemble)
outputAssembly(); outputAssembly();
else if (m_onlyLink) else if (m_onlyLink)
writeLinkedFiles(); writeLinkedFiles();

Some files were not shown because too many files have changed in this diff Show More