Merge pull request #8341 from ethereum/develop

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

View File

@ -62,6 +62,11 @@ defaults:
path: build/solc/solc
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
View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -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%

View File

@ -54,6 +54,9 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.")
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")

View File

@ -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)

View File

@ -63,7 +63,7 @@ This section highlights changes that affect syntax and semantics.
last one only works for value types). Change every ``keccak256(a, b, c)`` to
``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 {

View File

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

View File

@ -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

View File

@ -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");
}

View File

@ -717,3 +717,13 @@ in scope in the block that follows.
in a catch block or the execution of the try/catch statement itself
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.

View File

@ -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

View File

@ -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

View File

@ -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 {}
}

View File

@ -833,15 +833,19 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a
| insize, out, outsize) | | | providing g gas and v wei and output area |
| | | | 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

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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
)
{

View File

@ -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)

View File

@ -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

View File

@ -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
);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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)

View File

@ -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

View File

@ -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);
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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;

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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().

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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
{
};

View File

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

View File

@ -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&)
{

View File

@ -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;

View File

@ -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)

View File

@ -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 ===================

View File

@ -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

View File

@ -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);
}

View File

@ -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)

View File

@ -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

View File

@ -259,7 +259,33 @@ public:
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// 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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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());

View File

@ -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.

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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())

View File

@ -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

View File

@ -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;
};
}

View File

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

View File

@ -15,115 +15,51 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* 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;
};
}
}

View File

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

View File

@ -0,0 +1,85 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <optional>
#include <string>
#include <vector>
namespace solidity::frontend
{
class VariableDeclaration;
class Type;
class Expression;
/**
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
* of a specific S
* olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference
* type or a memory reference type will contain a single unnamed part containing an offset. An IRVariable referring to
* a value type will contain a single unnamed part containing the value, an IRVariable referring to a tuple will
* have the typed tuple components as parts.
*/
class IRVariable
{
public:
/// IR variable with explicit base name @a _baseName and type @a _type.
IRVariable(std::string _baseName, Type const& _type);
/// IR variable referring to the declaration @a _decl.
explicit IRVariable(VariableDeclaration const& _decl);
/// IR variable referring to the expression @a _expr.
/// Intentionally not defined as explicit to allow defining IRVariables for expressions directly via implicit conversions.
IRVariable(Expression const& _expression);
/// @returns the name of the variable, if it occupies a single stack slot (otherwise throws).
std::string name() const;
/// @returns a comma-separated list of the stack slots of the variable.
std::string commaSeparatedList() const;
/// @returns a comma-separated list of the stack slots of the variable that is
/// prefixed with a comma, unless it is empty.
std::string commaSeparatedListPrefixed() const;
/// @returns an IRVariable referring to the tuple component @a _i of a tuple variable.
IRVariable tupleComponent(std::size_t _i) const;
/// @returns the type of the variable.
Type const& type() const { return m_type; }
/// @returns an IRVariable referring to the stack component @a _slot of the variable.
/// @a _slot must be among the stack slots in ``m_type.stackItems()``.
/// The returned IRVariable is itself typed with the type of the stack slot as defined
/// in ``m_type.stackItems()`` and may again occupy multiple stack slots.
IRVariable part(std::string const& _slot) const;
private:
/// @returns a vector containing the names of the stack slots of the variable.
std::vector<std::string> stackSlots() const;
/// @returns a name consisting of the base name appended with an underscore and @æ _suffix,
/// unless @a _suffix is empty, in which case the base name itself is returned.
std::string suffixedName(std::string const& _suffix) const;
std::string m_baseName;
Type const& m_type;
};
}

View File

@ -441,26 +441,7 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall)
}
else
{
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()

View File

@ -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;

View File

@ -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 (

View File

@ -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);

View File

@ -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()
);

View File

@ -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;
}

View File

@ -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.

View File

@ -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);
}

View File

@ -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.

View File

@ -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))

View File

@ -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);
}

View File

@ -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

View File

@ -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__) << \

View File

@ -156,6 +156,23 @@ T convertContainer(U&& _from)
};
}
/// Gets a @a K -> @a V map and returns a map where values from the original map are keys and keys
/// from the original map are values.
///
/// @pre @a originalMap must have unique values.
template <typename K, typename V>
std::map<V, K> invertMap(std::map<K, V> const& originalMap)
{
std::map<V, K> inverseMap;
for (auto const& originalPair: originalMap)
{
assert(inverseMap.count(originalPair.second) == 0);
inverseMap.insert({originalPair.second, originalPair.first});
}
return inverseMap;
}
// String conversion functions, mainly to/from hex/nibble/byte representations.
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; }

View File

@ -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;

View File

@ -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;

View File

@ -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;
};

View File

@ -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