Merge pull request #8341 from ethereum/develop

Merge develop into release for 0.6.3
This commit is contained in:
chriseth 2020-02-18 15:50:19 +01:00 committed by GitHub
commit 8dda952108
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
435 changed files with 12432 additions and 3255 deletions

View File

@ -62,6 +62,11 @@ defaults:
path: build/solc/solc path: build/solc/solc
destination: solc destination: solc
# compiled tool executable target
- artifacts_tools: &artifacts_tools
path: build/tools/solidity-upgrade
destination: solidity-upgrade
# compiled executable targets # compiled executable targets
- artifacts_executables: &artifacts_executables - artifacts_executables: &artifacts_executables
root: build root: build
@ -257,6 +262,22 @@ jobs:
name: Check for C++ coding style name: Check for C++ coding style
command: ./scripts/check_style.sh command: ./scripts/check_style.sh
chk_pylint:
docker:
- image: buildpack-deps:eoan
steps:
- checkout
- run:
name: Install pip
command: apt -q update && apt install -y python3-pip
- run:
name: Install pylint
command: python3 -m pip install pylint z3-solver pygments-lexer-solidity
# also z3-solver to make sure pylint knows about this module, pygments-lexer-solidity for docs
- run:
name: Linting Python Scripts
command: ./scripts/pylint_all.py
chk_buglist: chk_buglist:
docker: docker:
- image: circleci/node - image: circleci/node
@ -308,6 +329,7 @@ jobs:
- checkout - checkout
- run: *run_build - run: *run_build
- store_artifacts: *artifacts_solc - store_artifacts: *artifacts_solc
- store_artifacts: *artifacts_tools
- persist_to_workspace: *artifacts_executables - persist_to_workspace: *artifacts_executables
b_ubu_release: &build_ubuntu1904_release b_ubu_release: &build_ubuntu1904_release
@ -391,8 +413,8 @@ jobs:
- run: - run:
name: Regression tests name: Regression tests
command: | command: |
git clone https://github.com/ethereum/solidity-fuzzing-corpus /tmp/solidity-fuzzing-corpus
mkdir -p test_results mkdir -p test_results
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
scripts/regressions.py -o test_results scripts/regressions.py -o test_results
- run: *gitter_notify_failure - run: *gitter_notify_failure
- run: *gitter_notify_success - run: *gitter_notify_success
@ -439,6 +461,7 @@ jobs:
- /usr/local/Homebrew - /usr/local/Homebrew
- run: *run_build - run: *run_build
- store_artifacts: *artifacts_solc - store_artifacts: *artifacts_solc
- store_artifacts: *artifacts_tools
- persist_to_workspace: *artifacts_build_dir - persist_to_workspace: *artifacts_build_dir
t_osx_soltest: t_osx_soltest:
@ -718,6 +741,7 @@ workflows:
# DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags # DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags
- chk_buglist: *workflow_trigger_on_tags - chk_buglist: *workflow_trigger_on_tags
- chk_proofs: *workflow_trigger_on_tags - chk_proofs: *workflow_trigger_on_tags
- chk_pylint: *workflow_trigger_on_tags
# build-only # build-only
- b_docs: *workflow_trigger_on_tags - b_docs: *workflow_trigger_on_tags

1
.gitignore vendored
View File

@ -40,6 +40,7 @@ docs/utils/*.pyc
/deps/downloads/ /deps/downloads/
deps/install deps/install
deps/cache deps/cache
cmake-build-debug/
# vim stuff # vim stuff
[._]*.sw[a-p] [._]*.sw[a-p]

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.6.2") set(PROJECT_VERSION "0.6.3")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
include(TestBigEndian) include(TestBigEndian)
@ -55,6 +55,7 @@ add_subdirectory(libevmasm)
add_subdirectory(libyul) add_subdirectory(libyul)
add_subdirectory(libsolidity) add_subdirectory(libsolidity)
add_subdirectory(libsolc) add_subdirectory(libsolc)
add_subdirectory(tools)
if (NOT EMSCRIPTEN) if (NOT EMSCRIPTEN)
add_subdirectory(solc) add_subdirectory(solc)

View File

@ -1,3 +1,26 @@
### 0.6.3 (2020-02-18)
Language Features:
* Allow contract types and enums as keys for mappings.
* Allow function selectors to be used as compile-time constants.
* Report source locations for structured documentation errors.
Compiler Features:
* AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions.
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
Bugfixes:
* Assembly: Added missing `source` field to legacy assembly json output to complete the source reference.
* Parser: Fix an internal error for ``abstract`` without ``contract``.
* Type Checker: Make invalid calls to uncallable types fatal errors instead of regular.
### 0.6.2 (2020-01-27) ### 0.6.2 (2020-01-27)
Language Features: Language Features:

View File

@ -1,5 +1,5 @@
# The Solidity Contract-Oriented Programming Language # The Solidity Contract-Oriented Programming Language
[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) You can talk to us on [![solidity at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge). Questions, feedback and suggestions are welcome!
Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform. Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform.

View File

@ -71,7 +71,7 @@ test_script:
- soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-smt - soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-smt
# Skip bytecode compare if private key is not available # Skip bytecode compare if private key is not available
- cd %APPVEYOR_BUILD_FOLDER% - cd %APPVEYOR_BUILD_FOLDER%
- ps: if ($env:priv_key) { - ps: if ($env:priv_key -and -not $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) {
scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir
} }
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%

View File

@ -54,6 +54,9 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.") message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.")
endif () endif ()
# Use fancy colors in the compiler diagnostics
add_compile_options(-fdiagnostics-color)
# Additional Clang-specific compiler settings. # Additional Clang-specific compiler settings.
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")

View File

@ -7,5 +7,5 @@ set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE)
set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE) set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
# Use libfuzzer as the fuzzing back-end # Use libfuzzer as the fuzzing back-end
set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE) set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE)
# clang/libfuzzer specific flags for ASan instrumentation # clang/libfuzzer specific flags for UBSan instrumentation
set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++" CACHE STRING "Custom compilation flags" FORCE) set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=undefined -fsanitize=fuzzer-no-link -stdlib=libstdc++" CACHE STRING "Custom compilation flags" FORCE)

View File

@ -63,7 +63,7 @@ This section highlights changes that affect syntax and semantics.
last one only works for value types). Change every ``keccak256(a, b, c)`` to last one only works for value types). Change every ``keccak256(a, b, c)`` to
``keccak256(abi.encodePacked(a, b, c))``. Even though it is not a breaking ``keccak256(abi.encodePacked(a, b, c))``. Even though it is not a breaking
change, it is suggested that developers change change, it is suggested that developers change
``x.call(bytes4(keccak256("f(uint256)"), a, b)`` to ``x.call(bytes4(keccak256("f(uint256)")), a, b)`` to
``x.call(abi.encodeWithSignature("f(uint256)", a, b))``. ``x.call(abi.encodeWithSignature("f(uint256)", a, b))``.
* Functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` now return * Functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` now return
@ -455,7 +455,7 @@ New version:
uint z = someInteger(); uint z = someInteger();
x += z; x += z;
// Throw is now disallowed. // Throw is now disallowed.
require(x > 100); require(x <= 100);
int y = -3 >> 1; int y = -3 >> 1;
require(y == -2); require(y == -2);
do { do {

View File

@ -884,5 +884,9 @@
"0.6.2": { "0.6.2": {
"bugs": [], "bugs": [],
"released": "2020-01-27" "released": "2020-01-27"
},
"0.6.3": {
"bugs": [],
"released": "2020-02-18"
} }
} }

View File

@ -17,6 +17,8 @@ import sys
import os import os
import re import re
from pygments_lexer_solidity import SolidityLexer
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
@ -24,7 +26,6 @@ import re
def setup(sphinx): def setup(sphinx):
thisdir = os.path.dirname(os.path.realpath(__file__)) thisdir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, thisdir + '/utils') sys.path.insert(0, thisdir + '/utils')
from pygments_lexer_solidity import SolidityLexer
sphinx.add_lexer('Solidity', SolidityLexer()) sphinx.add_lexer('Solidity', SolidityLexer())
sphinx.add_stylesheet('css/custom.css') sphinx.add_stylesheet('css/custom.css')
@ -53,7 +54,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = 'Solidity' project = 'Solidity'
copyright = '2016-2019, Ethereum' copyright = '2016-2020, Ethereum'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the

View File

@ -29,7 +29,7 @@ value types and strings.
pragma solidity >=0.4.0 <0.7.0; pragma solidity >=0.4.0 <0.7.0;
contract C { contract C {
uint constant x = 32**22 + 8; uint constant X = 32**22 + 8;
string constant text = "abc"; string constant TEXT = "abc";
bytes32 constant myHash = keccak256("abc"); bytes32 constant MY_HASH = keccak256("abc");
} }

View File

@ -717,3 +717,13 @@ in scope in the block that follows.
in a catch block or the execution of the try/catch statement itself in a catch block or the execution of the try/catch statement itself
reverts (for example due to decoding failures as noted above or reverts (for example due to decoding failures as noted above or
due to not providing a low-level catch clause). due to not providing a low-level catch clause).
.. note::
The reason behind a failed call can be manifold. Do not assume that
the error message is coming directly from the called contract:
The error might have happened deeper down in the call chain and the
called contract just forwarded it. Also, it could be due to an
out-of-gas situation and not a deliberate error condition:
The caller always retains 63/64th of the gas in a call and thus
even if the called contract goes out of gas, the caller still
has some gas left.

View File

@ -59,7 +59,7 @@ TypeName = ElementaryTypeName
UserDefinedTypeName = Identifier ( '.' Identifier )* UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' Mapping = 'mapping' '(' ( ElementaryTypeName | UserDefinedTypeName ) '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']' ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )* FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
( 'returns' FunctionTypeParameterList )? ( 'returns' FunctionTypeParameterList )?
@ -96,6 +96,7 @@ Expression
| IndexRangeAccess | IndexRangeAccess
| MemberAccess | MemberAccess
| FunctionCall | FunctionCall
| Expression '{' NameValueList '}'
| '(' Expression ')' | '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression | ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression | Expression '**' Expression

View File

@ -7,9 +7,8 @@ Mapping Types
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables
of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``. of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``.
The ``_KeyType`` can be any The ``_KeyType`` can be any
built-in value type plus ``bytes`` and ``string``. User-defined built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined
or complex types such as contract types, enums, mappings, structs or array types or complex types, such as mappings, structs or array types are not allowed.
apart from ``bytes`` and ``string`` are not allowed.
``_ValueType`` can be any type, including mappings, arrays and structs. ``_ValueType`` can be any type, including mappings, arrays and structs.
You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised

View File

@ -244,7 +244,7 @@ Input Description
// "default", "strip", "debug" and "verboseDebug". // "default", "strip", "debug" and "verboseDebug".
// "default" does not inject compiler-generated revert strings and keeps user-supplied ones. // "default" does not inject compiler-generated revert strings and keeps user-supplied ones.
// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects // "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects
// "debug" injects strings for compiler-generated internal reverts (not yet implemented) // "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now.
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) // "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
"revertStrings": "default" "revertStrings": "default"
} }
@ -474,3 +474,240 @@ Error types
11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue. 11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue.
12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue. 12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue.
13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible. 13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible.
.. _compiler-tools:
Compiler tools
**************
solidity-upgrade
----------------
``solidity-upgrade`` can help you to semi-automatically upgrade your contracts
to breaking language changes. While it does not and cannot implement all
required changes for every breaking release, it still supports the ones, that
would need plenty of repetitive manual adjustments otherwise.
.. note::
``solidity-upgrade`` carries out a large part of the work, but your
contracts will most likely need further manual adjustments. We recommend
using a version control system for your files. This helps reviewing and
eventually rolling back the changes made.
.. warning::
``solidity-upgrade`` is not considered to be complete or free from bugs, so
please use with care.
How it works
~~~~~~~~~~~~
You can pass (a) Solidity source file(s) to ``solidity-upgrade [files]``. If
these make use of ``import`` statement which refer to files outside the
current source file's directory, you need to specify directories that
are allowed to read and import files from, by passing
``--allow-paths [directory]``. You can ignore missing files by passing
``--ignore-missing``.
``solidity-upgrade`` is based on ``libsolidity`` and can parse, compile and
analyse your source files, and might find applicable source upgrades in them.
Source upgrades are considered to be small textual changes to your source code.
They are applied to an in-memory representation of the source files
given. The corresponding source file is updated by default, but you can pass
``--dry-run`` to simulate to whole upgrade process without writing to any file.
The upgrade process itself has two phases. In the first phase source files are
parsed, and since it is not possible to upgrade source code on that level,
errors are collected and can be logged by passing ``--verbose``. No source
upgrades available at this point.
In the second phase, all sources are compiled and all activated upgrade analysis
modules are run alongside compilation. By default, all available modules are
activated. Please read the documentation on
:ref:`available modules <upgrade-modules>` for further details.
This can result in compilation errors that may
be fixed by source upgrades. If no errors occur, no source upgrades are being
reported and you're done.
If errors occur and some upgrade module reported a source upgrade, the first
reported one gets applied and compilation is triggered again for all given
source files. The previous step is repeated as long as source upgrades are
reported. If errors still occur, you can log them by passing ``--verbose``.
If no errors occur, your contracts are up to date and can be compiled with
the latest version of the compiler.
.. _upgrade-modules:
Available upgrade modules
~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------+---------+--------------------------------------------------+
| Module | Version | Description |
+=================+=========+==================================================+
| ``constructor`` | 0.5.0 | Constructors must now be defined using the |
| | | ``constructor`` keyword. |
+-----------------+---------+--------------------------------------------------+
| ``visibility`` | 0.5.0 | Explicit function visibility is now mandatory, |
| | | defaults to ``public``. |
+-----------------+---------+--------------------------------------------------+
| ``abstract`` | 0.6.0 | The keyword ``abstract`` has to be used if a |
| | | contract does not implement all its functions. |
+-----------------+---------+--------------------------------------------------+
| ``virtual`` | 0.6.0 | Functions without implementation outside an |
| | | interface have to be marked ``virtual``. |
+-----------------+---------+--------------------------------------------------+
| ``override`` | 0.6.0 | When overriding a function or modifier, the new |
| | | keyword ``override`` must be used. |
+-----------------+---------+--------------------------------------------------+
Please read :doc:`0.5.0 release notes <050-breaking-changes>` and
:doc:`0.6.0 release notes <060-breaking-changes>` for further details.
Synopsis
~~~~~~~~
.. code-block:: none
Usage: solidity-upgrade [options] contract.sol
Allowed options:
--help Show help message and exit.
--version Show version and exit.
--allow-paths path(s)
Allow a given path for imports. A list of paths can be
supplied by separating them with a comma.
--ignore-missing Ignore missing files.
--modules module(s) Only activate a specific upgrade module. A list of
modules can be supplied by separating them with a comma.
--dry-run Apply changes in-memory only and don't write to input
file.
--verbose Print logs, errors and changes. Shortens output of
upgrade patches.
--unsafe Accept *unsafe* changes.
Bug Reports / Feature requests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you found a bug or if you have a feature request, please
`file an issue <https://github.com/ethereum/solidity/issues/new/choose>`_ on Github.
Example
~~~~~~~
Assume you have the following contracts you want to update declared in ``Source.sol``:
.. code-block:: none
// This will not compile
pragma solidity >0.4.23;
contract Updateable {
function run() public view returns (bool);
function update() public;
}
contract Upgradable {
function run() public view returns (bool);
function upgrade();
}
contract Source is Updateable, Upgradable {
function Source() public {}
function run()
public
view
returns (bool) {}
function update() {}
function upgrade() {}
}
Required changes
^^^^^^^^^^^^^^^^
To bring the contracts up to date with the current Solidity version, the
following upgrade modules have to be executed: ``constructor``,
``visibility``, ``abstract``, ``override`` and ``virtual``. Please read the
documentation on :ref:`available modules <upgrade-modules>` for further details.
Running the upgrade
^^^^^^^^^^^^^^^^^^^
In this example, all modules needed to upgrade the contracts above,
are available and all of them are activated by default. Therefore you
do not need to specify the ``--modules`` option.
.. code-block:: none
$ solidity-upgrade Source.sol --dry-run
.. code-block:: none
Running analysis (and upgrade) on given source files.
..............
After upgrade:
Found 0 errors.
Found 0 upgrades.
The above performs a dry-ran upgrade on the given file and logs statistics after all.
In this case, the upgrade was successful and no further adjustments are needed.
Finally, you can run the upgrade and also write to the source file.
.. code-block:: none
$ solidity-upgrade Source.sol
.. code-block:: none
Running analysis (and upgrade) on given source files.
..............
After upgrade:
Found 0 errors.
Found 0 upgrades.
Review changes
^^^^^^^^^^^^^^
The command above applies all changes as shown below. Please review them carefully.
.. code-block:: none
pragma solidity >0.4.23;
abstract contract Updateable {
function run() public view virtual returns (bool);
function update() public virtual;
}
abstract contract Upgradable {
function run() public view virtual returns (bool);
function upgrade() public virtual;
}
contract Source is Updateable, Upgradable {
constructor() public {}
function run()
public
view
override(Updateable,Upgradable)
returns (bool) {}
function update() public override {}
function upgrade() public override {}
}

View File

@ -833,15 +833,19 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a
| insize, out, outsize) | | | providing g gas and v wei and output area | | insize, out, outsize) | | | providing g gas and v wei and output area |
| | | | mem[out...(out+outsize)) returning 0 on error (eg. out of gas) | | | | | mem[out...(out+outsize)) returning 0 on error (eg. out of gas) |
| | | | and 1 on success | | | | | and 1 on success |
| | | | :ref:`See more <yul-call-return-area>` |
+-------------------------+-----+---+-----------------------------------------------------------------+ +-------------------------+-----+---+-----------------------------------------------------------------+
| callcode(g, a, v, in, | | F | identical to ``call`` but only use the code from a and stay | | callcode(g, a, v, in, | | F | identical to ``call`` but only use the code from a and stay |
| insize, out, outsize) | | | in the context of the current contract otherwise | | insize, out, outsize) | | | in the context of the current contract otherwise |
| | | | :ref:`See more <yul-call-return-area>` |
+-------------------------+-----+---+-----------------------------------------------------------------+ +-------------------------+-----+---+-----------------------------------------------------------------+
| delegatecall(g, a, in, | | H | identical to ``callcode`` but also keep ``caller`` | | delegatecall(g, a, in, | | H | identical to ``callcode`` but also keep ``caller`` |
| insize, out, outsize) | | | and ``callvalue`` | | insize, out, outsize) | | | and ``callvalue`` |
| | | | :ref:`See more <yul-call-return-area>` |
+-------------------------+-----+---+-----------------------------------------------------------------+ +-------------------------+-----+---+-----------------------------------------------------------------+
| staticcall(g, a, in, | | B | identical to ``call(g, a, 0, in, insize, out, outsize)`` but do | | staticcall(g, a, in, | | B | identical to ``call(g, a, 0, in, insize, out, outsize)`` but do |
| insize, out, outsize) | | | not allow state modifications | | insize, out, outsize) | | | not allow state modifications |
| | | | :ref:`See more <yul-call-return-area>` |
+-------------------------+-----+---+-----------------------------------------------------------------+ +-------------------------+-----+---+-----------------------------------------------------------------+
| return(p, s) | `-` | F | end execution, return data mem[p...(p+s)) | | return(p, s) | `-` | F | end execution, return data mem[p...(p+s)) |
+-------------------------+-----+---+-----------------------------------------------------------------+ +-------------------------+-----+---+-----------------------------------------------------------------+
@ -888,6 +892,17 @@ which are used to access other parts of a Yul object.
as arguments and return the size and offset in the data area, respectively. as arguments and return the size and offset in the data area, respectively.
For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. For the EVM, the ``datacopy`` function is equivalent to ``codecopy``.
.. _yul-call-return-area:
.. note::
The ``call*`` instructions use the ``out`` and ``outsize`` parameters to define an area in memory where
the return data is placed. This area is written to depending on how many bytes the called contract returns.
If it returns more data, only the first ``outsize`` bytes are written. You can access the rest of the data
using the ``returndatacopy`` opcode. If it returns less data, then the remaining bytes are not touched at all.
You need to use the ``returndatasize`` opcode to check which part of this memory area contains the return data.
The remaining bytes will retain their values as of before the call. If the call fails (it returns ``0``),
nothing is written to that area, but you can still retrieve the failure data using ``returndatacopy``.
.. _yul-object: .. _yul-object:
Specification of Yul Object Specification of Yul Object

View File

@ -43,7 +43,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i)
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
m_deposit += _i.deposit(); m_deposit += _i.deposit();
m_items.emplace_back(_i); m_items.emplace_back(_i);
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty()) if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid())
m_items.back().setLocation(m_currentSourceLocation); m_items.back().setLocation(m_currentSourceLocation);
m_items.back().m_modifierDepth = m_currentModifierDepth; m_items.back().m_modifierDepth = m_currentModifierDepth;
return m_items.back(); return m_items.back();
@ -69,7 +69,7 @@ namespace
string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location)
{ {
if (_location.isEmpty() || !_location.source.get() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0) if (!_location.hasText() || _sourceCodes.empty())
return ""; return "";
auto it = _sourceCodes.find(_location.source->name()); auto it = _sourceCodes.find(_location.source->name());
@ -97,7 +97,7 @@ public:
void feed(AssemblyItem const& _item) void feed(AssemblyItem const& _item)
{ {
if (!_item.location().isEmpty() && _item.location() != m_location) if (_item.location().isValid() && _item.location() != m_location)
{ {
flush(); flush();
m_location = _item.location(); m_location = _item.location();
@ -141,12 +141,12 @@ public:
void printLocation() void printLocation()
{ {
if (!m_location.source && m_location.isEmpty()) if (!m_location.isValid())
return; return;
m_out << m_prefix << " /*"; m_out << m_prefix << " /*";
if (m_location.source) if (m_location.source)
m_out << " \"" + m_location.source->name() + "\""; m_out << " \"" + m_location.source->name() + "\"";
if (!m_location.isEmpty()) if (m_location.hasText())
m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end); m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
m_out << " " << locationFromSources(m_sourceCodes, m_location); m_out << " " << locationFromSources(m_sourceCodes, m_location);
m_out << " */" << endl; m_out << " */" << endl;
@ -197,10 +197,11 @@ string Assembly::assemblyString(StringMap const& _sourceCodes) const
return tmp.str(); return tmp.str();
} }
Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string _value, string _jumpType) Json::Value Assembly::createJsonValue(string _name, int _source, int _begin, int _end, string _value, string _jumpType)
{ {
Json::Value value; Json::Value value;
value["name"] = _name; value["name"] = _name;
value["source"] = _source;
value["begin"] = _begin; value["begin"] = _begin;
value["end"] = _end; value["end"] = _end;
if (!_value.empty()) if (!_value.empty())
@ -217,65 +218,79 @@ string Assembly::toStringInHex(u256 _value)
return hexStr.str(); return hexStr.str();
} }
Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices) const
{ {
Json::Value root; Json::Value root;
Json::Value& collection = root[".code"] = Json::arrayValue; Json::Value& collection = root[".code"] = Json::arrayValue;
for (AssemblyItem const& i: m_items) for (AssemblyItem const& i: m_items)
{ {
unsigned sourceIndex = unsigned(-1);
if (i.location().source)
{
auto iter = _sourceIndices.find(i.location().source->name());
if (iter != _sourceIndices.end())
sourceIndex = iter->second;
}
switch (i.type()) switch (i.type())
{ {
case Operation: case Operation:
collection.append( collection.append(
createJsonValue(instructionInfo(i.instruction()).name, i.location().start, i.location().end, i.getJumpTypeAsString())); createJsonValue(
instructionInfo(i.instruction()).name,
sourceIndex,
i.location().start,
i.location().end,
i.getJumpTypeAsString())
);
break; break;
case Push: case Push:
collection.append( collection.append(
createJsonValue("PUSH", i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString())); createJsonValue("PUSH", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString()));
break; break;
case PushString: case PushString:
collection.append( collection.append(
createJsonValue("PUSH tag", i.location().start, i.location().end, m_strings.at((h256)i.data()))); createJsonValue("PUSH tag", sourceIndex, i.location().start, i.location().end, m_strings.at((h256)i.data())));
break; break;
case PushTag: case PushTag:
if (i.data() == 0) if (i.data() == 0)
collection.append( collection.append(
createJsonValue("PUSH [ErrorTag]", i.location().start, i.location().end, "")); createJsonValue("PUSH [ErrorTag]", sourceIndex, i.location().start, i.location().end, ""));
else else
collection.append( collection.append(
createJsonValue("PUSH [tag]", i.location().start, i.location().end, toString(i.data()))); createJsonValue("PUSH [tag]", sourceIndex, i.location().start, i.location().end, toString(i.data())));
break; break;
case PushSub: case PushSub:
collection.append( collection.append(
createJsonValue("PUSH [$]", i.location().start, i.location().end, toString(h256(i.data())))); createJsonValue("PUSH [$]", sourceIndex, i.location().start, i.location().end, toString(h256(i.data()))));
break; break;
case PushSubSize: case PushSubSize:
collection.append( collection.append(
createJsonValue("PUSH #[$]", i.location().start, i.location().end, toString(h256(i.data())))); createJsonValue("PUSH #[$]", sourceIndex, i.location().start, i.location().end, toString(h256(i.data()))));
break; break;
case PushProgramSize: case PushProgramSize:
collection.append( collection.append(
createJsonValue("PUSHSIZE", i.location().start, i.location().end)); createJsonValue("PUSHSIZE", sourceIndex, i.location().start, i.location().end));
break; break;
case PushLibraryAddress: case PushLibraryAddress:
collection.append( collection.append(
createJsonValue("PUSHLIB", i.location().start, i.location().end, m_libraries.at(h256(i.data()))) createJsonValue("PUSHLIB", sourceIndex, i.location().start, i.location().end, m_libraries.at(h256(i.data())))
); );
break; break;
case PushDeployTimeAddress: case PushDeployTimeAddress:
collection.append( collection.append(
createJsonValue("PUSHDEPLOYADDRESS", i.location().start, i.location().end) createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end)
); );
break; break;
case Tag: case Tag:
collection.append( collection.append(
createJsonValue("tag", i.location().start, i.location().end, toString(i.data()))); createJsonValue("tag", sourceIndex, i.location().start, i.location().end, toString(i.data())));
collection.append( collection.append(
createJsonValue("JUMPDEST", i.location().start, i.location().end)); createJsonValue("JUMPDEST", sourceIndex, i.location().start, i.location().end));
break; break;
case PushData: case PushData:
collection.append(createJsonValue("PUSH data", i.location().start, i.location().end, toStringInHex(i.data()))); collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data())));
break; break;
default: default:
assertThrow(false, InvalidOpcode, ""); assertThrow(false, InvalidOpcode, "");
@ -293,7 +308,7 @@ Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const
{ {
std::stringstream hexStr; std::stringstream hexStr;
hexStr << hex << i; hexStr << hex << i;
data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceCodes); data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices);
} }
} }

View File

@ -133,7 +133,7 @@ public:
/// Create a JSON representation of the assembly. /// Create a JSON representation of the assembly.
Json::Value assemblyJSON( Json::Value assemblyJSON(
StringMap const& _sourceCodes = StringMap() std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>()
) const; ) const;
protected: protected:
@ -145,7 +145,14 @@ protected:
unsigned bytesRequired(unsigned subTagSize) const; unsigned bytesRequired(unsigned subTagSize) const;
private: private:
static Json::Value createJsonValue(std::string _name, int _begin, int _end, std::string _value = std::string(), std::string _jumpType = std::string()); static Json::Value createJsonValue(
std::string _name,
int _source,
int _begin,
int _end,
std::string _value = std::string(),
std::string _jumpType = std::string()
);
static std::string toStringInHex(u256 _value); static std::string toStringInHex(u256 _value);
protected: protected:

View File

@ -69,8 +69,8 @@ public:
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
/// item that must be fed into a new instance of the eliminator. /// item that must be fed into a new instance of the eliminator.
/// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect /// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect
template <class _AssemblyItemIterator> template <class AssemblyItemIterator>
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant); AssemblyItemIterator feedItems(AssemblyItemIterator _iterator, AssemblyItemIterator _end, bool _msizeImportant);
/// @returns the resulting items after optimization. /// @returns the resulting items after optimization.
AssemblyItems getOptimizedItems(); AssemblyItems getOptimizedItems();
@ -169,10 +169,10 @@ private:
std::map<int, Id> m_targetStack; std::map<int, Id> m_targetStack;
}; };
template <class _AssemblyItemIterator> template <class AssemblyItemIterator>
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
_AssemblyItemIterator _iterator, AssemblyItemIterator _iterator,
_AssemblyItemIterator _end, AssemblyItemIterator _end,
bool _msizeImportant bool _msizeImportant
) )
{ {

View File

@ -179,7 +179,7 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
/// Helper function for KnownState::reduceToCommonKnowledge, removes everything from /// Helper function for KnownState::reduceToCommonKnowledge, removes everything from
/// _this which is not in or not equal to the value in _other. /// _this which is not in or not equal to the value in _other.
template <class _Mapping> void intersect(_Mapping& _this, _Mapping const& _other) template <class Mapping> void intersect(Mapping& _this, Mapping const& _other)
{ {
for (auto it = _this.begin(); it != _this.end();) for (auto it = _this.begin(); it != _this.end();)
if (_other.count(it->first) && _other.at(it->first) == it->second) if (_other.count(it->first) && _other.at(it->first) == it->second)

View File

@ -93,10 +93,13 @@ string CharStream::lineAtPosition(int _position) const
lineStart = 0; lineStart = 0;
else else
lineStart++; lineStart++;
return m_source.substr( string line = m_source.substr(
lineStart, lineStart,
min(m_source.find('\n', lineStart), m_source.size()) - lineStart min(m_source.find('\n', lineStart), m_source.size()) - lineStart
); );
if (!line.empty() && line.back() == '\r')
line.pop_back();
return line;
} }
tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const

View File

@ -244,3 +244,12 @@ void ErrorReporter::docstringParsingError(string const& _description)
_description _description
); );
} }
void ErrorReporter::docstringParsingError(SourceLocation const& _location, string const& _description)
{
error(
Error::Type::DocstringParsingError,
_location,
_description
);
}

