diff options
author | chriseth <chris@ethereum.org> | 2017-05-03 20:36:32 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-03 20:36:32 +0800 |
commit | 68ef5810593e7c8092ed41d5f474dd43141624eb (patch) | |
tree | 36453acfef9495095dc47305d9b40c2cd3b63813 | |
parent | f0d539ae05739e35336cc9cc8f44bd9798a95c28 (diff) | |
parent | 34b28ed760e8ba9b86f661c819fe489fb8403235 (diff) | |
download | dexon-solidity-68ef5810593e7c8092ed41d5f474dd43141624eb.tar.gz dexon-solidity-68ef5810593e7c8092ed41d5f474dd43141624eb.tar.zst dexon-solidity-68ef5810593e7c8092ed41d5f474dd43141624eb.zip |
Merge pull request #2219 from ethereum/develop
Release for version 0.4.11
121 files changed, 5006 insertions, 1398 deletions
@@ -1,5 +1,5 @@ -.commit_hash.txt -.prerelease.txt +commit_hash.txt +prerelease.txt # Compiled Object files *.slo diff --git a/.travis.yml b/.travis.yml index b722dacd..d947707c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,6 +44,7 @@ env: - SOLC_INSTALL_DEPS_TRAVIS=On - SOLC_RELEASE=On - SOLC_TESTS=On + - SOLC_STOREBYTECODE=Off - SOLC_DOCKER=Off matrix: @@ -61,6 +62,7 @@ matrix: compiler: gcc env: - ZIP_SUFFIX=ubuntu-trusty + - SOLC_STOREBYTECODE=On - os: linux dist: trusty @@ -68,6 +70,7 @@ matrix: compiler: clang env: - ZIP_SUFFIX=ubuntu-trusty-clang + - SOLC_STOREBYTECODE=On # Documentation target, which generates documentation using Phoenix / ReadTheDocs. - os: linux @@ -113,6 +116,8 @@ matrix: - SOLC_INSTALL_DEPS_TRAVIS=Off - SOLC_RELEASE=Off - SOLC_TESTS=Off + - ZIP_SUFFIX=emscripten + - SOLC_STOREBYTECODE=On # OS X Mavericks (10.9) # https://en.wikipedia.org/wiki/OS_X_Mavericks @@ -175,11 +180,12 @@ cache: install: - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.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 - - test $SOLC_DOCKER != On || (docker build -t ethereum/solc:build -f scripts/Dockerfile .) before_script: - test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh) + - test $SOLC_DOCKER != On || (scripts/docker_build.sh) - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE && scripts/release.sh $ZIP_SUFFIX && scripts/create_source_tarball.sh) @@ -187,6 +193,7 @@ before_script: script: - test $SOLC_DOCS != On || (scripts/docs.sh) - test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh) + - test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh) deploy: # This is the deploy target for the Emscripten build. @@ -223,11 +230,8 @@ deploy: overwrite: true file_glob: true - file: - - $TRAVIS_BUILD_DIR/solidity*.zip - - $TRAVIS_BUILD_DIR/solidity*tar.gz + file: $TRAVIS_BUILD_DIR/upload/* skip_cleanup: true on: all_branches: true tags: true - condition: $SOLC_RELEASE == On diff --git a/CMakeLists.txt b/CMakeLists.txt index cea219ff..931a8a0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.10") +set(PROJECT_VERSION "0.4.11") project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies diff --git a/Changelog.md b/Changelog.md index f1158672..9c69fb57 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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) Features: diff --git a/appveyor.yml b/appveyor.yml index 86a689a7..22ec30a0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,6 +34,10 @@ branches: os: Visual Studio 2015 configuration: - RelWithDebInfo +environment: + # This is used for pushing to solidity-test-bytecodes + priv_key: + secure: yYGwg4rhCdHfwuv2mFjaNEDwAx3IKUbp0D5fMGpaKefnfk+BiMS5bqSHRiOj91PZ91P9pUk2Vu+eNuS4hTFCf1zFGfrOhlJ4Ij0xSyU5m/LQr590Mo+f7W94Xc8ubgo6j2hp9qH/szTqTzmAkmxKO5TLlWjVzVny2t/s5o5UprLS1/MdzDNLjpVNXR03oKfdWUV9a2l6+PejXCbqyUCagh6BByZqeAPbDcil6eAfxu4EPX83Fuurof+KqFzIWycBG5qK1pTipn2pxiA0QKuUrD8y8VNL0S23NTgxoxSp7nPVMd3K0qRSzPM5lrqS7Z8i3evkVwPbuhu0gSiV08jGVahH2snQ3JGYsH2D4KmVn/xiVBeJ0lRplYlfZF0GUu7iJ+DDxi6wBPhW9A25/NyD/mx7Ub2dLheyWi8AjdSCzhfRD+4We8FQQeHRo3Q0kAohFmlCXdXhrcwOOloId8r6xYwg+hWxHTt2Oe9CKwXfmiPjgl/Gd6lYgLpyyfJ8drQ6tjO/pybLEa10v74qYNdVW5LaLIsRUM9Jm/FDVTrOGYtPndi87mF+/tBJIaXXNz0EMl5xvsKW0SBfUMV49zoDDKZZgWyO9U/cfViEUi7Sdn9QLsBWLZfSgBQNkq3WGZVKPq58OxEWT9dUghQHlSVh2qWF/NUx0TRBjiJl9JM56ENTMD00y18eDcXNCeLLVYB+R1axabUPdXivrO+BrWQK94IWxKEJ+YYN8WVJWAO5T/EBDKwgiXGneePwJ75WP7XCLtuYxqjC+CeW3xBVCzCEeZB/VVBvt7fhmtcoeZZ6tAS10h0yY5WWZ/EUVorj+c/FrMm7Nlpcrd1p4hciffePSLVg+yvy9/xTuM9trYWMgj4xcDQbYsaeItHO2Z3EiUoCgNdUw6rONiNwS/XBApWhCcklWm0/g62h2gOa7/hnKG6p2omQzYw+cOzWbF9+DBzoTSXXZXqbUshVee+CD+iYJKleGYSdbMdM89HW4HyskHk6HgM1ggE8CsgD1pMhXtqLTYZBlvsZCBkHPkD9NhGD2DtrNOmJOW8xwkL2/Il6roDF4n856XNdsjvd++rvQoKr58SkyApCJeCo3sfVres0W22g+7If2b2kWC4/DphrFkeaceFzJOctBUrwstvQBXIVOcadU978A3E7jvTaMR4JL9kC/iPOUVNjNRNM/gNvTlf3CIyMMszFeftjEBGnCZaSpht2RtNapRQQb6QPkOP88nufQVZq/TP1ECmvdTUWJ7kSnAupu6u8oH2x2IIm/KKeIwSYU5rGxjRb36DwgXCHcwfRYo3VNorwTeZGj4q1TSM9PuvgzNg//gKZW6VRa+HdNm/40ZGpDsOrr55tOBqfpq9k5RmevqW/OMZS3xUuArKdYLQY75t9eWcbHSgFN2ZY1KEdyEEvVKgs6Q4lEnSSulGxroRxTU5BOoA0V4tCeCUoSPD3FB93WsO9fBPzNsqOuBtDdIkApefzc1pT38uKpmVfggKUsoWUdqMXAWqCDWr2uw9EE900RJpEY6mIEWhkcro5LAMwaqByOGpqFFUkH+UWTC102eVHEmjxKpC6c6cSzoKKU6Ckd+jVRFO7TvmVe1MKCwjXj8lcAfAM2gQ+XehtrQdIBhAmCrnzurfz2u9tKVdpiADC1ig+kMs1/HX2713LYVXzDKdk+duQ94SVtGv9F2Iv+KN5oq4UFgll6VGt7GHsJOrYYf/wrOfB09IkpmjNygvcpmmSdcXXF8ulDD6KHTGEGUlFwLOpEwKx+zX2ZvviStHhN8KsoTKSVSueDmSSI63HdTS7FxfrHJc1yAzsdqEN5g5eV/z2Fn34qy64mdFSAZMF5zsbWZYFpc9ef3llF5aRcuD90JWT2VC7rB2jeGEtiwGkDlqKzxqRvJk06wTK6+n5RncN66bDaksulOPJMAR/bRW7dinV8T6yIvybuhqDetxJQP6eyAnW4xr1YxIAG4BXGZV6XAPTgOG2oGvMdncxkcLQHXVu07x39ySqP/m2MBxn0zF3DmaqrSPIRMhS8gG3d/23Jux3YHDEOBHjdJSdwqs5F5+QBFPV2rmJnpcSoW4d3M119XI20L914c62R7wY4e6+qmi3ydQU9g6p8psZgaE3TuMsyzX3k4C30nC/3gWT+zl253NjZwfbzIdHu5LWNDY9kEHtKzLP # NB: Appveyor cache is disabled, because it is proving very unreliable. # We can re-enable it when we find a way to mitigate the unreliability # issues. Have automated builds be reliable is the more important thing. @@ -43,7 +47,14 @@ configuration: #init: # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) install: + - ps: $fileContent = "-----BEGIN RSA PRIVATE KEY-----`n" + - ps: $fileContent += $env:priv_key.Replace(' ', "`n") + - ps: $fileContent += "`n-----END RSA PRIVATE KEY-----`n" + - ps: Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent - git submodule update --init --recursive + - ps: $prerelease = "nightly." + - ps: $prerelease += Get-Date -format "yyyy.M.d" + - ps: Set-Content prerelease.txt $prerelease - scripts/install_deps.bat - set ETHEREUM_DEPS_PATH=%APPVEYOR_BUILD_FOLDER%\deps\install before_build: @@ -54,15 +65,12 @@ build_script: - msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal - cd %APPVEYOR_BUILD_FOLDER% - scripts\release.bat %CONFIGURATION% + - scripts\bytecodecompare\storebytecode.bat %CONFIGURATION% %APPVEYOR_REPO_COMMIT% test_script: - cd %APPVEYOR_BUILD_FOLDER% - - cd deps\install\x64\eth - - ps: $ethProc = Start-Process eth.exe --test - - ps: Start-Sleep -s 100 - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - - copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" . - - soltest.exe --show-progress -- --ipcpath \\.\pipe\geth.ipc + - soltest.exe --show-progress -- --no-ipc artifacts: - path: solidity-windows.zip diff --git a/docs/assembly.rst b/docs/assembly.rst index 415bb1a1..420cea17 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -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 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 =============== @@ -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))`` * loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` * switch statements: ``switch x case 0: { y := mul(x, 2) } default: { y := 0 }`` -* function calls: ``function f(x) -> (y) { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }`` +* function calls: ``function f(x) -> y { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }`` .. note:: Of the above, loops, function calls and switch statements are not yet implemented. @@ -323,9 +323,12 @@ Access to External Variables and Functions ------------------------------------------ Solidity variables and other identifiers can be accessed by simply using their name. -For storage and memory variables, this will push the address and not the value onto the -stack. Also note that non-struct and non-array storage variable addresses occupy two slots -on the stack: One for the address and one for the byte offset inside the storage slot. +For memory variables, this will push the address and not the value onto the +stack. Storage variables are different: Values in storage might not occupy a +full storage slot, so their "address" is composed of a slot and a byte-offset +inside that slot. To retrieve the slot pointed to by the variable ``x``, you +used ``x_slot`` and to retrieve the byte-offset you used ``x_offset``. + In assignments (see below), we can even use local Solidity variables to assign to. Functions external to inline assembly can also be accessed: The assembly will @@ -340,17 +343,13 @@ changes during the call, and thus references to local variables will be wrong. .. code:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract C { uint b; function f(uint x) returns (uint r) { assembly { - b pop // remove the offset, we know it is zero - sload - x - mul - =: r // assign to return variable r + r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero } } } @@ -567,7 +566,7 @@ The following example implements the power function by square-and-multiply. .. code:: assembly { - function power(base, exponent) -> (result) { + function power(base, exponent) -> result { switch exponent 0: { result := 1 } 1: { result := base } @@ -702,12 +701,12 @@ The following assembly will be generated:: } default: { jump(invalidJumpLabel) } // memory allocator - function $allocate(size) -> (pos) { + function $allocate(size) -> pos { pos := mload(0x40) mstore(0x40, add(pos, size)) } // the contract function - function f(x) -> (y) { + function f(x) -> y { y := 1 for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) diff --git a/docs/bugs.json b/docs/bugs.json new file mode 100644 index 00000000..1a67d626 --- /dev/null +++ b/docs/bugs.json @@ -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" + } +]
\ No newline at end of file diff --git a/docs/bugs.rst b/docs/bugs.rst new file mode 100644 index 00000000..55771a35 --- /dev/null +++ b/docs/bugs.rst @@ -0,0 +1,61 @@ +.. index:: Bugs + +.. _known_bugs: + +################## +List of Known Bugs +################## + +Below, you can find a JSON-formatted list of some of the known security-relevant bugs in the +Solidity compiler. The file itself is hosted in the `Github repository +<https://github.com/ethereum/solidity/blob/develop/docs/bugs.json>`_. +The list stretches back as far as version 0.3.0, bugs known to be present only +in versions preceding that are not listed. + +There is another file called `bugs_by_version.json +<https://github.com/ethereum/solidity/blob/develop/docs/bugs_by_version.json>`_, +which can be used to check which bugs affect a specific version of the compiler. + +Contract source verification tools and also other tools interacting with +contracts should consult this list according to the following criteria: + + - It is mildly suspicious if a contract was compiled with a nightly + compiler version instead of a released version. This list does not keep + track of unreleased or nightly versions. + - It is also mildly suspicious if a contract was compiled with a version that was + not the most recent at the time the contract was created. For contracts + created from other contracts, you have to follow the creation chain + back to a transaction and use the date of that transaction as creation date. + - It is highly suspicious if a contract was compiled with a compiler that + contains a known bug and the contract was created at a time where a newer + compiler version containing a fix was already released. + +The JSON file of known bugs below is an array of objects, one for each bug, +with the following keys: + +name + Unique name given to the bug +summary + Short description of the bug +description + Detailed description of the bug +link + URL of a website with more detailed information, optional +introduced + The first published compiler version that contained the bug, optional +fixed + The first published compiler version that did not contain the bug anymore +publish + The date at which the bug became known publicly, optional +severity + Severity of the bug: low, medium, high. Takes into account + discoverability in contract tests, likelihood of occurrence and + potential damage by exploits. +conditions + Conditions that have to be met to trigger the bug. Currently, this + is an object that can contain a boolean value ``optimizer``, which + means that the optimizer has to be switched on to enable the bug. + If no conditions are given, assume that the bug is present. + +.. literalinclude:: bugs.json + :language: js diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json new file mode 100644 index 00000000..0f7346b4 --- /dev/null +++ b/docs/bugs_by_version.json @@ -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" + } +}
\ No newline at end of file diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index a2d7ce71..acef13b7 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -23,12 +23,12 @@ contract in order to become the "richest", inspired by `King of the Ether <https://www.kingoftheether.com/>`_. In the following contract, if you are usurped as the richest, -you will recieve the funds of the person who has gone on to +you will receive the funds of the person who has gone on to become the new richest. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract WithdrawalContract { address public richest; @@ -52,17 +52,12 @@ become the new richest. } } - function withdraw() returns (bool) { + function withdraw() { uint amount = pendingWithdrawals[msg.sender]; // Remember to zero the pending refund before // sending to prevent re-entrancy attacks pendingWithdrawals[msg.sender] = 0; - if (msg.sender.send(amount)) { - return true; - } else { - pendingWithdrawals[msg.sender] = amount; - return false; - } + msg.sender.transfer(amount); } } @@ -70,7 +65,7 @@ This is as opposed to the more intuitive sending pattern: :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract SendContract { address public richest; @@ -83,12 +78,8 @@ This is as opposed to the more intuitive sending pattern: function becomeRichest() payable returns (bool) { if (msg.value > mostSent) { - // Check if call succeeds to prevent an attacker - // from trapping the previous person's funds in - // this contract through a callstack attack - if (!richest.send(msg.value)) { - throw; - } + // This line can cause problems (explained below). + richest.transfer(msg.value); richest = msg.sender; mostSent = msg.value; return true; @@ -100,12 +91,16 @@ This is as opposed to the more intuitive sending pattern: Notice that, in this example, an attacker could trap the contract into an unusable state by causing ``richest`` to be -the address of a contract that has a fallback function -which consumes more than the 2300 gas stipend. That way, -whenever ``send`` is called to deliver funds to the -"poisoned" contract, it will cause execution to always fail -because there will not be enough gas to finish the execution -of the fallback function. +the address of a contract that has a fallback function +which fails (e.g. by using ``revert()`` or by just +conssuming more than the 2300 gas stipend). That way, +whenever ``transfer`` is called to deliver funds to the +"poisoned" contract, it will fail and thus also ``becomeRichest`` +will fail, with the contract being stuck forever. + +In contrast, if you use the "withdraw" pattern from the first example, +the attacker can only cause his or her own withdraw to fail and not the +rest of the contract's workings. .. index:: access;restricting @@ -135,7 +130,7 @@ restrictions highly readable. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract AccessRestriction { // These will be assigned at the construction @@ -152,8 +147,7 @@ restrictions highly readable. // a certain address. modifier onlyBy(address _account) { - if (msg.sender != _account) - throw; + require(msg.sender == _account); // Do not forget the "_;"! It will // be replaced by the actual function // body when the modifier is used. @@ -169,7 +163,7 @@ restrictions highly readable. } modifier onlyAfter(uint _time) { - if (now < _time) throw; + require(now >= _time); _; } @@ -190,8 +184,7 @@ restrictions highly readable. // This was dangerous before Solidity version 0.4.0, // where it was possible to skip the part after `_;`. modifier costs(uint _amount) { - if (msg.value < _amount) - throw; + require(msg.value >= _amount); _; if (msg.value > _amount) msg.sender.send(msg.value - _amount); @@ -232,7 +225,7 @@ reached at a certain point in **time**. An example for this is a blind auction contract which starts in the stage "accepting blinded bids", then transitions to "revealing bids" which is ended by -"determine auction autcome". +"determine auction outcome". .. index:: function;modifier @@ -276,7 +269,7 @@ function finishes. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract StateMachine { enum Stages { @@ -293,7 +286,7 @@ function finishes. uint public creationTime = now; modifier atStage(Stages _stage) { - if (stage != _stage) throw; + require(stage == _stage); _; } diff --git a/docs/contracts.rst b/docs/contracts.rst index 9145f016..8d7af2c1 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -27,7 +27,7 @@ From ``web3.js``, i.e. the JavaScript API, this is done as follows:: // Need to specify some source including contract name for the data param below - var source = "contract CONTRACT_NAME { function CONTRACT_NAME(unit a, uint b) {} }"; + var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }"; // The json abi array generated by the compiler var abiArray = [ @@ -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 { 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 // thrown. modifier onlyOwner { - if (msg.sender != owner) - throw; + require(msg.sender == owner); _; } } @@ -390,7 +389,7 @@ inheritable properties of contracts and may be overridden by derived contracts. contract Mutex { bool locked; modifier noReentrancy() { - if (locked) throw; + require(!locked); locked = true; _; locked = false; @@ -401,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 /// executes the statement `locked = false` in the modifier. function f() noReentrancy returns (uint) { - if (!msg.sender.call()) throw; + require(msg.sender.call()); 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 might have a side-effect on other memory objects are not. The built-in functions ``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 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. +.. index:: ! contract;interface, ! interface contract + +********** +Interfaces +********** + +Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions: + +#. Cannot inherit other contracts or interfaces. +#. Cannot define constructor. +#. Cannot define variables. +#. Cannot define structs. +#. Cannot define enums. + +Some of these restrictions might be lifted in the future. + +Interfaces are basically limited to what the Contract ABI can represent and the conversion between the ABI and +an Interface should be possible without any information loss. + +Interfaces are denoted by their own keyword: + +:: + + interface Token { + function transfer(address recipient, uint amount); + } + +Contracts can inherit interfaces as they would inherit other contracts. + .. index:: ! library, callcode, delegatecall .. _libraries: @@ -960,7 +988,7 @@ more advanced example to implement a set). :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; library Set { // We define a new struct datatype that will be used to @@ -1006,8 +1034,7 @@ more advanced example to implement a set). // The library functions can be called without a // specific instance of the library, since the // "instance" will be the current contract. - if (!Set.insert(knownValues, value)) - throw; + require(Set.insert(knownValues, value)); } // In this contract, we can also directly access knownValues.flags, if we want. } @@ -1101,7 +1128,7 @@ Restrictions for libraries in comparison to contracts: - No state variables - Cannot inherit nor be inherited -- Cannot recieve Ether +- Cannot receive Ether (These might be lifted at a later point.) @@ -1137,7 +1164,7 @@ available without having to add further code. Let us rewrite the set example from the :ref:`libraries` in this way:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; // This is the same code as before, just without comments library Set { @@ -1178,8 +1205,7 @@ Let us rewrite the set example from the // corresponding member functions. // The following function call is identical to // Set.insert(knownValues, value) - if (!knownValues.insert(value)) - throw; + require(knownValues.insert(value)); } } diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 25bf203b..a2d34274 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -113,8 +113,8 @@ actual contract has not been created yet. Functions of other contracts have to be called externally. For an external call, all function arguments have to be copied to memory. -When calling functions -of other contracts, the amount of Wei sent with the call and the gas can be specified:: +When calling functions of other contracts, the amount of Wei sent with the call and +the gas can be specified with special options ``.value()`` and ``.gas()``, respectively:: contract InfoFeed { function info() payable returns (uint ret) { return 42; } @@ -127,8 +127,8 @@ of other contracts, the amount of Wei sent with the call and the gas can be spec function callFeed() { feed.info.value(10).gas(800)(); } } -The modifier ``payable`` has to be used for ``info``, because otherwise, -we would not be able to send Ether to it in the call ``feed.info.value(10).gas(800)()``. +The modifier ``payable`` has to be used for ``info``, because otherwise, the `.value()` +option would not be available. Note that the expression ``InfoFeed(addr)`` performs an explicit type conversion stating that "we know that the type of the contract at the given address is ``InfoFeed``" and @@ -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 (due to out-of-stack, not enough balance or other problems), an exception 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. While a user-provided exception is generated in the following situations: + #. Calling ``throw``. #. Calling ``require`` with an argument that evaluates to ``false``. @@ -411,4 +412,4 @@ did not occur. Because we want to retain the atomicity of transactions, the safe If contracts are written so that ``assert`` is only used to test internal conditions and ``require`` is used in case of malformed input, a formal analysis tool that verifies that the invalid -opcode can never be reached can be used to check for the absence of errors assuming valid inputs.
\ No newline at end of file +opcode can never be reached can be used to check for the absence of errors assuming valid inputs. diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index 8a68ae5b..03ee8388 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -68,7 +68,7 @@ creator. Save it. Then ``selfdestruct(creator);`` to kill and return funds. Note that if you ``import "mortal"`` at the top of your contracts and declare ``contract SomeContract is mortal { ...`` and compile with a compiler that already -has it (which includes `browser-solidity <https://ethereum.github.io/browser-solidity/>`_), then +has it (which includes `Remix <https://remix.ethereum.org/>`_), then ``kill()`` is taken care of for you. Once a contract is "mortal", then you can ``contractname.kill.sendTransaction({from:eth.coinbase})``, just the same as my examples. @@ -665,8 +665,7 @@ What does the following strange check do in the Custom Token contract? :: - if (balanceOf[_to] + _value < balanceOf[_to]) - throw; + require((balanceOf[_to] + _value) >= balanceOf[_to]); Integers in Solidity (and most other machine-related programming languages) are restricted to a certain range. For ``uint256``, this is ``0`` up to ``2**256 - 1``. If the result of some operation on those numbers diff --git a/docs/index.rst b/docs/index.rst index fc1a4231..1312864a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,7 +2,7 @@ Solidity ======== Solidity is a contract-oriented, high-level language whose syntax is similar to that of JavaScript -and it is designed to target the Ethereum Virtual Machine. +and it is designed to target the Ethereum Virtual Machine (EVM). Solidity is statically typed, supports inheritance, libraries and complex user-defined types among other features. @@ -11,8 +11,8 @@ As you will see, it is possible to create contracts for voting, crowdfunding, blind auctions, multi-signature wallets and more. .. note:: - The best way to try out Solidity right now is using the - `Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_ + The best way to try out Solidity right now is using + `Remix <https://remix.ethereum.org/>`_ (it can take a while to load, please be patient). Useful links @@ -33,7 +33,7 @@ Useful links Available Solidity Integrations ------------------------------- -* `Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_ +* `Remix <https://remix.ethereum.org/>`_ Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. * `Ethereum Studio <https://live.ether.camp/>`_ @@ -48,12 +48,12 @@ Available Solidity Integrations * `Package for SublimeText — Solidity language syntax <https://packagecontrol.io/packages/Ethereum/>`_ Solidity syntax highlighting for SublimeText editor. -* `Atom Ethereum interface <https://github.com/gmtDevs/atom-ethereum-interface>`_ - Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (requires backend node). +* `Etheratom <https://github.com/0mkara/etheratom>`_ + Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (Backend node & VM compatible). * `Atom Solidity Linter <https://atom.io/packages/linter-solidity>`_ Plugin for the Atom editor that provides Solidity linting. - + * `Solium <https://github.com/duaraghav8/Solium/>`_ A commandline linter for Solidity which strictly follows the rules prescribed by the `Solidity Style Guide <http://solidity.readthedocs.io/en/latest/style-guide.html>`_. @@ -78,8 +78,8 @@ Discontinued: Solidity Tools -------------- -* `Dapple <https://github.com/nexusdev/dapple>`_ - Package and deployment manager for Solidity. +* `Dapp <https://dapp.readthedocs.io>`_ + Build tool, package manager, and deployment assistant for Solidity. * `Solidity REPL <https://github.com/raineorshine/solidity-repl>`_ Try Solidity instantly with a command-line Solidity console. @@ -90,6 +90,9 @@ Solidity Tools * `evmdis <https://github.com/Arachnid/evmdis>`_ EVM Disassembler that performs static analysis on the bytecode to provide a higher level of abstraction than raw EVM operations. +* `Doxity <https://github.com/DigixGlobal/doxity>`_ + Documentation Generator for Solidity. + Third-Party Solidity Parsers and Grammars ----------------------------------------- @@ -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 useful :ref:`example contracts <voting>` Remember that you can always try out the contracts -`in your browser <https://ethereum.github.io/browser-solidity>`_! +`in your browser <https://remix.ethereum.org>`_! The last and most extensive section will cover all aspects of Solidity in depth. @@ -136,5 +139,6 @@ Contents using-the-compiler.rst style-guide.rst common-patterns.rst + bugs.rst contributing.rst frequently-asked-questions.rst diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index fb405475..a2a3c3da 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -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 will use the latest release. -Browser-Solidity -================ +Remix +===== If you just want to try Solidity for small contracts, you -can try `browser-solidity <https://ethereum.github.io/browser-solidity>`_ +can try `Remix <https://remix.ethereum.org/>`_ which does not need any installation. If you want to use it without connection to the Internet, you can go to https://github.com/ethereum/browser-solidity/tree/gh-pages and @@ -31,7 +31,7 @@ npm / Node.js This is probably the most portable and most convenient way to install Solidity locally. A platform-independent JavaScript library is provided by compiling the C++ source -into JavaScript using Emscripten. 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. 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 version number - pre-release tag, usually set to ``develop.YYYY.MM.DD`` or ``nightly.YYYY.MM.DD`` - 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 4. the 0.5.0 release is made -This behaviour works well with the version pragma. +This behaviour works well with the :ref:`version pragma <version_pragma>`. diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index f02447cf..9001a08c 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -146,7 +146,7 @@ single account. The line ``event Sent(address from, address to, uint amount);`` declares a so-called "event" which is fired in the last line of the function -``send``. User interfaces (as well as server appliances of course) can +``send``. User interfaces (as well as server applications of course) can listen for those events being fired on the blockchain without much cost. As soon as it is fired, the listener will also receive the arguments ``from``, ``to`` and ``amount``, which makes it easy to track @@ -161,7 +161,7 @@ transactions. In order to listen for this event, you would use :: "Sender: " + Coin.balances.call(result.args.from) + "Receiver: " + Coin.balances.call(result.args.to)); } - } + }) Note how the automatically generated function ``balances`` is called from the user interface. diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index 1e27b7c0..715b29ae 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -7,6 +7,8 @@ and pragma directives. .. index:: ! pragma, version +.. _version_pragma: + Version Pragma ============== @@ -24,7 +26,7 @@ The version pragma is used as follows:: pragma solidity ^0.4.0; Such a source file will not compile with a compiler earlier than version 0.4.0 -and it will also not work on a compiler starting form version 0.5.0 (this +and it will also not work on a compiler starting from version 0.5.0 (this second condition is added by using ``^``). The idea behind this is that there will be no breaking changes until version ``0.5.0``, so we can always be sure that our code will compile the way we intended it to. We do not fix @@ -151,9 +153,9 @@ remapping ``=/``. If there are multiple remappings that lead to a valid file, the remapping with the longest common prefix is chosen. -**browser-solidity**: +**Remix**: -The `browser-based compiler <https://ethereum.github.io/browser-solidity>`_ +`Remix <https://remix.ethereum.org/>`_ provides an automatic remapping for github and will also automatically retrieve the file over the network: You can import the iterable mapping by e.g. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 2865d884..914dfacd 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -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 ============================================= diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 77e1bf08..33c613d8 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -22,7 +22,11 @@ you should be more careful. This section will list some pitfalls and general security recommendations but can, of course, never be complete. Also, keep in mind that even if your smart contract code is bug-free, the compiler or the platform itself might -have a bug. +have a bug. A list of some publicly known security-relevant bugs of the compiler +can be found in the +:ref:`list of known bugs<known_bugs>`, which is also machine-readable. Note +that there is a bug bounty program that covers the code generator of the +Solidity compiler. As always, with open source documentation, please help us extend this section (especially, some examples would not hurt)! @@ -75,7 +79,7 @@ outlined further below: :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract Fund { /// Mapping of ether shares of the contract. @@ -84,8 +88,7 @@ outlined further below: function withdraw() { var share = shares[msg.sender]; shares[msg.sender] = 0; - if (!msg.sender.send(share)) - throw; + msg.sender.transfer(share); } } @@ -117,25 +120,27 @@ Sending and Receiving Ether During the execution of the fallback function, the contract can only rely on the "gas stipend" (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way. To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function - (for example in the "details" section in browser-solidity). + (for example in the "details" section in Remix). - There is a way to forward more gas to the receiving contract using - ``addr.call.value(x)()``. This is essentially the same as ``addr.send(x)``, + ``addr.call.value(x)()``. This is essentially the same as ``addr.transfer(x)``, only that it forwards all remaining gas and opens up the ability for the - recipient to perform more expensive actions. This might include calling back + recipient to perform more expensive actions (and it only returns a failure code + and does not automatically propagate the error). This might include calling back into the sending contract or other state changes you might not have thought of. So it allows for great flexibility for honest users but also for malicious actors. -- If you want to send Ether using ``address.send``, there are certain details to be aware of: +- If you want to send Ether using ``address.transfer``, there are certain details to be aware of: 1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract. 2. Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call - depth, they can force the transfer to fail; make sure to always check the return value of ``send``. Better yet, + depth, they can force the transfer to fail; take this possibility into account or use ``send`` and make sure to always check its return value. Better yet, write your contract using a pattern where the recipient can withdraw Ether instead. 3. Sending Ether can also fail because the execution of the recipient contract - requires more than the allotted amount of gas (explicitly by using ``throw`` or + requires more than the allotted amount of gas (explicitly by using ``require``, + ``assert``, ``revert``, ``throw`` or because the operation is just too expensive) - it "runs out of gas" (OOG). - If the return value of ``send`` is checked, this might provide a + If you use ``transfer`` or ``send`` with a return value check, this might provide a means for the recipient to block progress in the sending contract. Again, the best practice here is to use a :ref:`"withdraw" pattern instead of a "send" pattern <withdrawal_pattern>`. @@ -158,7 +163,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; // THIS CONTRACT CONTAINS A BUG - DO NOT USE contract TxUserWallet { @@ -168,9 +173,9 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like owner = msg.sender; } - function transfer(address dest, uint amount) { - if (tx.origin != owner) { throw; } - if (!dest.call.value(amount)()) throw; + function transferTo(address dest, uint amount) { + require(tx.origin == owner); + dest.transfer(amount); } } @@ -188,7 +193,7 @@ Now someone tricks you into sending ether to the address of this attack wallet: } function() { - TxUserWallet(msg.sender).transfer(owner, msg.sender.balance); + TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); } } diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 7e08c6b4..4ed474df 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -36,7 +36,7 @@ of votes. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; /// @title Voting with delegation. contract Ballot { @@ -51,8 +51,7 @@ of votes. } // This is a type for a single proposal. - struct Proposal - { + struct Proposal { bytes32 name; // short name (up to 32 bytes) uint voteCount; // number of accumulated votes } @@ -88,14 +87,14 @@ of votes. // Give `voter` the right to vote on this ballot. // May only be called by `chairperson`. function giveRightToVote(address voter) { - if (msg.sender != chairperson || voters[voter].voted) { - // `throw` terminates and reverts all changes to - // the state and to Ether balances. It is often - // a good idea to use this if functions are - // called incorrectly. But watch out, this - // will also consume all provided gas. - throw; - } + // If the argument of `require` evaluates to `false`, + // it terminates and reverts all changes to + // the state and to Ether balances. It is often + // a good idea to use this if functions are + // called incorrectly. But watch out, this + // will currently also consume all provided gas + // (this is planned to change in the future). + require((msg.sender == chairperson) && !voters[voter].voted); voters[voter].weight = 1; } @@ -103,12 +102,10 @@ of votes. function delegate(address to) { // assigns reference Voter sender = voters[msg.sender]; - if (sender.voted) - throw; + require(!sender.voted); // Self-delegation is not allowed. - if (to == msg.sender) - throw; + require(to != msg.sender); // Forward the delegation as long as // `to` also delegated. @@ -122,8 +119,7 @@ of votes. to = voters[to].delegate; // We found a loop in the delegation, not allowed. - if (to == msg.sender) - throw; + require(to != msg.sender); } // Since `sender` is a reference, this @@ -146,8 +142,7 @@ of votes. /// to proposal `proposals[proposal].name`. function vote(uint proposal) { Voter sender = voters[msg.sender]; - if (sender.voted) - throw; + require(!sender.voted); sender.voted = true; sender.vote = proposal; @@ -219,7 +214,7 @@ activate themselves. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract SimpleAuction { // Parameters of the auction. Times are either @@ -270,22 +265,21 @@ activate themselves. // the transaction. The keyword payable // is required for the function to // be able to receive Ether. - if (now > auctionStart + biddingTime) { - // Revert the call if the bidding - // period is over. - throw; - } - if (msg.value <= highestBid) { - // If the bid is not higher, send the - // money back. - throw; - } + + // Revert the call if the bidding + // period is over. + require(now <= (auctionStart + biddingTime)); + + // If the bid is not higher, send the + // money back. + require(msg.value > highestBid); + if (highestBidder != 0) { // Sending back the money by simply using // highestBidder.send(highestBid) is a security risk // because it can be prevented by the caller by e.g. // raising the call stack to 1023. It is always safer - // to let the recipient withdraw their money themselves. + // to let the recipients withdraw their money themselves. pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; @@ -328,18 +322,15 @@ activate themselves. // external contracts. // 1. Conditions - if (now <= auctionStart + biddingTime) - throw; // auction did not yet end - if (ended) - throw; // this function has already been called + require(now >= (auctionStart + biddingTime)); // auction did not yet end + require(!ended); // this function has already been called // 2. Effects ended = true; AuctionEnded(highestBidder, highestBid); // 3. Interaction - if (!beneficiary.send(highestBid)) - throw; + beneficiary.transfer(highestBid); } } @@ -382,7 +373,7 @@ high or low invalid bids. :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract BlindAuction { struct Bid { @@ -410,8 +401,8 @@ high or low invalid bids. /// functions. `onlyBefore` is applied to `bid` below: /// The new function body is the modifier's body where /// `_` is replaced by the old function body. - modifier onlyBefore(uint _time) { if (now >= _time) throw; _; } - modifier onlyAfter(uint _time) { if (now <= _time) throw; _; } + modifier onlyBefore(uint _time) { require(now < _time); _; } + modifier onlyAfter(uint _time) { require(now > _time); _; } function BlindAuction( uint _biddingTime, @@ -455,13 +446,9 @@ high or low invalid bids. onlyBefore(revealEnd) { uint length = bids[msg.sender].length; - if ( - _values.length != length || - _fake.length != length || - _secret.length != length - ) { - throw; - } + require(_values.length == length); + require(_fake.length == length); + require(_secret.length == length); uint refund; for (uint i = 0; i < length; i++) { @@ -482,8 +469,7 @@ high or low invalid bids. // the same deposit. bid.blindedBid = 0; } - if (!msg.sender.send(refund)) - throw; + msg.sender.transfer(refund); } // This is an "internal" function which means that it @@ -528,14 +514,12 @@ high or low invalid bids. function auctionEnd() onlyAfter(revealEnd) { - if (ended) - throw; + require(!ended); AuctionEnded(highestBidder, highestBid); ended = true; // We send all the money we have, because some // of the refunds might have failed. - if (!beneficiary.send(this.balance)) - throw; + beneficiary.transfer(this.balance); } } @@ -547,7 +531,7 @@ Safe Remote Purchase :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract Purchase { uint public value; @@ -559,26 +543,26 @@ Safe Remote Purchase function Purchase() payable { seller = msg.sender; value = msg.value / 2; - if (2 * value != msg.value) throw; + require((2 * value) == msg.value); } - modifier require(bool _condition) { - if (!_condition) throw; + modifier condition(bool _condition) { + require(_condition); _; } modifier onlyBuyer() { - if (msg.sender != buyer) throw; + require(msg.sender == buyer); _; } modifier onlySeller() { - if (msg.sender != seller) throw; + require(msg.sender == seller); _; } modifier inState(State _state) { - if (state != _state) throw; + require(state == _state); _; } @@ -595,8 +579,7 @@ Safe Remote Purchase { aborted(); state = State.Inactive; - if (!seller.send(this.balance)) - throw; + seller.transfer(this.balance); } /// Confirm the purchase as buyer. @@ -605,7 +588,7 @@ Safe Remote Purchase /// is called. function confirmPurchase() inState(State.Created) - require(msg.value == 2 * value) + condition(msg.value == (2 * value)) payable { purchaseConfirmed(); @@ -624,10 +607,12 @@ Safe Remote Purchase // otherwise, the contracts called using `send` below // can call in again here. state = State.Inactive; - // This actually allows both the buyer and the seller to - // block the refund. - if (!buyer.send(value) || !seller.send(this.balance)) - throw; + + // NOTE: This actually allows both the buyer and the seller to + // block the refund - the withdraw pattern should be used. + + buyer.transfer(value); + seller.transfer(this.balance)); } } diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 24ef69a6..224eb368 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -62,13 +62,13 @@ Function modifiers can be used to amend the semantics of functions in a declarat :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract Purchase { address public seller; modifier onlySeller() { // Modifier - if (msg.sender != seller) throw; + require(msg.sender == seller); _; } diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 9aae3d7b..0742d2e9 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -164,7 +164,7 @@ Functions should be grouped according to their visibility and ordered: - internal - private -Within a grouping, place the `constant` functions last. +Within a grouping, place the ``constant`` functions last. Yes:: diff --git a/docs/types.rst b/docs/types.rst index 243a9a0c..c400aecb 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -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 sign extends. Shifting by a negative amount throws a runtime exception. +.. warning:: + The results produced by shift right of negative values of signed integer types is different from those produced + by other programming languages. In Solidity, shift right maps to division so the shifted negative values + are going to be rounded towards zero (truncated). In other programming languages the shift right of negative values + works like division with rounding down (towards negative infinity). + .. index:: address, balance, send, call, callcode, delegatecall, transfer .. _address: @@ -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. +The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``. + .. note:: All contracts inherit the members of address, so it is possible to query the balance of the current contract using ``this.balance``. @@ -232,7 +240,7 @@ a non-rational number). Integer literals and rational number literals belong to number literal types. Moreover, all number literal expressions (i.e. the expressions that contain only number literals and operators) belong to number literal - types. So the number literal expressions `1 + 2` and `2 + 1` both + types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both belong to the same number literal type for the rational number three. .. note:: @@ -261,7 +269,7 @@ a non-rational number). String Literals --------------- -String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; `"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. +String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. String literals support escape characters, such as ``\n``, ``\xNN`` and ``\uNNNN``. ``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence. @@ -412,7 +420,7 @@ Example that shows how to use internal function types:: Another example that uses external function types:: - pragma solidity ^0.4.5; + pragma solidity ^0.4.11; contract Oracle { struct Request { @@ -437,7 +445,7 @@ Another example that uses external function types:: oracle.query("USD", this.oracleResponse); } function oracleResponse(bytes response) { - if (msg.sender != address(oracle)) throw; + require(msg.sender == address(oracle)); // Use the data } } @@ -714,7 +722,7 @@ shown in the following example: :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract CrowdFunding { // Defines a new type with two fields. @@ -755,8 +763,7 @@ shown in the following example: return false; uint amount = c.amount; c.amount = 0; - if (!c.beneficiary.send(amount)) - throw; + c.beneficiary.transfer(amount); return true; } } diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 49fe5d84..246cc564 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -22,8 +22,8 @@ unit and units are considered naively in the following way: * ``1 minutes == 60 seconds`` * ``1 hours == 60 minutes`` * ``1 days == 24 hours`` - * ``1 weeks = 7 days`` - * ``1 years = 365 days`` + * ``1 weeks == 7 days`` + * ``1 years == 365 days`` Take care if you perform calendar calculations using these units, because not every year equals 365 days and not even every day has 24 hours @@ -93,13 +93,14 @@ Mathematical and Cryptographic Functions ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments ``sha3(...) returns (bytes32)``: - alias to `keccak256()` + alias to ``keccak256()`` ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments ``ripemd160(...) returns (bytes20)``: compute RIPEMD-160 hash of the (tightly packed) arguments ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover the address associated with the public key from elliptic curve signature or return zero on error + (`example usage <https://ethereum.stackexchange.com/q/1777/222>`_) ``revert()``: abort execution and revert state changes @@ -128,17 +129,23 @@ Address Related ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei -``<address>.send(uint256 amount) returns (bool)``: - send given amount of Wei to :ref:`address`, returns ``false`` on failure ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure +``<address>.send(uint256 amount) returns (bool)``: + send given amount of Wei to :ref:`address`, returns ``false`` on failure +``<address>.call(...) returns (bool)``: + issue low-level ``CALL``, returns ``false`` on failure +``<address>.callcode(...) returns (bool)``: + issue low-level ``CALLCODE``, returns ``false`` on failure +``<address>.delegatecall(...) returns (bool)``: + issue low-level ``DELEGATECALL``, returns ``false`` on failure For more information, see the section on :ref:`address`. .. warning:: There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order - to make safe Ether transfers, always check the return value of ``send`` or even better: + to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: Use a pattern where the recipient withdraws the money. .. index:: this, selfdestruct diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 08f18132..7aa997f8 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -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. +For security reasons the compiler has restrictions what directories it can access. Paths (and their subdirectories) of source files specified on the commandline and paths defined by remappings are allowed for import statements, but everything else is rejected. Additional paths (and their subdirectories) can be allowed via the ``--allow-paths /sample/path,/another/sample/path`` switch. + If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points: Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``. If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case. +If ``solc`` is called with the option ``--standard-json``, it will expect a JSON input (as explained below) on the standard input, and return a JSON output on the standard output. + .. _compiler-api: Compiler Input and Output JSON Description ****************************************** -.. 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, 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: // abi - ABI - // ast - AST of all source files - // why3 - Why3 translated output + // ast - AST of all source files (not supported atm) + // legacyAST - legacy AST of all source files // devdoc - Developer documentation (natspec) // userdoc - User documentation (natspec) // metadata - Metadata - // evm.ir - New assembly format before desugaring + // ir - New assembly format before desugaring // evm.assembly - New assembly format after desugaring - // evm.legacyAssemblyJSON - Old-style assembly format in JSON - // evm.opcodes - Opcodes list + // evm.legacyAssembly - Old-style assembly format in JSON + // evm.bytecode.object - Bytecode object + // evm.bytecode.opcodes - Opcodes list + // evm.bytecode.sourceMap - Source mapping (useful for debugging) + // evm.bytecode.linkReferences - Link references (if unlinked object) + // evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode) // evm.methodIdentifiers - The list of function hashes // evm.gasEstimates - Function gas estimates - // evm.bytecode - Bytecode - // evm.deployedBytecode - Deployed bytecode - // evm.sourceMap - Source mapping (useful for debugging) // ewasm.wast - eWASM S-expressions format (not supported atm) // ewasm.wasm - eWASM binary format (not supported atm) + // + // Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every + // target part of that output. + // outputSelection: { // Enable the metadata and bytecode outputs of every single contract. "*": { @@ -148,9 +153,9 @@ Input Description "*": { "*": [ "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 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", // Mandatory ("error" or "warning") severity: "error", // Mandatory message: "Invalid keyword" + // Optional: the message formatted with source location + formattedMessage: "sourceFile.sol:100: Invalid keyword" } ], // This contains the file-level outputs. In can be limited/filtered by the outputSelection settings. @@ -188,7 +195,9 @@ Output Description // Identifier (used in source maps) id: 1, // 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. @@ -199,17 +208,26 @@ Output Description // The Ethereum Contract ABI. If empty, it is represented as an empty array. // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI abi: [], + // See the Metadata Output documentation (serialised JSON string) + metadata: "{...}", + // User documentation (natspec) + userdoc: {}, + // Developer documentation (natspec) + devdoc: {}, + // Intermediate representation (string) + ir: "", + // EVM-related outputs evm: { - // Intermediate representation (string) - ir: "", // Assembly (string) assembly: "", - // Old-style assembly (string) - legacyAssemblyJSON: [], + // Old-style assembly (object) + legacyAssembly: {}, // Bytecode and related details. bytecode: { // The bytecode as a hex string. object: "00fe", + // Opcodes list (string) + opcodes: "", // The source mapping as a string. See the source mapping definition. sourceMap: "", // If given, this is an unlinked object. @@ -222,45 +240,36 @@ Output Description ] } } - } + }, // The same layout as above. deployedBytecode: { }, - // Opcodes list (string) - opcodes: "", // The list of function hashes methodIdentifiers: { - "5c19a95c": "delegate(address)", + "delegate(address)": "5c19a95c" }, // Function gas estimates gasEstimates: { creation: { - dataCost: 420000, - // -1 means infinite (aka. unknown) - executionCost: -1 + codeDepositCost: "420000", + executionCost: "infinite", + totalCost: "infinite" }, external: { - "delegate(address)": 25000 + "delegate(address)": "25000" }, internal: { - "heavyLifting()": -1 + "heavyLifting()": "infinite" } } }, - // See the Metadata Output documentation - metadata: {}, + // eWASM related outputs ewasm: { // S-expressions format wast: "", // Binary format (hex string) wasm: "" - }, - // User documentation (natspec) - userdoc: {}, - // Developer documentation (natspec) - devdoc: {} + } } } - }, - // Why3 output (string) - why3: "" + } } diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index f12e8aa8..ea061a30 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -205,7 +205,8 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con { _out << _prefix << "stop" << endl; 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) { diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 26d9fded..e69b5932 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -159,18 +159,25 @@ string AssemblyItem::toAssemblyText() const text = toHex(toCompactBigEndian(data(), 1), 1, HexPrefix::Add); break; case PushString: - assertThrow(false, AssemblyException, "Push string assembly output not implemented."); + text = string("data_") + toHex(data()); break; 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; + } 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())) + ":"; break; case PushData: - assertThrow(false, AssemblyException, "Push data not implemented."); + text = string("data_") + toHex(data()); break; case PushSub: text = string("dataOffset(sub_") + to_string(size_t(data())) + ")"; diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index a1dfd21c..0c093ebf 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -203,8 +203,13 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) u256 powerOfTwo = u256(1) << bits; u256 upperPart = _value >> bits; bigint lowerPart = _value & (powerOfTwo - 1); - if (abs(powerOfTwo - lowerPart) < lowerPart) + if ((powerOfTwo - lowerPart) < lowerPart) + { lowerPart = lowerPart - powerOfTwo; // make it negative + upperPart++; + } + if (upperPart == 0) + continue; if (abs(lowerPart) >= (powerOfTwo >> 8)) continue; @@ -212,7 +217,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) if (lowerPart != 0) newRoutine += findRepresentation(u256(abs(lowerPart))); newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP}; - if (upperPart != 1 && upperPart != 0) + if (upperPart != 1) newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; if (lowerPart > 0) newRoutine += AssemblyItems{Instruction::ADD}; @@ -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) { size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP); diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 4f12c49f..85bdabac 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -21,10 +21,14 @@ #pragma once -#include <vector> +#include <libevmasm/Exceptions.h> + +#include <libdevcore/Assertions.h> #include <libdevcore/CommonData.h> #include <libdevcore/CommonIO.h> +#include <vector> + namespace dev { namespace eth @@ -130,6 +134,11 @@ public: ConstantOptimisationMethod(_params, _value) { m_routine = findRepresentation(m_value); + assertThrow( + checkRepresentation(m_value, m_routine), + OptimizerException, + "Invalid constant expression created." + ); } virtual bigint gasNeeded() override { return gasNeeded(m_routine); } @@ -141,6 +150,8 @@ public: protected: /// Tries to recursively find a way to compute @a _value. AssemblyItems findRepresentation(u256 const& _value); + /// Recomputes the value from the calculated representation and checks for correctness. + bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine); bigint gasNeeded(AssemblyItems const& _routine); /// Counter for the complexity of optimization, will stop when it reaches zero. diff --git a/libevmasm/EVMSchedule.h b/libevmasm/EVMSchedule.h index ce9003bd..65d307ae 100644 --- a/libevmasm/EVMSchedule.h +++ b/libevmasm/EVMSchedule.h @@ -34,7 +34,7 @@ struct EVMSchedule unsigned expByteGas = 10; unsigned sha3Gas = 30; unsigned sha3WordGas = 6; - unsigned sloadGas = 50; + unsigned sloadGas = 200; unsigned sstoreSetGas = 20000; unsigned sstoreResetGas = 5000; unsigned sstoreRefundGas = 15000; diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index a0adc35d..f5fd00ea 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -149,6 +149,10 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ } break; } + case Instruction::SELFDESTRUCT: + gas = GasCosts::selfdestructGas; + gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. + break; case Instruction::CREATE: if (_includeExternalCosts) // 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::Ext: return GasCosts::tier6Gas; case Tier::Special: return GasCosts::tier7Gas; + case Tier::ExtCode: return GasCosts::extCodeGas; + case Tier::Balance: return GasCosts::balanceGas; default: break; } assertThrow(false, OptimizerException, "Invalid gas tier."); diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 8ade838a..3169ff2a 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -44,11 +44,13 @@ namespace GasCosts static unsigned const tier5Gas = 10; static unsigned const tier6Gas = 20; static unsigned const tier7Gas = 0; + static unsigned const extCodeGas = 700; + static unsigned const balanceGas = 400; static unsigned const expGas = 10; - static unsigned const expByteGas = 10; + static unsigned const expByteGas = 50; static unsigned const sha3Gas = 30; static unsigned const sha3WordGas = 6; - static unsigned const sloadGas = 50; + static unsigned const sloadGas = 200; static unsigned const sstoreSetGas = 20000; static unsigned const sstoreResetGas = 5000; static unsigned const sstoreRefundGas = 15000; @@ -57,10 +59,11 @@ namespace GasCosts static unsigned const logDataGas = 8; static unsigned const logTopicGas = 375; static unsigned const createGas = 32000; - static unsigned const callGas = 40; + static unsigned const callGas = 700; static unsigned const callStipend = 2300; static unsigned const callValueTransferGas = 9000; static unsigned const callNewAccountGas = 25000; + static unsigned const selfdestructGas = 5000; static unsigned const selfdestructRefundGas = 24000; static unsigned const memoryGas = 3; static unsigned const quadCoeffDiv = 512; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index de6630f3..5e92c6e6 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -191,7 +191,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo = { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } }, { Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::Special } }, { 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::CALLER, { "CALLER", 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::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } }, { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } }, - { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::Ext } }, - { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::Ext } }, + { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::ExtCode } }, + { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtCode } }, { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } }, { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } }, { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } }, @@ -297,7 +297,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo = { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, { Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } }, { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, - { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } } + { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Special } } }; void dev::solidity::eachInstruction( diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index d79ec969..192fe090 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -237,6 +237,8 @@ enum class Tier : unsigned Mid, // 8, Mid High, // 10, Slow Ext, // 20, Ext + ExtCode, // 700, Extcode + Balance, // 400, Balance Special, // multiparam or otherwise special Invalid // Invalid. }; diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index d8f1603a..a54b8c8d 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -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>("now", make_shared<IntegerType>(256)), make_shared<MagicVariableDeclaration>("suicide", - make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)), + make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)), make_shared<MagicVariableDeclaration>("selfdestruct", - make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)), + make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)), make_shared<MagicVariableDeclaration>("addmod", - make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::AddMod)), + make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod)), make_shared<MagicVariableDeclaration>("mulmod", - make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::MulMod)), + make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod)), make_shared<MagicVariableDeclaration>("sha3", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), make_shared<MagicVariableDeclaration>("keccak256", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), make_shared<MagicVariableDeclaration>("log0", - make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)), + make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), make_shared<MagicVariableDeclaration>("log1", - make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)), + make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), make_shared<MagicVariableDeclaration>("log2", - make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)), + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)), make_shared<MagicVariableDeclaration>("log3", - make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)), + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)), make_shared<MagicVariableDeclaration>("log4", - make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)), + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), make_shared<MagicVariableDeclaration>("sha256", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true)), make_shared<MagicVariableDeclaration>("ecrecover", - make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), + make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover)), make_shared<MagicVariableDeclaration>("ripemd160", - make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), + make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true)), make_shared<MagicVariableDeclaration>("assert", - make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert)), + make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert)), 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<FunctionType>(strings(), strings(), FunctionType::Location::Revert))}) + make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert))}) { } diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index cae77c74..e8da3ca4 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -58,6 +58,9 @@ void PostTypeChecker::endVisit(ContractDefinition const&) for (auto declaration: m_constVariables) if (auto identifier = findCycle(declaration)) typeError(declaration->location(), "The value of the constant " + declaration->name() + " has a cyclic dependency via " + identifier->name() + "."); + + m_constVariables.clear(); + m_constVariableDependencies.clear(); } bool PostTypeChecker::visit(VariableDeclaration const& _variable) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 37bcb2d9..9433976a 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -25,9 +25,12 @@ #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/analysis/ConstantEvaluator.h> -#include <libsolidity/inlineasm/AsmCodeGen.h> +#include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/inlineasm/AsmData.h> +#include <boost/algorithm/string.hpp> + using namespace std; using namespace dev; using namespace dev::solidity; @@ -158,21 +161,40 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) { - // We need to perform a full code generation pass here as inline assembly does not distinguish - // reference resolution and code generation. // Errors created in this stage are completely ignored because we do not yet know // the type and size of external identifiers, which would result in false errors. + // The only purpose of this step is to fill the inline assembly annotation with + // external references. ErrorList errorsIgnored; - assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored); - codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) { + assembly::ExternalIdentifierAccess::Resolver resolver = + [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) { auto declarations = m_resolver.nameFromCurrentScope(_identifier.name); + bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot"); + bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset"); + if (isSlot || isOffset) + { + // special mode to access storage variables + if (!declarations.empty()) + // the special identifier exists itself, we should not allow that. + return size_t(-1); + string realName = _identifier.name.substr(0, _identifier.name.size() - ( + isSlot ? + string("_slot").size() : + string("_offset").size() + )); + declarations = m_resolver.nameFromCurrentScope(realName); + } if (declarations.size() != 1) - return false; - _inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front(); - // At this stage we neither know the code to generate nor the stack size of the identifier, - // so we do not modify assembly. - return true; - }); + return size_t(-1); + _inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot; + _inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset; + _inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front(); + return size_t(1); + }; + + // Will be re-generated later with correct information + assembly::AsmAnalysisInfo analysisInfo; + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index c39f874e..369376fa 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -48,13 +48,65 @@ void StaticAnalyzer::endVisit(ContractDefinition const&) bool StaticAnalyzer::visit(FunctionDefinition const& _function) { + if (_function.isImplemented()) + m_currentFunction = &_function; + else + solAssert(!m_currentFunction, ""); + solAssert(m_localVarUseCount.empty(), ""); m_nonPayablePublic = _function.isPublic() && !_function.isPayable(); return true; } void StaticAnalyzer::endVisit(FunctionDefinition const&) { + m_currentFunction = nullptr; m_nonPayablePublic = false; + for (auto const& var: m_localVarUseCount) + if (var.second == 0) + 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) diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index 0cb961bd..ab72e7d9 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -60,6 +60,10 @@ private: virtual bool visit(FunctionDefinition const& _function) override; virtual void endVisit(FunctionDefinition const& _function) override; + virtual bool visit(ExpressionStatement const& _statement) override; + virtual bool visit(VariableDeclaration const& _variable) override; + virtual bool visit(Identifier const& _identifier) override; + virtual bool visit(Return const& _return) override; virtual bool visit(MemberAccess const& _memberAccess) override; ErrorList& m_errors; @@ -69,6 +73,11 @@ private: /// Flag that indicates whether a public function does not contain the "payable" modifier. bool m_nonPayablePublic = false; + + /// Number of uses of each (named) local variable in a function, counter is initialized with zero. + std::map<VariableDeclaration const*, int> m_localVarUseCount; + + FunctionDefinition const* m_currentFunction = nullptr; }; } diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 89014133..94e82a87 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -32,6 +32,16 @@ bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot) return Error::containsOnlyWarnings(m_errors); } +void SyntaxChecker::warning(SourceLocation const& _location, string const& _description) +{ + auto err = make_shared<Error>(Error::Type::Warning); + *err << + errinfo_sourceLocation(_location) << + errinfo_comment(_description); + + m_errors.push_back(err); +} + void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string const& _description) { auto err = make_shared<Error>(Error::Type::SyntaxError); @@ -148,6 +158,13 @@ bool SyntaxChecker::visit(Break const& _breakStatement) 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&) { m_placeholderFound = true; diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 308e128b..8d7dcdd3 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -32,6 +32,7 @@ namespace solidity * The module that performs syntax analysis on the AST: * - whether continue/break is in a for/while loop. * - whether a modifier contains at least one '_' + * - issues deprecation warnings for unary '+' */ class SyntaxChecker: private ASTConstVisitor { @@ -43,6 +44,7 @@ public: private: /// Adds a new error to the list of errors. + void warning(SourceLocation const& _location, std::string const& _description); void syntaxError(SourceLocation const& _location, std::string const& _description); virtual bool visit(SourceUnit const& _sourceUnit) override; @@ -60,6 +62,8 @@ private: virtual bool visit(Continue const& _continueStatement) override; virtual bool visit(Break const& _breakStatement) override; + virtual bool visit(UnaryOperation const& _operation) override; + virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; ErrorList& m_errors; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 512493cd..38cdc1f8 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -24,8 +24,9 @@ #include <memory> #include <boost/range/adaptor/reversed.hpp> #include <libsolidity/ast/AST.h> -#include <libevmasm/Assembly.h> // needed for inline assembly -#include <libsolidity/inlineasm/AsmCodeGen.h> +#include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> +#include <libsolidity/inlineasm/AsmData.h> using namespace std; using namespace dev; @@ -64,8 +65,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { m_scope = &_contract; - // We force our own visiting order here. - //@TODO structs will be visited again below, but it is probably fine. + // We force our own visiting order here. The structs have to be excluded below. + set<ASTNode const*> visited; + for (auto const& s: _contract.definedStructs()) + visited.insert(s); ASTNode::listAccept(_contract.definedStructs(), *this); ASTNode::listAccept(_contract.baseContracts(), *this); @@ -113,7 +116,9 @@ bool TypeChecker::visit(ContractDefinition const& _contract) _contract.annotation().isFullyImplemented = false; } - ASTNode::listAccept(_contract.subNodes(), *this); + for (auto const& n: _contract.subNodes()) + if (!visited.count(n.get())) + n->accept(*this); checkContractExternalTypeClashes(_contract); // check for hash collisions in function signatures @@ -183,13 +188,20 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>; map<string, vector<FunTypeAndFlag>> functions; + bool allBaseConstructorsImplemented = true; // Search from base to derived for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (FunctionDefinition const* function: contract->definedFunctions()) { // Take constructors out of overload hierarchy if (function->isConstructor()) + { + if (!function->isImplemented()) + // Base contract's constructor is not fully implemented, no way to get + // out of this. + allBaseConstructorsImplemented = false; continue; + } auto& overloads = functions[function->name()]; FunctionTypePointer funType = make_shared<FunctionType>(*function); auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) @@ -207,6 +219,9 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont it->second = true; } + if (!allBaseConstructorsImplemented) + _contract.annotation().isFullyImplemented = false; + // Set to not fully implemented if at least one flag is false. for (auto const& it: functions) for (auto const& funAndFlag: it.second) @@ -354,6 +369,9 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_inheritance.location(), "Interfaces cannot inherit."); + if (base->isLibrary()) typeError(_inheritance.location(), "Libraries cannot be inherited from."); @@ -396,6 +414,9 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) 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()) if (!type(*member)->canBeStored()) 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 : 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()) _function.body().accept(*this); return false; @@ -458,6 +488,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function) bool TypeChecker::visit(VariableDeclaration const& _variable) { + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_variable.location(), "Variables cannot be declared in interfaces."); + // Variables can be declared without type (with "var"), in which case the first assignment // sets the type. // Note that assignments before the first declaration are legal because of the special scoping @@ -504,6 +537,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) 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( ModifierInvocation const& _modifier, vector<ContractDefinition const*> const& _bases @@ -582,72 +622,98 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) void TypeChecker::endVisit(FunctionTypeName const& _funType) { FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type); - if (fun.location() == FunctionType::Location::External) + if (fun.kind() == FunctionType::Kind::External) if (!fun.canBeUsedExternally(false)) typeError(_funType.location(), "External function type uses internal types."); } 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. - auto identifierAccess = [&]( + // We run the resolve step again regardless. + assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&]( assembly::Identifier const& _identifier, - eth::Assembly& _assembly, - assembly::CodeGenerator::IdentifierContext _context + assembly::IdentifierContext _context ) { auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); if (ref == _inlineAssembly.annotation().externalReferences.end()) - return false; - Declaration const* declaration = ref->second; + return size_t(-1); + Declaration const* declaration = ref->second.declaration; solAssert(!!declaration, ""); - if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + if (auto var = dynamic_cast<VariableDeclaration const*>(declaration)) { - solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); - unsigned pushes = 0; - if (dynamic_cast<FunctionDefinition const*>(declaration)) - pushes = 1; - else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration)) + if (ref->second.isSlot || ref->second.isOffset) { - if (var->isConstant()) - fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); - if (var->isLocalVariable()) - pushes = var->type()->sizeOnStack(); - else if (!var->type()->isValueType()) - pushes = 1; - else - pushes = 2; // slot number, intra slot offset + if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage)) + { + 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 (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) + else if (var->isConstant()) { - if (!contract->isLibrary()) - return false; - pushes = 1; + 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 - return false; - for (unsigned i = 0; i < pushes; ++i) - _assembly.append(u256(0)); // just to verify the stack height } - else + 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) { - // lvalue context - if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) + solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); + if (dynamic_cast<FunctionDefinition const*>(declaration)) { - if (!varDecl->isLocalVariable()) - return false; // only local variables are inline-assemlby lvalues - for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i) - _assembly.append(Instruction::POP); // remove value just to verify the stack height + } + else if (dynamic_cast<VariableDeclaration const*>(declaration)) + { + } + else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) + { + if (!contract->isLibrary()) + { + typeError(_identifier.location, "Expected a library."); + return size_t(-1); + } } else - return false; + return size_t(-1); } - return true; + ref->second.valueSize = 1; + return size_t(1); }; - assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors); - if (!codeGen.typeCheck(identifierAccess)) + solAssert(!_inlineAssembly.annotation().analysisInfo, ""); + _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); + assembly::AsmAnalyzer analyzer( + *_inlineAssembly.annotation().analysisInfo, + m_errors, + identifierAccess + ); + if (!analyzer.analyze(_inlineAssembly.operations())) return false; return true; } @@ -886,15 +952,14 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) { if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression()).get())) { - using Location = FunctionType::Location; - Location location = callType->location(); + auto kind = callType->kind(); if ( - location == Location::Bare || - location == Location::BareCallCode || - location == Location::BareDelegateCall + kind == FunctionType::Kind::Bare || + kind == FunctionType::Kind::BareCallCode || + kind == FunctionType::Kind::BareDelegateCall ) 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."); } } @@ -1387,7 +1452,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) TypePointers{type}, strings(), strings(), - FunctionType::Location::ObjectCreation + FunctionType::Kind::ObjectCreation ); _newExpression.annotation().isPure = true; } @@ -1636,8 +1701,8 @@ bool TypeChecker::visit(Identifier const& _identifier) if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration)) annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration)) - if (auto functionType = dynamic_cast<FunctionType const*>(annotation.type.get())) - annotation.isPure = functionType->isPure(); + if (dynamic_cast<FunctionType const*>(annotation.type.get())) + annotation.isPure = true; return false; } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 46d8230a..88559f44 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -83,6 +83,7 @@ private: virtual bool visit(StructDefinition const& _struct) override; virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(VariableDeclaration const& _variable) override; + virtual bool visit(EnumDefinition const& _enum) override; /// We need to do this manually because we want to pass the bases of the current contract in /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 8031760d..02234ffc 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -316,19 +316,21 @@ protected: class ContractDefinition: public Declaration, public Documented { public: + enum class ContractKind { Interface, Contract, Library }; + ContractDefinition( SourceLocation const& _location, ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _documentation, std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, std::vector<ASTPointer<ASTNode>> const& _subNodes, - bool _isLibrary + ContractKind _contractKind = ContractKind::Contract ): Declaration(_location, _name), Documented(_documentation), m_baseContracts(_baseContracts), m_subNodes(_subNodes), - m_isLibrary(_isLibrary) + m_contractKind(_contractKind) {} virtual void accept(ASTVisitor& _visitor) override; @@ -344,7 +346,7 @@ public: std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); } std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); } std::vector<EventDefinition const*> const& interfaceEvents() const; - bool isLibrary() const { return m_isLibrary; } + bool isLibrary() const { return m_contractKind == ContractKind::Library; } /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. @@ -371,10 +373,12 @@ public: virtual ContractDefinitionAnnotation& annotation() const override; + ContractKind contractKind() const { return m_contractKind; } + private: std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts; std::vector<ASTPointer<ASTNode>> m_subNodes; - bool m_isLibrary; + ContractKind m_contractKind; // parsed Natspec documentation of the contract. Json::Value m_userDocumentation; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index bd297f9e..a7d89248 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -22,11 +22,12 @@ #pragma once +#include <libsolidity/ast/ASTForward.h> + #include <map> #include <memory> #include <vector> #include <set> -#include <libsolidity/ast/ASTForward.h> namespace dev { @@ -112,13 +113,24 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation namespace assembly { -struct Identifier; // forward + struct AsmAnalysisInfo; + struct Identifier; } struct InlineAssemblyAnnotation: StatementAnnotation { - /// Mapping containing resolved references to external identifiers. - std::map<assembly::Identifier const*, Declaration const*> externalReferences; + struct ExternalIdentifierInfo + { + Declaration const* declaration = nullptr; + bool isSlot = false; ///< Whether the storage slot of a variable is queried. + bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried. + size_t valueSize = size_t(-1); + }; + + /// Mapping containing resolved references to external identifiers and their value size + std::map<assembly::Identifier const*, ExternalIdentifierInfo> externalReferences; + /// Information generated during analysis phase. + std::shared_ptr<assembly::AsmAnalysisInfo> analysisInfo; }; struct ReturnAnnotation: StatementAnnotation diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 69c10c8d..9ea23687 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -40,6 +40,21 @@ void ASTJsonConverter::addJsonNode( 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; node["id"] = Json::UInt64(_node.id()); @@ -183,11 +198,18 @@ bool ASTJsonConverter::visit(FunctionDefinition 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("type", type(_node)) - }, true); + make_pair("type", type(_node)), + 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; + } bool ASTJsonConverter::visit(ModifierDefinition const& _node) @@ -209,6 +231,7 @@ bool ASTJsonConverter::visit(TypeName const&) bool ASTJsonConverter::visit(EventDefinition const& _node) { + m_inEvent = true; addJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()) }, true); return true; } @@ -502,6 +525,7 @@ void ASTJsonConverter::endVisit(ModifierInvocation const&) void ASTJsonConverter::endVisit(EventDefinition const&) { + m_inEvent = false; 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) { return _expression.annotation().type ? _expression.annotation().type->toString() : "Unknown"; diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 49f23f99..bd5e6560 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -151,8 +151,15 @@ private: std::initializer_list<std::pair<std::string const, Json::Value const>> _attributes, 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 visibility(Declaration::Visibility const& _visibility); + std::string location(VariableDeclaration::Location _location); std::string type(Expression const& _expression); std::string type(VariableDeclaration const& _varDecl); inline void goUp() @@ -161,6 +168,7 @@ private: m_jsonNodePtrs.pop(); } + bool m_inEvent = false; ///< whether we are currently inside an event or not bool processed = false; Json::Value m_astJson; std::stack<Json::Value*> m_jsonNodePtrs; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index e7f53422..41ee6498 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -462,11 +462,11 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons if (isAddress()) return { {"balance", make_shared<IntegerType >(256)}, - {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true, false, true)}, - {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true, false, true)}, - {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)}, - {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}, - {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Location::Transfer)} + {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::Bare, true, false, true)}, + {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)}, + {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)}, + {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, + {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)} }; else return MemberList::MemberMap(); @@ -1466,7 +1466,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const TypePointers{make_shared<IntegerType>(256)}, strings{string()}, strings{string()}, - isByteArray() ? FunctionType::Location::ByteArrayPush : FunctionType::Location::ArrayPush + isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush )}); } return members; @@ -1766,7 +1766,7 @@ FunctionTypePointer StructType::constructorType() const TypePointers{copyForLocation(DataLocation::Memory, false)}, paramNames, 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): - m_location(_isInternal ? Location::Internal : Location::External), + m_kind(_isInternal ? Kind::Internal : Kind::External), m_isConstant(_function.isDeclaredConst()), m_isPayable(_isInternal ? false : _function.isPayable()), m_declaration(&_function) @@ -1998,7 +1998,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal } FunctionType::FunctionType(VariableDeclaration const& _varDecl): - m_location(Location::External), m_isConstant(true), m_declaration(&_varDecl) + m_kind(Kind::External), m_isConstant(true), m_declaration(&_varDecl) { TypePointers paramTypes; vector<string> paramNames; @@ -2058,7 +2058,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): } FunctionType::FunctionType(EventDefinition const& _event): - m_location(Location::Event), m_isConstant(true), m_declaration(&_event) + m_kind(Kind::Event), m_isConstant(true), m_declaration(&_event) { TypePointers params; vector<string> paramNames; @@ -2074,19 +2074,19 @@ FunctionType::FunctionType(EventDefinition const& _event): } FunctionType::FunctionType(FunctionTypeName const& _typeName): - m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal), + m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal), m_isConstant(_typeName.isDeclaredConst()), m_isPayable(_typeName.isPayable()) { if (_typeName.isPayable()) { - solAssert(m_location == Location::External, "Internal payable function type used."); + solAssert(m_kind == Kind::External, "Internal payable function type used."); solAssert(!m_isConstant, "Payable constant function"); } for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); - if (m_location == Location::External) + if (m_kind == Kind::External) solAssert( t->annotation().type->canBeUsedExternally(false), "Internal type used as parameter for external function." @@ -2096,7 +2096,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): for (auto const& t: _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for return parameter."); - if (m_location == Location::External) + if (m_kind == Kind::External) solAssert( t->annotation().type->canBeUsedExternally(false), "Internal type used as return parameter for external function." @@ -2126,7 +2126,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c TypePointers{make_shared<ContractType>(_contract)}, parameterNames, strings{""}, - Location::Creation, + Kind::Creation, false, nullptr, false, @@ -2151,38 +2151,38 @@ TypePointers FunctionType::parameterTypes() const string FunctionType::identifier() const { string id = "t_function_"; - switch (location()) + switch (m_kind) { - case Location::Internal: id += "internal"; break; - case Location::External: id += "external"; break; - case Location::CallCode: id += "callcode"; break; - case Location::DelegateCall: id += "delegatecall"; break; - case Location::Bare: id += "bare"; break; - case Location::BareCallCode: id += "barecallcode"; break; - case Location::BareDelegateCall: id += "baredelegatecall"; break; - case Location::Creation: id += "creation"; break; - case Location::Send: id += "send"; break; - case Location::Transfer: id += "transfer"; break; - case Location::SHA3: id += "sha3"; break; - case Location::Selfdestruct: id += "selfdestruct"; break; - case Location::Revert: id += "revert"; break; - case Location::ECRecover: id += "ecrecover"; break; - case Location::SHA256: id += "sha256"; break; - case Location::RIPEMD160: id += "ripemd160"; break; - case Location::Log0: id += "log0"; break; - case Location::Log1: id += "log1"; break; - case Location::Log2: id += "log2"; break; - case Location::Log3: id += "log3"; break; - case Location::Log4: id += "log4"; break; - case Location::Event: id += "event"; break; - case Location::SetGas: id += "setgas"; break; - case Location::SetValue: id += "setvalue"; break; - case Location::BlockHash: id += "blockhash"; break; - case Location::AddMod: id += "addmod"; break; - case Location::MulMod: id += "mulmod"; break; - case Location::ArrayPush: id += "arraypush"; break; - case Location::ByteArrayPush: id += "bytearraypush"; break; - case Location::ObjectCreation: id += "objectcreation"; break; + case Kind::Internal: id += "internal"; break; + case Kind::External: id += "external"; break; + case Kind::CallCode: id += "callcode"; break; + case Kind::DelegateCall: id += "delegatecall"; break; + case Kind::Bare: id += "bare"; break; + case Kind::BareCallCode: id += "barecallcode"; break; + case Kind::BareDelegateCall: id += "baredelegatecall"; break; + case Kind::Creation: id += "creation"; break; + case Kind::Send: id += "send"; break; + case Kind::Transfer: id += "transfer"; break; + case Kind::SHA3: id += "sha3"; break; + case Kind::Selfdestruct: id += "selfdestruct"; break; + case Kind::Revert: id += "revert"; break; + case Kind::ECRecover: id += "ecrecover"; break; + case Kind::SHA256: id += "sha256"; break; + case Kind::RIPEMD160: id += "ripemd160"; break; + case Kind::Log0: id += "log0"; break; + case Kind::Log1: id += "log1"; break; + case Kind::Log2: id += "log2"; break; + case Kind::Log3: id += "log3"; break; + case Kind::Log4: id += "log4"; break; + case Kind::Event: id += "event"; break; + case Kind::SetGas: id += "setgas"; break; + case Kind::SetValue: id += "setvalue"; break; + case Kind::BlockHash: id += "blockhash"; break; + case Kind::AddMod: id += "addmod"; break; + case Kind::MulMod: id += "mulmod"; break; + case Kind::ArrayPush: id += "arraypush"; break; + case Kind::ByteArrayPush: id += "bytearraypush"; break; + case Kind::ObjectCreation: id += "objectcreation"; break; default: solAssert(false, "Unknown function location."); break; } if (isConstant()) @@ -2203,7 +2203,7 @@ bool FunctionType::operator==(Type const& _other) const return false; FunctionType const& other = dynamic_cast<FunctionType const&>(_other); - if (m_location != other.m_location) + if (m_kind != other.m_kind) return false; if (m_isConstant != other.isConstant()) return false; @@ -2231,7 +2231,7 @@ bool FunctionType::operator==(Type const& _other) 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); if (convertTo.isAddress()) @@ -2249,7 +2249,7 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const string FunctionType::canonicalName(bool) const { - solAssert(m_location == Location::External, ""); + solAssert(m_kind == Kind::External, ""); return "function"; } @@ -2263,7 +2263,7 @@ string FunctionType::toString(bool _short) const name += " constant"; if (m_isPayable) name += " payable"; - if (m_location == Location::External) + if (m_kind == Kind::External) name += " external"; if (!m_returnParameterTypes.empty()) { @@ -2285,7 +2285,7 @@ unsigned FunctionType::calldataEncodedSize(bool _padded) const u256 FunctionType::storageSize() const { - if (m_location == Location::External || m_location == Location::Internal) + if (m_kind == Kind::External || m_kind == Kind::Internal) return 1; else BOOST_THROW_EXCEPTION( @@ -2295,9 +2295,9 @@ u256 FunctionType::storageSize() const unsigned FunctionType::storageBytes() const { - if (m_location == Location::External) + if (m_kind == Kind::External) return 20 + 4; - else if (m_location == Location::Internal) + else if (m_kind == Kind::Internal) return 8; // it should really not be possible to create larger programs else BOOST_THROW_EXCEPTION( @@ -2307,21 +2307,21 @@ unsigned FunctionType::storageBytes() const unsigned FunctionType::sizeOnStack() const { - Location location = m_location; - if (m_location == Location::SetGas || m_location == Location::SetValue) + Kind kind = m_kind; + if (m_kind == Kind::SetGas || m_kind == Kind::SetValue) { solAssert(m_returnParameterTypes.size() == 1, ""); - location = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_location; + kind = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_kind; } unsigned size = 0; - if (location == Location::External || location == Location::CallCode || location == Location::DelegateCall) + if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall) size = 2; - else if (location == Location::Bare || location == Location::BareCallCode || location == Location::BareDelegateCall) + else if (kind == Kind::Bare || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall) size = 1; - else if (location == Location::Internal) + else if (kind == Kind::Internal) size = 1; - else if (location == Location::ArrayPush || location == Location::ByteArrayPush) + else if (kind == Kind::ArrayPush || kind == Kind::ByteArrayPush) size = 1; if (m_gasSet) size++; @@ -2362,26 +2362,26 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const return make_shared<FunctionType>( paramTypes, retParamTypes, m_parameterNames, m_returnParameterNames, - m_location, m_arbitraryParameters, + m_kind, m_arbitraryParameters, m_declaration, m_isConstant, m_isPayable ); } MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) const { - switch (m_location) + switch (m_kind) { - case Location::External: - case Location::Creation: - case Location::ECRecover: - case Location::SHA256: - case Location::RIPEMD160: - case Location::Bare: - case Location::BareCallCode: - case Location::BareDelegateCall: + case Kind::External: + case Kind::Creation: + case Kind::ECRecover: + case Kind::SHA256: + case Kind::RIPEMD160: + case Kind::Bare: + case Kind::BareCallCode: + case Kind::BareDelegateCall: { MemberList::MemberMap members; - if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall) + if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall) { if (m_isPayable) members.push_back(MemberList::Member( @@ -2391,7 +2391,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con TypePointers{copyAndSetGasOrValue(false, true)}, strings(), strings(), - Location::SetValue, + Kind::SetValue, false, nullptr, 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( "gas", make_shared<FunctionType>( @@ -2409,7 +2409,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con TypePointers{copyAndSetGasOrValue(true, false)}, strings(), strings(), - Location::SetGas, + Kind::SetGas, false, nullptr, false, @@ -2428,7 +2428,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con TypePointer FunctionType::encodingType() const { // Only external functions can be encoded, internal functions cannot leave code boundaries. - if (m_location == Location::External) + if (m_kind == Kind::External) return shared_from_this(); else return TypePointer(); @@ -2436,7 +2436,7 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { - if (m_location == Location::External) + if (m_kind == Kind::External) return shared_from_this(); else return TypePointer(); @@ -2478,14 +2478,14 @@ bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const bool FunctionType::isBareCall() const { - switch (m_location) + switch (m_kind) { - case Location::Bare: - case Location::BareCallCode: - case Location::BareDelegateCall: - case Location::ECRecover: - case Location::SHA256: - case Location::RIPEMD160: + case Kind::Bare: + case Kind::BareCallCode: + case Kind::BareDelegateCall: + case Kind::ECRecover: + case Kind::SHA256: + case Kind::RIPEMD160: return true; default: return false; @@ -2520,13 +2520,13 @@ u256 FunctionType::externalIdentifier() const bool FunctionType::isPure() const { return - m_location == Location::SHA3 || - m_location == Location::ECRecover || - m_location == Location::SHA256 || - m_location == Location::RIPEMD160 || - m_location == Location::AddMod || - m_location == Location::MulMod || - m_location == Location::ObjectCreation; + m_kind == Kind::SHA3 || + m_kind == Kind::ECRecover || + m_kind == Kind::SHA256 || + m_kind == Kind::RIPEMD160 || + m_kind == Kind::AddMod || + m_kind == Kind::MulMod || + m_kind == Kind::ObjectCreation; } TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) @@ -2545,7 +2545,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con m_returnParameterTypes, m_parameterNames, m_returnParameterNames, - m_location, + m_kind, m_arbitraryParameters, m_declaration, m_isConstant, @@ -2571,18 +2571,18 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) parameterTypes.push_back(t); } - Location location = m_location; + Kind kind = m_kind; if (_inLibrary) { solAssert(!!m_declaration, "Declaration has to be available."); if (!m_declaration->isPublic()) - location = Location::Internal; // will be inlined + kind = Kind::Internal; // will be inlined else - location = Location::DelegateCall; + kind = Kind::DelegateCall; } TypePointers returnParameterTypes = m_returnParameterTypes; - if (location != Location::Internal) + if (kind != Kind::Internal) { // Alter dynamic types to be non-accessible. for (auto& param: returnParameterTypes) @@ -2595,7 +2595,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) returnParameterTypes, m_parameterNames, m_returnParameterNames, - location, + kind, m_arbitraryParameters, m_declaration, m_isConstant, @@ -2821,7 +2821,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const return MemberList::MemberMap({ {"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, {"timestamp", make_shared<IntegerType>(256)}, - {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)}, + {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash)}, {"difficulty", make_shared<IntegerType>(256)}, {"number", make_shared<IntegerType>(256)}, {"gaslimit", make_shared<IntegerType>(256)} diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 78326aa6..c4ffc44c 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -817,8 +817,7 @@ class FunctionType: public Type { public: /// How this function is invoked on the EVM. - /// @todo This documentation is outdated, and Location should rather be named "Type" - enum class Location + enum class Kind { Internal, ///< stack-call using plain JUMP External, ///< external call using CALL @@ -868,7 +867,7 @@ public: FunctionType( strings const& _parameterTypes, strings const& _returnParameterTypes, - Location _location = Location::Internal, + Kind _kind = Kind::Internal, bool _arbitraryParameters = false, bool _constant = false, bool _payable = false @@ -877,7 +876,7 @@ public: parseElementaryTypeVector(_returnParameterTypes), strings(), strings(), - _location, + _kind, _arbitraryParameters, nullptr, _constant, @@ -895,7 +894,7 @@ public: TypePointers const& _returnParameterTypes, strings _parameterNames = strings(), strings _returnParameterNames = strings(), - Location _location = Location::Internal, + Kind _kind = Kind::Internal, bool _arbitraryParameters = false, Declaration const* _declaration = nullptr, bool _isConstant = false, @@ -908,7 +907,7 @@ public: m_returnParameterTypes(_returnParameterTypes), m_parameterNames(_parameterNames), m_returnParameterNames(_returnParameterNames), - m_location(_location), + m_kind(_kind), m_arbitraryParameters(_arbitraryParameters), m_gasSet(_gasSet), m_valueSet(_valueSet), @@ -937,11 +936,11 @@ public: virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; - virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } + virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } virtual u256 storageSize() const override; virtual unsigned storageBytes() const override; virtual bool isValueType() const override { return true; } - virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } + virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } virtual unsigned sizeOnStack() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override; @@ -964,7 +963,7 @@ public: /// @returns true if the ABI is used for this call (only meaningful for external calls) bool isBareCall() const; - Location const& location() const { return m_location; } + Kind const& kind() const { return m_kind; } /// @returns the external signature of this function type given the function name std::string externalSignature() const; /// @returns the external identifier of this function (the hash of the signature). @@ -986,7 +985,7 @@ public: ASTPointer<ASTString> documentation() const; /// true iff arguments are to be padded to multiples of 32 bytes for external calls - bool padArguments() const { return !(m_location == Location::SHA3 || m_location == Location::SHA256 || m_location == Location::RIPEMD160); } + bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160); } bool takesArbitraryParameters() const { return m_arbitraryParameters; } bool gasSet() const { return m_gasSet; } bool valueSet() const { return m_valueSet; } @@ -1012,7 +1011,7 @@ private: TypePointers m_returnParameterTypes; std::vector<std::string> m_parameterNames; std::vector<std::string> m_returnParameterNames; - Location const m_location; + Kind const m_kind; /// true if the function takes an arbitrary number of arguments of arbitrary types bool const m_arbitraryParameters = false; bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index a8316109..51dd9fd2 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -265,31 +265,40 @@ void CompilerContext::appendInlineAssembly( } unsigned startStackHeight = stackHeight(); - auto identifierAccess = [&]( + + assembly::ExternalIdentifierAccess identifierAccess; + identifierAccess.resolve = [&]( + assembly::Identifier const& _identifier, + assembly::IdentifierContext + ) + { + auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); + return it == _localVariables.end() ? size_t(-1) : 1; + }; + identifierAccess.generateCode = [&]( assembly::Identifier const& _identifier, - eth::Assembly& _assembly, - assembly::CodeGenerator::IdentifierContext _context - ) { + assembly::IdentifierContext _context, + eth::Assembly& _assembly + ) + { auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); - if (it == _localVariables.end()) - return false; + solAssert(it != _localVariables.end(), ""); unsigned stackDepth = _localVariables.end() - it; int stackDiff = _assembly.deposit() - startStackHeight + stackDepth; - if (_context == assembly::CodeGenerator::IdentifierContext::LValue) + if (_context == assembly::IdentifierContext::LValue) stackDiff -= 1; if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( CompilerError() << errinfo_comment("Stack too deep, try removing local variables.") ); - if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + if (_context == assembly::IdentifierContext::RValue) _assembly.append(dupInstruction(stackDiff)); else { _assembly.append(swapInstruction(stackDiff)); _assembly.append(Instruction::POP); } - return true; }; solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 42323abd..dc0b340c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -135,7 +135,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } else if ( _type.category() == Type::Category::Function && - dynamic_cast<FunctionType const&>(_type).location() == FunctionType::Location::External + dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External ) { solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); @@ -795,7 +795,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType); solAssert(targetType.isAddress(), "Function type can only be converted to address."); FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack); - solAssert(typeOnStack.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> m_context << Instruction::POP; @@ -820,7 +820,7 @@ void CompilerUtils::pushZeroValue(Type const& _type) { if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) { - if (funType->location() == FunctionType::Location::Internal) + if (funType->kind() == FunctionType::Kind::Internal) { m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { _context.appendInvalid(); @@ -983,7 +983,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda unsigned numBytes = _type.calldataEncodedSize(_padToWords); bool isExternalFunctionType = false; if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) - if (funType->location() == FunctionType::Location::External) + if (funType->kind() == FunctionType::Kind::External) isExternalFunctionType = true; if (numBytes == 0) { diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 6524bd03..34ef13c0 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -520,93 +520,129 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { ErrorList errors; - assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); + assembly::CodeGenerator codeGen(errors); unsigned startStackHeight = m_context.stackHeight(); - codeGen.assemble( - m_context.nonConstAssembly(), - [&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { - auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); - if (ref == _inlineAssembly.annotation().externalReferences.end()) - return false; - Declaration const* decl = ref->second; - solAssert(!!decl, ""); - if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + assembly::ExternalIdentifierAccess identifierAccess; + identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) + { + auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + if (ref == _inlineAssembly.annotation().externalReferences.end()) + return size_t(-1); + return ref->second.valueSize; + }; + identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly) + { + 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."); + if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) { - solAssert(!!decl->type(), "Type of declaration required but not yet determined."); - if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) + solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + functionDef = &m_context.resolveVirtualFunction(*functionDef); + _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) { - functionDef = &m_context.resolveVirtualFunction(*functionDef); - _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); - // If there is a runtime context, we have to merge both labels into the same - // stack slot in case we store it in storage. - if (CompilerContext* rtc = m_context.runtimeContext()) - { - _assembly.append(u256(1) << 32); - _assembly.append(Instruction::MUL); - _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); - _assembly.append(Instruction::OR); - } + _assembly.append(u256(1) << 32); + _assembly.append(Instruction::MUL); + _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); + _assembly.append(Instruction::OR); } - else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) + } + else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) + { + solAssert(!variable->isConstant(), ""); + if (m_context.isStateVariable(decl)) { - solAssert(!variable->isConstant(), ""); - if (m_context.isLocalVariable(variable)) - { - int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); - if (stackDiff < 1 || stackDiff > 16) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep, try removing local variables.") - ); - for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) - _assembly.append(dupInstruction(stackDiff)); - } + auto const& location = m_context.storageLocationOfVariable(*decl); + if (ref->second.isSlot) + m_context << location.first; + else if (ref->second.isOffset) + m_context << u256(location.second); else + solAssert(false, ""); + } + else if (m_context.isLocalVariable(decl)) + { + int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); + if (ref->second.isSlot || ref->second.isOffset) { - solAssert(m_context.isStateVariable(variable), "Invalid variable type."); - auto const& location = m_context.storageLocationOfVariable(*variable); - if (!variable->type()->isValueType()) + solAssert(variable->type()->dataStoredIn(DataLocation::Storage), ""); + unsigned size = variable->type()->sizeOnStack(); + if (size == 2) { - solAssert(location.second == 0, "Intra-slot offest assumed to be zero."); - _assembly.append(location.first); + // slot plus offset + if (ref->second.isOffset) + stackDiff--; } else { - _assembly.append(location.first); - _assembly.append(u256(location.second)); + solAssert(size == 1, ""); + // only slot, offset is zero + if (ref->second.isOffset) + { + _assembly.append(u256(0)); + return; + } } } - } - else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) - { - solAssert(contract->isLibrary(), ""); - _assembly.appendLibraryAddress(contract->fullyQualifiedName()); + else + solAssert(variable->type()->sizeOnStack() == 1, ""); + if (stackDiff < 1 || stackDiff > 16) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); + solAssert(variable->type()->sizeOnStack() == 1, ""); + _assembly.append(dupInstruction(stackDiff)); } else - solAssert(false, "Invalid declaration type."); - } else { - // lvalue context - auto variable = dynamic_cast<VariableDeclaration const*>(decl); - solAssert( - !!variable && m_context.isLocalVariable(variable), - "Can only assign to stack variables in inline assembly." - ); - unsigned size = variable->type()->sizeOnStack(); - int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size; - if (stackDiff > 16 || stackDiff < 1) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep, try removing local variables.") - ); - for (unsigned i = 0; i < size; ++i) { - _assembly.append(swapInstruction(stackDiff)); - _assembly.append(Instruction::POP); - } + solAssert(false, ""); } - return true; + else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) + { + solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + solAssert(contract->isLibrary(), ""); + _assembly.appendLibraryAddress(contract->fullyQualifiedName()); + } + else + solAssert(false, "Invalid declaration type."); + solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), ""); } + else + { + // lvalue context + solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + auto variable = dynamic_cast<VariableDeclaration const*>(decl); + solAssert( + !!variable && m_context.isLocalVariable(variable), + "Can only assign to stack variables in inline assembly." + ); + solAssert(variable->type()->sizeOnStack() == 1, ""); + int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1; + if (stackDiff > 16 || stackDiff < 1) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); + _assembly.append(swapInstruction(stackDiff)); + _assembly.append(Instruction::POP); + } + }; + 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."); m_context.setStackOffset(startStackHeight); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 744a80c4..f018b311 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -434,7 +434,6 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { CompilerContext::LocationSetter locationSetter(m_context, _functionCall); - using Location = FunctionType::Location; if (_functionCall.annotation().isTypeConversion) { solAssert(_functionCall.arguments().size() == 1, ""); @@ -499,10 +498,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) FunctionType const& function = *functionType; if (function.bound()) // Only delegatecall and internal functions can be bound, this might be lifted later. - solAssert(function.location() == Location::DelegateCall || function.location() == Location::Internal, ""); - switch (function.location()) + solAssert(function.kind() == FunctionType::Kind::DelegateCall || function.kind() == FunctionType::Kind::Internal, ""); + switch (function.kind()) { - case Location::Internal: + case FunctionType::Kind::Internal: { // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values @@ -538,16 +537,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context.adjustStackOffset(returnParametersSize - parameterSize - 1); break; } - case Location::External: - case Location::CallCode: - case Location::DelegateCall: - case Location::Bare: - case Location::BareCallCode: - case Location::BareDelegateCall: + case FunctionType::Kind::External: + case FunctionType::Kind::CallCode: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: _functionCall.expression().accept(*this); appendExternalFunctionCall(function, arguments); break; - case Location::Creation: + case FunctionType::Kind::Creation: { _functionCall.expression().accept(*this); solAssert(!function.gasSet(), "Gas limit set for contract creation."); @@ -592,7 +591,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << swapInstruction(1) << Instruction::POP; break; } - case Location::SetGas: + case FunctionType::Kind::SetGas: { // stack layout: contract_address function_id [gas] [value] _functionCall.expression().accept(*this); @@ -608,7 +607,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::POP; break; } - case Location::SetValue: + case FunctionType::Kind::SetValue: // stack layout: contract_address function_id [gas] [value] _functionCall.expression().accept(*this); // Note that function is not the original function, but the ".value" function. @@ -617,8 +616,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::POP; arguments.front()->accept(*this); break; - case Location::Send: - case Location::Transfer: + case FunctionType::Kind::Send: + case FunctionType::Kind::Transfer: _functionCall.expression().accept(*this); // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. @@ -637,7 +636,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypePointers{}, strings(), strings(), - Location::Bare, + FunctionType::Kind::Bare, false, nullptr, 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). m_context << Instruction::ISZERO; m_context.appendConditionalInvalid(); } break; - case Location::Selfdestruct: + case FunctionType::Kind::Selfdestruct: arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); m_context << Instruction::SELFDESTRUCT; break; - case Location::Revert: + case FunctionType::Kind::Revert: // memory offset returned - zero length m_context << u256(0) << u256(0); m_context << Instruction::REVERT; break; - case Location::SHA3: + case FunctionType::Kind::SHA3: { TypePointers argumentTypes; for (auto const& arg: arguments) @@ -678,13 +677,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::SHA3; break; } - case Location::Log0: - case Location::Log1: - case Location::Log2: - case Location::Log3: - case Location::Log4: + case FunctionType::Kind::Log0: + case FunctionType::Kind::Log1: + case FunctionType::Kind::Log2: + case FunctionType::Kind::Log3: + case FunctionType::Kind::Log4: { - unsigned logNumber = int(function.location()) - int(Location::Log0); + unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0); for (unsigned arg = logNumber; arg > 0; --arg) { arguments[arg]->accept(*this); @@ -701,7 +700,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << logInstruction(logNumber); break; } - case Location::Event: + case FunctionType::Kind::Event: { _functionCall.expression().accept(*this); auto const& event = dynamic_cast<EventDefinition const&>(function.declaration()); @@ -755,50 +754,50 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << logInstruction(numIndexed); break; } - case Location::BlockHash: + case FunctionType::Kind::BlockHash: { arguments[0]->accept(*this); utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true); m_context << Instruction::BLOCKHASH; break; } - case Location::AddMod: - case Location::MulMod: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: { for (unsigned i = 0; i < 3; i ++) { arguments[2 - i]->accept(*this); utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256)); } - if (function.location() == Location::AddMod) + if (function.kind() == FunctionType::Kind::AddMod) m_context << Instruction::ADDMOD; else m_context << Instruction::MULMOD; break; } - case Location::ECRecover: - case Location::SHA256: - case Location::RIPEMD160: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: { _functionCall.expression().accept(*this); - static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, - {Location::SHA256, 2}, - {Location::RIPEMD160, 3}}; - m_context << contractAddresses.find(function.location())->second; + static const map<FunctionType::Kind, u256> contractAddresses{{FunctionType::Kind::ECRecover, 1}, + {FunctionType::Kind::SHA256, 2}, + {FunctionType::Kind::RIPEMD160, 3}}; + m_context << contractAddresses.find(function.kind())->second; for (unsigned i = function.sizeOnStack(); i > 0; --i) m_context << swapInstruction(i); appendExternalFunctionCall(function, arguments); break; } - case Location::ByteArrayPush: - case Location::ArrayPush: + case FunctionType::Kind::ByteArrayPush: + case FunctionType::Kind::ArrayPush: { _functionCall.expression().accept(*this); solAssert(function.parameterTypes().size() == 1, ""); solAssert(!!function.parameterTypes()[0], ""); TypePointer paramType = function.parameterTypes()[0]; shared_ptr<ArrayType> arrayType = - function.location() == Location::ArrayPush ? + function.kind() == FunctionType::Kind::ArrayPush ? make_shared<ArrayType>(DataLocation::Storage, paramType) : make_shared<ArrayType>(DataLocation::Storage); // get the current length @@ -822,13 +821,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack()); // stack: newLength argValue storageSlot slotOffset - if (function.location() == Location::ArrayPush) + if (function.kind() == FunctionType::Kind::ArrayPush) StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); else StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); break; } - case Location::ObjectCreation: + case FunctionType::Kind::ObjectCreation: { // Will allocate at the end of memory (MSIZE) and not write at all unless the base // type is dynamically sized. @@ -878,15 +877,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::POP; break; } - case Location::Assert: - case Location::Require: + case FunctionType::Kind::Assert: + case FunctionType::Kind::Require: { arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); // jump if condition was met m_context << Instruction::ISZERO << Instruction::ISZERO; auto success = m_context.appendConditionalJump(); - if (function.location() == Location::Assert) + if (function.kind() == FunctionType::Kind::Assert) // condition was not met, flag an error m_context << Instruction::INVALID; else @@ -922,7 +921,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) *funType->selfType(), true ); - if (funType->location() == FunctionType::Location::Internal) + if (funType->kind() == FunctionType::Kind::Internal) { FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration()); utils().pushCombinedFunctionEntryLabel(funDef); @@ -930,7 +929,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) } else { - solAssert(funType->location() == FunctionType::Location::DelegateCall, ""); + solAssert(funType->kind() == FunctionType::Kind::DelegateCall, ""); auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope()); solAssert(contract && contract->isLibrary(), ""); m_context.appendLibraryAddress(contract->fullyQualifiedName()); @@ -949,9 +948,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(_memberAccess.annotation().type, "_memberAccess has no type"); if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) { - 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 // internal library function call, this would push the library address forcing // us to link against it although we actually do not need it. @@ -960,31 +959,31 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else solAssert(false, "Function not found in member access"); break; - case FunctionType::Location::Event: + case FunctionType::Kind::Event: if (!dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration)) solAssert(false, "event not found"); // no-op, because the parent node will do the job break; - case FunctionType::Location::External: - case FunctionType::Location::Creation: - case FunctionType::Location::DelegateCall: - case FunctionType::Location::CallCode: - case FunctionType::Location::Send: - case FunctionType::Location::Bare: - case FunctionType::Location::BareCallCode: - case FunctionType::Location::BareDelegateCall: - case FunctionType::Location::Transfer: + case FunctionType::Kind::External: + case FunctionType::Kind::Creation: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::CallCode: + case FunctionType::Kind::Send: + case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::Transfer: _memberAccess.expression().accept(*this); m_context << funType->externalIdentifier(); break; - case FunctionType::Location::Log0: - case FunctionType::Location::Log1: - case FunctionType::Location::Log2: - case FunctionType::Location::Log3: - case FunctionType::Location::Log4: - case FunctionType::Location::ECRecover: - case FunctionType::Location::SHA256: - case FunctionType::Location::RIPEMD160: + case FunctionType::Kind::Log0: + case FunctionType::Kind::Log1: + case FunctionType::Kind::Log2: + case FunctionType::Kind::Log3: + case FunctionType::Kind::Log4: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: default: solAssert(false, "unsupported member function"); } @@ -1372,7 +1371,7 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, 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 // be "unknown" in one of the operands and not in the other. @@ -1555,11 +1554,10 @@ void ExpressionCompiler::appendExternalFunctionCall( if (_functionType.bound()) utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); - using FunctionKind = FunctionType::Location; - FunctionKind funKind = _functionType.location(); - bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode; - bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode; - bool isDelegateCall = funKind == FunctionKind::BareDelegateCall || funKind == FunctionKind::DelegateCall; + auto funKind = _functionType.kind(); + bool returnSuccessCondition = funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode; + bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; + bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; unsigned retSize = 0; if (returnSuccessCondition) @@ -1576,7 +1574,7 @@ void ExpressionCompiler::appendExternalFunctionCall( TypePointers parameterTypes = _functionType.parameterTypes(); bool manualFunctionId = false; if ( - (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode || funKind == FunctionKind::BareDelegateCall) && + (funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) && !_arguments.empty() ) { @@ -1611,7 +1609,7 @@ void ExpressionCompiler::appendExternalFunctionCall( argumentTypes.push_back(_arguments[i]->annotation().type); } - if (funKind == FunctionKind::ECRecover) + if (funKind == FunctionType::Kind::ECRecover) { // Clears 32 bytes of currently free memory and advances free memory pointer. // Output area will be "start of input area" - 32. @@ -1667,7 +1665,7 @@ void ExpressionCompiler::appendExternalFunctionCall( // put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input> m_context << u256(retSize); utils().fetchFreeMemoryPointer(); // This is the start of input - if (funKind == FunctionKind::ECRecover) + if (funKind == FunctionType::Kind::ECRecover) { // In this case, output is 32 bytes before input and has already been cleared. m_context << u256(32) << Instruction::DUP2 << Instruction::SUB << Instruction::SWAP1; @@ -1693,7 +1691,7 @@ void ExpressionCompiler::appendExternalFunctionCall( bool existenceChecked = false; // Check the the target contract exists (has code) for non-low-level calls. - if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall) + if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; m_context.appendConditionalInvalid(); @@ -1741,14 +1739,14 @@ void ExpressionCompiler::appendExternalFunctionCall( { // already there } - else if (funKind == FunctionKind::RIPEMD160) + else if (funKind == FunctionType::Kind::RIPEMD160) { // fix: built-in contract returns right-aligned data utils().fetchFreeMemoryPointer(); utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); utils().convertType(IntegerType(160), FixedBytesType(20)); } - else if (funKind == FunctionKind::ECRecover) + else if (funKind == FunctionType::Kind::ECRecover) { // Output is 32 bytes before input / free mem pointer. // Failing ecrecover cannot be detected, so we clear output before the call. diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index b9e141d8..a74a3d74 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -199,7 +199,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const } else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) { - if (fun->location() == FunctionType::Location::External) + if (fun->kind() == FunctionType::Kind::External) { CompilerUtils(m_context).splitExternalFunctionType(false); cleaned = true; @@ -256,7 +256,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) { solAssert(_sourceType == *m_dataType, "function item stored but target is not equal to source"); - if (fun->location() == FunctionType::Location::External) + if (fun->kind() == FunctionType::Kind::External) // Combine the two-item function type into a single stack slot. utils.combineExternalFunctionType(false); else diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp index 2903a4e3..b6f17907 100644 --- a/libsolidity/formal/Why3Translator.cpp +++ b/libsolidity/formal/Why3Translator.cpp @@ -588,14 +588,14 @@ bool Why3Translator::visit(FunctionCall const& _node) return true; } FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type); - switch (function.location()) + switch (function.kind()) { - case FunctionType::Location::AddMod: - case FunctionType::Location::MulMod: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: { //@todo require that third parameter is not zero add("(of_int (mod (Int.("); - add(function.location() == FunctionType::Location::AddMod ? "+" : "*"); + add(function.kind() == FunctionType::Kind::AddMod ? "+" : "*"); add(") (to_int "); _node.arguments().at(0)->accept(*this); add(") (to_int "); @@ -605,7 +605,7 @@ bool Why3Translator::visit(FunctionCall const& _node) add(")))"); return false; } - case FunctionType::Location::Internal: + case FunctionType::Kind::Internal: { if (!_node.names().empty()) { @@ -626,7 +626,7 @@ bool Why3Translator::visit(FunctionCall const& _node) add(")"); return false; } - case FunctionType::Location::Bare: + case FunctionType::Kind::Bare: { if (!_node.arguments().empty()) { @@ -654,7 +654,7 @@ bool Why3Translator::visit(FunctionCall const& _node) add(")"); return false; } - case FunctionType::Location::SetValue: + case FunctionType::Kind::SetValue: { add("let amount = "); solAssert(_node.arguments().size() == 1, ""); diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index a3ddb61d..dad05a78 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -21,6 +21,9 @@ #include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/inlineasm/AsmScopeFiller.h> +#include <libsolidity/inlineasm/AsmScope.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Utils.h> @@ -35,146 +38,363 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::assembly; - -bool Scope::registerLabel(string const& _name) +AsmAnalyzer::AsmAnalyzer( + AsmAnalysisInfo& _analysisInfo, + ErrorList& _errors, + ExternalIdentifierAccess::Resolver const& _resolver +): + m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors) { - if (exists(_name)) - return false; - identifiers[_name] = Label(); - return true; } -bool Scope::registerVariable(string const& _name) +bool AsmAnalyzer::analyze(Block const& _block) { - if (exists(_name)) + if (!(ScopeFiller(m_info.scopes, m_errors))(_block)) 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; + return (*this)(_block); } -Scope::Identifier* Scope::lookup(string const& _name) +bool AsmAnalyzer::operator()(Label const& _label) { - 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; + m_info.stackHeightInfo[&_label] = m_stackHeight; + return true; } -AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::Scopes& _scopes, ErrorList& _errors): - m_scopes(_scopes), m_errors(_errors) +bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { - // 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(); + auto const& info = instructionInfo(_instruction.instruction); + m_stackHeight += info.ret - info.args; + m_info.stackHeightInfo[&_instruction] = m_stackHeight; + return true; } bool AsmAnalyzer::operator()(assembly::Literal const& _literal) { + ++m_stackHeight; if (!_literal.isNumber && _literal.value.size() > 32) { m_errors.push_back(make_shared<Error>( Error::Type::TypeError, - "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)" + "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)", + _literal.location )); return false; } + m_info.stackHeightInfo[&_literal] = m_stackHeight; 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 success = true; for (auto const& arg: _instr.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; + } + // Parser already checks that the number of arguments is correct. + solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), ""); if (!(*this)(_instr.instruction)) success = false; + m_info.stackHeightInfo[&_instr] = m_stackHeight; return success; } -bool AsmAnalyzer::operator()(Label const& _item) +bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) { - 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 success = checkAssignment(_assignment.variableName, size_t(-1)); + m_info.stackHeightInfo[&_assignment] = m_stackHeight; + return success; } + 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) { + int const stackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, *_varDecl.value); - if (!m_currentScope->registerVariable(_varDecl.name)) - { - //@TODO secondary location - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Variable name " + _varDecl.name + " already taken in this scope.", - _varDecl.location - )); - success = false; - } + solAssert(m_stackHeight - stackHeight == 1, "Invalid value size."); + boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true; + m_info.stackHeightInfo[&_varDecl] = m_stackHeight; 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. - return true; + Scope& bodyScope = scope(&_funDef.body); + 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. - return true; + bool success = 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 success = true; - auto scope = make_shared<Scope>(); - scope->superScope = m_currentScope; - m_scopes[&_block] = scope; - m_currentScope = scope.get(); + m_currentScope = &scope(&_block); + + int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock; + m_virtualVariablesInNextBlock = 0; for (auto const& s: _block.statements) if (!boost::apply_visitor(*this, s)) success = false; + for (auto const& identifier: scope(&_block).identifiers) + if (identifier.second.type() == typeid(Scope::Variable)) + --m_stackHeight; + + 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_info.stackHeightInfo[&_block] = m_stackHeight; + 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; +} diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 9726210d..426ee0d2 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -20,6 +20,8 @@ #pragma once +#include <libsolidity/inlineasm/AsmStack.h> + #include <libsolidity/interface/Exceptions.h> #include <boost/variant.hpp> @@ -46,101 +48,32 @@ struct Assignment; struct FunctionDefinition; struct FunctionCall; -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 - { - 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 Scope; +struct AsmAnalysisInfo; +/** + * Performs the full analysis stage, calls the ScopeFiller internally, then resolves + * references and performs other checks. + * If all these checks pass, code generation should not throw errors. + */ class AsmAnalyzer: public boost::static_visitor<bool> { public: - using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; - AsmAnalyzer(Scopes& _scopes, ErrorList& _errors); + AsmAnalyzer( + AsmAnalysisInfo& _analysisInfo, + ErrorList& _errors, + ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver() + ); + + bool analyze(assembly::Block const& _block); - bool operator()(assembly::Instruction const&) { return true; } + bool operator()(assembly::Instruction const&); 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::Label const& _label); - bool operator()(assembly::Assignment const&) { return true; } + bool operator()(assembly::Assignment const&); bool operator()(assembly::FunctionalAssignment const& _functionalAssignment); bool operator()(assembly::VariableDeclaration const& _variableDeclaration); bool operator()(assembly::FunctionDefinition const& _functionDefinition); @@ -148,8 +81,20 @@ public: bool operator()(assembly::Block const& _block); 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; - Scopes& m_scopes; + AsmAnalysisInfo& m_info; ErrorList& m_errors; }; diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.cpp b/libsolidity/inlineasm/AsmAnalysisInfo.cpp new file mode 100644 index 00000000..22318b12 --- /dev/null +++ b/libsolidity/inlineasm/AsmAnalysisInfo.cpp @@ -0,0 +1,26 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Information generated during analyzer part of inline assembly. + */ + +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> + +#include <libsolidity/inlineasm/AsmScope.h> + +#include <ostream> + diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libsolidity/inlineasm/AsmAnalysisInfo.h new file mode 100644 index 00000000..e21eb2c5 --- /dev/null +++ b/libsolidity/inlineasm/AsmAnalysisInfo.h @@ -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; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 78a9ee27..9ef3e6e7 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -24,7 +24,9 @@ #include <libsolidity/inlineasm/AsmParser.h> #include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/inlineasm/AsmScope.h> #include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libevmasm/Assembly.h> #include <libevmasm/SourceLocation.h> @@ -46,13 +48,8 @@ using namespace dev::solidity::assembly; struct GeneratorState { - GeneratorState(ErrorList& _errors, eth::Assembly& _assembly): - errors(_errors), assembly(_assembly) {} - - void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation()) - { - errors.push_back(make_shared<Error>(_type, _description, _location)); - } + GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly): + errors(_errors), info(_analysisInfo), assembly(_assembly) {} size_t newLabelId() { @@ -66,8 +63,8 @@ struct GeneratorState return size_t(id); } - std::map<assembly::Block const*, shared_ptr<Scope>> scopes; ErrorList& errors; + AsmAnalysisInfo info; eth::Assembly& assembly; }; @@ -80,13 +77,24 @@ public: explicit CodeTransform( GeneratorState& _state, 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_scope(*m_state.scopes.at(&_block)), - m_initialDeposit(m_state.assembly.deposit()), - m_identifierAccess(_identifierAccess) + m_scope(*m_state.info.scopes.at(&_block)), + 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)); m_state.assembly.setSourceLocation(_block.location); @@ -96,31 +104,16 @@ public: if (identifier.second.type() == typeid(Scope::Variable)) m_state.assembly.append(solidity::Instruction::POP); - int deposit = m_state.assembly.deposit() - m_initialDeposit; - - // 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 - ); - } + int deposit = m_state.assembly.deposit() - blockStartDeposit; + solAssert(deposit == 0, "Invalid stack height at end of block."); } +public: void operator()(assembly::Instruction const& _instruction) { m_state.assembly.setSourceLocation(_instruction.location); m_state.assembly.append(_instruction.instruction); + checkStackHeight(&_instruction); } void operator()(assembly::Literal const& _literal) { @@ -130,8 +123,9 @@ public: else { 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) { @@ -153,20 +147,18 @@ public: }, [=](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)) - { - m_state.addError( - Error::Type::DeclarationError, - "Identifier not found or not unique", - _identifier.location - ); - m_state.assembly.append(u256(0)); - } + solAssert( + m_identifierAccess.generateCode, + "Identifier not found and no external access available." + ); + m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly); + checkStackHeight(&_identifier); } void operator()(FunctionalInstruction const& _instr) { @@ -174,9 +166,10 @@ public: { int height = m_state.assembly.deposit(); boost::apply_visitor(*this, *it); - expectDeposit(1, height, locationOf(*it)); + expectDeposit(1, height); } (*this)(_instr.instruction); + checkStackHeight(&_instr); } void operator()(assembly::FunctionCall const&) { @@ -186,36 +179,39 @@ public: { m_state.assembly.setSourceLocation(_label.location); 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); m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id)); + checkStackHeight(&_label); } void operator()(assembly::Assignment const& _assignment) { m_state.assembly.setSourceLocation(_assignment.location); generateAssignment(_assignment.variableName, _assignment.location); + checkStackHeight(&_assignment); } void operator()(FunctionalAssignment const& _assignment) { int height = m_state.assembly.deposit(); boost::apply_visitor(*this, *_assignment.value); - expectDeposit(1, height, locationOf(*_assignment.value)); + expectDeposit(1, height); m_state.assembly.setSourceLocation(_assignment.location); generateAssignment(_assignment.variableName, _assignment.location); + checkStackHeight(&_assignment); } void operator()(assembly::VariableDeclaration const& _varDecl) { int height = m_state.assembly.deposit(); boost::apply_visitor(*this, *_varDecl.value); - expectDeposit(1, height, locationOf(*_varDecl.value)); - solAssert(m_scope.identifiers.count(_varDecl.name), ""); - auto& var = boost::get<Scope::Variable>(m_scope.identifiers[_varDecl.name]); + expectDeposit(1, height); + auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name)); var.stackHeight = height; var.active = true; } 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&) { @@ -225,35 +221,22 @@ public: private: void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location) { - if (m_scope.lookup(_variableName.name, Scope::Visitor( - [=](Scope::Variable const& _var) - { - if (int heightDiff = variableHeightDiff(_var, _location, true)) - m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); - 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." - ); - } - ))) + auto var = m_scope.lookup(_variableName.name); + if (var) { + Scope::Variable const& _var = boost::get<Scope::Variable>(*var); + if (int heightDiff = variableHeightDiff(_var, _location, true)) + m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); + m_state.assembly.append(solidity::Instruction::POP); } - else if (!m_identifierAccess || !m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue)) - m_state.addError( - Error::Type::DeclarationError, - "Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue." + else + { + solAssert( + m_identifierAccess.generateCode, + "Identifier not found and no external access available." ); + m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly); + } } /// Determines the stack height difference to the given variables. Automatically generates @@ -261,36 +244,33 @@ private: /// errors and the (positive) stack height difference otherwise. 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; 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, "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")", _location - ); + )); return 0; } else return heightDiff; } - void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location) + void expectDeposit(int _deposit, int _oldHeight) { - if (m_state.assembly.deposit() != _oldHeight + 1) - m_state.addError(Error::Type::TypeError, - "Expected instruction(s) to deposit " + - boost::lexical_cast<string>(_deposit) + - " item(s) to the stack, but did deposit " + - boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) + - " item(s).", - _location - ); + solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit."); + } + + void checkStackHeight(void const* _astElement) + { + solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found."); + solAssert( + m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit, + "Stack height mismatch between analysis and code generation phase." + ); } /// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set. @@ -305,35 +285,29 @@ private: GeneratorState& m_state; Scope& m_scope; + ExternalIdentifierAccess m_identifierAccess; int const m_initialDeposit; - assembly::CodeGenerator::IdentifierAccess m_identifierAccess; }; -bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) -{ - size_t initialErrorLen = m_errors.size(); - eth::Assembly assembly; - 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::CodeGenerator::assemble( + Block const& _parsedData, + AsmAnalysisInfo& _analysisInfo, + ExternalIdentifierAccess const& _identifierAccess +) { eth::Assembly assembly; - GeneratorState state(m_errors, assembly); - if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) - solAssert(false, "Assembly error"); - CodeTransform(state, m_parsedData, _identifierAccess); + GeneratorState state(m_errors, _analysisInfo, assembly); + CodeTransform(state, _parsedData, _identifierAccess); 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); - if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) - solAssert(false, "Assembly error"); - CodeTransform(state, m_parsedData, _identifierAccess); + GeneratorState state(m_errors, _analysisInfo, _assembly); + CodeTransform(state, _parsedData, _identifierAccess); } diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h index bd71812e..e830e047 100644 --- a/libsolidity/inlineasm/AsmCodeGen.h +++ b/libsolidity/inlineasm/AsmCodeGen.h @@ -22,9 +22,11 @@ #pragma once -#include <functional> +#include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/interface/Exceptions.h> +#include <functional> + namespace dev { namespace eth @@ -36,30 +38,27 @@ namespace solidity namespace assembly { struct Block; -struct Identifier; class CodeGenerator { public: - enum class IdentifierContext { LValue, RValue }; - /// Function type that is called for external identifiers. Such a function should search for - /// the identifier and append appropriate assembly items to the assembly. If in lvalue context, - /// the value to assign is assumed to be on the stack and an assignment is to be performed. - /// If in rvalue context, the function is assumed to append instructions to - /// push the value of the identifier onto the stack. On error, the function should return false. - using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>; - CodeGenerator(Block const& _parsedData, ErrorList& _errors): - m_parsedData(_parsedData), m_errors(_errors) {} - /// Performs type checks and @returns false on error. - /// Actually runs the full code generation but discards the result. - bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess()); + CodeGenerator(ErrorList& _errors): + m_errors(_errors) {} /// 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. - void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess()); + void assemble( + Block const& _parsedData, + AsmAnalysisInfo& _analysisInfo, + eth::Assembly& _assembly, + ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() + ); private: - Block const& m_parsedData; ErrorList& m_errors; }; diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 0fc0a34f..d7f78958 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -24,6 +24,7 @@ #include <ctype.h> #include <algorithm> #include <libsolidity/parsing/Scanner.h> +#include <libsolidity/interface/Exceptions.h> using namespace std; using namespace dev; @@ -68,12 +69,14 @@ assembly::Statement Parser::parseStatement() return parseBlock(); case Token::Assign: { + if (m_julia) + break; assembly::Assignment assignment = createWithLocation<assembly::Assignment>(); m_scanner->next(); expectToken(Token::Colon); assignment.variableName.location = location(); 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."); assignment.location.end = endPosition(); expectToken(Token::Identifier); @@ -105,7 +108,7 @@ assembly::Statement Parser::parseStatement() { // functional assignment 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."); m_scanner->next(); funAss.variableName = identifier; @@ -180,7 +183,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) else literal = m_scanner->currentLiteral(); // first search the set of instructions. - if (instructions().count(literal)) + if (!m_julia && instructions().count(literal)) { dev::solidity::Instruction const& instr = instructions().at(literal); if (_onlySinglePusher) @@ -242,15 +245,13 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() { expectToken(Token::Sub); expectToken(Token::GreaterThan); - expectToken(Token::LParen); while (true) { funDef.returns.push_back(expectAsmIdentifier()); - if (m_scanner->currentToken() == Token::RParen) + if (m_scanner->currentToken() == Token::LBrace) break; expectToken(Token::Comma); } - expectToken(Token::RParen); } funDef.body = parseBlock(); funDef.location.end = funDef.body.location.end; @@ -261,6 +262,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in { if (_instruction.type() == typeid(Instruction)) { + solAssert(!m_julia, "Instructions are invalid in JULIA"); FunctionalInstruction ret; ret.instruction = std::move(boost::get<Instruction>(_instruction)); ret.location = ret.instruction.location; @@ -323,7 +325,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in string Parser::expectAsmIdentifier() { string name = m_scanner->currentLiteral(); - if (instructions().count(name)) + if (!m_julia && instructions().count(name)) fatalParserError("Cannot use instruction names for identifier names."); expectToken(Token::Identifier); return name; diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 4b4a24ae..c55fd2ac 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -37,7 +37,7 @@ namespace assembly class Parser: public ParserBase { public: - Parser(ErrorList& _errors): ParserBase(_errors) {} + explicit Parser(ErrorList& _errors, bool _julia = false): ParserBase(_errors), m_julia(_julia) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. @@ -70,6 +70,9 @@ protected: FunctionDefinition parseFunctionDefinition(); Statement parseFunctionalInstruction(Statement&& _instruction); std::string expectAsmIdentifier(); + +private: + bool m_julia = false; }; } diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index a70b0b78..252e91f9 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -116,7 +116,7 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin { string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")"; if (!_functionDefinition.returns.empty()) - out += " -> (" + boost::algorithm::join(_functionDefinition.returns, ", ") + ")"; + out += " -> " + boost::algorithm::join(_functionDefinition.returns, ", "); return out + "\n" + (*this)(_functionDefinition.body); } diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp new file mode 100644 index 00000000..609dca16 --- /dev/null +++ b/libsolidity/inlineasm/AsmScope.cpp @@ -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; +} diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h new file mode 100644 index 00000000..37e0f0b8 --- /dev/null +++ b/libsolidity/inlineasm/AsmScope.h @@ -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; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp new file mode 100644 index 00000000..de6fbdaa --- /dev/null +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -0,0 +1,130 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * 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; +} diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h new file mode 100644 index 00000000..bb62948b --- /dev/null +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -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; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp index 266136a1..c2a7d8ea 100644 --- a/libsolidity/inlineasm/AsmStack.cpp +++ b/libsolidity/inlineasm/AsmStack.cpp @@ -26,6 +26,7 @@ #include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/inlineasm/AsmPrinter.h> #include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/parsing/Scanner.h> @@ -39,7 +40,10 @@ using namespace dev; using namespace dev::solidity; 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>(); Parser parser(m_errors); @@ -48,8 +52,8 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner) return false; *m_parserResult = std::move(*result); - AsmAnalyzer::Scopes scopes; - return (AsmAnalyzer(scopes, m_errors))(*m_parserResult); + AsmAnalysisInfo analysisInfo; + return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult); } string InlineAssemblyStack::toString() @@ -59,14 +63,17 @@ string InlineAssemblyStack::toString() eth::Assembly InlineAssemblyStack::assemble() { - CodeGenerator codeGen(*m_parserResult, m_errors); - return codeGen.assemble(); + AsmAnalysisInfo analysisInfo; + AsmAnalyzer analyzer(analysisInfo, m_errors); + solAssert(analyzer.analyze(*m_parserResult), ""); + CodeGenerator codeGen(m_errors); + return codeGen.assemble(*m_parserResult, analysisInfo); } bool InlineAssemblyStack::parseAndAssemble( string const& _input, eth::Assembly& _assembly, - CodeGenerator::IdentifierAccess const& _identifierAccess + ExternalIdentifierAccess const& _identifierAccess ) { ErrorList errors; @@ -74,8 +81,12 @@ bool InlineAssemblyStack::parseAndAssemble( auto parserResult = Parser(errors).parse(scanner); if (!errors.empty()) 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 // internal compiler error anyway. diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h index 4d5a99a4..77a7e02a 100644 --- a/libsolidity/inlineasm/AsmStack.h +++ b/libsolidity/inlineasm/AsmStack.h @@ -22,10 +22,10 @@ #pragma once +#include <libsolidity/interface/Exceptions.h> + #include <string> #include <functional> -#include <libsolidity/interface/Exceptions.h> -#include <libsolidity/inlineasm/AsmCodeGen.h> namespace dev { @@ -39,13 +39,34 @@ class Scanner; namespace assembly { struct Block; +struct Identifier; + +enum class IdentifierContext { LValue, RValue }; + +/// Object that is used to resolve references and generate code for access to identifiers external +/// to inline assembly (not used in standalone assembly mode). +struct ExternalIdentifierAccess +{ + using Resolver = std::function<size_t(assembly::Identifier const&, IdentifierContext)>; + /// Resolve a an external reference given by the identifier in the given context. + /// @returns the size of the value (number of stack slots) or size_t(-1) if not found. + Resolver resolve; + using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>; + /// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context) + /// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed + /// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack. + CodeGenerator generateCode; +}; class InlineAssemblyStack { public: /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`. /// @return false or error. - bool parse(std::shared_ptr<Scanner> const& _scanner); + bool parse( + std::shared_ptr<Scanner> const& _scanner, + ExternalIdentifierAccess::Resolver const& _externalIdentifierResolver = ExternalIdentifierAccess::Resolver() + ); /// Converts the parser result back into a string form (not necessarily the same form /// as the source form, but it should parse into the same parsed form again). std::string toString(); @@ -56,7 +77,7 @@ public: bool parseAndAssemble( std::string const& _input, eth::Assembly& _assembly, - CodeGenerator::IdentifierAccess const& _identifierAccess = CodeGenerator::IdentifierAccess() + ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() ); ErrorList const& errors() const { return m_errors; } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 6b0024ad..9c9c9614 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -38,6 +38,7 @@ #include <libsolidity/analysis/SyntaxChecker.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/InterfaceHandler.h> +#include <libsolidity/interface/GasEstimator.h> #include <libsolidity/formal/Why3Translator.h> #include <libevmasm/Exceptions.h> @@ -55,8 +56,8 @@ using namespace std; using namespace dev; using namespace dev::solidity; -CompilerStack::CompilerStack(ReadFileCallback const& _readFile): - m_readFile(_readFile), m_parseSuccessful(false) {} +CompilerStack::CompilerStack(ReadFile::Callback const& _readFile): + m_readFile(_readFile) {} void CompilerStack::setRemappings(vector<string> const& _remappings) { @@ -78,10 +79,12 @@ void CompilerStack::setRemappings(vector<string> const& _remappings) void CompilerStack::reset(bool _keepSources) { - m_parseSuccessful = false; if (_keepSources) + { + m_stackState = SourcesSet; for (auto sourcePair: m_sources) sourcePair.second.reset(); + } else { m_sources.clear(); @@ -93,6 +96,7 @@ void CompilerStack::reset(bool _keepSources) m_sourceOrder.clear(); m_contracts.clear(); m_errors.clear(); + m_stackState = Empty; } 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); m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name); m_sources[_name].isLibrary = _isLibrary; + m_stackState = SourcesSet; return existed; } @@ -113,9 +118,10 @@ void CompilerStack::setSource(string const& _sourceCode) bool CompilerStack::parse() { //reset + if(m_stackState != SourcesSet) + return false; m_errors.clear(); ASTNode::resetID(); - m_parseSuccessful = false; if (SemVerVersion{string(VersionString)}.isPrerelease()) { @@ -127,14 +133,12 @@ bool CompilerStack::parse() vector<string> sourcesToParse; for (auto const& s: m_sources) sourcesToParse.push_back(s.first); - map<string, SourceUnit const*> sourceUnitsByName; for (size_t i = 0; i < sourcesToParse.size(); ++i) { string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); source.ast = Parser(m_errors).parse(source.scanner); - sourceUnitsByName[path] = source.ast.get(); if (!source.ast) solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error."); else @@ -149,10 +153,19 @@ bool CompilerStack::parse() } } } - if (!Error::containsOnlyWarnings(m_errors)) - // errors while parsing. should stop before type checking + if (Error::containsOnlyWarnings(m_errors)) + { + m_stackState = ParsingSuccessful; + return true; + } + else return false; +} +bool CompilerStack::analyze() +{ + if (m_stackState != ParsingSuccessful) + return false; resolveImports(); bool noErrors = true; @@ -172,6 +185,9 @@ bool CompilerStack::parse() if (!resolver.registerDeclarations(*source->ast)) return false; + map<string, SourceUnit const*> sourceUnitsByName; + for (auto& source: m_sources) + sourceUnitsByName[source.first] = source.second.ast.get(); for (Source const* source: m_sourceOrder) if (!resolver.performImports(*source->ast, sourceUnitsByName)) return false; @@ -234,8 +250,13 @@ bool CompilerStack::parse() noErrors = false; } - m_parseSuccessful = noErrors; - return m_parseSuccessful; + if (noErrors) + { + m_stackState = AnalysisSuccessful; + return true; + } + else + return false; } bool CompilerStack::parse(string const& _sourceCode) @@ -244,9 +265,20 @@ bool CompilerStack::parse(string const& _sourceCode) return parse(); } +bool CompilerStack::parseAndAnalyze() +{ + return parse() && analyze(); +} + +bool CompilerStack::parseAndAnalyze(std::string const& _sourceCode) +{ + setSource(_sourceCode); + return parseAndAnalyze(); +} + vector<string> CompilerStack::contractNames() const { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); vector<string> contractNames; for (auto const& contract: m_contracts) @@ -257,8 +289,8 @@ vector<string> CompilerStack::contractNames() const bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> const& _libraries) { - if (!m_parseSuccessful) - if (!parse()) + if (m_stackState < AnalysisSuccessful) + if (!parseAndAnalyze()) return false; m_optimize = _optimize; @@ -271,12 +303,13 @@ bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> co if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) compileContract(*contract, compiledContracts); this->link(); + m_stackState = CompilationSuccessful; return true; } bool CompilerStack::compile(string const& _sourceCode, bool _optimize, unsigned _runs) { - return parse(_sourceCode) && compile(_optimize, _runs); + return parseAndAnalyze(_sourceCode) && compile(_optimize, _runs); } void CompilerStack::link() @@ -405,8 +438,9 @@ vector<string> CompilerStack::sourceNames() const map<string, unsigned> CompilerStack::sourceIndices() const { map<string, unsigned> indices; + unsigned index = 0; for (auto const& s: m_sources) - indices[s.first] = indices.size(); + indices[s.first] = index++; return indices; } @@ -417,7 +451,7 @@ Json::Value const& CompilerStack::interface(string const& _contractName) 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.")); 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 { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); solAssert(_contract.contract, ""); @@ -456,7 +490,7 @@ Json::Value const& CompilerStack::metadata(Contract const& _contract, Documentat 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.")); return contract(_contractName).onChainMetadata; @@ -522,18 +556,18 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string if (m_sources.count(importPath) || newSources.count(importPath)) continue; - ReadFileResult result{false, string("File not supplied initially.")}; + ReadFile::Result result{false, string("File not supplied initially.")}; if (m_readFile) result = m_readFile(importPath); if (result.success) - newSources[importPath] = result.contentsOrErrorMesage; + newSources[importPath] = result.contentsOrErrorMessage; else { auto err = make_shared<Error>(Error::Type::ParserError); *err << errinfo_sourceLocation(import->location()) << - errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMesage); + errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage); m_errors.push_back(std::move(err)); continue; } @@ -655,8 +689,33 @@ void CompilerStack::compileContract( cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); compiledContract.compiler = compiler; - compiledContract.object = compiler->assembledObject(); - compiledContract.runtimeObject = compiler->runtimeObject(); + + try + { + compiledContract.object = compiler->assembledObject(); + } + catch(eth::OptimizerException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for bytecode")); + } + catch(eth::AssemblyException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for bytecode")); + } + + try + { + compiledContract.runtimeObject = compiler->runtimeObject(); + } + catch(eth::OptimizerException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for deployed bytecode")); + } + catch(eth::AssemblyException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for deployed bytecode")); + } + compiledContract.onChainMetadata = onChainMetadata; _compiledContracts[compiledContract.contract] = &compiler->assembly(); @@ -841,3 +900,88 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con } return ret; } + +namespace +{ + +Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) +{ + if (_gas.isInfinite) + return Json::Value("infinite"); + else + return Json::Value(toString(_gas.value)); +} + +} + +Json::Value CompilerStack::gasEstimates(string const& _contractName) const +{ + if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName)) + return Json::Value(); + + using Gas = GasEstimator::GasConsumption; + Json::Value output(Json::objectValue); + + if (eth::AssemblyItems const* items = assemblyItems(_contractName)) + { + Gas executionGas = GasEstimator::functionalEstimation(*items); + u256 bytecodeSize(runtimeObject(_contractName).bytecode.size()); + Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas; + + Json::Value creation(Json::objectValue); + creation["codeDepositCost"] = gasToJson(codeDepositGas); + creation["executionCost"] = gasToJson(executionGas); + /// TODO: implement + overload to avoid the need of += + executionGas += codeDepositGas; + creation["totalCost"] = gasToJson(executionGas); + output["creation"] = creation; + } + + if (eth::AssemblyItems const* items = runtimeAssemblyItems(_contractName)) + { + /// External functions + ContractDefinition const& contract = contractDefinition(_contractName); + Json::Value externalFunctions(Json::objectValue); + for (auto it: contract.interfaceFunctions()) + { + string sig = it.second->externalSignature(); + externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); + } + + if (contract.fallbackFunction()) + /// This needs to be set to an invalid signature in order to trigger the fallback, + /// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound. + /// An empty string ("") would work to trigger the shortcut only. + externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); + + if (!externalFunctions.empty()) + output["external"] = externalFunctions; + + /// Internal functions + Json::Value internalFunctions(Json::objectValue); + for (auto const& it: contract.definedFunctions()) + { + /// Exclude externally visible functions, constructor and the fallback function + if (it->isPartOfExternalInterface() || it->isConstructor() || it->name().empty()) + continue; + + size_t entry = functionEntryPoint(_contractName, *it); + GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); + if (entry > 0) + gas = GasEstimator::functionalEstimation(*items, entry, *it); + + FunctionType type(*it); + string sig = it->name() + "("; + auto paramTypes = type.parameterTypes(); + for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) + sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ","); + sig += ")"; + internalFunctions[sig] = gasToJson(gas); + } + + if (!internalFunctions.empty()) + output["internal"] = internalFunctions; + } + + return output; +} diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index eddfea68..c1d344ca 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -36,6 +36,7 @@ #include <libevmasm/SourceLocation.h> #include <libevmasm/LinkerObject.h> #include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ReadFile.h> namespace dev { @@ -77,18 +78,10 @@ enum class DocumentationType: uint8_t class CompilerStack: boost::noncopyable { public: - struct ReadFileResult - { - bool success; - std::string contentsOrErrorMesage; - }; - - /// File reading callback. - using ReadFileCallback = std::function<ReadFileResult(std::string const&)>; - /// Creates a new compiler stack. - /// @param _readFile callback to used to read files for import statements. Should return - explicit CompilerStack(ReadFileCallback const& _readFile = ReadFileCallback()); + /// @param _readFile callback to used to read files for import statements. Must return + /// and must not emit exceptions. + explicit CompilerStack(ReadFile::Callback const& _readFile = ReadFile::Callback()); /// Sets path remappings in the format "context:prefix=target" 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. /// @returns false on error. bool parse(std::string const& _sourceCode); + /// performs the analyisis steps (imports, scopesetting, syntaxCheck, referenceResolving, + /// typechecking, staticAnalysis) on previously set sources + /// @returns false on error. + bool analyze(); + /// Parses and analyzes all source units that were added + /// @returns false on error. + bool parseAndAnalyze(); + /// Sets the given source code as the only source unit apart from standard sources and parses and analyzes it. + /// @returns false on error. + bool parseAndAnalyze(std::string const& _sourceCode); /// @returns a list of the contract names in the sources. std::vector<std::string> contractNames() const; std::string defaultContractName() const; @@ -181,6 +184,9 @@ public: std::string const& onChainMetadata(std::string const& _contractName) const; void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } + /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions + Json::Value gasEstimates(std::string const& _contractName) const; + /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& scanner(std::string const& _sourceName = "") const; /// @returns the parsed source unit with the supplied name. @@ -230,6 +236,13 @@ private: mutable std::unique_ptr<std::string const> sourceMapping; mutable std::unique_ptr<std::string const> runtimeSourceMapping; }; + enum State { + Empty, + SourcesSet, + ParsingSuccessful, + AnalysisSuccessful, + CompilationSuccessful + }; /// Loads the missing sources from @a _ast (named @a _path) using the callback /// @a m_readFile and stores the absolute paths of all imports in the AST annotations. @@ -263,14 +276,13 @@ private: std::string target; }; - ReadFileCallback m_readFile; + ReadFile::Callback m_readFile; bool m_optimize = false; unsigned m_optimizeRuns = 200; std::map<std::string, h160> m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// "context:prefix=target" std::vector<Remapping> m_remappings; - bool m_parseSuccessful; std::map<std::string const, Source> m_sources; std::shared_ptr<GlobalContext> m_globalContext; std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; @@ -279,6 +291,7 @@ private: std::string m_formalTranslation; ErrorList m_errors; bool m_metadataLiteralSources = false; + State m_stackState = Empty; }; } diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp index 968a24ad..c09180de 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/libsolidity/interface/Exceptions.cpp @@ -33,22 +33,22 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip switch(m_type) { case Type::DeclarationError: - m_typeName = "Declaration Error"; + m_typeName = "DeclarationError"; break; case Type::DocstringParsingError: - m_typeName = "Docstring Parsing Error"; + m_typeName = "DocstringParsingError"; break; case Type::ParserError: - m_typeName = "Parser Error"; + m_typeName = "ParserError"; break; case Type::SyntaxError: - m_typeName = "Syntax Error"; + m_typeName = "SyntaxError"; break; case Type::TypeError: - m_typeName = "Type Error"; + m_typeName = "TypeError"; break; case Type::Why3TranslatorError: - m_typeName = "Why3 Translator Error"; + m_typeName = "Why3TranslatorError"; break; case Type::Warning: m_typeName = "Warning"; diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h new file mode 100644 index 00000000..2e8a6bd8 --- /dev/null +++ b/libsolidity/interface/ReadFile.h @@ -0,0 +1,45 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <string> +#include <functional> +#include <boost/noncopyable.hpp> + +namespace dev +{ + +namespace solidity +{ + +class ReadFile: boost::noncopyable +{ +public: + /// File reading result. + struct Result + { + bool success; + std::string contentsOrErrorMessage; + }; + + /// File reading callback. + using Callback = std::function<Result(std::string const&)>; +}; + +} +} diff --git a/libsolidity/interface/SourceReferenceFormatter.h b/libsolidity/interface/SourceReferenceFormatter.h index 7034f4ab..e8676d60 100644 --- a/libsolidity/interface/SourceReferenceFormatter.h +++ b/libsolidity/interface/SourceReferenceFormatter.h @@ -23,6 +23,7 @@ #pragma once #include <ostream> +#include <sstream> #include <functional> #include <libevmasm/SourceLocation.h> @@ -53,6 +54,16 @@ public: std::string const& _name, ScannerFromSourceNameFun const& _scannerFromSourceName ); + static std::string formatExceptionInformation( + Exception const& _exception, + std::string const& _name, + ScannerFromSourceNameFun const& _scannerFromSourceName + ) + { + std::ostringstream errorOutput; + printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName); + return errorOutput.str(); + } private: /// Prints source name if location is given. static void printSourceName( diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp new file mode 100644 index 00000000..223cc15d --- /dev/null +++ b/libsolidity/interface/StandardCompiler.cpp @@ -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.\"}]}"; + } +} diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h new file mode 100644 index 00000000..dfaf88cd --- /dev/null +++ b/libsolidity/interface/StandardCompiler.h @@ -0,0 +1,63 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Alex Beregszaszi + * @date 2016 + * Standard JSON compiler interface. + */ + +#pragma once + +#include <libsolidity/interface/CompilerStack.h> + +namespace dev +{ + +namespace solidity +{ + +/** + * Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput. + * See docs/using-the-compiler#compiler-input-and-output-json-description. + */ +class StandardCompiler: boost::noncopyable +{ +public: + /// Creates a new StandardCompiler. + /// @param _readFile callback to used to read files for import statements. Must return + /// and must not emit exceptions. + StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback()) + : m_compilerStack(_readFile), m_readFile(_readFile) + { + } + + /// Sets all input parameters according to @a _input which conforms to the standardized input + /// format, performs compilation and returns a standardized output. + Json::Value compile(Json::Value const& _input); + /// Parses input as JSON and peforms the above processing steps, returning a serialized JSON + /// output. Parsing errors are returned as regular errors. + std::string compile(std::string const& _input); + +private: + Json::Value compileInternal(Json::Value const& _input); + + CompilerStack m_compilerStack; + ReadFile::Callback m_readFile; +}; + +} +} diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index e26e2908..b5130c8a 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -82,9 +82,10 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) case Token::Import: nodes.push_back(parseImportDirective()); break; + case Token::Interface: case Token::Contract: case Token::Library: - nodes.push_back(parseContractDefinition(token == Token::Library)); + nodes.push_back(parseContractDefinition(token)); break; default: fatalParserError(string("Expected import directive or contract definition.")); @@ -193,13 +194,30 @@ ASTPointer<ImportDirective> Parser::parseImportDirective() return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases)); } -ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) +ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token) +{ + switch(_token) + { + case Token::Interface: + return ContractDefinition::ContractKind::Interface; + case Token::Contract: + return ContractDefinition::ContractKind::Contract; + case Token::Library: + return ContractDefinition::ContractKind::Library; + default: + fatalParserError("Unsupported contract type."); + } + // FIXME: fatalParserError is not considered as throwing here + return ContractDefinition::ContractKind::Contract; +} + +ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind) { ASTNodeFactory nodeFactory(*this); ASTPointer<ASTString> docString; if (m_scanner->currentCommentLiteral() != "") docString = make_shared<ASTString>(m_scanner->currentCommentLiteral()); - expectToken(_isLibrary ? Token::Library : Token::Contract); + expectToken(_expectedKind); ASTPointer<ASTString> name = expectIdentifierToken(); vector<ASTPointer<InheritanceSpecifier>> baseContracts; if (m_scanner->currentToken() == Token::Is) @@ -252,7 +270,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) docString, baseContracts, subNodes, - _isLibrary + tokenToContractKind(_expectedKind) ); } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 79d1d1d4..282617ab 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -69,7 +69,8 @@ private: ///@name Parsing functions for the AST nodes ASTPointer<PragmaDirective> parsePragmaDirective(); ASTPointer<ImportDirective> parseImportDirective(); - ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary); + ContractDefinition::ContractKind tokenToContractKind(Token::Value _token); + ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index c6d050bb..9a557ebd 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -157,6 +157,7 @@ namespace solidity K(Hex, "hex", 0) \ K(If, "if", 0) \ K(Indexed, "indexed", 0) \ + K(Interface, "interface", 0) \ K(Internal, "internal", 0) \ K(Import, "import", 0) \ K(Is, "is", 0) \ @@ -225,7 +226,6 @@ namespace solidity K(Final, "final", 0) \ K(In, "in", 0) \ K(Inline, "inline", 0) \ - K(Interface, "interface", 0) \ K(Let, "let", 0) \ K(Match, "match", 0) \ K(NullLiteral, "null", 0) \ diff --git a/prerelease.txt b/prerelease.txt deleted file mode 100644 index e69de29b..00000000 --- a/prerelease.txt +++ /dev/null diff --git a/scripts/Dockerfile b/scripts/Dockerfile index ad448fd3..c984ce99 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -14,3 +14,5 @@ make solc && install -s solc/solc /usr/bin &&\ cd / && rm -rf solidity &&\ apk del sed build-base git make cmake gcc g++ musl-dev curl-dev boost-dev &&\ rm -rf /var/cache/apk/* + +ENTRYPOINT ["/usr/bin/solc"]
\ No newline at end of file diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index 9b432e95..6046978e 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -29,12 +29,6 @@ set -e if [[ "$OSTYPE" != "darwin"* ]]; then - if [ "$TRAVIS_BRANCH" = release ] - then - echo -n > prerelease.txt - else - date -u +"nightly.%Y.%-m.%-d" > prerelease.txt - fi ./scripts/travis-emscripten/install_deps.sh docker run -v $(pwd):/src trzeci/emscripten:sdk-tag-1.35.4-64bit ./scripts/travis-emscripten/build_emscripten.sh fi diff --git a/scripts/bytecodecompare/deploy_key.enc b/scripts/bytecodecompare/deploy_key.enc Binary files differnew file mode 100644 index 00000000..acab2a27 --- /dev/null +++ b/scripts/bytecodecompare/deploy_key.enc diff --git a/scripts/bytecodecompare/prepare_report.py b/scripts/bytecodecompare/prepare_report.py new file mode 100755 index 00000000..5a770981 --- /dev/null +++ b/scripts/bytecodecompare/prepare_report.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import sys +import glob +import subprocess +import json + +solc = sys.argv[1] +report = open("report.txt", "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") diff --git a/scripts/bytecodecompare/storebytecode.bat b/scripts/bytecodecompare/storebytecode.bat new file mode 100644 index 00000000..969a42a4 --- /dev/null +++ b/scripts/bytecodecompare/storebytecode.bat @@ -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 diff --git a/scripts/bytecodecompare/storebytecode.sh b/scripts/bytecodecompare/storebytecode.sh new file mode 100755 index 00000000..9a40bc6d --- /dev/null +++ b/scripts/bytecodecompare/storebytecode.sh @@ -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"
\ No newline at end of file diff --git a/scripts/create_source_tarball.sh b/scripts/create_source_tarball.sh index 1f78e12c..9ca72c31 100755 --- a/scripts/create_source_tarball.sh +++ b/scripts/create_source_tarball.sh @@ -15,7 +15,7 @@ REPO_ROOT="$(dirname "$0")"/.. then versionstring="$version" else - versionstring="$version-develop-$commitdate-$commithash" + versionstring="$version-nightly-$commitdate-$commithash" fi TEMPDIR=$(mktemp -d) @@ -29,6 +29,7 @@ REPO_ROOT="$(dirname "$0")"/.. # Add dependencies mkdir -p "$SOLDIR/deps/downloads/" 2>/dev/null || true wget -O "$SOLDIR/deps/downloads/jsoncpp-1.7.7.tar.gz" https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz - 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" ) diff --git a/scripts/docker_build.sh b/scripts/docker_build.sh new file mode 100755 index 00000000..22657a8c --- /dev/null +++ b/scripts/docker_build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +set -e + +docker build -t ethereum/solc:build -f scripts/Dockerfile . +tmp_container=$(docker create ethereum/solc:build sh) +mkdir -p upload +docker cp ${tmp_container}:/usr/bin/solc upload/solc-static-linux diff --git a/scripts/docker_deploy.sh b/scripts/docker_deploy.sh index d2810a3e..0abde840 100755 --- a/scripts/docker_deploy.sh +++ b/scripts/docker_deploy.sh @@ -10,13 +10,11 @@ then docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT" docker push ethereum/solc:nightly-"$version"-"$TRAVIS_COMMIT"; docker push ethereum/solc:nightly; -elif [ "$TRAVIS_BRANCH" = "release" ] -then - docker tag ethereum/solc:build ethereum/solc:stable; - docker push ethereum/solc:stable; elif [ "$TRAVIS_TAG" = v"$version" ] then + docker tag ethereum/solc:build ethereum/solc:stable; docker tag ethereum/solc:build ethereum/solc:"$version"; + docker push ethereum/solc:stable; docker push ethereum/solc:"$version"; else echo "Not publishing docker image from branch $TRAVIS_BRANCH or tag $TRAVIS_TAG" diff --git a/scripts/install_deps.bat b/scripts/install_deps.bat index bd68b07a..512a28df 100644 --- a/scripts/install_deps.bat +++ b/scripts/install_deps.bat @@ -59,4 +59,3 @@ REM Copyright (c) 2016 solidity contributors. REM --------------------------------------------------------------------------- cmake -P deps\install_deps.cmake -cmake -P scripts\install_eth.cmake diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 7cfc92f2..24cf49d5 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -96,7 +96,7 @@ case $(uname -s) in brew update brew install boost brew install cmake - if ["$CI" = true]; then + if [ "$CI" = true ]; then brew upgrade cmake brew tap ethereum/ethereum brew install cpp-ethereum @@ -314,12 +314,12 @@ case $(uname -s) in libboost-all-dev if [ "$CI" = true ]; then # Install 'eth', for use in the Solidity Tests-over-IPC. + # We will not use this 'eth', but its dependencies sudo add-apt-repository -y ppa:ethereum/ethereum sudo add-apt-repository -y ppa:ethereum/ethereum-dev sudo apt-get -y update sudo apt-get -y install eth fi - ;; #------------------------------------------------------------------------------ @@ -394,4 +394,4 @@ case $(uname -s) in echo "If you would like to get your operating system working, that would be fantastic." echo "Drop us a message at https://gitter.im/ethereum/solidity." ;; -esac
\ No newline at end of file +esac diff --git a/scripts/install_eth.cmake b/scripts/install_eth.cmake deleted file mode 100644 index 25f449e0..00000000 --- a/scripts/install_eth.cmake +++ /dev/null @@ -1,76 +0,0 @@ -#------------------------------------------------------------------------------ -# Cmake script for installing pre-requisite package eth for solidity. -# -# The aim of this script is to simply download and unpack eth binaries to the deps folder. -# -# The documentation for solidity is hosted at: -# -# http://solidity.readthedocs.io/ -# -# ------------------------------------------------------------------------------ -# This file is part of solidity. -# -# solidity is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# solidity is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with solidity. If not, see <http://www.gnu.org/licenses/> -# -# (c) 2016 solidity contributors. -#------------------------------------------------------------------------------ - -function(download URL DST_FILE STATUS) - set(TMP_FILE "${DST_FILE}.part") - - get_filename_component(FILE_NAME ${DST_FILE} NAME) - if (NOT EXISTS ${DST_FILE}) - message("Downloading ${FILE_NAME}") - file(DOWNLOAD ${URL} ${TMP_FILE} SHOW_PROGRESS STATUS DOWNLOAD_STATUS) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - if (STATUS_CODE EQUAL 0) - file(RENAME ${TMP_FILE} ${DST_FILE}) - else() - file(REMOVE ${TMP_FILE}) - list(GET DOWNLOAD_STATUS 1 ERROR_MSG) - - message("ERROR! Downloading '${FILE_NAME}' failed.") - message(STATUS "URL: ${URL}") - message(STATUS "Error: ${STATUS_CODE} ${ERROR_MSG}") - set(STATUS FALSE PARENT_SCOPE) - return() - endif() - else() - message("Using cached ${FILE_NAME}") - endif() - set(STATUS TRUE PARENT_SCOPE) -endfunction(download) - -function(download_and_unpack PACKAGE_URL DST_DIR) - get_filename_component(FILE_NAME ${PACKAGE_URL} NAME) - - set(DST_FILE "${CACHE_DIR}/${FILE_NAME}") - set(TMP_FILE "${DST_FILE}.part") - - file(MAKE_DIRECTORY ${CACHE_DIR}) - file(MAKE_DIRECTORY ${DST_DIR}) - - download(${PACKAGE_URL} ${DST_FILE} STATUS) - - if (STATUS) - message("Unpacking ${FILE_NAME} to ${DST_DIR}") - execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${DST_FILE} - WORKING_DIRECTORY ${DST_DIR}) - endif() -endfunction(download_and_unpack) - -get_filename_component(ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) -set(CACHE_DIR "${ROOT_DIR}/deps/cache") -set(INSTALL_DIR "${ROOT_DIR}/deps/install/x64/eth") -download_and_unpack("https://github.com/bobsummerwill/cpp-ethereum/releases/download/develop-v1.3.0.401/cpp-ethereum-develop-windows.zip" ${INSTALL_DIR}) diff --git a/scripts/isolate_tests.py b/scripts/isolate_tests.py index 91900aa6..a1d1c75c 100755 --- a/scripts/isolate_tests.py +++ b/scripts/isolate_tests.py @@ -7,38 +7,42 @@ # scripts/isolate_tests.py test/libsolidity/* import sys - +import re +import os +import hashlib +from os.path import join def extract_cases(path): - lines = open(path).read().splitlines() + lines = open(path, 'rb').read().splitlines() inside = False + delimiter = '' tests = [] for l in lines: if inside: - if l.strip().endswith(')";'): + if l.strip().endswith(')' + delimiter + '";'): inside = False else: tests[-1] += l + '\n' else: - if l.strip().endswith('R"('): + m = re.search(r'R"([^(]*)\($', l.strip()) + if m: inside = True + delimiter = m.group(1) tests += [''] return tests -def write_cases(tests, start=0): - for i, test in enumerate(tests, start=start): - open('test%d.sol' % i, 'w').write(test) - +def write_cases(tests): + for test in tests: + open('test_%s.sol' % hashlib.sha256(test).hexdigest(), 'wb').write(test) if __name__ == '__main__': - files = sys.argv[1:] + path = sys.argv[1] - i = 0 - for path in files: - cases = extract_cases(path) - write_cases(cases, start=i) - i += len(cases) + for root, dir, files in os.walk(path): + for f in files: + cases = extract_cases(join(root, f)) + write_cases(cases) diff --git a/scripts/release.sh b/scripts/release.sh index e9f43f6c..a2f4d98a 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -88,5 +88,5 @@ if [[ "$OSTYPE" == "darwin"* ]]; then fi # And ZIP it all up, with a filename suffix passed in on the command-line. - -zip -j $REPO_ROOT/solidity-$ZIP_SUFFIX.zip $ZIP_TEMP_DIR/* +mkdir -p $REPO_ROOT/upload +zip -j $REPO_ROOT/upload/solidity-$ZIP_SUFFIX.zip $ZIP_TEMP_DIR/* diff --git a/scripts/tests.sh b/scripts/tests.sh index d47edd28..6b76c154 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -42,8 +42,16 @@ test "${output//[[:blank:]]/}" = "3" # instead. This will go away soon. if [[ "$OSTYPE" == "darwin"* ]]; then ETH_PATH="$REPO_ROOT/eth" -else +elif [ -z $CI ]; then ETH_PATH="eth" +else + mkdir -p /tmp/test + wget -O /tmp/test/eth https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth + test "$(shasum /tmp/test/eth)" = "c132e8989229e4840831a4fb1a1d058b732a11d5 /tmp/test/eth" + sync + chmod +x /tmp/test/eth + sync # Otherwise we might get a "text file busy" error + ETH_PATH="/tmp/test/eth" fi # This trailing ampersand directs the shell to run the command in the background, diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index a6eb01a0..02740e6c 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -94,6 +94,8 @@ emmake make -j 4 cd .. cp build/solc/soljson.js ./ +mkdir -p upload +cp soljson.js upload/ OUTPUT_SIZE=`ls -la build/solc/soljson.js` diff --git a/scripts/update_bugs_by_version.py b/scripts/update_bugs_by_version.py new file mode 100755 index 00000000..c4bc0c9b --- /dev/null +++ b/scripts/update_bugs_by_version.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# +# This script is used to generate the list of bugs per compiler version +# from the list of bugs. +# It updates the list in place and signals failure if there were changes. +# This makes it possible to use this script as part of CI to check +# that the list is up to date. + +import os +import json +import re +import sys + +def comp(version_string): + return [int(c) for c in version_string.split('.')] + +path = os.path.dirname(os.path.realpath(__file__)) +with open(path + '/../docs/bugs.json') as bugsFile: + bugs = json.load(bugsFile) + +versions = {} +with open(path + '/../Changelog.md') as changelog: + for line in changelog: + m = re.search(r'^### (\S+) \((\d+-\d+-\d+)\)$', line) + if m: + versions[m.group(1)] = {} + versions[m.group(1)]['released'] = m.group(2) + +for v in versions: + versions[v]['bugs'] = [] + for bug in bugs: + if 'introduced' in bug and comp(bug['introduced']) > comp(v): + continue + if comp(bug['fixed']) <= comp(v): + continue + versions[v]['bugs'] += [bug['name']] + +with open(path + '/../docs/bugs_by_version.json', 'r+') as bugs_by_version: + old_contents = bugs_by_version.read() + new_contents = json.dumps(versions, sort_keys=True, indent=4) + bugs_by_version.seek(0) + bugs_by_version.write(new_contents) + sys.exit(old_contents != new_contents)
\ No newline at end of file diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt index fa7e0bde..a5515d81 100644 --- a/solc/CMakeLists.txt +++ b/solc/CMakeLists.txt @@ -18,7 +18,7 @@ else() endif() if (EMSCRIPTEN) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\"]' -s RESERVED_FUNCTION_POINTERS=20") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\",\"_compileStandard\"]' -s RESERVED_FUNCTION_POINTERS=20") add_executable(soljson jsonCompiler.cpp ${HEADERS}) eth_use(soljson REQUIRED Solidity::solidity) else() diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 31f70272..63d41cdf 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -32,6 +32,7 @@ #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/CompilerStack.h> +#include <libsolidity/interface/StandardCompiler.h> #include <libsolidity/interface/SourceReferenceFormatter.h> #include <libsolidity/interface/GasEstimator.h> #include <libsolidity/formal/Why3Translator.h> @@ -102,6 +103,8 @@ static string const g_strSrcMapRuntime = "srcmap-runtime"; static string const g_strVersion = "version"; static string const g_stdinFileNameStr = "<stdin>"; static string const g_strMetadataLiteral = "metadata-literal"; +static string const g_strAllowPaths = "allow-paths"; +static string const g_strStandardJSON = "standard-json"; static string const g_argAbi = g_strAbi; 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_stdinFileName = g_stdinFileNameStr; static string const g_argMetadataLiteral = g_strMetadataLiteral; +static string const g_argAllowPaths = g_strAllowPaths; +static string const g_argStandardJSON = g_strStandardJSON; /// Possible arguments to for --combined-json static set<string> const g_combinedJsonArgs{ @@ -315,49 +320,40 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co void CommandLineInterface::handleGasEstimation(string const& _contract) { - using Gas = GasEstimator::GasConsumption; - if (!m_compiler->assemblyItems(_contract) && !m_compiler->runtimeAssemblyItems(_contract)) - return; + Json::Value estimates = m_compiler->gasEstimates(_contract); cout << "Gas estimation:" << endl; - if (eth::AssemblyItems const* items = m_compiler->assemblyItems(_contract)) + + if (estimates["creation"].isObject()) { - Gas gas = GasEstimator::functionalEstimation(*items); - u256 bytecodeSize(m_compiler->runtimeObject(_contract).bytecode.size()); + Json::Value creation = estimates["creation"]; cout << "construction:" << endl; - cout << " " << gas << " + " << (bytecodeSize * eth::GasCosts::createDataGas) << " = "; - gas += bytecodeSize * eth::GasCosts::createDataGas; - cout << gas << endl; + cout << " " << creation["executionCost"].asString(); + cout << " + " << creation["codeDepositCost"].asString(); + cout << " = " << creation["totalCost"].asString() << endl; } - if (eth::AssemblyItems const* items = m_compiler->runtimeAssemblyItems(_contract)) + + if (estimates["external"].isObject()) { - ContractDefinition const& contract = m_compiler->contractDefinition(_contract); + Json::Value externalFunctions = estimates["external"]; cout << "external:" << endl; - for (auto it: contract.interfaceFunctions()) + for (auto const& name: externalFunctions.getMemberNames()) { - string sig = it.second->externalSignature(); - GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig); - cout << " " << sig << ":\t" << gas << endl; - } - if (contract.fallbackFunction()) - { - GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, "INVALID"); - cout << " fallback:\t" << gas << endl; + if (name.empty()) + cout << " fallback:\t"; + else + cout << " " << name << ":\t"; + cout << externalFunctions[name].asString() << endl; } + } + + if (estimates["internal"].isObject()) + { + Json::Value internalFunctions = estimates["internal"]; cout << "internal:" << endl; - for (auto const& it: contract.definedFunctions()) + for (auto const& name: internalFunctions.getMemberNames()) { - if (it->isPartOfExternalInterface() || it->isConstructor()) - continue; - size_t entry = m_compiler->functionEntryPoint(_contract, *it); - GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); - if (entry > 0) - gas = GasEstimator::functionalEstimation(*items, entry, *it); - FunctionType type(*it); - cout << " " << it->name() << "("; - auto paramTypes = type.parameterTypes(); - for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) - cout << (*it)->toString() << (it + 1 == paramTypes.end() ? "" : ","); - cout << "):\t" << gas << endl; + cout << " " << name << ":\t"; + cout << internalFunctions[name].asString() << endl; } } } @@ -534,6 +530,11 @@ Allowed options)", ) (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(), "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 " "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"); outputComponents.add_options() (g_argAst.c_str(), "AST of all source files.") @@ -610,6 +616,69 @@ Allowed options)", bool CommandLineInterface::processInput() { + ReadFile::Callback fileReader = [this](string const& _path) + { + try + { + auto path = boost::filesystem::path(_path); + auto canonicalPath = boost::filesystem::canonical(path); + bool isAllowed = false; + for (auto const& allowedDir: m_allowedDirectories) + { + // If dir is a prefix of boostPath, we are fine. + if ( + std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) && + std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin()) + ) + { + isAllowed = true; + break; + } + } + if (!isAllowed) + return ReadFile::Result{false, "File outside of allowed directories."}; + else if (!boost::filesystem::exists(path)) + return ReadFile::Result{false, "File not found."}; + else if (!boost::filesystem::is_regular_file(canonicalPath)) + return ReadFile::Result{false, "Not a valid file."}; + else + { + auto contents = dev::contentsString(canonicalPath.string()); + m_sourceCodes[path.string()] = contents; + return ReadFile::Result{true, contents}; + } + } + catch (Exception const& _exception) + { + return ReadFile::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)}; + } + catch (...) + { + return ReadFile::Result{false, "Unknown exception in read callback."}; + } + }; + + if (m_args.count(g_argAllowPaths)) + { + vector<string> paths; + for (string const& path: boost::split(paths, m_args[g_argAllowPaths].as<string>(), boost::is_any_of(","))) + m_allowedDirectories.push_back(boost::filesystem::path(path)); + } + + if (m_args.count(g_argStandardJSON)) + { + string input; + while (!cin.eof()) + { + string tmp; + getline(cin, tmp); + input.append(tmp + "\n"); + } + StandardCompiler compiler(fileReader); + cout << compiler.compile(input) << endl; + return true; + } + readInputFilesAndConfigureRemappings(); if (m_args.count(g_argLibraries)) @@ -630,37 +699,6 @@ bool CommandLineInterface::processInput() return link(); } - CompilerStack::ReadFileCallback fileReader = [this](string const& _path) - { - auto path = boost::filesystem::path(_path); - if (!boost::filesystem::exists(path)) - return CompilerStack::ReadFileResult{false, "File not found."}; - auto canonicalPath = boost::filesystem::canonical(path); - bool isAllowed = false; - for (auto const& allowedDir: m_allowedDirectories) - { - // If dir is a prefix of boostPath, we are fine. - if ( - std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) && - std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin()) - ) - { - isAllowed = true; - break; - } - } - if (!isAllowed) - return CompilerStack::ReadFileResult{false, "File outside of allowed directories."}; - else if (!boost::filesystem::is_regular_file(canonicalPath)) - return CompilerStack::ReadFileResult{false, "Not a valid file."}; - else - { - auto contents = dev::contentsString(canonicalPath.string()); - m_sourceCodes[path.string()] = contents; - return CompilerStack::ReadFileResult{true, contents}; - } - }; - m_compiler.reset(new CompilerStack(fileReader)); auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); }; try @@ -877,7 +915,9 @@ void CommandLineInterface::handleAst(string const& _argStr) bool CommandLineInterface::actOnInput() { - if (m_onlyAssemble) + if (m_args.count(g_argStandardJSON)) + return true; + else if (m_onlyAssemble) outputAssembly(); else if (m_onlyLink) writeLinkedFiles(); diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index 6ebd1a55..42c25de0 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -36,6 +36,7 @@ #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/CompilerStack.h> +#include <libsolidity/interface/StandardCompiler.h> #include <libsolidity/interface/SourceReferenceFormatter.h> #include <libsolidity/ast/ASTJsonConverter.h> #include <libsolidity/interface/Version.h> @@ -50,87 +51,9 @@ extern "C" { typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); } -string formatError( - Exception const& _exception, - string const& _name, - function<Scanner const&(string const&)> const& _scannerFromSourceName -) +ReadFile::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullptr) { - ostringstream errorOutput; - SourceReferenceFormatter::printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName); - return errorOutput.str(); -} - -Json::Value functionHashes(ContractDefinition const& _contract) -{ - Json::Value functionHashes(Json::objectValue); - for (auto const& it: _contract.interfaceFunctions()) - functionHashes[it.second->externalSignature()] = toHex(it.first.ref()); - return functionHashes; -} - -Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) -{ - if (_gas.isInfinite || _gas.value > std::numeric_limits<Json::LargestUInt>::max()) - return Json::Value(Json::nullValue); - else - return Json::Value(Json::LargestUInt(_gas.value)); -} - -Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) -{ - Json::Value gasEstimates(Json::objectValue); - using Gas = GasEstimator::GasConsumption; - if (!_compiler.assemblyItems(_contract) && !_compiler.runtimeAssemblyItems(_contract)) - return gasEstimates; - if (eth::AssemblyItems const* items = _compiler.assemblyItems(_contract)) - { - Gas gas = GasEstimator::functionalEstimation(*items); - u256 bytecodeSize(_compiler.runtimeObject(_contract).bytecode.size()); - Json::Value creationGas(Json::arrayValue); - creationGas[0] = gasToJson(gas); - creationGas[1] = gasToJson(bytecodeSize * eth::GasCosts::createDataGas); - gasEstimates["creation"] = creationGas; - } - if (eth::AssemblyItems const* items = _compiler.runtimeAssemblyItems(_contract)) - { - ContractDefinition const& contract = _compiler.contractDefinition(_contract); - Json::Value externalFunctions(Json::objectValue); - for (auto it: contract.interfaceFunctions()) - { - string sig = it.second->externalSignature(); - externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); - } - if (contract.fallbackFunction()) - externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); - gasEstimates["external"] = externalFunctions; - Json::Value internalFunctions(Json::objectValue); - for (auto const& it: contract.definedFunctions()) - { - if (it->isPartOfExternalInterface() || it->isConstructor()) - continue; - size_t entry = _compiler.functionEntryPoint(_contract, *it); - GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); - if (entry > 0) - gas = GasEstimator::functionalEstimation(*items, entry, *it); - FunctionType type(*it); - string sig = it->name() + "("; - auto paramTypes = type.parameterTypes(); - for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) - sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ","); - sig += ")"; - internalFunctions[sig] = gasToJson(gas); - } - gasEstimates["internal"] = internalFunctions; - } - return gasEstimates; -} - -string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback) -{ - Json::Value output(Json::objectValue); - Json::Value errors(Json::arrayValue); - CompilerStack::ReadFileCallback readCallback; + ReadFile::Callback readCallback; if (_readCallback) { readCallback = [=](string const& _path) @@ -138,29 +61,85 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback char* contents_c = nullptr; char* error_c = nullptr; _readCallback(_path.c_str(), &contents_c, &error_c); - CompilerStack::ReadFileResult result; + ReadFile::Result result; result.success = true; if (!contents_c && !error_c) { result.success = false; - result.contentsOrErrorMesage = "File not found."; + result.contentsOrErrorMessage = "File not found."; } if (contents_c) { result.success = true; - result.contentsOrErrorMesage = string(contents_c); + result.contentsOrErrorMessage = string(contents_c); free(contents_c); } if (error_c) { result.success = false; - result.contentsOrErrorMesage = string(error_c); + result.contentsOrErrorMessage = string(error_c); free(error_c); } return result; }; } - CompilerStack compiler(readCallback); + return readCallback; +} + +Json::Value functionHashes(ContractDefinition const& _contract) +{ + Json::Value functionHashes(Json::objectValue); + for (auto const& it: _contract.interfaceFunctions()) + functionHashes[it.second->externalSignature()] = toHex(it.first.ref()); + return functionHashes; +} + +/// Translates a gas value as a string to a JSON number or null +Json::Value gasToJson(Json::Value const& _value) +{ + if (_value.isObject()) + { + Json::Value ret = Json::objectValue; + for (auto const& sig: _value.getMemberNames()) + ret[sig] = gasToJson(_value[sig]); + return ret; + } + + if (_value == "infinite") + return Json::Value(Json::nullValue); + + u256 value(_value.asString()); + if (value > std::numeric_limits<Json::LargestUInt>::max()) + return Json::Value(Json::nullValue); + else + return Json::Value(Json::LargestUInt(value)); +} + +Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) +{ + Json::Value estimates = _compiler.gasEstimates(_contract); + Json::Value output(Json::objectValue); + + if (estimates["creation"].isObject()) + { + Json::Value creation(Json::arrayValue); + creation[0] = gasToJson(estimates["creation"]["executionCost"]); + creation[1] = gasToJson(estimates["creation"]["codeDepositCost"]); + output["creation"] = creation; + } + else + output["creation"] = Json::objectValue; + output["external"] = gasToJson(estimates.get("external", Json::objectValue)); + output["internal"] = gasToJson(estimates.get("internal", Json::objectValue)); + + return output; +} + +string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback) +{ + Json::Value output(Json::objectValue); + Json::Value errors(Json::arrayValue); + CompilerStack compiler(wrapReadCallback(_readCallback)); auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return compiler.scanner(_sourceName); }; bool success = false; try @@ -170,7 +149,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback for (auto const& error: compiler.errors()) { auto err = dynamic_pointer_cast<Error const>(error); - errors.append(formatError( + errors.append(SourceReferenceFormatter::formatExceptionInformation( *error, (err->type() == Error::Type::Warning) ? "Warning" : "Error", scannerFromSourceName @@ -180,19 +159,19 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback } catch (Error const& error) { - errors.append(formatError(error, error.typeName(), scannerFromSourceName)); + errors.append(SourceReferenceFormatter::formatExceptionInformation(error, error.typeName(), scannerFromSourceName)); } catch (CompilerError const& exception) { - errors.append(formatError(exception, "Compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); + errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (InternalCompilerError const& exception) { - errors.append(formatError(exception, "Internal compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); + errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Internal compiler error (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (UnimplementedFeatureError const& exception) { - errors.append(formatError(exception, "Unimplemented feature (" + exception.lineInfo() + ")", scannerFromSourceName)); + errors.append(SourceReferenceFormatter::formatExceptionInformation(exception, "Unimplemented feature (" + exception.lineInfo() + ")", scannerFromSourceName)); } catch (Exception const& exception) { @@ -245,7 +224,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback { Json::Value errors(Json::arrayValue); for (auto const& error: formalErrors) - errors.append(formatError( + errors.append(SourceReferenceFormatter::formatExceptionInformation( *error, (error->type() == Error::Type::Warning) ? "Warning" : "Error", scannerFromSourceName @@ -314,6 +293,13 @@ string compileSingle(string const& _input, bool _optimize) return compile(sources, _optimize, nullptr); } + +string compileStandardInternal(string const& _input, CStyleReadFileCallback _readCallback = nullptr) +{ + StandardCompiler compiler(wrapReadCallback(_readCallback)); + return compiler.compile(_input); +} + static string s_outputBuffer; extern "C" @@ -337,4 +323,9 @@ extern char const* compileJSONCallback(char const* _input, bool _optimize, CStyl s_outputBuffer = compileMulti(_input, _optimize, _readCallback); return s_outputBuffer.c_str(); } +extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback) +{ + s_outputBuffer = compileStandardInternal(_input, _readCallback); + return s_outputBuffer.c_str(); +} } diff --git a/std/StandardToken.sol b/std/StandardToken.sol index 4dad8541..51f925e0 100644 --- a/std/StandardToken.sol +++ b/std/StandardToken.sol @@ -25,7 +25,7 @@ contract StandardToken is Token { return doTransfer(msg.sender, _to, _value); } - function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { + function transferFrom(address _from, address _to, uint256 _value) returns (bool) { if (m_allowance[_from][msg.sender] >= _value) { if (doTransfer(_from, _to, _value)) { m_allowance[_from][msg.sender] -= _value; @@ -53,7 +53,7 @@ contract StandardToken is Token { return true; } - function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + function allowance(address _owner, address _spender) constant returns (uint256) { return m_allowance[_owner][_spender]; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4d56ec9d..01a9a188 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,4 +21,4 @@ include_directories(BEFORE ..) target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) add_executable(solfuzzer fuzzer.cpp) -target_link_libraries(solfuzzer soljson) +target_link_libraries(solfuzzer soljson ${Boost_PROGRAM_OPTIONS_LIBRARIES}) diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index f8b364d1..3ea3b1fe 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -220,7 +220,10 @@ void RPCSession::test_setChainParams(vector<string> const& _accounts) "accountStartNonce": "0x", "maximumExtraDataSize": "0x1000000", "blockReward": "0x", - "allowFutureBlocks": "1" + "allowFutureBlocks": "1", + "homsteadForkBlock": "0x00", + "EIP150ForkBlock": "0x00", + "EIP158ForkBlock": "0x00" }, "genesis": { "author": "0000000000000010000000000000000000000000", diff --git a/test/RPCSession.h b/test/RPCSession.h index b37cc322..f3c3339a 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -68,7 +68,7 @@ private: int m_socket; /// Socket read timeout in milliseconds. Needs to be large because the key generation routine /// might take long. - unsigned static constexpr m_readTimeOutMS = 15000; + unsigned static constexpr m_readTimeOutMS = 300000; char m_readBuf[512000]; }; #endif @@ -133,7 +133,7 @@ private: IPCSocket m_ipcSocket; size_t m_rpcSequence = 1; - unsigned m_maxMiningTime = 15000; // 15 seconds + unsigned m_maxMiningTime = 6000000; // 600 seconds unsigned m_sleepTime = 10; // 10 milliseconds unsigned m_successfulMineRuns = 0; diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp index 0c0857c9..094b59c6 100644 --- a/test/TestHelper.cpp +++ b/test/TestHelper.cpp @@ -43,8 +43,10 @@ Options::Options() optimize = true; else if (string(suite.argv[i]) == "--show-messages") showMessages = true; + else if (string(suite.argv[i]) == "--no-ipc") + disableIPC = true; - if (ipcPath.empty()) + if (!disableIPC && ipcPath.empty()) if (auto path = getenv("ETH_TEST_IPC")) ipcPath = path; } diff --git a/test/TestHelper.h b/test/TestHelper.h index 8f05eead..3e74b54c 100644 --- a/test/TestHelper.h +++ b/test/TestHelper.h @@ -108,6 +108,7 @@ struct Options: boost::noncopyable std::string ipcPath; bool showMessages = false; bool optimize = false; + bool disableIPC = false; static Options const& get(); diff --git a/test/boostTest.cpp b/test/boostTest.cpp index d1d35be3..6fc1c925 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -21,11 +21,9 @@ * Original code taken from boost sources. */ -#define BOOST_TEST_MODULE EthereumTests #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" - #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4535) // calling _set_se_translator requires /EHa @@ -36,3 +34,32 @@ #endif #pragma GCC diagnostic pop + +#include <test/TestHelper.h> + +using namespace boost::unit_test; + +test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) +{ + master_test_suite_t& master = framework::master_test_suite(); + master.p_name.value = "SolidityTests"; + if (dev::test::Options::get().disableIPC) + { + for (auto suite: { + "SolidityAuctionRegistrar", + "SolidityFixedFeeRegistrar", + "SolidityWallet", + "LLLEndToEndTest", + "GasMeterTests", + "SolidityEndToEndTest", + "SolidityOptimizer" + }) + { + auto id = master.get(suite); + assert(id != INV_TEST_UNIT_ID); + master.remove(id); + } + } + + return 0; +} diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index e2ee6a5e..6dc3f77f 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -31,7 +31,10 @@ set -e REPO_ROOT="$(dirname "$0")"/.. SOLC="$REPO_ROOT/build/solc/solc" -# Compile all files in std and examples. +echo "Checking that the bug list is up to date..." +"$REPO_ROOT"/scripts/update_bugs_by_version.py + +echo "Compiling all files in std and examples..." for f in "$REPO_ROOT"/std/*.sol do @@ -73,10 +76,10 @@ TMPDIR=$(mktemp -d) cd "$REPO_ROOT" REPO_ROOT=$(pwd) # make it absolute cd "$TMPDIR" - "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/contracts/* "$REPO_ROOT"/test/libsolidity/*EndToEnd* + "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/ for f in *.sol do - "$REPO_ROOT"/build/test/solfuzzer < "$f" + "$REPO_ROOT"/build/test/solfuzzer --quiet < "$f" done ) rm -rf "$TMPDIR" diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index 410313c5..053d880f 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -16,21 +16,32 @@ */ /** * Executable for use with AFL <http://lcamtuf.coredump.cx/afl>. - * Reads a single source from stdin and signals a failure for internal errors. */ +#include <libevmasm/Assembly.h> +#include <libevmasm/ConstantOptimiser.h> + #include <json/json.h> +#include <boost/program_options.hpp> + #include <string> #include <iostream> using namespace std; +using namespace dev; +using namespace dev::eth; +namespace po = boost::program_options; extern "C" { extern char const* compileJSON(char const* _input, bool _optimize); +typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); +extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback); } +bool quiet = false; + string contains(string const& _haystack, vector<string> const& _needles) { for (string const& needle: _needles) @@ -39,7 +50,42 @@ string contains(string const& _haystack, vector<string> const& _needles) return ""; } -int main() +void testConstantOptimizer() +{ + if (!quiet) + cout << "Testing constant optimizer" << endl; + vector<u256> numbers; + while (!cin.eof()) + { + h256 data; + cin.read(reinterpret_cast<char*>(data.data()), 32); + numbers.push_back(u256(data)); + } + if (!quiet) + cout << "Got " << numbers.size() << " inputs:" << endl; + + Assembly assembly; + for (u256 const& n: numbers) + { + if (!quiet) + cout << n << endl; + assembly.append(n); + } + for (bool isCreation: {false, true}) + { + for (unsigned runs: {1, 2, 3, 20, 40, 100, 200, 400, 1000}) + { + ConstantOptimisationMethod::optimiseConstants( + isCreation, + runs, + assembly, + const_cast<AssemblyItems&>(assembly.items()) + ); + } + } +} + +string readInput() { string input; while (!cin.eof()) @@ -48,6 +94,41 @@ int main() getline(cin, s); input += s + '\n'; } + return input; +} + +void testStandardCompiler() +{ + if (!quiet) + cout << "Testing compiler via JSON interface." << endl; + string input = readInput(); + string outputString(compileStandard(input.c_str(), NULL)); + Json::Value output; + if (!Json::Reader().parse(outputString, output)) + { + cout << "Compiler produced invalid JSON output." << endl; + abort(); + } + if (output.isMember("errors")) + for (auto const& error: output["errors"]) + { + string invalid = contains(error["type"].asString(), vector<string>{ + "Exception", + "InternalCompilerError" + }); + if (!invalid.empty()) + { + cout << "Invalid error: \"" << error["type"].asString() << "\"" << endl; + abort(); + } + } +} + +void testCompiler() +{ + if (!quiet) + cout << "Testing compiler." << endl; + string input = readInput(); bool optimize = true; string outputString(compileJSON(input.c_str(), optimize)); @@ -87,5 +168,57 @@ int main() cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl; abort(); } +} + +int main(int argc, char** argv) +{ + po::options_description options( + R"(solfuzzer, fuzz-testing binary for use with AFL. +Usage: solfuzzer [Options] < input +Reads a single source from stdin, compiles it and signals a failure for internal errors. + +Allowed options)", + po::options_description::m_default_line_length, + po::options_description::m_default_line_length - 23); + options.add_options() + ("help", "Show this help screen.") + ("quiet", "Only output errors.") + ( + "standard-json", + "Test via the standard-json interface, i.e. " + "input is expected to be JSON-encoded instead of " + "plain source file." + ) + ( + "const-opt", + "Run the constant optimizer instead of compiling. " + "Expects a binary string of up to 32 bytes on stdin." + ); + + po::variables_map arguments; + try + { + po::command_line_parser cmdLineParser(argc, argv); + cmdLineParser.options(options); + po::store(cmdLineParser.run(), arguments); + } + catch (po::error const& _exception) + { + cerr << _exception.what() << endl; + return 1; + } + + if (arguments.count("quiet")) + quiet = true; + + if (arguments.count("help")) + cout << options; + else if (arguments.count("const-opt")) + testConstantOptimizer(); + else if (arguments.count("standard-json")) + testStandardCompiler(); + else + testCompiler(); + return 0; } diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index 0972ce82..d23815b4 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -41,7 +41,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) { CompilerStack c; c.addSource("a", "contract C {}"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(source_location) { CompilerStack c; c.addSource("a", "contract C { function f() { var x = 2; x++; } }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -66,7 +66,7 @@ BOOST_AUTO_TEST_CASE(inheritance_specifier) { CompilerStack c; c.addSource("a", "contract C1 {} contract C2 is C1 {}"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE(using_for_directive) { CompilerStack c; c.addSource("a", "library L {} contract C { using L for uint; }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -91,14 +91,14 @@ BOOST_AUTO_TEST_CASE(using_for_directive) BOOST_CHECK_EQUAL(usingFor["children"][0]["name"], "UserDefinedTypeName"); BOOST_CHECK_EQUAL(usingFor["children"][0]["attributes"]["name"], "L"); BOOST_CHECK_EQUAL(usingFor["children"][1]["name"], "ElementaryTypeName"); - BOOST_CHECK_EQUAL(usingFor["children"][1]["attributes"]["name"], "uint"); + BOOST_CHECK_EQUAL(usingFor["children"][1]["attributes"]["name"], "uint"); } BOOST_AUTO_TEST_CASE(enum_value) { CompilerStack c; c.addSource("a", "contract C { enum E { A, B } }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -115,7 +115,7 @@ BOOST_AUTO_TEST_CASE(modifier_definition) { CompilerStack c; c.addSource("a", "contract C { modifier M(uint i) { _; } function F() M(1) {} }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -129,7 +129,7 @@ BOOST_AUTO_TEST_CASE(modifier_invocation) { CompilerStack c; c.addSource("a", "contract C { modifier M(uint i) { _; } function F() M(1) {} }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(event_definition) { CompilerStack c; c.addSource("a", "contract C { event E(); }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -159,7 +159,7 @@ BOOST_AUTO_TEST_CASE(array_type_name) { CompilerStack c; c.addSource("a", "contract C { uint[] i; }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -172,7 +172,7 @@ BOOST_AUTO_TEST_CASE(placeholder_statement) { CompilerStack c; c.addSource("a", "contract C { modifier M { _; } }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -185,7 +185,7 @@ BOOST_AUTO_TEST_CASE(non_utf8) { CompilerStack c; c.addSource("a", "contract C { function f() { var x = hex\"ff\"; } }"); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); @@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(function_type) "contract C { function f(function() external payable returns (uint) x) " "returns (function() external constant returns (uint)) {} }" ); - c.parse(); + c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 0671fb15..f90cb105 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -248,6 +248,51 @@ BOOST_AUTO_TEST_CASE(multiple_external_functions) testRunTimeGas("g(uint256)", vector<bytes>{encodeArgs(2)}); } +BOOST_AUTO_TEST_CASE(exponent_size) +{ + char const* sourceCode = R"( + contract A { + function g(uint x) returns (uint) { + return x ** 0x100; + } + function h(uint x) returns (uint) { + return x ** 0x10000; + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("g(uint256)", vector<bytes>{encodeArgs(2)}); + testRunTimeGas("h(uint256)", vector<bytes>{encodeArgs(2)}); +} + +BOOST_AUTO_TEST_CASE(balance_gas) +{ + char const* sourceCode = R"( + contract A { + function lookup_balance(address a) returns (uint) { + return a.balance; + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("lookup_balance(address)", vector<bytes>{encodeArgs(2), encodeArgs(100)}); +} + +BOOST_AUTO_TEST_CASE(extcodesize_gas) +{ + char const* sourceCode = R"( + contract A { + function f() returns (uint _s) { + assembly { + _s := extcodesize(0x30) + } + } + } + )"; + testCreationTimeGas(sourceCode); + testRunTimeGas("f()", vector<bytes>{encodeArgs()}); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 9035599b..8bf4df8e 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -30,6 +30,7 @@ #include <libevmasm/Assembly.h> #include <boost/optional.hpp> +#include <boost/algorithm/string/replace.hpp> #include <string> #include <memory> @@ -63,7 +64,7 @@ boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _ass } if (!success) { - BOOST_CHECK_EQUAL(stack.errors().size(), 1); + BOOST_REQUIRE_EQUAL(stack.errors().size(), 1); return *stack.errors().front(); } else @@ -137,22 +138,22 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_AUTO_TEST_CASE(simple_instructions) { - BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub }")); + BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub pop }")); } BOOST_AUTO_TEST_CASE(suicide_selfdestruct) { - BOOST_CHECK(successParse("{ suicide selfdestruct }")); + BOOST_CHECK(successParse("{ 0x01 suicide 0x02 selfdestruct }")); } BOOST_AUTO_TEST_CASE(keywords) { - BOOST_CHECK(successParse("{ byte return address }")); + BOOST_CHECK(successParse("{ 1 2 byte 2 return address pop }")); } BOOST_AUTO_TEST_CASE(constants) { - BOOST_CHECK(successParse("{ 7 8 mul }")); + BOOST_CHECK(successParse("{ 7 8 mul pop }")); } BOOST_AUTO_TEST_CASE(vardecl) @@ -162,37 +163,43 @@ BOOST_AUTO_TEST_CASE(vardecl) BOOST_AUTO_TEST_CASE(assignment) { - BOOST_CHECK(successParse("{ 7 8 add =: x }")); + BOOST_CHECK(successParse("{ let x := 2 7 8 add =: x }")); } BOOST_AUTO_TEST_CASE(label) { - BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump }")); + BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump pop }")); } BOOST_AUTO_TEST_CASE(label_complex) { - BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) }")); + BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop }")); } BOOST_AUTO_TEST_CASE(functional) { - BOOST_CHECK(successParse("{ add(7, mul(6, x)) add mul(7, 8) }")); + BOOST_CHECK(successParse("{ let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }")); } BOOST_AUTO_TEST_CASE(functional_assignment) { - BOOST_CHECK(successParse("{ x := 7 }")); + BOOST_CHECK(successParse("{ let x := 2 x := 7 }")); } BOOST_AUTO_TEST_CASE(functional_assignment_complex) { - BOOST_CHECK(successParse("{ x := add(7, mul(6, x)) add mul(7, 8) }")); + BOOST_CHECK(successParse("{ let x := 2 x := add(7, mul(6, x)) mul(7, 8) add }")); } BOOST_AUTO_TEST_CASE(vardecl_complex) { - BOOST_CHECK(successParse("{ let x := add(7, mul(6, x)) add mul(7, 8) }")); + BOOST_CHECK(successParse("{ let y := 2 let x := add(7, mul(6, y)) add mul(7, 8) }")); +} + +BOOST_AUTO_TEST_CASE(variable_use_before_decl) +{ + CHECK_PARSE_ERROR("{ x := 2 let x := 3 }", DeclarationError, "Variable x used before it was declared."); + CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared."); } BOOST_AUTO_TEST_CASE(blocks) @@ -202,17 +209,38 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_AUTO_TEST_CASE(function_definitions) { - BOOST_CHECK(successParse("{ function f() { } function g(a) -> (x) { } }")); + BOOST_CHECK(successParse("{ function f() { } function g(a) -> x { } }")); } BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) { - BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> (x, y) { } }")); + BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> x, y { } }")); } BOOST_AUTO_TEST_CASE(function_calls) { - BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }")); + BOOST_CHECK(successParse("{ function f(a) -> b {} function g(a, b, c) {} function x() { g(1, 2, f(mul(2, 3))) x() } }")); +} + +BOOST_AUTO_TEST_CASE(opcode_for_functions) +{ + CHECK_PARSE_ERROR("{ function gas() { } }", ParserError, "Cannot use instruction names for identifier names."); +} + +BOOST_AUTO_TEST_CASE(opcode_for_function_args) +{ + CHECK_PARSE_ERROR("{ function f(gas) { } }", ParserError, "Cannot use instruction names for identifier names."); + CHECK_PARSE_ERROR("{ function f() -> gas { } }", ParserError, "Cannot use instruction names for identifier names."); +} + +BOOST_AUTO_TEST_CASE(name_clashes) +{ + CHECK_PARSE_ERROR("{ let g := 2 function g() { } }", DeclarationError, "Function name g already taken in this scope"); +} + +BOOST_AUTO_TEST_CASE(variable_access_cross_functions) +{ + CHECK_PARSE_ERROR("{ let x := 2 function g() { x pop } }", DeclarationError, "Identifier not found."); } BOOST_AUTO_TEST_SUITE_END() @@ -226,7 +254,7 @@ BOOST_AUTO_TEST_CASE(print_smoke) BOOST_AUTO_TEST_CASE(print_instructions) { - parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n}"); + parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n pop\n}"); } BOOST_AUTO_TEST_CASE(print_subblock) @@ -236,7 +264,7 @@ BOOST_AUTO_TEST_CASE(print_subblock) BOOST_AUTO_TEST_CASE(print_functional) { - parsePrintCompare("{\n mul(sload(0x12), 7)\n}"); + parsePrintCompare("{\n let x := mul(sload(0x12), 7)\n}"); } BOOST_AUTO_TEST_CASE(print_label) @@ -251,13 +279,13 @@ BOOST_AUTO_TEST_CASE(print_assignments) BOOST_AUTO_TEST_CASE(print_string_literals) { - parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n}"); + parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n pop\n}"); } BOOST_AUTO_TEST_CASE(print_string_literal_unicode) { - string source = "{ \"\\u1bac\" }"; - string parsed = "{\n \"\\xe1\\xae\\xac\"\n}"; + string source = "{ let x := \"\\u1bac\" }"; + string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}"; assembly::InlineAssemblyStack stack; BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(source)))); BOOST_REQUIRE(stack.errors().empty()); @@ -267,12 +295,26 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) { - parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> (x, y)\n {\n }\n}"); + parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> x, y\n {\n }\n}"); } BOOST_AUTO_TEST_CASE(function_calls) { - parsePrintCompare("{\n g(1, mul(2, x), f(mul(2, 3)))\n x()\n}"); + string source = R"({ + function y() + { + } + function f(a) -> b + { + } + function g(a, b, c) + { + } + g(1, mul(2, address), f(mul(2, caller))) + y() +})"; + boost::replace_all(source, "\t", " "); + parsePrintCompare(source); } BOOST_AUTO_TEST_SUITE_END() @@ -291,27 +333,32 @@ BOOST_AUTO_TEST_CASE(oversize_string_literals) BOOST_AUTO_TEST_CASE(assignment_after_tag) { - BOOST_CHECK(successParse("{ let x := 1 { tag: =: x } }")); + BOOST_CHECK(successParse("{ let x := 1 { 7 tag: =: x } }")); } BOOST_AUTO_TEST_CASE(magic_variables) { - CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique"); - CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique"); - BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }")); + CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found"); + CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found"); + BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover pop }")); +} + +BOOST_AUTO_TEST_CASE(stack_variables) +{ + BOOST_CHECK(successAssemble("{ let y := 3 { 2 { let x := y } pop} }")); } BOOST_AUTO_TEST_CASE(imbalanced_stack) { BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false)); - CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves"); - CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes"); + CHECK_ASSEMBLE_ERROR("{ 1 }", DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s)."); + CHECK_ASSEMBLE_ERROR("{ pop }", DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s)."); BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false)); } BOOST_AUTO_TEST_CASE(error_tag) { - BOOST_CHECK(successAssemble("{ invalidJumpLabel }")); + BOOST_CHECK(successAssemble("{ jump(invalidJumpLabel) }")); } BOOST_AUTO_TEST_CASE(designated_invalid_instruction) diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 043d74ed..bdcc5b10 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -42,7 +42,7 @@ public: void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString) { - ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing contract failed"); + ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parseAndAnalyze("pragma solidity >=0.0;\n" + _code), "Parsing contract failed"); Json::Value generatedInterface = m_compilerStack.metadata("", DocumentationType::ABIInterface); Json::Value expectedInterface; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7ef34383..f2f4b8b0 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7426,18 +7426,52 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access) uint16 x; uint16 public y; uint public z; - function f() { - // we know that z is aligned because it is too large, so we just discard its - // intra-slot offset value - assembly { 7 z pop sstore } + function f() returns (bool) { + uint off1; + uint off2; + assembly { + sstore(z_slot, 7) + off1 := z_offset + off2 := y_offset + } + assert(off1 == 0); + assert(off2 == 2); + return true; } } )"; compileAndRun(sourceCode, 0, "C"); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer) +{ + char const* sourceCode = R"( + contract C { + struct Data { uint contents; } + uint public separator; + Data public a; + uint public separator2; + function f() returns (bool) { + Data x = a; + uint off; + assembly { + sstore(x_slot, 7) + off := x_offset + } + assert(off == 0); + return true; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("separator()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("separator2()") == encodeArgs(u256(0))); +} + BOOST_AUTO_TEST_CASE(inline_assembly_jumps) { char const* sourceCode = R"( @@ -7474,6 +7508,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access) assembly { _x jump(g) + pop } } } @@ -9266,6 +9301,41 @@ BOOST_AUTO_TEST_CASE(scientific_notation) BOOST_CHECK(callContractFunction("k()") == encodeArgs(u256(-25))); } +BOOST_AUTO_TEST_CASE(interface) +{ + char const* sourceCode = R"( + interface I { + event A(); + function f() returns (bool); + function() payable; + } + + contract A is I { + function f() returns (bool) { + return g(); + } + + function g() returns (bool) { + return true; + } + + function() payable { + } + } + + contract C { + function f(address _interfaceAddress) returns (bool) { + I i = I(_interfaceAddress); + return i.f(); + } + } + )"; + compileAndRun(sourceCode, 0, "A"); + u160 const recipient = m_contractAddress; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(address)", recipient) == encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index fa310434..3a9f7295 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -216,7 +216,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) char const* text = R"( contract test { uint256 stateVariable1; - function fun(uint256 arg1) { uint256 y; } + function fun(uint256 arg1) { uint256 y; y = arg1; } } )"; CHECK_SUCCESS(text); @@ -237,8 +237,8 @@ BOOST_AUTO_TEST_CASE(double_function_declaration) { char const* text = R"( contract test { - function fun() { uint x; } - function fun() { uint x; } + function fun() { } + function fun() { } } )"; CHECK_ERROR(text, DeclarationError, ""); @@ -262,7 +262,7 @@ BOOST_AUTO_TEST_CASE(name_shadowing) char const* text = R"( contract test { uint256 variable; - function f() { uint32 variable; } + function f() { uint32 variable; variable = 2; } } )"; CHECK_SUCCESS(text); @@ -273,7 +273,7 @@ BOOST_AUTO_TEST_CASE(name_references) char const* text = R"( contract test { uint256 variable; - function f(uint256 arg) returns (uint out) { f(variable); test; out; } + function f(uint256) returns (uint out) { f(variable); test; out; } } )"; CHECK_SUCCESS(text); @@ -404,8 +404,8 @@ BOOST_AUTO_TEST_CASE(type_checking_function_call) { char const* text = R"( contract test { - function f() returns (bool r) { return g(12, true) == 3; } - function g(uint256 a, bool b) returns (uint256 r) { } + function f() returns (bool) { return g(12, true) == 3; } + function g(uint256, bool) returns (uint256) { } } )"; CHECK_SUCCESS(text); @@ -523,12 +523,12 @@ BOOST_AUTO_TEST_CASE(forward_function_reference) { char const* text = R"( contract First { - function fun() returns (bool ret) { + function fun() returns (bool) { return Second(1).fun(1, true, 3) > 0; } } contract Second { - function fun(uint a, bool b, uint c) returns (uint ret) { + function fun(uint, bool, uint) returns (uint) { if (First(2).fun() == true) return 1; } } @@ -624,17 +624,22 @@ BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_optional) function foo() {} } )"; - ETH_TEST_REQUIRE_NO_THROW(parseAndAnalyse(text), "Parsing and name resolving failed"); + ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); + std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); + BOOST_CHECK_EQUAL(nodes.size(), 4); + ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[3].get()); + BOOST_REQUIRE(derived); + BOOST_CHECK(!derived->annotation().isFullyImplemented); } BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_not_provided) { ASTPointer<SourceUnit> sourceUnit; char const* text = R"( - contract BaseBase { function BaseBase(uint j); } + contract BaseBase { function BaseBase(uint); } contract base is BaseBase { function foo(); } contract derived is base { - function derived(uint i) {} + function derived(uint) {} function foo() {} } )"; @@ -696,7 +701,7 @@ BOOST_AUTO_TEST_CASE(function_canonical_signature_type_aliases) ASTPointer<SourceUnit> sourceUnit; char const* text = R"( contract Test { - function boo(uint arg1, bytes32 arg2, address arg3) returns (uint ret) { + function boo(uint, bytes32, address) returns (uint ret) { ret = 5; } } @@ -720,7 +725,7 @@ BOOST_AUTO_TEST_CASE(function_external_types) uint a; } contract Test { - function boo(uint arg2, bool arg3, bytes8 arg4, bool[2] pairs, uint[] dynamic, C carg, address[] addresses) external returns (uint ret) { + function boo(uint, bool, bytes8, bool[2], uint[], C, address[]) external returns (uint ret) { ret = 5; } } @@ -909,7 +914,7 @@ BOOST_AUTO_TEST_CASE(complex_inheritance) { char const* text = R"( contract A { function f() { uint8 x = C(0).g(); } } - contract B { function f() {} function g() returns (uint8 r) {} } + contract B { function f() {} function g() returns (uint8) {} } contract C is A, B { } )"; CHECK_SUCCESS(text); @@ -1667,7 +1672,7 @@ BOOST_AUTO_TEST_CASE(exp_warn_literal_base) { char const* sourceCode = R"( contract test { - function f() returns(uint d) { + function f() returns(uint) { uint8 x = 100; return 10**x; } @@ -1676,7 +1681,7 @@ BOOST_AUTO_TEST_CASE(exp_warn_literal_base) CHECK_WARNING(sourceCode, "might overflow"); sourceCode = R"( contract test { - function f() returns(uint d) { + function f() returns(uint) { uint8 x = 100; return uint8(10)**x; } @@ -1685,7 +1690,7 @@ BOOST_AUTO_TEST_CASE(exp_warn_literal_base) CHECK_SUCCESS(sourceCode); sourceCode = R"( contract test { - function f() returns(uint d) { + function f() returns(uint) { return 2**80; } } @@ -1936,10 +1941,10 @@ BOOST_AUTO_TEST_CASE(test_for_bug_override_function_with_bytearray_type) { char const* sourceCode = R"( contract Vehicle { - function f(bytes _a) external returns (uint256 r) {r = 1;} + function f(bytes) external returns (uint256 r) {r = 1;} } contract Bike is Vehicle { - function f(bytes _a) external returns (uint256 r) {r = 42;} + function f(bytes) external returns (uint256 r) {r = 42;} } )"; ETH_TEST_CHECK_NO_THROW(parseAndAnalyse(sourceCode), "Parsing and Name Resolving failed"); @@ -2565,6 +2570,7 @@ BOOST_AUTO_TEST_CASE(storage_location_local_variables) uint[] storage x; uint[] memory y; uint[] memory z; + x;y;z; } } )"; @@ -2620,6 +2626,7 @@ BOOST_AUTO_TEST_CASE(uninitialized_mapping_variable) contract C { function f() { mapping(uint => uint) x; + x; } } )"; @@ -2632,6 +2639,7 @@ BOOST_AUTO_TEST_CASE(uninitialized_mapping_array_variable) contract C { function f() { mapping(uint => uint)[] x; + x; } } )"; @@ -2784,6 +2792,7 @@ BOOST_AUTO_TEST_CASE(literal_strings) function f() { string memory long = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; string memory short = "123"; + long; short; } } )"; @@ -2860,7 +2869,7 @@ BOOST_AUTO_TEST_CASE(call_to_library_function) { char const* text = R"( library Lib { - function min(uint x, uint y) returns (uint); + function min(uint, uint) returns (uint); } contract Test { function f() { @@ -2958,9 +2967,9 @@ BOOST_AUTO_TEST_CASE(cyclic_binary_dependency_via_inheritance) BOOST_AUTO_TEST_CASE(multi_variable_declaration_fail) { char const* text = R"( - contract C { function f() { var (x,y); } } + contract C { function f() { var (x,y); x = 1; y = 1;} } )"; - CHECK_ERROR(text, TypeError, ""); + CHECK_ERROR(text, TypeError, "Assignment necessary for type detection."); } BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fine) @@ -2977,6 +2986,7 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fine) var (,e,g) = two(); var (,,) = three(); var () = none(); + a;b;c;d;e;g; } } )"; @@ -3035,6 +3045,7 @@ BOOST_AUTO_TEST_CASE(tuples) var (b,) = (1,); var (c,d) = (1, 2 + a); var (e,) = (1, 2, b); + a;b;c;d;e; } } )"; @@ -3192,7 +3203,7 @@ BOOST_AUTO_TEST_CASE(using_for_overload) library D { struct s { uint a; } function mul(s storage self, uint x) returns (uint) { return self.a *= x; } - function mul(s storage self, bytes32 x) returns (bytes32) { } + function mul(s storage, bytes32) returns (bytes32) { } } contract C { using D for D.s; @@ -3304,6 +3315,7 @@ BOOST_AUTO_TEST_CASE(create_memory_arrays) L.S[][] memory x = new L.S[][](10); var y = new uint[](20); var z = new bytes(size); + x;y;z; } } )"; @@ -3350,8 +3362,8 @@ BOOST_AUTO_TEST_CASE(function_overload_array_type) { char const* text = R"( contract M { - function f(uint[] values); - function f(int[] values); + function f(uint[]); + function f(int[]); } )"; CHECK_SUCCESS(text); @@ -3648,55 +3660,65 @@ BOOST_AUTO_TEST_CASE(conditional_with_all_types) // integers uint x; uint y; - true ? x : y; + uint g = true ? x : y; + g += 1; // Avoid unused var warning // integer constants - true ? 1 : 3; + uint h = true ? 1 : 3; + h += 1; // Avoid unused var warning // string literal - true ? "hello" : "world"; - + var i = true ? "hello" : "world"; + i = "used"; //Avoid unused var warning + } + function f2() { // bool - true ? true : false; + bool j = true ? true : false; + j = j && true; // Avoid unused var warning // real is not there yet. // array byte[2] memory a; byte[2] memory b; - true ? a : b; + var k = true ? a : b; + k[0] = 0; //Avoid unused var warning bytes memory e; bytes memory f; - true ? e : f; + var l = true ? e : f; + l[0] = 0; // Avoid unused var warning // fixed bytes bytes2 c; bytes2 d; - true ? c : d; + var m = true ? c : d; + m &= m; + } + function f3() { // contract doesn't fit in here // struct - true ? struct_x : struct_y; + struct_x = true ? struct_x : struct_y; // function - true ? fun_x : fun_y; - + var r = true ? fun_x : fun_y; + r(); // Avoid unused var warning // enum small enum_x; small enum_y; - true ? enum_x : enum_y; + enum_x = true ? enum_x : enum_y; // tuple - true ? (1, 2) : (3, 4); - + var (n, o) = true ? (1, 2) : (3, 4); + (n, o) = (o, n); // Avoid unused var warning // mapping - true ? table1 : table2; - + var p = true ? table1 : table2; + p[0] = 0; // Avoid unused var warning // typetype - true ? uint32(1) : uint32(2); - + var q = true ? uint32(1) : uint32(2); + q += 1; // Avoid unused var warning // modifier doesn't fit in here // magic doesn't fit in here @@ -3746,6 +3768,7 @@ BOOST_AUTO_TEST_CASE(uint7_and_uintM_as_identifier) uint7 = 5; string memory intM; uint bytesM = 21; + intM; bytesM; } } )"; @@ -3797,6 +3820,7 @@ BOOST_AUTO_TEST_CASE(int10abc_is_identifier) function f() { uint uint10abc = 3; int int10abc = 4; + uint10abc; int10abc; } } )"; @@ -3881,6 +3905,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_int_conversion) int128 b = 4; fixed c = b; ufixed d = a; + c; d; } } )"; @@ -3894,6 +3919,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_rational_int_conversion) function f() { fixed c = 3; ufixed d = 4; + c; d; } } )"; @@ -3907,6 +3933,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_rational_fraction_conversion) function f() { fixed a = 4.5; ufixed d = 2.5; + a; d; } } )"; @@ -3920,6 +3947,7 @@ BOOST_AUTO_TEST_CASE(invalid_int_implicit_conversion_from_fixed) function f() { fixed a = 4.5; int b = a; + a; b; } } )"; @@ -3931,12 +3959,33 @@ BOOST_AUTO_TEST_CASE(rational_unary_operation) char const* text = R"( contract test { function f() { + ufixed8x16 a = 3.25; + fixed8x16 b = -3.25; + a; + b; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract test { + function f() { ufixed8x16 a = +3.25; fixed8x16 b = -3.25; + a; b; } } )"; - CHECK_SUCCESS(text); + CHECK_WARNING(text,"Use of unary + is deprecated"); + text = R"( + contract test { + function f(uint x) { + uint y = +x; + y; + } + } + )"; + CHECK_WARNING(text,"Use of unary + is deprecated"); } BOOST_AUTO_TEST_CASE(leading_zero_rationals_convert) @@ -3948,6 +3997,7 @@ BOOST_AUTO_TEST_CASE(leading_zero_rationals_convert) ufixed0x56 b = 0.0000000000000006661338147750939242541790008544921875; fixed0x8 c = -0.5; fixed0x56 d = -0.0000000000000006661338147750939242541790008544921875; + a; b; c; d; } } )"; @@ -3965,6 +4015,7 @@ BOOST_AUTO_TEST_CASE(size_capabilities_of_fixed_point_types) fixed248x8 d = -123456781234567979695948382928485849359686494864095409282048094275023098123.5; fixed0x256 e = -0.93322335481643744342575580035176794825198893968114429702091846411734101080123092162893656820177312738451291806995868682861328125; fixed0x256 g = -0.00011788606643744342575580035176794825198893968114429702091846411734101080123092162893656820177312738451291806995868682861328125; + a; b; c; d; e; g; } } )"; @@ -4004,6 +4055,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_valid_explicit_conversions) ufixed0x256 a = ufixed0x256(1/3); ufixed0x248 b = ufixed0x248(1/3); ufixed0x8 c = ufixed0x8(1/3); + a; b; c; } } )"; @@ -4135,6 +4187,7 @@ BOOST_AUTO_TEST_CASE(rational_to_fixed_literal_expression) ufixed8x248 e = ufixed8x248(35.245 % 12.9); ufixed8x248 f = ufixed8x248(1.2 % 2); fixed g = 2 ** -2; + a; b; c; d; e; f; g; } } )"; @@ -4245,6 +4298,7 @@ BOOST_AUTO_TEST_CASE(var_capable_of_holding_constant_rationals) var a = 0.12345678; var b = 12345678.352; var c = 0.00000009; + a; b; c; } } )"; @@ -4257,6 +4311,7 @@ BOOST_AUTO_TEST_CASE(var_and_rational_with_tuple) contract test { function f() { var (a, b) = (.5, 1/3); + a; b; } } )"; @@ -4330,6 +4385,7 @@ BOOST_AUTO_TEST_CASE(zero_handling) function f() { fixed8x8 a = 0; ufixed8x8 b = 0; + a; b; } } )"; @@ -4835,6 +4891,7 @@ BOOST_AUTO_TEST_CASE(function_type_arrays) function(uint) returns (uint)[10] storage b = y; function(uint) external returns (uint)[] memory c; c = new function(uint) external returns (uint)[](200); + a; b; } } )"; @@ -4893,8 +4950,8 @@ BOOST_AUTO_TEST_CASE(external_function_to_function_type_calldata_parameter) // when converting to a function type. char const* text = R"( contract C { - function f(function(bytes memory x) external g) { } - function callback(bytes x) external {} + function f(function(bytes memory) external g) { } + function callback(bytes) external {} function g() { f(this.callback); } @@ -4990,7 +5047,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_positive_stack) } } )"; - CHECK_WARNING(text, "Inline assembly block is not balanced"); + CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s)."); } BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) @@ -5004,7 +5061,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) } } )"; - CHECK_WARNING(text, "Inline assembly block is not balanced"); + CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s)."); } BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) @@ -5017,7 +5074,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) } } )"; - CHECK_WARNING(text, "Inline assembly block is not balanced"); + CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); } BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) @@ -5046,12 +5103,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage) function f() { assembly { x := 2 - pop } } } )"; - CHECK_ERROR(text, DeclarationError, "not found, not unique or not lvalue."); + CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); } BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) @@ -5062,7 +5118,6 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) modifier m { assembly { x := 2 - pop } _; } @@ -5070,7 +5125,37 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) } } )"; - CHECK_ERROR(text, DeclarationError, ""); + CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign) +{ + char const* text = R"( + contract test { + uint constant x = 1; + function f() { + assembly { + x := 2 + } + } + } + )"; + CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly"); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_constant_access) +{ + char const* text = R"( + contract test { + uint constant x = 1; + function f() { + assembly { + let y := x + } + } + } + )"; + CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly"); } BOOST_AUTO_TEST_CASE(invalid_mobile_type) @@ -5235,6 +5320,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_checksum) contract C { function f() { var x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + x; } } )"; @@ -5247,6 +5333,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_no_checksum) contract C { function f() { var x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e; + x; } } )"; @@ -5259,6 +5346,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_length) contract C { function f() { var x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E; + x; } } )"; @@ -5293,6 +5381,7 @@ BOOST_AUTO_TEST_CASE(address_methods) bool delegatecallRet = addr.delegatecall(); bool sendRet = addr.send(1); addr.transfer(1); + callRet; callcodeRet; delegatecallRet; sendRet; } } )"; @@ -5327,6 +5416,311 @@ BOOST_AUTO_TEST_CASE(cyclic_dependency_for_constants) CHECK_SUCCESS(text); } +BOOST_AUTO_TEST_CASE(interface) +{ + char const* text = R"( + interface I { + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(interface_constructor) +{ + char const* text = R"( + interface I { + function I(); + } + )"; + CHECK_ERROR(text, TypeError, "Constructor cannot be defined in interfaces"); +} + +BOOST_AUTO_TEST_CASE(interface_functions) +{ + char const* text = R"( + interface I { + function(); + function f(); + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(interface_function_bodies) +{ + char const* text = R"( + interface I { + function f() { + } + } + )"; + CHECK_ERROR(text, TypeError, "Functions in interfaces cannot have an implementation"); +} + +BOOST_AUTO_TEST_CASE(interface_function_internal) +{ + char const* text = R"( + interface I { + function f() internal; + } + )"; + CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private."); +} + +BOOST_AUTO_TEST_CASE(interface_function_private) +{ + char const* text = R"( + interface I { + function f() private; + } + )"; + CHECK_ERROR(text, TypeError, "Functions in interfaces cannot be internal or private."); +} + +BOOST_AUTO_TEST_CASE(interface_events) +{ + char const* text = R"( + interface I { + event E(); + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(interface_inheritance) +{ + char const* text = R"( + interface A { + } + interface I is A { + } + )"; + CHECK_ERROR(text, TypeError, "Interfaces cannot inherit"); +} + + +BOOST_AUTO_TEST_CASE(interface_structs) +{ + char const* text = R"( + interface I { + struct A { + } + } + )"; + CHECK_ERROR(text, TypeError, "Structs cannot be defined in interfaces"); +} + +BOOST_AUTO_TEST_CASE(interface_variables) +{ + char const* text = R"( + interface I { + uint a; + } + )"; + CHECK_ERROR(text, TypeError, "Variables cannot be declared in interfaces"); +} + +BOOST_AUTO_TEST_CASE(interface_enums) +{ + char const* text = R"( + interface I { + enum A { B, C } + } + )"; + CHECK_ERROR(text, TypeError, "Enumerable cannot be declared in interfaces"); +} + +BOOST_AUTO_TEST_CASE(using_interface) +{ + char const* text = R"( + interface I { + function f(); + } + contract C is I { + function f() { + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(using_interface_complex) +{ + char const* text = R"( + interface I { + event A(); + function f(); + function g(); + function(); + } + contract C is I { + function f() { + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(bare_revert) +{ + char const* text = R"( + contract C { + function f(uint x) { + if (x > 7) + revert; + } + } + )"; + CHECK_WARNING(text, "Statement has no effect."); +} + +BOOST_AUTO_TEST_CASE(bare_others) +{ + CHECK_WARNING("contract C { function f() { selfdestruct; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() { assert; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() { require; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() { suicide; } }", "Statement has no effect."); +} + +BOOST_AUTO_TEST_CASE(pure_statement_in_for_loop) +{ + char const* text = R"( + contract C { + function f() { + for (uint x = 0; x < 10; true) + x++; + } + } + )"; + CHECK_WARNING(text, "Statement has no effect."); +} + +BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop) +{ + char const* text = R"( + contract C { + function f() { + for (uint x = 0; true; x++) + {} + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(warn_unused_local) +{ + char const* text = R"( + contract C { + function f() { + uint a; + } + } + )"; + CHECK_WARNING(text, "Unused"); +} + +BOOST_AUTO_TEST_CASE(warn_unused_local_assigned) +{ + char const* text = R"( + contract C { + function f() { + var a = 1; + } + } + )"; + CHECK_WARNING(text, "Unused"); +} + +BOOST_AUTO_TEST_CASE(warn_unused_param) +{ + char const* text = R"( + contract C { + function f(uint a) { + } + } + )"; + CHECK_WARNING(text, "Unused"); + text = R"( + contract C { + function f(uint a) { + } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(warn_unused_return_param) +{ + char const* text = R"( + contract C { + function f() returns (uint a) { + } + } + )"; + CHECK_WARNING(text, "Unused"); + text = R"( + contract C { + function f() returns (uint a) { + return; + } + } + )"; + CHECK_WARNING(text, "Unused"); + text = R"( + contract C { + function f() returns (uint) { + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f() returns (uint a) { + a = 1; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f() returns (uint a) { + return 1; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(no_unused_warnings) +{ + char const* text = R"( + contract C { + function f(uint a) returns (uint b) { + uint c = 1; + b = a + c; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(no_unused_dec_after_use) +{ + char const* text = R"( + contract C { + function f() { + a = 7; + uint a; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + + + + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index ac55382b..78c1a0ee 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -45,7 +45,7 @@ public: bool _userDocumentation ) { - ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing failed"); + ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parseAndAnalyze("pragma solidity >=0.0;\n" + _code), "Parsing failed"); Json::Value generatedDocumentation; if (_userDocumentation) @@ -63,7 +63,7 @@ public: void expectNatspecError(std::string const& _code) { - BOOST_CHECK(!m_compilerStack.parse(_code)); + BOOST_CHECK(!m_compilerStack.parseAndAnalyze(_code)); BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError)); } diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index bcf6cd49..d705e3c8 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -74,16 +74,17 @@ public: void compileBothVersions( std::string const& _sourceCode, u256 const& _value = 0, - std::string const& _contractName = "" + std::string const& _contractName = "", + unsigned const _optimizeRuns = 200 ) { - bytes nonOptimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, false); + bytes nonOptimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, false, _optimizeRuns); m_nonOptimizedContract = m_contractAddress; - bytes optimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, true); + bytes optimizedBytecode = compileAndRunWithOptimizer(_sourceCode, _value, _contractName, true, _optimizeRuns); size_t nonOptimizedSize = numInstructions(nonOptimizedBytecode); size_t optimizedSize = numInstructions(optimizedBytecode); BOOST_CHECK_MESSAGE( - optimizedSize < nonOptimizedSize, + _optimizeRuns < 50 || optimizedSize < nonOptimizedSize, string("Optimizer did not reduce bytecode size. Non-optimized size: ") + std::to_string(nonOptimizedSize) + " - optimized size: " + std::to_string(optimizedSize) @@ -1191,31 +1192,42 @@ BOOST_AUTO_TEST_CASE(clear_unreachable_code) BOOST_AUTO_TEST_CASE(computing_constants) { char const* sourceCode = R"( - contract c { - uint a; - uint b; - uint c; - function set() returns (uint a, uint b, uint c) { - a = 0x77abc0000000000000000000000000000000000000000000000000000000001; - b = 0x817416927846239487123469187231298734162934871263941234127518276; + contract C { + uint m_a; + uint m_b; + uint m_c; + uint m_d; + function C() { + set(); + } + function set() returns (uint) { + m_a = 0x77abc0000000000000000000000000000000000000000000000000000000001; + m_b = 0x817416927846239487123469187231298734162934871263941234127518276; g(); + return 1; } function g() { - b = 0x817416927846239487123469187231298734162934871263941234127518276; - c = 0x817416927846239487123469187231298734162934871263941234127518276; + m_b = 0x817416927846239487123469187231298734162934871263941234127518276; + m_c = 0x817416927846239487123469187231298734162934871263941234127518276; + h(); + } + function h() { + m_d = 0xff05694900000000000000000000000000000000000000000000000000000000; } - function get() returns (uint ra, uint rb, uint rc) { - ra = a; - rb = b; - rc = c ; + function get() returns (uint ra, uint rb, uint rc, uint rd) { + ra = m_a; + rb = m_b; + rc = m_c; + rd = m_d; } } )"; - compileBothVersions(sourceCode); + compileBothVersions(sourceCode, 0, "C", 1); + compareVersions("get()"); compareVersions("set()"); compareVersions("get()"); - bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "c", true, 1); + bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "C", true, 1); bytes complicatedConstant = toBigEndian(u256("0x817416927846239487123469187231298734162934871263941234127518276")); unsigned occurrences = 0; for (auto iter = optimizedBytecode.cbegin(); iter < optimizedBytecode.cend(); ++occurrences) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index ffb4b6f2..6e33aba5 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1493,6 +1493,15 @@ BOOST_AUTO_TEST_CASE(scientific_notation) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(interface) +{ + char const* text = R"( + interface Interface { + function f(); + } + )"; + BOOST_CHECK(successParse(text)); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index 2dcb9226..0b5ab516 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -115,7 +115,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers) TypePointer multiArray = make_shared<ArrayType>(DataLocation::Storage, stringArray); BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr"); - ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, false); + ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, ContractDefinition::ContractKind::Contract); BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$"); BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2"); @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers) TupleType t({e.type(), s.type(), stringArray, nullptr}); BOOST_CHECK_EQUAL(t.identifier(), "t_tuple$_t_type$_t_enum$_Enum_$4_$_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_t_array$_t_string_storage_$20_storage_ptr_$__$"); - TypePointer sha3fun = make_shared<FunctionType>(strings{}, strings{}, FunctionType::Location::SHA3); + TypePointer sha3fun = make_shared<FunctionType>(strings{}, strings{}, FunctionType::Kind::SHA3); BOOST_CHECK_EQUAL(sha3fun->identifier(), "t_function_sha3$__$returns$__$"); FunctionType metaFun(TypePointers{sha3fun}, TypePointers{s.type()}); diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp new file mode 100644 index 00000000..ffb0e2c6 --- /dev/null +++ b/test/libsolidity/StandardCompiler.cpp @@ -0,0 +1,273 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @date 2017 + * Unit tests for interface/StandardCompiler.h. + */ + +#include <string> +#include <iostream> +#include <regex> +#include <boost/test/unit_test.hpp> +#include <libsolidity/interface/StandardCompiler.h> +#include <libdevcore/JSON.h> + + +using namespace std; +using namespace dev::eth; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ + +/// Helper to match a specific error type and message +bool containsError(Json::Value const& _compilerResult, string const& _type, string const& _message) +{ + if (!_compilerResult.isMember("errors")) + return false; + + for (auto const& error: _compilerResult["errors"]) + { + BOOST_REQUIRE(error.isObject()); + BOOST_REQUIRE(error["type"].isString()); + BOOST_REQUIRE(error["message"].isString()); + if ((error["type"].asString() == _type) && (error["message"].asString() == _message)) + return true; + } + + return false; +} + +bool containsAtMostWarnings(Json::Value const& _compilerResult) +{ + if (!_compilerResult.isMember("errors")) + return true; + + for (auto const& error: _compilerResult["errors"]) + { + BOOST_REQUIRE(error.isObject()); + BOOST_REQUIRE(error["severity"].isString()); + if (error["severity"].asString() != "warning") + { + cout << error << std::endl; + return false; + } + } + + return true; +} + +string bytecodeSansMetadata(string const& _bytecode) +{ + /// The metadata hash takes up 43 bytes (or 86 characters in hex) + /// /a165627a7a72305820([0-9a-f]{64})0029$/ + + if (_bytecode.size() < 88) + return _bytecode; + + if (_bytecode.substr(_bytecode.size() - 4, 4) != "0029") + return _bytecode; + + if (_bytecode.substr(_bytecode.size() - 86, 18) != "a165627a7a72305820") + return _bytecode; + + return _bytecode.substr(0, _bytecode.size() - 86); +} + +bool isValidMetadata(string const& _metadata) +{ + Json::Value metadata; + if (!Json::Reader().parse(_metadata, metadata, false)) + return false; + + if ( + !metadata.isObject() || + !metadata.isMember("version") || + !metadata.isMember("language") || + !metadata.isMember("compiler") || + !metadata.isMember("settings") || + !metadata.isMember("sources") || + !metadata.isMember("output") + ) + return false; + + if (!metadata["version"].isNumeric() || metadata["version"] != 1) + return false; + + if (!metadata["language"].isString() || metadata["language"].asString() != "Solidity") + return false; + + /// @TODO add more strict checks + + return true; +} + +Json::Value getContractResult(Json::Value const& _compilerResult, string const& _file, string const& _name) +{ + if ( + !_compilerResult["contracts"].isObject() || + !_compilerResult["contracts"][_file].isObject() || + !_compilerResult["contracts"][_file][_name].isObject() + ) + return Json::Value(); + return _compilerResult["contracts"][_file][_name]; +} + +Json::Value compile(string const& _input) +{ + StandardCompiler compiler; + string output = compiler.compile(_input); + Json::Value ret; + BOOST_REQUIRE(Json::Reader().parse(output, ret, false)); + return ret; +} + +} // end anonymous namespace + +BOOST_AUTO_TEST_SUITE(StandardCompiler) + +BOOST_AUTO_TEST_CASE(assume_object_input) +{ + Json::Value result; + + /// Use the native JSON interface of StandardCompiler to trigger these + solidity::StandardCompiler compiler; + result = compiler.compile(Json::Value()); + BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object.")); + result = compiler.compile(Json::Value("INVALID")); + BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object.")); + + /// Use the string interface of StandardCompiler to trigger these + result = compile(""); + BOOST_CHECK(containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n")); + result = compile("invalid"); + BOOST_CHECK(containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n")); + result = compile("\"invalid\""); + BOOST_CHECK(containsError(result, "JSONError", "Input is not a JSON object.")); + BOOST_CHECK(!containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n")); + result = compile("{}"); + BOOST_CHECK(!containsError(result, "JSONError", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n")); + BOOST_CHECK(!containsAtMostWarnings(result)); +} + +BOOST_AUTO_TEST_CASE(invalid_language) +{ + char const* input = R"( + { + "language": "INVALID" + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "Only \"Solidity\" is supported as a language.")); +} + +BOOST_AUTO_TEST_CASE(valid_language) +{ + char const* input = R"( + { + "language": "Solidity" + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(!containsError(result, "JSONError", "Only \"Solidity\" is supported as a language.")); +} + +BOOST_AUTO_TEST_CASE(no_sources) +{ + char const* input = R"( + { + "language": "Solidity" + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "No input sources specified.")); +} + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + char const* input = R"( + { + "language": "Solidity", + "sources": { + "empty": { + "content": "" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); +} + +BOOST_AUTO_TEST_CASE(basic_compilation) +{ + char const* input = R"( + { + "language": "Solidity", + "sources": { + "fileA": { + "content": "contract A { }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["abi"].isArray()); + BOOST_CHECK(dev::jsonCompactPrint(contract["abi"]) == "[]"); + BOOST_CHECK(contract["devdoc"].isObject()); + BOOST_CHECK(dev::jsonCompactPrint(contract["devdoc"]) == "{\"methods\":{}}"); + BOOST_CHECK(contract["userdoc"].isObject()); + BOOST_CHECK(dev::jsonCompactPrint(contract["userdoc"]) == "{\"methods\":{}}"); + BOOST_CHECK(contract["evm"].isObject()); + /// @TODO check evm.methodIdentifiers, legacyAssembly, bytecode, deployedBytecode + BOOST_CHECK(contract["evm"]["bytecode"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString()); + BOOST_CHECK(bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()) == + "60606040523415600b57fe5b5b60338060196000396000f30060606040525bfe00"); + BOOST_CHECK(contract["evm"]["assembly"].isString()); + BOOST_CHECK(contract["evm"]["assembly"].asString() == + " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x60)\n jumpi(tag_1, iszero(callvalue))\n" + " invalid\ntag_1:\ntag_2:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n" + " return\nstop\n\nsub_0: assembly {\n /* \"fileA\":0:14 contract A { } */\n" + " mstore(0x40, 0x60)\n tag_1:\n invalid\n}\n"); + BOOST_CHECK(contract["evm"]["gasEstimates"].isObject()); + BOOST_CHECK(dev::jsonCompactPrint(contract["evm"]["gasEstimates"]) == + "{\"creation\":{\"codeDepositCost\":\"10200\",\"executionCost\":\"62\",\"totalCost\":\"10262\"}}"); + BOOST_CHECK(contract["metadata"].isString()); + BOOST_CHECK(isValidMetadata(contract["metadata"].asString())); + BOOST_CHECK(result["sources"].isObject()); + BOOST_CHECK(result["sources"]["fileA"].isObject()); + BOOST_CHECK(result["sources"]["fileA"]["legacyAST"].isObject()); + BOOST_CHECK(dev::jsonCompactPrint(result["sources"]["fileA"]["legacyAST"]) == + "{\"children\":[{\"attributes\":{\"fullyImplemented\":true,\"isLibrary\":false,\"linearizedBaseContracts\":[1]," + "\"name\":\"A\"},\"children\":[],\"id\":1,\"name\":\"ContractDefinition\",\"src\":\"0:14:0\"}],\"name\":\"SourceUnit\"}"); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces |