mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8341 from ethereum/develop
Merge develop into release for 0.6.3
This commit is contained in:
commit
8dda952108
@ -62,6 +62,11 @@ defaults:
|
||||
path: build/solc/solc
|
||||
destination: solc
|
||||
|
||||
# compiled tool executable target
|
||||
- artifacts_tools: &artifacts_tools
|
||||
path: build/tools/solidity-upgrade
|
||||
destination: solidity-upgrade
|
||||
|
||||
# compiled executable targets
|
||||
- artifacts_executables: &artifacts_executables
|
||||
root: build
|
||||
@ -257,6 +262,22 @@ jobs:
|
||||
name: Check for C++ coding style
|
||||
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:
|
||||
docker:
|
||||
- image: circleci/node
|
||||
@ -308,6 +329,7 @@ jobs:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- store_artifacts: *artifacts_tools
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu_release: &build_ubuntu1904_release
|
||||
@ -391,8 +413,8 @@ jobs:
|
||||
- run:
|
||||
name: Regression tests
|
||||
command: |
|
||||
git clone https://github.com/ethereum/solidity-fuzzing-corpus /tmp/solidity-fuzzing-corpus
|
||||
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
|
||||
- run: *gitter_notify_failure
|
||||
- run: *gitter_notify_success
|
||||
@ -439,6 +461,7 @@ jobs:
|
||||
- /usr/local/Homebrew
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- store_artifacts: *artifacts_tools
|
||||
- persist_to_workspace: *artifacts_build_dir
|
||||
|
||||
t_osx_soltest:
|
||||
@ -718,6 +741,7 @@ workflows:
|
||||
# DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags
|
||||
- chk_buglist: *workflow_trigger_on_tags
|
||||
- chk_proofs: *workflow_trigger_on_tags
|
||||
- chk_pylint: *workflow_trigger_on_tags
|
||||
|
||||
# build-only
|
||||
- b_docs: *workflow_trigger_on_tags
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -40,6 +40,7 @@ docs/utils/*.pyc
|
||||
/deps/downloads/
|
||||
deps/install
|
||||
deps/cache
|
||||
cmake-build-debug/
|
||||
|
||||
# vim stuff
|
||||
[._]*.sw[a-p]
|
||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# 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)
|
||||
|
||||
include(TestBigEndian)
|
||||
@ -55,6 +55,7 @@ add_subdirectory(libevmasm)
|
||||
add_subdirectory(libyul)
|
||||
add_subdirectory(libsolidity)
|
||||
add_subdirectory(libsolc)
|
||||
add_subdirectory(tools)
|
||||
|
||||
if (NOT EMSCRIPTEN)
|
||||
add_subdirectory(solc)
|
||||
|
23
Changelog.md
23
Changelog.md
@ -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)
|
||||
|
||||
Language Features:
|
||||
|
@ -1,5 +1,5 @@
|
||||
# The Solidity Contract-Oriented Programming Language
|
||||
[](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
You can talk to us on [](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.
|
||||
|
||||
|
@ -71,7 +71,7 @@ test_script:
|
||||
- soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-smt
|
||||
# Skip bytecode compare if private key is not available
|
||||
- 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
|
||||
}
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
|
||||
|
@ -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.")
|
||||
endif ()
|
||||
|
||||
# Use fancy colors in the compiler diagnostics
|
||||
add_compile_options(-fdiagnostics-color)
|
||||
|
||||
# Additional Clang-specific compiler settings.
|
||||
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
|
||||
|
@ -7,5 +7,5 @@ set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE)
|
||||
set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
|
||||
# Use libfuzzer as the fuzzing back-end
|
||||
set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE)
|
||||
# clang/libfuzzer specific flags for ASan 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)
|
||||
# clang/libfuzzer specific flags for UBSan instrumentation
|
||||
set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=undefined -fsanitize=fuzzer-no-link -stdlib=libstdc++" CACHE STRING "Custom compilation flags" FORCE)
|
||||
|
@ -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
|
||||
``keccak256(abi.encodePacked(a, b, c))``. Even though it is not a breaking
|
||||
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))``.
|
||||
|
||||
* Functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` now return
|
||||
@ -455,7 +455,7 @@ New version:
|
||||
uint z = someInteger();
|
||||
x += z;
|
||||
// Throw is now disallowed.
|
||||
require(x > 100);
|
||||
require(x <= 100);
|
||||
int y = -3 >> 1;
|
||||
require(y == -2);
|
||||
do {
|
||||
|
@ -884,5 +884,9 @@
|
||||
"0.6.2": {
|
||||
"bugs": [],
|
||||
"released": "2020-01-27"
|
||||
},
|
||||
"0.6.3": {
|
||||
"bugs": [],
|
||||
"released": "2020-02-18"
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
from pygments_lexer_solidity import SolidityLexer
|
||||
|
||||
# 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
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
@ -24,7 +26,6 @@ import re
|
||||
def setup(sphinx):
|
||||
thisdir = os.path.dirname(os.path.realpath(__file__))
|
||||
sys.path.insert(0, thisdir + '/utils')
|
||||
from pygments_lexer_solidity import SolidityLexer
|
||||
sphinx.add_lexer('Solidity', SolidityLexer())
|
||||
|
||||
sphinx.add_stylesheet('css/custom.css')
|
||||
@ -53,7 +54,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Solidity'
|
||||
copyright = '2016-2019, Ethereum'
|
||||
copyright = '2016-2020, Ethereum'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
@ -29,7 +29,7 @@ value types and strings.
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint constant x = 32**22 + 8;
|
||||
string constant text = "abc";
|
||||
bytes32 constant myHash = keccak256("abc");
|
||||
uint constant X = 32**22 + 8;
|
||||
string constant TEXT = "abc";
|
||||
bytes32 constant MY_HASH = keccak256("abc");
|
||||
}
|
||||
|
@ -717,3 +717,13 @@ in scope in the block that follows.
|
||||
in a catch block or the execution of the try/catch statement itself
|
||||
reverts (for example due to decoding failures as noted above or
|
||||
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.
|
@ -59,7 +59,7 @@ TypeName = ElementaryTypeName
|
||||
|
||||
UserDefinedTypeName = Identifier ( '.' Identifier )*
|
||||
|
||||
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
|
||||
Mapping = 'mapping' '(' ( ElementaryTypeName | UserDefinedTypeName ) '=>' TypeName ')'
|
||||
ArrayTypeName = TypeName '[' Expression? ']'
|
||||
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
|
||||
( 'returns' FunctionTypeParameterList )?
|
||||
@ -96,6 +96,7 @@ Expression
|
||||
| IndexRangeAccess
|
||||
| MemberAccess
|
||||
| FunctionCall
|
||||
| Expression '{' NameValueList '}'
|
||||
| '(' Expression ')'
|
||||
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
|
||||
| Expression '**' Expression
|
||||
|
@ -7,9 +7,8 @@ Mapping Types
|
||||
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables
|
||||
of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``.
|
||||
The ``_KeyType`` can be any
|
||||
built-in value type plus ``bytes`` and ``string``. User-defined
|
||||
or complex types such as contract types, enums, mappings, structs or array types
|
||||
apart from ``bytes`` and ``string`` are not allowed.
|
||||
built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined
|
||||
or complex types, such as mappings, structs or array types are not allowed.
|
||||
``_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
|
||||
|
@ -244,7 +244,7 @@ Input Description
|
||||
// "default", "strip", "debug" and "verboseDebug".
|
||||
// "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
|
||||
// "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)
|
||||
"revertStrings": "default"
|
||||
}
|
||||
@ -474,3 +474,240 @@ Error types
|
||||
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.
|
||||
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 {}
|
||||
}
|
||||
|
15
docs/yul.rst
15
docs/yul.rst
@ -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 |
|
||||
| | | | mem[out...(out+outsize)) returning 0 on error (eg. out of gas) |
|
||||
| | | | 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 |
|
||||
| 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`` |
|
||||
| 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 |
|
||||
| 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)) |
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
@ -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.
|
||||
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:
|
||||
|
||||
Specification of Yul Object
|
||||
|
@ -43,7 +43,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i)
|
||||
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
|
||||
m_deposit += _i.deposit();
|
||||
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().m_modifierDepth = m_currentModifierDepth;
|
||||
return m_items.back();
|
||||
@ -69,7 +69,7 @@ namespace
|
||||
|
||||
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 "";
|
||||
|
||||
auto it = _sourceCodes.find(_location.source->name());
|
||||
@ -97,7 +97,7 @@ public:
|
||||
|
||||
void feed(AssemblyItem const& _item)
|
||||
{
|
||||
if (!_item.location().isEmpty() && _item.location() != m_location)
|
||||
if (_item.location().isValid() && _item.location() != m_location)
|
||||
{
|
||||
flush();
|
||||
m_location = _item.location();
|
||||
@ -141,12 +141,12 @@ public:
|
||||
|
||||
void printLocation()
|
||||
{
|
||||
if (!m_location.source && m_location.isEmpty())
|
||||
if (!m_location.isValid())
|
||||
return;
|
||||
m_out << m_prefix << " /*";
|
||||
if (m_location.source)
|
||||
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 << " " << locationFromSources(m_sourceCodes, m_location);
|
||||
m_out << " */" << endl;
|
||||
@ -197,10 +197,11 @@ string Assembly::assemblyString(StringMap const& _sourceCodes) const
|
||||
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;
|
||||
value["name"] = _name;
|
||||
value["source"] = _source;
|
||||
value["begin"] = _begin;
|
||||
value["end"] = _end;
|
||||
if (!_value.empty())
|
||||
@ -217,65 +218,79 @@ string Assembly::toStringInHex(u256 _value)
|
||||
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& collection = root[".code"] = Json::arrayValue;
|
||||
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())
|
||||
{
|
||||
case Operation:
|
||||
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;
|
||||
case Push:
|
||||
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;
|
||||
case PushString:
|
||||
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;
|
||||
case PushTag:
|
||||
if (i.data() == 0)
|
||||
collection.append(
|
||||
createJsonValue("PUSH [ErrorTag]", i.location().start, i.location().end, ""));
|
||||
createJsonValue("PUSH [ErrorTag]", sourceIndex, i.location().start, i.location().end, ""));
|
||||
else
|
||||
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;
|
||||
case PushSub:
|
||||
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;
|
||||
case PushSubSize:
|
||||
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;
|
||||
case PushProgramSize:
|
||||
collection.append(
|
||||
createJsonValue("PUSHSIZE", i.location().start, i.location().end));
|
||||
createJsonValue("PUSHSIZE", sourceIndex, i.location().start, i.location().end));
|
||||
break;
|
||||
case PushLibraryAddress:
|
||||
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;
|
||||
case PushDeployTimeAddress:
|
||||
collection.append(
|
||||
createJsonValue("PUSHDEPLOYADDRESS", i.location().start, i.location().end)
|
||||
createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end)
|
||||
);
|
||||
break;
|
||||
case Tag:
|
||||
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(
|
||||
createJsonValue("JUMPDEST", i.location().start, i.location().end));
|
||||
createJsonValue("JUMPDEST", sourceIndex, i.location().start, i.location().end));
|
||||
break;
|
||||
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;
|
||||
default:
|
||||
assertThrow(false, InvalidOpcode, "");
|
||||
@ -293,7 +308,7 @@ Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const
|
||||
{
|
||||
std::stringstream hexStr;
|
||||
hexStr << hex << i;
|
||||
data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceCodes);
|
||||
data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ public:
|
||||
|
||||
/// Create a JSON representation of the assembly.
|
||||
Json::Value assemblyJSON(
|
||||
StringMap const& _sourceCodes = StringMap()
|
||||
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>()
|
||||
) const;
|
||||
|
||||
protected:
|
||||
@ -145,7 +145,14 @@ protected:
|
||||
unsigned bytesRequired(unsigned subTagSize) const;
|
||||
|
||||
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);
|
||||
|
||||
protected:
|
||||
|
@ -69,8 +69,8 @@ public:
|
||||
/// 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.
|
||||
/// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect
|
||||
template <class _AssemblyItemIterator>
|
||||
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant);
|
||||
template <class AssemblyItemIterator>
|
||||
AssemblyItemIterator feedItems(AssemblyItemIterator _iterator, AssemblyItemIterator _end, bool _msizeImportant);
|
||||
|
||||
/// @returns the resulting items after optimization.
|
||||
AssemblyItems getOptimizedItems();
|
||||
@ -169,10 +169,10 @@ private:
|
||||
std::map<int, Id> m_targetStack;
|
||||
};
|
||||
|
||||
template <class _AssemblyItemIterator>
|
||||
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
|
||||
_AssemblyItemIterator _iterator,
|
||||
_AssemblyItemIterator _end,
|
||||
template <class AssemblyItemIterator>
|
||||
AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
|
||||
AssemblyItemIterator _iterator,
|
||||
AssemblyItemIterator _end,
|
||||
bool _msizeImportant
|
||||
)
|
||||
{
|
||||
|
@ -179,7 +179,7 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
|
||||
|
||||
/// Helper function for KnownState::reduceToCommonKnowledge, removes everything from
|
||||
/// _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();)
|
||||
if (_other.count(it->first) && _other.at(it->first) == it->second)
|
||||
|
@ -93,10 +93,13 @@ string CharStream::lineAtPosition(int _position) const
|
||||
lineStart = 0;
|
||||
else
|
||||
lineStart++;
|
||||
return m_source.substr(
|
||||
string line = m_source.substr(
|
||||
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
|
||||
|
@ -244,3 +244,12 @@ void ErrorReporter::docstringParsingError(string const& _description)
|
||||
_description
|
||||
);
|
||||
}
|
||||
|
||||
void ErrorReporter::docstringParsingError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
error(
|
||||
Error::Type::DocstringParsingError,
|
||||
_location,
|
||||
_description
|
||||
);
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ public:
|
||||
void fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description);
|
||||
|
||||
void docstringParsingError(std::string const& _description);
|
||||
void docstringParsingError(SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
ErrorList const& errors() const;
|
||||
|
||||
|
@ -51,7 +51,7 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_location.isEmpty())
|
||||
if (_location.isValid())
|
||||
*this << errinfo_sourceLocation(_location);
|
||||
if (!_description.empty())
|
||||
*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(_type)
|
||||
{
|
||||
if (!_location.isEmpty())
|
||||
if (_location.isValid())
|
||||
*this << errinfo_sourceLocation(_location);
|
||||
*this << util::errinfo_comment(_description);
|
||||
}
|
||||
|
@ -28,14 +28,9 @@ using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::langutil;
|
||||
|
||||
int ParserBase::position() const
|
||||
SourceLocation ParserBase::currentLocation() const
|
||||
{
|
||||
return m_scanner->currentLocation().start;
|
||||
}
|
||||
|
||||
int ParserBase::endPosition() const
|
||||
{
|
||||
return m_scanner->currentLocation().end;
|
||||
return m_scanner->currentLocation();
|
||||
}
|
||||
|
||||
Token ParserBase::currentToken() const
|
||||
@ -101,8 +96,8 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN
|
||||
Token tok = m_scanner->currentToken();
|
||||
if (tok != _value)
|
||||
{
|
||||
int startPosition = position();
|
||||
SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()};
|
||||
SourceLocation errorLoc = currentLocation();
|
||||
int startPosition = errorLoc.start;
|
||||
while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS)
|
||||
m_scanner->next();
|
||||
|
||||
@ -150,7 +145,7 @@ void ParserBase::decreaseRecursionDepth()
|
||||
|
||||
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)
|
||||
@ -160,12 +155,12 @@ void ParserBase::parserError(SourceLocation const& _location, string const& _des
|
||||
|
||||
void ParserBase::parserError(string const& _description)
|
||||
{
|
||||
parserError(SourceLocation{position(), endPosition(), source()}, _description);
|
||||
parserError(currentLocation(), _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)
|
||||
|
@ -62,10 +62,8 @@ protected:
|
||||
ParserBase& m_parser;
|
||||
};
|
||||
|
||||
/// Start position of the current token
|
||||
int position() const;
|
||||
/// End position of the current token
|
||||
int endPosition() const;
|
||||
/// Location of the current token
|
||||
SourceLocation currentLocation() const;
|
||||
|
||||
///@{
|
||||
///@name Helper functions
|
||||
|
@ -306,19 +306,24 @@ bool Scanner::tryScanEndOfLine()
|
||||
return false;
|
||||
}
|
||||
|
||||
Token Scanner::scanSingleLineDocComment()
|
||||
int Scanner::scanSingleLineDocComment()
|
||||
{
|
||||
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
|
||||
int endPosition = m_source->position();
|
||||
advance(); //consume the last '/' at ///
|
||||
|
||||
skipWhitespaceExceptUnicodeLinebreak();
|
||||
|
||||
while (!isSourcePastEndOfInput())
|
||||
{
|
||||
endPosition = m_source->position();
|
||||
if (tryScanEndOfLine())
|
||||
{
|
||||
// check if next line is also a documentation comment
|
||||
skipWhitespace();
|
||||
// Check if next line is also a single-line comment.
|
||||
// If any whitespaces were skipped, use source position before.
|
||||
if (!skipWhitespace())
|
||||
endPosition = m_source->position();
|
||||
|
||||
if (!m_source->isPastEndOfInput(3) &&
|
||||
m_source->get(0) == '/' &&
|
||||
m_source->get(1) == '/' &&
|
||||
@ -338,7 +343,7 @@ Token Scanner::scanSingleLineDocComment()
|
||||
advance();
|
||||
}
|
||||
literal.complete();
|
||||
return Token::CommentLiteral;
|
||||
return endPosition;
|
||||
}
|
||||
|
||||
Token Scanner::skipMultiLineComment()
|
||||
@ -426,11 +431,10 @@ Token Scanner::scanSlash()
|
||||
else if (m_char == '/')
|
||||
{
|
||||
// doxygen style /// comment
|
||||
Token comment;
|
||||
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||
comment = scanSingleLineDocComment();
|
||||
m_skippedComments[NextNext].location.end = sourcePos();
|
||||
m_skippedComments[NextNext].token = comment;
|
||||
m_skippedComments[NextNext].location.source = m_source;
|
||||
m_skippedComments[NextNext].token = Token::CommentLiteral;
|
||||
m_skippedComments[NextNext].location.end = scanSingleLineDocComment();
|
||||
return Token::Whitespace;
|
||||
}
|
||||
else
|
||||
@ -454,6 +458,7 @@ Token Scanner::scanSlash()
|
||||
// we actually have a multiline documentation comment
|
||||
Token comment;
|
||||
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||
m_skippedComments[NextNext].location.source = m_source;
|
||||
comment = scanMultiLineDocComment();
|
||||
m_skippedComments[NextNext].location.end = sourcePos();
|
||||
m_skippedComments[NextNext].token = comment;
|
||||
@ -679,6 +684,7 @@ void Scanner::scanToken()
|
||||
}
|
||||
while (token == Token::Whitespace);
|
||||
m_tokens[NextNext].location.end = sourcePos();
|
||||
m_tokens[NextNext].location.source = m_source;
|
||||
m_tokens[NextNext].token = token;
|
||||
m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n);
|
||||
}
|
||||
|
@ -167,12 +167,6 @@ public:
|
||||
/// Do only use in error cases, they are quite expensive.
|
||||
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::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:
|
||||
@ -235,7 +229,8 @@ private:
|
||||
|
||||
Token scanString();
|
||||
Token scanHexString();
|
||||
Token scanSingleLineDocComment();
|
||||
/// Scans a single line comment and returns its corrected end position.
|
||||
int scanSingleLineDocComment();
|
||||
Token scanMultiLineDocComment();
|
||||
/// Scans a slash '/' and depending on the characters returns the appropriate token
|
||||
Token scanSlash();
|
||||
|
@ -56,24 +56,33 @@ struct SourceLocation
|
||||
|
||||
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 start <= _other.start && _other.end <= end;
|
||||
}
|
||||
|
||||
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 _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
|
||||
{
|
||||
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(end <= int(source->source().length()), SourceLocationError, "Invalid source location.");
|
||||
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).
|
||||
inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location)
|
||||
{
|
||||
if (_location.isEmpty())
|
||||
if (!_location.isValid())
|
||||
return _out << "NO_LOCATION_SPECIFIED";
|
||||
|
||||
if (_location.source)
|
||||
_out << _location.source->name();
|
||||
|
||||
_out << "[" << _location.start << "," << _location.end << ")";
|
||||
_out << "[" << _location.start << "," << _location.end << "]";
|
||||
|
||||
return _out;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ SourceReference SourceReferenceExtractor::extract(SourceLocation const* _locatio
|
||||
if (!_location || !_location->source.get()) // Nothing we can extract here
|
||||
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());
|
||||
|
||||
shared_ptr<CharStream> const& source = _location->source;
|
||||
|
@ -69,6 +69,8 @@ void SourceReferenceFormatter::printSourceName(SourceReference const& _ref)
|
||||
{
|
||||
if (_ref.position.line != -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)
|
||||
|
@ -67,13 +67,20 @@ AnsiColorized SourceReferenceFormatterHuman::diagColored() const
|
||||
|
||||
void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ref)
|
||||
{
|
||||
if (_ref.position.line < 0)
|
||||
if (_ref.sourceName.empty())
|
||||
return; // Nothing we can print here
|
||||
|
||||
int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1;
|
||||
|
||||
// line 0: source name
|
||||
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';
|
||||
|
||||
if (!_ref.multiline)
|
||||
|
@ -79,8 +79,9 @@ set(sources
|
||||
codegen/ir/IRGeneratorForStatements.h
|
||||
codegen/ir/IRGenerationContext.cpp
|
||||
codegen/ir/IRGenerationContext.h
|
||||
codegen/ir/IRLValue.cpp
|
||||
codegen/ir/IRLValue.h
|
||||
codegen/ir/IRVariable.cpp
|
||||
codegen/ir/IRVariable.h
|
||||
formal/BMC.cpp
|
||||
formal/BMC.h
|
||||
formal/CHC.cpp
|
||||
|
@ -163,7 +163,7 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
|
||||
std::set<SourceLocation> unreachable;
|
||||
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
||||
[&](CFGNode const* _node, auto&& _addChild) {
|
||||
if (!reachable.count(_node) && !_node->location.isEmpty())
|
||||
if (!reachable.count(_node) && _node->location.isValid())
|
||||
unreachable.insert(_node->location);
|
||||
for (CFGNode const* entry: _node->entries)
|
||||
_addChild(entry);
|
||||
|
@ -73,7 +73,8 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
|
||||
|
||||
void DocStringAnalyser::checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
DocumentedAnnotation& _annotation
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
)
|
||||
{
|
||||
set<string> validParams;
|
||||
@ -86,6 +87,7 @@ void DocStringAnalyser::checkParameters(
|
||||
for (auto i = paramRange.first; i != paramRange.second; ++i)
|
||||
if (!validParams.count(i->second.paramName))
|
||||
appendError(
|
||||
_node.documentation()->location(),
|
||||
"Documented parameter \"" +
|
||||
i->second.paramName +
|
||||
"\" not found in the parameter list of the function."
|
||||
@ -95,37 +97,37 @@ void DocStringAnalyser::checkParameters(
|
||||
|
||||
void DocStringAnalyser::handleConstructor(
|
||||
CallableDeclaration const& _callable,
|
||||
Documented const& _node,
|
||||
DocumentedAnnotation& _annotation
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
)
|
||||
{
|
||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "param"};
|
||||
parseDocStrings(_node, _annotation, validTags, "constructor");
|
||||
checkParameters(_callable, _annotation);
|
||||
checkParameters(_callable, _node, _annotation);
|
||||
}
|
||||
|
||||
void DocStringAnalyser::handleCallable(
|
||||
CallableDeclaration const& _callable,
|
||||
Documented const& _node,
|
||||
DocumentedAnnotation& _annotation
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
)
|
||||
{
|
||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
||||
checkParameters(_callable, _annotation);
|
||||
checkParameters(_callable, _node, _annotation);
|
||||
}
|
||||
|
||||
void DocStringAnalyser::parseDocStrings(
|
||||
Documented const& _node,
|
||||
DocumentedAnnotation& _annotation,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation,
|
||||
set<string> const& _validTags,
|
||||
string const& _nodeName
|
||||
)
|
||||
{
|
||||
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;
|
||||
_annotation.docTags = parser.tags();
|
||||
}
|
||||
@ -134,7 +136,10 @@ void DocStringAnalyser::parseDocStrings(
|
||||
for (auto const& docTag: _annotation.docTags)
|
||||
{
|
||||
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
|
||||
if (docTag.first == "return")
|
||||
{
|
||||
@ -145,14 +150,18 @@ void DocStringAnalyser::parseDocStrings(
|
||||
string firstWord = content.substr(0, content.find_first_of(" \t"));
|
||||
|
||||
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."
|
||||
);
|
||||
else
|
||||
{
|
||||
auto parameter = function->returnParameters().at(returnTagsVisited - 1);
|
||||
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."
|
||||
);
|
||||
}
|
||||
@ -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_errorReporter.docstringParsingError(_description);
|
||||
m_errorReporter.docstringParsingError(_location, _description);
|
||||
}
|
||||
|
@ -51,29 +51,30 @@ private:
|
||||
|
||||
void checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
DocumentedAnnotation& _annotation
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void handleConstructor(
|
||||
CallableDeclaration const& _callable,
|
||||
Documented const& _node,
|
||||
DocumentedAnnotation& _annotation
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void handleCallable(
|
||||
CallableDeclaration const& _callable,
|
||||
Documented const& _node,
|
||||
DocumentedAnnotation& _annotation
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void parseDocStrings(
|
||||
Documented const& _node,
|
||||
DocumentedAnnotation& _annotation,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation,
|
||||
std::set<std::string> const& _validTags,
|
||||
std::string const& _nodeName
|
||||
);
|
||||
|
||||
void appendError(std::string const& _description);
|
||||
void appendError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
bool m_errorOccured = false;
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
|
@ -405,13 +405,13 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
|
||||
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
|
||||
}
|
||||
|
||||
template <class _T>
|
||||
vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMerge)
|
||||
template <class T>
|
||||
vector<T const*> NameAndTypeResolver::cThreeMerge(list<list<T const*>>& _toMerge)
|
||||
{
|
||||
// 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(), "");
|
||||
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;
|
||||
};
|
||||
// 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(), "");
|
||||
if (appearsOnlyAtHead(bases.front()))
|
||||
@ -431,7 +431,7 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
|
||||
return nullptr;
|
||||
};
|
||||
// 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();)
|
||||
{
|
||||
@ -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(); });
|
||||
vector<_T const*> result;
|
||||
_toMerge.remove_if([](list<T const*> const& _bases) { return _bases.empty(); });
|
||||
vector<T const*> result;
|
||||
while (!_toMerge.empty())
|
||||
{
|
||||
_T const* candidate = nextCandidate();
|
||||
T const* candidate = nextCandidate();
|
||||
if (!candidate)
|
||||
return vector<_T const*>();
|
||||
return vector<T const*>();
|
||||
result.push_back(candidate);
|
||||
removeCandidate(candidate);
|
||||
}
|
||||
|
@ -122,8 +122,8 @@ private:
|
||||
void linearizeBaseContracts(ContractDefinition& _contract);
|
||||
/// 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.
|
||||
template <class _T>
|
||||
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge);
|
||||
template <class T>
|
||||
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,
|
||||
/// where nullptr denotes the global scope. Note that structs are not scope since they do
|
||||
|
@ -144,8 +144,8 @@ vector<ContractDefinition const*> resolveDirectBaseContracts(ContractDefinition
|
||||
Declaration const* baseDecl =
|
||||
specifier->name().annotation().referencedDeclaration;
|
||||
auto contract = dynamic_cast<ContractDefinition const*>(baseDecl);
|
||||
solAssert(contract, "contract is null");
|
||||
resolvedContracts.emplace_back(contract);
|
||||
if (contract)
|
||||
resolvedContracts.emplace_back(contract);
|
||||
}
|
||||
|
||||
return resolvedContracts;
|
||||
|
@ -129,6 +129,7 @@ private:
|
||||
class OverrideChecker
|
||||
{
|
||||
public:
|
||||
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
|
||||
|
||||
/// @param _errorReporter provides the error logging functionality.
|
||||
explicit OverrideChecker(langutil::ErrorReporter& _errorReporter):
|
||||
@ -137,12 +138,17 @@ public:
|
||||
|
||||
void check(ContractDefinition const& _contract);
|
||||
|
||||
private:
|
||||
struct CompareByID
|
||||
{
|
||||
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);
|
||||
/// Performs various checks related to @a _overriding overriding @a _super like
|
||||
/// different return type, invalid visibility change, etc.
|
||||
@ -174,15 +180,8 @@ private:
|
||||
/// Resolves an override list of UserDefinedTypeNames to a list of contracts.
|
||||
std::set<ContractDefinition const*, CompareByID> resolveOverrideList(OverrideSpecifier const& _overrides) const;
|
||||
|
||||
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
|
||||
|
||||
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;
|
||||
|
||||
/// Cache for inheritedFunctions().
|
||||
|
@ -133,9 +133,9 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
|
||||
|
||||
bool visit(VariableDeclaration const& _variable) override
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
if (_variable.isConstant())
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
m_currentConstVariable = &_variable;
|
||||
m_constVariables.push_back(&_variable);
|
||||
}
|
||||
|
@ -224,15 +224,36 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _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;
|
||||
TypePointer valueType = _typeName.valueType().annotation().type;
|
||||
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
|
||||
{
|
||||
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.
|
||||
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
||||
|
||||
// Convert value type to storage reference.
|
||||
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)
|
||||
@ -270,87 +291,10 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
m_resolver.warnVariablesNamedLikeInstructions();
|
||||
|
||||
// Errors created in this stage are completely ignored because we do not yet know
|
||||
// the type and size of external identifiers, which would result in false errors.
|
||||
// The only purpose of this step is to fill the inline assembly annotation with
|
||||
// external references.
|
||||
ErrorList 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);
|
||||
};
|
||||
m_yulAnnotation = &_inlineAssembly.annotation();
|
||||
(*this)(_inlineAssembly.operations());
|
||||
m_yulAnnotation = nullptr;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -468,6 +412,90 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
|
||||
_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)
|
||||
{
|
||||
m_errorOccurred = true;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/ast/ASTAnnotations.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <list>
|
||||
@ -45,7 +46,7 @@ class NameAndTypeResolver;
|
||||
* Resolves references to declarations (of variables and types) and also establishes the link
|
||||
* between a return statement and the return parameter list.
|
||||
*/
|
||||
class ReferencesResolver: private ASTConstVisitor
|
||||
class ReferencesResolver: private ASTConstVisitor, private yul::ASTWalker
|
||||
{
|
||||
public:
|
||||
ReferencesResolver(
|
||||
@ -64,6 +65,9 @@ public:
|
||||
bool resolve(ASTNode const& _root);
|
||||
|
||||
private:
|
||||
using yul::ASTWalker::visit;
|
||||
using yul::ASTWalker::operator();
|
||||
|
||||
bool visit(Block const& _block) override;
|
||||
void endVisit(Block const& _block) override;
|
||||
bool visit(ForStatement const& _for) override;
|
||||
@ -77,12 +81,16 @@ private:
|
||||
void endVisit(ModifierDefinition const& _modifierDefinition) override;
|
||||
void endVisit(UserDefinedTypeName 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;
|
||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||
bool visit(Return const& _return) 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.
|
||||
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
@ -105,6 +113,9 @@ private:
|
||||
std::vector<ParameterList const*> m_returnParameters;
|
||||
bool const m_resolveInsideCode;
|
||||
bool m_errorOccurred = false;
|
||||
|
||||
InlineAssemblyAnnotation* m_yulAnnotation = nullptr;
|
||||
bool m_yulInsideFunction = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -68,7 +68,8 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
|
||||
to_string(recommendedVersion.patch()) +
|
||||
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;
|
||||
}
|
||||
|
@ -2154,7 +2154,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
}
|
||||
|
||||
default:
|
||||
m_errorReporter.typeError(_functionCall.location(), "Type is not callable");
|
||||
m_errorReporter.fatalTypeError(_functionCall.location(), "Type is not callable");
|
||||
funcCallAnno.kind = FunctionCallKind::Unset;
|
||||
funcCallAnno.isPure = argumentsArePure;
|
||||
break;
|
||||
@ -2527,7 +2527,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
|
||||
{
|
||||
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
|
||||
{
|
||||
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
|
||||
@ -2535,6 +2543,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
if (auto tt = dynamic_cast<TypeType const*>(exprType))
|
||||
if (tt->actualType()->category() == Type::Category::Enum)
|
||||
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 (magicType->kind() == MagicType::Kind::ABI)
|
||||
@ -2762,7 +2784,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
SecondarySourceLocation ssl;
|
||||
|
||||
for (Declaration const* declaration: annotation.overloadedDeclarations)
|
||||
if (declaration->location().isEmpty())
|
||||
if (!declaration->location().isValid())
|
||||
{
|
||||
// Try to re-construct function definition
|
||||
string description;
|
||||
|
@ -88,8 +88,8 @@ public:
|
||||
}
|
||||
|
||||
/// @returns a copy of the vector containing only the nodes which derive from T.
|
||||
template <class _T>
|
||||
static std::vector<_T const*> filteredNodes(std::vector<ASTPointer<ASTNode>> const& _nodes);
|
||||
template <class T>
|
||||
static std::vector<T const*> filteredNodes(std::vector<ASTPointer<ASTNode>> const& _nodes);
|
||||
|
||||
/// Returns the source code location of this node.
|
||||
SourceLocation const& location() const { return m_location; }
|
||||
@ -121,12 +121,12 @@ private:
|
||||
SourceLocation m_location;
|
||||
};
|
||||
|
||||
template <class _T>
|
||||
std::vector<_T const*> ASTNode::filteredNodes(std::vector<ASTPointer<ASTNode>> const& _nodes)
|
||||
template <class T>
|
||||
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)
|
||||
if (auto const* nt = dynamic_cast<_T const*>(n.get()))
|
||||
if (auto const* nt = dynamic_cast<T const*>(n.get()))
|
||||
ret.push_back(nt);
|
||||
return ret;
|
||||
}
|
||||
@ -345,6 +345,30 @@ private:
|
||||
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.
|
||||
*/
|
||||
@ -362,6 +386,24 @@ protected:
|
||||
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
|
||||
*/
|
||||
@ -385,21 +427,21 @@ protected:
|
||||
* document order. It first visits all struct declarations, then all variable declarations and
|
||||
* finally all function declarations.
|
||||
*/
|
||||
class ContractDefinition: public Declaration, public Documented
|
||||
class ContractDefinition: public Declaration, public StructurallyDocumented
|
||||
{
|
||||
public:
|
||||
ContractDefinition(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
ASTPointer<ASTString> const& _documentation,
|
||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
|
||||
std::vector<ASTPointer<ASTNode>> const& _subNodes,
|
||||
ContractKind _contractKind = ContractKind::Contract,
|
||||
bool _abstract = false
|
||||
):
|
||||
Declaration(_id, _location, _name),
|
||||
Documented(_documentation),
|
||||
StructurallyDocumented(_documentation),
|
||||
m_baseContracts(_baseContracts),
|
||||
m_subNodes(_subNodes),
|
||||
m_contractKind(_contractKind),
|
||||
@ -681,7 +723,7 @@ protected:
|
||||
std::vector<ASTPointer<UserDefinedTypeName>> m_overrides;
|
||||
};
|
||||
|
||||
class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional
|
||||
class FunctionDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
|
||||
{
|
||||
public:
|
||||
FunctionDefinition(
|
||||
@ -693,14 +735,14 @@ public:
|
||||
Token _kind,
|
||||
bool _isVirtual,
|
||||
ASTPointer<OverrideSpecifier> const& _overrides,
|
||||
ASTPointer<ASTString> const& _documentation,
|
||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
|
||||
ASTPointer<ParameterList> const& _returnParameters,
|
||||
ASTPointer<Block> const& _body
|
||||
):
|
||||
CallableDeclaration(_id, _location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters),
|
||||
Documented(_documentation),
|
||||
StructurallyDocumented(_documentation),
|
||||
ImplementationOptional(_body != nullptr),
|
||||
m_stateMutability(_stateMutability),
|
||||
m_kind(_kind),
|
||||
@ -755,7 +797,7 @@ public:
|
||||
{
|
||||
return
|
||||
CallableDeclaration::virtualSemantics() ||
|
||||
annotation().contract->isInterface();
|
||||
(annotation().contract && annotation().contract->isInterface());
|
||||
}
|
||||
private:
|
||||
StateMutability m_stateMutability;
|
||||
@ -870,21 +912,21 @@ private:
|
||||
/**
|
||||
* Definition of a function modifier.
|
||||
*/
|
||||
class ModifierDefinition: public CallableDeclaration, public Documented
|
||||
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented
|
||||
{
|
||||
public:
|
||||
ModifierDefinition(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
ASTPointer<ASTString> const& _documentation,
|
||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
bool _isVirtual,
|
||||
ASTPointer<OverrideSpecifier> const& _overrides,
|
||||
ASTPointer<Block> const& _body
|
||||
):
|
||||
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
|
||||
Documented(_documentation),
|
||||
StructurallyDocumented(_documentation),
|
||||
m_body(_body)
|
||||
{
|
||||
}
|
||||
@ -935,19 +977,19 @@ private:
|
||||
/**
|
||||
* Definition of a (loggable) event.
|
||||
*/
|
||||
class EventDefinition: public CallableDeclaration, public Documented
|
||||
class EventDefinition: public CallableDeclaration, public StructurallyDocumented
|
||||
{
|
||||
public:
|
||||
EventDefinition(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
ASTPointer<ASTString> const& _documentation,
|
||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
bool _anonymous = false
|
||||
):
|
||||
CallableDeclaration(_id, _location, _name, Visibility::Default, _parameters),
|
||||
Documented(_documentation),
|
||||
StructurallyDocumented(_documentation),
|
||||
m_anonymous(_anonymous)
|
||||
{
|
||||
}
|
||||
@ -1110,18 +1152,18 @@ public:
|
||||
Mapping(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ElementaryTypeName> const& _keyType,
|
||||
ASTPointer<TypeName> const& _keyType,
|
||||
ASTPointer<TypeName> const& _valueType
|
||||
):
|
||||
TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {}
|
||||
void accept(ASTVisitor& _visitor) 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; }
|
||||
|
||||
private:
|
||||
ASTPointer<ElementaryTypeName> m_keyType;
|
||||
ASTPointer<TypeName> m_keyType;
|
||||
ASTPointer<TypeName> m_valueType;
|
||||
};
|
||||
|
||||
|
@ -56,9 +56,9 @@ struct DocTag
|
||||
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.
|
||||
std::multimap<std::string, DocTag> docTags;
|
||||
};
|
||||
@ -101,7 +101,7 @@ struct TypeDeclarationAnnotation: DeclarationAnnotation
|
||||
std::string canonicalName;
|
||||
};
|
||||
|
||||
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation
|
||||
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||
{
|
||||
/// List of functions without a body. Can also contain functions from base classes.
|
||||
std::vector<FunctionDefinition const*> unimplementedFunctions;
|
||||
@ -122,15 +122,15 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation
|
||||
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;
|
||||
};
|
||||
|
||||
struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||
struct StatementAnnotation: ASTAnnotation
|
||||
{
|
||||
};
|
||||
|
||||
|
@ -93,6 +93,7 @@ class PrimaryExpression;
|
||||
class Identifier;
|
||||
class ElementaryTypeNameExpression;
|
||||
class Literal;
|
||||
class StructuredDocumentation;
|
||||
|
||||
class VariableScope;
|
||||
|
||||
|
@ -268,7 +268,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
||||
{
|
||||
setJsonNode(_node, "ContractDefinition", {
|
||||
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("abstract", _node.abstract()),
|
||||
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
|
||||
@ -349,7 +349,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
|
||||
{
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
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("stateMutability", stateMutabilityToString(_node.stateMutability())),
|
||||
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
|
||||
@ -400,7 +400,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
|
||||
{
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
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("parameters", toJson(_node.parameterList())),
|
||||
make_pair("virtual", _node.markedVirtual()),
|
||||
@ -427,7 +427,7 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
|
||||
m_inEvent = true;
|
||||
setJsonNode(_node, "EventDefinition", {
|
||||
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("anonymous", _node.isAnonymous())
|
||||
});
|
||||
@ -833,6 +833,17 @@ bool ASTJsonConverter::visit(Literal const& _node)
|
||||
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&)
|
||||
{
|
||||
|
@ -119,6 +119,7 @@ public:
|
||||
bool visit(Identifier const& _node) override;
|
||||
bool visit(ElementaryTypeNameExpression const& _node) override;
|
||||
bool visit(Literal const& _node) override;
|
||||
bool visit(StructuredDocumentation const& _node) override;
|
||||
|
||||
void endVisit(EventDefinition const&) override;
|
||||
|
||||
|
@ -208,6 +208,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
|
||||
return createElementaryTypeNameExpression(_json);
|
||||
if (nodeType == "Literal")
|
||||
return createLiteral(_json);
|
||||
if (nodeType == "StructuredDocumentation")
|
||||
return createDocumentation(_json);
|
||||
else
|
||||
astAssert(false, "Unknown type of ASTNode: " + nodeType);
|
||||
}
|
||||
@ -283,7 +285,7 @@ ASTPointer<ContractDefinition> ASTJsonImporter::createContractDefinition(Json::V
|
||||
return createASTNode<ContractDefinition>(
|
||||
_node,
|
||||
make_shared<ASTString>(_node["name"].asString()),
|
||||
nullOrASTString(_node, "documentation"),
|
||||
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
|
||||
baseContracts,
|
||||
subNodes,
|
||||
contractKind(_node),
|
||||
@ -397,7 +399,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
|
||||
kind,
|
||||
memberAsBool(_node, "virtual"),
|
||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||
nullOrASTString(_node, "documentation"),
|
||||
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
|
||||
createParameterList(member(_node, "parameters")),
|
||||
modifiers,
|
||||
createParameterList(member(_node, "returnParameters")),
|
||||
@ -428,7 +430,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
|
||||
return createASTNode<ModifierDefinition>(
|
||||
_node,
|
||||
memberAsASTString(_node, "name"),
|
||||
nullOrASTString(_node,"documentation"),
|
||||
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
|
||||
createParameterList(member(_node, "parameters")),
|
||||
memberAsBool(_node, "virtual"),
|
||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||
@ -453,7 +455,7 @@ ASTPointer<EventDefinition> ASTJsonImporter::createEventDefinition(Json::Value c
|
||||
return createASTNode<EventDefinition>(
|
||||
_node,
|
||||
memberAsASTString(_node, "name"),
|
||||
nullOrASTString(_node, "documentation"),
|
||||
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
|
||||
createParameterList(member(_node, "parameters")),
|
||||
memberAsBool(_node, "anonymous")
|
||||
);
|
||||
@ -509,7 +511,7 @@ ASTPointer<Mapping> ASTJsonImporter::createMapping(Json::Value const& _node)
|
||||
{
|
||||
return createASTNode<Mapping>(
|
||||
_node,
|
||||
createElementaryTypeName(member(_node, "keyType")),
|
||||
convertJsonToASTNode<TypeName>(member(_node, "keyType")),
|
||||
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 ==========
|
||||
|
||||
Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name)
|
||||
|
@ -119,6 +119,7 @@ private:
|
||||
ASTPointer<Identifier> createIdentifier(Json::Value const& _node);
|
||||
ASTPointer<ElementaryTypeNameExpression> createElementaryTypeNameExpression(Json::Value const& _node);
|
||||
ASTPointer<ASTNode> createLiteral(Json::Value const& _node);
|
||||
ASTPointer<StructuredDocumentation> createDocumentation(Json::Value const& _node);
|
||||
///@}
|
||||
|
||||
// =============== general helper functions ===================
|
||||
|
@ -92,6 +92,7 @@ public:
|
||||
virtual bool visit(Identifier& _node) { return visitNode(_node); }
|
||||
virtual bool visit(ElementaryTypeNameExpression& _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(PragmaDirective& _node) { endVisitNode(_node); }
|
||||
@ -143,6 +144,7 @@ public:
|
||||
virtual void endVisit(Identifier& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(Literal& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(StructuredDocumentation& _node) { endVisitNode(_node); }
|
||||
|
||||
protected:
|
||||
/// 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(ElementaryTypeNameExpression 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(PragmaDirective const& _node) { endVisitNode(_node); }
|
||||
@ -258,6 +261,7 @@ public:
|
||||
virtual void endVisit(Identifier const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(Literal const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(StructuredDocumentation const& _node) { endVisitNode(_node); }
|
||||
|
||||
protected:
|
||||
/// Generic function called by default for each node, to be overridden by derived classes
|
||||
|
@ -67,10 +67,24 @@ void ImportDirective::accept(ASTConstVisitor& _visitor) const
|
||||
_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)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
listAccept(m_baseContracts, _visitor);
|
||||
listAccept(m_subNodes, _visitor);
|
||||
}
|
||||
@ -81,6 +95,8 @@ void ContractDefinition::accept(ASTConstVisitor& _visitor) const
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
listAccept(m_baseContracts, _visitor);
|
||||
listAccept(m_subNodes, _visitor);
|
||||
}
|
||||
@ -203,6 +219,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
if (m_overrides)
|
||||
m_overrides->accept(_visitor);
|
||||
m_parameters->accept(_visitor);
|
||||
@ -219,6 +237,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
if (m_overrides)
|
||||
m_overrides->accept(_visitor);
|
||||
m_parameters->accept(_visitor);
|
||||
@ -263,6 +283,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
m_parameters->accept(_visitor);
|
||||
if (m_overrides)
|
||||
m_overrides->accept(_visitor);
|
||||
@ -275,6 +297,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
m_parameters->accept(_visitor);
|
||||
if (m_overrides)
|
||||
m_overrides->accept(_visitor);
|
||||
@ -308,14 +332,22 @@ void ModifierInvocation::accept(ASTConstVisitor& _visitor) const
|
||||
void EventDefinition::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
m_parameters->accept(_visitor);
|
||||
}
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
void EventDefinition::accept(ASTConstVisitor& _visitor) const
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_documentation)
|
||||
m_documentation->accept(_visitor);
|
||||
m_parameters->accept(_visitor);
|
||||
}
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
|
@ -54,9 +54,11 @@ T AsmJsonImporter::createAsmNode(Json::Value const& _node)
|
||||
{
|
||||
T r;
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
Json::Value AsmJsonImporter::member(Json::Value const& _node, string const& _name)
|
||||
|
@ -151,6 +151,8 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
|
||||
void Type::clearCache() const
|
||||
{
|
||||
m_members.clear();
|
||||
m_stackItems.reset();
|
||||
m_stackSize.reset();
|
||||
}
|
||||
|
||||
void StorageOffsets::computeOffsets(TypePointers const& _types)
|
||||
@ -1701,15 +1703,22 @@ u256 ArrayType::storageSize() const
|
||||
return max<u256>(1, u256(size));
|
||||
}
|
||||
|
||||
unsigned ArrayType::sizeOnStack() const
|
||||
vector<tuple<string, TypePointer>> ArrayType::makeStackItems() const
|
||||
{
|
||||
if (m_location == DataLocation::CallData)
|
||||
// offset [length] (stack top)
|
||||
return 1 + (isDynamicallySized() ? 1 : 0);
|
||||
else
|
||||
// storage slot or memory offset
|
||||
// byte offset inside storage value is omitted
|
||||
return 1;
|
||||
switch (m_location)
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
if (isDynamicallySized())
|
||||
return {std::make_tuple("offset", TypeProvider::uint256()), std::make_tuple("length", TypeProvider::uint256())};
|
||||
else
|
||||
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
|
||||
@ -1891,6 +1900,11 @@ string ArraySliceType::toString(bool _short) const
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
Type::clearCache();
|
||||
@ -2422,12 +2444,17 @@ u256 TupleType::storageSize() const
|
||||
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())
|
||||
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
|
||||
@ -2883,8 +2910,9 @@ unsigned FunctionType::storageBytes() const
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
unsigned size = 0;
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case Kind::External:
|
||||
case Kind::DelegateCall:
|
||||
size = 2;
|
||||
slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))};
|
||||
break;
|
||||
case Kind::BareCall:
|
||||
case Kind::BareCallCode:
|
||||
case Kind::BareDelegateCall:
|
||||
case Kind::BareStaticCall:
|
||||
case Kind::Transfer:
|
||||
case Kind::Send:
|
||||
slots = {make_tuple("address", TypeProvider::address())};
|
||||
break;
|
||||
case Kind::Internal:
|
||||
slots = {make_tuple("functionIdentifier", TypeProvider::uint256())};
|
||||
break;
|
||||
case Kind::ArrayPush:
|
||||
case Kind::ArrayPop:
|
||||
case Kind::ByteArrayPush:
|
||||
case Kind::Transfer:
|
||||
case Kind::Send:
|
||||
size = 1;
|
||||
slots = {make_tuple("slot", TypeProvider::uint256())};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_gasSet)
|
||||
size++;
|
||||
slots.emplace_back("gas", TypeProvider::uint256());
|
||||
if (m_valueSet)
|
||||
size++;
|
||||
slots.emplace_back("value", TypeProvider::uint256());
|
||||
if (m_saltSet)
|
||||
size++;
|
||||
slots.emplace_back("salt", TypeProvider::uint256());
|
||||
if (bound())
|
||||
size += m_parameterTypes.front()->sizeOnStack();
|
||||
return size;
|
||||
for (auto const& [boundName, boundType]: m_parameterTypes.front()->stackItems())
|
||||
slots.emplace_back("self_" + boundName, boundType);
|
||||
return slots;
|
||||
}
|
||||
|
||||
FunctionTypePointer FunctionType::interfaceFunctionType() const
|
||||
@ -3323,13 +3354,13 @@ Type const* FunctionType::selfType() const
|
||||
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)
|
||||
return function->documentation();
|
||||
|
||||
return ASTPointer<ASTString>();
|
||||
return ASTPointer<StructuredDocumentation>();
|
||||
}
|
||||
|
||||
bool FunctionType::padArguments() const
|
||||
@ -3418,12 +3449,12 @@ u256 TypeType::storageSize() const
|
||||
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 (contractType->contractDefinition().isLibrary())
|
||||
return 1;
|
||||
return 0;
|
||||
return {make_tuple("address", TypeProvider::address())};
|
||||
return {};
|
||||
}
|
||||
|
||||
MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _currentScope) const
|
||||
|
@ -259,7 +259,33 @@ public:
|
||||
/// 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.
|
||||
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
|
||||
/// of the size memoryHeadSize().
|
||||
virtual bool hasSimpleZeroValueInMemory() const { return true; }
|
||||
@ -336,9 +362,18 @@ protected:
|
||||
{
|
||||
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.
|
||||
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 canLiveOutsideStorage() const override { return false; }
|
||||
unsigned sizeOnStack() const override { return 0; }
|
||||
|
||||
std::string toString(bool) const override;
|
||||
TypePointer mobileType() const override;
|
||||
@ -571,6 +605,8 @@ public:
|
||||
|
||||
std::string const& value() const { return m_value; }
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||
private:
|
||||
std::string m_value;
|
||||
};
|
||||
@ -725,7 +761,6 @@ public:
|
||||
bool isDynamicallyEncoded() const override;
|
||||
u256 storageSize() const override;
|
||||
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
|
||||
unsigned sizeOnStack() const override;
|
||||
std::string toString(bool _short) const override;
|
||||
std::string canonicalName() const override;
|
||||
std::string signatureInExternalFunction(bool _structsByName) const override;
|
||||
@ -756,6 +791,8 @@ public:
|
||||
|
||||
void clearCache() const override;
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
private:
|
||||
/// String is interpreted as a subtype of Bytes.
|
||||
enum class ArrayKind { Ordinary, Bytes, String };
|
||||
@ -785,7 +822,6 @@ public:
|
||||
bool isDynamicallySized() const override { return true; }
|
||||
bool isDynamicallyEncoded() const override { return true; }
|
||||
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
|
||||
unsigned sizeOnStack() const override { return 2; }
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
/// @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, ""); }
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
private:
|
||||
ArrayType const& m_arrayType;
|
||||
};
|
||||
@ -825,7 +863,6 @@ public:
|
||||
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
|
||||
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
|
||||
bool canLiveOutsideStorage() const override { return !isSuper(); }
|
||||
unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
|
||||
bool isValueType() const override { return !isSuper(); }
|
||||
std::string toString(bool _short) 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
|
||||
/// offsets in storage.
|
||||
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
private:
|
||||
ContractDefinition const& m_contract;
|
||||
/// 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; }
|
||||
u256 storageSize() const override;
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
unsigned sizeOnStack() const override;
|
||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||
TypePointer mobileType() const override;
|
||||
/// 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; }
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
private:
|
||||
std::vector<TypePointer> const m_components;
|
||||
};
|
||||
@ -1158,7 +1197,6 @@ public:
|
||||
unsigned storageBytes() const override;
|
||||
bool isValueType() const override { return true; }
|
||||
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||
unsigned sizeOnStack() const override;
|
||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
TypePointer encodingType() const override;
|
||||
@ -1208,9 +1246,9 @@ public:
|
||||
/// Currently, this will only return true for internal functions like keccak and ecrecover.
|
||||
bool isPure() const;
|
||||
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
|
||||
/// @return A shared pointer of an ASTString.
|
||||
/// Can contain a nullptr in which case indicates absence of documentation
|
||||
ASTPointer<ASTString> documentation() const;
|
||||
/// @return A shared pointer of StructuredDocumentation.
|
||||
/// Can contain a nullptr in which case indicates absence of documentation.
|
||||
ASTPointer<StructuredDocumentation> documentation() const;
|
||||
|
||||
/// 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
|
||||
@ -1252,6 +1290,8 @@ public:
|
||||
/// @param _bound if true, the function type is set to be bound.
|
||||
FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const;
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
private:
|
||||
static TypePointers parseElementaryTypeVector(strings const& _types);
|
||||
|
||||
@ -1321,12 +1361,13 @@ public:
|
||||
bool canBeStored() const override { return false; }
|
||||
u256 storageSize() const override;
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
unsigned sizeOnStack() const override;
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
private:
|
||||
TypePointer m_actualType;
|
||||
};
|
||||
@ -1346,12 +1387,13 @@ public:
|
||||
bool canBeStored() const override { return false; }
|
||||
u256 storageSize() const override;
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
unsigned sizeOnStack() const override { return 0; }
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||
private:
|
||||
TypePointers m_parameterTypes;
|
||||
};
|
||||
@ -1374,11 +1416,12 @@ public:
|
||||
bool canBeStored() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
unsigned sizeOnStack() const override { return 0; }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||
private:
|
||||
SourceUnit const& m_sourceUnit;
|
||||
};
|
||||
@ -1413,7 +1456,6 @@ public:
|
||||
bool canBeStored() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
unsigned sizeOnStack() const override { return 0; }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
@ -1422,6 +1464,8 @@ public:
|
||||
|
||||
TypePointer typeArgument() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||
private:
|
||||
Kind m_kind;
|
||||
/// Contract type used for contract metadata magic.
|
||||
@ -1445,7 +1489,6 @@ public:
|
||||
bool canBeStored() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
bool isValueType() const override { return true; }
|
||||
unsigned sizeOnStack() const override { return 1; }
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
std::string toString(bool) const override { return "inaccessible dynamic type"; }
|
||||
TypePointer decodingType() const override;
|
||||
|
@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
|
||||
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
|
||||
if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString> }
|
||||
<decodeElements>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short"));
|
||||
templ("minimumSize", to_string(headSize(decodingTypes)));
|
||||
|
||||
string decodeElements;
|
||||
@ -211,7 +212,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
R"(
|
||||
{
|
||||
let offset := <load>(add(headStart, <pos>))
|
||||
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if gt(offset, 0xffffffffffffffff) { <revertString> }
|
||||
<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("values", boost::algorithm::join(valueNamesLocal, ", "));
|
||||
elementTempl("pos", to_string(headPos));
|
||||
@ -453,12 +456,14 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
|
||||
else
|
||||
templ("scaleLengthByStride",
|
||||
Whiskers(R"(
|
||||
if gt(length, <maxLength>) { revert(0, 0) }
|
||||
if gt(length, <maxLength>) { <revertString> }
|
||||
length := mul(length, <stride>)
|
||||
)")
|
||||
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
|
||||
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
|
||||
("revertString", revertReasonIfDebug("ABI encoding: array data too long"))
|
||||
.render()
|
||||
// TODO add revert test
|
||||
);
|
||||
templ("readableTypeNameFrom", _from.toString(true));
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
@ -1124,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
R"(
|
||||
// <readableTypeName>
|
||||
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>
|
||||
array := <allocate>(<allocationSize>(length))
|
||||
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("readableTypeName", _type.toString(true));
|
||||
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
||||
@ -1159,7 +1166,12 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
}
|
||||
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("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
|
||||
@ -1184,11 +1196,11 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
templ = R"(
|
||||
// <readableTypeName>
|
||||
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)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
|
||||
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
|
||||
@ -1196,10 +1208,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> arrayPos {
|
||||
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};
|
||||
// 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("readableTypeName", _type.toString(true));
|
||||
w("stride", toCompactHexWithPrefix(_type.calldataStride()));
|
||||
@ -1223,17 +1239,20 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
|
||||
Whiskers templ(
|
||||
R"(
|
||||
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)
|
||||
array := <allocate>(<allocationSize>(length))
|
||||
mstore(array, length)
|
||||
let src := add(offset, 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)
|
||||
}
|
||||
)"
|
||||
);
|
||||
// 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("load", _fromMemory ? "mload" : "calldataload");
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
@ -1254,10 +1273,12 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
|
||||
Whiskers w{R"(
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> value {
|
||||
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) }
|
||||
if slt(sub(end, offset), <minimumSize>) { <revertString> }
|
||||
value := offset
|
||||
}
|
||||
)"};
|
||||
// TODO add test
|
||||
w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short"));
|
||||
w("functionName", functionName);
|
||||
w("readableTypeName", _type.toString(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"(
|
||||
// <readableTypeName>
|
||||
function <functionName>(headStart, end) -> value {
|
||||
if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
|
||||
if slt(sub(end, headStart), <minimumSize>) { <revertString> }
|
||||
value := <allocate>(<memorySize>)
|
||||
<#members>
|
||||
{
|
||||
@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
</members>
|
||||
}
|
||||
)");
|
||||
// TODO add test
|
||||
templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short"));
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeName", _type.toString(true));
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
@ -1305,7 +1328,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
dynamic ?
|
||||
R"(
|
||||
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))
|
||||
)" :
|
||||
R"(
|
||||
@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
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("pos", to_string(headPos));
|
||||
memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
|
||||
@ -1380,7 +1405,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
Whiskers w(R"(
|
||||
function <functionName>(base_ref, ptr) -> <return> {
|
||||
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)
|
||||
<handleLength>
|
||||
}
|
||||
@ -1392,9 +1417,15 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
w("handleLength", Whiskers(R"(
|
||||
length := calldataload(value)
|
||||
value := add(value, 0x20)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render());
|
||||
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { <revertStringStride> }
|
||||
)")
|
||||
("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");
|
||||
}
|
||||
else
|
||||
@ -1404,6 +1435,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
}
|
||||
w("neededLength", toCompactHexWithPrefix(tailSize));
|
||||
w("functionName", functionName);
|
||||
w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset"));
|
||||
return w.render();
|
||||
}
|
||||
else if (_type.isValueType())
|
||||
@ -1493,3 +1525,8 @@ size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions cons
|
||||
else
|
||||
return _type.sizeOnStack();
|
||||
}
|
||||
|
||||
std::string ABIFunctions::revertReasonIfDebug(std::string const& _message)
|
||||
{
|
||||
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <functional>
|
||||
@ -55,11 +57,13 @@ class ABIFunctions
|
||||
public:
|
||||
explicit ABIFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
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
|
||||
@ -200,7 +204,7 @@ private:
|
||||
/// @param _fromMemory if decoding from memory instead of from calldata
|
||||
/// @param _forUseOnStack if the decoded value is stored on stack or in memory.
|
||||
std::string abiDecodingFunction(
|
||||
Type const& _Type,
|
||||
Type const& _type,
|
||||
bool _fromMemory,
|
||||
bool _forUseOnStack
|
||||
);
|
||||
@ -249,7 +253,12 @@ private:
|
||||
/// is true), for which it is two.
|
||||
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;
|
||||
RevertStrings const m_revertStrings;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
YulUtilFunctions m_utils;
|
||||
|
@ -35,7 +35,7 @@ void Compiler::compileContract(
|
||||
bytes const& _metadata
|
||||
)
|
||||
{
|
||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings);
|
||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings);
|
||||
runtimeCompiler.compileContract(_contract, _otherCompilers);
|
||||
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
|
||||
// settings accordingly.
|
||||
creationSettings.expectedExecutionsPerDeployment = 1;
|
||||
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings);
|
||||
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings);
|
||||
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
|
||||
|
||||
m_context.optimise(m_optimiserSettings);
|
||||
|
@ -37,9 +37,8 @@ class Compiler
|
||||
public:
|
||||
Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
|
||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_runtimeContext(_evmVersion),
|
||||
m_context(_evmVersion, &m_runtimeContext)
|
||||
m_runtimeContext(_evmVersion, _revertStrings),
|
||||
m_context(_evmVersion, _revertStrings, &m_runtimeContext)
|
||||
{ }
|
||||
|
||||
/// Compiles a contract.
|
||||
@ -65,9 +64,9 @@ public:
|
||||
return m_context.assemblyString(_sourceCodes);
|
||||
}
|
||||
/// @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
|
||||
evmasm::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); }
|
||||
@ -80,7 +79,6 @@ public:
|
||||
|
||||
private:
|
||||
OptimiserSettings const m_optimiserSettings;
|
||||
RevertStrings const m_revertStrings;
|
||||
CompilerContext m_runtimeContext;
|
||||
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
|
||||
CompilerContext m_context;
|
||||
|
@ -37,6 +37,8 @@
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/YulString.h>
|
||||
|
||||
#include <libsolutil/Whiskers.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
@ -55,6 +57,7 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::evmasm;
|
||||
using namespace solidity::frontend;
|
||||
using namespace solidity::langutil;
|
||||
@ -296,12 +299,13 @@ CompilerContext& CompilerContext::appendConditionalInvalid()
|
||||
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())
|
||||
appendInlineAssembly(R"({
|
||||
@ -311,9 +315,7 @@ CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnDat
|
||||
}
|
||||
})", {"condition"});
|
||||
else
|
||||
appendInlineAssembly(R"({
|
||||
if condition { revert(0, 0) }
|
||||
})", {"condition"});
|
||||
appendInlineAssembly("{ if condition { " + revertReasonIfDebug(_message) + " } }", {"condition"});
|
||||
*this << Instruction::POP;
|
||||
return *this;
|
||||
}
|
||||
@ -385,9 +387,9 @@ void CompilerContext::appendInlineAssembly(
|
||||
ErrorReporter errorReporter(errors);
|
||||
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--"));
|
||||
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
|
||||
cout << yul::AsmPrinter()(*parserResult) << endl;
|
||||
cout << yul::AsmPrinter(&dialect)(*parserResult) << endl;
|
||||
#endif
|
||||
|
||||
auto reportError = [&](string const& _context)
|
||||
@ -421,24 +423,18 @@ void CompilerContext::appendInlineAssembly(
|
||||
// so we essentially only optimize the ABI functions.
|
||||
if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
|
||||
{
|
||||
bool const isCreation = m_runtimeContext != nullptr;
|
||||
yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment);
|
||||
yul::Object obj;
|
||||
obj.code = parserResult;
|
||||
obj.analysisInfo = make_shared<yul::AsmAnalysisInfo>(analysisInfo);
|
||||
yul::OptimiserSuite::run(
|
||||
dialect,
|
||||
&meter,
|
||||
obj,
|
||||
_optimiserSettings.optimizeStackAllocation,
|
||||
externallyUsedIdentifiers
|
||||
);
|
||||
|
||||
optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers);
|
||||
|
||||
analysisInfo = std::move(*obj.analysisInfo);
|
||||
parserResult = std::move(obj.code);
|
||||
|
||||
#ifdef SOL_OUTPUT_ASM
|
||||
cout << "After optimizer:" << endl;
|
||||
cout << yul::AsmPrinter()(*parserResult) << endl;
|
||||
cout << yul::AsmPrinter(&dialect)(*parserResult) << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -460,6 +456,29 @@ void CompilerContext::appendInlineAssembly(
|
||||
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& _function,
|
||||
vector<ContractDefinition const*>::const_iterator _searchStart
|
||||
@ -488,6 +507,11 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::superContract
|
||||
return ++it;
|
||||
}
|
||||
|
||||
string CompilerContext::revertReasonIfDebug(string const& _message)
|
||||
{
|
||||
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
|
||||
}
|
||||
|
||||
void CompilerContext::updateSourceLocation()
|
||||
{
|
||||
m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location());
|
||||
|
@ -27,13 +27,18 @@
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <libsolutil/Common.h>
|
||||
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
#include <functional>
|
||||
#include <ostream>
|
||||
#include <stack>
|
||||
@ -51,11 +56,16 @@ class Compiler;
|
||||
class CompilerContext
|
||||
{
|
||||
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_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_runtimeContext(_runtimeContext),
|
||||
m_abiFunctions(m_evmVersion)
|
||||
m_abiFunctions(m_evmVersion, m_revertStrings)
|
||||
{
|
||||
if (m_runtimeContext)
|
||||
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
||||
@ -160,12 +170,14 @@ public:
|
||||
/// Appends a conditional INVALID instruction
|
||||
CompilerContext& appendConditionalInvalid();
|
||||
/// 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
|
||||
/// empty string. Consumes the condition.
|
||||
/// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward
|
||||
/// 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
|
||||
CompilerContext& appendJumpTo(
|
||||
evmasm::AssemblyItem const& _tag,
|
||||
@ -219,6 +231,13 @@ public:
|
||||
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.
|
||||
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
|
||||
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(); }
|
||||
@ -263,6 +282,8 @@ public:
|
||||
|
||||
void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; }
|
||||
|
||||
RevertStrings revertStrings() const { return m_revertStrings; }
|
||||
|
||||
private:
|
||||
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
|
||||
/// the first function definition that is overwritten by _function.
|
||||
@ -312,6 +333,7 @@ private:
|
||||
evmasm::AssemblyPointer m_asm;
|
||||
/// Version of the EVM to compile against.
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings const m_revertStrings;
|
||||
/// Activated experimental features.
|
||||
std::set<ExperimentalFeature> m_experimentalFeatures;
|
||||
/// Other already compiled contracts to be used in contract creation calls.
|
||||
|
@ -130,9 +130,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||
// returns the absolute offset of the tail in "base_ref"
|
||||
m_context.appendInlineAssembly(Whiskers(R"({
|
||||
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)
|
||||
})")("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>
|
||||
|
||||
if (!_type.isDynamicallySized())
|
||||
@ -158,9 +161,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||
Whiskers(R"({
|
||||
length := calldataload(base_ref)
|
||||
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) }
|
||||
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(),
|
||||
})")
|
||||
("calldataStride", toCompactHexWithPrefix(calldataStride))
|
||||
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
|
||||
.render(),
|
||||
{"base_ref", "length"}
|
||||
);
|
||||
// stack layout: <absolute_offset_of_tail> <length>
|
||||
@ -277,7 +283,13 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
size_t encodedSize = 0;
|
||||
for (auto const& t: _typeParameters)
|
||||
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::SWAP1;
|
||||
@ -319,19 +331,23 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
// Check that the data pointer is valid and that length times
|
||||
// item size is still inside the range.
|
||||
Whiskers templ(R"({
|
||||
if gt(ptr, 0x100000000) { revert(0, 0) }
|
||||
if gt(ptr, 0x100000000) { <revertStringPointer> }
|
||||
ptr := add(ptr, base_offset)
|
||||
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)
|
||||
if or(
|
||||
gt(array_length, 0x100000000),
|
||||
gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
|
||||
) { revert(0, 0) }
|
||||
) { <revertStringLength> }
|
||||
mstore(dst, array_length)
|
||||
dst := add(dst, 0x20)
|
||||
})");
|
||||
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"});
|
||||
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata
|
||||
m_context << Instruction::SWAP1;
|
||||
@ -359,24 +375,33 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory);
|
||||
m_context << Instruction::SWAP1;
|
||||
// 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;
|
||||
// stack: input_end base_offset next_pointer array_head_ptr
|
||||
m_context.appendInlineAssembly(
|
||||
"{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
|
||||
{"input_end", "base_offset", "next_ptr", "array_head_ptr"}
|
||||
);
|
||||
m_context.appendInlineAssembly(Whiskers(R"({
|
||||
if gt(add(array_head_ptr, 0x20), input_end) { <revertString> }
|
||||
})")
|
||||
("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid head pointer"))
|
||||
.render(), {"input_end", "base_offset", "next_ptr", "array_head_ptr"});
|
||||
|
||||
// retrieve length
|
||||
loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true);
|
||||
// stack: input_end base_offset next_pointer array_length data_pointer
|
||||
m_context << Instruction::SWAP2;
|
||||
// stack: input_end base_offset data_pointer array_length next_pointer
|
||||
m_context.appendInlineAssembly(R"({
|
||||
m_context.appendInlineAssembly(Whiskers(R"({
|
||||
if or(
|
||||
gt(array_length, 0x100000000),
|
||||
gt(add(data_ptr, mul(array_length, )" + to_string(arrayType.calldataStride()) + R"()), input_end)
|
||||
) { revert(0, 0) }
|
||||
})", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
|
||||
) { <revertString> }
|
||||
})")
|
||||
("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data pointer"))
|
||||
.render(), {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -605,7 +630,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||
Whiskers templ(R"({
|
||||
let size := mul(length, <element_size>)
|
||||
// cheap way of zero-initializing a memory range
|
||||
codecopy(memptr, codesize(), size)
|
||||
calldatacopy(memptr, calldatasize(), size)
|
||||
memptr := add(memptr, size)
|
||||
})");
|
||||
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.");
|
||||
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
|
||||
if (_asPartOfArgumentDecoding)
|
||||
// TODO: error message?
|
||||
m_context.appendConditionalRevert();
|
||||
m_context.appendConditionalRevert(false, "Enum out of range");
|
||||
else
|
||||
m_context.appendConditionalInvalid();
|
||||
enumOverflowCheckPending = false;
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
|
||||
@ -34,7 +35,8 @@ class Type; // forward
|
||||
class CompilerUtils
|
||||
{
|
||||
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;
|
||||
void initialiseFreeMemoryPointer();
|
||||
|
@ -20,6 +20,7 @@
|
||||
* Solidity compiler.
|
||||
*/
|
||||
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTUtils.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
@ -27,7 +28,16 @@
|
||||
#include <libsolidity/codegen/ContractCompiler.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/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/Assembly.h>
|
||||
@ -38,6 +48,7 @@
|
||||
#include <libsolutil/Whiskers.h>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std;
|
||||
@ -128,8 +139,7 @@ void ContractCompiler::appendCallValueCheck()
|
||||
{
|
||||
// Throw if function is not payable but call contained ether.
|
||||
m_context << Instruction::CALLVALUE;
|
||||
// TODO: error message?
|
||||
m_context.appendConditionalRevert();
|
||||
m_context.appendConditionalRevert(false, "Ether sent to non-payable function");
|
||||
}
|
||||
|
||||
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
|
||||
@ -409,7 +419,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
m_context << notFoundOrReceiveEther;
|
||||
|
||||
if (!fallback && !etherReceiver)
|
||||
m_context.appendRevert();
|
||||
m_context.appendRevert("Contract does not have fallback nor receive functions");
|
||||
else
|
||||
{
|
||||
if (etherReceiver)
|
||||
@ -440,8 +450,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
m_context << Instruction::STOP;
|
||||
}
|
||||
else
|
||||
// TODO: error message here?
|
||||
m_context.appendRevert();
|
||||
m_context.appendRevert("Unknown signature and no fallback defined");
|
||||
}
|
||||
|
||||
|
||||
@ -457,7 +466,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
// If the function is not a view function and is called without DELEGATECALL,
|
||||
// we revert.
|
||||
m_context << dupInstruction(2);
|
||||
m_context.appendConditionalRevert();
|
||||
m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL");
|
||||
}
|
||||
m_context.setStackOffset(0);
|
||||
// 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.");
|
||||
for (VariableDeclaration const* variable: _contract.stateVariables())
|
||||
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)
|
||||
@ -530,10 +539,10 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
|
||||
m_continueTags.clear();
|
||||
|
||||
if (_variableDeclaration.isConstant())
|
||||
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
|
||||
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
|
||||
.appendConstStateVariableAccessor(_variableDeclaration);
|
||||
else
|
||||
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
|
||||
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals)
|
||||
.appendStateVariableAccessor(_variableDeclaration);
|
||||
|
||||
return false;
|
||||
@ -790,10 +799,36 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
_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(
|
||||
_inlineAssembly.operations(),
|
||||
*_inlineAssembly.annotation().analysisInfo,
|
||||
*code,
|
||||
*analysisInfo,
|
||||
*m_context.assemblyPtr(),
|
||||
m_context.evmVersion(),
|
||||
identifierAccess,
|
||||
@ -954,7 +989,8 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
|
||||
revert(0, returndatasize())
|
||||
})");
|
||||
else
|
||||
m_context.appendRevert();
|
||||
// Since both returndata and revert are >=byzantium, this should be unreachable.
|
||||
solAssert(false, "");
|
||||
}
|
||||
m_context << endTag;
|
||||
}
|
||||
@ -1316,7 +1352,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con
|
||||
|
||||
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);
|
||||
if (_targetType)
|
||||
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);
|
||||
|
@ -43,11 +43,9 @@ public:
|
||||
explicit ContractCompiler(
|
||||
ContractCompiler* _runtimeCompiler,
|
||||
CompilerContext& _context,
|
||||
OptimiserSettings _optimiserSettings,
|
||||
RevertStrings _revertStrings
|
||||
OptimiserSettings _optimiserSettings
|
||||
):
|
||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_runtimeCompiler(_runtimeCompiler),
|
||||
m_context(_context)
|
||||
{
|
||||
@ -140,7 +138,6 @@ private:
|
||||
void storeStackHeight(ASTNode const* _node);
|
||||
|
||||
OptimiserSettings const m_optimiserSettings;
|
||||
RevertStrings const m_revertStrings;
|
||||
/// Pointer to the runtime compiler in case this is a creation compiler.
|
||||
ContractCompiler* m_runtimeCompiler = nullptr;
|
||||
CompilerContext& m_context;
|
||||
|
@ -677,7 +677,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
m_context << errorCase;
|
||||
}
|
||||
else
|
||||
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
|
||||
m_context.appendConditionalRevert(true);
|
||||
break;
|
||||
}
|
||||
@ -734,8 +733,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
if (function.kind() == FunctionType::Kind::Transfer)
|
||||
{
|
||||
// Check if zero (out of stack or not enough balance).
|
||||
// TODO: bubble up here, but might also be different error.
|
||||
m_context << Instruction::ISZERO;
|
||||
// Revert message bubbles up.
|
||||
m_context.appendConditionalRevert(true);
|
||||
}
|
||||
break;
|
||||
@ -752,7 +751,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
// function-sel(Error(string)) + encoding
|
||||
solAssert(arguments.size() == 1, "");
|
||||
solAssert(function.parameterTypes().size() == 1, "");
|
||||
if (m_revertStrings == RevertStrings::Strip)
|
||||
if (m_context.revertStrings() == RevertStrings::Strip)
|
||||
{
|
||||
if (!arguments.front()->annotation().isPure)
|
||||
{
|
||||
@ -1032,7 +1031,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -1041,7 +1040,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
// function call.
|
||||
solAssert(arguments.size() == 2, "");
|
||||
solAssert(function.kind() == FunctionType::Kind::Require, "");
|
||||
if (m_revertStrings == RevertStrings::Strip)
|
||||
if (m_context.revertStrings() == RevertStrings::Strip)
|
||||
{
|
||||
if (!arguments.at(1)->annotation().isPure)
|
||||
{
|
||||
@ -1304,6 +1303,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
switch (funType->kind())
|
||||
{
|
||||
case FunctionType::Kind::Declaration:
|
||||
break;
|
||||
case FunctionType::Kind::Internal:
|
||||
// We do not visit the expression here on purpose, because in the case of an
|
||||
// internal library function call, this would push the library address forcing
|
||||
@ -1821,12 +1822,16 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
|
||||
|
||||
m_context.appendInlineAssembly(
|
||||
Whiskers(R"({
|
||||
if gt(sliceStart, sliceEnd) { revert(0, 0) }
|
||||
if gt(sliceEnd, length) { revert(0, 0) }
|
||||
if gt(sliceStart, sliceEnd) { <revertStringStartEnd> }
|
||||
if gt(sliceEnd, length) { <revertStringEndLength> }
|
||||
|
||||
offset := add(offset, mul(sliceStart, <stride>))
|
||||
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"}
|
||||
);
|
||||
|
||||
@ -2299,8 +2304,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall)
|
||||
{
|
||||
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
|
||||
// TODO: error message?
|
||||
m_context.appendConditionalRevert();
|
||||
m_context.appendConditionalRevert(false, "Target contract does not contain code");
|
||||
existenceChecked = true;
|
||||
}
|
||||
|
||||
|
@ -58,10 +58,8 @@ class ExpressionCompiler: private ASTConstVisitor
|
||||
public:
|
||||
ExpressionCompiler(
|
||||
CompilerContext& _compilerContext,
|
||||
RevertStrings _revertStrings,
|
||||
bool _optimiseOrderLiterals
|
||||
):
|
||||
m_revertStrings(_revertStrings),
|
||||
m_optimiseOrderLiterals(_optimiseOrderLiterals),
|
||||
m_context(_compilerContext)
|
||||
{}
|
||||
@ -127,8 +125,8 @@ private:
|
||||
void setLValueToStorageItem(Expression const& _expression);
|
||||
/// Sets the current LValue to a new LValue constructed from the arguments.
|
||||
/// Also retrieves the value if it was not requested by @a _expression.
|
||||
template <class _LValueType, class... _Arguments>
|
||||
void setLValue(Expression const& _expression, _Arguments const&... _arguments);
|
||||
template <class LValueType, class... Arguments>
|
||||
void setLValue(Expression const& _expression, Arguments const&... _arguments);
|
||||
|
||||
/// @returns true if the operator applied to the given type requires a cleanup prior to the
|
||||
/// operation.
|
||||
@ -139,18 +137,17 @@ private:
|
||||
/// @returns the CompilerUtils object containing the current context.
|
||||
CompilerUtils utils();
|
||||
|
||||
RevertStrings m_revertStrings;
|
||||
bool m_optimiseOrderLiterals;
|
||||
CompilerContext& m_context;
|
||||
std::unique_ptr<LValue> m_currentLValue;
|
||||
|
||||
};
|
||||
|
||||
template <class _LValueType, class... _Arguments>
|
||||
void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments const&... _arguments)
|
||||
template <class LValueType, class... Arguments>
|
||||
void ExpressionCompiler::setLValue(Expression const& _expression, Arguments const&... _arguments)
|
||||
{
|
||||
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)
|
||||
m_currentLValue = move(lvalue);
|
||||
else
|
||||
|
@ -134,7 +134,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
||||
FixedHash<hashHeaderSize>(keccak256("Error(string)"))
|
||||
)) << (256 - hashHeaderSize * byteSize);
|
||||
|
||||
string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector)
|
||||
string const encodeFunc = ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector)
|
||||
.tupleEncoder(
|
||||
{_messageType},
|
||||
{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)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
@ -1627,7 +1679,7 @@ string YulUtilFunctions::packedHashFunction(
|
||||
templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack));
|
||||
templ("comma", sizeOnStack > 0 ? "," : "");
|
||||
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();
|
||||
});
|
||||
}
|
||||
@ -1791,6 +1843,44 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
||||
"_to_" +
|
||||
_to.identifier();
|
||||
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(
|
||||
_from.category() == Type::Category::StringLiteral,
|
||||
"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 {
|
||||
value := <load>(memPtr)
|
||||
<?needsValidation>
|
||||
value := <validate>(value)
|
||||
<validate>(value)
|
||||
</needsValidation>
|
||||
}
|
||||
)")
|
||||
@ -1905,3 +1995,41 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
|
||||
.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);
|
||||
}
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -44,9 +46,11 @@ class YulUtilFunctions
|
||||
public:
|
||||
explicit YulUtilFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_functionCollector(std::move(_functionCollector))
|
||||
{}
|
||||
|
||||
@ -168,9 +172,14 @@ public:
|
||||
|
||||
/// @returns the name of a function that returns the calldata address for the
|
||||
/// given array base ref and index.
|
||||
/// signature: (baseRef, index) -> address
|
||||
/// signature: (baseRef, index) -> offset[, length]
|
||||
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.
|
||||
/// 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);
|
||||
@ -281,6 +290,13 @@ public:
|
||||
/// zero
|
||||
/// signature: (slot, offset) ->
|
||||
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:
|
||||
/// Special case of conversionFunction - handles everything that does not
|
||||
/// use exactly one variable to hold the value.
|
||||
@ -289,6 +305,7 @@ private:
|
||||
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings m_revertStrings;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
};
|
||||
|
||||
|
@ -31,23 +31,22 @@ using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
|
||||
IRVariable const& IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solUnimplementedAssert(
|
||||
_varDecl.annotation().type->sizeOnStack() == 1,
|
||||
"Multi-slot types not yet implemented."
|
||||
auto const& [it, didInsert] = m_localVariables.emplace(
|
||||
std::make_pair(&_varDecl, IRVariable{_varDecl})
|
||||
);
|
||||
|
||||
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
||||
solAssert(didInsert, "Local variable added multiple times.");
|
||||
return it->second;
|
||||
}
|
||||
|
||||
string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl)
|
||||
IRVariable const& IRGenerationContext::localVariable(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(
|
||||
m_localVariables.count(&_varDecl),
|
||||
"Unknown variable: " + _varDecl.name()
|
||||
);
|
||||
return m_localVariables[&_varDecl];
|
||||
return m_localVariables.at(&_varDecl);
|
||||
}
|
||||
|
||||
void IRGenerationContext::addStateVariable(
|
||||
@ -98,23 +97,6 @@ string IRGenerationContext::newYulVariable()
|
||||
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 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("comma", _in > 0 ? "," : "");
|
||||
YulUtilFunctions utils(m_evmVersion, m_functions);
|
||||
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
|
||||
templ("in", suffixedVariableNameList("in_", 0, _in));
|
||||
templ("arrow", _out > 0 ? "->" : "");
|
||||
templ("out", suffixedVariableNameList("out_", 0, _out));
|
||||
@ -161,5 +143,10 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -20,7 +20,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||
#include <libsolidity/interface/OptimiserSettings.h>
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
@ -47,8 +49,13 @@ class YulUtilFunctions;
|
||||
class IRGenerationContext
|
||||
{
|
||||
public:
|
||||
IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
|
||||
IRGenerationContext(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
OptimiserSettings _optimiserSettings
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||
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); }
|
||||
std::string localVariableName(VariableDeclaration const& _varDecl);
|
||||
IRVariable const& localVariable(VariableDeclaration const& _varDecl);
|
||||
|
||||
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
|
||||
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
|
||||
@ -79,11 +86,6 @@ public:
|
||||
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
|
||||
|
||||
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);
|
||||
|
||||
@ -92,11 +94,18 @@ public:
|
||||
|
||||
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:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings m_revertStrings;
|
||||
OptimiserSettings m_optimiserSettings;
|
||||
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
|
||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
|
||||
|
@ -139,11 +139,11 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||
t("functionName", functionName);
|
||||
string params;
|
||||
for (auto const& varDecl: _function.parameters())
|
||||
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl);
|
||||
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
|
||||
t("params", params);
|
||||
string retParams;
|
||||
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("body", generate(_function.body()));
|
||||
return t.render();
|
||||
@ -159,21 +159,70 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
solAssert(!_varDecl.isConstant(), "");
|
||||
solAssert(_varDecl.isStateVariable(), "");
|
||||
|
||||
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>)
|
||||
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
|
||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
||||
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
||||
solAssert(slot_offset.second == 0, "");
|
||||
FunctionType funType(_varDecl);
|
||||
solUnimplementedAssert(funType.returnParameterTypes().size() == 1, "");
|
||||
TypePointer returnType = funType.returnParameterTypes().front();
|
||||
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();
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false))
|
||||
("slot", slot_offset.first.str())
|
||||
.render();
|
||||
});
|
||||
while ((mappingType = dynamic_cast<MappingType const*>(mappingType->valueType())));
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(<keys>) -> rval {
|
||||
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)
|
||||
@ -284,7 +333,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ";
|
||||
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["params"] = suffixedVariableNameList("param_", 0, paramVars);
|
||||
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
|
||||
@ -337,8 +386,8 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||
m_context.functionCollector()->requestedFunctions().empty(),
|
||||
"Reset context while it still had functions."
|
||||
);
|
||||
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings);
|
||||
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector());
|
||||
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
|
||||
m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
|
||||
|
||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||
for (auto const& var: ContractType(_contract).stateVariables())
|
||||
|
@ -37,11 +37,15 @@ class SourceUnit;
|
||||
class IRGenerator
|
||||
{
|
||||
public:
|
||||
IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
|
||||
IRGenerator(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
RevertStrings _revertStrings,
|
||||
OptimiserSettings _optimiserSettings
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_optimiserSettings(_optimiserSettings),
|
||||
m_context(_evmVersion, std::move(_optimiserSettings)),
|
||||
m_utils(_evmVersion, m_context.functionCollector())
|
||||
m_context(_evmVersion, _revertStrings, std::move(_optimiserSettings)),
|
||||
m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector())
|
||||
{}
|
||||
|
||||
/// Generates and returns the IR code, in unoptimized and optimized form
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/codegen/ir/IRLValue.h>
|
||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
@ -47,6 +48,7 @@ public:
|
||||
void initializeStateVar(VariableDeclaration const& _varDecl);
|
||||
|
||||
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
|
||||
bool visit(Conditional const& _conditional) override;
|
||||
bool visit(Assignment const& _assignment) override;
|
||||
bool visit(TupleExpression const& _tuple) override;
|
||||
bool visit(IfStatement const& _ifStatement) override;
|
||||
@ -73,14 +75,29 @@ private:
|
||||
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,
|
||||
/// converted to type @a _to if it does not yet have that type.
|
||||
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.
|
||||
std::ostream& defineExpressionPart(Expression const& _expression, std::string const& _part);
|
||||
|
||||
/// @returns an output stream that can be used to define @a _var using a function call or
|
||||
/// 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 appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);
|
||||
@ -93,7 +110,14 @@ private:
|
||||
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(
|
||||
Statement const& _body,
|
||||
Expression const* _conditionExpression,
|
||||
@ -107,7 +131,7 @@ private:
|
||||
std::ostringstream m_code;
|
||||
IRGenerationContext& m_context;
|
||||
YulUtilFunctions& m_utils;
|
||||
std::unique_ptr<IRLValue> m_currentLValue;
|
||||
std::optional<IRLValue> m_currentLValue;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -15,115 +15,51 @@
|
||||
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
|
||||
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include <boost/variant.hpp>
|
||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||
#include <variant>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
class VariableDeclaration;
|
||||
class IRGenerationContext;
|
||||
class Type;
|
||||
class ArrayType;
|
||||
|
||||
/**
|
||||
* Abstract class used to retrieve, delete and store data in LValues.
|
||||
*/
|
||||
class IRLValue
|
||||
struct IRLValue
|
||||
{
|
||||
protected:
|
||||
explicit IRLValue(YulUtilFunctions _utils, Type const* _type = nullptr):
|
||||
m_utils(std::move(_utils)),
|
||||
m_type(_type)
|
||||
{}
|
||||
|
||||
public:
|
||||
virtual ~IRLValue() = default;
|
||||
/// @returns an expression to retrieve the value of the lvalue.
|
||||
virtual std::string retrieveValue() const = 0;
|
||||
/// Returns code that stores the value of @a _value (should be an identifier)
|
||||
/// of type @a _type in the lvalue. Might perform type conversion.
|
||||
virtual std::string storeValue(std::string const& _value, Type const& _type) const = 0;
|
||||
|
||||
/// Returns code that will reset the stored value to zero
|
||||
virtual std::string setToZero() const = 0;
|
||||
protected:
|
||||
YulUtilFunctions mutable m_utils;
|
||||
Type const* m_type;
|
||||
Type const& type;
|
||||
struct Stack
|
||||
{
|
||||
IRVariable variable;
|
||||
};
|
||||
struct Storage
|
||||
{
|
||||
std::string const slot;
|
||||
/// unsigned: Used when the offset is known at compile time, uses optimized
|
||||
/// functions
|
||||
/// string: Used when the offset is determined at run time
|
||||
std::variant<std::string, unsigned> const offset;
|
||||
std::string offsetString() const
|
||||
{
|
||||
if (std::holds_alternative<unsigned>(offset))
|
||||
return std::to_string(std::get<unsigned>(offset));
|
||||
else
|
||||
return std::get<std::string>(offset);
|
||||
}
|
||||
};
|
||||
struct Memory
|
||||
{
|
||||
std::string const address;
|
||||
bool byteArrayElement = false;
|
||||
};
|
||||
struct Tuple
|
||||
{
|
||||
std::vector<std::optional<IRLValue>> components;
|
||||
};
|
||||
std::variant<Stack, Storage, Memory, Tuple> kind;
|
||||
};
|
||||
|
||||
class IRLocalVariable: public IRLValue
|
||||
{
|
||||
public:
|
||||
IRLocalVariable(
|
||||
IRGenerationContext& _context,
|
||||
VariableDeclaration const& _varDecl
|
||||
);
|
||||
std::string retrieveValue() const override { return m_variableName; }
|
||||
std::string storeValue(std::string const& _value, Type const& _type) const override;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
111
libsolidity/codegen/ir/IRVariable.cpp
Normal file
111
libsolidity/codegen/ir/IRVariable.cpp
Normal 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;
|
||||
}
|
85
libsolidity/codegen/ir/IRVariable.h
Normal file
85
libsolidity/codegen/ir/IRVariable.h
Normal 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;
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -441,26 +441,7 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall)
|
||||
}
|
||||
else
|
||||
{
|
||||
vector<smt::Expression> funArgs;
|
||||
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);
|
||||
initializeFunctionCallParameters(*funDef, symbolicArguments(_funCall));
|
||||
|
||||
// The reason why we need to pushCallStack here instead of visit(FunctionDefinition)
|
||||
// is that there we don't have `_funCall`.
|
||||
@ -575,7 +556,7 @@ void BMC::checkVerificationTargets(smt::Expression const& _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)
|
||||
{
|
||||
@ -606,7 +587,7 @@ void BMC::checkVerificationTarget(VerificationTarget& _target, smt::Expression c
|
||||
}
|
||||
}
|
||||
|
||||
void BMC::checkConstantCondition(VerificationTarget& _target)
|
||||
void BMC::checkConstantCondition(BMCVerificationTarget& _target)
|
||||
{
|
||||
checkBooleanNotConstant(
|
||||
*_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(
|
||||
_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(
|
||||
_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, "");
|
||||
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, "");
|
||||
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, "");
|
||||
if (!m_safeAssertions.count(_target.expression))
|
||||
@ -703,10 +684,12 @@ void BMC::addVerificationTarget(
|
||||
Expression const* _expression
|
||||
)
|
||||
{
|
||||
VerificationTarget target{
|
||||
_type,
|
||||
_value,
|
||||
currentPathConditions() && m_context.assertions(),
|
||||
BMCVerificationTarget target{
|
||||
{
|
||||
_type,
|
||||
_value,
|
||||
currentPathConditions() && m_context.assertions()
|
||||
},
|
||||
_expression,
|
||||
m_callStack,
|
||||
modelExpressions()
|
||||
|
@ -117,24 +117,21 @@ private:
|
||||
|
||||
/// 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;
|
||||
std::vector<CallStackEntry> callStack;
|
||||
std::pair<std::vector<smt::Expression>, std::vector<std::string>> modelExpressions;
|
||||
};
|
||||
|
||||
void checkVerificationTargets(smt::Expression const& _constraints);
|
||||
void checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true));
|
||||
void checkConstantCondition(VerificationTarget& _target);
|
||||
void checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints);
|
||||
void checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints);
|
||||
void checkDivByZero(VerificationTarget& _target);
|
||||
void checkBalance(VerificationTarget& _target);
|
||||
void checkAssert(VerificationTarget& _target);
|
||||
void checkVerificationTarget(BMCVerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true));
|
||||
void checkConstantCondition(BMCVerificationTarget& _target);
|
||||
void checkUnderflow(BMCVerificationTarget& _target, smt::Expression const& _constraints);
|
||||
void checkOverflow(BMCVerificationTarget& _target, smt::Expression const& _constraints);
|
||||
void checkDivByZero(BMCVerificationTarget& _target);
|
||||
void checkBalance(BMCVerificationTarget& _target);
|
||||
void checkAssert(BMCVerificationTarget& _target);
|
||||
void addVerificationTarget(
|
||||
VerificationTarget::Type _type,
|
||||
smt::Expression const& _value,
|
||||
@ -179,7 +176,7 @@ private:
|
||||
/// ErrorReporter that comes from CompilerStack.
|
||||
langutil::ErrorReporter& m_outerErrorReporter;
|
||||
|
||||
std::vector<VerificationTarget> m_verificationTargets;
|
||||
std::vector<BMCVerificationTarget> m_verificationTargets;
|
||||
|
||||
/// Assertions that are known to be safe.
|
||||
std::set<Expression const*> m_safeAssertions;
|
||||
|
@ -505,6 +505,23 @@ void CHC::eraseKnowledge()
|
||||
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
|
||||
{
|
||||
if (
|
||||
|
@ -83,6 +83,7 @@ private:
|
||||
//@{
|
||||
void reset();
|
||||
void eraseKnowledge();
|
||||
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
||||
bool shouldVisit(ContractDefinition const& _contract) const;
|
||||
bool shouldVisit(FunctionDefinition const& _function) const;
|
||||
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);
|
||||
|
@ -46,7 +46,7 @@ public:
|
||||
/// should be used, even if all are available. The default choice is to use all.
|
||||
ModelChecker(
|
||||
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(),
|
||||
smt::SMTSolverChoice _enabledSolvers = smt::SMTSolverChoice::All()
|
||||
);
|
||||
|
@ -1682,3 +1682,31 @@ void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
|
||||
else if (returnParams.size() == 1)
|
||||
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;
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ protected:
|
||||
/// Resets the variable indices.
|
||||
void resetVariableIndices(VariableIndices const& _indices);
|
||||
/// 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.
|
||||
@ -230,9 +230,21 @@ protected:
|
||||
/// and set them as the components of the symbolic tuple.
|
||||
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.
|
||||
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;
|
||||
bool m_arrayAssignmentHappened = false;
|
||||
// True if the "No SMT solver available" warning was already created.
|
||||
|
@ -165,7 +165,7 @@ void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
|
||||
{
|
||||
if (m_stackState >= ParsingPerformed)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -670,14 +670,14 @@ string CompilerStack::assemblyString(string const& _contractName, StringMap _sou
|
||||
}
|
||||
|
||||
/// 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)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
|
||||
Contract const& currentContract = contract(_contractName);
|
||||
if (currentContract.compiler)
|
||||
return currentContract.compiler->assemblyJSON(_sourceCodes);
|
||||
return currentContract.compiler->assemblyJSON(sourceIndices());
|
||||
else
|
||||
return Json::Value();
|
||||
}
|
||||
@ -906,35 +906,42 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
|
||||
{
|
||||
solAssert(m_stackState < ParsingPerformed, "");
|
||||
StringMap newSources;
|
||||
for (auto const& node: _ast.nodes())
|
||||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
||||
{
|
||||
solAssert(!import->path().empty(), "Import path cannot be empty.");
|
||||
|
||||
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
|
||||
try
|
||||
{
|
||||
for (auto const& node: _ast.nodes())
|
||||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
||||
{
|
||||
m_errorReporter.parserError(
|
||||
import->location(),
|
||||
string("Source \"" + importPath + "\" not found: " + result.responseOrErrorMessage)
|
||||
);
|
||||
continue;
|
||||
solAssert(!import->path().empty(), "Import path cannot be empty.");
|
||||
|
||||
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(
|
||||
import->location(),
|
||||
string("Source \"" + importPath + "\" not found: " + result.responseOrErrorMessage)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FatalError const&)
|
||||
{
|
||||
solAssert(m_errorReporter.hasErrors(), "");
|
||||
}
|
||||
return newSources;
|
||||
}
|
||||
|
||||
@ -1112,7 +1119,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
|
||||
for (auto const* dependency: _contract.annotation().contractDependencies)
|
||||
generateIR(*dependency);
|
||||
|
||||
IRGenerator generator(m_evmVersion, m_optimiserSettings);
|
||||
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings);
|
||||
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
|
||||
}
|
||||
|
||||
|
@ -287,7 +287,7 @@ public:
|
||||
/// @returns a JSON representation of the assembly.
|
||||
/// @arg _sourceCodes is the map of input files to source code strings
|
||||
/// 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.
|
||||
/// Prerequisite: Successful call to parse or compile.
|
||||
|
@ -661,10 +661,10 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
|
||||
std::optional<RevertStrings> revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].asString());
|
||||
if (!revertStrings)
|
||||
return formatFatalError("JSONError", "Invalid value for settings.debug.revertStrings.");
|
||||
if (*revertStrings != RevertStrings::Default && *revertStrings != RevertStrings::Strip)
|
||||
if (*revertStrings == RevertStrings::VerboseDebug)
|
||||
return formatFatalError(
|
||||
"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;
|
||||
}
|
||||
@ -967,7 +967,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental))
|
||||
evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList);
|
||||
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))
|
||||
evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental))
|
||||
|
@ -45,11 +45,11 @@ class Parser::ASTNodeFactory
|
||||
{
|
||||
public:
|
||||
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):
|
||||
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 setLocationEmpty() { m_location.end = m_location.start; }
|
||||
/// 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()
|
||||
{
|
||||
RecursionGuard recursionGuard(*this);
|
||||
@ -205,12 +218,12 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
|
||||
while (true)
|
||||
{
|
||||
ASTPointer<ASTString> alias;
|
||||
SourceLocation aliasLocation = SourceLocation{position(), endPosition(), source()};
|
||||
SourceLocation aliasLocation = currentLocation();
|
||||
ASTPointer<Identifier> id = parseIdentifier();
|
||||
if (m_scanner->currentToken() == Token::As)
|
||||
{
|
||||
expectToken(Token::As);
|
||||
aliasLocation = SourceLocation{position(), endPosition(), source()};
|
||||
aliasLocation = currentLocation();
|
||||
alias = expectIdentifierToken();
|
||||
}
|
||||
symbolAliases.emplace_back(ImportDirective::SymbolAlias{move(id), move(alias), aliasLocation});
|
||||
@ -265,7 +278,8 @@ std::pair<ContractKind, bool> Parser::parseContractKind()
|
||||
kind = ContractKind::Library;
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Invalid contract kind.");
|
||||
parserError("Expected keyword \"contract\", \"interface\" or \"library\".");
|
||||
return std::make_pair(ContractKind::Contract, abstract);
|
||||
}
|
||||
m_scanner->next();
|
||||
return std::make_pair(kind, abstract);
|
||||
@ -276,14 +290,13 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
|
||||
RecursionGuard recursionGuard(*this);
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
ASTPointer<ASTString> name = nullptr;
|
||||
ASTPointer<ASTString> docString;
|
||||
ASTPointer<StructuredDocumentation> documentation;
|
||||
vector<ASTPointer<InheritanceSpecifier>> baseContracts;
|
||||
vector<ASTPointer<ASTNode>> subNodes;
|
||||
std::pair<ContractKind, bool> contractKind{};
|
||||
try
|
||||
{
|
||||
if (m_scanner->currentCommentLiteral() != "")
|
||||
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
|
||||
documentation = parseStructuredDocumentation();
|
||||
contractKind = parseContractKind();
|
||||
name = expectIdentifierToken();
|
||||
if (m_scanner->currentToken() == Token::Is)
|
||||
@ -350,7 +363,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
|
||||
expectToken(Token::RBrace);
|
||||
return nodeFactory.createNode<ContractDefinition>(
|
||||
name,
|
||||
docString,
|
||||
documentation,
|
||||
baseContracts,
|
||||
subNodes,
|
||||
contractKind.first,
|
||||
@ -538,9 +551,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
|
||||
{
|
||||
RecursionGuard recursionGuard(*this);
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
ASTPointer<ASTString> docstring;
|
||||
if (m_scanner->currentCommentLiteral() != "")
|
||||
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
|
||||
ASTPointer<StructuredDocumentation> documentation = parseStructuredDocumentation();
|
||||
|
||||
Token kind = m_scanner->currentToken();
|
||||
ASTPointer<ASTString> name;
|
||||
@ -598,7 +609,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
|
||||
kind,
|
||||
header.isVirtual,
|
||||
header.overrides,
|
||||
docstring,
|
||||
documentation,
|
||||
header.parameters,
|
||||
header.modifiers,
|
||||
header.returnParameters,
|
||||
@ -792,9 +803,7 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
|
||||
m_insideModifier = true;
|
||||
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
ASTPointer<ASTString> docstring;
|
||||
if (m_scanner->currentCommentLiteral() != "")
|
||||
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
|
||||
ASTPointer<StructuredDocumentation> documentation = parseStructuredDocumentation();
|
||||
|
||||
expectToken(Token::Modifier);
|
||||
ASTPointer<ASTString> name(expectIdentifierToken());
|
||||
@ -835,16 +844,14 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
|
||||
|
||||
ASTPointer<Block> block = parseBlock();
|
||||
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()
|
||||
{
|
||||
RecursionGuard recursionGuard(*this);
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
ASTPointer<ASTString> docstring;
|
||||
if (m_scanner->currentCommentLiteral() != "")
|
||||
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
|
||||
ASTPointer<StructuredDocumentation> documentation = parseStructuredDocumentation();
|
||||
|
||||
expectToken(Token::Event);
|
||||
ASTPointer<ASTString> name(expectIdentifierToken());
|
||||
@ -861,7 +868,7 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
|
||||
}
|
||||
nodeFactory.markEndPosition();
|
||||
expectToken(Token::Semicolon);
|
||||
return nodeFactory.createNode<EventDefinition>(name, docstring, parameters, anonymous);
|
||||
return nodeFactory.createNode<EventDefinition>(name, documentation, parameters, anonymous);
|
||||
}
|
||||
|
||||
ASTPointer<UsingForDirective> Parser::parseUsingDirective()
|
||||
@ -1013,16 +1020,22 @@ ASTPointer<Mapping> Parser::parseMapping()
|
||||
ASTNodeFactory nodeFactory(*this);
|
||||
expectToken(Token::Mapping);
|
||||
expectToken(Token::LParen);
|
||||
ASTPointer<ElementaryTypeName> keyType;
|
||||
ASTPointer<TypeName> keyType;
|
||||
Token token = m_scanner->currentToken();
|
||||
if (!TokenTraits::isElementaryTypeName(token))
|
||||
fatalParserError(string("Expected elementary type name for mapping key type"));
|
||||
unsigned firstSize;
|
||||
unsigned secondSize;
|
||||
tie(firstSize, secondSize) = m_scanner->currentTokenInfo();
|
||||
ElementaryTypeNameToken elemTypeName(token, firstSize, secondSize);
|
||||
keyType = ASTNodeFactory(*this).createNode<ElementaryTypeName>(elemTypeName);
|
||||
m_scanner->next();
|
||||
if (token == Token::Identifier)
|
||||
keyType = parseUserDefinedTypeName();
|
||||
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);
|
||||
bool const allowVar = false;
|
||||
ASTPointer<TypeName> valueType = parseTypeName(allowVar);
|
||||
@ -1176,7 +1189,7 @@ ASTPointer<Statement> Parser::parseStatement()
|
||||
ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> const& _docString)
|
||||
{
|
||||
RecursionGuard recursionGuard(*this);
|
||||
SourceLocation location{position(), -1, source()};
|
||||
SourceLocation location = currentLocation();
|
||||
|
||||
expectToken(Token::Assembly);
|
||||
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)
|
||||
break;
|
||||
m_scanner->next();
|
||||
};
|
||||
}
|
||||
|
||||
auto eventName = expressionFromIndexAccessStructure(iap);
|
||||
expectToken(Token::LParen);
|
||||
@ -1755,7 +1768,7 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
|
||||
nodeFactory.markEndPosition();
|
||||
expectToken(Token::RBrace);
|
||||
|
||||
expression = parseLeftHandSideExpression(nodeFactory.createNode<FunctionCallOptions>(expression, optionList.first, optionList.second));
|
||||
expression = nodeFactory.createNode<FunctionCallOptions>(expression, optionList.first, optionList.second);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -2011,13 +2024,13 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath()
|
||||
ASTPointer<Expression> endIndex;
|
||||
if (m_scanner->currentToken() != Token::RBrack)
|
||||
endIndex = parseExpression();
|
||||
indexLocation.end = endPosition();
|
||||
indexLocation.end = currentLocation().end;
|
||||
iap.indices.emplace_back(IndexAccessedPath::Index{index, {endIndex}, indexLocation});
|
||||
expectToken(Token::RBrack);
|
||||
}
|
||||
else
|
||||
{
|
||||
indexLocation.end = endPosition();
|
||||
indexLocation.end = currentLocation().end;
|
||||
iap.indices.emplace_back(IndexAccessedPath::Index{index, {}, indexLocation});
|
||||
expectToken(Token::RBrack);
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ private:
|
||||
///@{
|
||||
///@name Parsing functions for the AST nodes
|
||||
void parsePragmaVersion(langutil::SourceLocation const& _location, std::vector<Token> const& _tokens, std::vector<std::string> const& _literals);
|
||||
ASTPointer<StructuredDocumentation> parseStructuredDocumentation();
|
||||
ASTPointer<PragmaDirective> parsePragmaDirective();
|
||||
ASTPointer<ImportDirective> parseImportDirective();
|
||||
/// @returns an std::pair<ContractKind, bool>, where
|
||||
|
@ -40,12 +40,12 @@ namespace solidity::util
|
||||
/// Assertion that throws an exception containing the given description if it is not met.
|
||||
/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong.");
|
||||
/// Do NOT supply an exception object as the second parameter.
|
||||
#define assertThrow(_condition, _ExceptionType, _description) \
|
||||
#define assertThrow(_condition, _exceptionType, _description) \
|
||||
do \
|
||||
{ \
|
||||
if (!(_condition)) \
|
||||
::boost::throw_exception( \
|
||||
_ExceptionType() << \
|
||||
_exceptionType() << \
|
||||
::solidity::util::errinfo_comment(_description) << \
|
||||
::boost::throw_function(ETH_FUNC) << \
|
||||
::boost::throw_file(__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.
|
||||
|
||||
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.
|
||||
/// @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.
|
||||
template <class T, class _In>
|
||||
inline T fromBigEndian(_In const& _bytes)
|
||||
template <class T, class In>
|
||||
inline T fromBigEndian(In const& _bytes)
|
||||
{
|
||||
T ret = (T)0;
|
||||
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;
|
||||
}
|
||||
inline bytes toBigEndian(u256 _val) { bytes ret(32); toBigEndian(_val, ret); return ret; }
|
||||
|
@ -40,11 +40,11 @@ using namespace solidity::util;
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename _T>
|
||||
inline _T readFile(std::string const& _file)
|
||||
template <typename T>
|
||||
inline T readFile(std::string const& _file)
|
||||
{
|
||||
_T ret;
|
||||
size_t const c_elementSize = sizeof(typename _T::value_type);
|
||||
T ret;
|
||||
size_t const c_elementSize = sizeof(typename T::value_type);
|
||||
std::ifstream is(_file, std::ifstream::binary);
|
||||
if (!is)
|
||||
return ret;
|
||||
|
@ -42,8 +42,8 @@ std::string readStandardInput();
|
||||
int readStandardInputChar();
|
||||
|
||||
/// Converts arbitrary value to string representation using std::stringstream.
|
||||
template <class _T>
|
||||
std::string toString(_T const& _t)
|
||||
template <class T>
|
||||
std::string toString(T const& _t)
|
||||
{
|
||||
std::ostringstream o;
|
||||
o << _t;
|
||||
|
@ -16,63 +16,63 @@ namespace solidity::util
|
||||
/**
|
||||
* A modifiable reference to an existing object or vector in memory.
|
||||
*/
|
||||
template <class _T>
|
||||
template <class T>
|
||||
class vector_ref
|
||||
{
|
||||
public:
|
||||
using value_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 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 iterator = _T*;
|
||||
using const_iterator = _T const*;
|
||||
using value_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 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 iterator = T*;
|
||||
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.");
|
||||
|
||||
vector_ref(): m_data(nullptr), m_count(0) {}
|
||||
/// 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).
|
||||
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).
|
||||
vector_ref(string_type& _data): vector_ref(&_data) {}
|
||||
/// 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()) {}
|
||||
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::string toString() const { return std::string((char const*)m_data, ((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)); }
|
||||
|
||||
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); }
|
||||
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); }
|
||||
|
||||
_T* data() const { return m_data; }
|
||||
T* data() const { return m_data; }
|
||||
/// @returns the number of elements referenced (not necessarily number of bytes).
|
||||
size_t size() 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.
|
||||
/// 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.
|
||||
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).
|
||||
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* end() { return m_data + m_count; }
|
||||
_T const* begin() const { return m_data; }
|
||||
_T const* end() const { return m_data + m_count; }
|
||||
T* begin() { return m_data; }
|
||||
T* end() { return m_data + m_count; }
|
||||
T const* begin() const { return m_data; }
|
||||
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 const& operator[](size_t _i) const { 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]; }
|
||||
|
||||
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 m_data == _cmp.m_data && m_count == _cmp.m_count; }
|
||||
bool operator!=(vector_ref<T> const& _cmp) const { return !operator==(_cmp); }
|
||||
|
||||
void reset() { m_data = nullptr; m_count = 0; }
|
||||
|
||||
private:
|
||||
_T* m_data = nullptr;
|
||||
T* m_data = nullptr;
|
||||
size_t m_count = 0;
|
||||
};
|
||||
|
||||
|
@ -45,14 +45,14 @@ using namespace solidity::langutil;
|
||||
|
||||
bool AsmAnalyzer::analyze(Block const& _block)
|
||||
{
|
||||
bool success = false;
|
||||
m_success = true;
|
||||
try
|
||||
{
|
||||
if (!(ScopeFiller(m_info, m_errorReporter))(_block))
|
||||
return false;
|
||||
|
||||
success = (*this)(_block);
|
||||
if (!success)
|
||||
(*this)(_block);
|
||||
if (!m_success)
|
||||
yulAssert(m_errorReporter.hasErrors(), "No success but no error.");
|
||||
}
|
||||
catch (FatalError const&)
|
||||
@ -60,7 +60,7 @@ bool AsmAnalyzer::analyze(Block const& _block)
|
||||
// This FatalError con occur if the errorReporter has too many errors.
|
||||
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)
|
||||
@ -79,131 +79,112 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect,
|
||||
return analysisInfo;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Literal const& _literal)
|
||||
vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
|
||||
{
|
||||
expectValidType(_literal.type, _literal.location);
|
||||
++m_stackHeight;
|
||||
|
||||
if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
typeError(
|
||||
_literal.location,
|
||||
"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))
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
_literal.location,
|
||||
"Number literal too large (> 256 bits)"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
typeError(_literal.location, "Number literal too large (> 256 bits)");
|
||||
else if (_literal.kind == LiteralKind::Boolean)
|
||||
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(), "");
|
||||
size_t numErrorsBefore = m_errorReporter.errors().size();
|
||||
bool success = true;
|
||||
YulString type = m_dialect.defaultType;
|
||||
|
||||
if (m_currentScope->lookup(_identifier.name, GenericVisitor{
|
||||
[&](Scope::Variable const& _var)
|
||||
{
|
||||
if (!m_activeVariables.count(&_var))
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
declarationError(
|
||||
_identifier.location,
|
||||
"Variable " + _identifier.name.str() + " used before it was declared."
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
++m_stackHeight;
|
||||
type = _var.type;
|
||||
},
|
||||
[&](Scope::Function const&)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
typeError(
|
||||
_identifier.location,
|
||||
"Function " + _identifier.name.str() + " used without being called."
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
}))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t stackSize(-1);
|
||||
bool found = false;
|
||||
if (m_resolver)
|
||||
{
|
||||
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.
|
||||
if (numErrorsBefore == m_errorReporter.errors().size())
|
||||
m_errorReporter.declarationError(_identifier.location, "Identifier not found.");
|
||||
success = false;
|
||||
declarationError(_identifier.location, "Identifier not found.");
|
||||
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;
|
||||
bool success = std::visit(*this, _statement.expression);
|
||||
if (success && m_stackHeight != initialStackHeight)
|
||||
{
|
||||
string msg =
|
||||
vector<YulString> types = std::visit(*this, _statement.expression);
|
||||
if (m_success && !types.empty())
|
||||
typeError(_statement.location,
|
||||
"Top-level expressions are not supposed to return values (this expression returns " +
|
||||
to_string(m_stackHeight - initialStackHeight) +
|
||||
to_string(types.size()) +
|
||||
" value" +
|
||||
(m_stackHeight - initialStackHeight == 1 ? "" : "s") +
|
||||
"). Use ``pop()`` or assign them.";
|
||||
m_errorReporter.error(Error::Type::TypeError, _statement.location, msg);
|
||||
success = false;
|
||||
}
|
||||
m_info.stackHeightInfo[&_statement] = m_stackHeight;
|
||||
return success;
|
||||
(types.size() == 1 ? "" : "s") +
|
||||
"). Use ``pop()`` or assign them."
|
||||
);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Assignment const& _assignment)
|
||||
void AsmAnalyzer::operator()(Assignment const& _assignment)
|
||||
{
|
||||
yulAssert(_assignment.value, "");
|
||||
int const expectedItems = _assignment.variableNames.size();
|
||||
yulAssert(expectedItems >= 1, "");
|
||||
int const stackHeight = m_stackHeight;
|
||||
bool success = std::visit(*this, *_assignment.value);
|
||||
if ((m_stackHeight - stackHeight) != expectedItems)
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
size_t const numVariables = _assignment.variableNames.size();
|
||||
yulAssert(numVariables >= 1, "");
|
||||
|
||||
vector<YulString> types = std::visit(*this, *_assignment.value);
|
||||
|
||||
if (types.size() != numVariables)
|
||||
declarationError(
|
||||
_assignment.location,
|
||||
"Variable count does not match number of values (" +
|
||||
to_string(expectedItems) +
|
||||
to_string(numVariables) +
|
||||
" vs. " +
|
||||
to_string(m_stackHeight - stackHeight) +
|
||||
to_string(types.size()) +
|
||||
")"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
for (auto const& variableName: _assignment.variableNames)
|
||||
if (!checkAssignment(variableName, 1))
|
||||
success = false;
|
||||
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
|
||||
return success;
|
||||
|
||||
for (size_t i = 0; i < numVariables; ++i)
|
||||
checkAssignment(_assignment.variableNames[i]);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
|
||||
void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
bool success = true;
|
||||
int const numVariables = _varDecl.variables.size();
|
||||
size_t const numVariables = _varDecl.variables.size();
|
||||
if (m_resolver)
|
||||
for (auto const& variable: _varDecl.variables)
|
||||
// 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)
|
||||
{
|
||||
int const stackHeight = m_stackHeight;
|
||||
success = std::visit(*this, *_varDecl.value);
|
||||
int numValues = m_stackHeight - stackHeight;
|
||||
if (numValues != numVariables)
|
||||
{
|
||||
m_errorReporter.declarationError(_varDecl.location,
|
||||
vector<YulString> types = std::visit(*this, *_varDecl.value);
|
||||
if (types.size() != numVariables)
|
||||
declarationError(_varDecl.location,
|
||||
"Variable count mismatch: " +
|
||||
to_string(numVariables) +
|
||||
" variables and " +
|
||||
to_string(numValues) +
|
||||
to_string(types.size()) +
|
||||
" 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);
|
||||
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(), "");
|
||||
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)));
|
||||
}
|
||||
|
||||
int const stackHeight = m_stackHeight;
|
||||
m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size();
|
||||
|
||||
bool success = (*this)(_funDef.body);
|
||||
|
||||
m_stackHeight = stackHeight;
|
||||
m_info.stackHeightInfo[&_funDef] = m_stackHeight;
|
||||
return success;
|
||||
(*this)(_funDef.body);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
{
|
||||
yulAssert(!_funCall.functionName.name.empty(), "");
|
||||
bool success = true;
|
||||
size_t parameters = 0;
|
||||
size_t returns = 0;
|
||||
vector<YulString> const* parameterTypes = nullptr;
|
||||
vector<YulString> const* returnTypes = nullptr;
|
||||
bool needsLiteralArguments = false;
|
||||
|
||||
if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name))
|
||||
{
|
||||
// TODO: compare types, too
|
||||
parameters = f->parameters.size();
|
||||
returns = f->returns.size();
|
||||
parameterTypes = &f->parameters;
|
||||
returnTypes = &f->returns;
|
||||
if (f->literalArguments)
|
||||
needsLiteralArguments = true;
|
||||
}
|
||||
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
|
||||
[&](Scope::Variable const&)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
typeError(
|
||||
_funCall.functionName.location,
|
||||
"Attempt to call variable instead of function."
|
||||
);
|
||||
success = false;
|
||||
},
|
||||
[&](Scope::Function const& _fun)
|
||||
{
|
||||
/// TODO: compare types too
|
||||
parameters = _fun.arguments.size();
|
||||
returns = _fun.returns.size();
|
||||
parameterTypes = &_fun.arguments;
|
||||
returnTypes = &_fun.returns;
|
||||
}
|
||||
}))
|
||||
{
|
||||
if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location))
|
||||
m_errorReporter.declarationError(_funCall.functionName.location, "Function not found.");
|
||||
success = false;
|
||||
declarationError(_funCall.functionName.location, "Function not found.");
|
||||
m_success = false;
|
||||
}
|
||||
if (success)
|
||||
if (_funCall.arguments.size() != parameters)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
_funCall.functionName.location,
|
||||
"Function expects " +
|
||||
to_string(parameters) +
|
||||
" arguments but got " +
|
||||
to_string(_funCall.arguments.size()) + "."
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
if (parameterTypes && _funCall.arguments.size() != parameterTypes->size())
|
||||
typeError(
|
||||
_funCall.functionName.location,
|
||||
"Function expects " +
|
||||
to_string(parameterTypes->size()) +
|
||||
" arguments but got " +
|
||||
to_string(_funCall.arguments.size()) + "."
|
||||
);
|
||||
|
||||
vector<YulString> argTypes;
|
||||
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
|
||||
{
|
||||
if (!expectExpression(arg))
|
||||
success = false;
|
||||
else if (needsLiteralArguments)
|
||||
argTypes.emplace_back(expectExpression(arg));
|
||||
|
||||
if (needsLiteralArguments)
|
||||
{
|
||||
if (!holds_alternative<Literal>(arg))
|
||||
m_errorReporter.typeError(
|
||||
typeError(
|
||||
_funCall.functionName.location,
|
||||
"Function expects direct literals as arguments."
|
||||
);
|
||||
else if (!m_dataNames.count(std::get<Literal>(arg).value))
|
||||
m_errorReporter.typeError(
|
||||
typeError(
|
||||
_funCall.functionName.location,
|
||||
"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());
|
||||
m_info.stackHeightInfo[&_funCall] = m_stackHeight;
|
||||
return success;
|
||||
|
||||
if (m_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;
|
||||
if (!expectExpression(*_if.condition))
|
||||
success = false;
|
||||
|
||||
m_stackHeight = initialHeight;
|
||||
|
||||
if (!(*this)(_if.body))
|
||||
success = false;
|
||||
|
||||
m_info.stackHeightInfo[&_if] = m_stackHeight;
|
||||
|
||||
return success;
|
||||
(*this)(_if.body);
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Switch const& _switch)
|
||||
void AsmAnalyzer::operator()(Switch const& _switch)
|
||||
{
|
||||
yulAssert(_switch.expression, "");
|
||||
|
||||
bool success = true;
|
||||
|
||||
int const initialHeight = m_stackHeight;
|
||||
if (!expectExpression(*_switch.expression))
|
||||
success = false;
|
||||
expectExpression(*_switch.expression);
|
||||
|
||||
YulString caseType;
|
||||
bool mismatchingTypes = false;
|
||||
@ -379,7 +330,6 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatchingTypes)
|
||||
m_errorReporter.typeError(
|
||||
_switch.location,
|
||||
@ -391,212 +341,103 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
|
||||
{
|
||||
if (_case.value)
|
||||
{
|
||||
int const initialStackHeight = m_stackHeight;
|
||||
bool isCaseValueValid = true;
|
||||
// We cannot use "expectExpression" here because *_case.value is not a
|
||||
// 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--;
|
||||
// We cannot use "expectExpression" here because *_case.value is not an
|
||||
// Expression and would be converted to an Expression otherwise.
|
||||
(*this)(*_case.value);
|
||||
|
||||
// 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
|
||||
if (isCaseValueValid && !cases.insert(valueOfLiteral(*_case.value)).second)
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
_case.location,
|
||||
"Duplicate case defined."
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
if (m_success && !cases.insert(valueOfLiteral(*_case.value)).second)
|
||||
declarationError(_case.location, "Duplicate case defined.");
|
||||
}
|
||||
|
||||
if (!(*this)(_case.body))
|
||||
success = false;
|
||||
(*this)(_case.body);
|
||||
}
|
||||
|
||||
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, "");
|
||||
|
||||
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
|
||||
// condition, the body and the post part inside.
|
||||
m_stackHeight += scope(&_for.pre).numberOfVariables();
|
||||
m_currentScope = &scope(&_for.pre);
|
||||
|
||||
if (!expectExpression(*_for.condition))
|
||||
success = false;
|
||||
|
||||
m_stackHeight--;
|
||||
expectExpression(*_for.condition);
|
||||
|
||||
// backup outer for-loop & create new state
|
||||
auto outerForLoop = m_currentForLoop;
|
||||
m_currentForLoop = &_for;
|
||||
|
||||
if (!(*this)(_for.body))
|
||||
success = false;
|
||||
(*this)(_for.body);
|
||||
(*this)(_for.post);
|
||||
|
||||
if (!(*this)(_for.post))
|
||||
success = false;
|
||||
|
||||
m_stackHeight = initialHeight;
|
||||
m_info.stackHeightInfo[&_for] = m_stackHeight;
|
||||
m_currentScope = outerScope;
|
||||
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;
|
||||
m_currentScope = &scope(&_block);
|
||||
|
||||
int const initialStackHeight = m_stackHeight;
|
||||
|
||||
for (auto const& s: _block.statements)
|
||||
if (!std::visit(*this, s))
|
||||
success = false;
|
||||
std::visit(*this, s);
|
||||
|
||||
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;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::expectExpression(Expression const& _expr)
|
||||
YulString AsmAnalyzer::expectExpression(Expression const& _expr)
|
||||
{
|
||||
bool success = true;
|
||||
int const initialHeight = m_stackHeight;
|
||||
if (!std::visit(*this, _expr))
|
||||
success = false;
|
||||
if (success && !expectDeposit(1, initialHeight, locationOf(_expr)))
|
||||
success = false;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
|
||||
{
|
||||
if (m_stackHeight - _oldHeight != _deposit)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
_location,
|
||||
"Expected expression to return one item to the stack, but did return " +
|
||||
to_string(m_stackHeight - _oldHeight) +
|
||||
" items."
|
||||
vector<YulString> types = std::visit(*this, _expr);
|
||||
if (types.size() != 1)
|
||||
typeError(
|
||||
locationOf(_expr),
|
||||
"Expected expression to evaluate to one value, but got " +
|
||||
to_string(types.size()) +
|
||||
" values instead."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return types.empty() ? m_dialect.defaultType : types.front();
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::checkAssignment(Identifier const& _variable, size_t _valueSize)
|
||||
void AsmAnalyzer::checkAssignment(Identifier const& _variable)
|
||||
{
|
||||
yulAssert(!_variable.name.empty(), "");
|
||||
bool success = true;
|
||||
size_t numErrorsBefore = m_errorReporter.errors().size();
|
||||
size_t variableSize(-1);
|
||||
bool found = false;
|
||||
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
|
||||
{
|
||||
// Check that it is a variable
|
||||
if (!holds_alternative<Scope::Variable>(*var))
|
||||
{
|
||||
m_errorReporter.typeError(_variable.location, "Assignment requires variable.");
|
||||
success = false;
|
||||
}
|
||||
typeError(_variable.location, "Assignment requires variable.");
|
||||
else if (!m_activeVariables.count(&std::get<Scope::Variable>(*var)))
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
declarationError(
|
||||
_variable.location,
|
||||
"Variable " + _variable.name.str() + " used before it was declared."
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
variableSize = 1;
|
||||
found = true;
|
||||
}
|
||||
else if (m_resolver)
|
||||
{
|
||||
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.
|
||||
if (numErrorsBefore == m_errorReporter.errors().size())
|
||||
m_errorReporter.declarationError(_variable.location, "Variable not found or variable not lvalue.");
|
||||
success = false;
|
||||
declarationError(_variable.location, "Variable not found or variable not lvalue.");
|
||||
}
|
||||
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)
|
||||
@ -608,7 +449,7 @@ Scope& AsmAnalyzer::scope(Block const* _block)
|
||||
}
|
||||
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(
|
||||
_location,
|
||||
"\"" + _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(), "");
|
||||
|
||||
auto errorForVM = [=](string const& vmKindMessage) {
|
||||
m_errorReporter.typeError(
|
||||
typeError(
|
||||
_location,
|
||||
"The \"" +
|
||||
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. "
|
||||
"Use functions, \"switch\", \"if\" or \"for\" statements instead."
|
||||
);
|
||||
m_success = false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
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
Loading…
Reference in New Issue
Block a user