View File

@ -107,6 +107,7 @@ public:
void fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description); void fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description);
void docstringParsingError(std::string const& _description); void docstringParsingError(std::string const& _description);
void docstringParsingError(SourceLocation const& _location, std::string const& _description);
ErrorList const& errors() const; ErrorList const& errors() const;

View File

@ -51,7 +51,7 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
break; break;
} }
if (!_location.isEmpty()) if (_location.isValid())
*this << errinfo_sourceLocation(_location); *this << errinfo_sourceLocation(_location);
if (!_description.empty()) if (!_description.empty())
*this << util::errinfo_comment(_description); *this << util::errinfo_comment(_description);
@ -60,7 +60,7 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
Error::Error(Error::Type _type, std::string const& _description, SourceLocation const& _location): Error::Error(Error::Type _type, std::string const& _description, SourceLocation const& _location):
Error(_type) Error(_type)
{ {
if (!_location.isEmpty()) if (_location.isValid())
*this << errinfo_sourceLocation(_location); *this << errinfo_sourceLocation(_location);
*this << util::errinfo_comment(_description); *this << util::errinfo_comment(_description);
} }

View File

@ -28,14 +28,9 @@ using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::langutil; using namespace solidity::langutil;
int ParserBase::position() const SourceLocation ParserBase::currentLocation() const
{ {
return m_scanner->currentLocation().start; return m_scanner->currentLocation();
}
int ParserBase::endPosition() const
{
return m_scanner->currentLocation().end;
} }
Token ParserBase::currentToken() const Token ParserBase::currentToken() const
@ -101,8 +96,8 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN
Token tok = m_scanner->currentToken(); Token tok = m_scanner->currentToken();
if (tok != _value) if (tok != _value)
{ {
int startPosition = position(); SourceLocation errorLoc = currentLocation();
SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()}; int startPosition = errorLoc.start;
while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS) while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS)
m_scanner->next(); m_scanner->next();
@ -150,7 +145,7 @@ void ParserBase::decreaseRecursionDepth()
void ParserBase::parserWarning(string const& _description) void ParserBase::parserWarning(string const& _description)
{ {
m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description); m_errorReporter.warning(currentLocation(), _description);
} }
void ParserBase::parserError(SourceLocation const& _location, string const& _description) void ParserBase::parserError(SourceLocation const& _location, string const& _description)
@ -160,12 +155,12 @@ void ParserBase::parserError(SourceLocation const& _location, string const& _des
void ParserBase::parserError(string const& _description) void ParserBase::parserError(string const& _description)
{ {
parserError(SourceLocation{position(), endPosition(), source()}, _description); parserError(currentLocation(), _description);
} }
void ParserBase::fatalParserError(string const& _description) void ParserBase::fatalParserError(string const& _description)
{ {
fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); fatalParserError(currentLocation(), _description);
} }
void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description) void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description)

View File

@ -62,10 +62,8 @@ protected:
ParserBase& m_parser; ParserBase& m_parser;
}; };
/// Start position of the current token /// Location of the current token
int position() const; SourceLocation currentLocation() const;
/// End position of the current token
int endPosition() const;
///@{ ///@{
///@name Helper functions ///@name Helper functions

View File

@ -306,19 +306,24 @@ bool Scanner::tryScanEndOfLine()
return false; return false;
} }
Token Scanner::scanSingleLineDocComment() int Scanner::scanSingleLineDocComment()
{ {
LiteralScope literal(this, LITERAL_TYPE_COMMENT); LiteralScope literal(this, LITERAL_TYPE_COMMENT);
int endPosition = m_source->position();
advance(); //consume the last '/' at /// advance(); //consume the last '/' at ///
skipWhitespaceExceptUnicodeLinebreak(); skipWhitespaceExceptUnicodeLinebreak();
while (!isSourcePastEndOfInput()) while (!isSourcePastEndOfInput())
{ {
endPosition = m_source->position();
if (tryScanEndOfLine()) if (tryScanEndOfLine())
{ {
// check if next line is also a documentation comment // Check if next line is also a single-line comment.
skipWhitespace(); // If any whitespaces were skipped, use source position before.
if (!skipWhitespace())
endPosition = m_source->position();
if (!m_source->isPastEndOfInput(3) && if (!m_source->isPastEndOfInput(3) &&
m_source->get(0) == '/' && m_source->get(0) == '/' &&
m_source->get(1) == '/' && m_source->get(1) == '/' &&
@ -338,7 +343,7 @@ Token Scanner::scanSingleLineDocComment()
advance(); advance();
} }
literal.complete(); literal.complete();
return Token::CommentLiteral; return endPosition;
} }
Token Scanner::skipMultiLineComment() Token Scanner::skipMultiLineComment()
@ -426,11 +431,10 @@ Token Scanner::scanSlash()
else if (m_char == '/') else if (m_char == '/')
{ {
// doxygen style /// comment // doxygen style /// comment
Token comment;
m_skippedComments[NextNext].location.start = firstSlashPosition; m_skippedComments[NextNext].location.start = firstSlashPosition;
comment = scanSingleLineDocComment(); m_skippedComments[NextNext].location.source = m_source;
m_skippedComments[NextNext].location.end = sourcePos(); m_skippedComments[NextNext].token = Token::CommentLiteral;
m_skippedComments[NextNext].token = comment; m_skippedComments[NextNext].location.end = scanSingleLineDocComment();
return Token::Whitespace; return Token::Whitespace;
} }
else else
@ -454,6 +458,7 @@ Token Scanner::scanSlash()
// we actually have a multiline documentation comment // we actually have a multiline documentation comment
Token comment; Token comment;
m_skippedComments[NextNext].location.start = firstSlashPosition; m_skippedComments[NextNext].location.start = firstSlashPosition;
m_skippedComments[NextNext].location.source = m_source;
comment = scanMultiLineDocComment(); comment = scanMultiLineDocComment();
m_skippedComments[NextNext].location.end = sourcePos(); m_skippedComments[NextNext].location.end = sourcePos();
m_skippedComments[NextNext].token = comment; m_skippedComments[NextNext].token = comment;
@ -679,6 +684,7 @@ void Scanner::scanToken()
} }
while (token == Token::Whitespace); while (token == Token::Whitespace);
m_tokens[NextNext].location.end = sourcePos(); m_tokens[NextNext].location.end = sourcePos();
m_tokens[NextNext].location.source = m_source;
m_tokens[NextNext].token = token; m_tokens[NextNext].token = token;
m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n); m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n);
} }

View File

@ -167,12 +167,6 @@ public:
/// Do only use in error cases, they are quite expensive. /// Do only use in error cases, they are quite expensive.
std::string lineAtPosition(int _position) const { return m_source->lineAtPosition(_position); } std::string lineAtPosition(int _position) const { return m_source->lineAtPosition(_position); }
std::tuple<int, int> translatePositionToLineColumn(int _position) const { return m_source->translatePositionToLineColumn(_position); } std::tuple<int, int> translatePositionToLineColumn(int _position) const { return m_source->translatePositionToLineColumn(_position); }
std::string sourceAt(SourceLocation const& _location) const
{
solAssert(!_location.isEmpty(), "");
solAssert(m_source.get() == _location.source.get(), "CharStream memory locations must match.");
return m_source->source().substr(_location.start, _location.end - _location.start);
}
///@} ///@}
private: private:
@ -235,7 +229,8 @@ private:
Token scanString(); Token scanString();
Token scanHexString(); Token scanHexString();
Token scanSingleLineDocComment(); /// Scans a single line comment and returns its corrected end position.
int scanSingleLineDocComment();
Token scanMultiLineDocComment(); Token scanMultiLineDocComment();
/// Scans a slash '/' and depending on the characters returns the appropriate token /// Scans a slash '/' and depending on the characters returns the appropriate token
Token scanSlash(); Token scanSlash();

View File

@ -56,24 +56,33 @@ struct SourceLocation
inline bool contains(SourceLocation const& _other) const inline bool contains(SourceLocation const& _other) const
{ {
if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) if (!hasText() || !_other.hasText() || source.get() != _other.source.get())
return false; return false;
return start <= _other.start && _other.end <= end; return start <= _other.start && _other.end <= end;
} }
inline bool intersects(SourceLocation const& _other) const inline bool intersects(SourceLocation const& _other) const
{ {
if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) if (!hasText() || !_other.hasText() || source.get() != _other.source.get())
return false; return false;
return _other.start < end && start < _other.end; return _other.start < end && start < _other.end;
} }
bool isEmpty() const { return start == -1 && end == -1; } bool isValid() const { return source || start != -1 || end != -1; }
bool hasText() const
{
return
source &&
0 <= start &&
start <= end &&
end <= int(source->source().length());
}
std::string text() const std::string text() const
{ {
assertThrow(source, SourceLocationError, "Requested text from null source."); assertThrow(source, SourceLocationError, "Requested text from null source.");
assertThrow(!isEmpty(), SourceLocationError, "Requested text from empty source location."); assertThrow(0 <= start, SourceLocationError, "Invalid source location.");
assertThrow(start <= end, SourceLocationError, "Invalid source location."); assertThrow(start <= end, SourceLocationError, "Invalid source location.");
assertThrow(end <= int(source->source().length()), SourceLocationError, "Invalid source location."); assertThrow(end <= int(source->source().length()), SourceLocationError, "Invalid source location.");
return source->source().substr(start, end - start); return source->source().substr(start, end - start);
@ -109,13 +118,13 @@ SourceLocation const parseSourceLocation(std::string const& _input, std::string
/// Stream output for Location (used e.g. in boost exceptions). /// Stream output for Location (used e.g. in boost exceptions).
inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location) inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location)
{ {
if (_location.isEmpty()) if (!_location.isValid())
return _out << "NO_LOCATION_SPECIFIED"; return _out << "NO_LOCATION_SPECIFIED";
if (_location.source) if (_location.source)
_out << _location.source->name(); _out << _location.source->name();
_out << "[" << _location.start << "," << _location.end << ")"; _out << "[" << _location.start << "," << _location.end << "]";
return _out; return _out;
} }

View File

@ -46,7 +46,7 @@ SourceReference SourceReferenceExtractor::extract(SourceLocation const* _locatio
if (!_location || !_location->source.get()) // Nothing we can extract here if (!_location || !_location->source.get()) // Nothing we can extract here
return SourceReference::MessageOnly(std::move(message)); return SourceReference::MessageOnly(std::move(message));
if (_location->source->source().empty()) // No source text, so we can only extract the source name if (!_location->hasText()) // No source text, so we can only extract the source name
return SourceReference::MessageOnly(std::move(message), _location->source->name()); return SourceReference::MessageOnly(std::move(message), _location->source->name());
shared_ptr<CharStream> const& source = _location->source; shared_ptr<CharStream> const& source = _location->source;

View File

@ -69,6 +69,8 @@ void SourceReferenceFormatter::printSourceName(SourceReference const& _ref)
{ {
if (_ref.position.line != -1) if (_ref.position.line != -1)
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": "; m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": ";
else if (!_ref.sourceName.empty())
m_stream << _ref.sourceName << ": ";
} }
void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _category) void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _category)

View File

@ -67,13 +67,20 @@ AnsiColorized SourceReferenceFormatterHuman::diagColored() const
void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ref) void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ref)
{ {
if (_ref.position.line < 0) if (_ref.sourceName.empty())
return; // Nothing we can print here return; // Nothing we can print here
int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1; int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1;
// line 0: source name // line 0: source name
frameColored() << string(leftpad, ' ') << "--> "; frameColored() << string(leftpad, ' ') << "--> ";
if (_ref.position.line < 0)
{
m_stream << _ref.sourceName << "\n";
return; // No line available, nothing else to print
}
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n'; m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n';
if (!_ref.multiline) if (!_ref.multiline)

View File

@ -79,8 +79,9 @@ set(sources
codegen/ir/IRGeneratorForStatements.h codegen/ir/IRGeneratorForStatements.h
codegen/ir/IRGenerationContext.cpp codegen/ir/IRGenerationContext.cpp
codegen/ir/IRGenerationContext.h codegen/ir/IRGenerationContext.h
codegen/ir/IRLValue.cpp
codegen/ir/IRLValue.h codegen/ir/IRLValue.h
codegen/ir/IRVariable.cpp
codegen/ir/IRVariable.h
formal/BMC.cpp formal/BMC.cpp
formal/BMC.h formal/BMC.h
formal/CHC.cpp formal/CHC.cpp

View File

@ -163,7 +163,7 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
std::set<SourceLocation> unreachable; std::set<SourceLocation> unreachable;
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run( util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
[&](CFGNode const* _node, auto&& _addChild) { [&](CFGNode const* _node, auto&& _addChild) {
if (!reachable.count(_node) && !_node->location.isEmpty()) if (!reachable.count(_node) && _node->location.isValid())
unreachable.insert(_node->location); unreachable.insert(_node->location);
for (CFGNode const* entry: _node->entries) for (CFGNode const* entry: _node->entries)
_addChild(entry); _addChild(entry);

View File

@ -73,7 +73,8 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
void DocStringAnalyser::checkParameters( void DocStringAnalyser::checkParameters(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
DocumentedAnnotation& _annotation StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation
) )
{ {
set<string> validParams; set<string> validParams;
@ -86,6 +87,7 @@ void DocStringAnalyser::checkParameters(
for (auto i = paramRange.first; i != paramRange.second; ++i) for (auto i = paramRange.first; i != paramRange.second; ++i)
if (!validParams.count(i->second.paramName)) if (!validParams.count(i->second.paramName))
appendError( appendError(
_node.documentation()->location(),
"Documented parameter \"" + "Documented parameter \"" +
i->second.paramName + i->second.paramName +
"\" not found in the parameter list of the function." "\" not found in the parameter list of the function."
@ -95,37 +97,37 @@ void DocStringAnalyser::checkParameters(
void DocStringAnalyser::handleConstructor( void DocStringAnalyser::handleConstructor(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
Documented const& _node, StructurallyDocumented const& _node,
DocumentedAnnotation& _annotation StructurallyDocumentedAnnotation& _annotation
) )
{ {
static set<string> const validTags = set<string>{"author", "dev", "notice", "param"}; static set<string> const validTags = set<string>{"author", "dev", "notice", "param"};
parseDocStrings(_node, _annotation, validTags, "constructor"); parseDocStrings(_node, _annotation, validTags, "constructor");
checkParameters(_callable, _annotation); checkParameters(_callable, _node, _annotation);
} }
void DocStringAnalyser::handleCallable( void DocStringAnalyser::handleCallable(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
Documented const& _node, StructurallyDocumented const& _node,
DocumentedAnnotation& _annotation StructurallyDocumentedAnnotation& _annotation
) )
{ {
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"}; static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
parseDocStrings(_node, _annotation, validTags, "functions"); parseDocStrings(_node, _annotation, validTags, "functions");
checkParameters(_callable, _annotation); checkParameters(_callable, _node, _annotation);
} }
void DocStringAnalyser::parseDocStrings( void DocStringAnalyser::parseDocStrings(
Documented const& _node, StructurallyDocumented const& _node,
DocumentedAnnotation& _annotation, StructurallyDocumentedAnnotation& _annotation,
set<string> const& _validTags, set<string> const& _validTags,
string const& _nodeName string const& _nodeName
) )
{ {
DocStringParser parser; DocStringParser parser;
if (_node.documentation() && !_node.documentation()->empty()) if (_node.documentation() && !_node.documentation()->text()->empty())
{ {
if (!parser.parse(*_node.documentation(), m_errorReporter)) if (!parser.parse(*_node.documentation()->text(), m_errorReporter))
m_errorOccured = true; m_errorOccured = true;
_annotation.docTags = parser.tags(); _annotation.docTags = parser.tags();
} }
@ -134,7 +136,10 @@ void DocStringAnalyser::parseDocStrings(
for (auto const& docTag: _annotation.docTags) for (auto const& docTag: _annotation.docTags)
{ {
if (!_validTags.count(docTag.first)) if (!_validTags.count(docTag.first))
appendError("Documentation tag @" + docTag.first + " not valid for " + _nodeName + "."); appendError(
_node.documentation()->location(),
"Documentation tag @" + docTag.first + " not valid for " + _nodeName + "."
);
else else
if (docTag.first == "return") if (docTag.first == "return")
{ {
@ -145,14 +150,18 @@ void DocStringAnalyser::parseDocStrings(
string firstWord = content.substr(0, content.find_first_of(" \t")); string firstWord = content.substr(0, content.find_first_of(" \t"));
if (returnTagsVisited > function->returnParameters().size()) if (returnTagsVisited > function->returnParameters().size())
appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + appendError(
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
" exceedes the number of return parameters." " exceedes the number of return parameters."
); );
else else
{ {
auto parameter = function->returnParameters().at(returnTagsVisited - 1); auto parameter = function->returnParameters().at(returnTagsVisited - 1);
if (!parameter->name().empty() && parameter->name() != firstWord) if (!parameter->name().empty() && parameter->name() != firstWord)
appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + appendError(
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
" does not contain the name of its return parameter." " does not contain the name of its return parameter."
); );
} }
@ -161,8 +170,8 @@ void DocStringAnalyser::parseDocStrings(
} }
} }
void DocStringAnalyser::appendError(string const& _description) void DocStringAnalyser::appendError(SourceLocation const& _location, string const& _description)
{ {
m_errorOccured = true; m_errorOccured = true;
m_errorReporter.docstringParsingError(_description); m_errorReporter.docstringParsingError(_location, _description);
} }

View File

@ -51,29 +51,30 @@ private:
void checkParameters( void checkParameters(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
DocumentedAnnotation& _annotation StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation
); );
void handleConstructor( void handleConstructor(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
Documented const& _node, StructurallyDocumented const& _node,
DocumentedAnnotation& _annotation StructurallyDocumentedAnnotation& _annotation
); );
void handleCallable( void handleCallable(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
Documented const& _node, StructurallyDocumented const& _node,
DocumentedAnnotation& _annotation StructurallyDocumentedAnnotation& _annotation
); );
void parseDocStrings( void parseDocStrings(
Documented const& _node, StructurallyDocumented const& _node,
DocumentedAnnotation& _annotation, StructurallyDocumentedAnnotation& _annotation,
std::set<std::string> const& _validTags, std::set<std::string> const& _validTags,
std::string const& _nodeName std::string const& _nodeName
); );
void appendError(std::string const& _description); void appendError(langutil::SourceLocation const& _location, std::string const& _description);
bool m_errorOccured = false; bool m_errorOccured = false;
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;

View File

@ -405,13 +405,13 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end()); _contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
} }
template <class _T> template <class T>
vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMerge) vector<T const*> NameAndTypeResolver::cThreeMerge(list<list<T const*>>& _toMerge)
{ {
// returns true iff _candidate appears only as last element of the lists // returns true iff _candidate appears only as last element of the lists
auto appearsOnlyAtHead = [&](_T const* _candidate) -> bool auto appearsOnlyAtHead = [&](T const* _candidate) -> bool
{ {
for (list<_T const*> const& bases: _toMerge) for (list<T const*> const& bases: _toMerge)
{ {
solAssert(!bases.empty(), ""); solAssert(!bases.empty(), "");
if (find(++bases.begin(), bases.end(), _candidate) != bases.end()) if (find(++bases.begin(), bases.end(), _candidate) != bases.end())
@ -420,9 +420,9 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
return true; return true;
}; };
// returns the next candidate to append to the linearized list or nullptr on failure // returns the next candidate to append to the linearized list or nullptr on failure
auto nextCandidate = [&]() -> _T const* auto nextCandidate = [&]() -> T const*
{ {
for (list<_T const*> const& bases: _toMerge) for (list<T const*> const& bases: _toMerge)
{ {
solAssert(!bases.empty(), ""); solAssert(!bases.empty(), "");
if (appearsOnlyAtHead(bases.front())) if (appearsOnlyAtHead(bases.front()))
@ -431,7 +431,7 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
return nullptr; return nullptr;
}; };
// removes the given contract from all lists // removes the given contract from all lists
auto removeCandidate = [&](_T const* _candidate) auto removeCandidate = [&](T const* _candidate)
{ {
for (auto it = _toMerge.begin(); it != _toMerge.end();) for (auto it = _toMerge.begin(); it != _toMerge.end();)
{ {
@ -443,13 +443,13 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
} }
}; };
_toMerge.remove_if([](list<_T const*> const& _bases) { return _bases.empty(); }); _toMerge.remove_if([](list<T const*> const& _bases) { return _bases.empty(); });
vector<_T const*> result; vector<T const*> result;
while (!_toMerge.empty()) while (!_toMerge.empty())
{ {
_T const* candidate = nextCandidate(); T const* candidate = nextCandidate();
if (!candidate) if (!candidate)
return vector<_T const*>(); return vector<T const*>();
result.push_back(candidate); result.push_back(candidate);
removeCandidate(candidate); removeCandidate(candidate);
} }

View File

@ -122,8 +122,8 @@ private:
void linearizeBaseContracts(ContractDefinition& _contract); void linearizeBaseContracts(ContractDefinition& _contract);
/// Computes the C3-merge of the given list of lists of bases. /// Computes the C3-merge of the given list of lists of bases.
/// @returns the linearized vector or an empty vector if linearization is not possible. /// @returns the linearized vector or an empty vector if linearization is not possible.
template <class _T> template <class T>
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge); static std::vector<T const*> cThreeMerge(std::list<std::list<T const*>>& _toMerge);
/// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration, /// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration,
/// where nullptr denotes the global scope. Note that structs are not scope since they do /// where nullptr denotes the global scope. Note that structs are not scope since they do

View File

@ -144,8 +144,8 @@ vector<ContractDefinition const*> resolveDirectBaseContracts(ContractDefinition
Declaration const* baseDecl = Declaration const* baseDecl =
specifier->name().annotation().referencedDeclaration; specifier->name().annotation().referencedDeclaration;
auto contract = dynamic_cast<ContractDefinition const*>(baseDecl); auto contract = dynamic_cast<ContractDefinition const*>(baseDecl);
solAssert(contract, "contract is null"); if (contract)
resolvedContracts.emplace_back(contract); resolvedContracts.emplace_back(contract);
} }
return resolvedContracts; return resolvedContracts;

View File

@ -129,6 +129,7 @@ private:
class OverrideChecker class OverrideChecker
{ {
public: public:
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
/// @param _errorReporter provides the error logging functionality. /// @param _errorReporter provides the error logging functionality.
explicit OverrideChecker(langutil::ErrorReporter& _errorReporter): explicit OverrideChecker(langutil::ErrorReporter& _errorReporter):
@ -137,12 +138,17 @@ public:
void check(ContractDefinition const& _contract); void check(ContractDefinition const& _contract);
private:
struct CompareByID struct CompareByID
{ {
bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const; bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const;
}; };
/// Returns all functions of bases (including public state variables) that have not yet been overwritten.
/// May contain the same function multiple times when used with shared bases.
OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const;
OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const;
private:
void checkIllegalOverrides(ContractDefinition const& _contract); void checkIllegalOverrides(ContractDefinition const& _contract);
/// Performs various checks related to @a _overriding overriding @a _super like /// Performs various checks related to @a _overriding overriding @a _super like
/// different return type, invalid visibility change, etc. /// different return type, invalid visibility change, etc.
@ -174,15 +180,8 @@ private:
/// Resolves an override list of UserDefinedTypeNames to a list of contracts. /// Resolves an override list of UserDefinedTypeNames to a list of contracts.
std::set<ContractDefinition const*, CompareByID> resolveOverrideList(OverrideSpecifier const& _overrides) const; std::set<ContractDefinition const*, CompareByID> resolveOverrideList(OverrideSpecifier const& _overrides) const;
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
void checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited); void checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited);
/// Returns all functions of bases (including public state variables) that have not yet been overwritten.
/// May contain the same function multiple times when used with shared bases.
OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const;
OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const;
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;
/// Cache for inheritedFunctions(). /// Cache for inheritedFunctions().

View File

@ -133,9 +133,9 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
bool visit(VariableDeclaration const& _variable) override bool visit(VariableDeclaration const& _variable) override
{ {
solAssert(!m_currentConstVariable, "");
if (_variable.isConstant()) if (_variable.isConstant())
{ {
solAssert(!m_currentConstVariable, "");
m_currentConstVariable = &_variable; m_currentConstVariable = &_variable;
m_constVariables.push_back(&_variable); m_constVariables.push_back(&_variable);
} }

View File

@ -224,15 +224,36 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
_typeName.annotation().type = TypeProvider::function(_typeName); _typeName.annotation().type = TypeProvider::function(_typeName);
} }
void ReferencesResolver::endVisit(Mapping const& _typeName) void ReferencesResolver::endVisit(Mapping const& _mapping)
{ {
TypePointer keyType = _typeName.keyType().annotation().type; if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
TypePointer valueType = _typeName.valueType().annotation().type; {
if (auto const* contractType = dynamic_cast<ContractType const*>(typeName->annotation().type))
{
if (contractType->contractDefinition().isLibrary())
m_errorReporter.fatalTypeError(
typeName->location(),
"Library types cannot be used as mapping keys."
);
}
else if (typeName->annotation().type->category() != Type::Category::Enum)
m_errorReporter.fatalTypeError(
typeName->location(),
"Only elementary types, contract types or enums are allowed as mapping keys."
);
}
else
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
TypePointer keyType = _mapping.keyType().annotation().type;
TypePointer valueType = _mapping.valueType().annotation().type;
// Convert key type to memory. // Convert key type to memory.
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType); keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
// Convert value type to storage reference. // Convert value type to storage reference.
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType); valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
_typeName.annotation().type = TypeProvider::mapping(keyType, valueType); _mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
} }
void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
@ -270,87 +291,10 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
{ {
m_resolver.warnVariablesNamedLikeInstructions(); m_resolver.warnVariablesNamedLikeInstructions();
// Errors created in this stage are completely ignored because we do not yet know m_yulAnnotation = &_inlineAssembly.annotation();
// the type and size of external identifiers, which would result in false errors. (*this)(_inlineAssembly.operations());
// The only purpose of this step is to fill the inline assembly annotation with m_yulAnnotation = nullptr;
// external references.
ErrorList errors;
ErrorReporter errorsIgnored(errors);
yul::ExternalIdentifierAccess::Resolver resolver =
[&](yul::Identifier const& _identifier, yul::IdentifierContext _context, bool _crossesFunctionBoundary) {
bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset");
if (_context == yul::IdentifierContext::VariableDeclaration)
{
string namePrefix = _identifier.name.str().substr(0, _identifier.name.str().find('.'));
if (isSlot || isOffset)
declarationError(_identifier.location, "In variable declarations _slot and _offset can not be used as a suffix.");
else if (
auto declarations = m_resolver.nameFromCurrentScope(namePrefix);
!declarations.empty()
)
{
SecondarySourceLocation ssl;
for (auto const* decl: declarations)
ssl.append("The shadowed declaration is here:", decl->location());
if (!ssl.infos.empty())
declarationError(
_identifier.location,
ssl,
namePrefix.size() < _identifier.name.str().size() ?
"The prefix of this declaration conflicts with a declaration outside the inline assembly block." :
"This declaration shadows a declaration outside the inline assembly block."
);
}
return size_t(-1);
}
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
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.str().substr(0, _identifier.name.str().size() - (
isSlot ?
string("_slot").size() :
string("_offset").size()
));
if (realName.empty())
{
declarationError(_identifier.location, "In variable names _slot and _offset can only be used as a suffix.");
return size_t(-1);
}
declarations = m_resolver.nameFromCurrentScope(realName);
}
if (declarations.size() > 1)
{
declarationError(_identifier.location, "Multiple matching identifiers. Resolving overloaded identifiers is not supported.");
return size_t(-1);
}
else if (declarations.size() == 0)
return size_t(-1);
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && _crossesFunctionBoundary)
{
declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function.");
return size_t(-1);
}
_inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
_inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
_inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
return size_t(1);
};
// Will be re-generated later with correct information
// We use the latest EVM version because we will re-run it anyway.
yul::AsmAnalysisInfo analysisInfo;
yul::AsmAnalyzer(
analysisInfo,
errorsIgnored,
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
resolver
).analyze(_inlineAssembly.operations());
return false; return false;
} }
@ -468,6 +412,90 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
_variable.annotation().type = type; _variable.annotation().type = type;
} }
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
{
bool wasInsideFunction = m_yulInsideFunction;
m_yulInsideFunction = true;
this->operator()(_function.body);
m_yulInsideFunction = wasInsideFunction;
}
void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{
bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset");
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
if (isSlot || isOffset)
{
// special mode to access storage variables
if (!declarations.empty())
// the special identifier exists itself, we should not allow that.
return;
string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - (
isSlot ?
string("_slot").size() :
string("_offset").size()
));
if (realName.empty())
{
declarationError(_identifier.location, "In variable names _slot and _offset can only be used as a suffix.");
return;
}
declarations = m_resolver.nameFromCurrentScope(realName);
}
if (declarations.size() > 1)
{
declarationError(_identifier.location, "Multiple matching identifiers. Resolving overloaded identifiers is not supported.");
return;
}
else if (declarations.size() == 0)
return;
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && m_yulInsideFunction)
{
declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function.");
return;
}
m_yulAnnotation->externalReferences[&_identifier].isSlot = isSlot;
m_yulAnnotation->externalReferences[&_identifier].isOffset = isOffset;
m_yulAnnotation->externalReferences[&_identifier].declaration = declarations.front();
}
void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
{
for (auto const& identifier: _varDecl.variables)
{
bool isSlot = boost::algorithm::ends_with(identifier.name.str(), "_slot");
bool isOffset = boost::algorithm::ends_with(identifier.name.str(), "_offset");
string namePrefix = identifier.name.str().substr(0, identifier.name.str().find('.'));
if (isSlot || isOffset)
declarationError(identifier.location, "In variable declarations _slot and _offset can not be used as a suffix.");
else if (
auto declarations = m_resolver.nameFromCurrentScope(namePrefix);
!declarations.empty()
)
{
SecondarySourceLocation ssl;
for (auto const* decl: declarations)
ssl.append("The shadowed declaration is here:", decl->location());
if (!ssl.infos.empty())
declarationError(
identifier.location,
ssl,
namePrefix.size() < identifier.name.str().size() ?
"The prefix of this declaration conflicts with a declaration outside the inline assembly block." :
"This declaration shadows a declaration outside the inline assembly block."
);
}
}
if (_varDecl.value)
visit(*_varDecl.value);
}
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description) void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
{ {
m_errorOccurred = true; m_errorOccurred = true;

View File

@ -25,6 +25,7 @@
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTAnnotations.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libyul/optimiser/ASTWalker.h>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <list> #include <list>
@ -45,7 +46,7 @@ class NameAndTypeResolver;
* Resolves references to declarations (of variables and types) and also establishes the link * Resolves references to declarations (of variables and types) and also establishes the link
* between a return statement and the return parameter list. * between a return statement and the return parameter list.
*/ */
class ReferencesResolver: private ASTConstVisitor class ReferencesResolver: private ASTConstVisitor, private yul::ASTWalker
{ {
public: public:
ReferencesResolver( ReferencesResolver(
@ -64,6 +65,9 @@ public:
bool resolve(ASTNode const& _root); bool resolve(ASTNode const& _root);
private: private:
using yul::ASTWalker::visit;
using yul::ASTWalker::operator();
bool visit(Block const& _block) override; bool visit(Block const& _block) override;
void endVisit(Block const& _block) override; void endVisit(Block const& _block) override;
bool visit(ForStatement const& _for) override; bool visit(ForStatement const& _for) override;
@ -77,12 +81,16 @@ private:
void endVisit(ModifierDefinition const& _modifierDefinition) override; void endVisit(ModifierDefinition const& _modifierDefinition) override;
void endVisit(UserDefinedTypeName const& _typeName) override; void endVisit(UserDefinedTypeName const& _typeName) override;
void endVisit(FunctionTypeName const& _typeName) override; void endVisit(FunctionTypeName const& _typeName) override;
void endVisit(Mapping const& _typeName) override; void endVisit(Mapping const& _mapping) override;
void endVisit(ArrayTypeName const& _typeName) override; void endVisit(ArrayTypeName const& _typeName) override;
bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(Return const& _return) override; bool visit(Return const& _return) override;
void endVisit(VariableDeclaration const& _variable) override; void endVisit(VariableDeclaration const& _variable) override;
void operator()(yul::FunctionDefinition const& _function) override;
void operator()(yul::Identifier const& _identifier) override;
void operator()(yul::VariableDeclaration const& _varDecl) override;
/// Adds a new error to the list of errors. /// Adds a new error to the list of errors.
void typeError(langutil::SourceLocation const& _location, std::string const& _description); void typeError(langutil::SourceLocation const& _location, std::string const& _description);
@ -105,6 +113,9 @@ private:
std::vector<ParameterList const*> m_returnParameters; std::vector<ParameterList const*> m_returnParameters;
bool const m_resolveInsideCode; bool const m_resolveInsideCode;
bool m_errorOccurred = false; bool m_errorOccurred = false;
InlineAssemblyAnnotation* m_yulAnnotation = nullptr;
bool m_yulInsideFunction = false;
}; };
} }

View File

@ -68,7 +68,8 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
to_string(recommendedVersion.patch()) + to_string(recommendedVersion.patch()) +
string(";\""); string(";\"");
m_errorReporter.warning(_sourceUnit.location(), errorString); // when reporting the warning, print the source name only
m_errorReporter.warning({-1, -1, _sourceUnit.location().source}, errorString);
} }
m_sourceUnit = nullptr; m_sourceUnit = nullptr;
} }

View File

@ -2154,7 +2154,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
} }
default: default:
m_errorReporter.typeError(_functionCall.location(), "Type is not callable"); m_errorReporter.fatalTypeError(_functionCall.location(), "Type is not callable");
funcCallAnno.kind = FunctionCallKind::Unset; funcCallAnno.kind = FunctionCallKind::Unset;
funcCallAnno.isPure = argumentsArePure; funcCallAnno.isPure = argumentsArePure;
break; break;
@ -2527,7 +2527,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType)) else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
{ {
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType())) if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
{
annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.isLValue = annotation.referencedDeclaration->isLValue();
if (
auto const* functionType = dynamic_cast<FunctionType const*>(annotation.type);
functionType &&
functionType->kind() == FunctionType::Kind::Declaration
)
annotation.isPure = _memberAccess.expression().annotation().isPure;
}
} }
// TODO some members might be pure, but for example `address(0x123).balance` is not pure // TODO some members might be pure, but for example `address(0x123).balance` is not pure
@ -2535,6 +2543,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (auto tt = dynamic_cast<TypeType const*>(exprType)) if (auto tt = dynamic_cast<TypeType const*>(exprType))
if (tt->actualType()->category() == Type::Category::Enum) if (tt->actualType()->category() == Type::Category::Enum)
annotation.isPure = true; annotation.isPure = true;
if (
auto const* functionType = dynamic_cast<FunctionType const*>(exprType);
functionType &&
functionType->hasDeclaration() &&
dynamic_cast<FunctionDefinition const*>(&functionType->declaration()) &&
memberName == "selector"
)
if (auto const* parentAccess = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
{
annotation.isPure = parentAccess->expression().annotation().isPure;
if (auto const* exprInt = dynamic_cast<Identifier const*>(&parentAccess->expression()))
if (exprInt->name() == "this" || exprInt->name() == "super")
annotation.isPure = true;
}
if (auto magicType = dynamic_cast<MagicType const*>(exprType)) if (auto magicType = dynamic_cast<MagicType const*>(exprType))
{ {
if (magicType->kind() == MagicType::Kind::ABI) if (magicType->kind() == MagicType::Kind::ABI)
@ -2762,7 +2784,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
SecondarySourceLocation ssl; SecondarySourceLocation ssl;
for (Declaration const* declaration: annotation.overloadedDeclarations) for (Declaration const* declaration: annotation.overloadedDeclarations)
if (declaration->location().isEmpty()) if (!declaration->location().isValid())
{ {
// Try to re-construct function definition // Try to re-construct function definition
string description; string description;

View File

@ -88,8 +88,8 @@ public:
} }
/// @returns a copy of the vector containing only the nodes which derive from T. /// @returns a copy of the vector containing only the nodes which derive from T.
template <class _T> template <class T>
static std::vector<_T const*> filteredNodes(std::vector<ASTPointer<ASTNode>> const& _nodes); static std::vector<T const*> filteredNodes(std::vector<ASTPointer<ASTNode>> const& _nodes);
/// Returns the source code location of this node. /// Returns the source code location of this node.
SourceLocation const& location() const { return m_location; } SourceLocation const& location() const { return m_location; }
@ -121,12 +121,12 @@ private:
SourceLocation m_location; SourceLocation m_location;
}; };
template <class _T> template <class T>
std::vector<_T const*> ASTNode::filteredNodes(std::vector<ASTPointer<ASTNode>> const& _nodes) std::vector<T const*> ASTNode::filteredNodes(std::vector<ASTPointer<ASTNode>> const& _nodes)
{ {
std::vector<_T const*> ret; std::vector<T const*> ret;
for (auto const& n: _nodes) for (auto const& n: _nodes)
if (auto const* nt = dynamic_cast<_T const*>(n.get())) if (auto const* nt = dynamic_cast<T const*>(n.get()))
ret.push_back(nt); ret.push_back(nt);
return ret; return ret;
} }
@ -345,6 +345,30 @@ private:
std::vector<VariableDeclaration const*> m_localVariables; std::vector<VariableDeclaration const*> m_localVariables;
}; };
/**
* The doxygen-style, structured documentation class that represents an AST node.
*/
class StructuredDocumentation: public ASTNode
{
public:
StructuredDocumentation(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _text
): ASTNode(_id, _location), m_text(_text)
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
/// @return A shared pointer of an ASTString.
/// Contains doxygen-style, structured documentation that is parsed later on.
ASTPointer<ASTString> const& text() const { return m_text; }
private:
ASTPointer<ASTString> m_text;
};
/** /**
* Abstract class that is added to each AST node that can receive documentation. * Abstract class that is added to each AST node that can receive documentation.
*/ */
@ -362,6 +386,24 @@ protected:
ASTPointer<ASTString> m_documentation; ASTPointer<ASTString> m_documentation;
}; };
/**
* Abstract class that is added to each AST node that can receive a structured documentation.
*/
class StructurallyDocumented
{
public:
virtual ~StructurallyDocumented() = default;
explicit StructurallyDocumented(ASTPointer<StructuredDocumentation> const& _documentation): m_documentation(_documentation) {}
/// @return A shared pointer of a FormalDocumentation.
/// Can contain a nullptr in which case indicates absence of documentation
ASTPointer<StructuredDocumentation> const& documentation() const { return m_documentation; }
protected:
ASTPointer<StructuredDocumentation> m_documentation;
};
/** /**
* Abstract class that is added to AST nodes that can be marked as not being fully implemented * Abstract class that is added to AST nodes that can be marked as not being fully implemented
*/ */
@ -385,21 +427,21 @@ protected:
* document order. It first visits all struct declarations, then all variable declarations and * document order. It first visits all struct declarations, then all variable declarations and
* finally all function declarations. * finally all function declarations.
*/ */
class ContractDefinition: public Declaration, public Documented class ContractDefinition: public Declaration, public StructurallyDocumented
{ {
public: public:
ContractDefinition( ContractDefinition(
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation, ASTPointer<StructuredDocumentation> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<ASTNode>> const& _subNodes, std::vector<ASTPointer<ASTNode>> const& _subNodes,
ContractKind _contractKind = ContractKind::Contract, ContractKind _contractKind = ContractKind::Contract,
bool _abstract = false bool _abstract = false
): ):
Declaration(_id, _location, _name), Declaration(_id, _location, _name),
Documented(_documentation), StructurallyDocumented(_documentation),
m_baseContracts(_baseContracts), m_baseContracts(_baseContracts),
m_subNodes(_subNodes), m_subNodes(_subNodes),
m_contractKind(_contractKind), m_contractKind(_contractKind),
@ -681,7 +723,7 @@ protected:
std::vector<ASTPointer<UserDefinedTypeName>> m_overrides; std::vector<ASTPointer<UserDefinedTypeName>> m_overrides;
}; };
class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional class FunctionDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
{ {
public: public:
FunctionDefinition( FunctionDefinition(
@ -693,14 +735,14 @@ public:
Token _kind, Token _kind,
bool _isVirtual, bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides, ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<ASTString> const& _documentation, ASTPointer<StructuredDocumentation> const& _documentation,
ASTPointer<ParameterList> const& _parameters, ASTPointer<ParameterList> const& _parameters,
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers, std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
ASTPointer<ParameterList> const& _returnParameters, ASTPointer<ParameterList> const& _returnParameters,
ASTPointer<Block> const& _body ASTPointer<Block> const& _body
): ):
CallableDeclaration(_id, _location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), CallableDeclaration(_id, _location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters),
Documented(_documentation), StructurallyDocumented(_documentation),
ImplementationOptional(_body != nullptr), ImplementationOptional(_body != nullptr),
m_stateMutability(_stateMutability), m_stateMutability(_stateMutability),
m_kind(_kind), m_kind(_kind),
@ -755,7 +797,7 @@ public:
{ {
return return
CallableDeclaration::virtualSemantics() || CallableDeclaration::virtualSemantics() ||
annotation().contract->isInterface(); (annotation().contract && annotation().contract->isInterface());
} }
private: private:
StateMutability m_stateMutability; StateMutability m_stateMutability;
@ -870,21 +912,21 @@ private:
/** /**
* Definition of a function modifier. * Definition of a function modifier.
*/ */
class ModifierDefinition: public CallableDeclaration, public Documented class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented
{ {
public: public:
ModifierDefinition( ModifierDefinition(
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation, ASTPointer<StructuredDocumentation> const& _documentation,
ASTPointer<ParameterList> const& _parameters, ASTPointer<ParameterList> const& _parameters,
bool _isVirtual, bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides, ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<Block> const& _body ASTPointer<Block> const& _body
): ):
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
Documented(_documentation), StructurallyDocumented(_documentation),
m_body(_body) m_body(_body)
{ {
} }
@ -935,19 +977,19 @@ private:
/** /**
* Definition of a (loggable) event. * Definition of a (loggable) event.
*/ */
class EventDefinition: public CallableDeclaration, public Documented class EventDefinition: public CallableDeclaration, public StructurallyDocumented
{ {
public: public:
EventDefinition( EventDefinition(
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation, ASTPointer<StructuredDocumentation> const& _documentation,
ASTPointer<ParameterList> const& _parameters, ASTPointer<ParameterList> const& _parameters,
bool _anonymous = false bool _anonymous = false
): ):
CallableDeclaration(_id, _location, _name, Visibility::Default, _parameters), CallableDeclaration(_id, _location, _name, Visibility::Default, _parameters),
Documented(_documentation), StructurallyDocumented(_documentation),
m_anonymous(_anonymous) m_anonymous(_anonymous)
{ {
} }
@ -1110,18 +1152,18 @@ public:
Mapping( Mapping(
int64_t _id, int64_t _id,
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<ElementaryTypeName> const& _keyType, ASTPointer<TypeName> const& _keyType,
ASTPointer<TypeName> const& _valueType ASTPointer<TypeName> const& _valueType
): ):
TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {} TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
ElementaryTypeName const& keyType() const { return *m_keyType; } TypeName const& keyType() const { return *m_keyType; }
TypeName const& valueType() const { return *m_valueType; } TypeName const& valueType() const { return *m_valueType; }
private: private:
ASTPointer<ElementaryTypeName> m_keyType; ASTPointer<TypeName> m_keyType;
ASTPointer<TypeName> m_valueType; ASTPointer<TypeName> m_valueType;
}; };

View File

@ -56,9 +56,9 @@ struct DocTag
std::string paramName; ///< Only used for @param, stores the parameter name. std::string paramName; ///< Only used for @param, stores the parameter name.
}; };
struct DocumentedAnnotation struct StructurallyDocumentedAnnotation
{ {
virtual ~DocumentedAnnotation() = default; virtual ~StructurallyDocumentedAnnotation() = default;
/// Mapping docstring tag name -> content. /// Mapping docstring tag name -> content.
std::multimap<std::string, DocTag> docTags; std::multimap<std::string, DocTag> docTags;
}; };
@ -101,7 +101,7 @@ struct TypeDeclarationAnnotation: DeclarationAnnotation
std::string canonicalName; std::string canonicalName;
}; };
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
{ {
/// List of functions without a body. Can also contain functions from base classes. /// List of functions without a body. Can also contain functions from base classes.
std::vector<FunctionDefinition const*> unimplementedFunctions; std::vector<FunctionDefinition const*> unimplementedFunctions;
@ -122,15 +122,15 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation
std::set<CallableDeclaration const*> baseFunctions; std::set<CallableDeclaration const*> baseFunctions;
}; };
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
{ {
}; };
struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation struct EventDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
{ {
}; };
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
{ {
}; };
@ -142,7 +142,7 @@ struct VariableDeclarationAnnotation: DeclarationAnnotation
std::set<CallableDeclaration const*> baseFunctions; std::set<CallableDeclaration const*> baseFunctions;
}; };
struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation struct StatementAnnotation: ASTAnnotation
{ {
}; };

View File

@ -93,6 +93,7 @@ class PrimaryExpression;
class Identifier; class Identifier;
class ElementaryTypeNameExpression; class ElementaryTypeNameExpression;
class Literal; class Literal;
class StructuredDocumentation;
class VariableScope; class VariableScope;

View File

@ -268,7 +268,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
{ {
setJsonNode(_node, "ContractDefinition", { setJsonNode(_node, "ContractDefinition", {
make_pair("name", _node.name()), make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("contractKind", contractKind(_node.contractKind())), make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()), make_pair("abstract", _node.abstract()),
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
@ -349,7 +349,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
{ {
std::vector<pair<string, Json::Value>> attributes = { std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()), make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("kind", TokenTraits::toString(_node.kind())), make_pair("kind", TokenTraits::toString(_node.kind())),
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
@ -400,7 +400,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
{ {
std::vector<pair<string, Json::Value>> attributes = { std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()), make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("parameters", toJson(_node.parameterList())), make_pair("parameters", toJson(_node.parameterList())),
make_pair("virtual", _node.markedVirtual()), make_pair("virtual", _node.markedVirtual()),
@ -427,7 +427,7 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
m_inEvent = true; m_inEvent = true;
setJsonNode(_node, "EventDefinition", { setJsonNode(_node, "EventDefinition", {
make_pair("name", _node.name()), make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("parameters", toJson(_node.parameterList())), make_pair("parameters", toJson(_node.parameterList())),
make_pair("anonymous", _node.isAnonymous()) make_pair("anonymous", _node.isAnonymous())
}); });
@ -833,6 +833,17 @@ bool ASTJsonConverter::visit(Literal const& _node)
return false; return false;
} }
bool ASTJsonConverter::visit(StructuredDocumentation const& _node)
{
Json::Value text{*_node.text()};
std::vector<pair<string, Json::Value>> attributes = {
make_pair("text", text)
};
setJsonNode(_node, "StructuredDocumentation", std::move(attributes));
return false;
}
void ASTJsonConverter::endVisit(EventDefinition const&) void ASTJsonConverter::endVisit(EventDefinition const&)
{ {

View File

@ -119,6 +119,7 @@ public:
bool visit(Identifier const& _node) override; bool visit(Identifier const& _node) override;
bool visit(ElementaryTypeNameExpression const& _node) override; bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override; bool visit(Literal const& _node) override;
bool visit(StructuredDocumentation const& _node) override;
void endVisit(EventDefinition const&) override; void endVisit(EventDefinition const&) override;

View File

@ -208,6 +208,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
return createElementaryTypeNameExpression(_json); return createElementaryTypeNameExpression(_json);
if (nodeType == "Literal") if (nodeType == "Literal")
return createLiteral(_json); return createLiteral(_json);
if (nodeType == "StructuredDocumentation")
return createDocumentation(_json);
else else
astAssert(false, "Unknown type of ASTNode: " + nodeType); astAssert(false, "Unknown type of ASTNode: " + nodeType);
} }
@ -283,7 +285,7 @@ ASTPointer<ContractDefinition> ASTJsonImporter::createContractDefinition(Json::V
return createASTNode<ContractDefinition>( return createASTNode<ContractDefinition>(
_node, _node,
make_shared<ASTString>(_node["name"].asString()), make_shared<ASTString>(_node["name"].asString()),
nullOrASTString(_node, "documentation"), _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
baseContracts, baseContracts,
subNodes, subNodes,
contractKind(_node), contractKind(_node),
@ -397,7 +399,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
kind, kind,
memberAsBool(_node, "virtual"), memberAsBool(_node, "virtual"),
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
nullOrASTString(_node, "documentation"), _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
createParameterList(member(_node, "parameters")), createParameterList(member(_node, "parameters")),
modifiers, modifiers,
createParameterList(member(_node, "returnParameters")), createParameterList(member(_node, "returnParameters")),
@ -428,7 +430,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
return createASTNode<ModifierDefinition>( return createASTNode<ModifierDefinition>(
_node, _node,
memberAsASTString(_node, "name"), memberAsASTString(_node, "name"),
nullOrASTString(_node,"documentation"), _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
createParameterList(member(_node, "parameters")), createParameterList(member(_node, "parameters")),
memberAsBool(_node, "virtual"), memberAsBool(_node, "virtual"),
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
@ -453,7 +455,7 @@ ASTPointer<EventDefinition> ASTJsonImporter::createEventDefinition(Json::Value c
return createASTNode<EventDefinition>( return createASTNode<EventDefinition>(
_node, _node,
memberAsASTString(_node, "name"), memberAsASTString(_node, "name"),
nullOrASTString(_node, "documentation"), _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
createParameterList(member(_node, "parameters")), createParameterList(member(_node, "parameters")),
memberAsBool(_node, "anonymous") memberAsBool(_node, "anonymous")
); );
@ -509,7 +511,7 @@ ASTPointer<Mapping> ASTJsonImporter::createMapping(Json::Value const& _node)
{ {
return createASTNode<Mapping>( return createASTNode<Mapping>(
_node, _node,
createElementaryTypeName(member(_node, "keyType")), convertJsonToASTNode<TypeName>(member(_node, "keyType")),
convertJsonToASTNode<TypeName>(member(_node, "valueType")) convertJsonToASTNode<TypeName>(member(_node, "valueType"))
); );
} }
@ -842,6 +844,18 @@ ASTPointer<ASTNode> ASTJsonImporter::createLiteral(Json::Value const& _node)
); );
} }
ASTPointer<StructuredDocumentation> ASTJsonImporter::createDocumentation(Json::Value const& _node)
{
static string const textString = "text";
astAssert(member(_node, textString).isString(), "'text' must be a string");
return createASTNode<StructuredDocumentation>(
_node,
make_shared<ASTString>(_node[textString].asString())
);
}
// ===== helper functions ========== // ===== helper functions ==========
Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name) Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name)

View File

@ -119,6 +119,7 @@ private:
ASTPointer<Identifier> createIdentifier(Json::Value const& _node); ASTPointer<Identifier> createIdentifier(Json::Value const& _node);
ASTPointer<ElementaryTypeNameExpression> createElementaryTypeNameExpression(Json::Value const& _node); ASTPointer<ElementaryTypeNameExpression> createElementaryTypeNameExpression(Json::Value const& _node);
ASTPointer<ASTNode> createLiteral(Json::Value const& _node); ASTPointer<ASTNode> createLiteral(Json::Value const& _node);
ASTPointer<StructuredDocumentation> createDocumentation(Json::Value const& _node);
///@} ///@}
// =============== general helper functions =================== // =============== general helper functions ===================

View File

@ -92,6 +92,7 @@ public:
virtual bool visit(Identifier& _node) { return visitNode(_node); } virtual bool visit(Identifier& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); }
virtual bool visit(Literal& _node) { return visitNode(_node); } virtual bool visit(Literal& _node) { return visitNode(_node); }
virtual bool visit(StructuredDocumentation& _node) { return visitNode(_node); }
virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); } virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); }
virtual void endVisit(PragmaDirective& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective& _node) { endVisitNode(_node); }
@ -143,6 +144,7 @@ public:
virtual void endVisit(Identifier& _node) { endVisitNode(_node); } virtual void endVisit(Identifier& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); }
virtual void endVisit(Literal& _node) { endVisitNode(_node); } virtual void endVisit(Literal& _node) { endVisitNode(_node); }
virtual void endVisit(StructuredDocumentation& _node) { endVisitNode(_node); }
protected: protected:
/// Generic function called by default for each node, to be overridden by derived classes /// Generic function called by default for each node, to be overridden by derived classes
@ -207,6 +209,7 @@ public:
virtual bool visit(Identifier const& _node) { return visitNode(_node); } virtual bool visit(Identifier const& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); }
virtual bool visit(Literal const& _node) { return visitNode(_node); } virtual bool visit(Literal const& _node) { return visitNode(_node); }
virtual bool visit(StructuredDocumentation const& _node) { return visitNode(_node); }
virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); } virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); }
virtual void endVisit(PragmaDirective const& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective const& _node) { endVisitNode(_node); }
@ -258,6 +261,7 @@ public:
virtual void endVisit(Identifier const& _node) { endVisitNode(_node); } virtual void endVisit(Identifier const& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(Literal const& _node) { endVisitNode(_node); } virtual void endVisit(Literal const& _node) { endVisitNode(_node); }
virtual void endVisit(StructuredDocumentation const& _node) { endVisitNode(_node); }
protected: protected:
/// Generic function called by default for each node, to be overridden by derived classes /// Generic function called by default for each node, to be overridden by derived classes

View File

@ -67,10 +67,24 @@ void ImportDirective::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }
void StructuredDocumentation::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);
_visitor.endVisit(*this);
}
void StructuredDocumentation::accept(ASTConstVisitor& _visitor) const
{
_visitor.visit(*this);
_visitor.endVisit(*this);
}
void ContractDefinition::accept(ASTVisitor& _visitor) void ContractDefinition::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_documentation)
m_documentation->accept(_visitor);
listAccept(m_baseContracts, _visitor); listAccept(m_baseContracts, _visitor);
listAccept(m_subNodes, _visitor); listAccept(m_subNodes, _visitor);
} }
@ -81,6 +95,8 @@ void ContractDefinition::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_documentation)
m_documentation->accept(_visitor);
listAccept(m_baseContracts, _visitor); listAccept(m_baseContracts, _visitor);
listAccept(m_subNodes, _visitor); listAccept(m_subNodes, _visitor);
} }
@ -203,6 +219,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_documentation)
m_documentation->accept(_visitor);
if (m_overrides) if (m_overrides)
m_overrides->accept(_visitor); m_overrides->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
@ -219,6 +237,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_documentation)
m_documentation->accept(_visitor);
if (m_overrides) if (m_overrides)
m_overrides->accept(_visitor); m_overrides->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
@ -263,6 +283,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_documentation)
m_documentation->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
if (m_overrides) if (m_overrides)
m_overrides->accept(_visitor); m_overrides->accept(_visitor);
@ -275,6 +297,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_documentation)
m_documentation->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
if (m_overrides) if (m_overrides)
m_overrides->accept(_visitor); m_overrides->accept(_visitor);
@ -308,14 +332,22 @@ void ModifierInvocation::accept(ASTConstVisitor& _visitor) const
void EventDefinition::accept(ASTVisitor& _visitor) void EventDefinition::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{
if (m_documentation)
m_documentation->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
}
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }
void EventDefinition::accept(ASTConstVisitor& _visitor) const void EventDefinition::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{
if (m_documentation)
m_documentation->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
}
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }

View File

@ -54,9 +54,11 @@ T AsmJsonImporter::createAsmNode(Json::Value const& _node)
{ {
T r; T r;
r.location = createSourceLocation(_node); r.location = createSourceLocation(_node);
astAssert(!r.location.isEmpty() || !r.location.source, "Invalid source location in Asm AST"); astAssert(
r.location.source && 0 <= r.location.start && r.location.start <= r.location.end,
"Invalid source location in Asm AST"
);
return r; return r;
} }
Json::Value AsmJsonImporter::member(Json::Value const& _node, string const& _name) Json::Value AsmJsonImporter::member(Json::Value const& _node, string const& _name)

View File

@ -151,6 +151,8 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
void Type::clearCache() const void Type::clearCache() const
{ {
m_members.clear(); m_members.clear();
m_stackItems.reset();
m_stackSize.reset();
} }
void StorageOffsets::computeOffsets(TypePointers const& _types) void StorageOffsets::computeOffsets(TypePointers const& _types)
@ -1701,15 +1703,22 @@ u256 ArrayType::storageSize() const
return max<u256>(1, u256(size)); return max<u256>(1, u256(size));
} }
unsigned ArrayType::sizeOnStack() const vector<tuple<string, TypePointer>> ArrayType::makeStackItems() const
{ {
if (m_location == DataLocation::CallData) switch (m_location)
// offset [length] (stack top) {
return 1 + (isDynamicallySized() ? 1 : 0); case DataLocation::CallData:
else if (isDynamicallySized())
// storage slot or memory offset return {std::make_tuple("offset", TypeProvider::uint256()), std::make_tuple("length", TypeProvider::uint256())};
// byte offset inside storage value is omitted else
return 1; return {std::make_tuple("offset", TypeProvider::uint256())};
case DataLocation::Memory:
return {std::make_tuple("mpos", TypeProvider::uint256())};
case DataLocation::Storage:
// byte offset inside storage value is omitted
return {std::make_tuple("slot", TypeProvider::uint256())};
}
solAssert(false, "");
} }
string ArrayType::toString(bool _short) const string ArrayType::toString(bool _short) const
@ -1891,6 +1900,11 @@ string ArraySliceType::toString(bool _short) const
return m_arrayType.toString(_short) + " slice"; return m_arrayType.toString(_short) + " slice";
} }
std::vector<std::tuple<std::string, TypePointer>> ArraySliceType::makeStackItems() const
{
return {{"offset", TypeProvider::uint256()}, {"length", TypeProvider::uint256()}};
}
string ContractType::richIdentifier() const string ContractType::richIdentifier() const
{ {
return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id()); return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id());
@ -1989,6 +2003,14 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
return variablesAndOffsets; return variablesAndOffsets;
} }
vector<tuple<string, TypePointer>> ContractType::makeStackItems() const
{
if (m_super)
return {};
else
return {make_tuple("address", isPayable() ? TypeProvider::payableAddress() : TypeProvider::address())};
}
void StructType::clearCache() const void StructType::clearCache() const
{ {
Type::clearCache(); Type::clearCache();
@ -2422,12 +2444,17 @@ u256 TupleType::storageSize() const
solAssert(false, "Storage size of non-storable tuple type requested."); solAssert(false, "Storage size of non-storable tuple type requested.");
} }
unsigned TupleType::sizeOnStack() const vector<tuple<string, TypePointer>> TupleType::makeStackItems() const
{ {
unsigned size = 0; vector<tuple<string, TypePointer>> slots;
unsigned i = 1;
for (auto const& t: components()) for (auto const& t: components())
size += t ? t->sizeOnStack() : 0; {
return size; if (t)
slots.emplace_back("component_" + std::to_string(i), t);
++i;
}
return slots;
} }
TypePointer TupleType::mobileType() const TypePointer TupleType::mobileType() const
@ -2883,8 +2910,9 @@ unsigned FunctionType::storageBytes() const
solAssert(false, "Storage size of non-storable function type requested."); solAssert(false, "Storage size of non-storable function type requested.");
} }
unsigned FunctionType::sizeOnStack() const vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
{ {
vector<tuple<string, TypePointer>> slots;
Kind kind = m_kind; Kind kind = m_kind;
if (m_kind == Kind::SetGas || m_kind == Kind::SetValue) if (m_kind == Kind::SetGas || m_kind == Kind::SetValue)
{ {
@ -2892,39 +2920,42 @@ unsigned FunctionType::sizeOnStack() const
kind = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_kind; kind = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_kind;
} }
unsigned size = 0;
switch (kind) switch (kind)
{ {
case Kind::External: case Kind::External:
case Kind::DelegateCall: case Kind::DelegateCall:
size = 2; slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))};
break; break;
case Kind::BareCall: case Kind::BareCall:
case Kind::BareCallCode: case Kind::BareCallCode:
case Kind::BareDelegateCall: case Kind::BareDelegateCall:
case Kind::BareStaticCall: case Kind::BareStaticCall:
case Kind::Transfer:
case Kind::Send:
slots = {make_tuple("address", TypeProvider::address())};
break;
case Kind::Internal: case Kind::Internal:
slots = {make_tuple("functionIdentifier", TypeProvider::uint256())};
break;
case Kind::ArrayPush: case Kind::ArrayPush:
case Kind::ArrayPop: case Kind::ArrayPop:
case Kind::ByteArrayPush: case Kind::ByteArrayPush:
case Kind::Transfer: slots = {make_tuple("slot", TypeProvider::uint256())};
case Kind::Send:
size = 1;
break; break;
default: default:
break; break;
} }
if (m_gasSet) if (m_gasSet)
size++; slots.emplace_back("gas", TypeProvider::uint256());
if (m_valueSet) if (m_valueSet)
size++; slots.emplace_back("value", TypeProvider::uint256());
if (m_saltSet) if (m_saltSet)
size++; slots.emplace_back("salt", TypeProvider::uint256());
if (bound()) if (bound())
size += m_parameterTypes.front()->sizeOnStack(); for (auto const& [boundName, boundType]: m_parameterTypes.front()->stackItems())
return size; slots.emplace_back("self_" + boundName, boundType);
return slots;
} }
FunctionTypePointer FunctionType::interfaceFunctionType() const FunctionTypePointer FunctionType::interfaceFunctionType() const
@ -3323,13 +3354,13 @@ Type const* FunctionType::selfType() const
return m_parameterTypes.at(0); return m_parameterTypes.at(0);
} }
ASTPointer<ASTString> FunctionType::documentation() const ASTPointer<StructuredDocumentation> FunctionType::documentation() const
{ {
auto function = dynamic_cast<Documented const*>(m_declaration); auto function = dynamic_cast<StructurallyDocumented const*>(m_declaration);
if (function) if (function)
return function->documentation(); return function->documentation();
return ASTPointer<ASTString>(); return ASTPointer<StructuredDocumentation>();
} }
bool FunctionType::padArguments() const bool FunctionType::padArguments() const
@ -3418,12 +3449,12 @@ u256 TypeType::storageSize() const
solAssert(false, "Storage size of non-storable type type requested."); solAssert(false, "Storage size of non-storable type type requested.");
} }
unsigned TypeType::sizeOnStack() const vector<tuple<string, TypePointer>> TypeType::makeStackItems() const
{ {
if (auto contractType = dynamic_cast<ContractType const*>(m_actualType)) if (auto contractType = dynamic_cast<ContractType const*>(m_actualType))
if (contractType->contractDefinition().isLibrary()) if (contractType->contractDefinition().isLibrary())
return 1; return {make_tuple("address", TypeProvider::address())};
return 0; return {};
} }
MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _currentScope) const MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _currentScope) const

View File

@ -259,7 +259,33 @@ public:
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack, /// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// i.e. it behaves differently in lvalue context and in value context. /// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; } virtual bool isValueType() const { return false; }
virtual unsigned sizeOnStack() const { return 1; } /// @returns a list of named and typed stack items that determine the layout of this type on the stack.
/// A stack item either has an empty name and type ``nullptr`` referring to a single stack slot, or
/// has a non-empty name and a valid type referring to the stack layout of that type.
/// The complete layout of a type on the stack can be obtained from its stack items recursively as follows:
/// - Each unnamed stack item is untyped (its type is ``nullptr``) and contributes exactly one stack slot.
/// - Each named stack item is typed and contributes the stack slots given by the stack items of its type.
std::vector<std::tuple<std::string, TypePointer>> const& stackItems() const
{
if (!m_stackItems)
m_stackItems = makeStackItems();
return *m_stackItems;
}
/// Total number of stack slots occupied by this type. This is the sum of ``sizeOnStack`` of all ``stackItems()``.
unsigned sizeOnStack() const
{
if (!m_stackSize)
{
size_t sizeOnStack = 0;
for (auto const& slot: stackItems())
if (std::get<1>(slot))
sizeOnStack += std::get<1>(slot)->sizeOnStack();
else
++sizeOnStack;
m_stackSize = sizeOnStack;
}
return *m_stackSize;
}
/// If it is possible to initialize such a value in memory by just writing zeros /// If it is possible to initialize such a value in memory by just writing zeros
/// of the size memoryHeadSize(). /// of the size memoryHeadSize().
virtual bool hasSimpleZeroValueInMemory() const { return true; } virtual bool hasSimpleZeroValueInMemory() const { return true; }
@ -336,9 +362,18 @@ protected:
{ {
return MemberList::MemberMap(); return MemberList::MemberMap();
} }
/// Generates the stack items to be returned by ``stackItems()``. Defaults
/// to exactly one unnamed and untyped stack item referring to a single stack slot.
virtual std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const
{
return {std::make_tuple(std::string(), nullptr)};
}
/// List of member types (parameterised by scape), will be lazy-initialized. /// List of member types (parameterised by scape), will be lazy-initialized.
mutable std::map<ContractDefinition const*, std::unique_ptr<MemberList>> m_members; mutable std::map<ContractDefinition const*, std::unique_ptr<MemberList>> m_members;
mutable std::optional<std::vector<std::tuple<std::string, TypePointer>>> m_stackItems;
mutable std::optional<size_t> m_stackSize;
}; };
/** /**
@ -562,7 +597,6 @@ public:
bool canBeStored() const override { return false; } bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; } bool canLiveOutsideStorage() const override { return false; }
unsigned sizeOnStack() const override { return 0; }
std::string toString(bool) const override; std::string toString(bool) const override;
TypePointer mobileType() const override; TypePointer mobileType() const override;
@ -571,6 +605,8 @@ public:
std::string const& value() const { return m_value; } std::string const& value() const { return m_value; }
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private: private:
std::string m_value; std::string m_value;
}; };
@ -725,7 +761,6 @@ public:
bool isDynamicallyEncoded() const override; bool isDynamicallyEncoded() const override;
u256 storageSize() const override; u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
unsigned sizeOnStack() const override;
std::string toString(bool _short) const override; std::string toString(bool _short) const override;
std::string canonicalName() const override; std::string canonicalName() const override;
std::string signatureInExternalFunction(bool _structsByName) const override; std::string signatureInExternalFunction(bool _structsByName) const override;
@ -756,6 +791,8 @@ public:
void clearCache() const override; void clearCache() const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private: private:
/// String is interpreted as a subtype of Bytes. /// String is interpreted as a subtype of Bytes.
enum class ArrayKind { Ordinary, Bytes, String }; enum class ArrayKind { Ordinary, Bytes, String };
@ -785,7 +822,6 @@ public:
bool isDynamicallySized() const override { return true; } bool isDynamicallySized() const override { return true; }
bool isDynamicallyEncoded() const override { return true; } bool isDynamicallyEncoded() const override { return true; }
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); } bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
unsigned sizeOnStack() const override { return 2; }
std::string toString(bool _short) const override; std::string toString(bool _short) const override;
/// @returns true if this is valid to be stored in calldata /// @returns true if this is valid to be stored in calldata
@ -796,6 +832,8 @@ public:
std::unique_ptr<ReferenceType> copyForLocation(DataLocation, bool) const override { solAssert(false, ""); } std::unique_ptr<ReferenceType> copyForLocation(DataLocation, bool) const override { solAssert(false, ""); }
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private: private:
ArrayType const& m_arrayType; ArrayType const& m_arrayType;
}; };
@ -825,7 +863,6 @@ public:
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; } unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; } bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
bool canLiveOutsideStorage() const override { return !isSuper(); } bool canLiveOutsideStorage() const override { return !isSuper(); }
unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
bool isValueType() const override { return !isSuper(); } bool isValueType() const override { return !isSuper(); }
std::string toString(bool _short) const override; std::string toString(bool _short) const override;
std::string canonicalName() const override; std::string canonicalName() const override;
@ -856,7 +893,8 @@ public:
/// @returns a list of all state variables (including inherited) of the contract and their /// @returns a list of all state variables (including inherited) of the contract and their
/// offsets in storage. /// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const; std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private: private:
ContractDefinition const& m_contract; ContractDefinition const& m_contract;
/// If true, this is a special "super" type of m_contract containing only members that m_contract inherited /// If true, this is a special "super" type of m_contract containing only members that m_contract inherited
@ -989,7 +1027,6 @@ public:
bool canBeStored() const override { return false; } bool canBeStored() const override { return false; }
u256 storageSize() const override; u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; } bool canLiveOutsideStorage() const override { return false; }
unsigned sizeOnStack() const override;
bool hasSimpleZeroValueInMemory() const override { return false; } bool hasSimpleZeroValueInMemory() const override { return false; }
TypePointer mobileType() const override; TypePointer mobileType() const override;
/// Converts components to their temporary types and performs some wildcard matching. /// Converts components to their temporary types and performs some wildcard matching.
@ -997,6 +1034,8 @@ public:
std::vector<TypePointer> const& components() const { return m_components; } std::vector<TypePointer> const& components() const { return m_components; }
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private: private:
std::vector<TypePointer> const m_components; std::vector<TypePointer> const m_components;
}; };
@ -1158,7 +1197,6 @@ public:
unsigned storageBytes() const override; unsigned storageBytes() const override;
bool isValueType() const override { return true; } bool isValueType() const override { return true; }
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
unsigned sizeOnStack() const override;
bool hasSimpleZeroValueInMemory() const override { return false; } bool hasSimpleZeroValueInMemory() const override { return false; }
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
TypePointer encodingType() const override; TypePointer encodingType() const override;
@ -1208,9 +1246,9 @@ public:
/// Currently, this will only return true for internal functions like keccak and ecrecover. /// Currently, this will only return true for internal functions like keccak and ecrecover.
bool isPure() const; bool isPure() const;
bool isPayable() const { return m_stateMutability == StateMutability::Payable; } bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
/// @return A shared pointer of an ASTString. /// @return A shared pointer of StructuredDocumentation.
/// Can contain a nullptr in which case indicates absence of documentation /// Can contain a nullptr in which case indicates absence of documentation.
ASTPointer<ASTString> documentation() const; ASTPointer<StructuredDocumentation> documentation() const;
/// true iff arguments are to be padded to multiples of 32 bytes for external calls /// true iff arguments are to be padded to multiples of 32 bytes for external calls
/// The only functions that do not pad are hash functions, the low-level call functions /// The only functions that do not pad are hash functions, the low-level call functions
@ -1252,6 +1290,8 @@ public:
/// @param _bound if true, the function type is set to be bound. /// @param _bound if true, the function type is set to be bound.
FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const; FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private: private:
static TypePointers parseElementaryTypeVector(strings const& _types); static TypePointers parseElementaryTypeVector(strings const& _types);
@ -1321,12 +1361,13 @@ public:
bool canBeStored() const override { return false; } bool canBeStored() const override { return false; }
u256 storageSize() const override; u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; } bool canLiveOutsideStorage() const override { return false; }
unsigned sizeOnStack() const override;
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private: private:
TypePointer m_actualType; TypePointer m_actualType;
}; };
@ -1346,12 +1387,13 @@ public:
bool canBeStored() const override { return false; } bool canBeStored() const override { return false; }
u256 storageSize() const override; u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; } bool canLiveOutsideStorage() const override { return false; }
unsigned sizeOnStack() const override { return 0; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string richIdentifier() const override; std::string richIdentifier() const override;
bool operator==(Type const& _other) const override; bool operator==(Type const& _other) const override;
std::string toString(bool _short) const override; std::string toString(bool _short) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private: private:
TypePointers m_parameterTypes; TypePointers m_parameterTypes;
}; };
@ -1374,11 +1416,12 @@ public:
bool canBeStored() const override { return false; } bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return true; } bool canLiveOutsideStorage() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
unsigned sizeOnStack() const override { return 0; }
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
std::string toString(bool _short) const override; std::string toString(bool _short) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private: private:
SourceUnit const& m_sourceUnit; SourceUnit const& m_sourceUnit;
}; };
@ -1413,7 +1456,6 @@ public:
bool canBeStored() const override { return false; } bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return true; } bool canLiveOutsideStorage() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
unsigned sizeOnStack() const override { return 0; }
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
std::string toString(bool _short) const override; std::string toString(bool _short) const override;
@ -1422,6 +1464,8 @@ public:
TypePointer typeArgument() const; TypePointer typeArgument() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private: private:
Kind m_kind; Kind m_kind;
/// Contract type used for contract metadata magic. /// Contract type used for contract metadata magic.
@ -1445,7 +1489,6 @@ public:
bool canBeStored() const override { return false; } bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; } bool canLiveOutsideStorage() const override { return false; }
bool isValueType() const override { return true; } bool isValueType() const override { return true; }
unsigned sizeOnStack() const override { return 1; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string toString(bool) const override { return "inaccessible dynamic type"; } std::string toString(bool) const override { return "inaccessible dynamic type"; }
TypePointer decodingType() const override; TypePointer decodingType() const override;

View File

@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
Whiskers templ(R"( Whiskers templ(R"(
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> { function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) } if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString> }
<decodeElements> <decodeElements>
} }
)"); )");
templ("functionName", functionName); templ("functionName", functionName);
templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short"));
templ("minimumSize", to_string(headSize(decodingTypes))); templ("minimumSize", to_string(headSize(decodingTypes)));
string decodeElements; string decodeElements;
@ -211,7 +212,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
R"( R"(
{ {
let offset := <load>(add(headStart, <pos>)) let offset := <load>(add(headStart, <pos>))
if gt(offset, 0xffffffffffffffff) { revert(0, 0) } if gt(offset, 0xffffffffffffffff) { <revertString> }
<values> := <abiDecode>(add(headStart, offset), dataEnd) <values> := <abiDecode>(add(headStart, offset), dataEnd)
} }
)" : )" :
@ -222,6 +223,8 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
} }
)" )"
); );
// TODO add test
elementTempl("revertString", revertReasonIfDebug("ABI decoding: invalid tuple offset"));
elementTempl("load", _fromMemory ? "mload" : "calldataload"); elementTempl("load", _fromMemory ? "mload" : "calldataload");
elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
elementTempl("pos", to_string(headPos)); elementTempl("pos", to_string(headPos));
@ -453,12 +456,14 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
else else
templ("scaleLengthByStride", templ("scaleLengthByStride",
Whiskers(R"( Whiskers(R"(
if gt(length, <maxLength>) { revert(0, 0) } if gt(length, <maxLength>) { <revertString> }
length := mul(length, <stride>) length := mul(length, <stride>)
)") )")
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride())) ("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride())) ("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
("revertString", revertReasonIfDebug("ABI encoding: array data too long"))
.render() .render()
// TODO add revert test
); );
templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameFrom", _from.toString(true));
templ("readableTypeNameTo", _to.toString(true)); templ("readableTypeNameTo", _to.toString(true));
@ -1124,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
R"( R"(
// <readableTypeName> // <readableTypeName>
function <functionName>(offset, end) -> array { function <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } if iszero(slt(add(offset, 0x1f), end)) { <revertString> }
let length := <retrieveLength> let length := <retrieveLength>
array := <allocate>(<allocationSize>(length)) array := <allocate>(<allocationSize>(length))
let dst := array let dst := array
@ -1141,6 +1146,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
} }
)" )"
); );
// TODO add test
templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset"));
templ("functionName", functionName); templ("functionName", functionName);
templ("readableTypeName", _type.toString(true)); templ("readableTypeName", _type.toString(true));
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
@ -1159,7 +1166,12 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
} }
else else
{ {
templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }"); templ("staticBoundsCheck", "if gt(add(src, mul(length, " +
calldataStride +
")), end) { " +
revertReasonIfDebug("ABI decoding: invalid calldata array stride") +
" }"
);
templ("retrieveElementPos", "src"); templ("retrieveElementPos", "src");
} }
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
@ -1184,11 +1196,11 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
templ = R"( templ = R"(
// <readableTypeName> // <readableTypeName>
function <functionName>(offset, end) -> arrayPos, length { function <functionName>(offset, end) -> arrayPos, length {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
length := calldataload(offset) length := calldataload(offset)
if gt(length, 0xffffffffffffffff) { revert(0, 0) } if gt(length, 0xffffffffffffffff) { <revertStringLength> }
arrayPos := add(offset, 0x20) arrayPos := add(offset, 0x20)
if gt(add(arrayPos, mul(length, <stride>)), end) { revert(0, 0) } if gt(add(arrayPos, mul(length, <stride>)), end) { <revertStringPos> }
} }
)"; )";
else else
@ -1196,10 +1208,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
// <readableTypeName> // <readableTypeName>
function <functionName>(offset, end) -> arrayPos { function <functionName>(offset, end) -> arrayPos {
arrayPos := offset arrayPos := offset
if gt(add(arrayPos, mul(<length>, <stride>)), end) { revert(0, 0) } if gt(add(arrayPos, mul(<length>, <stride>)), end) { <revertStringPos> }
} }
)"; )";
Whiskers w{templ}; Whiskers w{templ};
// TODO add test
w("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid calldata array offset"));
w("revertStringLength", revertReasonIfDebug("ABI decoding: invalid calldata array length"));
w("revertStringPos", revertReasonIfDebug("ABI decoding: invalid calldata array stride"));
w("functionName", functionName); w("functionName", functionName);
w("readableTypeName", _type.toString(true)); w("readableTypeName", _type.toString(true));
w("stride", toCompactHexWithPrefix(_type.calldataStride())); w("stride", toCompactHexWithPrefix(_type.calldataStride()));
@ -1223,17 +1239,20 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
Whiskers templ( Whiskers templ(
R"( R"(
function <functionName>(offset, end) -> array { function <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
let length := <load>(offset) let length := <load>(offset)
array := <allocate>(<allocationSize>(length)) array := <allocate>(<allocationSize>(length))
mstore(array, length) mstore(array, length)
let src := add(offset, 0x20) let src := add(offset, 0x20)
let dst := add(array, 0x20) let dst := add(array, 0x20)
if gt(add(src, length), end) { revert(0, 0) } if gt(add(src, length), end) { <revertStringLength> }
<copyToMemFun>(src, dst, length) <copyToMemFun>(src, dst, length)
} }
)" )"
); );
// TODO add test
templ("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid byte array offset"));
templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length"));
templ("functionName", functionName); templ("functionName", functionName);
templ("load", _fromMemory ? "mload" : "calldataload"); templ("load", _fromMemory ? "mload" : "calldataload");
templ("allocate", m_utils.allocationFunction()); templ("allocate", m_utils.allocationFunction());
@ -1254,10 +1273,12 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
Whiskers w{R"( Whiskers w{R"(
// <readableTypeName> // <readableTypeName>
function <functionName>(offset, end) -> value { function <functionName>(offset, end) -> value {
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) } if slt(sub(end, offset), <minimumSize>) { <revertString> }
value := offset value := offset
} }
)"}; )"};
// TODO add test
w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short"));
w("functionName", functionName); w("functionName", functionName);
w("readableTypeName", _type.toString(true)); w("readableTypeName", _type.toString(true));
w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true))); w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true)));
@ -1277,7 +1298,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
Whiskers templ(R"( Whiskers templ(R"(
// <readableTypeName> // <readableTypeName>
function <functionName>(headStart, end) -> value { function <functionName>(headStart, end) -> value {
if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) } if slt(sub(end, headStart), <minimumSize>) { <revertString> }
value := <allocate>(<memorySize>) value := <allocate>(<memorySize>)
<#members> <#members>
{ {
@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
</members> </members>
} }
)"); )");
// TODO add test
templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short"));
templ("functionName", functionName); templ("functionName", functionName);
templ("readableTypeName", _type.toString(true)); templ("readableTypeName", _type.toString(true));
templ("allocate", m_utils.allocationFunction()); templ("allocate", m_utils.allocationFunction());
@ -1305,7 +1328,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
dynamic ? dynamic ?
R"( R"(
let offset := <load>(add(headStart, <pos>)) let offset := <load>(add(headStart, <pos>))
if gt(offset, 0xffffffffffffffff) { revert(0, 0) } if gt(offset, 0xffffffffffffffff) { <revertString> }
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end)) mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
)" : )" :
R"( R"(
@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end)) mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
)" )"
); );
// TODO add test
memberTempl("revertString", revertReasonIfDebug("ABI decoding: invalid struct offset"));
memberTempl("load", _fromMemory ? "mload" : "calldataload"); memberTempl("load", _fromMemory ? "mload" : "calldataload");
memberTempl("pos", to_string(headPos)); memberTempl("pos", to_string(headPos));
memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
@ -1380,7 +1405,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
Whiskers w(R"( Whiskers w(R"(
function <functionName>(base_ref, ptr) -> <return> { function <functionName>(base_ref, ptr) -> <return> {
let rel_offset_of_tail := calldataload(ptr) let rel_offset_of_tail := calldataload(ptr)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) } if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertStringOffset> }
value := add(rel_offset_of_tail, base_ref) value := add(rel_offset_of_tail, base_ref)
<handleLength> <handleLength>
} }
@ -1392,9 +1417,15 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
w("handleLength", Whiskers(R"( w("handleLength", Whiskers(R"(
length := calldataload(value) length := calldataload(value)
value := add(value, 0x20) value := add(value, 0x20)
if gt(length, 0xffffffffffffffff) { revert(0, 0) } if gt(length, 0xffffffffffffffff) { <revertStringLength> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) } if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { <revertStringStride> }
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render()); )")
("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride()))
// TODO add test
("revertStringLength", revertReasonIfDebug("Invalid calldata access length"))
// TODO add test
("revertStringStride", revertReasonIfDebug("Invalid calldata access stride"))
.render());
w("return", "value, length"); w("return", "value, length");
} }
else else
@ -1404,6 +1435,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
} }
w("neededLength", toCompactHexWithPrefix(tailSize)); w("neededLength", toCompactHexWithPrefix(tailSize));
w("functionName", functionName); w("functionName", functionName);
w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset"));
return w.render(); return w.render();
} }
else if (_type.isValueType()) else if (_type.isValueType())
@ -1493,3 +1525,8 @@ size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions cons
else else
return _type.sizeOnStack(); return _type.sizeOnStack();
} }
std::string ABIFunctions::revertReasonIfDebug(std::string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}

View File

@ -25,6 +25,8 @@
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h> #include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/codegen/YulUtilFunctions.h> #include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <functional> #include <functional>
@ -55,11 +57,13 @@ class ABIFunctions
public: public:
explicit ABIFunctions( explicit ABIFunctions(
langutil::EVMVersion _evmVersion, langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>() std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
): ):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector)), m_functionCollector(std::move(_functionCollector)),
m_utils(_evmVersion, m_functionCollector) m_utils(_evmVersion, m_revertStrings, m_functionCollector)
{} {}
/// @returns name of an assembly function to ABI-encode values of @a _givenTypes /// @returns name of an assembly function to ABI-encode values of @a _givenTypes
@ -200,7 +204,7 @@ private:
/// @param _fromMemory if decoding from memory instead of from calldata /// @param _fromMemory if decoding from memory instead of from calldata
/// @param _forUseOnStack if the decoded value is stored on stack or in memory. /// @param _forUseOnStack if the decoded value is stored on stack or in memory.
std::string abiDecodingFunction( std::string abiDecodingFunction(
Type const& _Type, Type const& _type,
bool _fromMemory, bool _fromMemory,
bool _forUseOnStack bool _forUseOnStack
); );
@ -249,7 +253,12 @@ private:
/// is true), for which it is two. /// is true), for which it is two.
static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options); static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options);
/// @returns code that stores @param _message for revert reason
/// if m_revertStrings is debug.
std::string revertReasonIfDebug(std::string const& _message = "");
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
RevertStrings const m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector; std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
std::set<std::string> m_externallyUsedFunctions; std::set<std::string> m_externallyUsedFunctions;
YulUtilFunctions m_utils; YulUtilFunctions m_utils;

View File

@ -35,7 +35,7 @@ void Compiler::compileContract(
bytes const& _metadata bytes const& _metadata
) )
{ {
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings); ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings);
runtimeCompiler.compileContract(_contract, _otherCompilers); runtimeCompiler.compileContract(_contract, _otherCompilers);
m_runtimeContext.appendAuxiliaryData(_metadata); m_runtimeContext.appendAuxiliaryData(_metadata);
@ -45,7 +45,7 @@ void Compiler::compileContract(
// The creation code will be executed at most once, so we modify the optimizer // The creation code will be executed at most once, so we modify the optimizer
// settings accordingly. // settings accordingly.
creationSettings.expectedExecutionsPerDeployment = 1; creationSettings.expectedExecutionsPerDeployment = 1;
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings); ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
m_context.optimise(m_optimiserSettings); m_context.optimise(m_optimiserSettings);

View File

@ -37,9 +37,8 @@ class Compiler
public: public:
Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings): Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings), m_runtimeContext(_evmVersion, _revertStrings),
m_runtimeContext(_evmVersion), m_context(_evmVersion, _revertStrings, &m_runtimeContext)
m_context(_evmVersion, &m_runtimeContext)
{ } { }
/// Compiles a contract. /// Compiles a contract.
@ -65,9 +64,9 @@ public:
return m_context.assemblyString(_sourceCodes); return m_context.assemblyString(_sourceCodes);
} }
/// @arg _sourceCodes is the map of input files to source code strings /// @arg _sourceCodes is the map of input files to source code strings
Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const Json::Value assemblyJSON(std::map<std::string, unsigned> const& _indices = std::map<std::string, unsigned>()) const
{ {
return m_context.assemblyJSON(_sourceCodes); return m_context.assemblyJSON(_indices);
} }
/// @returns Assembly items of the normal compiler context /// @returns Assembly items of the normal compiler context
evmasm::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); } evmasm::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); }
@ -80,7 +79,6 @@ public:
private: private:
OptimiserSettings const m_optimiserSettings; OptimiserSettings const m_optimiserSettings;
RevertStrings const m_revertStrings;
CompilerContext m_runtimeContext; CompilerContext m_runtimeContext;
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
CompilerContext m_context; CompilerContext m_context;

View File

@ -37,6 +37,8 @@
#include <libyul/Object.h> #include <libyul/Object.h>
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <libsolutil/Whiskers.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <liblangutil/Scanner.h> #include <liblangutil/Scanner.h>
#include <liblangutil/SourceReferenceFormatter.h> #include <liblangutil/SourceReferenceFormatter.h>
@ -55,6 +57,7 @@
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::util;
using namespace solidity::evmasm; using namespace solidity::evmasm;
using namespace solidity::frontend; using namespace solidity::frontend;
using namespace solidity::langutil; using namespace solidity::langutil;
@ -296,12 +299,13 @@ CompilerContext& CompilerContext::appendConditionalInvalid()
return *this; return *this;
} }
CompilerContext& CompilerContext::appendRevert() CompilerContext& CompilerContext::appendRevert(string const& _message)
{ {
return *this << u256(0) << u256(0) << Instruction::REVERT; appendInlineAssembly("{ " + revertReasonIfDebug(_message) + " }");
return *this;
} }
CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData) CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData, string const& _message)
{ {
if (_forwardReturnData && m_evmVersion.supportsReturndata()) if (_forwardReturnData && m_evmVersion.supportsReturndata())
appendInlineAssembly(R"({ appendInlineAssembly(R"({
@ -311,9 +315,7 @@ CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnDat
} }
})", {"condition"}); })", {"condition"});
else else
appendInlineAssembly(R"({ appendInlineAssembly("{ if condition { " + revertReasonIfDebug(_message) + " } }", {"condition"});
if condition { revert(0, 0) }
})", {"condition"});
*this << Instruction::POP; *this << Instruction::POP;
return *this; return *this;
} }
@ -385,9 +387,9 @@ void CompilerContext::appendInlineAssembly(
ErrorReporter errorReporter(errors); ErrorReporter errorReporter(errors);
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--")); auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--"));
yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion); yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion);
auto parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false); shared_ptr<yul::Block> parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false);
#ifdef SOL_OUTPUT_ASM #ifdef SOL_OUTPUT_ASM
cout << yul::AsmPrinter()(*parserResult) << endl; cout << yul::AsmPrinter(&dialect)(*parserResult) << endl;
#endif #endif
auto reportError = [&](string const& _context) auto reportError = [&](string const& _context)
@ -421,24 +423,18 @@ void CompilerContext::appendInlineAssembly(
// so we essentially only optimize the ABI functions. // so we essentially only optimize the ABI functions.
if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
{ {
bool const isCreation = m_runtimeContext != nullptr;
yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment);
yul::Object obj; yul::Object obj;
obj.code = parserResult; obj.code = parserResult;
obj.analysisInfo = make_shared<yul::AsmAnalysisInfo>(analysisInfo); obj.analysisInfo = make_shared<yul::AsmAnalysisInfo>(analysisInfo);
yul::OptimiserSuite::run(
dialect, optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers);
&meter,
obj,
_optimiserSettings.optimizeStackAllocation,
externallyUsedIdentifiers
);
analysisInfo = std::move(*obj.analysisInfo); analysisInfo = std::move(*obj.analysisInfo);
parserResult = std::move(obj.code); parserResult = std::move(obj.code);
#ifdef SOL_OUTPUT_ASM #ifdef SOL_OUTPUT_ASM
cout << "After optimizer:" << endl; cout << "After optimizer:" << endl;
cout << yul::AsmPrinter()(*parserResult) << endl; cout << yul::AsmPrinter(&dialect)(*parserResult) << endl;
#endif #endif
} }
@ -460,6 +456,29 @@ void CompilerContext::appendInlineAssembly(
updateSourceLocation(); updateSourceLocation();
} }
void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSettings, std::set<yul::YulString> const& _externalIdentifiers)
{
#ifdef SOL_OUTPUT_ASM
cout << yul::AsmPrinter(*dialect)(*_object.code) << endl;
#endif
bool const isCreation = runtimeContext() != nullptr;
yul::GasMeter meter(_dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment);
yul::OptimiserSuite::run(
_dialect,
&meter,
_object,
_optimiserSettings.optimizeStackAllocation,
_externalIdentifiers
);
#ifdef SOL_OUTPUT_ASM
cout << "After optimizer:" << endl;
cout << yul::AsmPrinter(*dialect)(*object.code) << endl;
#endif
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction( FunctionDefinition const& CompilerContext::resolveVirtualFunction(
FunctionDefinition const& _function, FunctionDefinition const& _function,
vector<ContractDefinition const*>::const_iterator _searchStart vector<ContractDefinition const*>::const_iterator _searchStart
@ -488,6 +507,11 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::superContract
return ++it; return ++it;
} }
string CompilerContext::revertReasonIfDebug(string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}
void CompilerContext::updateSourceLocation() void CompilerContext::updateSourceLocation()
{ {
m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location());

View File

@ -27,13 +27,18 @@
#include <libsolidity/ast/Types.h> #include <libsolidity/ast/Types.h>
#include <libsolidity/codegen/ABIFunctions.h> #include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/OptimiserSettings.h> #include <libsolidity/interface/OptimiserSettings.h>
#include <libevmasm/Assembly.h> #include <libevmasm/Assembly.h>
#include <libevmasm/Instruction.h> #include <libevmasm/Instruction.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libyul/AsmAnalysisInfo.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <functional> #include <functional>
#include <ostream> #include <ostream>
#include <stack> #include <stack>
@ -51,11 +56,16 @@ class Compiler;
class CompilerContext class CompilerContext
{ {
public: public:
explicit CompilerContext(langutil::EVMVersion _evmVersion, CompilerContext* _runtimeContext = nullptr): explicit CompilerContext(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
CompilerContext* _runtimeContext = nullptr
):
m_asm(std::make_shared<evmasm::Assembly>()), m_asm(std::make_shared<evmasm::Assembly>()),
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_runtimeContext(_runtimeContext), m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion) m_abiFunctions(m_evmVersion, m_revertStrings)
{ {
if (m_runtimeContext) if (m_runtimeContext)
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
@ -160,12 +170,14 @@ public:
/// Appends a conditional INVALID instruction /// Appends a conditional INVALID instruction
CompilerContext& appendConditionalInvalid(); CompilerContext& appendConditionalInvalid();
/// Appends a REVERT(0, 0) call /// Appends a REVERT(0, 0) call
CompilerContext& appendRevert(); /// @param _message is an optional revert message used in debug mode
CompilerContext& appendRevert(std::string const& _message = "");
/// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the /// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the
/// empty string. Consumes the condition. /// empty string. Consumes the condition.
/// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward /// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward
/// the data. /// the data.
CompilerContext& appendConditionalRevert(bool _forwardReturnData = false); /// @param _message is an optional revert message used in debug mode
CompilerContext& appendConditionalRevert(bool _forwardReturnData = false, std::string const& _message = "");
/// Appends a JUMP to a specific tag /// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo( CompilerContext& appendJumpTo(
evmasm::AssemblyItem const& _tag, evmasm::AssemblyItem const& _tag,
@ -219,6 +231,13 @@ public:
OptimiserSettings const& _optimiserSettings = OptimiserSettings::none() OptimiserSettings const& _optimiserSettings = OptimiserSettings::none()
); );
/// If m_revertStrings is debug, @returns inline assembly code that
/// stores @param _message in memory position 0 and reverts.
/// Otherwise returns "revert(0, 0)".
std::string revertReasonIfDebug(std::string const& _message = "");
void optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSetting, std::set<yul::YulString> const& _externalIdentifiers = {});
/// Appends arbitrary data to the end of the bytecode. /// Appends arbitrary data to the end of the bytecode.
void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); }
@ -243,9 +262,9 @@ public:
} }
/// @arg _sourceCodes is the map of input files to source code strings /// @arg _sourceCodes is the map of input files to source code strings
Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const Json::Value assemblyJSON(std::map<std::string, unsigned> const& _indicies = std::map<std::string, unsigned>()) const
{ {
return m_asm->assemblyJSON(_sourceCodes); return m_asm->assemblyJSON(_indicies);
} }
evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); } evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
@ -263,6 +282,8 @@ public:
void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; } void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; }
RevertStrings revertStrings() const { return m_revertStrings; }
private: private:
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
/// the first function definition that is overwritten by _function. /// the first function definition that is overwritten by _function.
@ -312,6 +333,7 @@ private:
evmasm::AssemblyPointer m_asm; evmasm::AssemblyPointer m_asm;
/// Version of the EVM to compile against. /// Version of the EVM to compile against.
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
RevertStrings const m_revertStrings;
/// Activated experimental features. /// Activated experimental features.
std::set<ExperimentalFeature> m_experimentalFeatures; std::set<ExperimentalFeature> m_experimentalFeatures;
/// Other already compiled contracts to be used in contract creation calls. /// Other already compiled contracts to be used in contract creation calls.

View File

@ -130,9 +130,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type)
// returns the absolute offset of the tail in "base_ref" // returns the absolute offset of the tail in "base_ref"
m_context.appendInlineAssembly(Whiskers(R"({ m_context.appendInlineAssembly(Whiskers(R"({
let rel_offset_of_tail := calldataload(ptr_to_tail) let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) } if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertString> }
base_ref := add(base_ref, rel_offset_of_tail) base_ref := add(base_ref, rel_offset_of_tail)
})")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"}); })")
("neededLength", toCompactHexWithPrefix(tailSize))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset"))
.render(), {"base_ref", "ptr_to_tail"});
// stack layout: <absolute_offset_of_tail> <garbage> // stack layout: <absolute_offset_of_tail> <garbage>
if (!_type.isDynamicallySized()) if (!_type.isDynamicallySized())
@ -158,9 +161,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type)
Whiskers(R"({ Whiskers(R"({
length := calldataload(base_ref) length := calldataload(base_ref)
base_ref := add(base_ref, 0x20) base_ref := add(base_ref, 0x20)
if gt(length, 0xffffffffffffffff) { revert(0, 0) } if gt(length, 0xffffffffffffffff) { <revertString> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) } if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(), })")
("calldataStride", toCompactHexWithPrefix(calldataStride))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
.render(),
{"base_ref", "length"} {"base_ref", "length"}
); );
// stack layout: <absolute_offset_of_tail> <length> // stack layout: <absolute_offset_of_tail> <length>
@ -277,7 +283,13 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
size_t encodedSize = 0; size_t encodedSize = 0;
for (auto const& t: _typeParameters) for (auto const& t: _typeParameters)
encodedSize += t->decodingType()->calldataHeadSize(); encodedSize += t->decodingType()->calldataHeadSize();
m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
Whiskers templ(R"({
if lt(len, <encodedSize>) { <revertString> }
})");
templ("encodedSize", to_string(encodedSize));
templ("revertString", m_context.revertReasonIfDebug("Calldata too short"));
m_context.appendInlineAssembly(templ.render(), {"len"});
m_context << Instruction::DUP2 << Instruction::ADD; m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
@ -319,19 +331,23 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
// Check that the data pointer is valid and that length times // Check that the data pointer is valid and that length times
// item size is still inside the range. // item size is still inside the range.
Whiskers templ(R"({ Whiskers templ(R"({
if gt(ptr, 0x100000000) { revert(0, 0) } if gt(ptr, 0x100000000) { <revertStringPointer> }
ptr := add(ptr, base_offset) ptr := add(ptr, base_offset)
let array_data_start := add(ptr, 0x20) let array_data_start := add(ptr, 0x20)
if gt(array_data_start, input_end) { revert(0, 0) } if gt(array_data_start, input_end) { <revertStringStart> }
let array_length := mload(ptr) let array_length := mload(ptr)
if or( if or(
gt(array_length, 0x100000000), gt(array_length, 0x100000000),
gt(add(array_data_start, mul(array_length, <item_size>)), input_end) gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
) { revert(0, 0) } ) { <revertStringLength> }
mstore(dst, array_length) mstore(dst, array_length)
dst := add(dst, 0x20) dst := add(dst, 0x20)
})"); })");
templ("item_size", to_string(arrayType.calldataStride())); templ("item_size", to_string(arrayType.calldataStride()));
// TODO add test
templ("revertStringPointer", m_context.revertReasonIfDebug("ABI memory decoding: invalid data pointer"));
templ("revertStringStart", m_context.revertReasonIfDebug("ABI memory decoding: invalid data start"));
templ("revertStringLength", m_context.revertReasonIfDebug("ABI memory decoding: invalid data length"));
m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr", "dst"}); m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr", "dst"});
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
@ -359,24 +375,33 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory); loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory);
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
// stack: input_end base_offset next_pointer data_offset // stack: input_end base_offset next_pointer data_offset
m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"}); m_context.appendInlineAssembly(Whiskers(R"({
if gt(data_offset, 0x100000000) { <revertString> }
})")
// TODO add test
("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data offset"))
.render(), {"data_offset"});
m_context << Instruction::DUP3 << Instruction::ADD; m_context << Instruction::DUP3 << Instruction::ADD;
// stack: input_end base_offset next_pointer array_head_ptr // stack: input_end base_offset next_pointer array_head_ptr
m_context.appendInlineAssembly( m_context.appendInlineAssembly(Whiskers(R"({
"{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }", if gt(add(array_head_ptr, 0x20), input_end) { <revertString> }
{"input_end", "base_offset", "next_ptr", "array_head_ptr"} })")
); ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid head pointer"))
.render(), {"input_end", "base_offset", "next_ptr", "array_head_ptr"});
// retrieve length // retrieve length
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true); loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true);
// stack: input_end base_offset next_pointer array_length data_pointer // stack: input_end base_offset next_pointer array_length data_pointer
m_context << Instruction::SWAP2; m_context << Instruction::SWAP2;
// stack: input_end base_offset data_pointer array_length next_pointer // stack: input_end base_offset data_pointer array_length next_pointer
m_context.appendInlineAssembly(R"({ m_context.appendInlineAssembly(Whiskers(R"({
if or( if or(
gt(array_length, 0x100000000), gt(array_length, 0x100000000),
gt(add(data_ptr, mul(array_length, )" + to_string(arrayType.calldataStride()) + R"()), input_end) gt(add(data_ptr, mul(array_length, )" + to_string(arrayType.calldataStride()) + R"()), input_end)
) { revert(0, 0) } ) { <revertString> }
})", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"}); })")
("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data pointer"))
.render(), {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
} }
else else
{ {
@ -605,7 +630,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
Whiskers templ(R"({ Whiskers templ(R"({
let size := mul(length, <element_size>) let size := mul(length, <element_size>)
// cheap way of zero-initializing a memory range // cheap way of zero-initializing a memory range
codecopy(memptr, codesize(), size) calldatacopy(memptr, calldatasize(), size)
memptr := add(memptr, size) memptr := add(memptr, size)
})"); })");
templ("element_size", to_string(_type.memoryStride())); templ("element_size", to_string(_type.memoryStride()));
@ -792,8 +817,7 @@ void CompilerUtils::convertType(
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
if (_asPartOfArgumentDecoding) if (_asPartOfArgumentDecoding)
// TODO: error message? m_context.appendConditionalRevert(false, "Enum out of range");
m_context.appendConditionalRevert();
else else
m_context.appendConditionalInvalid(); m_context.appendConditionalInvalid();
enumOverflowCheckPending = false; enumOverflowCheckPending = false;

View File

@ -24,6 +24,7 @@
#include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/ASTForward.h>
#include <libsolidity/ast/TypeProvider.h> #include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerContext.h>
@ -34,7 +35,8 @@ class Type; // forward
class CompilerUtils class CompilerUtils
{ {
public: public:
explicit CompilerUtils(CompilerContext& _context): m_context(_context) {} explicit CompilerUtils(CompilerContext& _context): m_context(_context)
{}
/// Stores the initial value of the free-memory-pointer at its position; /// Stores the initial value of the free-memory-pointer at its position;
void initialiseFreeMemoryPointer(); void initialiseFreeMemoryPointer();

View File

@ -20,6 +20,7 @@
* Solidity compiler. * Solidity compiler.
*/ */
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTUtils.h> #include <libsolidity/ast/ASTUtils.h>
#include <libsolidity/ast/TypeProvider.h> #include <libsolidity/ast/TypeProvider.h>
@ -27,7 +28,16 @@
#include <libsolidity/codegen/ContractCompiler.h> #include <libsolidity/codegen/ContractCompiler.h>
#include <libsolidity/codegen/ExpressionCompiler.h> #include <libsolidity/codegen/ExpressionCompiler.h>
#include <libyul/AsmAnalysisInfo.h>
#include <libyul/AsmAnalysis.h>
#include <libyul/AsmData.h>
#include <libyul/backends/evm/AsmCodeGen.h> #include <libyul/backends/evm/AsmCodeGen.h>
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/optimiser/Suite.h>
#include <libyul/Object.h>
#include <libyul/optimiser/ASTCopier.h>
#include <libyul/YulString.h>
#include <libevmasm/Instruction.h> #include <libevmasm/Instruction.h>
#include <libevmasm/Assembly.h> #include <libevmasm/Assembly.h>
@ -38,6 +48,7 @@
#include <libsolutil/Whiskers.h> #include <libsolutil/Whiskers.h>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <algorithm> #include <algorithm>
using namespace std; using namespace std;
@ -128,8 +139,7 @@ void ContractCompiler::appendCallValueCheck()
{ {
// Throw if function is not payable but call contained ether. // Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE; m_context << Instruction::CALLVALUE;
// TODO: error message? m_context.appendConditionalRevert(false, "Ether sent to non-payable function");
m_context.appendConditionalRevert();
} }
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
@ -409,7 +419,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << notFoundOrReceiveEther; m_context << notFoundOrReceiveEther;
if (!fallback && !etherReceiver) if (!fallback && !etherReceiver)
m_context.appendRevert(); m_context.appendRevert("Contract does not have fallback nor receive functions");
else else
{ {
if (etherReceiver) if (etherReceiver)
@ -440,8 +450,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << Instruction::STOP; m_context << Instruction::STOP;
} }
else else
// TODO: error message here? m_context.appendRevert("Unknown signature and no fallback defined");
m_context.appendRevert();
} }
@ -457,7 +466,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
// If the function is not a view function and is called without DELEGATECALL, // If the function is not a view function and is called without DELEGATECALL,
// we revert. // we revert.
m_context << dupInstruction(2); m_context << dupInstruction(2);
m_context.appendConditionalRevert(); m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL");
} }
m_context.setStackOffset(0); m_context.setStackOffset(0);
// We have to allow this for libraries, because value of the previous // We have to allow this for libraries, because value of the previous
@ -517,7 +526,7 @@ void ContractCompiler::initializeStateVariables(ContractDefinition const& _contr
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
for (VariableDeclaration const* variable: _contract.stateVariables()) for (VariableDeclaration const* variable: _contract.stateVariables())
if (variable->value() && !variable->isConstant()) if (variable->value() && !variable->isConstant())
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
} }
bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
@ -530,10 +539,10 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
m_continueTags.clear(); m_continueTags.clear();
if (_variableDeclaration.isConstant()) if (_variableDeclaration.isConstant())
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendConstStateVariableAccessor(_variableDeclaration); .appendConstStateVariableAccessor(_variableDeclaration);
else else
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
.appendStateVariableAccessor(_variableDeclaration); .appendStateVariableAccessor(_variableDeclaration);
return false; return false;
@ -790,10 +799,36 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
_assembly.appendInstruction(Instruction::POP); _assembly.appendInstruction(Instruction::POP);
} }
}; };
solAssert(_inlineAssembly.annotation().analysisInfo, "");
yul::Block const* code = &_inlineAssembly.operations();
yul::AsmAnalysisInfo* analysisInfo = _inlineAssembly.annotation().analysisInfo.get();
// Only used in the scope below, but required to live outside to keep the
// shared_ptr's alive
yul::Object object = {};
// The optimiser cannot handle external references
if (
m_optimiserSettings.runYulOptimiser &&
_inlineAssembly.annotation().externalReferences.empty()
)
{
yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&_inlineAssembly.dialect());
solAssert(dialect, "");
// Create a modifiable copy of the code and analysis
object.code = make_shared<yul::Block>(yul::ASTCopier().translate(*code));
object.analysisInfo = make_shared<yul::AsmAnalysisInfo>(yul::AsmAnalyzer::analyzeStrictAssertCorrect(*dialect, object));
m_context.optimizeYul(object, *dialect, m_optimiserSettings);
code = object.code.get();
analysisInfo = object.analysisInfo.get();
}
yul::CodeGenerator::assemble( yul::CodeGenerator::assemble(
_inlineAssembly.operations(), *code,
*_inlineAssembly.annotation().analysisInfo, *analysisInfo,
*m_context.assemblyPtr(), *m_context.assemblyPtr(),
m_context.evmVersion(), m_context.evmVersion(),
identifierAccess, identifierAccess,
@ -954,7 +989,8 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
revert(0, returndatasize()) revert(0, returndatasize())
})"); })");
else else
m_context.appendRevert(); // Since both returndata and revert are >=byzantium, this should be unreachable.
solAssert(false, "");
} }
m_context << endTag; m_context << endTag;
} }
@ -1316,7 +1352,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con
void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType)
{ {
ExpressionCompiler expressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals); ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals);
expressionCompiler.compile(_expression); expressionCompiler.compile(_expression);
if (_targetType) if (_targetType)
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);

View File

@ -43,11 +43,9 @@ public:
explicit ContractCompiler( explicit ContractCompiler(
ContractCompiler* _runtimeCompiler, ContractCompiler* _runtimeCompiler,
CompilerContext& _context, CompilerContext& _context,
OptimiserSettings _optimiserSettings, OptimiserSettings _optimiserSettings
RevertStrings _revertStrings
): ):
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeCompiler(_runtimeCompiler), m_runtimeCompiler(_runtimeCompiler),
m_context(_context) m_context(_context)
{ {
@ -140,7 +138,6 @@ private:
void storeStackHeight(ASTNode const* _node); void storeStackHeight(ASTNode const* _node);
OptimiserSettings const m_optimiserSettings; OptimiserSettings const m_optimiserSettings;
RevertStrings const m_revertStrings;
/// Pointer to the runtime compiler in case this is a creation compiler. /// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr; ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context; CompilerContext& m_context;

View File

@ -677,7 +677,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << errorCase; m_context << errorCase;
} }
else else
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true); m_context.appendConditionalRevert(true);
break; break;
} }
@ -734,8 +733,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Transfer) if (function.kind() == FunctionType::Kind::Transfer)
{ {
// Check if zero (out of stack or not enough balance). // Check if zero (out of stack or not enough balance).
// TODO: bubble up here, but might also be different error.
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
// Revert message bubbles up.
m_context.appendConditionalRevert(true); m_context.appendConditionalRevert(true);
} }
break; break;
@ -752,7 +751,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// function-sel(Error(string)) + encoding // function-sel(Error(string)) + encoding
solAssert(arguments.size() == 1, ""); solAssert(arguments.size() == 1, "");
solAssert(function.parameterTypes().size() == 1, ""); solAssert(function.parameterTypes().size() == 1, "");
if (m_revertStrings == RevertStrings::Strip) if (m_context.revertStrings() == RevertStrings::Strip)
{ {
if (!arguments.front()->annotation().isPure) if (!arguments.front()->annotation().isPure)
{ {
@ -1032,7 +1031,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false);
bool haveReasonString = arguments.size() > 1 && m_revertStrings != RevertStrings::Strip; bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip;
if (arguments.size() > 1) if (arguments.size() > 1)
{ {
@ -1041,7 +1040,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// function call. // function call.
solAssert(arguments.size() == 2, ""); solAssert(arguments.size() == 2, "");
solAssert(function.kind() == FunctionType::Kind::Require, ""); solAssert(function.kind() == FunctionType::Kind::Require, "");
if (m_revertStrings == RevertStrings::Strip) if (m_context.revertStrings() == RevertStrings::Strip)
{ {
if (!arguments.at(1)->annotation().isPure) if (!arguments.at(1)->annotation().isPure)
{ {
@ -1304,6 +1303,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
{ {
switch (funType->kind()) switch (funType->kind())
{ {
case FunctionType::Kind::Declaration:
break;
case FunctionType::Kind::Internal: case FunctionType::Kind::Internal:
// We do not visit the expression here on purpose, because in the case of an // We do not visit the expression here on purpose, because in the case of an
// internal library function call, this would push the library address forcing // internal library function call, this would push the library address forcing
@ -1821,12 +1822,16 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
m_context.appendInlineAssembly( m_context.appendInlineAssembly(
Whiskers(R"({ Whiskers(R"({
if gt(sliceStart, sliceEnd) { revert(0, 0) } if gt(sliceStart, sliceEnd) { <revertStringStartEnd> }
if gt(sliceEnd, length) { revert(0, 0) } if gt(sliceEnd, length) { <revertStringEndLength> }
offset := add(offset, mul(sliceStart, <stride>)) offset := add(offset, mul(sliceStart, <stride>))
length := sub(sliceEnd, sliceStart) length := sub(sliceEnd, sliceStart)
})")("stride", toString(arrayType->calldataStride())).render(), })")
("stride", toString(arrayType->calldataStride()))
("revertStringStartEnd", m_context.revertReasonIfDebug("Slice starts after end"))
("revertStringEndLength", m_context.revertReasonIfDebug("Slice is greater than length"))
.render(),
{"offset", "length", "sliceStart", "sliceEnd"} {"offset", "length", "sliceStart", "sliceEnd"}
); );
@ -2299,8 +2304,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall) if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall)
{ {
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
// TODO: error message? m_context.appendConditionalRevert(false, "Target contract does not contain code");
m_context.appendConditionalRevert();
existenceChecked = true; existenceChecked = true;
} }

View File

@ -58,10 +58,8 @@ class ExpressionCompiler: private ASTConstVisitor
public: public:
ExpressionCompiler( ExpressionCompiler(
CompilerContext& _compilerContext, CompilerContext& _compilerContext,
RevertStrings _revertStrings,
bool _optimiseOrderLiterals bool _optimiseOrderLiterals
): ):
m_revertStrings(_revertStrings),
m_optimiseOrderLiterals(_optimiseOrderLiterals), m_optimiseOrderLiterals(_optimiseOrderLiterals),
m_context(_compilerContext) m_context(_compilerContext)
{} {}
@ -127,8 +125,8 @@ private:
void setLValueToStorageItem(Expression const& _expression); void setLValueToStorageItem(Expression const& _expression);
/// Sets the current LValue to a new LValue constructed from the arguments. /// Sets the current LValue to a new LValue constructed from the arguments.
/// Also retrieves the value if it was not requested by @a _expression. /// Also retrieves the value if it was not requested by @a _expression.
template <class _LValueType, class... _Arguments> template <class LValueType, class... Arguments>
void setLValue(Expression const& _expression, _Arguments const&... _arguments); void setLValue(Expression const& _expression, Arguments const&... _arguments);
/// @returns true if the operator applied to the given type requires a cleanup prior to the /// @returns true if the operator applied to the given type requires a cleanup prior to the
/// operation. /// operation.
@ -139,18 +137,17 @@ private:
/// @returns the CompilerUtils object containing the current context. /// @returns the CompilerUtils object containing the current context.
CompilerUtils utils(); CompilerUtils utils();
RevertStrings m_revertStrings;
bool m_optimiseOrderLiterals; bool m_optimiseOrderLiterals;
CompilerContext& m_context; CompilerContext& m_context;
std::unique_ptr<LValue> m_currentLValue; std::unique_ptr<LValue> m_currentLValue;
}; };
template <class _LValueType, class... _Arguments> template <class LValueType, class... Arguments>
void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments const&... _arguments) void ExpressionCompiler::setLValue(Expression const& _expression, Arguments const&... _arguments)
{ {
solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one."); solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one.");
std::unique_ptr<_LValueType> lvalue = std::make_unique<_LValueType>(m_context, _arguments...); std::unique_ptr<LValueType> lvalue = std::make_unique<LValueType>(m_context, _arguments...);
if (_expression.annotation().lValueRequested) if (_expression.annotation().lValueRequested)
m_currentLValue = move(lvalue); m_currentLValue = move(lvalue);
else else

View File

@ -134,7 +134,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
FixedHash<hashHeaderSize>(keccak256("Error(string)")) FixedHash<hashHeaderSize>(keccak256("Error(string)"))
)) << (256 - hashHeaderSize * byteSize); )) << (256 - hashHeaderSize * byteSize);
string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector) string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector)
.tupleEncoder( .tupleEncoder(
{_messageType}, {_messageType},
{TypeProvider::stringMemory()} {TypeProvider::stringMemory()}
@ -908,6 +908,58 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
}); });
} }
string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
addr := add(base_ref, mul(index, <stride>))
<?dynamicallyEncodedBase>
addr<?dynamicallySizedBase>, len</dynamicallySizedBase> := <accessCalldataTail>(base_ref, addr)
</dynamicallyEncodedBase>
}
)")
("functionName", functionName)
("stride", to_string(_type.calldataStride()))
("dynamicallySized", _type.isDynamicallySized())
("dynamicallyEncodedBase", _type.baseType()->isDynamicallyEncoded())
("dynamicallySizedBase", _type.baseType()->isDynamicallySized())
("arrayLen", toCompactHexWithPrefix(_type.length()))
("accessCalldataTail", _type.baseType()->isDynamicallyEncoded() ? accessCalldataTailFunction(*_type.baseType()): "")
.render();
});
}
string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
{
solAssert(_type.isDynamicallyEncoded(), "");
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "access_calldata_tail_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
addr := add(base_ref, rel_offset_of_tail)
<?dynamicallySized>
length := calldataload(addr)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
addr := add(addr, 32)
</dynamicallySized>
}
)")
("functionName", functionName)
("dynamicallySized", _type.isDynamicallySized())
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
.render();
});
}
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{ {
solAssert(!_type.isByteArray(), ""); solAssert(!_type.isByteArray(), "");
@ -1627,7 +1679,7 @@ string YulUtilFunctions::packedHashFunction(
templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack));
templ("comma", sizeOnStack > 0 ? "," : ""); templ("comma", sizeOnStack > 0 ? "," : "");
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); templ("packedEncode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes));
return templ.render(); return templ.render();
}); });
} }
@ -1791,6 +1843,44 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
"_to_" + "_to_" +
_to.identifier(); _to.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
if (
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
)
{
size_t sourceStackSize = 0;
size_t destStackSize = 0;
std::string conversions;
for (size_t i = 0; i < fromTuple->components().size(); ++i)
{
auto fromComponent = fromTuple->components()[i];
auto toComponent = toTuple->components()[i];
solAssert(fromComponent, "");
if (toComponent)
{
conversions +=
suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) +
" := " +
conversionFunction(*fromComponent, *toComponent) +
"(" +
suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) +
")\n";
destStackSize += toComponent->sizeOnStack();
}
sourceStackSize += fromComponent->sizeOnStack();
}
return Whiskers(R"(
function <functionName>(<values>) -> <converted> {
<conversions>
}
)")
("functionName", functionName)
("values", suffixedVariableNameList("value", 0, sourceStackSize))
("converted", suffixedVariableNameList("converted", 0, destStackSize))
("conversions", conversions)
.render();
}
solUnimplementedAssert( solUnimplementedAssert(
_from.category() == Type::Category::StringLiteral, _from.category() == Type::Category::StringLiteral,
"Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented." "Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented."
@ -1894,7 +1984,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
function <functionName>(memPtr) -> value { function <functionName>(memPtr) -> value {
value := <load>(memPtr) value := <load>(memPtr)
<?needsValidation> <?needsValidation>
value := <validate>(value) <validate>(value)
</needsValidation> </needsValidation>
} }
)") )")
@ -1905,3 +1995,41 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
.render(); .render();
}); });
} }
string YulUtilFunctions::revertReasonIfDebug(RevertStrings revertStrings, string const& _message)
{
if (revertStrings >= RevertStrings::Debug && !_message.empty())
{
Whiskers templ(R"({
mstore(0, <sig>)
mstore(4, 0x20)
mstore(add(4, 0x20), <length>)
let reasonPos := add(4, 0x40)
<#word>
mstore(add(reasonPos, <offset>), <wordValue>)
</word>
revert(0, add(reasonPos, <end>))
})");
templ("sig", (u256(util::FixedHash<4>::Arith(util::FixedHash<4>(util::keccak256("Error(string)")))) << (256 - 32)).str());
templ("length", to_string(_message.length()));
size_t words = (_message.length() + 31) / 32;
vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = to_string(i * 32);
wordParams[i]["wordValue"] = formatAsStringOrNumber(_message.substr(32 * i, 32));
}
templ("word", wordParams);
templ("end", to_string(words * 32));
return templ.render();
}
else
return "revert(0, 0)";
}
string YulUtilFunctions::revertReasonIfDebug(string const& _message)
{
return revertReasonIfDebug(m_revertStrings, _message);
}

View File

@ -24,6 +24,8 @@
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h> #include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/interface/DebugSettings.h>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -44,9 +46,11 @@ class YulUtilFunctions
public: public:
explicit YulUtilFunctions( explicit YulUtilFunctions(
langutil::EVMVersion _evmVersion, langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
): ):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector)) m_functionCollector(std::move(_functionCollector))
{} {}
@ -168,9 +172,14 @@ public:
/// @returns the name of a function that returns the calldata address for the /// @returns the name of a function that returns the calldata address for the
/// given array base ref and index. /// given array base ref and index.
/// signature: (baseRef, index) -> address /// signature: (baseRef, index) -> offset[, length]
std::string calldataArrayIndexAccessFunction(ArrayType const& _type); std::string calldataArrayIndexAccessFunction(ArrayType const& _type);
/// @returns the name of a function that follows a calldata tail while performing
/// bounds checks.
/// signature: (baseRef, tailPointer) -> offset[, length]
std::string accessCalldataTailFunction(Type const& _type);
/// @returns the name of a function that advances an array data pointer to the next element. /// @returns the name of a function that advances an array data pointer to the next element.
/// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots. /// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots.
std::string nextArrayElementFunction(ArrayType const& _type); std::string nextArrayElementFunction(ArrayType const& _type);
@ -281,6 +290,13 @@ public:
/// zero /// zero
/// signature: (slot, offset) -> /// signature: (slot, offset) ->
std::string storageSetToZeroFunction(Type const& _type); std::string storageSetToZeroFunction(Type const& _type);
/// If revertStrings is debug, @returns inline assembly code that
/// stores @param _message in memory position 0 and reverts.
/// Otherwise returns "revert(0, 0)".
static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = "");
std::string revertReasonIfDebug(std::string const& _message = "");
private: private:
/// Special case of conversionFunction - handles everything that does not /// Special case of conversionFunction - handles everything that does not
/// use exactly one variable to hold the value. /// use exactly one variable to hold the value.
@ -289,6 +305,7 @@ private:
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata); std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector; std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
}; };

View File

@ -31,23 +31,22 @@ using namespace solidity;
using namespace solidity::util; using namespace solidity::util;
using namespace solidity::frontend; using namespace solidity::frontend;
string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl) IRVariable const& IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
{ {
solUnimplementedAssert( auto const& [it, didInsert] = m_localVariables.emplace(
_varDecl.annotation().type->sizeOnStack() == 1, std::make_pair(&_varDecl, IRVariable{_varDecl})
"Multi-slot types not yet implemented."
); );
solAssert(didInsert, "Local variable added multiple times.");
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id()); return it->second;
} }
string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl) IRVariable const& IRGenerationContext::localVariable(VariableDeclaration const& _varDecl)
{ {
solAssert( solAssert(
m_localVariables.count(&_varDecl), m_localVariables.count(&_varDecl),
"Unknown variable: " + _varDecl.name() "Unknown variable: " + _varDecl.name()
); );
return m_localVariables[&_varDecl]; return m_localVariables.at(&_varDecl);
} }
void IRGenerationContext::addStateVariable( void IRGenerationContext::addStateVariable(
@ -98,23 +97,6 @@ string IRGenerationContext::newYulVariable()
return "_" + to_string(++m_varCounter); return "_" + to_string(++m_varCounter);
} }
string IRGenerationContext::variable(Expression const& _expression)
{
unsigned size = _expression.annotation().type->sizeOnStack();
string var = "expr_" + to_string(_expression.id());
if (size == 1)
return var;
else
return suffixedVariableNameList(move(var) + "_", 1, 1 + size);
}
string IRGenerationContext::variablePart(Expression const& _expression, string const& _part)
{
size_t numVars = _expression.annotation().type->sizeOnStack();
solAssert(numVars > 1, "");
return "expr_" + to_string(_expression.id()) + "_" + _part;
}
string IRGenerationContext::internalDispatch(size_t _in, size_t _out) string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{ {
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out); string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
@ -133,7 +115,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
)"); )");
templ("functionName", funName); templ("functionName", funName);
templ("comma", _in > 0 ? "," : ""); templ("comma", _in > 0 ? "," : "");
YulUtilFunctions utils(m_evmVersion, m_functions); YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
templ("in", suffixedVariableNameList("in_", 0, _in)); templ("in", suffixedVariableNameList("in_", 0, _in));
templ("arrow", _out > 0 ? "->" : ""); templ("arrow", _out > 0 ? "->" : "");
templ("out", suffixedVariableNameList("out_", 0, _out)); templ("out", suffixedVariableNameList("out_", 0, _out));
@ -161,5 +143,10 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
YulUtilFunctions IRGenerationContext::utils() YulUtilFunctions IRGenerationContext::utils()
{ {
return YulUtilFunctions(m_evmVersion, m_functions); return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
}
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
} }

View File

@ -20,7 +20,9 @@
#pragma once #pragma once
#include <libsolidity/codegen/ir/IRVariable.h>
#include <libsolidity/interface/OptimiserSettings.h> #include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h> #include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
@ -47,8 +49,13 @@ class YulUtilFunctions;
class IRGenerationContext class IRGenerationContext
{ {
public: public:
IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): IRGenerationContext(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
OptimiserSettings _optimiserSettings
):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings)),
m_functions(std::make_shared<MultiUseYulFunctionCollector>()) m_functions(std::make_shared<MultiUseYulFunctionCollector>())
{} {}
@ -62,9 +69,9 @@ public:
} }
std::string addLocalVariable(VariableDeclaration const& _varDecl); IRVariable const& addLocalVariable(VariableDeclaration const& _varDecl);
bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); } bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); }
std::string localVariableName(VariableDeclaration const& _varDecl); IRVariable const& localVariable(VariableDeclaration const& _varDecl);
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
@ -79,11 +86,6 @@ public:
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration); std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
std::string newYulVariable(); std::string newYulVariable();
/// @returns the variable (or comma-separated list of variables) that contain
/// the value of the given expression.
std::string variable(Expression const& _expression);
/// @returns the sub-variable of a multi-variable expression.
std::string variablePart(Expression const& _expression, std::string const& _part);
std::string internalDispatch(size_t _in, size_t _out); std::string internalDispatch(size_t _in, size_t _out);
@ -92,11 +94,18 @@ public:
langutil::EVMVersion evmVersion() const { return m_evmVersion; }; langutil::EVMVersion evmVersion() const { return m_evmVersion; };
/// @returns code that stores @param _message for revert reason
/// if m_revertStrings is debug.
std::string revertReasonIfDebug(std::string const& _message = "");
RevertStrings revertStrings() const { return m_revertStrings; }
private: private:
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
OptimiserSettings m_optimiserSettings; OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy; std::vector<ContractDefinition const*> m_inheritanceHierarchy;
std::map<VariableDeclaration const*, std::string> m_localVariables; std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Storage offsets of state variables /// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions; std::shared_ptr<MultiUseYulFunctionCollector> m_functions;

View File

@ -139,11 +139,11 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
t("functionName", functionName); t("functionName", functionName);
string params; string params;
for (auto const& varDecl: _function.parameters()) for (auto const& varDecl: _function.parameters())
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params); t("params", params);
string retParams; string retParams;
for (auto const& varDecl: _function.returnParameters()) for (auto const& varDecl: _function.returnParameters())
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("returns", retParams.empty() ? "" : " -> " + retParams); t("returns", retParams.empty() ? "" : " -> " + retParams);
t("body", generate(_function.body())); t("body", generate(_function.body()));
return t.render(); return t.render();
@ -159,21 +159,70 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
solAssert(!_varDecl.isConstant(), ""); solAssert(!_varDecl.isConstant(), "");
solAssert(_varDecl.isStateVariable(), ""); solAssert(_varDecl.isStateVariable(), "");
solUnimplementedAssert(type->isValueType(), ""); if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
return m_context.functionCollector()->createFunction(functionName, [&]() {
return m_context.functionCollector()->createFunction(functionName, [&]() { pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl); solAssert(slot_offset.second == 0, "");
FunctionType funType(_varDecl);
return Whiskers(R"( solUnimplementedAssert(funType.returnParameterTypes().size() == 1, "");
function <functionName>() -> rval { TypePointer returnType = funType.returnParameterTypes().front();
rval := <readStorage>(<slot>) unsigned num_keys = 0;
stringstream indexAccesses;
string slot = m_context.newYulVariable();
do
{
solUnimplementedAssert(
mappingType->keyType()->sizeOnStack() == 1,
"Multi-slot mapping key unimplemented - might not be a problem"
);
indexAccesses <<
slot <<
" := " <<
m_utils.mappingIndexAccessFunction(*mappingType, *mappingType->keyType()) <<
"(" <<
slot;
if (mappingType->keyType()->sizeOnStack() > 0)
indexAccesses <<
", " <<
suffixedVariableNameList("key", num_keys, num_keys + mappingType->keyType()->sizeOnStack());
indexAccesses << ")\n";
num_keys += mappingType->keyType()->sizeOnStack();
} }
)") while ((mappingType = dynamic_cast<MappingType const*>(mappingType->valueType())));
("functionName", functionName)
("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) return Whiskers(R"(
("slot", slot_offset.first.str()) function <functionName>(<keys>) -> rval {
.render(); let <slot> := <base>
}); <indexAccesses>
rval := <readStorage>(<slot>)
}
)")
("functionName", functionName)
("keys", suffixedVariableNameList("key", 0, num_keys))
("readStorage", m_utils.readFromStorage(*returnType, 0, false))
("indexAccesses", indexAccesses.str())
("slot", slot)
("base", slot_offset.first.str())
.render();
});
else
{
solUnimplementedAssert(type->isValueType(), "");
return m_context.functionCollector()->createFunction(functionName, [&]() {
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
return Whiskers(R"(
function <functionName>() -> rval {
rval := <readStorage>(<slot>)
}
)")
("functionName", functionName)
("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false))
("slot", slot_offset.first.str())
.render();
});
}
} }
string IRGenerator::constructorCode(ContractDefinition const& _contract) string IRGenerator::constructorCode(ContractDefinition const& _contract)
@ -284,7 +333,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := "; templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ";
templ["assignToRetParams"] = retVars == 0 ? "" : "let " + suffixedVariableNameList("ret_", 0, retVars) + " := "; templ["assignToRetParams"] = retVars == 0 ? "" : "let " + suffixedVariableNameList("ret_", 0, retVars) + " := ";
ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector()); ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes()); templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes());
templ["params"] = suffixedVariableNameList("param_", 0, paramVars); templ["params"] = suffixedVariableNameList("param_", 0, paramVars);
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0); templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
@ -337,8 +386,8 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
m_context.functionCollector()->requestedFunctions().empty(), m_context.functionCollector()->requestedFunctions().empty(),
"Reset context while it still had functions." "Reset context while it still had functions."
); );
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
for (auto const& var: ContractType(_contract).stateVariables()) for (auto const& var: ContractType(_contract).stateVariables())

View File

@ -37,11 +37,15 @@ class SourceUnit;
class IRGenerator class IRGenerator
{ {
public: public:
IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): IRGenerator(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
OptimiserSettings _optimiserSettings
):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_optimiserSettings(_optimiserSettings), m_optimiserSettings(_optimiserSettings),
m_context(_evmVersion, std::move(_optimiserSettings)), m_context(_evmVersion, _revertStrings, std::move(_optimiserSettings)),
m_utils(_evmVersion, m_context.functionCollector()) m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector())
{} {}
/// Generates and returns the IR code, in unoptimized and optimized form /// Generates and returns the IR code, in unoptimized and optimized form

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/ir/IRLValue.h> #include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/ir/IRVariable.h>
namespace solidity::frontend namespace solidity::frontend
{ {
@ -47,6 +48,7 @@ public:
void initializeStateVar(VariableDeclaration const& _varDecl); void initializeStateVar(VariableDeclaration const& _varDecl);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override; void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
bool visit(Assignment const& _assignment) override; bool visit(Assignment const& _assignment) override;
bool visit(TupleExpression const& _tuple) override; bool visit(TupleExpression const& _tuple) override;
bool visit(IfStatement const& _ifStatement) override; bool visit(IfStatement const& _ifStatement) override;
@ -73,14 +75,29 @@ private:
std::vector<ASTPointer<Expression const>> const& _arguments std::vector<ASTPointer<Expression const>> const& _arguments
); );
std::string fetchFreeMem() const; /// @returns code that evaluates to the first unused memory slot (which does not have to
/// be empty).
static std::string freeMemory();
/// Generates the required conversion code and @returns an IRVariable referring to the value of @a _variable
/// converted to type @a _to.
IRVariable convert(IRVariable const& _variable, Type const& _to);
/// @returns a Yul expression representing the current value of @a _expression, /// @returns a Yul expression representing the current value of @a _expression,
/// converted to type @a _to if it does not yet have that type. /// converted to type @a _to if it does not yet have that type.
std::string expressionAsType(Expression const& _expression, Type const& _to); std::string expressionAsType(Expression const& _expression, Type const& _to);
std::ostream& defineExpression(Expression const& _expression);
/// Defines only one of many variables corresponding to an expression. /// @returns an output stream that can be used to define @a _var using a function call or
std::ostream& defineExpressionPart(Expression const& _expression, std::string const& _part); /// single stack slot expression.
std::ostream& define(IRVariable const& _var);
/// Defines @a _var using the value of @a _value while performing type conversions, if required.
void define(IRVariable const& _var, IRVariable const& _value) { declareAssign(_var, _value, true); }
/// Assigns @a _var to the value of @a _value while performing type conversions, if required.
void assign(IRVariable const& _var, IRVariable const& _value) { declareAssign(_var, _value, false); }
/// Declares variable @a _var.
void declare(IRVariable const& _var);
void declareAssign(IRVariable const& _var, IRVariable const& _value, bool _define);
void appendAndOrOperatorCode(BinaryOperation const& _binOp); void appendAndOrOperatorCode(BinaryOperation const& _binOp);
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr); void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);
@ -93,7 +110,14 @@ private:
std::string const& _right std::string const& _right
); );
void setLValue(Expression const& _expression, std::unique_ptr<IRLValue> _lvalue); /// Assigns the value of @a _value to the lvalue @a _lvalue.
void writeToLValue(IRLValue const& _lvalue, IRVariable const& _value);
/// @returns a fresh IR variable containing the value of the lvalue @a _lvalue.
IRVariable readFromLValue(IRLValue const& _lvalue);
/// Stores the given @a _lvalue in m_currentLValue, if it will be written to (lValueRequested). Otherwise
/// defines the expression @a _expression by reading the value from @a _lvalue.
void setLValue(Expression const& _expression, IRLValue _lvalue);
void generateLoop( void generateLoop(
Statement const& _body, Statement const& _body,
Expression const* _conditionExpression, Expression const* _conditionExpression,
@ -107,7 +131,7 @@ private:
std::ostringstream m_code; std::ostringstream m_code;
IRGenerationContext& m_context; IRGenerationContext& m_context;
YulUtilFunctions& m_utils; YulUtilFunctions& m_utils;
std::unique_ptr<IRLValue> m_currentLValue; std::optional<IRLValue> m_currentLValue;
}; };
} }

View File

@ -1,228 +0,0 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Generator for code that handles LValues.
*/
#include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libsolutil/Whiskers.h>
using namespace std;
using namespace solidity;
using namespace solidity::frontend;
IRLocalVariable::IRLocalVariable(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRLValue(_context.utils(), _varDecl.annotation().type),
m_variableName(_context.localVariableName(_varDecl))
{
}
string IRLocalVariable::storeValue(string const& _value, Type const& _type) const
{
solAssert(_type == *m_type, "Storing different types - not necessarily a problem.");
return m_variableName + " := " + _value + "\n";
}
string IRLocalVariable::setToZero() const
{
return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type);
}
IRStorageItem::IRStorageItem(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRStorageItem(
_context.utils(),
*_varDecl.annotation().type,
_context.storageLocationOfVariable(_varDecl)
)
{ }
IRStorageItem::IRStorageItem(
YulUtilFunctions _utils,
Type const& _type,
std::pair<u256, unsigned> slot_offset
):
IRLValue(std::move(_utils), &_type),
m_slot(util::toCompactHexWithPrefix(slot_offset.first)),
m_offset(slot_offset.second)
{
}
IRStorageItem::IRStorageItem(
YulUtilFunctions _utils,
string _slot,
boost::variant<string, unsigned> _offset,
Type const& _type
):
IRLValue(std::move(_utils), &_type),
m_slot(std::move(_slot)),
m_offset(std::move(_offset))
{
solAssert(!m_offset.empty(), "");
solAssert(!m_slot.empty(), "");
}
string IRStorageItem::retrieveValue() const
{
if (!m_type->isValueType())
return m_slot;
solUnimplementedAssert(m_type->category() != Type::Category::Function, "");
if (m_offset.type() == typeid(string))
return
m_utils.readFromStorageDynamic(*m_type, false) +
"(" +
m_slot +
", " +
boost::get<string>(m_offset) +
")";
else if (m_offset.type() == typeid(unsigned))
return
m_utils.readFromStorage(*m_type, boost::get<unsigned>(m_offset), false) +
"(" +
m_slot +
")";
solAssert(false, "");
}
string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const
{
if (m_type->isValueType())
solAssert(_sourceType == *m_type, "Different type, but might not be an error.");
std::optional<unsigned> offset;
if (m_offset.type() == typeid(unsigned))
offset = get<unsigned>(m_offset);
return
m_utils.updateStorageValueFunction(*m_type, offset) +
"(" +
m_slot +
(m_offset.type() == typeid(string) ?
(", " + get<string>(m_offset)) :
""
) +
", " +
_value +
")\n";
}
string IRStorageItem::setToZero() const
{
return
m_utils.storageSetToZeroFunction(*m_type) +
"(" +
m_slot +
", " +
(
m_offset.type() == typeid(unsigned) ?
to_string(get<unsigned>(m_offset)) :
get<string>(m_offset)
) +
")\n";
}
IRMemoryItem::IRMemoryItem(
YulUtilFunctions _utils,
std::string _address,
bool _byteArrayElement,
Type const& _type
):
IRLValue(std::move(_utils), &_type),
m_address(move(_address)),
m_byteArrayElement(_byteArrayElement)
{ }
string IRMemoryItem::retrieveValue() const
{
if (m_byteArrayElement)
return m_utils.cleanupFunction(*m_type) +
"(mload(" +
m_address +
"))";
if (m_type->isValueType())
return m_utils.readFromMemory(*m_type) +
"(" +
m_address +
")";
else
return "mload(" + m_address + ")";
}
string IRMemoryItem::storeValue(string const& _value, Type const& _type) const
{
if (!m_type->isValueType())
{
solUnimplementedAssert(_type == *m_type, "Conversion not implemented for assignment to memory.");
solAssert(m_type->sizeOnStack() == 1, "");
solAssert(dynamic_cast<ReferenceType const*>(m_type), "");
return "mstore(" + m_address + ", " + _value + ")\n";
}
solAssert(_type.isValueType(), "");
string prepared = _value;
// Exists to see if this case ever happens
solAssert(_type == *m_type, "");
if (_type != *m_type)
prepared =
m_utils.conversionFunction(_type, *m_type) +
"(" +
_value +
")";
else
prepared =
m_utils.cleanupFunction(*m_type) +
"(" +
_value +
")";
if (m_byteArrayElement)
{
solAssert(*m_type == *TypeProvider::byte(), "");
return "mstore8(" + m_address + ", byte(0, " + prepared + "))\n";
}
else
return m_utils.writeToMemoryFunction(*m_type) +
"(" +
m_address +
", " +
prepared +
")\n";
}
string IRMemoryItem::setToZero() const
{
return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type);
}

View File

@ -15,115 +15,51 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>. along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/ */
/** /**
* Generator for code that handles LValues. * Classes that store locations of lvalues.
*/ */
#pragma once #pragma once
#include <libsolidity/codegen/YulUtilFunctions.h> #include <libsolidity/codegen/ir/IRVariable.h>
#include <variant>
#include <libsolutil/Common.h>
#include <string>
#include <ostream>
#include <boost/variant.hpp>
namespace solidity::frontend namespace solidity::frontend
{ {
class VariableDeclaration;
class IRGenerationContext;
class Type; class Type;
class ArrayType;
/** struct IRLValue
* Abstract class used to retrieve, delete and store data in LValues.
*/
class IRLValue
{ {
protected: Type const& type;
explicit IRLValue(YulUtilFunctions _utils, Type const* _type = nullptr): struct Stack
m_utils(std::move(_utils)), {
m_type(_type) IRVariable variable;
{} };
struct Storage
public: {
virtual ~IRLValue() = default; std::string const slot;
/// @returns an expression to retrieve the value of the lvalue. /// unsigned: Used when the offset is known at compile time, uses optimized
virtual std::string retrieveValue() const = 0; /// functions
/// Returns code that stores the value of @a _value (should be an identifier) /// string: Used when the offset is determined at run time
/// of type @a _type in the lvalue. Might perform type conversion. std::variant<std::string, unsigned> const offset;
virtual std::string storeValue(std::string const& _value, Type const& _type) const = 0; std::string offsetString() const
{
/// Returns code that will reset the stored value to zero if (std::holds_alternative<unsigned>(offset))
virtual std::string setToZero() const = 0; return std::to_string(std::get<unsigned>(offset));
protected: else
YulUtilFunctions mutable m_utils; return std::get<std::string>(offset);
Type const* m_type; }
}; };
struct Memory
class IRLocalVariable: public IRLValue {
{ std::string const address;
public: bool byteArrayElement = false;
IRLocalVariable( };
IRGenerationContext& _context, struct Tuple
VariableDeclaration const& _varDecl {
); std::vector<std::optional<IRLValue>> components;
std::string retrieveValue() const override { return m_variableName; } };
std::string storeValue(std::string const& _value, Type const& _type) const override; std::variant<Stack, Storage, Memory, Tuple> kind;
std::string setToZero() const override;
private:
std::string m_variableName;
};
class IRStorageItem: public IRLValue
{
public:
IRStorageItem(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
IRStorageItem(
YulUtilFunctions _utils,
std::string _slot,
boost::variant<std::string, unsigned> _offset,
Type const& _type
);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
IRStorageItem(
YulUtilFunctions _utils,
Type const& _type,
std::pair<u256, unsigned> slot_offset
);
std::string const m_slot;
/// unsigned: Used when the offset is known at compile time, uses optimized
/// functions
/// string: Used when the offset is determined at run time
boost::variant<std::string, unsigned> const m_offset;
};
class IRMemoryItem: public IRLValue
{
public:
IRMemoryItem(
YulUtilFunctions _utils,
std::string _address,
bool _byteArrayElement,
Type const& _type
);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
std::string const m_address;
bool m_byteArrayElement;
}; };
} }

View File

@ -0,0 +1,111 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/codegen/ir/IRVariable.h>
#include <libsolidity/ast/AST.h>
#include <boost/range/adaptor/transformed.hpp>
#include <libsolutil/StringUtils.h>
using namespace std;
using namespace solidity;
using namespace solidity::frontend;
using namespace solidity::util;
IRVariable::IRVariable(std::string _baseName, Type const& _type):
m_baseName(std::move(_baseName)), m_type(_type)
{
}
IRVariable::IRVariable(VariableDeclaration const& _declaration):
IRVariable(
"vloc_" + _declaration.name() + '_' + std::to_string(_declaration.id()),
*_declaration.annotation().type
)
{
solAssert(!_declaration.isStateVariable(), "");
}
IRVariable::IRVariable(Expression const& _expression):
IRVariable(
"expr_" + to_string(_expression.id()),
*_expression.annotation().type
)
{
}
IRVariable IRVariable::part(string const& _name) const
{
for (auto const& [itemName, itemType]: m_type.stackItems())
if (itemName == _name)
{
solAssert(itemName.empty() || itemType, "");
return IRVariable{suffixedName(itemName), itemType ? *itemType : m_type};
}
solAssert(false, "Invalid stack item name.");
}
vector<string> IRVariable::stackSlots() const
{
vector<string> result;
for (auto const& [itemName, itemType]: m_type.stackItems())
if (itemType)
{
solAssert(!itemName.empty(), "");
solAssert(m_type != *itemType, "");
result += IRVariable{suffixedName(itemName), *itemType}.stackSlots();
}
else
{
solAssert(itemName.empty(), "");
result.emplace_back(m_baseName);
}
return result;
}
string IRVariable::commaSeparatedList() const
{
return joinHumanReadable(stackSlots());
}
string IRVariable::commaSeparatedListPrefixed() const
{
return joinHumanReadablePrefixed(stackSlots());
}
string IRVariable::name() const
{
solAssert(m_type.sizeOnStack() == 1, "");
auto const& [itemName, type] = m_type.stackItems().front();
solAssert(!type, "");
return suffixedName(itemName);
}
IRVariable IRVariable::tupleComponent(size_t _i) const
{
solAssert(
m_type.category() == Type::Category::Tuple,
"Requested tuple component of non-tuple IR variable."
);
return part("component_" + std::to_string(_i + 1));
}
string IRVariable::suffixedName(string const& _suffix) const
{
if (_suffix.empty())
return m_baseName;
else
return m_baseName + '_' + _suffix;
}

View File

@ -0,0 +1,85 @@
/*
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 <optional>
#include <string>
#include <vector>
namespace solidity::frontend
{
class VariableDeclaration;
class Type;
class Expression;
/**
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
* of a specific S
* olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference
* type or a memory reference type will contain a single unnamed part containing an offset. An IRVariable referring to
* a value type will contain a single unnamed part containing the value, an IRVariable referring to a tuple will
* have the typed tuple components as parts.
*/
class IRVariable
{
public:
/// IR variable with explicit base name @a _baseName and type @a _type.
IRVariable(std::string _baseName, Type const& _type);
/// IR variable referring to the declaration @a _decl.
explicit IRVariable(VariableDeclaration const& _decl);
/// IR variable referring to the expression @a _expr.
/// Intentionally not defined as explicit to allow defining IRVariables for expressions directly via implicit conversions.
IRVariable(Expression const& _expression);
/// @returns the name of the variable, if it occupies a single stack slot (otherwise throws).
std::string name() const;
/// @returns a comma-separated list of the stack slots of the variable.
std::string commaSeparatedList() const;
/// @returns a comma-separated list of the stack slots of the variable that is
/// prefixed with a comma, unless it is empty.
std::string commaSeparatedListPrefixed() const;
/// @returns an IRVariable referring to the tuple component @a _i of a tuple variable.
IRVariable tupleComponent(std::size_t _i) const;
/// @returns the type of the variable.
Type const& type() const { return m_type; }
/// @returns an IRVariable referring to the stack component @a _slot of the variable.
/// @a _slot must be among the stack slots in ``m_type.stackItems()``.
/// The returned IRVariable is itself typed with the type of the stack slot as defined
/// in ``m_type.stackItems()`` and may again occupy multiple stack slots.
IRVariable part(std::string const& _slot) const;
private:
/// @returns a vector containing the names of the stack slots of the variable.
std::vector<std::string> stackSlots() const;
/// @returns a name consisting of the base name appended with an underscore and @æ _suffix,
/// unless @a _suffix is empty, in which case the base name itself is returned.
std::string suffixedName(std::string const& _suffix) const;
std::string m_baseName;
Type const& m_type;
};
}

View File

@ -441,26 +441,7 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall)
} }
else else
{ {
vector<smt::Expression> funArgs; initializeFunctionCallParameters(*funDef, symbolicArguments(_funCall));
Expression const* calledExpr = &_funCall.expression();
auto const& funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type);
solAssert(funType, "");
auto const& functionParams = funDef->parameters();
auto const& arguments = _funCall.arguments();
unsigned firstParam = 0;
if (funType->bound())
{
auto const& boundFunction = dynamic_cast<MemberAccess const*>(calledExpr);
solAssert(boundFunction, "");
funArgs.push_back(expr(boundFunction->expression(), functionParams.front()->type()));
firstParam = 1;
}
solAssert((arguments.size() + firstParam) == functionParams.size(), "");
for (unsigned i = 0; i < arguments.size(); ++i)
funArgs.push_back(expr(*arguments.at(i), functionParams.at(i + firstParam)->type()));
initializeFunctionCallParameters(*funDef, funArgs);
// The reason why we need to pushCallStack here instead of visit(FunctionDefinition) // The reason why we need to pushCallStack here instead of visit(FunctionDefinition)
// is that there we don't have `_funCall`. // is that there we don't have `_funCall`.
@ -575,7 +556,7 @@ void BMC::checkVerificationTargets(smt::Expression const& _constraints)
checkVerificationTarget(target, _constraints); checkVerificationTarget(target, _constraints);
} }
void BMC::checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints) void BMC::checkVerificationTarget(BMCVerificationTarget& _target, smt::Expression const& _constraints)
{ {
switch (_target.type) switch (_target.type)
{ {
@ -606,7 +587,7 @@ void BMC::checkVerificationTarget(VerificationTarget& _target, smt::Expression c
} }
} }
void BMC::checkConstantCondition(VerificationTarget& _target) void BMC::checkConstantCondition(BMCVerificationTarget& _target)
{ {
checkBooleanNotConstant( checkBooleanNotConstant(
*_target.expression, *_target.expression,
@ -617,7 +598,7 @@ void BMC::checkConstantCondition(VerificationTarget& _target)
); );
} }
void BMC::checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints) void BMC::checkUnderflow(BMCVerificationTarget& _target, smt::Expression const& _constraints)
{ {
solAssert( solAssert(
_target.type == VerificationTarget::Type::Underflow || _target.type == VerificationTarget::Type::Underflow ||
@ -637,7 +618,7 @@ void BMC::checkUnderflow(VerificationTarget& _target, smt::Expression const& _co
); );
} }
void BMC::checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints) void BMC::checkOverflow(BMCVerificationTarget& _target, smt::Expression const& _constraints)
{ {
solAssert( solAssert(
_target.type == VerificationTarget::Type::Overflow || _target.type == VerificationTarget::Type::Overflow ||
@ -657,7 +638,7 @@ void BMC::checkOverflow(VerificationTarget& _target, smt::Expression const& _con
); );
} }
void BMC::checkDivByZero(VerificationTarget& _target) void BMC::checkDivByZero(BMCVerificationTarget& _target)
{ {
solAssert(_target.type == VerificationTarget::Type::DivByZero, ""); solAssert(_target.type == VerificationTarget::Type::DivByZero, "");
checkCondition( checkCondition(
@ -671,7 +652,7 @@ void BMC::checkDivByZero(VerificationTarget& _target)
); );
} }
void BMC::checkBalance(VerificationTarget& _target) void BMC::checkBalance(BMCVerificationTarget& _target)
{ {
solAssert(_target.type == VerificationTarget::Type::Balance, ""); solAssert(_target.type == VerificationTarget::Type::Balance, "");
checkCondition( checkCondition(
@ -684,7 +665,7 @@ void BMC::checkBalance(VerificationTarget& _target)
); );
} }
void BMC::checkAssert(VerificationTarget& _target) void BMC::checkAssert(BMCVerificationTarget& _target)
{ {
solAssert(_target.type == VerificationTarget::Type::Assert, ""); solAssert(_target.type == VerificationTarget::Type::Assert, "");
if (!m_safeAssertions.count(_target.expression)) if (!m_safeAssertions.count(_target.expression))
@ -703,10 +684,12 @@ void BMC::addVerificationTarget(
Expression const* _expression Expression const* _expression
) )
{ {
VerificationTarget target{ BMCVerificationTarget target{
_type, {
_value, _type,
currentPathConditions() && m_context.assertions(), _value,
currentPathConditions() && m_context.assertions()
},
_expression, _expression,
m_callStack, m_callStack,
modelExpressions() modelExpressions()

View File

@ -117,24 +117,21 @@ private:
/// Verification targets. /// Verification targets.
//@{ //@{
struct VerificationTarget struct BMCVerificationTarget: VerificationTarget
{ {
enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert } type;
smt::Expression value;
smt::Expression constraints;
Expression const* expression; Expression const* expression;
std::vector<CallStackEntry> callStack; std::vector<CallStackEntry> callStack;
std::pair<std::vector<smt::Expression>, std::vector<std::string>> modelExpressions; std::pair<std::vector<smt::Expression>, std::vector<std::string>> modelExpressions;
}; };
void checkVerificationTargets(smt::Expression const& _constraints); void checkVerificationTargets(smt::Expression const& _constraints);
void checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true)); void checkVerificationTarget(BMCVerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true));
void checkConstantCondition(VerificationTarget& _target); void checkConstantCondition(BMCVerificationTarget& _target);
void checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints); void checkUnderflow(BMCVerificationTarget& _target, smt::Expression const& _constraints);
void checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints); void checkOverflow(BMCVerificationTarget& _target, smt::Expression const& _constraints);
void checkDivByZero(VerificationTarget& _target); void checkDivByZero(BMCVerificationTarget& _target);
void checkBalance(VerificationTarget& _target); void checkBalance(BMCVerificationTarget& _target);
void checkAssert(VerificationTarget& _target); void checkAssert(BMCVerificationTarget& _target);
void addVerificationTarget( void addVerificationTarget(
VerificationTarget::Type _type, VerificationTarget::Type _type,
smt::Expression const& _value, smt::Expression const& _value,
@ -179,7 +176,7 @@ private:
/// ErrorReporter that comes from CompilerStack. /// ErrorReporter that comes from CompilerStack.
langutil::ErrorReporter& m_outerErrorReporter; langutil::ErrorReporter& m_outerErrorReporter;
std::vector<VerificationTarget> m_verificationTargets; std::vector<BMCVerificationTarget> m_verificationTargets;
/// Assertions that are known to be safe. /// Assertions that are known to be safe.
std::set<Expression const*> m_safeAssertions; std::set<Expression const*> m_safeAssertions;

View File

@ -505,6 +505,23 @@ void CHC::eraseKnowledge()
m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); });
} }
void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function)
{
SMTEncoder::clearIndices(_contract, _function);
for (auto const* var: m_stateVariables)
/// SSA index 0 is reserved for state variables at the beginning
/// of the current transaction.
m_context.variable(*var)->increaseIndex();
if (_function)
{
for (auto const& var: _function->parameters() + _function->returnParameters())
m_context.variable(*var)->increaseIndex();
for (auto const& var: _function->localVariables())
m_context.variable(*var)->increaseIndex();
}
}
bool CHC::shouldVisit(ContractDefinition const& _contract) const bool CHC::shouldVisit(ContractDefinition const& _contract) const
{ {
if ( if (

View File

@ -83,6 +83,7 @@ private:
//@{ //@{
void reset(); void reset();
void eraseKnowledge(); void eraseKnowledge();
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
bool shouldVisit(ContractDefinition const& _contract) const; bool shouldVisit(ContractDefinition const& _contract) const;
bool shouldVisit(FunctionDefinition const& _function) const; bool shouldVisit(FunctionDefinition const& _function) const;
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr); void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);

View File

@ -46,7 +46,7 @@ public:
/// should be used, even if all are available. The default choice is to use all. /// should be used, even if all are available. The default choice is to use all.
ModelChecker( ModelChecker(
langutil::ErrorReporter& _errorReporter, langutil::ErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses, std::map<solidity::util::h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback = ReadCallback::Callback(), ReadCallback::Callback const& _smtCallback = ReadCallback::Callback(),
smt::SMTSolverChoice _enabledSolvers = smt::SMTSolverChoice::All() smt::SMTSolverChoice _enabledSolvers = smt::SMTSolverChoice::All()
); );

View File

@ -1682,3 +1682,31 @@ void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
else if (returnParams.size() == 1) else if (returnParams.size() == 1)
defineExpr(_funCall, currentValue(*returnParams.front())); defineExpr(_funCall, currentValue(*returnParams.front()));
} }
vector<smt::Expression> SMTEncoder::symbolicArguments(FunctionCall const& _funCall)
{
auto const* function = functionCallToDefinition(_funCall);
solAssert(function, "");
vector<smt::Expression> args;
Expression const* calledExpr = &_funCall.expression();
auto const& funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type);
solAssert(funType, "");
auto const& functionParams = function->parameters();
auto const& arguments = _funCall.arguments();
unsigned firstParam = 0;
if (funType->bound())
{
auto const& boundFunction = dynamic_cast<MemberAccess const*>(calledExpr);
solAssert(boundFunction, "");
args.push_back(expr(boundFunction->expression(), functionParams.front()->type()));
firstParam = 1;
}
solAssert((arguments.size() + firstParam) == functionParams.size(), "");
for (unsigned i = 0; i < arguments.size(); ++i)
args.push_back(expr(*arguments.at(i), functionParams.at(i + firstParam)->type()));
return args;
}

View File

@ -217,7 +217,7 @@ protected:
/// Resets the variable indices. /// Resets the variable indices.
void resetVariableIndices(VariableIndices const& _indices); void resetVariableIndices(VariableIndices const& _indices);
/// Used when starting a new block. /// Used when starting a new block.
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr); virtual void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr);
/// @returns variables that are touched in _node's subtree. /// @returns variables that are touched in _node's subtree.
@ -230,9 +230,21 @@ protected:
/// and set them as the components of the symbolic tuple. /// and set them as the components of the symbolic tuple.
void createReturnedExpressions(FunctionCall const& _funCall); void createReturnedExpressions(FunctionCall const& _funCall);
/// @returns the symbolic arguments for a function call,
/// taking into account bound functions and
/// type conversion.
std::vector<smt::Expression> symbolicArguments(FunctionCall const& _funCall);
/// @returns a note to be added to warnings. /// @returns a note to be added to warnings.
std::string extraComment(); std::string extraComment();
struct VerificationTarget
{
enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert } type;
smt::Expression value;
smt::Expression constraints;
};
smt::VariableUsage m_variableUsage; smt::VariableUsage m_variableUsage;
bool m_arrayAssignmentHappened = false; bool m_arrayAssignmentHappened = false;
// True if the "No SMT solver available" warning was already created. // True if the "No SMT solver available" warning was already created.

View File

@ -165,7 +165,7 @@ void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
{ {
if (m_stackState >= ParsingPerformed) if (m_stackState >= ParsingPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set revert string settings before parsing.")); BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set revert string settings before parsing."));
solUnimplementedAssert(_revertStrings == RevertStrings::Default || _revertStrings == RevertStrings::Strip, ""); solUnimplementedAssert(_revertStrings != RevertStrings::VerboseDebug, "");
m_revertStrings = _revertStrings; m_revertStrings = _revertStrings;
} }
@ -670,14 +670,14 @@ string CompilerStack::assemblyString(string const& _contractName, StringMap _sou
} }
/// TODO: cache the JSON /// TODO: cache the JSON
Json::Value CompilerStack::assemblyJSON(string const& _contractName, StringMap const& _sourceCodes) const Json::Value CompilerStack::assemblyJSON(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
Contract const& currentContract = contract(_contractName); Contract const& currentContract = contract(_contractName);
if (currentContract.compiler) if (currentContract.compiler)
return currentContract.compiler->assemblyJSON(_sourceCodes); return currentContract.compiler->assemblyJSON(sourceIndices());
else else
return Json::Value(); return Json::Value();
} }
@ -906,35 +906,42 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
{ {
solAssert(m_stackState < ParsingPerformed, ""); solAssert(m_stackState < ParsingPerformed, "");
StringMap newSources; StringMap newSources;
for (auto const& node: _ast.nodes()) try
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get())) {
{ for (auto const& node: _ast.nodes())
solAssert(!import->path().empty(), "Import path cannot be empty."); if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
string importPath = util::absolutePath(import->path(), _sourcePath);
// The current value of `path` is the absolute path as seen from this source file.
// We first have to apply remappings before we can store the actual absolute path
// as seen globally.
importPath = applyRemapping(importPath, _sourcePath);
import->annotation().absolutePath = importPath;
if (m_sources.count(importPath) || newSources.count(importPath))
continue;
ReadCallback::Result result{false, string("File not supplied initially.")};
if (m_readFile)
result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), importPath);
if (result.success)
newSources[importPath] = result.responseOrErrorMessage;
else
{ {
m_errorReporter.parserError( solAssert(!import->path().empty(), "Import path cannot be empty.");
import->location(),
string("Source \"" + importPath + "\" not found: " + result.responseOrErrorMessage) string importPath = util::absolutePath(import->path(), _sourcePath);
); // The current value of `path` is the absolute path as seen from this source file.
continue; // We first have to apply remappings before we can store the actual absolute path
// as seen globally.
importPath = applyRemapping(importPath, _sourcePath);
import->annotation().absolutePath = importPath;
if (m_sources.count(importPath) || newSources.count(importPath))
continue;
ReadCallback::Result result{false, string("File not supplied initially.")};
if (m_readFile)
result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), importPath);
if (result.success)
newSources[importPath] = result.responseOrErrorMessage;
else
{
m_errorReporter.parserError(
import->location(),
string("Source \"" + importPath + "\" not found: " + result.responseOrErrorMessage)
);
continue;
}
} }
} }
catch (FatalError const&)
{
solAssert(m_errorReporter.hasErrors(), "");
}
return newSources; return newSources;
} }
@ -1112,7 +1119,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
for (auto const* dependency: _contract.annotation().contractDependencies) for (auto const* dependency: _contract.annotation().contractDependencies)
generateIR(*dependency); generateIR(*dependency);
IRGenerator generator(m_evmVersion, m_optimiserSettings); IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract); tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
} }

View File

@ -287,7 +287,7 @@ public:
/// @returns a JSON representation of the assembly. /// @returns a JSON representation of the assembly.
/// @arg _sourceCodes is the map of input files to source code strings /// @arg _sourceCodes is the map of input files to source code strings
/// Prerequisite: Successful compilation. /// Prerequisite: Successful compilation.
Json::Value assemblyJSON(std::string const& _contractName, StringMap const& _sourceCodes = StringMap()) const; Json::Value assemblyJSON(std::string const& _contractName) const;
/// @returns a JSON representing the contract ABI. /// @returns a JSON representing the contract ABI.
/// Prerequisite: Successful call to parse or compile. /// Prerequisite: Successful call to parse or compile.

View File

@ -661,10 +661,10 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
std::optional<RevertStrings> revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].asString()); std::optional<RevertStrings> revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].asString());
if (!revertStrings) if (!revertStrings)
return formatFatalError("JSONError", "Invalid value for settings.debug.revertStrings."); return formatFatalError("JSONError", "Invalid value for settings.debug.revertStrings.");
if (*revertStrings != RevertStrings::Default && *revertStrings != RevertStrings::Strip) if (*revertStrings == RevertStrings::VerboseDebug)
return formatFatalError( return formatFatalError(
"UnimplementedFeatureError", "UnimplementedFeatureError",
"Only \"default\" and \"strip\" are implemented for settings.debug.revertStrings for now." "Only \"default\", \"strip\" and \"debug\" are implemented for settings.debug.revertStrings for now."
); );
ret.revertStrings = *revertStrings; ret.revertStrings = *revertStrings;
} }
@ -967,7 +967,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental)) if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental))
evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList); evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental)) if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental))
evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList); evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName);
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental)) if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental))
evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName); evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName);
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental)) if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental))

View File

@ -45,11 +45,11 @@ class Parser::ASTNodeFactory
{ {
public: public:
explicit ASTNodeFactory(Parser& _parser): explicit ASTNodeFactory(Parser& _parser):
m_parser(_parser), m_location{_parser.position(), -1, _parser.source()} {} m_parser(_parser), m_location{_parser.currentLocation().start, -1, _parser.currentLocation().source} {}
ASTNodeFactory(Parser& _parser, ASTPointer<ASTNode> const& _childNode): ASTNodeFactory(Parser& _parser, ASTPointer<ASTNode> const& _childNode):
m_parser(_parser), m_location{_childNode->location()} {} m_parser(_parser), m_location{_childNode->location()} {}
void markEndPosition() { m_location.end = m_parser.endPosition(); } void markEndPosition() { m_location.end = m_parser.currentLocation().end; }
void setLocation(SourceLocation const& _location) { m_location = _location; } void setLocation(SourceLocation const& _location) { m_location = _location; }
void setLocationEmpty() { m_location.end = m_location.start; } void setLocationEmpty() { m_location.end = m_location.start; }
/// Set the end position to the one of the given node. /// Set the end position to the one of the given node.
@ -135,6 +135,19 @@ void Parser::parsePragmaVersion(SourceLocation const& _location, vector<Token> c
); );
} }
ASTPointer<StructuredDocumentation> Parser::parseStructuredDocumentation()
{
if (m_scanner->currentCommentLiteral() != "")
{
ASTNodeFactory nodeFactory{*this};
nodeFactory.setLocation(m_scanner->currentCommentLocation());
return nodeFactory.createNode<StructuredDocumentation>(
make_shared<ASTString>(m_scanner->currentCommentLiteral())
);
}
return nullptr;
}
ASTPointer<PragmaDirective> Parser::parsePragmaDirective() ASTPointer<PragmaDirective> Parser::parsePragmaDirective()
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
@ -205,12 +218,12 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
while (true) while (true)
{ {
ASTPointer<ASTString> alias; ASTPointer<ASTString> alias;
SourceLocation aliasLocation = SourceLocation{position(), endPosition(), source()}; SourceLocation aliasLocation = currentLocation();
ASTPointer<Identifier> id = parseIdentifier(); ASTPointer<Identifier> id = parseIdentifier();
if (m_scanner->currentToken() == Token::As) if (m_scanner->currentToken() == Token::As)
{ {
expectToken(Token::As); expectToken(Token::As);
aliasLocation = SourceLocation{position(), endPosition(), source()}; aliasLocation = currentLocation();
alias = expectIdentifierToken(); alias = expectIdentifierToken();
} }
symbolAliases.emplace_back(ImportDirective::SymbolAlias{move(id), move(alias), aliasLocation}); symbolAliases.emplace_back(ImportDirective::SymbolAlias{move(id), move(alias), aliasLocation});
@ -265,7 +278,8 @@ std::pair<ContractKind, bool> Parser::parseContractKind()
kind = ContractKind::Library; kind = ContractKind::Library;
break; break;
default: default:
solAssert(false, "Invalid contract kind."); parserError("Expected keyword \"contract\", \"interface\" or \"library\".");
return std::make_pair(ContractKind::Contract, abstract);
} }
m_scanner->next(); m_scanner->next();
return std::make_pair(kind, abstract); return std::make_pair(kind, abstract);
@ -276,14 +290,13 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> name = nullptr; ASTPointer<ASTString> name = nullptr;
ASTPointer<ASTString> docString; ASTPointer<StructuredDocumentation> documentation;
vector<ASTPointer<InheritanceSpecifier>> baseContracts; vector<ASTPointer<InheritanceSpecifier>> baseContracts;
vector<ASTPointer<ASTNode>> subNodes; vector<ASTPointer<ASTNode>> subNodes;
std::pair<ContractKind, bool> contractKind{}; std::pair<ContractKind, bool> contractKind{};
try try
{ {
if (m_scanner->currentCommentLiteral() != "") documentation = parseStructuredDocumentation();
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
contractKind = parseContractKind(); contractKind = parseContractKind();
name = expectIdentifierToken(); name = expectIdentifierToken();
if (m_scanner->currentToken() == Token::Is) if (m_scanner->currentToken() == Token::Is)
@ -350,7 +363,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
expectToken(Token::RBrace); expectToken(Token::RBrace);
return nodeFactory.createNode<ContractDefinition>( return nodeFactory.createNode<ContractDefinition>(
name, name,
docString, documentation,
baseContracts, baseContracts,
subNodes, subNodes,
contractKind.first, contractKind.first,
@ -538,9 +551,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docstring; ASTPointer<StructuredDocumentation> documentation = parseStructuredDocumentation();
if (m_scanner->currentCommentLiteral() != "")
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
Token kind = m_scanner->currentToken(); Token kind = m_scanner->currentToken();
ASTPointer<ASTString> name; ASTPointer<ASTString> name;
@ -598,7 +609,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
kind, kind,
header.isVirtual, header.isVirtual,
header.overrides, header.overrides,
docstring, documentation,
header.parameters, header.parameters,
header.modifiers, header.modifiers,
header.returnParameters, header.returnParameters,
@ -792,9 +803,7 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
m_insideModifier = true; m_insideModifier = true;
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docstring; ASTPointer<StructuredDocumentation> documentation = parseStructuredDocumentation();
if (m_scanner->currentCommentLiteral() != "")
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
expectToken(Token::Modifier); expectToken(Token::Modifier);
ASTPointer<ASTString> name(expectIdentifierToken()); ASTPointer<ASTString> name(expectIdentifierToken());
@ -835,16 +844,14 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
ASTPointer<Block> block = parseBlock(); ASTPointer<Block> block = parseBlock();
nodeFactory.setEndPositionFromNode(block); nodeFactory.setEndPositionFromNode(block);
return nodeFactory.createNode<ModifierDefinition>(name, docstring, parameters, isVirtual, overrides, block); return nodeFactory.createNode<ModifierDefinition>(name, documentation, parameters, isVirtual, overrides, block);
} }
ASTPointer<EventDefinition> Parser::parseEventDefinition() ASTPointer<EventDefinition> Parser::parseEventDefinition()
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docstring; ASTPointer<StructuredDocumentation> documentation = parseStructuredDocumentation();
if (m_scanner->currentCommentLiteral() != "")
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
expectToken(Token::Event); expectToken(Token::Event);
ASTPointer<ASTString> name(expectIdentifierToken()); ASTPointer<ASTString> name(expectIdentifierToken());
@ -861,7 +868,7 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
} }
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::Semicolon); expectToken(Token::Semicolon);
return nodeFactory.createNode<EventDefinition>(name, docstring, parameters, anonymous); return nodeFactory.createNode<EventDefinition>(name, documentation, parameters, anonymous);
} }
ASTPointer<UsingForDirective> Parser::parseUsingDirective() ASTPointer<UsingForDirective> Parser::parseUsingDirective()
@ -1013,16 +1020,22 @@ ASTPointer<Mapping> Parser::parseMapping()
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
expectToken(Token::Mapping); expectToken(Token::Mapping);
expectToken(Token::LParen); expectToken(Token::LParen);
ASTPointer<ElementaryTypeName> keyType; ASTPointer<TypeName> keyType;
Token token = m_scanner->currentToken(); Token token = m_scanner->currentToken();
if (!TokenTraits::isElementaryTypeName(token))
fatalParserError(string("Expected elementary type name for mapping key type"));
unsigned firstSize; unsigned firstSize;
unsigned secondSize; unsigned secondSize;
tie(firstSize, secondSize) = m_scanner->currentTokenInfo(); tie(firstSize, secondSize) = m_scanner->currentTokenInfo();
ElementaryTypeNameToken elemTypeName(token, firstSize, secondSize); if (token == Token::Identifier)
keyType = ASTNodeFactory(*this).createNode<ElementaryTypeName>(elemTypeName); keyType = parseUserDefinedTypeName();
m_scanner->next(); else if (TokenTraits::isElementaryTypeName(token))
{
keyType = ASTNodeFactory(*this).createNode<ElementaryTypeName>(
ElementaryTypeNameToken{token, firstSize, secondSize}
);
m_scanner->next();
}
else
fatalParserError(string("Expected elementary type name or identifier for mapping key type"));
expectToken(Token::Arrow); expectToken(Token::Arrow);
bool const allowVar = false; bool const allowVar = false;
ASTPointer<TypeName> valueType = parseTypeName(allowVar); ASTPointer<TypeName> valueType = parseTypeName(allowVar);
@ -1176,7 +1189,7 @@ ASTPointer<Statement> Parser::parseStatement()
ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> const& _docString) ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> const& _docString)
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
SourceLocation location{position(), -1, source()}; SourceLocation location = currentLocation();
expectToken(Token::Assembly); expectToken(Token::Assembly);
yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion); yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion);
@ -1355,7 +1368,7 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const
if (m_scanner->currentToken() != Token::Period) if (m_scanner->currentToken() != Token::Period)
break; break;
m_scanner->next(); m_scanner->next();
}; }
auto eventName = expressionFromIndexAccessStructure(iap); auto eventName = expressionFromIndexAccessStructure(iap);
expectToken(Token::LParen); expectToken(Token::LParen);
@ -1755,7 +1768,7 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::RBrace); expectToken(Token::RBrace);
expression = parseLeftHandSideExpression(nodeFactory.createNode<FunctionCallOptions>(expression, optionList.first, optionList.second)); expression = nodeFactory.createNode<FunctionCallOptions>(expression, optionList.first, optionList.second);
break; break;
} }
default: default:
@ -2011,13 +2024,13 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath()
ASTPointer<Expression> endIndex; ASTPointer<Expression> endIndex;
if (m_scanner->currentToken() != Token::RBrack) if (m_scanner->currentToken() != Token::RBrack)
endIndex = parseExpression(); endIndex = parseExpression();
indexLocation.end = endPosition(); indexLocation.end = currentLocation().end;
iap.indices.emplace_back(IndexAccessedPath::Index{index, {endIndex}, indexLocation}); iap.indices.emplace_back(IndexAccessedPath::Index{index, {endIndex}, indexLocation});
expectToken(Token::RBrack); expectToken(Token::RBrack);
} }
else else
{ {
indexLocation.end = endPosition(); indexLocation.end = currentLocation().end;
iap.indices.emplace_back(IndexAccessedPath::Index{index, {}, indexLocation}); iap.indices.emplace_back(IndexAccessedPath::Index{index, {}, indexLocation});
expectToken(Token::RBrack); expectToken(Token::RBrack);
} }

View File

@ -80,6 +80,7 @@ private:
///@{ ///@{
///@name Parsing functions for the AST nodes ///@name Parsing functions for the AST nodes
void parsePragmaVersion(langutil::SourceLocation const& _location, std::vector<Token> const& _tokens, std::vector<std::string> const& _literals); void parsePragmaVersion(langutil::SourceLocation const& _location, std::vector<Token> const& _tokens, std::vector<std::string> const& _literals);
ASTPointer<StructuredDocumentation> parseStructuredDocumentation();
ASTPointer<PragmaDirective> parsePragmaDirective(); ASTPointer<PragmaDirective> parsePragmaDirective();
ASTPointer<ImportDirective> parseImportDirective(); ASTPointer<ImportDirective> parseImportDirective();
/// @returns an std::pair<ContractKind, bool>, where /// @returns an std::pair<ContractKind, bool>, where

View File

@ -40,12 +40,12 @@ namespace solidity::util
/// Assertion that throws an exception containing the given description if it is not met. /// Assertion that throws an exception containing the given description if it is not met.
/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong."); /// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong.");
/// Do NOT supply an exception object as the second parameter. /// Do NOT supply an exception object as the second parameter.
#define assertThrow(_condition, _ExceptionType, _description) \ #define assertThrow(_condition, _exceptionType, _description) \
do \ do \
{ \ { \
if (!(_condition)) \ if (!(_condition)) \
::boost::throw_exception( \ ::boost::throw_exception( \
_ExceptionType() << \ _exceptionType() << \
::solidity::util::errinfo_comment(_description) << \ ::solidity::util::errinfo_comment(_description) << \
::boost::throw_function(ETH_FUNC) << \ ::boost::throw_function(ETH_FUNC) << \
::boost::throw_file(__FILE__) << \ ::boost::throw_file(__FILE__) << \

View File

@ -156,6 +156,23 @@ T convertContainer(U&& _from)
}; };
} }
/// Gets a @a K -> @a V map and returns a map where values from the original map are keys and keys
/// from the original map are values.
///
/// @pre @a originalMap must have unique values.
template <typename K, typename V>
std::map<V, K> invertMap(std::map<K, V> const& originalMap)
{
std::map<V, K> inverseMap;
for (auto const& originalPair: originalMap)
{
assert(inverseMap.count(originalPair.second) == 0);
inverseMap.insert({originalPair.second, originalPair.first});
}
return inverseMap;
}
// String conversion functions, mainly to/from hex/nibble/byte representations. // String conversion functions, mainly to/from hex/nibble/byte representations.
enum class WhenError enum class WhenError
@ -232,14 +249,14 @@ inline void toBigEndian(T _val, Out& o_out)
} }
/// Converts a big-endian byte-stream represented on a templated collection to a templated integer value. /// Converts a big-endian byte-stream represented on a templated collection to a templated integer value.
/// @a _In will typically be either std::string or bytes. /// @a In will typically be either std::string or bytes.
/// @a T will typically by unsigned, u160, u256 or bigint. /// @a T will typically by unsigned, u160, u256 or bigint.
template <class T, class _In> template <class T, class In>
inline T fromBigEndian(_In const& _bytes) inline T fromBigEndian(In const& _bytes)
{ {
T ret = (T)0; T ret = (T)0;
for (auto i: _bytes) for (auto i: _bytes)
ret = (T)((ret << 8) | (uint8_t)(typename std::make_unsigned<typename _In::value_type>::type)i); ret = (T)((ret << 8) | (uint8_t)(typename std::make_unsigned<typename In::value_type>::type)i);
return ret; return ret;
} }
inline bytes toBigEndian(u256 _val) { bytes ret(32); toBigEndian(_val, ret); return ret; } inline bytes toBigEndian(u256 _val) { bytes ret(32); toBigEndian(_val, ret); return ret; }

View File

@ -40,11 +40,11 @@ using namespace solidity::util;
namespace namespace
{ {
template <typename _T> template <typename T>
inline _T readFile(std::string const& _file) inline T readFile(std::string const& _file)
{ {
_T ret; T ret;
size_t const c_elementSize = sizeof(typename _T::value_type); size_t const c_elementSize = sizeof(typename T::value_type);
std::ifstream is(_file, std::ifstream::binary); std::ifstream is(_file, std::ifstream::binary);
if (!is) if (!is)
return ret; return ret;

View File

@ -42,8 +42,8 @@ std::string readStandardInput();
int readStandardInputChar(); int readStandardInputChar();
/// Converts arbitrary value to string representation using std::stringstream. /// Converts arbitrary value to string representation using std::stringstream.
template <class _T> template <class T>
std::string toString(_T const& _t) std::string toString(T const& _t)
{ {
std::ostringstream o; std::ostringstream o;
o << _t; o << _t;

View File

@ -16,63 +16,63 @@ namespace solidity::util
/** /**
* A modifiable reference to an existing object or vector in memory. * A modifiable reference to an existing object or vector in memory.
*/ */
template <class _T> template <class T>
class vector_ref class vector_ref
{ {
public: public:
using value_type = _T; using value_type = T;
using element_type = _T; using element_type = T;
using mutable_value_type = typename std::conditional<std::is_const<_T>::value, typename std::remove_const<_T>::type, _T>::type; using mutable_value_type = typename std::conditional<std::is_const<T>::value, typename std::remove_const<T>::type, T>::type;
using string_type = typename std::conditional<std::is_const<_T>::value, std::string const, std::string>::type; using string_type = typename std::conditional<std::is_const<T>::value, std::string const, std::string>::type;
using vector_type = typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const, std::vector<_T>>::type; using vector_type = typename std::conditional<std::is_const<T>::value, std::vector<typename std::remove_const<T>::type> const, std::vector<T>>::type;
using iterator = _T*; using iterator = T*;
using const_iterator = _T const*; using const_iterator = T const*;
static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data."); static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data.");
vector_ref(): m_data(nullptr), m_count(0) {} vector_ref(): m_data(nullptr), m_count(0) {}
/// Creates a new vector_ref to point to @a _count elements starting at @a _data. /// Creates a new vector_ref to point to @a _count elements starting at @a _data.
vector_ref(_T* _data, size_t _count): m_data(_data), m_count(_count) {} vector_ref(T* _data, size_t _count): m_data(_data), m_count(_count) {}
/// Creates a new vector_ref pointing to the data part of a string (given as pointer). /// Creates a new vector_ref pointing to the data part of a string (given as pointer).
vector_ref(string_type* _data): m_data(reinterpret_cast<_T*>(_data->data())), m_count(_data->size() / sizeof(_T)) {} vector_ref(string_type* _data): m_data(reinterpret_cast<T*>(_data->data())), m_count(_data->size() / sizeof(T)) {}
/// Creates a new vector_ref pointing to the data part of a string (given as reference). /// Creates a new vector_ref pointing to the data part of a string (given as reference).
vector_ref(string_type& _data): vector_ref(&_data) {} vector_ref(string_type& _data): vector_ref(&_data) {}
/// Creates a new vector_ref pointing to the data part of a vector (given as pointer). /// Creates a new vector_ref pointing to the data part of a vector (given as pointer).
vector_ref(vector_type* _data): m_data(_data->data()), m_count(_data->size()) {} vector_ref(vector_type* _data): m_data(_data->data()), m_count(_data->size()) {}
explicit operator bool() const { return m_data && m_count; } explicit operator bool() const { return m_data && m_count; }
std::vector<unsigned char> toBytes() const { return std::vector<unsigned char>(reinterpret_cast<unsigned char const*>(m_data), reinterpret_cast<unsigned char const*>(m_data) + m_count * sizeof(_T)); } std::vector<unsigned char> toBytes() const { return std::vector<unsigned char>(reinterpret_cast<unsigned char const*>(m_data), reinterpret_cast<unsigned char const*>(m_data) + m_count * sizeof(T)); }
std::string toString() const { return std::string((char const*)m_data, ((char const*)m_data) + m_count * sizeof(_T)); } std::string toString() const { return std::string((char const*)m_data, ((char const*)m_data) + m_count * sizeof(T)); }
template <class _T2> explicit operator vector_ref<_T2>() const { assert(m_count * sizeof(_T) / sizeof(_T2) * sizeof(_T2) / sizeof(_T) == m_count); return vector_ref<_T2>(reinterpret_cast<_T2*>(m_data), m_count * sizeof(_T) / sizeof(_T2)); } template <class T2> explicit operator vector_ref<T2>() const { assert(m_count * sizeof(T) / sizeof(T2) * sizeof(T2) / sizeof(T) == m_count); return vector_ref<T2>(reinterpret_cast<T2*>(m_data), m_count * sizeof(T) / sizeof(T2)); }
operator vector_ref<_T const>() const { return vector_ref<_T const>(m_data, m_count); } operator vector_ref<T const>() const { return vector_ref<T const>(m_data, m_count); }
_T* data() const { return m_data; } T* data() const { return m_data; }
/// @returns the number of elements referenced (not necessarily number of bytes). /// @returns the number of elements referenced (not necessarily number of bytes).
size_t size() const { return m_count; } size_t size() const { return m_count; }
bool empty() const { return !m_count; } bool empty() const { return !m_count; }
/// @returns a new vector_ref which is a shifted and shortened view of the original data. /// @returns a new vector_ref which is a shifted and shortened view of the original data.
/// If this goes out of bounds in any way, returns an empty vector_ref. /// If this goes out of bounds in any way, returns an empty vector_ref.
/// If @a _count is ~size_t(0), extends the view to the end of the data. /// If @a _count is ~size_t(0), extends the view to the end of the data.
vector_ref<_T> cropped(size_t _begin, size_t _count) const { if (m_data && _begin <= m_count && _count <= m_count && _begin + _count <= m_count) return vector_ref<_T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<_T>(); } vector_ref<T> cropped(size_t _begin, size_t _count) const { if (m_data && _begin <= m_count && _count <= m_count && _begin + _count <= m_count) return vector_ref<T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<T>(); }
/// @returns a new vector_ref which is a shifted view of the original data (not going beyond it). /// @returns a new vector_ref which is a shifted view of the original data (not going beyond it).
vector_ref<_T> cropped(size_t _begin) const { if (m_data && _begin <= m_count) return vector_ref<_T>(m_data + _begin, m_count - _begin); else return vector_ref<_T>(); } vector_ref<T> cropped(size_t _begin) const { if (m_data && _begin <= m_count) return vector_ref<T>(m_data + _begin, m_count - _begin); else return vector_ref<T>(); }
_T* begin() { return m_data; } T* begin() { return m_data; }
_T* end() { return m_data + m_count; } T* end() { return m_data + m_count; }
_T const* begin() const { return m_data; } T const* begin() const { return m_data; }
_T const* end() const { return m_data + m_count; } T const* end() const { return m_data + m_count; }
_T& operator[](size_t _i) { assert(m_data); assert(_i < m_count); return m_data[_i]; } T& operator[](size_t _i) { assert(m_data); assert(_i < m_count); return m_data[_i]; }
_T const& operator[](size_t _i) const { assert(m_data); assert(_i < m_count); return m_data[_i]; } T const& operator[](size_t _i) const { assert(m_data); assert(_i < m_count); return m_data[_i]; }
bool operator==(vector_ref<_T> const& _cmp) const { return m_data == _cmp.m_data && m_count == _cmp.m_count; } bool operator==(vector_ref<T> const& _cmp) const { return m_data == _cmp.m_data && m_count == _cmp.m_count; }
bool operator!=(vector_ref<_T> const& _cmp) const { return !operator==(_cmp); } bool operator!=(vector_ref<T> const& _cmp) const { return !operator==(_cmp); }
void reset() { m_data = nullptr; m_count = 0; } void reset() { m_data = nullptr; m_count = 0; }
private: private:
_T* m_data = nullptr; T* m_data = nullptr;
size_t m_count = 0; size_t m_count = 0;
}; };

View File

@ -45,14 +45,14 @@ using namespace solidity::langutil;
bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::analyze(Block const& _block)
{ {
bool success = false; m_success = true;
try try
{ {
if (!(ScopeFiller(m_info, m_errorReporter))(_block)) if (!(ScopeFiller(m_info, m_errorReporter))(_block))
return false; return false;
success = (*this)(_block); (*this)(_block);
if (!success) if (!m_success)
yulAssert(m_errorReporter.hasErrors(), "No success but no error."); yulAssert(m_errorReporter.hasErrors(), "No success but no error.");
} }
catch (FatalError const&) catch (FatalError const&)
@ -60,7 +60,7 @@ bool AsmAnalyzer::analyze(Block const& _block)
// This FatalError con occur if the errorReporter has too many errors. // This FatalError con occur if the errorReporter has too many errors.
yulAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); yulAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported.");
} }
return success && !m_errorReporter.hasErrors(); return m_success && !m_errorReporter.hasErrors();
} }
AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object) AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object)
@ -79,131 +79,112 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect,
return analysisInfo; return analysisInfo;
} }
bool AsmAnalyzer::operator()(Literal const& _literal) vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
{ {
expectValidType(_literal.type, _literal.location); expectValidType(_literal.type, _literal.location);
++m_stackHeight;
if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32) if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32)
{ typeError(
m_errorReporter.typeError(
_literal.location, _literal.location,
"String literal too long (" + to_string(_literal.value.str().size()) + " > 32)" "String literal too long (" + to_string(_literal.value.str().size()) + " > 32)"
); );
return false;
}
else if (_literal.kind == LiteralKind::Number && bigint(_literal.value.str()) > u256(-1)) else if (_literal.kind == LiteralKind::Number && bigint(_literal.value.str()) > u256(-1))
{ typeError(_literal.location, "Number literal too large (> 256 bits)");
m_errorReporter.typeError(
_literal.location,
"Number literal too large (> 256 bits)"
);
return false;
}
else if (_literal.kind == LiteralKind::Boolean) else if (_literal.kind == LiteralKind::Boolean)
yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, ""); yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "");
m_info.stackHeightInfo[&_literal] = m_stackHeight;
return true; return {_literal.type};
} }
bool AsmAnalyzer::operator()(Identifier const& _identifier) vector<YulString> AsmAnalyzer::operator()(Identifier const& _identifier)
{ {
yulAssert(!_identifier.name.empty(), ""); yulAssert(!_identifier.name.empty(), "");
size_t numErrorsBefore = m_errorReporter.errors().size(); size_t numErrorsBefore = m_errorReporter.errors().size();
bool success = true; YulString type = m_dialect.defaultType;
if (m_currentScope->lookup(_identifier.name, GenericVisitor{ if (m_currentScope->lookup(_identifier.name, GenericVisitor{
[&](Scope::Variable const& _var) [&](Scope::Variable const& _var)
{ {
if (!m_activeVariables.count(&_var)) if (!m_activeVariables.count(&_var))
{ declarationError(
m_errorReporter.declarationError(
_identifier.location, _identifier.location,
"Variable " + _identifier.name.str() + " used before it was declared." "Variable " + _identifier.name.str() + " used before it was declared."
); );
success = false; type = _var.type;
}
++m_stackHeight;
}, },
[&](Scope::Function const&) [&](Scope::Function const&)
{ {
m_errorReporter.typeError( typeError(
_identifier.location, _identifier.location,
"Function " + _identifier.name.str() + " used without being called." "Function " + _identifier.name.str() + " used without being called."
); );
success = false;
} }
})) }))
{ {
} }
else else
{ {
size_t stackSize(-1); bool found = false;
if (m_resolver) if (m_resolver)
{ {
bool insideFunction = m_currentScope->insideFunction(); bool insideFunction = m_currentScope->insideFunction();
stackSize = m_resolver(_identifier, yul::IdentifierContext::RValue, insideFunction); size_t stackSize = m_resolver(_identifier, yul::IdentifierContext::RValue, insideFunction);
if (stackSize != size_t(-1))
{
found = true;
yulAssert(stackSize == 1, "Invalid stack size of external reference.");
}
} }
if (stackSize == size_t(-1)) if (!found)
{ {
// Only add an error message if the callback did not do it. // Only add an error message if the callback did not do it.
if (numErrorsBefore == m_errorReporter.errors().size()) if (numErrorsBefore == m_errorReporter.errors().size())
m_errorReporter.declarationError(_identifier.location, "Identifier not found."); declarationError(_identifier.location, "Identifier not found.");
success = false; m_success = false;
} }
m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
} }
m_info.stackHeightInfo[&_identifier] = m_stackHeight;
return success; return {type};
} }
bool AsmAnalyzer::operator()(ExpressionStatement const& _statement) void AsmAnalyzer::operator()(ExpressionStatement const& _statement)
{ {
int initialStackHeight = m_stackHeight; vector<YulString> types = std::visit(*this, _statement.expression);
bool success = std::visit(*this, _statement.expression); if (m_success && !types.empty())
if (success && m_stackHeight != initialStackHeight) typeError(_statement.location,
{
string msg =
"Top-level expressions are not supposed to return values (this expression returns " + "Top-level expressions are not supposed to return values (this expression returns " +
to_string(m_stackHeight - initialStackHeight) + to_string(types.size()) +
" value" + " value" +
(m_stackHeight - initialStackHeight == 1 ? "" : "s") + (types.size() == 1 ? "" : "s") +
"). Use ``pop()`` or assign them."; "). Use ``pop()`` or assign them."
m_errorReporter.error(Error::Type::TypeError, _statement.location, msg); );
success = false;
}
m_info.stackHeightInfo[&_statement] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(Assignment const& _assignment) void AsmAnalyzer::operator()(Assignment const& _assignment)
{ {
yulAssert(_assignment.value, ""); yulAssert(_assignment.value, "");
int const expectedItems = _assignment.variableNames.size(); size_t const numVariables = _assignment.variableNames.size();
yulAssert(expectedItems >= 1, ""); yulAssert(numVariables >= 1, "");
int const stackHeight = m_stackHeight;
bool success = std::visit(*this, *_assignment.value); vector<YulString> types = std::visit(*this, *_assignment.value);
if ((m_stackHeight - stackHeight) != expectedItems)
{ if (types.size() != numVariables)
m_errorReporter.declarationError( declarationError(
_assignment.location, _assignment.location,
"Variable count does not match number of values (" + "Variable count does not match number of values (" +
to_string(expectedItems) + to_string(numVariables) +
" vs. " + " vs. " +
to_string(m_stackHeight - stackHeight) + to_string(types.size()) +
")" ")"
); );
return false;
} for (size_t i = 0; i < numVariables; ++i)
for (auto const& variableName: _assignment.variableNames) checkAssignment(_assignment.variableNames[i]);
if (!checkAssignment(variableName, 1))
success = false;
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
{ {
bool success = true; size_t const numVariables = _varDecl.variables.size();
int const numVariables = _varDecl.variables.size();
if (m_resolver) if (m_resolver)
for (auto const& variable: _varDecl.variables) for (auto const& variable: _varDecl.variables)
// Call the resolver for variable declarations to allow it to raise errors on shadowing. // Call the resolver for variable declarations to allow it to raise errors on shadowing.
@ -214,36 +195,27 @@ bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
); );
if (_varDecl.value) if (_varDecl.value)
{ {
int const stackHeight = m_stackHeight; vector<YulString> types = std::visit(*this, *_varDecl.value);
success = std::visit(*this, *_varDecl.value); if (types.size() != numVariables)
int numValues = m_stackHeight - stackHeight; declarationError(_varDecl.location,
if (numValues != numVariables)
{
m_errorReporter.declarationError(_varDecl.location,
"Variable count mismatch: " + "Variable count mismatch: " +
to_string(numVariables) + to_string(numVariables) +
" variables and " + " variables and " +
to_string(numValues) + to_string(types.size()) +
" values." " values."
); );
// Adjust stack height to avoid misleading additional errors.
m_stackHeight += numVariables - numValues;
return false;
}
} }
else
m_stackHeight += numVariables;
for (auto const& variable: _varDecl.variables) for (TypedName const& variable: _varDecl.variables)
{ {
expectValidType(variable.type, variable.location); expectValidType(variable.type, variable.location);
m_activeVariables.insert(&std::get<Scope::Variable>(m_currentScope->identifiers.at(variable.name))); m_activeVariables.insert(&std::get<Scope::Variable>(
m_currentScope->identifiers.at(variable.name))
);
} }
m_info.stackHeightInfo[&_varDecl] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(FunctionDefinition const& _funDef) void AsmAnalyzer::operator()(FunctionDefinition const& _funDef)
{ {
yulAssert(!_funDef.name.empty(), ""); yulAssert(!_funDef.name.empty(), "");
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
@ -255,116 +227,95 @@ bool AsmAnalyzer::operator()(FunctionDefinition const& _funDef)
m_activeVariables.insert(&std::get<Scope::Variable>(varScope.identifiers.at(var.name))); m_activeVariables.insert(&std::get<Scope::Variable>(varScope.identifiers.at(var.name)));
} }
int const stackHeight = m_stackHeight; (*this)(_funDef.body);
m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size();
bool success = (*this)(_funDef.body);
m_stackHeight = stackHeight;
m_info.stackHeightInfo[&_funDef] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(FunctionCall const& _funCall) vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
{ {
yulAssert(!_funCall.functionName.name.empty(), ""); yulAssert(!_funCall.functionName.name.empty(), "");
bool success = true; vector<YulString> const* parameterTypes = nullptr;
size_t parameters = 0; vector<YulString> const* returnTypes = nullptr;
size_t returns = 0;
bool needsLiteralArguments = false; bool needsLiteralArguments = false;
if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name)) if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name))
{ {
// TODO: compare types, too parameterTypes = &f->parameters;
parameters = f->parameters.size(); returnTypes = &f->returns;
returns = f->returns.size();
if (f->literalArguments) if (f->literalArguments)
needsLiteralArguments = true; needsLiteralArguments = true;
} }
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{ else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
[&](Scope::Variable const&) [&](Scope::Variable const&)
{ {
m_errorReporter.typeError( typeError(
_funCall.functionName.location, _funCall.functionName.location,
"Attempt to call variable instead of function." "Attempt to call variable instead of function."
); );
success = false;
}, },
[&](Scope::Function const& _fun) [&](Scope::Function const& _fun)
{ {
/// TODO: compare types too parameterTypes = &_fun.arguments;
parameters = _fun.arguments.size(); returnTypes = &_fun.returns;
returns = _fun.returns.size();
} }
})) }))
{ {
if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location)) if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location))
m_errorReporter.declarationError(_funCall.functionName.location, "Function not found."); declarationError(_funCall.functionName.location, "Function not found.");
success = false; m_success = false;
} }
if (success) if (parameterTypes && _funCall.arguments.size() != parameterTypes->size())
if (_funCall.arguments.size() != parameters) typeError(
{ _funCall.functionName.location,
m_errorReporter.typeError( "Function expects " +
_funCall.functionName.location, to_string(parameterTypes->size()) +
"Function expects " + " arguments but got " +
to_string(parameters) + to_string(_funCall.arguments.size()) + "."
" arguments but got " + );
to_string(_funCall.arguments.size()) + "."
);
success = false;
}
vector<YulString> argTypes;
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed) for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
{ {
if (!expectExpression(arg)) argTypes.emplace_back(expectExpression(arg));
success = false;
else if (needsLiteralArguments) if (needsLiteralArguments)
{ {
if (!holds_alternative<Literal>(arg)) if (!holds_alternative<Literal>(arg))
m_errorReporter.typeError( typeError(
_funCall.functionName.location, _funCall.functionName.location,
"Function expects direct literals as arguments." "Function expects direct literals as arguments."
); );
else if (!m_dataNames.count(std::get<Literal>(arg).value)) else if (!m_dataNames.count(std::get<Literal>(arg).value))
m_errorReporter.typeError( typeError(
_funCall.functionName.location, _funCall.functionName.location,
"Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"." "Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"."
); );
} }
} }
// Use argument size instead of parameter count to avoid misleading errors.
m_stackHeight += int(returns) - int(_funCall.arguments.size()); if (m_success)
m_info.stackHeightInfo[&_funCall] = m_stackHeight; {
return success; yulAssert(parameterTypes && parameterTypes->size() == argTypes.size(), "");
yulAssert(returnTypes, "");
return *returnTypes;
}
else if (returnTypes)
return vector<YulString>(returnTypes->size(), m_dialect.defaultType);
else
return {};
} }
bool AsmAnalyzer::operator()(If const& _if) void AsmAnalyzer::operator()(If const& _if)
{ {
bool success = true; expectExpression(*_if.condition);
int const initialHeight = m_stackHeight; (*this)(_if.body);
if (!expectExpression(*_if.condition))
success = false;
m_stackHeight = initialHeight;
if (!(*this)(_if.body))
success = false;
m_info.stackHeightInfo[&_if] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(Switch const& _switch) void AsmAnalyzer::operator()(Switch const& _switch)
{ {
yulAssert(_switch.expression, ""); yulAssert(_switch.expression, "");
bool success = true; expectExpression(*_switch.expression);
int const initialHeight = m_stackHeight;
if (!expectExpression(*_switch.expression))
success = false;
YulString caseType; YulString caseType;
bool mismatchingTypes = false; bool mismatchingTypes = false;
@ -379,7 +330,6 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
break; break;
} }
} }
if (mismatchingTypes) if (mismatchingTypes)
m_errorReporter.typeError( m_errorReporter.typeError(
_switch.location, _switch.location,
@ -391,212 +341,103 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
{ {
if (_case.value) if (_case.value)
{ {
int const initialStackHeight = m_stackHeight; // We cannot use "expectExpression" here because *_case.value is not an
bool isCaseValueValid = true; // Expression and would be converted to an Expression otherwise.
// We cannot use "expectExpression" here because *_case.value is not a (*this)(*_case.value);
// Statement and would be converted to a Statement otherwise.
if (!(*this)(*_case.value))
{
isCaseValueValid = false;
success = false;
}
expectDeposit(1, initialStackHeight, _case.value->location);
m_stackHeight--;
// If the case value is not valid, we should not insert it into cases.
yulAssert(isCaseValueValid || m_errorReporter.hasErrors(), "Invalid case value.");
/// Note: the parser ensures there is only one default case /// Note: the parser ensures there is only one default case
if (isCaseValueValid && !cases.insert(valueOfLiteral(*_case.value)).second) if (m_success && !cases.insert(valueOfLiteral(*_case.value)).second)
{ declarationError(_case.location, "Duplicate case defined.");
m_errorReporter.declarationError(
_case.location,
"Duplicate case defined."
);
success = false;
}
} }
if (!(*this)(_case.body)) (*this)(_case.body);
success = false;
} }
m_stackHeight = initialHeight;
m_info.stackHeightInfo[&_switch] = m_stackHeight;
return success;
} }
bool AsmAnalyzer::operator()(ForLoop const& _for) void AsmAnalyzer::operator()(ForLoop const& _for)
{ {
yulAssert(_for.condition, ""); yulAssert(_for.condition, "");
Scope* outerScope = m_currentScope; Scope* outerScope = m_currentScope;
int const initialHeight = m_stackHeight; (*this)(_for.pre);
bool success = true;
if (!(*this)(_for.pre))
success = false;
// The block was closed already, but we re-open it again and stuff the // The block was closed already, but we re-open it again and stuff the
// condition, the body and the post part inside. // condition, the body and the post part inside.
m_stackHeight += scope(&_for.pre).numberOfVariables();
m_currentScope = &scope(&_for.pre); m_currentScope = &scope(&_for.pre);
if (!expectExpression(*_for.condition)) expectExpression(*_for.condition);
success = false;
m_stackHeight--;
// backup outer for-loop & create new state // backup outer for-loop & create new state
auto outerForLoop = m_currentForLoop; auto outerForLoop = m_currentForLoop;
m_currentForLoop = &_for; m_currentForLoop = &_for;
if (!(*this)(_for.body)) (*this)(_for.body);
success = false; (*this)(_for.post);
if (!(*this)(_for.post))
success = false;
m_stackHeight = initialHeight;
m_info.stackHeightInfo[&_for] = m_stackHeight;
m_currentScope = outerScope; m_currentScope = outerScope;
m_currentForLoop = outerForLoop; m_currentForLoop = outerForLoop;
return success;
} }
bool AsmAnalyzer::operator()(Break const& _break) void AsmAnalyzer::operator()(Block const& _block)
{ {
m_info.stackHeightInfo[&_break] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(Continue const& _continue)
{
m_info.stackHeightInfo[&_continue] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(Leave const& _leaveStatement)
{
m_info.stackHeightInfo[&_leaveStatement] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(Block const& _block)
{
bool success = true;
auto previousScope = m_currentScope; auto previousScope = m_currentScope;
m_currentScope = &scope(&_block); m_currentScope = &scope(&_block);
int const initialStackHeight = m_stackHeight;
for (auto const& s: _block.statements) for (auto const& s: _block.statements)
if (!std::visit(*this, s)) std::visit(*this, s);
success = false;
m_stackHeight -= scope(&_block).numberOfVariables();
int const stackDiff = m_stackHeight - initialStackHeight;
if (success && stackDiff != 0)
{
m_errorReporter.declarationError(
_block.location,
"Unbalanced stack at the end of a block: " +
(
stackDiff > 0 ?
to_string(stackDiff) + string(" surplus item(s).") :
to_string(-stackDiff) + string(" missing item(s).")
)
);
success = false;
}
m_info.stackHeightInfo[&_block] = m_stackHeight;
m_currentScope = previousScope; m_currentScope = previousScope;
return success;
} }
bool AsmAnalyzer::expectExpression(Expression const& _expr) YulString AsmAnalyzer::expectExpression(Expression const& _expr)
{ {
bool success = true; vector<YulString> types = std::visit(*this, _expr);
int const initialHeight = m_stackHeight; if (types.size() != 1)
if (!std::visit(*this, _expr)) typeError(
success = false; locationOf(_expr),
if (success && !expectDeposit(1, initialHeight, locationOf(_expr))) "Expected expression to evaluate to one value, but got " +
success = false; to_string(types.size()) +
return success; " values instead."
}
bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
{
if (m_stackHeight - _oldHeight != _deposit)
{
m_errorReporter.typeError(
_location,
"Expected expression to return one item to the stack, but did return " +
to_string(m_stackHeight - _oldHeight) +
" items."
); );
return false; return types.empty() ? m_dialect.defaultType : types.front();
}
return true;
} }
bool AsmAnalyzer::checkAssignment(Identifier const& _variable, size_t _valueSize) void AsmAnalyzer::checkAssignment(Identifier const& _variable)
{ {
yulAssert(!_variable.name.empty(), ""); yulAssert(!_variable.name.empty(), "");
bool success = true;
size_t numErrorsBefore = m_errorReporter.errors().size(); size_t numErrorsBefore = m_errorReporter.errors().size();
size_t variableSize(-1); bool found = false;
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
{ {
// Check that it is a variable // Check that it is a variable
if (!holds_alternative<Scope::Variable>(*var)) if (!holds_alternative<Scope::Variable>(*var))
{ typeError(_variable.location, "Assignment requires variable.");
m_errorReporter.typeError(_variable.location, "Assignment requires variable.");
success = false;
}
else if (!m_activeVariables.count(&std::get<Scope::Variable>(*var))) else if (!m_activeVariables.count(&std::get<Scope::Variable>(*var)))
{ declarationError(
m_errorReporter.declarationError(
_variable.location, _variable.location,
"Variable " + _variable.name.str() + " used before it was declared." "Variable " + _variable.name.str() + " used before it was declared."
); );
success = false; found = true;
}
variableSize = 1;
} }
else if (m_resolver) else if (m_resolver)
{ {
bool insideFunction = m_currentScope->insideFunction(); bool insideFunction = m_currentScope->insideFunction();
variableSize = m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction); size_t variableSize = m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction);
if (variableSize != size_t(-1))
{
found = true;
yulAssert(variableSize == 1, "Invalid stack size of external reference.");
}
} }
if (variableSize == size_t(-1))
if (!found)
{ {
m_success = false;
// Only add message if the callback did not. // Only add message if the callback did not.
if (numErrorsBefore == m_errorReporter.errors().size()) if (numErrorsBefore == m_errorReporter.errors().size())
m_errorReporter.declarationError(_variable.location, "Variable not found or variable not lvalue."); declarationError(_variable.location, "Variable not found or variable not lvalue.");
success = false;
} }
if (_valueSize == size_t(-1))
_valueSize = variableSize == size_t(-1) ? 1 : variableSize;
m_stackHeight -= _valueSize;
if (_valueSize != variableSize && variableSize != size_t(-1))
{
m_errorReporter.typeError(
_variable.location,
"Variable size (" +
to_string(variableSize) +
") and value size (" +
to_string(_valueSize) +
") do not match."
);
success = false;
}
return success;
} }
Scope& AsmAnalyzer::scope(Block const* _block) Scope& AsmAnalyzer::scope(Block const* _block)
@ -608,7 +449,7 @@ Scope& AsmAnalyzer::scope(Block const* _block)
} }
void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location) void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location)
{ {
if (!_type.empty() && !contains(m_dialect.types, _type)) if (!_type.empty() && !m_dialect.types.count(_type))
m_errorReporter.typeError( m_errorReporter.typeError(
_location, _location,
"\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)." "\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)."
@ -633,7 +474,7 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), ""); yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), "");
auto errorForVM = [=](string const& vmKindMessage) { auto errorForVM = [=](string const& vmKindMessage) {
m_errorReporter.typeError( typeError(
_location, _location,
"The \"" + "The \"" +
boost::to_lower_copy(instructionInfo(_instr).name) boost::to_lower_copy(instructionInfo(_instr).name)
@ -694,9 +535,23 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
"incorrect stack access. Because of that they are disallowed in strict assembly. " "incorrect stack access. Because of that they are disallowed in strict assembly. "
"Use functions, \"switch\", \"if\" or \"for\" statements instead." "Use functions, \"switch\", \"if\" or \"for\" statements instead."
); );
m_success = false;
} }
else else
return false; return false;
return true; return true;
} }
void AsmAnalyzer::typeError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.typeError(_location, _description);
m_success = false;
}
void AsmAnalyzer::declarationError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.declarationError(_location, _description);
m_success = false;
}

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