Merge pull request #7950 from ethereum/develop_060

Merge develop_060 into develop
This commit is contained in:
chriseth 2019-12-10 14:16:32 +01:00 committed by GitHub
commit 7244aa01da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
920 changed files with 16885 additions and 4488 deletions

View File

@ -593,19 +593,24 @@ jobs:
t_ems_solcjs:
docker:
- image: circleci/node:10
- image: ethereum/solidity-buildpack-deps:ubuntu1904
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
name: Install test dependencies
command: |
apt-get update
apt-get install -qqy --no-install-recommends nodejs npm cvc4
- run:
name: Test solcjs
command: |
node --version
npm --version
test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt)
test/externalTests/solc-js/solc-js.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt)
t_ems_compile_ext_gnosis:
docker:
@ -716,7 +721,7 @@ workflows:
# basic checks
- chk_spelling: *workflow_trigger_on_tags
- chk_coding_style: *workflow_trigger_on_tags
- chk_docs_examples: *workflow_trigger_on_tags
# DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags
- chk_buglist: *workflow_trigger_on_tags
- chk_proofs: *workflow_trigger_on_tags

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.5.14")
set(PROJECT_VERSION "0.6.0")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
include(TestBigEndian)

View File

@ -1,3 +1,47 @@
### 0.6.0 (unreleased)
Breaking changes:
* ABI: remove the deprecated ``constant`` and ``payable`` fields.
* ABI: the ``type`` field is now required and no longer specified to default to ``function``.
* C API (``libsolc``): the provided callback now takes two parameters, kind and data. The callback can then be used for multiple purposes, such has file imports and SMT queries.
* C API (``libsolc``): Introduce context parameter.
* Commandline interface: remove the text-based ast printer (``--ast``).
* Command line interface: Switch to the new error reporter by default. ``--old-reporter`` falls back to the deprecated old error reporter.
* Command line interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata.
* General: Disallow explicit conversions from external function types to ``address`` and add a member called ``address`` to them as replacement.
* General: Enable Yul optimizer as part of standard optimization.
* General: New reserved keywords: ``virtual``.
* General: Split unnamed fallback functions into two cases defined using ``fallback()`` and ``receive()``.
* Standard JSON Interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata.
* Syntax: ``push(element)`` for dynamic storage arrays do not return the new length anymore.
* Syntax: Abstract contracts need to be marked explicitly as abstract by using the ``abstract`` keyword.
* Inline Assembly: Only strict inline assembly is allowed.
* Inline Assembly: Variable declarations cannot shadow declarations outside the assembly block.
* Syntax: ``length`` member of arrays is now always read-only, even for storage arrays.
* Type checker: Resulting type of exponentiation is equal to the type of the base. Also allow signed types for the base.
* Natspec JSON Interface: Properly support multiple ``@return`` statements in ``@dev`` documentation and enforce named return parameters to be mentioned documentation.
* Source mappings: Add "modifier depth" as a fifth field in the source mappings.
* AST: Inline assembly is exported as structured JSON instead of plain string.
* JSON AST: Replace ``superFunction`` attribute by ``baseFunctions``.
* General: ``private`` cannot be used together with ``virtual``.
* Inheritance: State variable shadowing is now disallowed.
Language Features:
* Allow global enums and structs.
* Allow underscores as delimiters in hex strings.
* Allow explicit conversions from ``address`` to ``address payable`` via ``payable(...)``.
* Introduce syntax for array slices and implement them for dynamic calldata arrays.
* Introduce ``push()`` for dynamic storage arrays. It returns a reference to the newly allocated element, if applicable.
* Introduce ``virtual`` and ``override`` keywords.
* Modify ``push(element)`` for dynamic storage arrays such that it does not return the new length anymore.
* Yul: Introduce ``leave`` statement that exits the current function.
* Allow public variables to override external functions.
Compiler Features:
* Allow revert strings to be stripped from the binary using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting.
* ABIEncoderV2: Do not warn about enabled ABIEncoderV2 anymore (the pragma is still needed, though).
### 0.5.14 (2019-12-09)
Language Features:
@ -42,7 +86,6 @@ Compiler Features:
* TypeChecker: List possible candidates when overload resolution fails.
* TypeChecker: Disallow variables of library types.
Bugfixes:
* Code Generator: Fixed a faulty assert that would wrongly trigger for array sizes exceeding unsigned integer.
* SMTChecker: Fix internal error when accessing indices of fixed bytes.
@ -53,7 +96,6 @@ Bugfixes:
* Code Generator: Fix internal error when trying to convert ``super`` to a different type
### 0.5.12 (2019-10-01)
Language Features:

View File

@ -10,7 +10,9 @@ include(CheckCXXCompilerFlag)
#
function(eth_add_cxx_compiler_flag_if_supported FLAG)
# Remove leading - or / from the flag name.
string(REGEX REPLACE "^-|/" "" name ${FLAG})
string(REGEX REPLACE "^[-/]" "" name ${FLAG})
# Deletes any ':' because it's invalid variable names.
string(REGEX REPLACE ":" "" name ${name})
check_cxx_compiler_flag(${FLAG} ${name})
if(${name})
add_compile_options(${FLAG})

View File

@ -345,7 +345,8 @@ commandline compiler for linking):
::
pragma solidity >=0.5.0 <0.7.0;
// This will not compile after 0.6.0
pragma solidity >=0.5.0 <0.5.99;
library OldLibrary {
function someFunction(uint8 a) public returns(bool);
@ -437,7 +438,7 @@ New version:
function f(uint y) external {
x = y;
}
function() payable external {}
receive() payable external {}
}
contract New {

View File

@ -0,0 +1,128 @@
********************************
Solidity v0.6.0 Breaking Changes
********************************
This section highlights the main breaking changes introduced in Solidity
version 0.6.0, along with the reasoning behind the changes and how to update
affected code.
For the full list check
`the release changelog <https://github.com/ethereum/solidity/releases/tag/v0.6.0>`_.
Syntactic Only Changes
======================
This section lists purely syntactic changes that do not affect the behavior of existing code.
* Conversions from external function types to ``address`` are now disallowed. Instead external
function types have a member called ``address``, similar to the existing ``selector`` member.
* Conversions from ``address`` to ``address payable`` are now possible via ``payable(x)``, where
``x`` must be of type ``address``.
* Function ``push(value)`` for dynamic storage arrays do not return the new length anymore.
* The new keyword ``abstract`` can be used to mark contracts as abstract. It has to be used
if a contract does not implement all its functions.
* Libraries have to implement all their functions, not only the internal ones.
* Member-access to ``length`` of arrays is now always read-only, even for storage arrays. It's no
longer possible to resize storage arrays assigning a new value to their length. Use ``push()``,
``push(value)`` or ``pop()`` instead, or assign a full array, which will of course overwrite existing content.
* New reserved keywords: ``virtual``.
* The names of variables declared in inline assembly may no longer end in ``_slot`` or ``_offset``.
* Variable declarations in inline assembly may no longer shadow any declaration outside the inline assembly block.
If the name contains a dot, its prefix up to the dot may not conflict with any declaration outside the inline
assembly block.
* State variable shadowing is now disallowed. A derived contract can only
declare a state variable ``x``, if there is no visible state variable with
the same name in any of its bases.
Semantic Only Changes
=====================
This section lists the changes that are semantic-only, thus potentially
hiding new and different behavior in existing code.
Semantic and Syntactic Changes
==============================
This section highlights changes that affect syntax and semantics.
* The resulting type of an exponentiation is the type of the base. It used to be the smallest type
that can hold both the type of the base and the type of the exponent, as with symmentric
operations. Additionally, signed types are allowed for the base of the exponetation.
* The unnamed function commonly referred to as "fallback function" was split up into a new
fallback function that is defined using the ``fallback`` keyword and a receive ether function
defined using the ``receive`` keyword.
* If present, the receive ether function is called whenever the call data is empty (whether
or not ether is received). This function is implicitly ``payable``.
* The new fallback function is called when no other function matches (if the receive ether
function does not exist then this includes calls with empty call data).
You can make this function ``payable`` or not. If it is not ``payable`` then transactions
not matching any other function which send value will revert. You should only need to
implement the new fallback function if you are following an upgrade or proxy pattern.
* Functions can now only be overridden when they are either marked with the
``virtual`` keyword or defined in an interface. Functions without
implementation outside an interface have to be marked ``virtual``.
When overriding a function or modifier, the new keyword ``override``
must be used. When overriding a function or modifier defined in multiple
parallel bases, all bases must be listed in parentheses after the keyword
like so: ``override(Base1, Base2)``.
How to update your code
=======================
This section gives detailed instructions on how to update prior code for every breaking change.
* Change ``address(f)`` to ``f.address`` for ``f`` being of external function type.
* Replace ``function () external [payable] { ... }`` by either ``receive() external payable { ... }``, ``fallback() external [payable] { ... }`` or both. Prefer using a ``receive`` function only, whenever possible.
* Change ``uint length = array.push(value)`` to ``array.push(value);``. The new length can be
accessed via ``array.length``.
* Change ``array.length++`` to ``array.push()`` to increase, and use ``pop()`` to decrease
the length of a storage array.
* For every named return parameter in a function's ``@dev`` documentation define a ``@return``
entry which contains the parameter's name as the first word. E.g. if you have function ``f()`` defined
like ``function f() public returns (uint value)`` and a ``@dev`` annotating it, document its return
parameters like so: ``@return value The return value.``. You can mix named and un-named return parameters
documentation so long as the notices are in the order they appear in the tuple return type.
* Choose unique identifiers for variable declarations in inline assembly that do not conflict with declartions outside the inline assembly block.
* Add ``virtual`` to every non-interface function you intend to override. Add ``virtual`` to all functions without implementation outside interfaces. For single inheritance, add ``override`` to every overriding function. For multiple inheritance, add ``override(A, B, ..)``, where you list all contracts that define the overridden function in the brackets. When multiple bases define the same function, the inheriting contract must override all conflicting functions.
New Features
============
* The :ref:`try/catch statement <try-catch>` allows you to react on failed external calls.
* Natspec supports multiple return parameters in dev documentation, enforcing the same naming check as ``@param``.
* Yul and Inline Assembly have a new statement called ``leave`` that exits the current function.
Deprecated Elements
===================
This section lists changes that deprecate prior features or syntax.
.. _interoperability_060:
Interoperability With Older Contracts
=====================================

View File

@ -444,8 +444,8 @@ JSON
The JSON format for a contract's interface is given by an array of function and/or event descriptions.
A function description is a JSON object with the fields:
- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-function>`).
- ``name``: the name of the function.
- ``type``: ``"function"``, ``"constructor"``, ``"receive"`` (the :ref:`"receive Ether" function <receive-ether-function>`) or ``"fallback"`` (the :ref:`"default" function <fallback-function>`);
- ``name``: the name of the function;
- ``inputs``: an array of objects, each of which contains:
* ``name``: the name of the parameter.
@ -454,16 +454,9 @@ A function description is a JSON object with the fields:
- ``outputs``: an array of objects similar to ``inputs``.
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain state <view-functions>`), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether).
- ``payable``: ``true`` if function accepts Ether, ``false`` otherwise.
- ``constant``: ``true`` if function is either ``pure`` or ``view``, ``false`` otherwise.
``type`` can be omitted, defaulting to ``"function"``, likewise ``payable`` and ``constant`` can be omitted, both defaulting to ``false``.
Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either.
.. warning::
The fields ``constant`` and ``payable`` are deprecated and will be removed in the future. Instead, the ``stateMutability`` field can be used to determine the same properties.
.. note::
Sending non-zero Ether to non-payable function will revert the transaction.
@ -539,8 +532,8 @@ As an example, the code
contract Test {
struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public;
function g() public returns (S memory s, T memory t, uint a);
function f(S memory s, T memory t, uint a) public {}
function g() public returns (S memory s, T memory t, uint a) {}
}
would result in the JSON:

View File

@ -439,6 +439,12 @@ for the variable and automatically removed again when the end of the block
is reached. You need to provide an initial value for the variable which can
be just ``0``, but it can also be a complex functional-style expression.
Since 0.6.0 the name of a declared variable may not end in ``_offset`` or ``_slot``
and it may not shadow any declaration visible in the scope of the inline assembly block
(including variable, contract and function declarations). Similarly, if the name of a declared
variable contains a dot ``.``, the prefix up to the ``.`` may not conflict with any
declaration visible in the scope of the inline assembly block.
.. code::
pragma solidity >=0.4.16 <0.7.0;
@ -530,6 +536,9 @@ the other two are blocks. If the initializing part
declares any variables, the scope of these variables is extended into the
body (including the condition and the post-iteration part).
The ``break`` and ``continue`` statements can be used to exit the loop
or skip to the post-part, respectively.
The following example computes the sum of an area in memory.
.. code::
@ -571,6 +580,11 @@ statement.
If you call a function that returns multiple values, you have to assign
them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``.
The ``leave`` statement can be used to exit the current function. It
works like the ``return`` statement in other languages just that it does
not take a value to return, it just exits the functions and the function
will return whatever values are currently assigned to the return variable(s).
The following example implements the power function by square-and-multiply.
.. code::
@ -763,14 +777,13 @@ Grammar::
AssemblyExpression |
AssemblyLocalDefinition |
AssemblyAssignment |
AssemblyStackAssignment |
LabelDefinition |
AssemblyIf |
AssemblySwitch |
AssemblyFunctionDefinition |
AssemblyFor |
'break' |
'continue' |
'leave' |
SubAssembly
AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral
AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral
@ -780,8 +793,6 @@ Grammar::
AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression
IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)*
AssemblyStackAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression AssemblyCase*
( 'default' AssemblyBlock )?

View File

@ -6,38 +6,49 @@
Abstract Contracts
******************
Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``)::
Contracts need to be marked as abstract when at least one of their functions is not implemented.
Contracts may be marked as abstract even though all functions are implemented.
This can be done by using the ``abstract`` keyword as shown in the following example. Note that this contract needs to be
defined as abstract, because the function ``utterance()`` was defined, but no implementation was
provided (no implementation body ``{ }`` was given).::
pragma solidity >=0.4.0 <0.7.0;
contract Feline {
function utterance() public returns (bytes32);
abstract contract Feline {
function utterance() public virtual returns (bytes32);
}
Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts::
Such abstract contracts can not be instantiated directly. This is also true, if an abstract contract itself does implement
all defined functions. The usage of an abstract contract as a base class is shown in the following example::
pragma solidity >=0.4.0 <0.7.0;
contract Feline {
function utterance() public returns (bytes32);
abstract contract Feline {
function utterance() public virtual returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
function utterance() public override returns (bytes32) { return "miaow"; }
}
If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract.
If a contract inherits from an abstract contract and does not implement all non-implemented
functions by overriding, it needs to be marked as abstract as well.
Note that a function without implementation is different from a :ref:`Function Type <function_types>` even though their syntax looks very similar.
Note that a function without implementation is different from
a :ref:`Function Type <function_types>` even though their syntax looks very similar.
Example of function without implementation (a function declaration)::
function foo(address) external returns (address);
Example of a Function Type (a variable declaration, where the variable is of type ``function``)::
Example of a declaration of a variable whose type is a function type::
function(address) external returns (address) foo;
Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and
Abstract contracts decouple the definition of a contract from its
implementation providing better extensibility and self-documentation and
facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/Template_method_pattern>`_ and removing code duplication.
Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say "any child of mine must implement this method".
Abstract contracts are useful in the same way that defining methods
in an interface is useful. It is a way for the designer of the
abstract contract to say "any child of mine must implement this method".

View File

@ -8,16 +8,17 @@ Contracts can be created "from outside" via Ethereum transactions or from within
IDEs, such as `Remix <https://remix.ethereum.org/>`_, make the creation process seamless using UI elements.
Creating contracts programmatically on Ethereum is best done via using the JavaScript API `web3.js <https://github.com/ethereum/web3.js>`_.
One way to create contracts programmatically on Ethereum is via the JavaScript API `web3.js <https://github.com/ethereum/web3.js>`_.
It has a function called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract>`_
to facilitate contract creation.
When a contract is created, its :ref:`constructor <constructor>` (a function declared with the ``constructor`` keyword) is executed once.
When a contract is created, its :ref:`constructor <constructor>` (a function declared with
the ``constructor`` keyword) is executed once.
A constructor is optional. Only one constructor is allowed, which means
overloading is not supported.
After the constructor has executed, the final code of the contract is deployed to the
After the constructor has executed, the final code of the contract is stored on the
blockchain. This code includes all public and external functions and all functions
that are reachable from there through function calls. The deployed code does not
include the constructor code or internal functions only called from the constructor.
@ -57,18 +58,20 @@ This means that cyclic creation dependencies are impossible.
// See the next section for details.
owner = msg.sender;
// We do an explicit type conversion from `address`
// We perform an explicit type conversion from `address`
// to `TokenCreator` and assume that the type of
// the calling contract is `TokenCreator`, there is
// no real way to check that.
// no real way to verify that.
// This does not create a new contract.
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(bytes32 newName) public {
// Only the creator can alter the name --
// the comparison is possible since contracts
// are explicitly convertible to addresses.
// Only the creator can alter the name.
// We compare the contract based on its
// address which can be retrieved by
// explicit conversion to address.
if (msg.sender == address(creator))
name = newName;
}
@ -94,9 +97,9 @@ This means that cyclic creation dependencies are impossible.
returns (OwnedToken tokenAddress)
{
// Create a new `Token` contract and return its address.
// From the JavaScript side, the return type is
// `address`, as this is the closest type available in
// the ABI.
// From the JavaScript side, the return type
// of this function is `address`, as this is
// the closest type available in the ABI.
return new OwnedToken(name);
}

View File

@ -13,12 +13,12 @@ Events are inheritable members of contracts. When you call them, they cause the
arguments to be stored in the transaction's log - a special data structure
in the blockchain. These logs are associated with the address of the contract,
are incorporated into the blockchain, and stay there as long as a block is
accessible (forever as of the Frontier and Homestead releases, but this might
accessible (forever as of now, but this might
change with Serenity). The Log and its event data is not accessible from within
contracts (not even from the contract that created them).
It is possible to request a simple payment verification (SPV) for logs, so if
an external entity supplies a contract with such a verification, it can check
It is possible to request a Merkle proof for logs, so if
an external entity supplies a contract with such a proof, it can check
that the log actually exists inside the blockchain. You have to supply block headers
because the contract can only see the last 256 block hashes.

View File

@ -6,9 +6,14 @@
Function Modifiers
******************
Modifiers can be used to easily change the behaviour of functions. For example,
they can automatically check a condition prior to executing the function. Modifiers are
inheritable properties of contracts and may be overridden by derived contracts.
Modifiers can be used to change the behaviour of functions in a declarative way.
For example,
you can use a modifier to automatically check a condition prior to executing the function.
Modifiers are
inheritable properties of contracts and may be overridden by derived contracts, but only
if they are marked ``virtual``. For details, please see
:ref:`Modifier Overriding <modifier-overriding>`.
::

View File

@ -11,8 +11,8 @@ Functions
Function Parameters and Return Variables
========================================
As in JavaScript, functions may take parameters as input. Unlike in JavaScript
and C, functions may also return an arbitrary number of values as output.
Functions take typed parameters as input and may, unlike in many other
languages, also return an arbitrary number of values as output.
Function Parameters
-------------------
@ -21,7 +21,7 @@ Function parameters are declared the same way as variables, and the name of
unused parameters can be omitted.
For example, if you want your contract to accept one kind of external call
with two integers, you would use something like::
with two integers, you would use something like the following::
pragma solidity >=0.4.16 <0.7.0;
@ -39,7 +39,7 @@ Function parameters can be used as any other local variable and they can also be
An :ref:`external function<external-function-calls>` cannot accept a
multi-dimensional array as an input
parameter. This functionality is possible if you enable the new
experimental ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file.
``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file.
An :ref:`internal function<external-function-calls>` can accept a
multi-dimensional array without enabling the feature.
@ -70,7 +70,8 @@ two integers passed as function parameters, then you use something like::
The names of return variables can be omitted.
Return variables can be used as any other local variable and they
are initialized with their :ref:`default value <default-value>` and have that value unless explicitly set.
are initialized with their :ref:`default value <default-value>` and have that
value until they are (re-)assigned.
You can either explicitly assign to return variables and
then leave the function using ``return;``,
@ -96,7 +97,7 @@ return variables and then using ``return;`` to leave the function.
.. note::
You cannot return some types from non-internal functions, notably
multi-dimensional dynamic arrays and structs. If you enable the
new experimental ``ABIEncoderV2`` feature by adding ``pragma experimental
new ``ABIEncoderV2`` feature by adding ``pragma experimental
ABIEncoderV2;`` to your source file then more types are available, but
``mapping`` types are still limited to inside a single contract and you
cannot transfer them.
@ -107,7 +108,8 @@ Returning Multiple Values
-------------------------
When a function has multiple return types, the statement ``return (v0, v1, ..., vn)`` can be used to return multiple values.
The number of components must be the same as the number of return types.
The number of components must be the same as the number of return variables
and their types have to match, potentially after an :ref:`implicit conversion <types-conversion-elementary-types>`.
.. index:: ! view function, function;view
@ -120,7 +122,7 @@ Functions can be declared ``view`` in which case they promise not to modify the
.. note::
If the compiler's EVM target is Byzantium or newer (default) the opcode
``STATICCALL`` is used for ``view`` functions which enforces the state
``STATICCALL`` is used when ``view`` functions are called, which enforces the state
to stay unmodified as part of the EVM execution. For library ``view`` functions
``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``.
This means library ``view`` functions do not have run-time checks that prevent state
@ -222,23 +224,25 @@ This behaviour is also in line with the ``STATICCALL`` opcode.
not do state-changing operations, but it cannot check that the contract that will be called
at runtime is actually of that type.
.. index:: ! fallback function, function;fallback
.. index:: ! receive ether function, function;receive ! receive
.. _fallback-function:
.. _receive-ether-function:
Fallback Function
=================
Receive Ether Function
======================
A contract can have exactly one unnamed function. This function cannot have
arguments, cannot return anything and has to have ``external`` visibility.
It is executed on a call to the contract if none of the other
functions match the given function identifier (or if no data was supplied at
all).
Furthermore, this function is executed whenever the contract receives plain
Ether (without data). To receive Ether and add it to the total balance of the contract, the fallback function
must be marked ``payable``. If no such function exists, the contract cannot receive
Ether through regular transactions and throws an exception.
A contract can have at most one ``receive`` function, declared using
``receive() external payable { ... }``
(without the ``function`` keyword).
This function cannot have arguments, cannot return anything and must have
``external`` visibility and ``payable`` state mutability. It is executed on a
call to the contract with empty calldata. This is the function that is executed
on plain Ether transfers (e.g. via `.send()` or `.transfer()`). If no such
function exists, but a payable :ref:`fallback function <fallback-function>`
exists, the fallback function will be called on a plain Ether transfer. If
neither a receive Ether nor a payable fallback function is present, the
contract cannot receive Ether through regular transactions and throws an
exception.
In the worst case, the fallback function can only rely on 2300 gas being
available (for example when `send` or `transfer` is used), leaving little
@ -250,32 +254,84 @@ will consume more gas than the 2300 gas stipend:
- Calling an external function which consumes a large amount of gas
- Sending Ether
Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it.
.. warning::
The fallback function is also executed if the caller meant to call
a function that is not available. If you want to implement the fallback
function only to receive ether, you should add a check
like ``require(msg.data.length == 0)`` to prevent invalid calls.
.. warning::
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
but do not define a fallback function
but do not define a receive Ether function or a payable fallback function
throw an exception, sending back the Ether (this was different
before Solidity v0.4.0). So if you want your contract to receive Ether,
you have to implement a payable fallback function.
you have to implement a receive Ether function (using payable fallback functions for receiving Ether is
not recommended, since it would not fail on interface confusions).
.. warning::
A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`)
A contract without a receive Ether function can receive Ether as a
recipient of a `coinbase transaction` (aka `miner block reward`)
or as a destination of a ``selfdestruct``.
A contract cannot react to such Ether transfers and thus also
cannot reject them. This is a design choice of the EVM and
Solidity cannot work around it.
It also means that ``address(this).balance`` can be higher
than the sum of some manual accounting implemented in a
contract (i.e. having a counter updated in the receive Ether function).
Below you can see an example of a Sink contract that uses function ``receive``.
::
pragma solidity >=0.5.0 <0.7.0;
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
event Received(address, uint);
receive() external payable {
emit Received(msg.sender, msg.value);
}
}
.. index:: ! fallback function, function;fallback
.. _fallback-function:
Fallback Function
=================
A contract can have at most one ``fallback`` function, declared using ``fallback () external [payable]``
(without the ``function`` keyword).
This function cannot have arguments, cannot return anything and must have ``external`` visibility.
It is executed on a call to the contract if none of the other
functions match the given function signature, or if no data was supplied at
all and there is no :ref:`receive Ether function <receive-ether-function>`.
The fallback function always receives data, but in order to also receive Ether
it must be marked ``payable``.
In the worst case, if a payable fallback function is also used in
place of a receive function, it can only rely on 2300 gas being
available (see :ref:`receive Ether function <receive-ether-function>`
for a brief description of the implications of this).
Like any function, the fallback function can execute complex
operations as long as there is enough gas passed on to it.
.. warning::
A ``payable`` fallback function is also executed for
plain Ether transfers, if no :ref:`receive Ether function <receive-ether-function>`
is present. It is recommended to always define a receive Ether
function as well, if you define a payable fallback function
to distinguish Ether transfers from interface confusions.
.. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
any payload supplied with the call.
After having checked the first four bytes of ``msg.data``,
you can use ``abi.decode`` together with the array slice syntax to
decode ABI-encoded data:
``(c, d) = abi.decode(msg.data[4:], (uint256, uint256));``
Note that this should only be used as a last resort and
proper functions should be used instead.
A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.
It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function).
::
@ -287,15 +343,23 @@ Like any function, the fallback function can execute complex operations as long
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable`
// modifier.
function() external { x = 1; }
fallback() external { x = 1; }
uint x;
}
contract TestPayable {
// This function is called for all messages sent to
// this contract, except plain Ether transfers
// (there is no other function except the receive function).
// Any call with non-empty calldata to this contract will execute
// the fallback function (even if Ether is sent along with the call).
fallback() external payable { x = 1; y = msg.value; }
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() external payable { }
// This function is called for plain Ether transfers, i.e.
// for every call with empty calldata.
receive() external payable { x = 2; y = msg.value; }
uint x;
uint y;
}
contract Caller {
@ -305,14 +369,27 @@ Like any function, the fallback function can execute complex operations as long
// results in test.x becoming == 1.
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable
// fallback function. It has to be converted to the ``address payable`` type via an
// intermediate conversion to ``uint160`` to even allow calling ``send`` on it.
address payable testPayable = address(uint160(address(test)));
// fallback function.
// It has to be converted to the ``address payable`` type to even allow calling ``send`` on it.
address payable testPayable = payable(address(test));
// If someone sends ether to that contract,
// If someone sends Ether to that contract,
// the transfer will fail, i.e. this returns false here.
return testPayable.send(2 ether);
}
function callTestPayable(TestPayable test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 0.
(success,) = address(test).call.value(1)(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 1.
// If someone sends Ether to that contract, the receive function in TestPayable will be called.
require(address(test).send(2 ether));
// results in test.x becoming == 2 and test.y becoming 2 ether.
}
}
.. index:: ! overload

View File

@ -6,9 +6,18 @@ Inheritance
Solidity supports multiple inheritance including polymorphism.
All function calls are virtual, which means that the most derived function
is called, except when the contract name is explicitly given or the
``super`` keyword is used.
Polymorphism means that a function call (internal and external)
always executes the function of the same name (and parameter types)
in the most derived contract in the inheritance hierarchy.
This has to be explicitly enabled on each function in the
hierarchy using the ``virtual`` and ``override`` keywords.
See :ref:`Function Overriding <function-overriding>` for more details.
It is possible to call functions further up in the inheritance
hierarchy internally by explicitly specifying the contract
using ``ContractName.functionName()`` or using ``super.functionName()``
if you want to call the function one level higher up in
the flattened inheritance hierarchy (see below).
When a contract inherits from other contracts, only a single
contract is created on the blockchain, and the code from all the base contracts
@ -16,6 +25,10 @@ is compiled into the created contract. This means that all internal calls
to functions of base contracts also just use internal function calls
(``super.f(..)`` will use JUMP and not a message call).
State variable shadowing is considered as an error. A derived contract can
only declare a state variable ``x``, if there is no visible state variable
with the same name in any of its bases.
The general inheritance system is very similar to
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_,
especially concerning multiple inheritance, but there are also
@ -39,7 +52,7 @@ Details are given in the following example.
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract Mortal is Owned {
function kill() public {
function kill() virtual public {
if (msg.sender == owner) selfdestruct(owner);
}
}
@ -49,14 +62,14 @@ Details are given in the following example.
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
contract Config {
function lookup(uint id) public returns (address adr);
abstract contract Config {
function lookup(uint id) public virtual returns (address adr);
}
contract NameReg {
function register(bytes32 name) public;
function unregister() public;
abstract contract NameReg {
function register(bytes32 name) public virtual;
function unregister() public virtual;
}
@ -74,7 +87,7 @@ Details are given in the following example.
// types of output parameters, that causes an error.
// Both local and message-based function calls take these overrides
// into account.
function kill() public {
function kill() public virtual override {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
@ -94,6 +107,7 @@ Details are given in the following example.
if (msg.sender == owner) info = newInfo;
}
function kill() public override (Mortal, Named) { Named.kill(); }
function get() public view returns(uint r) { return info; }
uint info;
@ -111,26 +125,26 @@ seen in the following example::
}
contract mortal is owned {
function kill() public {
function kill() public virtual {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ mortal.kill(); }
function kill() public virtual override { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ mortal.kill(); }
function kill() public virtual override { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
function kill() public override(Base1, Base2) { Base2.kill(); }
}
A call to ``Final.kill()`` will call ``Base2.kill`` as the most
derived override, but this function will bypass
``Base1.kill``, basically because it does not even know about
``Base1``. The way around this is to use ``super``::
A call to ``Final.kill()`` will call ``Base2.kill`` because we specify it
explicitly in the final override, but this function will bypass
``Base1.kill``. The way around this is to use ``super``::
pragma solidity >=0.4.22 <0.7.0;
@ -140,21 +154,22 @@ derived override, but this function will bypass
}
contract mortal is owned {
function kill() public {
function kill() virtual public {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ super.kill(); }
function kill() public virtual override { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ super.kill(); }
function kill() public virtual override { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base1, Base2 {
function kill() public override(Base1, Base2) { super.kill(); }
}
If ``Base2`` calls a function of ``super``, it does not simply
@ -168,6 +183,171 @@ not known in the context of the class where it is used,
although its type is known. This is similar for ordinary
virtual method lookup.
.. _function-overriding:
.. index:: ! overriding;function
Function Overriding
===================
Base functions can be overridden by inheriting contracts to change their
behavior if they are marked as ``virtual``. The overriding function must then
use the ``override`` keyword in the function header as shown in this example:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base
{
function foo() virtual public {}
}
contract Middle is Base {}
contract Inherited is Middle
{
function foo() public override {}
}
For multiple inheritance, the most derived base contracts that define the same
function must be specified explicitly after the ``override`` keyword.
In other words, you have to specify all base contracts that define the same function and have not yet been overridden by another base contract (on some path through the inheritance graph).
Additionally, if a contract inherits the same function from multiple (unrelated)
bases, it has to explicitly override it:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base1
{
function foo() virtual public {}
}
contract Base2
{
function foo() virtual public {}
}
contract Inherited is Base1, Base2
{
// Derives from multiple bases defining foo(), so we must explicitly
// override it
function foo() public override(Base1, Base2) {}
}
An explicit override specifier is not required if
the function is defined in a common base contract
or if there is a unique function in a common base contract
that already overrides all other functions.
::
pragma solidity >=0.5.0 <0.7.0;
contract A { function f() public pure{} }
contract B is A {}
contract C is A {}
// No explicit override required
contract D is B, C {}
More formally, it is not required to override a function (directly or
indirectly) inherited from multiple bases if there is a base contract
that is part of all override paths for the signature, and (1) that
base implements the function and no paths from the current contract
to the base mentions a function with that signature or (2) that base
does not implement the function and there is at most one mention of
the function in all paths from the current contract to that base.
In this sense, an override path for a signature is a path through
the inheritance graph that starts at the contract under consideration
and ends at a contract mentioning a function with that signature
that does not override.
.. note::
Functions with the ``private`` visibility cannot be ``virtual``.
.. note::
Functions without implementation have to be marked ``virtual``
outside of interfaces.
Public state variables can override external functions if the
parameter and return types of the function matches the getter function
of the variable:
::
pragma solidity >=0.5.0 <0.7.0;
contract A
{
function f() external virtual pure returns(uint) { return 5; }
}
contract B is A
{
uint public override f;
}
.. note::
While public state variables can override external functions, they themselves cannot
be overridden.
.. _modifier-overriding:
.. index:: ! overriding;modifier
Modifier Overriding
===================
Function modifiers can override each other. This works in the same way as
`function overriding <function-overriding>`_ (except that there is no overloading for modifiers). The
``virtual`` keyword must be used on the overridden modifier
and the ``override`` keyword must be used in the overriding modifier:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base
{
modifier foo() virtual {_;}
}
contract Inherited is Base
{
modifier foo() override {_;}
}
In case of multiple inheritance, all direct base contracts must be specified
explicitly:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base1
{
modifier foo() virtual {_;}
}
contract Base2
{
modifier foo() virtual {_;}
}
contract Inherited is Base1, Base2
{
modifier foo() override(Base1, Base2) {_;}
}
.. index:: ! constructor
.. _constructor:

View File

@ -15,6 +15,10 @@ Most of the control structures known from curly-braces languages are available i
There is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, with
the usual semantics known from C or JavaScript.
Solidity also supports exception handling in the form of ``try``/``catch``-statements,
but only for :ref:`external function calls <external-function-calls>` and
contract creation calls.
Parentheses can *not* be omitted for conditionals, but curly brances can be omitted
around single-statement bodies.
@ -227,7 +231,7 @@ groupings of expressions.
pragma solidity >0.4.23 <0.7.0;
contract C {
uint[] data;
uint index;
function f() public pure returns (uint, bool, uint) {
return (7, true, 2);
@ -240,7 +244,7 @@ groupings of expressions.
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
(data.length, , ) = f(); // Sets the length to 7
(index, , ) = f(); // Sets the index to 7
}
}
@ -383,7 +387,7 @@ of an exception instead of "bubbling up".
.. warning::
The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed.
It is not yet possible to catch exceptions with Solidity.
Exceptions can be caught with the ``try``/``catch`` statement.
``assert`` and ``require``
--------------------------
@ -488,3 +492,94 @@ In the above example, ``revert("Not enough Ether provided.");`` returns the foll
.. note::
There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which
was deprecated in version 0.4.13 and removed in version 0.5.0.
.. _try-catch:
``try``/``catch``
-----------------
A failure in an external call can be caught using a try/catch statement, as follows:
::
pragma solidity >=0.5.0 <0.7.0;
interface DataFeed { function getData(address token) external returns (uint value); }
contract FeedConsumer {
DataFeed feed;
uint errorCount;
function rate(address token) public returns (uint value, bool success) {
// Permanently disable the mechanism if there are
// more than 10 errors.
require(errorCount < 10);
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// This is executed in case
// revert was called inside getData
// and a reason string was provided.
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// This is executed in case revert() was used
// or there was a failing assertion, division
// by zero, etc. inside getData.
errorCount++;
return (0, false);
}
}
}
The ``try`` keyword has to be followed by an expression representing an external function call
or a contract creation (``new ContractName()``).
Errors inside the expression are not caught (for example if it is a complex expression
that also involves internal function calls), only a revert happening inside the external
call itself. The ``returns`` part (which is optional) that follows declares return variables
matching the types returned by the external call. In case there was no error,
these variables are assigned and the contract's execution continues inside the
first success block. If the end of the success block is reached, execution continues after the ``catch`` blocks.
Currently, Solidity supports different kinds of catch blocks depending on the
type of error. If the error was caused by ``revert("reasonString")`` or
``require(false, "reasonString")`` (or an internal error that causes such an
exception), then the catch clause
of the type ``catch Error(string memory reason)`` will be executed.
It is planned to support other types of error data in the future.
The string ``Error`` is currently parsed as is and is not treated as an identifier.
The clause ``catch (bytes memory lowLevelData)`` is executed if the error signature
does not match any other clause, there was an error during decoding of the error
message, if there was a failing assertion in the external
call (for example due to a division by zero or a failing ``assert()``) or
if no error data was provided with the exception.
The declared variable provides access to the low-level error data in that case.
If you are not interested in the error data, you can just use
``catch { ... }`` (even as the only catch clause).
In order to catch all error cases, you have to have at least the clause
``catch { ...}`` or the clause ``catch (bytes memory lowLevelData) { ... }``.
The variables declared in the ``returns`` and the ``catch`` clause are only
in scope in the block that follows.
.. note::
If an error happens during the decoding of the return data
inside a try/catch-statement, this causes an exception in the currently
executing contract and because of that, it is not caught in the catch clause.
If there is an error during decoding of ``catch Error(string memory reason)``
and there is a low-level catch clause, this error is caught there.
.. note::
If execution reaches a catch-block, then the state-changing effects of
the external call have been reverted. If execution reaches
the success block, the effects were not reverted.
If the effects have been reverted, then execution either continues
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).

View File

@ -7,7 +7,7 @@ ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
ContractDefinition = 'abstract'? ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
@ -62,12 +62,14 @@ StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+
CatchClause = 'catch' Identifier? ParameterList Block
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
@ -86,6 +88,7 @@ Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| IndexRangeAccess
| MemberAccess
| FunctionCall
| '(' Expression ')'
@ -123,6 +126,7 @@ FunctionCallArguments = '{' NameValueList? '}'
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
IndexRangeAccess = Expression '[' Expression? ':' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?

View File

@ -35,9 +35,7 @@ a good idea to read through the changelog at least for releases that contain
breaking changes. These releases always have versions of the form
``0.x.0`` or ``x.0.0``.
The version pragma is used as follows::
pragma solidity ^0.5.2;
The version pragma is used as follows: ``pragma solidity ^0.5.2;``
A source file with the line above does not compile with a compiler earlier than version 0.5.2,
and it also does not work on a compiler starting from version 0.6.0 (this

View File

@ -10,16 +10,18 @@ this file to query the compiler version, the sources used, the ABI and NatSpec
documentation to more safely interact with the contract and verify its source
code.
The compiler appends a Swarm hash of the metadata file to the end of the
bytecode (for details, see below) of each contract, so that you can retrieve
the file in an authenticated way without having to resort to a centralized
data provider.
The compiler appends by default the IPFS hash of the metadata file to the end
of the bytecode (for details, see below) of each contract, so that you can
retrieve the file in an authenticated way without having to resort to a
centralized data provider. The other available options are the Swarm hash and
not appending the metadata hash to the bytecode. These can be configured via
the :ref:`Standard JSON Interface<compiler-api>`.
You have to publish the metadata file to Swarm (or another service) so that
others can access it. You create the file by using the ``solc --metadata``
You have to publish the metadata file to IPFS, Swarm, or another service so
that others can access it. You create the file by using the ``solc --metadata``
command that generates a file called ``ContractName_meta.json``. It contains
Swarm references to the source code, so you have to upload all source files and
the metadata file.
IPFS and Swarm references to the source code, so you have to upload all source
files and the metadata file.
The metadata file has the following format. The example below is presented in a
human-readable way. Properly formatted metadata should use quotes correctly,
@ -86,7 +88,9 @@ explanatory purposes.
},
metadata: {
// Reflects the setting used in the input json, defaults to false
useLiteralContent: true
useLiteralContent: true,
// Reflects the setting used in the input json, defaults to "ipfs"
bytecodeHash: "ipfs"
}
// Required for Solidity: File and name of the contract or library this
// metadata is created for.
@ -111,8 +115,8 @@ explanatory purposes.
}
.. warning::
Since the bytecode of the resulting contract contains the metadata hash, any
change to the metadata results in a change of the bytecode. This includes
Since the bytecode of the resulting contract contains the metadata hash by default, any
change to the metadata might result in a change of the bytecode. This includes
changes to a filename or path, and since the metadata includes a hash of all the
sources used, a single whitespace change results in different metadata, and
different bytecode.
@ -124,7 +128,7 @@ Encoding of the Metadata Hash in the Bytecode
=============================================
Because we might support other ways to retrieve the metadata file in the future,
the mapping ``{"bzzr1": <Swarm hash>, "solc": <compiler version>}`` is stored
the mapping ``{"ipfs": <IPFS hash>, "solc": <compiler version>}`` is stored
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might
contain more keys (see below) and the beginning of that
encoding is not easy to find, its length is added in a two-byte big-endian
@ -132,12 +136,12 @@ encoding. The current version of the Solidity compiler usually adds the followin
to the end of the deployed bytecode::
0xa2
0x65 'b' 'z' 'z' 'r' '1' 0x58 0x20 <32 bytes swarm hash>
0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash>
0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding>
0x00 0x32
So in order to retrieve the data, the end of the deployed bytecode can be checked
to match that pattern and use the Swarm hash to retrieve the file.
to match that pattern and use the IPFS hash to retrieve the file.
Whereas release builds of solc use a 3 byte encoding of the version as shown
above (one byte each for major, minor and patch version number), prerelease builds
@ -145,25 +149,25 @@ will instead use a complete version string including commit hash and build date.
.. note::
The CBOR mapping can also contain other keys, so it is better to fully
decode the data instead of relying on it starting with ``0xa265``.
decode the data instead of relying on it starting with ``0xa264``.
For example, if any experimental features that affect code generation
are used, the mapping will also contain ``"experimental": true``.
.. note::
The compiler currently uses the "swarm version 1" hash of the metadata,
but this might change in the future, so do not rely on this sequence
to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '1'``. We might also
add additional data to this CBOR structure, so the
best option is to use a proper CBOR parser.
The compiler currently uses the IPFS hash of the metadata by default, but
it may also use the bzzr1 hash or some other hash in the future, so do
not rely on this sequence to start with ``0xa2 0x64 'i' 'p' 'f' 's'``. We
might also add additional data to this CBOR structure, so the best option
is to use a proper CBOR parser.
Usage for Automatic Interface Generation and NatSpec
====================================================
The metadata is used in the following way: A component that wants to interact
with a contract (e.g. Mist or any wallet) retrieves the code of the contract, from that
the Swarm hash of a file which is then retrieved.
That file is JSON-decoded into a structure like above.
with a contract (e.g. Mist or any wallet) retrieves the code of the contract,
from that the IPFS/Swarm hash of a file which is then retrieved. That file
is JSON-decoded into a structure like above.
The component can then use the ABI to automatically generate a rudimentary
user interface for the contract.
@ -177,7 +181,7 @@ For additional information, read :doc:`Ethereum Natural Language Specification (
Usage for Source Code Verification
==================================
In order to verify the compilation, sources can be retrieved from Swarm
In order to verify the compilation, sources can be retrieved from IPFS/Swarm
via the link in the metadata file.
The compiler of the correct version (which is checked to be part of the "official" compilers)
is invoked on that input with the specified settings. The resulting

View File

@ -487,7 +487,8 @@ Furthermore, the compiler can also generate a mapping from the bytecode
to the range in the source code that generated the instruction. This is again
important for static analysis tools that operate on bytecode level and
for displaying the current position in the source code inside a debugger
or for breakpoint handling.
or for breakpoint handling. This mapping also contains other information,
like the jump type and the modifier depth (see below).
Both kinds of source mappings use integer identifiers to refer to source files.
The identifier of a source file is stored in
@ -509,12 +510,17 @@ Where ``s`` is the byte-offset to the start of the range in the source file,
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
It is a list of ``s:l:f:j`` separated by ``;``. Each of these
It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these
elements corresponds to an instruction, i.e. you cannot use the byte offset
but have to use the instruction offset (push instructions are longer than a single byte).
The fields ``s``, ``l`` and ``f`` are as above and ``j`` can be either
The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either
``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a
function, returns from a function or is a regular jump as part of e.g. a loop.
The last field, ``m``, is an integer that denotes the "modifier depth". This depth
is increased whenever the placeholder statement (``_``) is entered in a modifier
and decreased when it is left again. This allows debuggers to track tricky cases
like the same modifier being used twice or multiple placeholder statements being
used in a single modifier.
In order to compress these source mappings especially for bytecode, the
following rules are used:
@ -715,7 +721,7 @@ These keywords are reserved in Solidity. They might become part of the syntax in
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``,
``unchecked``.
``unchecked``, ``virtual``.
Language Grammar
================

View File

@ -49,7 +49,7 @@ The following example shows a contract and a function using all available tags.
.. code:: solidity
pragma solidity ^0.5.6;
pragma solidity >=0.5.0 <0.7.0;
/// @title A simulator for trees
/// @author Larry A. Gardner

View File

@ -224,7 +224,7 @@ Now someone tricks you into sending ether to the address of this attack wallet:
owner = msg.sender;
}
function() external {
fallback() external {
TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
}
}
@ -278,7 +278,8 @@ a ``mapping``.
mapping (uint => uint)[] array;
function allocate(uint _newMaps) public {
array.length += _newMaps;
for (uint i = 0; i < _newMaps; i++)
array.push();
}
function writeMap(uint _map, uint _key, uint _value) public {

View File

@ -19,3 +19,4 @@ If something is missing here, please contact us on
assembly.rst
miscellaneous.rst
050-breaking-changes.rst
060-breaking-changes.rst

View File

@ -67,6 +67,11 @@ Function Modifiers
Function modifiers can be used to amend the semantics of functions in a declarative way
(see :ref:`modifiers` in the contracts section).
Overloading, that is, having the same modifier name with different parameters,
is not possible.
Like functions, modifiers can be :ref:`overridden <modifier-overriding>`.
::
pragma solidity >=0.4.22 <0.7.0;

View File

@ -91,18 +91,18 @@ Yes::
pragma solidity >=0.4.0 <0.7.0;
contract A {
function spam() public pure;
function ham() public pure;
abstract contract A {
function spam() public virtual pure;
function ham() public virtual pure;
}
contract B is A {
function spam() public pure {
function spam() public override pure {
// ...
}
function ham() public pure {
function ham() public override pure {
// ...
}
}
@ -273,6 +273,7 @@ Ordering helps readers identify which functions they can call and to find the co
Functions should be grouped according to their visibility and ordered:
- constructor
- receive function (if exists)
- fallback function (if exists)
- external
- public
@ -290,7 +291,11 @@ Yes::
// ...
}
function() external {
receive() external payable {
// ...
}
fallback() external {
// ...
}
@ -322,7 +327,10 @@ No::
// External functions
// ...
function() external {
fallback() external {
// ...
}
receive() external payable {
// ...
}
@ -384,20 +392,29 @@ No::
y = 2;
long_variable = 3;
Don't include a whitespace in the fallback function:
Don't include a whitespace in the receive and fallback functions:
Yes::
function() external {
receive() external payable {
...
}
fallback() external {
...
}
No::
function () external {
receive () external payable {
...
}
fallback () external {
...
}
Control Structures
==================

View File

@ -128,3 +128,5 @@ As described in :ref:`address_literals`, hex literals of the correct size that p
test are of ``address`` type. No other literals can be implicitly converted to the ``address`` type.
Explicit conversions from ``bytes20`` or any integer type to ``address`` result in ``address payable``.
An ``address a`` can be converted to ``address payable`` via ``payable(a)``.

View File

@ -137,7 +137,8 @@ the ``sum`` function iterates over to sum all the values.
if (keyIndex > 0)
return true;
else {
keyIndex = self.keys.length++;
self.keys.push();
keyIndex = self.keys.length;
self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key;
self.size++;

View File

@ -59,7 +59,7 @@ Data locations are not only relevant for persistency of data, but also for the s
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.length = 2; // fine, modifies x through y
y.pop(); // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
@ -109,8 +109,9 @@ restrictions for types apply, in that mappings can only be stored in the
It is possible to mark state variable arrays ``public`` and have Solidity create a :ref:`getter <visibility-and-getters>`.
The numeric index becomes a required parameter for the getter.
Accessing an array past its end causes a failing assertion. You can use the ``.push()`` method to append a new element at the end or assign to the ``.length`` :ref:`member <array-members>` to change the size (see below for caveats).
method or increase the ``.length`` :ref:`member <array-members>` to add elements.
Accessing an array past its end causes a failing assertion. Methods ``.push()`` and ``.push(value)`` can be used
to append a new element at the end of the array, where ``.push()`` appends a zero-initialized element and returns
a reference to it.
``bytes`` and ``strings`` as Arrays
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -214,25 +215,18 @@ Array Members
**length**:
Arrays have a ``length`` member that contains their number of elements.
The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created.
For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array.
Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion.
Increasing the length adds new zero-initialised elements to the array.
Reducing the length performs an implicit :ref:`delete<delete>` on each of the
removed elements. If you try to resize a non-dynamic array that isn't in
storage, you receive a ``Value must be an lvalue`` error.
**push**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length.
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array.
If no argument is given, the element will be zero-initialised and a reference to the new element is returned.
If a value is given as argument, ``push`` returns nothing.
**pop**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:`delete<delete>` on the removed element.
.. warning::
If you use ``.length--`` on an empty array, it causes an underflow and
thus sets the length to ``2**256-1``.
.. note::
Increasing the length of a storage array has constant gas costs because
storage is assumed to be zero-initialised, while decreasing
the length has at least linear cost (but in most cases worse than linear),
Increasing the length of a storage array by calling ``push()``
has constant gas costs because storage is zero-initialised,
while decreasing the length by calling ``pop()`` has at least
linear cost (but in most cases worse than linear),
because it includes explicitly clearing the removed
elements similar to calling :ref:`delete<delete>` on them.
@ -292,7 +286,13 @@ Array Members
function changeFlagArraySize(uint newSize) public {
// if the new size is smaller, removed array elements will be cleared
m_pairsOfFlags.length = newSize;
if (newSize < m_pairsOfFlags.length) {
while (m_pairsOfFlags.length > newSize)
m_pairsOfFlags.pop();
} else if (newSize > m_pairsOfFlags.length) {
while (m_pairsOfFlags.length < newSize)
m_pairsOfFlags.push();
}
}
function clear() public {
@ -300,7 +300,7 @@ Array Members
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// identical effect here
m_pairsOfFlags.length = 0;
m_pairsOfFlags = new bool[2][](0);
}
bytes m_byteData;
@ -309,13 +309,15 @@ Array Members
// byte arrays ("bytes") are different as they are stored without padding,
// but can be treated identical to "uint8[]"
m_byteData = data;
m_byteData.length += 7;
for (uint i = 0; i < 7; i++)
m_byteData.push();
m_byteData[3] = 0x08;
delete m_byteData[2];
}
function addFlag(bool[2] memory flag) public returns (uint) {
return m_pairsOfFlags.push(flag);
m_pairsOfFlags.push(flag);
return m_pairsOfFlags.length;
}
function createMemoryArray(uint size) public pure returns (bytes memory) {

View File

@ -123,8 +123,9 @@ results in the same sign as its left operand (or zero) and ``a % n == -(-a % n)`
Exponentiation
^^^^^^^^^^^^^^
Exponentiation is only available for unsigned types. Please take care that the types
you are using are large enough to hold the result and prepare for potential wrapping behaviour.
Exponentiation is only available for unsigned types in the exponent. The resulting type
of an exponentiation is always equal to the type of the base. Please take care that it is
large enough to hold the result and prepare for potential wrapping behaviour.
.. note::
Note that ``0**0`` is defined by the EVM as ``1``.
@ -170,18 +171,20 @@ while a plain ``address`` cannot be sent Ether.
Type conversions:
Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are
not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``).
Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable``
must be explicit via ``payable(<address>)``.
:ref:`Address literals<address_literals>` can be implicitly converted to ``address payable``.
Explicit conversions to and from ``address`` are allowed for integers, integer literals, ``bytes20`` and contract types with the following
caveat:
Conversions of the form ``address payable(x)`` are not allowed. Instead the result of a conversion of the form ``address(x)``
The result of a conversion of the form ``address(x)``
has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a literal or a contract with a payable fallback function.
If ``x`` is a contract without payable fallback function, then ``address(x)`` will be of type ``address``.
In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type.
Only expressions of type ``address`` can be converted to type ``address payable`` via ``payable(<address>)``.
.. note::
It might very well be that you do not need to care about the distinction between ``address``
and ``address payable`` and just use ``address`` everywhere. For example,
@ -229,7 +232,7 @@ or if the Ether transfer is rejected by the receiving account. The ``transfer``
reverts on failure.
.. note::
If ``x`` is a contract address, its code (more specifically: its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception.
If ``x`` is a contract address, its code (more specifically: its :ref:`receive-ether-function`, if present, or otherwise its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception.
* ``send``
@ -309,10 +312,12 @@ Every :ref:`contract<contracts>` defines its own type.
You can implicitly convert contracts to contracts they inherit from.
Contracts can be explicitly converted to and from the ``address`` type.
Explicit conversion to and from the ``address payable`` type
is only possible if the contract type has a payable fallback function.
The conversion is still performed using ``address(x)`` and not
using ``address payable(x)``. You can find more information in the section about
Explicit conversion to and from the ``address payable`` type is only possible
if the contract type has a payable fallback function. The conversion is still
performed using ``address(x)``. If the contract type does not have a payable
fallback function, the conversion to ``address payable`` can be done using
``payable(address(x))``.
You can find more information in the section about
the :ref:`address type<address>`.
.. note::
@ -498,7 +503,14 @@ terminate the string literal. Newline only terminates the string literal if it i
Hexadecimal Literals
--------------------
Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``), and they can also be split into multiple consecutive parts (``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"``). Their content must be a hexadecimal string and their value will be the binary representation of those values.
Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double
or single-quotes (``hex"001122FF"``, ``hex'0011_22_FF'``). Their content must be
hexadecimal digits which can optionally use a single underscore as separator between
byte boundaries. The value of the literal will be the binary representation
of the hexadecimal sequence.
Multiple hexadecimal literals separated by whitespace are concatenated into a single literal:
``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"``
Hexadecimal literals behave like :ref:`string literals <string_literals>` and have the same convertibility restrictions.
@ -582,9 +594,6 @@ do not have a default.
Conversions:
A value of external function type can be explicitly converted to ``address``
resulting in the address of the contract of the function.
A function type ``A`` is implicitly convertible to a function type ``B`` if and only if
their parameter types are identical, their return types are identical,
their internal/external property is identical and the state mutability of ``A``
@ -616,8 +625,9 @@ just use ``f``, if you want to use its external form, use ``this.f``.
Members:
Public (or external) functions have the following members:
External (or public) functions have the following members:
* ``.address`` returns the address of the contract of the function.
* ``.selector`` returns the :ref:`ABI function selector <abi_function_selector>`
* ``.gas(uint)`` returns a callable function object which, when called, will send the specified amount of gas to the target function. See :ref:`External Function Calls <external-function-calls>` for more information.
* ``.value(uint)`` returns a callable function object which, when called, will send the specified amount of wei to the target function. See :ref:`External Function Calls <external-function-calls>` for more information.
@ -629,6 +639,7 @@ Example that shows how to use the members::
contract Example {
function f() public payable returns (bytes4) {
assert(this.f.address == address(this));
return this.f.selector;
}

View File

@ -14,7 +14,7 @@ Using the Commandline Compiler
One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler.
Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast-json --asm sourceFile.sol``.
Before you deploy your contract, activate the optimizer when compiling using ``solc --optimize --bin sourceFile.sol``.
By default, the optimizer will optimize the contract assuming it is called 200 times across its lifetime
@ -236,10 +236,25 @@ Input Description
// Affects type checking and code generation. Can be homestead,
// tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin
"evmVersion": "byzantium",
// Optional: Debugging settings
"debug": {
// How to treat revert (and require) reason strings. Settings are
// "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)
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
"revertStrings": "default"
}
// Metadata settings (optional)
"metadata": {
// Use only literal content and not URLs (false by default)
"useLiteralContent": true
"useLiteralContent": true,
// Use the given hash method for the metadata hash that is appended to the bytecode.
// The metadata hash can be removed from the bytecode via option "none".
// The other options are "ipfs" and "bzzr1".
// If the option is omitted, "ipfs" is used by default.
"bytecodeHash": "ipfs"
},
// Addresses of the libraries. If not all libraries are given here,
// it can result in unlinked objects whose output data is different.

View File

@ -100,7 +100,8 @@ Grammar::
Expression |
Switch |
ForLoop |
BreakContinue
BreakContinue |
Leave
FunctionDefinition =
'function' Identifier '(' TypedIdentifierList? ')'
( '->' TypedIdentifierList )? Block
@ -122,6 +123,7 @@ Grammar::
'for' Block Expression Block Block
BreakContinue =
'break' | 'continue'
Leave = 'leave'
FunctionCall =
Identifier '(' ( Expression ( ',' Expression )* )? ')'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]*
@ -167,6 +169,7 @@ In all other situations, expressions have to evaluate to exactly one value.
The ``continue`` and ``break`` statements can only be used inside loop bodies
and have to be in the same function as the loop (or both have to be at the
top level).
The ``leave`` statement can only be used inside a function.
The condition part of the for-loop has to evaluate to exactly one value.
Functions cannot be defined anywhere inside for loop init blocks.
@ -220,7 +223,7 @@ The two state objects are the global state object
blockchain) and the local state object (the state of local variables, i.e. a
segment of the stack in the EVM).
If the AST node is a statement, E returns the two state objects and a "mode",
which is used for the ``break`` and ``continue`` statements.
which is used for the ``break``, ``continue`` and ``leave`` statements.
If the AST node is an expression, E returns the two state objects and
as many values as the expression evaluates to.
@ -261,12 +264,13 @@ We will use a destructuring notation for the AST nodes.
G, L2, regular
E(G, L, <for { i1, ..., in } condition post body>: ForLoop) =
if n >= 1:
let G1, L1, mode = E(G, L, i1, ..., in)
// mode has to be regular due to the syntactic restrictions
let G1, L, mode = E(G, L, i1, ..., in)
// mode has to be regular or leave due to the syntactic restrictions
if mode is leave then
G1, L1 restricted to variables of L, leave
otherwise
let G2, L2, mode = E(G1, L1, for {} condition post body)
// mode has to be regular due to the syntactic restrictions
let L3 be the restriction of L2 to only variables of L
G2, L3, regular
G2, L2 restricted to variables of L, mode
else:
let G1, L1, v = E(G, L, condition)
if v is false:
@ -275,13 +279,20 @@ We will use a destructuring notation for the AST nodes.
let G2, L2, mode = E(G1, L, body)
if mode is break:
G2, L2, regular
otherwise if mode is leave:
G2, L2, leave
else:
G3, L3, mode = E(G2, L2, post)
if mode is leave:
G2, L3, leave
otherwise
E(G3, L3, for {} condition post body)
E(G, L, break: BreakContinue) =
G, L, break
E(G, L, continue: BreakContinue) =
G, L, continue
E(G, L, leave: Leave) =
G, L, leave
E(G, L, <if condition body>: If) =
let G0, L0, v = E(G, L, condition)
if v is true:

View File

@ -23,6 +23,7 @@
#pragma once
#include <iterator>
#include <libdevcore/Common.h>
#include <vector>
@ -49,14 +50,27 @@ template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U&& _
std::move(_b.begin(), _b.end(), std::back_inserter(_a));
return _a;
}
/// Concatenate the contents of a container onto a multiset
template <class U, class... T> std::multiset<T...>& operator+=(std::multiset<T...>& _a, U const& _b)
{
_a.insert(_b.begin(), _b.end());
return _a;
}
/// Concatenate the contents of a container onto a multiset, move variant.
template <class U, class... T> std::multiset<T...>& operator+=(std::multiset<T...>& _a, U&& _b)
{
for (auto&& x: _b)
_a.insert(std::move(x));
return _a;
}
/// Concatenate the contents of a container onto a set
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U const& _b)
template <class U, class... T> std::set<T...>& operator+=(std::set<T...>& _a, U const& _b)
{
_a.insert(_b.begin(), _b.end());
return _a;
}
/// Concatenate the contents of a container onto a set, move variant.
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U&& _b)
template <class U, class... T> std::set<T...>& operator+=(std::set<T...>& _a, U&& _b)
{
for (auto&& x: _b)
_a.insert(std::move(x));
@ -97,9 +111,27 @@ inline std::set<T> operator+(std::set<T>&& _a, U&& _b)
ret += std::forward<U>(_b);
return ret;
}
/// Remove one set from another one.
template <class T>
inline std::set<T>& operator-=(std::set<T>& _a, std::set<T> const& _b)
/// Remove the elements of a container from a set.
template <class C, class... T>
inline std::set<T...>& operator-=(std::set<T...>& _a, C const& _b)
{
for (auto const& x: _b)
_a.erase(x);
return _a;
}
template <class C, class... T>
inline std::set<T...> operator-(std::set<T...> const& _a, C const& _b)
{
auto result = _a;
result -= _b;
return result;
}
/// Remove the elements of a container from a multiset.
template <class C, class... T>
inline std::multiset<T...>& operator-=(std::multiset<T...>& _a, C const& _b)
{
for (auto const& x: _b)
_a.erase(x);
@ -109,6 +141,21 @@ inline std::set<T>& operator-=(std::set<T>& _a, std::set<T> const& _b)
namespace dev
{
template <class T, class U>
T convertContainer(U const& _from)
{
return T{_from.cbegin(), _from.cend()};
}
template <class T, class U>
T convertContainer(U&& _from)
{
return T{
std::make_move_iterator(_from.begin()),
std::make_move_iterator(_from.end())
};
}
// String conversion functions, mainly to/from hex/nibble/byte representations.
enum class WhenError
@ -260,6 +307,12 @@ bool contains(T const& _t, V const& _v)
return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v);
}
template <class T, class Predicate>
bool contains_if(T const& _t, Predicate const& _p)
{
return std::end(_t) != std::find_if(std::begin(_t), std::end(_t), _p);
}
/// Function that iterates over a vector, calling a function on each of its
/// elements. If that function returns a vector, the element is replaced by
/// the returned vector. During the iteration, the original vector is only valid

View File

@ -86,6 +86,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i)
m_items.emplace_back(_i);
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty())
m_items.back().setLocation(m_currentSourceLocation);
m_items.back().m_modifierDepth = m_currentModifierDepth;
return back();
}

View File

@ -181,6 +181,8 @@ protected:
int m_deposit = 0;
langutil::SourceLocation m_currentSourceLocation;
public:
size_t m_currentModifierDepth = 0;
};
inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a)

View File

@ -146,6 +146,8 @@ public:
std::string toAssemblyText() const;
size_t m_modifierDepth = 0;
private:
AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation

View File

@ -68,7 +68,7 @@ string langutil::to_string(ScannerError _errorCode)
{
case ScannerError::NoError: return "No error.";
case ScannerError::IllegalToken: return "Invalid token.";
case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles within double-quotes.";
case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles.";
case ScannerError::IllegalHexDigit: return "Hexadecimal digit missing or invalid.";
case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator.";
case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence.";
@ -784,13 +784,25 @@ Token Scanner::scanHexString()
char const quote = m_char;
advance(); // consume quote
LiteralScope literal(this, LITERAL_TYPE_STRING);
bool allowUnderscore = false;
while (m_char != quote && !isSourcePastEndOfInput())
{
char c = m_char;
if (!scanHexByte(c))
// can only return false if hex-byte is incomplete (only one hex digit instead of two)
return setError(ScannerError::IllegalHexString);
if (scanHexByte(c))
{
addLiteralChar(c);
allowUnderscore = true;
}
else if (c == '_')
{
advance();
if (!allowUnderscore || m_char == quote)
return setError(ScannerError::IllegalNumberSeparator);
allowUnderscore = false;
}
else
return setError(ScannerError::IllegalHexString);
}
if (m_char != quote)

View File

@ -72,6 +72,9 @@ void ElementaryTypeNameToken::assertDetails(Token _baseType, unsigned const& _fi
"No elementary type " + string(TokenTraits::toString(_baseType)) + to_string(_first) + "x" + to_string(_second) + "."
);
}
else
solAssert(_first == 0 && _second == 0, "Unexpected size arguments");
m_token = _baseType;
m_firstNumber = _first;
m_secondNumber = _second;

View File

@ -143,6 +143,7 @@ namespace langutil
/* Inline Assembly Operators */ \
T(AssemblyAssign, ":=", 2) \
/* Keywords */ \
K(Abstract, "abstract", 0) \
K(Anonymous, "anonymous", 0) \
K(As, "as", 0) \
K(Assembly, "assembly", 0) \
@ -157,6 +158,7 @@ namespace langutil
K(Emit, "emit", 0) \
K(Event, "event", 0) \
K(External, "external", 0) \
K(Fallback, "fallback", 0) \
K(For, "for", 0) \
K(Function, "function", 0) \
K(Hex, "hex", 0) \
@ -171,11 +173,13 @@ namespace langutil
K(Memory, "memory", 0) \
K(Modifier, "modifier", 0) \
K(New, "new", 0) \
K(Override, "override", 0) \
K(Payable, "payable", 0) \
K(Public, "public", 0) \
K(Pragma, "pragma", 0) \
K(Private, "private", 0) \
K(Pure, "pure", 0) \
K(Receive, "receive", 0) \
K(Return, "return", 0) \
K(Returns, "returns", 0) \
K(Storage, "storage", 0) \
@ -186,6 +190,7 @@ namespace langutil
K(Using, "using", 0) \
K(Var, "var", 0) \
K(View, "view", 0) \
K(Virtual, "virtual", 0) \
K(While, "while", 0) \
\
/* Ether subdenominations */ \
@ -228,7 +233,6 @@ namespace langutil
T(Identifier, nullptr, 0) \
\
/* Keywords reserved for future use. */ \
K(Abstract, "abstract", 0) \
K(After, "after", 0) \
K(Alias, "alias", 0) \
K(Apply, "apply", 0) \
@ -249,7 +253,6 @@ namespace langutil
K(Mutable, "mutable", 0) \
K(NullLiteral, "null", 0) \
K(Of, "of", 0) \
K(Override, "override", 0) \
K(Partial, "partial", 0) \
K(Promise, "promise", 0) \
K(Reference, "reference", 0) \
@ -311,7 +314,7 @@ namespace TokenTraits
constexpr bool isEtherSubdenomination(Token op) { return op == Token::SubWei || op == Token::SubSzabo || op == Token::SubFinney || op == Token::SubEther; }
constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; }
constexpr bool isReservedKeyword(Token op) { return (Token::Abstract <= op && op <= Token::Unchecked); }
constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Unchecked); }
inline Token AssignmentToBinaryOp(Token op)
{

View File

@ -38,22 +38,22 @@ using namespace solidity;
namespace
{
ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullptr)
ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, void* _readContext)
{
ReadCallback::Callback readCallback;
if (_readCallback)
{
readCallback = [=](string const& _path)
readCallback = [=](string const& _kind, string const& _data)
{
char* contents_c = nullptr;
char* error_c = nullptr;
_readCallback(_path.c_str(), &contents_c, &error_c);
_readCallback(_readContext, _kind.c_str(), _data.c_str(), &contents_c, &error_c);
ReadCallback::Result result;
result.success = true;
if (!contents_c && !error_c)
{
result.success = false;
result.responseOrErrorMessage = "File not found.";
result.responseOrErrorMessage = "Callback not supported.";
}
if (contents_c)
{
@ -73,9 +73,9 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = n
return readCallback;
}
string compile(string _input, CStyleReadFileCallback _readCallback = nullptr)
string compile(string _input, CStyleReadFileCallback _readCallback, void* _readContext)
{
StandardCompiler compiler(wrapReadCallback(_readCallback));
StandardCompiler compiler(wrapReadCallback(_readCallback, _readContext));
return compiler.compile(std::move(_input));
}
@ -94,9 +94,9 @@ extern char const* solidity_version() noexcept
{
return VersionString.c_str();
}
extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) noexcept
extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) noexcept
{
s_outputBuffer = compile(_input, _readCallback);
s_outputBuffer = compile(_input, _readCallback, _readContext);
return s_outputBuffer.c_str();
}
extern void solidity_free() noexcept

View File

@ -34,10 +34,18 @@
extern "C" {
#endif
/// Callback used to retrieve additional source files.
/// Callback used to retrieve additional source files or data.
///
/// "Returns" two pointers that should be heap-allocated and are free'd by the caller.
typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error);
/// @param _context The readContext passed to solidity_compile. Can be NULL.
/// @param _kind The kind of callback (a string).
/// @param _data The data for the callback.
/// @param o_contents A pointer to the contents of the file, if found.
/// @param o_error A pointer to an error message, if there is one.
///
/// If the callback is not supported, o_contents and o_error should be set to NULL.
///
/// The two pointers (o_contents and o_error) should be heap-allocated and are free'd by the caller.
typedef void (*CStyleReadFileCallback)(void* _context, char const* _kind, char const* _data, char** o_contents, char** o_error);
/// Returns the complete license document.
///
@ -52,8 +60,12 @@ char const* solidity_version() SOLC_NOEXCEPT;
/// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns
/// a "Standard Output JSON". Both are to be UTF-8 encoded.
///
/// The pointer returned must not be freed by the caller.
char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) SOLC_NOEXCEPT;
/// @param _input The input JSON to process.
/// @param _readCallback The optional callback pointer. Can be NULL.
/// @param _readContext An optional context pointer passed to _readCallback. Can be NULL.
///
/// @returns A pointer to the result. The pointer returned must not be freed by the caller.
char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) SOLC_NOEXCEPT;
/// Frees up any allocated memory.
///

View File

@ -39,8 +39,6 @@ set(sources
ast/ASTForward.h
ast/ASTJsonConverter.cpp
ast/ASTJsonConverter.h
ast/ASTPrinter.cpp
ast/ASTPrinter.h
ast/ASTUtils.cpp
ast/ASTUtils.h
ast/ASTVisitor.h
@ -107,6 +105,7 @@ set(sources
interface/ABI.h
interface/CompilerStack.cpp
interface/CompilerStack.h
interface/DebugSettings.h
interface/GasEstimator.cpp
interface/GasEstimator.h
interface/Natspec.cpp

View File

@ -26,6 +26,7 @@
#include <libsolidity/analysis/TypeChecker.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string/predicate.hpp>
using namespace std;
@ -33,20 +34,113 @@ using namespace dev;
using namespace langutil;
using namespace dev::solidity;
namespace
{
// Helper struct to do a search by name
struct MatchByName
{
string const& m_name;
bool operator()(CallableDeclaration const* _callable)
{
return _callable->name() == m_name;
}
};
vector<ASTPointer<UserDefinedTypeName>> sortByContract(vector<ASTPointer<UserDefinedTypeName>> const& _list)
{
auto sorted = _list;
stable_sort(sorted.begin(), sorted.end(),
[] (ASTPointer<UserDefinedTypeName> _a, ASTPointer<UserDefinedTypeName> _b) {
if (!_a || !_b)
return _a < _b;
Declaration const* aDecl = _a->annotation().referencedDeclaration;
Declaration const* bDecl = _b->annotation().referencedDeclaration;
if (!aDecl || !bDecl)
return aDecl < bDecl;
return aDecl->id() < bDecl->id();
}
);
return sorted;
}
template <class T, class B>
bool hasEqualNameAndParameters(T const& _a, B const& _b)
{
return
_a.name() == _b.name() &&
FunctionType(_a).asCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asCallableFunction(false)
);
}
vector<ContractDefinition const*> resolveDirectBaseContracts(ContractDefinition const& _contract)
{
vector<ContractDefinition const*> resolvedContracts;
for (ASTPointer<InheritanceSpecifier> const& specifier: _contract.baseContracts())
{
Declaration const* baseDecl =
specifier->name().annotation().referencedDeclaration;
auto contract = dynamic_cast<ContractDefinition const*>(baseDecl);
solAssert(contract, "contract is null");
resolvedContracts.emplace_back(contract);
}
return resolvedContracts;
}
}
bool ContractLevelChecker::LessFunction::operator()(ModifierDefinition const* _a, ModifierDefinition const* _b) const
{
return _a->name() < _b->name();
}
bool ContractLevelChecker::LessFunction::operator()(FunctionDefinition const* _a, FunctionDefinition const* _b) const
{
if (_a->name() != _b->name())
return _a->name() < _b->name();
if (_a->kind() != _b->kind())
return _a->kind() < _b->kind();
return boost::lexicographical_compare(
FunctionType(*_a).asCallableFunction(false)->parameterTypes(),
FunctionType(*_b).asCallableFunction(false)->parameterTypes(),
[](auto const& _paramTypeA, auto const& _paramTypeB)
{
return _paramTypeA->richIdentifier() < _paramTypeB->richIdentifier();
}
);
}
bool ContractLevelChecker::LessFunction::operator()(ContractDefinition const* _a, ContractDefinition const* _b) const
{
if (!_a || !_b)
return _a < _b;
return _a->id() < _b->id();
}
bool ContractLevelChecker::check(ContractDefinition const& _contract)
{
checkDuplicateFunctions(_contract);
checkDuplicateEvents(_contract);
checkIllegalOverrides(_contract);
checkAbstractFunctions(_contract);
checkAmbiguousOverrides(_contract);
checkBaseConstructorArguments(_contract);
checkConstructor(_contract);
checkFallbackFunction(_contract);
checkAbstractFunctions(_contract);
checkExternalTypeClashes(_contract);
checkHashCollisions(_contract);
checkLibraryRequirements(_contract);
checkBaseABICompatibility(_contract);
checkPayableFallbackWithoutReceive(_contract);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
@ -58,6 +152,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
map<string, vector<FunctionDefinition const*>> functions;
FunctionDefinition const* constructor = nullptr;
FunctionDefinition const* fallback = nullptr;
FunctionDefinition const* receive = nullptr;
for (FunctionDefinition const* function: _contract.definedFunctions())
if (function->isConstructor())
{
@ -79,6 +174,16 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
);
fallback = function;
}
else if (function->isReceive())
{
if (receive)
m_errorReporter.declarationError(
function->location(),
SecondarySourceLocation().append("Another declaration is here:", receive->location()),
"Only one receive function is allowed."
);
receive = function;
}
else
{
solAssert(!function->name().empty(), "");
@ -111,9 +216,7 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
SecondarySourceLocation ssl;
for (size_t j = i + 1; j < overloads.size(); ++j)
if (FunctionType(*overloads[i]).asCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(*overloads[j]).asCallableFunction(false))
)
if (hasEqualNameAndParameters(*overloads[i], *overloads[j]))
{
ssl.append("Other declaration is here:", overloads[j]->location());
reported.insert(j);
@ -135,82 +238,215 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
void ContractLevelChecker::checkIllegalOverrides(ContractDefinition const& _contract)
{
// TODO unify this at a later point. for this we need to put the constness and the access specifier
// into the types
map<string, vector<FunctionDefinition const*>> functions;
map<string, ModifierDefinition const*> modifiers;
FunctionMultiSet const& inheritedFuncs = inheritedFunctions(_contract);
ModifierMultiSet const& inheritedMods = inheritedModifiers(_contract);
// We search from derived to base, so the stored item causes the error.
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* stateVar: _contract.stateVariables())
{
for (FunctionDefinition const* function: contract->definedFunctions())
if (!stateVar->isPublic())
continue;
bool found = false;
for (
auto it = find_if(inheritedFuncs.begin(), inheritedFuncs.end(), MatchByName{stateVar->name()});
it != inheritedFuncs.end();
it = find_if(++it, inheritedFuncs.end(), MatchByName{stateVar->name()})
)
{
if (!hasEqualNameAndParameters(*stateVar, **it))
continue;
if ((*it)->visibility() != Declaration::Visibility::External)
overrideError(*stateVar, **it, "Public state variables can only override functions with external visibility.");
else
checkOverride(*stateVar, **it);
found = true;
}
if (!found && stateVar->overrides())
m_errorReporter.typeError(
stateVar->overrides()->location(),
"Public state variable has override specified but does not override anything."
);
}
for (ModifierDefinition const* modifier: _contract.functionModifiers())
{
if (contains_if(inheritedFuncs, MatchByName{modifier->name()}))
m_errorReporter.typeError(
modifier->location(),
"Override changes function to modifier."
);
auto [begin, end] = inheritedMods.equal_range(modifier);
if (begin == end && modifier->overrides())
m_errorReporter.typeError(
modifier->overrides()->location(),
"Modifier has override specified but does not override anything."
);
for (; begin != end; begin++)
if (ModifierType(**begin) != ModifierType(*modifier))
m_errorReporter.typeError(
modifier->location(),
"Override changes modifier signature."
);
checkOverrideList(inheritedMods, *modifier);
}
for (FunctionDefinition const* function: _contract.definedFunctions())
{
if (function->isConstructor())
continue; // constructors can neither be overridden nor override anything
string const& name = function->name();
if (modifiers.count(name))
m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier.");
continue;
for (FunctionDefinition const* overriding: functions[name])
checkFunctionOverride(*overriding, *function);
if (contains_if(inheritedMods, MatchByName{function->name()}))
m_errorReporter.typeError(function->location(), "Override changes modifier to function.");
functions[name].push_back(function);
}
for (ModifierDefinition const* modifier: contract->functionModifiers())
{
string const& name = modifier->name();
ModifierDefinition const*& override = modifiers[name];
if (!override)
override = modifier;
else if (ModifierType(*override) != ModifierType(*modifier))
m_errorReporter.typeError(override->location(), "Override changes modifier signature.");
if (!functions[name].empty())
m_errorReporter.typeError(override->location(), "Override changes modifier to function.");
}
// No inheriting functions found
if (!inheritedFuncs.count(function) && function->overrides())
m_errorReporter.typeError(
function->overrides()->location(),
"Function has override specified but does not override anything."
);
checkOverrideList(inheritedFuncs, *function);
}
}
void ContractLevelChecker::checkFunctionOverride(FunctionDefinition const& _function, FunctionDefinition const& _super)
template<class T, class U>
void ContractLevelChecker::checkOverride(T const& _overriding, U const& _super)
{
FunctionTypePointer functionType = FunctionType(_function).asCallableFunction(false);
FunctionTypePointer superType = FunctionType(_super).asCallableFunction(false);
static_assert(
std::is_same<VariableDeclaration, T>::value ||
std::is_same<FunctionDefinition, T>::value ||
std::is_same<ModifierDefinition, T>::value,
"Invalid call to checkOverride."
);
if (!functionType->hasEqualParameterTypes(*superType))
return;
if (!functionType->hasEqualReturnTypes(*superType))
overrideError(_function, _super, "Overriding function return types differ.");
static_assert(
std::is_same<FunctionDefinition, U>::value ||
std::is_same<ModifierDefinition, U>::value,
"Invalid call to checkOverride."
);
static_assert(
!std::is_same<ModifierDefinition, U>::value ||
std::is_same<ModifierDefinition, T>::value,
"Invalid call to checkOverride."
);
if (!_function.annotation().superFunction)
_function.annotation().superFunction = &_super;
string overridingName;
if constexpr(std::is_same<FunctionDefinition, T>::value)
overridingName = "function";
else if constexpr(std::is_same<ModifierDefinition, T>::value)
overridingName = "modifier";
else
overridingName = "public state variable";
if (_function.visibility() != _super.visibility())
string superName;
if constexpr(std::is_same<FunctionDefinition, U>::value)
superName = "function";
else
superName = "modifier";
if (!_overriding.overrides())
overrideError(_overriding, _super, "Overriding " + overridingName + " is missing 'override' specifier.");
if (!_super.virtualSemantics())
overrideError(
_super,
_overriding,
"Trying to override non-virtual " + superName + ". Did you forget to add \"virtual\"?",
"Overriding " + overridingName + " is here:"
);
if (_overriding.visibility() != _super.visibility())
{
// Visibility change from external to public is fine.
// Any other change is disallowed.
if (!(
_super.visibility() == FunctionDefinition::Visibility::External &&
_function.visibility() == FunctionDefinition::Visibility::Public
_overriding.visibility() == FunctionDefinition::Visibility::Public
))
overrideError(_function, _super, "Overriding function visibility differs.");
overrideError(_overriding, _super, "Overriding " + overridingName + " visibility differs.");
}
if (_function.stateMutability() != _super.stateMutability())
// This is only relevant for overriding functions by functions or state variables,
// it is skipped for modifiers.
if constexpr(std::is_same<FunctionDefinition, U>::value)
{
FunctionTypePointer functionType = FunctionType(_overriding).asCallableFunction(false);
FunctionTypePointer superType = FunctionType(_super).asCallableFunction(false);
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
if (!functionType->hasEqualReturnTypes(*superType))
overrideError(_overriding, _super, "Overriding " + overridingName + " return types differ.");
// This is only relevant for a function overriding a function.
if constexpr(std::is_same<T, FunctionDefinition>::value)
{
_overriding.annotation().baseFunctions.emplace(&_super);
if (_overriding.stateMutability() != _super.stateMutability())
overrideError(
_function,
_overriding,
_super,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
stateMutabilityToString(_function.stateMutability()) +
stateMutabilityToString(_overriding.stateMutability()) +
"\"."
);
if (!_overriding.isImplemented() && _super.isImplemented())
overrideError(
_overriding,
_super,
"Overriding an implemented function with an unimplemented function is not allowed."
);
}
}
}
void ContractLevelChecker::overrideListError(
CallableDeclaration const& _callable,
set<ContractDefinition const*, LessFunction> _secondary,
string const& _message1,
string const& _message2
)
{
// Using a set rather than a vector so the order is always the same
set<string> names;
SecondarySourceLocation ssl;
for (Declaration const* c: _secondary)
{
ssl.append("This contract: ", c->location());
names.insert(c->name());
}
string contractSingularPlural = "contract ";
if (_secondary.size() > 1)
contractSingularPlural = "contracts ";
m_errorReporter.typeError(
_callable.overrides() ? _callable.overrides()->location() : _callable.location(),
ssl,
_message1 +
contractSingularPlural +
_message2 +
joinHumanReadable(names, ", ", " and ") +
"."
);
}
void ContractLevelChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message)
void ContractLevelChecker::overrideError(Declaration const& _overriding, Declaration const& _super, string _message, string _secondaryMsg)
{
m_errorReporter.typeError(
function.location(),
SecondarySourceLocation().append("Overridden function is here:", super.location()),
message
_overriding.location(),
SecondarySourceLocation().append(_secondaryMsg, _super.location()),
_message
);
}
@ -230,11 +466,6 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
});
if (it == overloads.end())
overloads.emplace_back(_type, _implemented);
else if (it->second)
{
if (!_implemented)
m_errorReporter.typeError(_declaration.location(), "Redeclaring an already implemented function as abstract");
}
else if (_implemented)
it->second = true;
};
@ -257,6 +488,8 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
}
// Set to not fully implemented if at least one flag is false.
// Note that `_contract.annotation().unimplementedFunctions` has already been
// pre-filled by `checkBaseConstructorArguments`.
for (auto const& it: functions)
for (auto const& funAndFlag: it.second)
if (!funAndFlag.second)
@ -266,6 +499,32 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
_contract.annotation().unimplementedFunctions.push_back(function);
break;
}
if (_contract.abstract())
{
if (_contract.contractKind() == ContractDefinition::ContractKind::Interface)
m_errorReporter.typeError(_contract.location(), "Interfaces do not need the \"abstract\" keyword, they are abstract implicitly.");
else if (_contract.contractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_contract.location(), "Libraries cannot be abstract.");
else
solAssert(_contract.contractKind() == ContractDefinition::ContractKind::Contract, "");
}
// For libraries, we emit errors on function-level, so this is fine as long as we do
// not have inheritance for libraries.
if (
_contract.contractKind() == ContractDefinition::ContractKind::Contract &&
!_contract.abstract() &&
!_contract.annotation().unimplementedFunctions.empty()
)
{
SecondarySourceLocation ssl;
for (auto function: _contract.annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
m_errorReporter.typeError(_contract.location(), ssl,
"Contract \"" + _contract.annotation().canonicalName
+ "\" should be marked as abstract.");
}
}
@ -358,48 +617,6 @@ void ContractLevelChecker::annotateBaseConstructorArguments(
}
void ContractLevelChecker::checkConstructor(ContractDefinition const& _contract)
{
FunctionDefinition const* constructor = _contract.constructor();
if (!constructor)
return;
if (!constructor->returnParameters().empty())
m_errorReporter.typeError(constructor->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (constructor->stateMutability() != StateMutability::NonPayable && constructor->stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
constructor->location(),
"Constructor must be payable or non-payable, but is \"" +
stateMutabilityToString(constructor->stateMutability()) +
"\"."
);
if (constructor->visibility() != FunctionDefinition::Visibility::Public && constructor->visibility() != FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(constructor->location(), "Constructor must be public or internal.");
}
void ContractLevelChecker::checkFallbackFunction(ContractDefinition const& _contract)
{
FunctionDefinition const* fallback = _contract.fallbackFunction();
if (!fallback)
return;
if (_contract.isLibrary())
m_errorReporter.typeError(fallback->location(), "Libraries cannot have fallback functions.");
if (fallback->stateMutability() != StateMutability::NonPayable && fallback->stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
fallback->location(),
"Fallback function must be payable or non-payable, but is \"" +
stateMutabilityToString(fallback->stateMutability()) +
"\"."
);
if (!fallback->parameters().empty())
m_errorReporter.typeError(fallback->parameterList().location(), "Fallback function cannot take parameters.");
if (!fallback->returnParameters().empty())
m_errorReporter.typeError(fallback->returnParameterList()->location(), "Fallback function cannot return values.");
if (fallback->visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(fallback->location(), "Fallback function must be defined as \"external\".");
}
void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract)
{
map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations;
@ -510,3 +727,338 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _
);
}
void ContractLevelChecker::checkAmbiguousOverrides(ContractDefinition const& _contract) const
{
std::function<bool(CallableDeclaration const*, CallableDeclaration const*)> compareById =
[](auto const* _a, auto const* _b) { return _a->id() < _b->id(); };
{
// Fetch inherited functions and sort them by signature.
// We get at least one function per signature and direct base contract, which is
// enough because we re-construct the inheritance graph later.
FunctionMultiSet nonOverriddenFunctions = inheritedFunctions(_contract);
// Remove all functions that match the signature of a function in the current contract.
nonOverriddenFunctions -= _contract.definedFunctions();
// Walk through the set of functions signature by signature.
for (auto it = nonOverriddenFunctions.cbegin(); it != nonOverriddenFunctions.cend();)
{
std::set<CallableDeclaration const*, decltype(compareById)> baseFunctions(compareById);
for (auto nextSignature = nonOverriddenFunctions.upper_bound(*it); it != nextSignature; ++it)
baseFunctions.insert(*it);
checkAmbiguousOverridesInternal(std::move(baseFunctions), _contract.location());
}
}
{
ModifierMultiSet modifiers = inheritedModifiers(_contract);
modifiers -= _contract.functionModifiers();
for (auto it = modifiers.cbegin(); it != modifiers.cend();)
{
std::set<CallableDeclaration const*, decltype(compareById)> baseModifiers(compareById);
for (auto next = modifiers.upper_bound(*it); it != next; ++it)
baseModifiers.insert(*it);
checkAmbiguousOverridesInternal(std::move(baseModifiers), _contract.location());
}
}
}
void ContractLevelChecker::checkAmbiguousOverridesInternal(set<
CallableDeclaration const*,
std::function<bool(CallableDeclaration const*, CallableDeclaration const*)>
> _baseCallables, SourceLocation const& _location) const
{
if (_baseCallables.size() <= 1)
return;
// Construct the override graph for this signature.
// Reserve node 0 for the current contract and node
// 1 for an artificial top node to which all override paths
// connect at the end.
struct OverrideGraph
{
OverrideGraph(decltype(_baseCallables) const& __baseCallables)
{
for (auto const* baseFunction: __baseCallables)
addEdge(0, visit(baseFunction));
}
std::map<CallableDeclaration const*, int> nodes;
std::map<int, CallableDeclaration const*> nodeInv;
std::map<int, std::set<int>> edges;
int numNodes = 2;
void addEdge(int _a, int _b)
{
edges[_a].insert(_b);
edges[_b].insert(_a);
}
private:
/// Completes the graph starting from @a _function and
/// @returns the node ID.
int visit(CallableDeclaration const* _function)
{
auto it = nodes.find(_function);
if (it != nodes.end())
return it->second;
int currentNode = numNodes++;
nodes[_function] = currentNode;
nodeInv[currentNode] = _function;
if (_function->overrides())
for (auto const* baseFunction: _function->annotation().baseFunctions)
addEdge(currentNode, visit(baseFunction));
else
addEdge(currentNode, 1);
return currentNode;
}
} overrideGraph(_baseCallables);
// Detect cut vertices following https://en.wikipedia.org/wiki/Biconnected_component#Pseudocode
// Can ignore the root node, since it is never a cut vertex in our case.
struct CutVertexFinder
{
CutVertexFinder(OverrideGraph const& _graph): m_graph(_graph)
{
run();
}
std::set<CallableDeclaration const*> const& cutVertices() const { return m_cutVertices; }
private:
OverrideGraph const& m_graph;
std::vector<bool> m_visited = std::vector<bool>(m_graph.numNodes, false);
std::vector<int> m_depths = std::vector<int>(m_graph.numNodes, -1);
std::vector<int> m_low = std::vector<int>(m_graph.numNodes, -1);
std::vector<int> m_parent = std::vector<int>(m_graph.numNodes, -1);
std::set<CallableDeclaration const*> m_cutVertices{};
void run(int _u = 0, int _depth = 0)
{
m_visited.at(_u) = true;
m_depths.at(_u) = m_low.at(_u) = _depth;
for (int v: m_graph.edges.at(_u))
if (!m_visited.at(v))
{
m_parent[v] = _u;
run(v, _depth + 1);
if (m_low[v] >= m_depths[_u] && m_parent[_u] != -1)
m_cutVertices.insert(m_graph.nodeInv.at(_u));
m_low[_u] = min(m_low[_u], m_low[v]);
}
else if (v != m_parent[_u])
m_low[_u] = min(m_low[_u], m_depths[v]);
}
} cutVertexFinder{overrideGraph};
// Remove all base functions overridden by cut vertices (they don't need to be overridden).
for (auto const* function: cutVertexFinder.cutVertices())
{
std::set<CallableDeclaration const*> toTraverse = function->annotation().baseFunctions;
while (!toTraverse.empty())
{
auto const *base = *toTraverse.begin();
toTraverse.erase(toTraverse.begin());
_baseCallables.erase(base);
for (CallableDeclaration const* f: base->annotation().baseFunctions)
toTraverse.insert(f);
}
// Remove unimplemented base functions at the cut vertices itself as well.
if (auto opt = dynamic_cast<ImplementationOptional const*>(function))
if (!opt->isImplemented())
_baseCallables.erase(function);
}
// If more than one function is left, they have to be overridden.
if (_baseCallables.size() <= 1)
return;
SecondarySourceLocation ssl;
for (auto const* baseFunction: _baseCallables)
{
string contractName = dynamic_cast<ContractDefinition const&>(*baseFunction->scope()).name();
ssl.append("Definition in \"" + contractName + "\": ", baseFunction->location());
}
string callableName;
string distinguishigProperty;
if (dynamic_cast<FunctionDefinition const*>(*_baseCallables.begin()))
{
callableName = "function";
distinguishigProperty = "name and parameter types";
}
else if (dynamic_cast<ModifierDefinition const*>(*_baseCallables.begin()))
{
callableName = "modifier";
distinguishigProperty = "name";
}
else
solAssert(false, "Invalid type for ambiguous override.");
m_errorReporter.typeError(
_location,
ssl,
"Derived contract must override " + callableName + " \"" +
(*_baseCallables.begin())->name() +
"\". Two or more base classes define " + callableName + " with same " + distinguishigProperty + "."
);
}
set<ContractDefinition const*, ContractLevelChecker::LessFunction> ContractLevelChecker::resolveOverrideList(OverrideSpecifier const& _overrides) const
{
set<ContractDefinition const*, LessFunction> resolved;
for (ASTPointer<UserDefinedTypeName> const& override: _overrides.overrides())
{
Declaration const* decl = override->annotation().referencedDeclaration;
solAssert(decl, "Expected declaration to be resolved.");
// If it's not a contract it will be caught
// in the reference resolver
if (ContractDefinition const* contract = dynamic_cast<decltype(contract)>(decl))
resolved.insert(contract);
}
return resolved;
}
template <class T>
void ContractLevelChecker::checkOverrideList(
std::multiset<T const*, LessFunction> const& _inheritedCallables,
T const& _callable
)
{
set<ContractDefinition const*, LessFunction> specifiedContracts =
_callable.overrides() ?
resolveOverrideList(*_callable.overrides()) :
decltype(specifiedContracts){};
// Check for duplicates in override list
if (_callable.overrides() && specifiedContracts.size() != _callable.overrides()->overrides().size())
{
// Sort by contract id to find duplicate for error reporting
vector<ASTPointer<UserDefinedTypeName>> list =
sortByContract(_callable.overrides()->overrides());
// Find duplicates and output error
for (size_t i = 1; i < list.size(); i++)
{
Declaration const* aDecl = list[i]->annotation().referencedDeclaration;
Declaration const* bDecl = list[i-1]->annotation().referencedDeclaration;
if (!aDecl || !bDecl)
continue;
if (aDecl->id() == bDecl->id())
{
SecondarySourceLocation ssl;
ssl.append("First occurrence here: ", list[i-1]->location());
m_errorReporter.typeError(
list[i]->location(),
ssl,
"Duplicate contract \"" +
joinHumanReadable(list[i]->namePath(), ".") +
"\" found in override list of \"" +
_callable.name() +
"\"."
);
}
}
}
decltype(specifiedContracts) expectedContracts;
// Build list of expected contracts
for (auto [begin, end] = _inheritedCallables.equal_range(&_callable); begin != end; begin++)
{
// Validate the override
checkOverride(_callable, **begin);
expectedContracts.insert(&dynamic_cast<ContractDefinition const&>(*(*begin)->scope()));
}
decltype(specifiedContracts) missingContracts;
decltype(specifiedContracts) surplusContracts;
// If we expect only one contract, no contract needs to be specified
if (expectedContracts.size() > 1)
missingContracts = expectedContracts - specifiedContracts;
surplusContracts = specifiedContracts - expectedContracts;
if (!missingContracts.empty())
overrideListError(
_callable,
missingContracts,
"Function needs to specify overridden ",
""
);
if (!surplusContracts.empty())
overrideListError(
_callable,
surplusContracts,
"Invalid ",
"specified in override list: "
);
}
ContractLevelChecker::FunctionMultiSet const& ContractLevelChecker::inheritedFunctions(ContractDefinition const& _contract) const
{
if (!m_inheritedFunctions.count(&_contract))
{
FunctionMultiSet set;
for (auto const* base: resolveDirectBaseContracts(_contract))
{
std::set<FunctionDefinition const*, LessFunction> functionsInBase;
for (FunctionDefinition const* fun: base->definedFunctions())
if (!fun->isConstructor())
functionsInBase.emplace(fun);
for (auto const& func: inheritedFunctions(*base))
functionsInBase.insert(func);
set += functionsInBase;
}
m_inheritedFunctions[&_contract] = set;
}
return m_inheritedFunctions[&_contract];
}
ContractLevelChecker::ModifierMultiSet const& ContractLevelChecker::inheritedModifiers(ContractDefinition const& _contract) const
{
auto const& result = m_contractBaseModifiers.find(&_contract);
if (result != m_contractBaseModifiers.cend())
return result->second;
ModifierMultiSet set;
for (auto const* base: resolveDirectBaseContracts(_contract))
{
std::set<ModifierDefinition const*, LessFunction> tmpSet =
convertContainer<decltype(tmpSet)>(base->functionModifiers());
for (auto const& mod: inheritedModifiers(*base))
tmpSet.insert(mod);
set += tmpSet;
}
return m_contractBaseModifiers[&_contract] = set;
}
void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition const& _contract)
{
if (auto const* fallback = _contract.fallbackFunction())
if (fallback->isPayable() && !_contract.interfaceFunctionList().empty() && !_contract.receiveFunction())
m_errorReporter.warning(
_contract.location(),
"This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function.",
SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location())
);
}

View File

@ -22,7 +22,10 @@
#pragma once
#include <libsolidity/ast/ASTForward.h>
#include <liblangutil/SourceLocation.h>
#include <map>
#include <functional>
#include <set>
namespace langutil
{
@ -41,6 +44,7 @@ namespace solidity
class ContractLevelChecker
{
public:
/// @param _errorReporter provides the error logging functionality.
explicit ContractLevelChecker(langutil::ErrorReporter& _errorReporter):
m_errorReporter(_errorReporter)
@ -51,6 +55,22 @@ public:
bool check(ContractDefinition const& _contract);
private:
/**
* Comparator that compares
* - functions such that equality means that the functions override each other
* - modifiers by name
* - contracts by AST id.
*/
struct LessFunction
{
bool operator()(ModifierDefinition const* _a, ModifierDefinition const* _b) const;
bool operator()(FunctionDefinition const* _a, FunctionDefinition const* _b) const;
bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const;
};
using FunctionMultiSet = std::multiset<FunctionDefinition const*, LessFunction>;
using ModifierMultiSet = std::multiset<ModifierDefinition const*, LessFunction>;
/// Checks that two functions defined in this contract with the same name have different
/// arguments and that there is at most one constructor.
void checkDuplicateFunctions(ContractDefinition const& _contract);
@ -58,19 +78,33 @@ private:
template <class T>
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
void checkIllegalOverrides(ContractDefinition const& _contract);
/// Reports a type error with an appropriate message if overridden function signature differs.
/// Also stores the direct super function in the AST annotations.
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
/// Performs various checks related to @a _overriding overriding @a _super like
/// different return type, invalid visibility change, etc.
/// Works on functions, modifiers and public state variables.
/// Also stores @a _super as a base function of @a _function in its AST annotations.
template<class T, class U>
void checkOverride(T const& _overriding, U const& _super);
void overrideListError(
CallableDeclaration const& _callable,
std::set<ContractDefinition const*, LessFunction> _secondary,
std::string const& _message1,
std::string const& _message2
);
void overrideError(
Declaration const& _overriding,
Declaration const& _super,
std::string _message,
std::string _secondaryMsg = "Overridden function is here:"
);
void checkAbstractFunctions(ContractDefinition const& _contract);
/// Checks that the base constructor arguments are properly provided.
/// Fills the list of unimplemented functions in _contract's annotations.
void checkBaseConstructorArguments(ContractDefinition const& _contract);
void annotateBaseConstructorArguments(
ContractDefinition const& _currentContract,
FunctionDefinition const* _baseConstructor,
ASTNode const* _argumentNode
);
void checkConstructor(ContractDefinition const& _contract);
void checkFallbackFunction(ContractDefinition const& _contract);
/// Checks that different functions with external visibility end up having different
/// external argument types (i.e. different signature).
void checkExternalTypeClashes(ContractDefinition const& _contract);
@ -80,8 +114,35 @@ private:
void checkLibraryRequirements(ContractDefinition const& _contract);
/// Checks base contracts for ABI compatibility
void checkBaseABICompatibility(ContractDefinition const& _contract);
/// Checks for functions in different base contracts which conflict with each
/// other and thus need to be overridden explicitly.
void checkAmbiguousOverrides(ContractDefinition const& _contract) const;
void checkAmbiguousOverridesInternal(std::set<
CallableDeclaration const*,
std::function<bool(CallableDeclaration const*, CallableDeclaration const*)>
> _baseCallables, langutil::SourceLocation const& _location) const;
/// Resolves an override list of UserDefinedTypeNames to a list of contracts.
std::set<ContractDefinition const*, LessFunction> resolveOverrideList(OverrideSpecifier const& _overrides) const;
template <class T>
void checkOverrideList(
std::multiset<T const*, LessFunction> const& _funcSet,
T const& _function
);
/// Returns all functions of bases that have not yet been overwritten.
/// May contain the same function multiple times when used with shared bases.
FunctionMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const;
ModifierMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const;
/// Warns if the contract has a payable fallback, but no receive ether function.
void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract);
langutil::ErrorReporter& m_errorReporter;
/// Cache for inheritedFunctions().
std::map<ContractDefinition const*, FunctionMultiSet> mutable m_inheritedFunctions;
std::map<ContractDefinition const*, ModifierMultiSet> mutable m_contractBaseModifiers;
};
}

View File

@ -85,6 +85,18 @@ bool ControlFlowBuilder::visit(Conditional const& _conditional)
return false;
}
bool ControlFlowBuilder::visit(TryStatement const& _tryStatement)
{
appendControlFlow(_tryStatement.externalCall());
auto nodes = splitFlow(_tryStatement.clauses().size());
for (size_t i = 0; i < _tryStatement.clauses().size(); ++i)
nodes[i] = createFlow(nodes[i], _tryStatement.clauses()[i]->block());
mergeFlow(nodes);
return false;
}
bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
{
solAssert(!!m_currentNode, "");
@ -384,7 +396,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
_variableDeclaration.value().get()
);
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
else if (_variableDeclaration.isCallableParameter() && !_variableDeclaration.isReturnParameter())
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment,

View File

@ -45,6 +45,7 @@ private:
// Visits for constructing the control flow.
bool visit(BinaryOperation const& _operation) override;
bool visit(Conditional const& _conditional) override;
bool visit(TryStatement const& _tryStatement) override;
bool visit(IfStatement const& _ifStatement) override;
bool visit(ForStatement const& _forStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
@ -98,12 +99,27 @@ private:
return result;
}
/// Splits the control flow starting at the current node into @a _n paths.
/// m_currentNode is set to nullptr and has to be set manually or
/// using mergeFlow later.
std::vector<CFGNode*> splitFlow(size_t n)
{
std::vector<CFGNode*> result(n);
for (auto& node: result)
{
node = m_nodeContainer.newNode();
connect(m_currentNode, node);
}
m_currentNode = nullptr;
return result;
}
/// Merges the control flow of @a _nodes to @a _endNode.
/// If @a _endNode is nullptr, a new node is creates and used as end node.
/// Sets the merge destination as current node.
/// Note: @a _endNode may be one of the nodes in @a _nodes.
template<size_t n>
void mergeFlow(std::array<CFGNode*, n> const& _nodes, CFGNode* _endNode = nullptr)
template<typename C>
void mergeFlow(C const& _nodes, CFGNode* _endNode = nullptr)
{
CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode;
for (auto& node: _nodes)

View File

@ -129,9 +129,36 @@ void DocStringAnalyser::parseDocStrings(
m_errorOccured = true;
_annotation.docTags = parser.tags();
}
size_t returnTagsVisited = 0;
for (auto const& docTag: _annotation.docTags)
{
if (!_validTags.count(docTag.first))
appendError("Doc tag @" + docTag.first + " not valid for " + _nodeName + ".");
appendError("Documentation tag @" + docTag.first + " not valid for " + _nodeName + ".");
else
if (docTag.first == "return")
{
returnTagsVisited++;
if (auto* function = dynamic_cast<FunctionDefinition const*>(&_node))
{
string content = docTag.second.content;
string firstWord = content.substr(0, content.find_first_of(" \t"));
if (returnTagsVisited > function->returnParameters().size())
appendError("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 + "\"" +
" does not contain the name of its return parameter."
);
}
}
}
}
}
void DocStringAnalyser::appendError(string const& _description)

View File

@ -346,11 +346,19 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
solAssert(conflictingDeclaration, "");
// Usual shadowing is not an error
if (dynamic_cast<VariableDeclaration const*>(declaration) && dynamic_cast<VariableDeclaration const*>(conflictingDeclaration))
if (
dynamic_cast<ModifierDefinition const*>(declaration) &&
dynamic_cast<ModifierDefinition const*>(conflictingDeclaration)
)
continue;
// Usual shadowing is not an error
if (dynamic_cast<ModifierDefinition const*>(declaration) && dynamic_cast<ModifierDefinition const*>(conflictingDeclaration))
// Public state variable can override functions
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(conflictingDeclaration))
if (
dynamic_cast<FunctionDefinition const*>(declaration) &&
varDecl->isStateVariable() &&
varDecl->isPublic()
)
continue;
if (declaration->location().start < conflictingDeclaration->location().start)
@ -572,6 +580,7 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
m_globalContext.setCurrentContract(_contract);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, false, true);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true);
m_currentContract = &_contract;
registerDeclaration(_contract, true);
_contract.annotation().canonicalName = currentCanonicalName();
@ -580,6 +589,7 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
void DeclarationRegistrationHelper::endVisit(ContractDefinition&)
{
m_currentContract = nullptr;
closeCurrentScope();
}
@ -617,6 +627,7 @@ bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
{
registerDeclaration(_function, true);
m_currentFunction = &_function;
_function.annotation().contract = m_currentContract;
return true;
}
@ -626,6 +637,18 @@ void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(TryCatchClause& _tryCatchClause)
{
_tryCatchClause.setScope(m_currentScope);
enterNewSubScope(_tryCatchClause);
return true;
}
void DeclarationRegistrationHelper::endVisit(TryCatchClause&)
{
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier)
{
registerDeclaration(_modifier, true);

View File

@ -181,6 +181,8 @@ private:
bool visit(EnumValue& _value) override;
bool visit(FunctionDefinition& _function) override;
void endVisit(FunctionDefinition& _function) override;
bool visit(TryCatchClause& _tryCatchClause) override;
void endVisit(TryCatchClause& _tryCatchClause) override;
bool visit(ModifierDefinition& _modifier) override;
void endVisit(ModifierDefinition& _modifier) override;
bool visit(FunctionTypeName& _funTypeName) override;
@ -206,6 +208,7 @@ private:
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;
ContractDefinition const* m_currentContract = nullptr;
langutil::ErrorReporter& m_errorReporter;
GlobalContext& m_globalContext;
};

View File

@ -60,6 +60,27 @@ void PostTypeChecker::endVisit(ContractDefinition const&)
m_constVariableDependencies.clear();
}
void PostTypeChecker::endVisit(OverrideSpecifier const& _overrideSpecifier)
{
for (ASTPointer<UserDefinedTypeName> const& override: _overrideSpecifier.overrides())
{
Declaration const* decl = override->annotation().referencedDeclaration;
solAssert(decl, "Expected declaration to be resolved.");
if (dynamic_cast<ContractDefinition const*>(decl))
continue;
TypeType const* actualTypeType = dynamic_cast<TypeType const*>(decl->type());
m_errorReporter.typeError(
override->location(),
"Expected contract but got " +
actualTypeType->actualType()->toString(true) +
"."
);
}
}
bool PostTypeChecker::visit(VariableDeclaration const& _variable)
{
solAssert(!m_currentConstVariable, "");

View File

@ -37,6 +37,7 @@ namespace solidity
/**
* This module performs analyses on the AST that are done after type checking and assignments of types:
* - whether there are circular references in constant state variables
* - whether override specifiers are actually contracts
* @TODO factor out each use-case into an individual class (but do the traversal only once)
*/
class PostTypeChecker: private ASTConstVisitor
@ -53,6 +54,7 @@ private:
bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override;
void endVisit(OverrideSpecifier const& _overrideSpecifier) override;
bool visit(VariableDeclaration const& _variable) override;
void endVisit(VariableDeclaration const& _variable) override;

View File

@ -279,10 +279,34 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
ErrorList errors;
ErrorReporter errorsIgnored(errors);
yul::ExternalIdentifierAccess::Resolver resolver =
[&](yul::Identifier const& _identifier, yul::IdentifierContext, bool _crossesFunctionBoundary) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
[&](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
@ -323,12 +347,10 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information
// We use the latest EVM version because we will re-run it anyway.
yul::AsmAnalysisInfo analysisInfo;
std::optional<Error::Type> errorTypeForLoose = Error::Type::SyntaxError;
yul::AsmAnalyzer(
analysisInfo,
errorsIgnored,
errorTypeForLoose,
yul::EVMDialect::looseAssemblyForEVM(m_evmVersion),
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
resolver
).analyze(_inlineAssembly.operations());
return false;
@ -388,7 +410,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
", ",
" or "
);
if (_variable.isCallableParameter())
if (_variable.isCallableOrCatchParameter())
errorString +=
" for " +
string(_variable.isReturnParameter() ? "return " : "") +
@ -466,6 +488,12 @@ void ReferencesResolver::declarationError(SourceLocation const& _location, strin
m_errorReporter.declarationError(_location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, SecondarySourceLocation const& _ssl, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.declarationError(_location, _ssl, _description);
}
void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;

View File

@ -94,6 +94,9 @@ private:
/// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, langutil::SecondarySourceLocation const& _ssl, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description);

View File

@ -118,10 +118,12 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
{
if (var.first.second->isCallableParameter())
if (var.first.second->isCallableOrCatchParameter())
m_errorReporter.warning(
var.first.second->location(),
"Unused function parameter. Remove or comment out the variable name to silence this warning."
"Unused " +
string(var.first.second->isTryCatchParameter() ? "try/catch" : "function") +
" parameter. Remove or comment out the variable name to silence this warning."
);
else
m_errorReporter.warning(var.first.second->location(), "Unused local variable.");

View File

@ -106,7 +106,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
{
auto feature = ExperimentalFeatureNames.at(literal);
m_sourceUnit->annotation().experimentalFeatures.insert(feature);
if (!ExperimentalFeatureOnlyAnalysis.count(feature))
if (!ExperimentalFeatureWithoutWarning.count(feature))
m_errorReporter.warning(_pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments.");
}
}
@ -295,7 +295,7 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
if (_function.noVisibilitySpecified())
{
string suggestedVisibility = _function.isFallback() || m_isInterface ? "external" : "public";
string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public";
m_errorReporter.syntaxError(
_function.location(),
"No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?"

View File

@ -89,7 +89,6 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
for (auto const& n: _contract.subNodes())
n->accept(*this);
return false;
}
@ -137,19 +136,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
);
if (arguments.size() >= 1)
{
BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory());
if (!result)
m_errorReporter.typeErrorConcatenateDescriptions(
if (
!type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) &&
!type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata())
)
m_errorReporter.typeError(
arguments.front()->location(),
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
"The first argument to \"abi.decode\" must be implicitly convertible to "
"bytes memory or bytes calldata, but is of type " +
type(*arguments.front())->toString() +
" to bytes memory requested.",
result.message()
"."
);
}
if (arguments.size() < 2)
return {};
@ -330,11 +327,20 @@ bool TypeChecker::visit(StructDefinition const& _struct)
bool TypeChecker::visit(FunctionDefinition const& _function)
{
bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library;
if (_function.markedVirtual())
{
if (_function.annotation().contract->isInterface())
m_errorReporter.warning(_function.location(), "Interface functions are implicitly \"virtual\"");
if (_function.visibility() == Declaration::Visibility::Private)
m_errorReporter.typeError(_function.location(), "\"virtual\" and \"private\" cannot be used together.");
}
if (_function.isPayable())
{
if (isLibraryFunction)
m_errorReporter.typeError(_function.location(), "Library functions cannot be payable.");
if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface())
if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable.");
}
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) {
@ -426,8 +432,19 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
_function.body().accept(*this);
else if (_function.isConstructor())
m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared.");
else if (isLibraryFunction && _function.visibility() <= FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(_function.location(), "Internal library function must be implemented if declared.");
else if (isLibraryFunction)
m_errorReporter.typeError(_function.location(), "Library functions must be implemented if declared.");
else if (!_function.virtualSemantics())
m_errorReporter.typeError(_function.location(), "Functions without implementation must be marked virtual.");
if (_function.isFallback())
typeCheckFallbackFunction(_function);
else if (_function.isReceive())
typeCheckReceiveFunction(_function);
else if (_function.isConstructor())
typeCheckConstructor(_function);
return false;
}
@ -438,7 +455,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
// * or inside of a struct definition.
if (
m_scope->isInterface()
&& !_variable.isCallableParameter()
&& !_variable.isCallableOrCatchParameter()
&& !m_insideStruct
)
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
@ -743,7 +760,6 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
yul::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
m_errorReporter,
Error::Type::SyntaxError,
_inlineAssembly.dialect(),
identifierAccess
);
@ -761,6 +777,133 @@ bool TypeChecker::visit(IfStatement const& _ifStatement)
return false;
}
void TypeChecker::endVisit(TryStatement const& _tryStatement)
{
FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall());
if (!externalCall || externalCall->annotation().kind != FunctionCallKind::FunctionCall)
{
m_errorReporter.typeError(
_tryStatement.externalCall().location(),
"Try can only be used with external function calls and contract creation calls."
);
return;
}
FunctionType const& functionType = dynamic_cast<FunctionType const&>(*externalCall->expression().annotation().type);
if (
functionType.kind() != FunctionType::Kind::External &&
functionType.kind() != FunctionType::Kind::Creation &&
functionType.kind() != FunctionType::Kind::DelegateCall
)
{
m_errorReporter.typeError(
_tryStatement.externalCall().location(),
"Try can only be used with external function calls and contract creation calls."
);
return;
}
externalCall->annotation().tryCall = true;
solAssert(_tryStatement.clauses().size() >= 2, "");
solAssert(_tryStatement.clauses().front(), "");
TryCatchClause const& successClause = *_tryStatement.clauses().front();
if (successClause.parameters())
{
TypePointers returnTypes =
m_evmVersion.supportsReturndata() ?
functionType.returnParameterTypes() :
functionType.returnParameterTypesWithoutDynamicTypes();
std::vector<ASTPointer<VariableDeclaration>> const& parameters =
successClause.parameters()->parameters();
if (returnTypes.size() != parameters.size())
m_errorReporter.typeError(
successClause.location(),
"Function returns " +
to_string(functionType.returnParameterTypes().size()) +
" values, but returns clause has " +
to_string(parameters.size()) +
" variables."
);
size_t len = min(returnTypes.size(), parameters.size());
for (size_t i = 0; i < len; ++i)
{
solAssert(returnTypes[i], "");
if (parameters[i] && *parameters[i]->annotation().type != *returnTypes[i])
m_errorReporter.typeError(
parameters[i]->location(),
"Invalid type, expected " +
returnTypes[i]->toString(false) +
" but got " +
parameters[i]->annotation().type->toString() +
"."
);
}
}
TryCatchClause const* errorClause = nullptr;
TryCatchClause const* lowLevelClause = nullptr;
for (size_t i = 1; i < _tryStatement.clauses().size(); ++i)
{
TryCatchClause const& clause = *_tryStatement.clauses()[i];
if (clause.errorName() == "")
{
if (lowLevelClause)
m_errorReporter.typeError(
clause.location(),
SecondarySourceLocation{}.append("The first clause is here:", lowLevelClause->location()),
"This try statement already has a low-level catch clause."
);
lowLevelClause = &clause;
if (clause.parameters() && !clause.parameters()->parameters().empty())
{
if (
clause.parameters()->parameters().size() != 1 ||
*clause.parameters()->parameters().front()->type() != *TypeProvider::bytesMemory()
)
m_errorReporter.typeError(clause.location(), "Expected `catch (bytes memory ...) { ... }` or `catch { ... }`.");
if (!m_evmVersion.supportsReturndata())
m_errorReporter.typeError(
clause.location(),
"This catch clause type cannot be used on the selected EVM version (" +
m_evmVersion.name() +
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
);
}
}
else if (clause.errorName() == "Error")
{
if (!m_evmVersion.supportsReturndata())
m_errorReporter.typeError(
clause.location(),
"This catch clause type cannot be used on the selected EVM version (" +
m_evmVersion.name() +
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
);
if (errorClause)
m_errorReporter.typeError(
clause.location(),
SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()),
"This try statement already has an \"Error\" catch clause."
);
errorClause = &clause;
if (
!clause.parameters() ||
clause.parameters()->parameters().size() != 1 ||
*clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory()
)
m_errorReporter.typeError(clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
}
else
m_errorReporter.typeError(
clause.location(),
"Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`."
);
}
}
bool TypeChecker::visit(WhileStatement const& _whileStatement)
{
expectType(_whileStatement.condition(), *TypeProvider::boolean());
@ -1413,6 +1556,23 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
"might overflow. Silence this warning by converting the literal to the "
"expected type."
);
if (
commonType->category() == Type::Category::Integer &&
rightType->category() == Type::Category::Integer &&
dynamic_cast<IntegerType const&>(*commonType).numBits() <
dynamic_cast<IntegerType const&>(*rightType).numBits()
)
m_errorReporter.warning(
_operation.location(),
"The result type of the " +
operation +
" operation is equal to the type of the first operand (" +
commonType->toString() +
") ignoring the (larger) type of the second operand (" +
rightType->toString() +
") which might be unexpected. Silence this warning by either converting "
"the first or the second operand to the type of the other."
);
}
}
@ -1494,12 +1654,28 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
variableDeclaration->location()
);
m_errorReporter.typeError(
_functionCall.location(), ssl,
_functionCall.location(),
ssl,
"Explicit type conversion not allowed from non-payable \"address\" to \"" +
resultType->toString() +
"\", which has a payable fallback function."
);
}
else if (
auto const* functionType = dynamic_cast<FunctionType const*>(argType);
functionType &&
functionType->kind() == FunctionType::Kind::External &&
resultType->category() == Type::Category::Address
)
m_errorReporter.typeError(
_functionCall.location(),
"Explicit type conversion not allowed from \"" +
argType->toString() +
"\" to \"" +
resultType->toString() +
"\". To obtain the address of the contract of the function, " +
"you can use the .address member of the function."
);
else
m_errorReporter.typeError(
_functionCall.location(),
@ -1510,9 +1686,12 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
"\"."
);
}
if (resultType->category() == Type::Category::Address)
if (auto addressType = dynamic_cast<AddressType const*>(resultType))
if (addressType->stateMutability() != StateMutability::Payable)
{
bool const payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress());
bool payable = false;
if (argType->category() != Type::Category::Address)
payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress());
resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address();
}
}
@ -1550,6 +1729,71 @@ void TypeChecker::typeCheckFunctionCall(
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
}
void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function)
{
solAssert(_function.isFallback(), "");
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_function.location(), "Libraries cannot have fallback functions.");
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Fallback function must be payable or non-payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(_function.location(), "Fallback function must be defined as \"external\".");
if (!_function.returnParameters().empty())
{
if (_function.returnParameters().size() > 1 || *type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Fallback function can only have a single \"bytes memory\" return value.");
else
m_errorReporter.typeError(_function.returnParameterList()->location(), "Return values for fallback functions are not yet implemented.");
}
if (!_function.parameters().empty())
m_errorReporter.typeError(_function.parameterList().location(), "Fallback function cannot take parameters.");
}
void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
{
solAssert(_function.isReceive(), "");
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_function.location(), "Libraries cannot have receive ether functions.");
if (_function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Receive ether function must be payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(_function.location(), "Receive ether function must be defined as \"external\".");
if (!_function.returnParameters().empty())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Receive ether function cannot return values.");
if (!_function.parameters().empty())
m_errorReporter.typeError(_function.parameterList().location(), "Receive ether function cannot take parameters.");
}
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
{
solAssert(_function.isConstructor(), "");
if (!_function.returnParameters().empty())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Constructor must be payable or non-payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != FunctionDefinition::Visibility::Public && _function.visibility() != FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(_function.location(), "Constructor must be public or internal.");
}
void TypeChecker::typeCheckABIEncodeFunctions(
FunctionCall const& _functionCall,
FunctionTypePointer _functionType
@ -1871,7 +2115,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
FunctionTypePointer functionType = nullptr;
// Determine and assign function call kind, purity and function type for this FunctionCall node
// Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node
switch (expressionType->category())
{
case Type::Category::Function:
@ -1885,6 +2129,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
functionType &&
functionType->isPure();
if (
functionType->kind() == FunctionType::Kind::ArrayPush ||
functionType->kind() == FunctionType::Kind::ByteArrayPush
)
funcCallAnno.isLValue = functionType->parameterTypes().empty();
break;
case Type::Category::TypeType:
@ -1992,21 +2242,10 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
if (contract->isInterface())
m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface.");
if (!contract->annotation().unimplementedFunctions.empty())
{
SecondarySourceLocation ssl;
for (auto function: contract->annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
string msg = "Trying to create an instance of an abstract contract.";
ssl.limitSize(msg);
m_errorReporter.typeError(
_newExpression.location(),
ssl,
msg
);
}
if (!contract->constructorIsPublic())
m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
if (contract->abstract())
m_errorReporter.typeError(_newExpression.location(), "Cannot instantiate an abstract contract.");
solAssert(!!m_scope, "");
m_scope->annotation().contractDependencies.insert(contract);
@ -2169,14 +2408,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
else if (exprType->category() == Type::Category::Array)
{
auto const& arrayType(dynamic_cast<ArrayType const&>(*exprType));
annotation.isLValue = (
memberName == "length" &&
arrayType.location() == DataLocation::Storage &&
arrayType.isDynamicallySized()
);
}
annotation.isLValue = false;
else if (exprType->category() == Type::Category::FixedBytes)
annotation.isLValue = false;
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
@ -2225,6 +2457,14 @@ bool TypeChecker::visit(IndexAccess const& _access)
Expression const* index = _access.indexExpression();
switch (baseType->category())
{
case Type::Category::ArraySlice:
{
auto const& arrayType = dynamic_cast<ArraySliceType const&>(*baseType).arrayType();
if (arrayType.location() != DataLocation::CallData || !arrayType.isDynamicallySized())
m_errorReporter.typeError(_access.location(), "Index access is only implemented for slices of dynamic calldata arrays.");
baseType = &arrayType;
[[fallthrough]];
}
case Type::Category::Array:
{
ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType);
@ -2321,6 +2561,50 @@ bool TypeChecker::visit(IndexAccess const& _access)
return false;
}
bool TypeChecker::visit(IndexRangeAccess const& _access)
{
_access.baseExpression().accept(*this);
bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays
bool isPure = _access.baseExpression().annotation().isPure;
if (Expression const* start = _access.startExpression())
{
expectType(*start, *TypeProvider::uint256());
if (!start->annotation().isPure)
isPure = false;
}
if (Expression const* end = _access.endExpression())
{
expectType(*end, *TypeProvider::uint256());
if (!end->annotation().isPure)
isPure = false;
}
TypePointer exprType = type(_access.baseExpression());
if (exprType->category() == Type::Category::TypeType)
{
m_errorReporter.typeError(_access.location(), "Types cannot be sliced.");
_access.annotation().type = exprType;
return false;
}
ArrayType const* arrayType = nullptr;
if (auto const* arraySlice = dynamic_cast<ArraySliceType const*>(exprType))
arrayType = &arraySlice->arrayType();
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
m_errorReporter.fatalTypeError(_access.location(), "Index range access is only possible for arrays and array slices.");
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
m_errorReporter.typeError(_access.location(), "Index range access is only supported for dynamic calldata arrays.");
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
_access.annotation().isLValue = isLValue;
_access.annotation().isPure = isPure;
return false;
}
bool TypeChecker::visit(Identifier const& _identifier)
{
IdentifierAnnotation& annotation = _identifier.annotation();
@ -2433,7 +2717,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.typeName()));
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability()));
_expr.annotation().isPure = true;
}
@ -2581,17 +2865,9 @@ void TypeChecker::requireLValue(Expression const& _expression)
if (structType->dataStoredIn(DataLocation::CallData))
return "Calldata structs are read-only.";
}
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
else if (dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
if (memberAccess->memberName() == "length")
switch (arrayType->location())
{
case DataLocation::Memory:
return "Memory arrays cannot be resized.";
case DataLocation::CallData:
return "Calldata arrays cannot be resized.";
case DataLocation::Storage:
break;
}
return "Member \"length\" is read-only and cannot be used to resize arrays.";
}
if (auto identifier = dynamic_cast<Identifier const*>(&_expression))

View File

@ -96,6 +96,10 @@ private:
FunctionTypePointer _functionType
);
void typeCheckFallbackFunction(FunctionDefinition const& _function);
void typeCheckReceiveFunction(FunctionDefinition const& _function);
void typeCheckConstructor(FunctionDefinition const& _function);
/// Performs general number and type checks of arguments against function call and struct ctor FunctionCall node parameters.
void typeCheckFunctionGeneralChecks(
FunctionCall const& _functionCall,
@ -120,6 +124,7 @@ private:
void endVisit(FunctionTypeName const& _funType) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(IfStatement const& _ifStatement) override;
void endVisit(TryStatement const& _tryStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
bool visit(ForStatement const& _forStatement) override;
void endVisit(Return const& _return) override;
@ -136,6 +141,7 @@ private:
void endVisit(NewExpression const& _newExpression) override;
bool visit(MemberAccess const& _memberAccess) override;
bool visit(IndexAccess const& _indexAccess) override;
bool visit(IndexRangeAccess const& _indexRangeAccess) override;
bool visit(Identifier const& _identifier) override;
void endVisit(ElementaryTypeNameExpression const& _expr) override;
void endVisit(Literal const& _literal) override;

View File

@ -43,24 +43,16 @@ public:
m_dialect(_dialect),
m_reportMutability(_reportMutability) {}
void operator()(yul::Label const&) { }
void operator()(yul::Instruction const& _instruction)
{
checkInstruction(_instruction.location, _instruction.instruction);
}
void operator()(yul::Literal const&) {}
void operator()(yul::Identifier const&) {}
void operator()(yul::FunctionalInstruction const& _instr)
{
checkInstruction(_instr.location, _instr.instruction);
for (auto const& arg: _instr.arguments)
std::visit(*this, arg);
}
void operator()(yul::ExpressionStatement const& _expr)
{
std::visit(*this, _expr.expression);
}
void operator()(yul::StackAssignment const&) {}
void operator()(yul::Assignment const& _assignment)
{
std::visit(*this, *_assignment.value);
@ -112,6 +104,9 @@ public:
void operator()(yul::Continue const&)
{
}
void operator()(yul::Leave const&)
{
}
void operator()(yul::Block const& _block)
{
for (auto const& s: _block.statements)
@ -175,7 +170,8 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
!_funDef.body().statements().empty() &&
!_funDef.isConstructor() &&
!_funDef.isFallback() &&
!_funDef.annotation().superFunction
!_funDef.isReceive() &&
!_funDef.overrides()
)
m_errorReporter.warning(
_funDef.location(),
@ -387,7 +383,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage))
mutability = writes ? StateMutability::NonPayable : StateMutability::View;
mutability = StateMutability::View;
break;
}
default:
@ -415,6 +411,13 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
}
}
void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
bool writes = _indexRangeAccess.annotation().lValueRequested;
if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location());
}
void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
{
solAssert(_modifier.name(), "");

View File

@ -59,6 +59,7 @@ private:
bool visit(MemberAccess const& _memberAccess) override;
void endVisit(MemberAccess const& _memberAccess) override;
void endVisit(IndexAccess const& _indexAccess) override;
void endVisit(IndexRangeAccess const& _indexAccess) override;
void endVisit(ModifierInvocation const& _modifier) override;
void endVisit(FunctionCall const& _functionCall) override;
void endVisit(InlineAssembly const& _inlineAssembly) override;

View File

@ -146,7 +146,7 @@ bool ContractDefinition::constructorIsPublic() const
bool ContractDefinition::canBeDeployed() const
{
return constructorIsPublic() && annotation().unimplementedFunctions.empty();
return constructorIsPublic() && !abstract() && !isInterface();
}
FunctionDefinition const* ContractDefinition::fallbackFunction() const
@ -158,6 +158,15 @@ FunctionDefinition const* ContractDefinition::fallbackFunction() const
return nullptr;
}
FunctionDefinition const* ContractDefinition::receiveFunction() const
{
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
for (FunctionDefinition const* f: contract->definedFunctions())
if (f->isReceive())
return f;
return nullptr;
}
vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() const
{
if (!m_interfaceEvents)
@ -303,6 +312,16 @@ ContractDefinition::ContractKind FunctionDefinition::inContractKind() const
return contractDef->contractKind();
}
CallableDeclarationAnnotation& CallableDeclaration::annotation() const
{
solAssert(
m_annotation,
"CallableDeclarationAnnotation is an abstract base, need to call annotation on the concrete class first."
);
return dynamic_cast<CallableDeclarationAnnotation&>(*m_annotation);
}
FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
{
if (_internal)
@ -443,12 +462,13 @@ bool VariableDeclaration::isLocalVariable() const
dynamic_cast<FunctionTypeName const*>(s) ||
dynamic_cast<CallableDeclaration const*>(s) ||
dynamic_cast<Block const*>(s) ||
dynamic_cast<TryCatchClause const*>(s) ||
dynamic_cast<ForStatement const*>(s);
}
bool VariableDeclaration::isCallableParameter() const
bool VariableDeclaration::isCallableOrCatchParameter() const
{
if (isReturnParameter())
if (isReturnParameter() || isTryCatchParameter())
return true;
vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
@ -467,7 +487,7 @@ bool VariableDeclaration::isCallableParameter() const
bool VariableDeclaration::isLocalOrReturn() const
{
return isReturnParameter() || (isLocalVariable() && !isCallableParameter());
return isReturnParameter() || (isLocalVariable() && !isCallableOrCatchParameter());
}
bool VariableDeclaration::isReturnParameter() const
@ -487,9 +507,14 @@ bool VariableDeclaration::isReturnParameter() const
return false;
}
bool VariableDeclaration::isTryCatchParameter() const
{
return dynamic_cast<TryCatchClause const*>(scope());
}
bool VariableDeclaration::isExternalCallableParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
@ -501,7 +526,7 @@ bool VariableDeclaration::isExternalCallableParameter() const
bool VariableDeclaration::isInternalCallableParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
@ -513,7 +538,7 @@ bool VariableDeclaration::isInternalCallableParameter() const
bool VariableDeclaration::isLibraryFunctionParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* funDef = dynamic_cast<FunctionDefinition const*>(scope()))
return dynamic_cast<ContractDefinition const&>(*funDef->scope()).isLibrary();
@ -549,10 +574,10 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
locations.insert(Location::Storage);
return locations;
}
else if (isCallableParameter())
else if (isCallableOrCatchParameter())
{
set<Location> locations{ Location::Memory };
if (isInternalCallableParameter() || isLibraryFunctionParameter())
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
locations.insert(Location::Storage);
return locations;
}

View File

@ -388,13 +388,15 @@ public:
ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<ASTNode>> const& _subNodes,
ContractKind _contractKind = ContractKind::Contract
ContractKind _contractKind = ContractKind::Contract,
bool _abstract = false
):
Declaration(_location, _name),
Documented(_documentation),
m_baseContracts(_baseContracts),
m_subNodes(_subNodes),
m_contractKind(_contractKind)
m_contractKind(_contractKind),
m_abstract(_abstract)
{}
void accept(ASTVisitor& _visitor) override;
@ -433,6 +435,9 @@ public:
/// Returns the fallback function or nullptr if no fallback function was specified.
FunctionDefinition const* fallbackFunction() const;
/// Returns the ether receiver function or nullptr if no receive function was specified.
FunctionDefinition const* receiveFunction() const;
std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
TypePointer type() const override;
@ -441,10 +446,13 @@ public:
ContractKind contractKind() const { return m_contractKind; }
bool abstract() const { return m_abstract; }
private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<ASTNode>> m_subNodes;
ContractKind m_contractKind;
bool m_abstract{false};
mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
@ -563,7 +571,7 @@ public:
};
/**
* Parameter list, used as function parameter list and return list.
* Parameter list, used as function parameter list, return list and for try and catch.
* None of the parameters is allowed to contain mappings (not even recursively
* inside structs).
*/
@ -596,22 +604,59 @@ public:
ASTPointer<ASTString> const& _name,
Declaration::Visibility _visibility,
ASTPointer<ParameterList> const& _parameters,
bool _isVirtual = false,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>()
):
Declaration(_location, _name, _visibility),
m_parameters(_parameters),
m_returnParameters(_returnParameters)
m_overrides(_overrides),
m_returnParameters(_returnParameters),
m_isVirtual(_isVirtual)
{
}
std::vector<ASTPointer<VariableDeclaration>> const& parameters() const { return m_parameters->parameters(); }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
ParameterList const& parameterList() const { return *m_parameters; }
ASTPointer<ParameterList> const& returnParameterList() const { return m_returnParameters; }
bool markedVirtual() const { return m_isVirtual; }
virtual bool virtualSemantics() const { return markedVirtual(); }
CallableDeclarationAnnotation& annotation() const override;
protected:
ASTPointer<ParameterList> m_parameters;
ASTPointer<OverrideSpecifier> m_overrides;
ASTPointer<ParameterList> m_returnParameters;
bool m_isVirtual = false;
};
/**
* Function override specifier. Consists of a single override keyword
* potentially followed by a parenthesized list of base contract names.
*/
class OverrideSpecifier: public ASTNode
{
public:
OverrideSpecifier(
SourceLocation const& _location,
std::vector<ASTPointer<UserDefinedTypeName>> const& _overrides
):
ASTNode(_location),
m_overrides(_overrides)
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
/// @returns the list of specific overrides, if any
std::vector<ASTPointer<UserDefinedTypeName>> const& overrides() const { return m_overrides; }
protected:
std::vector<ASTPointer<UserDefinedTypeName>> m_overrides;
};
class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional
@ -622,36 +667,43 @@ public:
ASTPointer<ASTString> const& _name,
Declaration::Visibility _visibility,
StateMutability _stateMutability,
bool _isConstructor,
Token _kind,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<ASTString> const& _documentation,
ASTPointer<ParameterList> const& _parameters,
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
ASTPointer<ParameterList> const& _returnParameters,
ASTPointer<Block> const& _body
):
CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters),
CallableDeclaration(_location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters),
Documented(_documentation),
ImplementationOptional(_body != nullptr),
m_stateMutability(_stateMutability),
m_isConstructor(_isConstructor),
m_kind(_kind),
m_functionModifiers(_modifiers),
m_body(_body)
{}
{
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
StateMutability stateMutability() const { return m_stateMutability; }
bool isConstructor() const { return m_isConstructor; }
bool isFallback() const { return !m_isConstructor && name().empty(); }
bool isOrdinary() const { return m_kind == Token::Function; }
bool isConstructor() const { return m_kind == Token::Constructor; }
bool isFallback() const { return m_kind == Token::Fallback; }
bool isReceive() const { return m_kind == Token::Receive; }
Token kind() const { return m_kind; }
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
bool isVisibleInContract() const override
{
return Declaration::isVisibleInContract() && !isConstructor() && !isFallback();
return Declaration::isVisibleInContract() && isOrdinary();
}
bool isPartOfExternalInterface() const override { return isPublic() && !isConstructor() && !isFallback(); }
bool isPartOfExternalInterface() const override { return isPublic() && isOrdinary(); }
/// @returns the external signature of the function
/// That consists of the name of the function followed by the types of the
@ -668,9 +720,15 @@ public:
FunctionDefinitionAnnotation& annotation() const override;
bool virtualSemantics() const override
{
return
CallableDeclaration::virtualSemantics() ||
annotation().contract->isInterface();
}
private:
StateMutability m_stateMutability;
bool m_isConstructor;
Token const m_kind;
std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers;
ASTPointer<Block> m_body;
};
@ -693,6 +751,7 @@ public:
bool _isStateVar = false,
bool _isIndexed = false,
bool _isConstant = false,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
Location _referenceLocation = Location::Unspecified
):
Declaration(_sourceLocation, _name, _visibility),
@ -701,8 +760,10 @@ public:
m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed),
m_isConstant(_isConstant),
m_overrides(_overrides),
m_location(_referenceLocation) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -716,9 +777,12 @@ public:
/// (or function type name or event) or declared inside a function body.
bool isLocalVariable() const;
/// @returns true if this variable is a parameter or return parameter of a function.
bool isCallableParameter() const;
bool isCallableOrCatchParameter() const;
/// @returns true if this variable is a return parameter of a function.
bool isReturnParameter() const;
/// @returns true if this variable is a parameter of the success or failure clausse
/// of a try/catch statement.
bool isTryCatchParameter() const;
/// @returns true if this variable is a local variable or return parameter.
bool isLocalOrReturn() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function.
@ -740,6 +804,7 @@ public:
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
Location referenceLocation() const { return m_location; }
/// @returns a set of allowed storage locations for the variable.
std::set<Location> allowedDataLocations() const;
@ -760,10 +825,11 @@ private:
/// Initially assigned value, can be missing. For local variables, this is stored inside
/// VariableDeclarationStatement and not here.
ASTPointer<Expression> m_value;
bool m_isStateVariable; ///< Whether or not this is a contract state variable
bool m_isIndexed; ///< Whether this is an indexed variable (used by events).
bool m_isConstant; ///< Whether the variable is a compile-time constant.
Location m_location; ///< Location of the variable if it is of reference type.
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
bool m_isConstant = false; ///< Whether the variable is a compile-time constant.
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
};
/**
@ -777,9 +843,11 @@ public:
ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation,
ASTPointer<ParameterList> const& _parameters,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<Block> const& _body
):
CallableDeclaration(_location, _name, Visibility::Internal, _parameters),
CallableDeclaration(_location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
Documented(_documentation),
m_body(_body)
{
@ -1150,6 +1218,76 @@ private:
ASTPointer<Statement> m_falseBody; ///< "else" part, optional
};
/**
* Clause of a try-catch block. Includes both the successful case and the
* unsuccessful cases.
* Names are only allowed for the unsuccessful cases.
*/
class TryCatchClause: public ASTNode, public Scopable
{
public:
TryCatchClause(
SourceLocation const& _location,
ASTPointer<ASTString> const& _errorName,
ASTPointer<ParameterList> const& _parameters,
ASTPointer<Block> const& _block
):
ASTNode(_location),
m_errorName(_errorName),
m_parameters(_parameters),
m_block(_block)
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
ASTString const& errorName() const { return *m_errorName; }
ParameterList const* parameters() const { return m_parameters.get(); }
Block const& block() const { return *m_block; }
private:
ASTPointer<ASTString> m_errorName;
ASTPointer<ParameterList> m_parameters;
ASTPointer<Block> m_block;
};
/**
* Try-statement with a variable number of catch statements.
* Syntax:
* try <call> returns (uint x, uint y) {
* // success code
* } catch Error(string memory cause) {
* // error code, reason provided
* } catch (bytes memory lowLevelData) {
* // error code, no reason provided or non-matching error signature.
* }
*
* The last statement given above can also be specified as
* } catch () {
*/
class TryStatement: public Statement
{
public:
TryStatement(
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _externalCall,
std::vector<ASTPointer<TryCatchClause>> const& _clauses
):
Statement(_location, _docString),
m_externalCall(_externalCall),
m_clauses(_clauses)
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Expression const& externalCall() const { return *m_externalCall; }
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
private:
ASTPointer<Expression> m_externalCall;
std::vector<ASTPointer<TryCatchClause>> m_clauses;
};
/**
* Statement in which a break statement is legal (abstract class).
*/
@ -1618,6 +1756,32 @@ private:
ASTPointer<Expression> m_index;
};
/**
* Index range access to an array. Example: a[2:3]
*/
class IndexRangeAccess: public Expression
{
public:
IndexRangeAccess(
SourceLocation const& _location,
ASTPointer<Expression> const& _base,
ASTPointer<Expression> const& _start,
ASTPointer<Expression> const& _end
):
Expression(_location), m_base(_base), m_start(_start), m_end(_end) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Expression const& baseExpression() const { return *m_base; }
Expression const* startExpression() const { return m_start.get(); }
Expression const* endExpression() const { return m_end.get(); }
private:
ASTPointer<Expression> m_base;
ASTPointer<Expression> m_start;
ASTPointer<Expression> m_end;
};
/**
* Primary expression, i.e. an expression that cannot be divided any further. Examples are literals
* or variable references.
@ -1658,16 +1822,21 @@ private:
class ElementaryTypeNameExpression: public PrimaryExpression
{
public:
ElementaryTypeNameExpression(SourceLocation const& _location, ElementaryTypeNameToken const& _type):
PrimaryExpression(_location), m_typeToken(_type)
{}
ElementaryTypeNameExpression(
SourceLocation const& _location,
ASTPointer<ElementaryTypeName> const& _type
):
PrimaryExpression(_location),
m_type(_type)
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
ElementaryTypeNameToken const& typeName() const { return m_typeToken; }
ElementaryTypeName const& type() const { return *m_type; }
private:
ElementaryTypeNameToken m_typeToken;
ASTPointer<ElementaryTypeName> m_type;
};
/**

View File

@ -104,18 +104,23 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
};
struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
struct CallableDeclarationAnnotation: ASTAnnotation
{
/// The function this function overrides, if any. This is always the closest
/// in the linearized inheritance hierarchy.
FunctionDefinition const* superFunction = nullptr;
/// The set of functions/modifiers/events this callable overrides.
std::set<CallableDeclaration const*> baseFunctions;
};
struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
{
/// Pointer to the contract this function is defined in
ContractDefinition const* contract = nullptr;
};
struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
{
};
struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
{
};
@ -217,6 +222,8 @@ enum class FunctionCallKind
struct FunctionCallAnnotation: ExpressionAnnotation
{
FunctionCallKind kind = FunctionCallKind::Unset;
/// If true, this is the external call of a try statement.
bool tryCall = false;
};
}

View File

@ -39,6 +39,7 @@ class PragmaDirective;
class ImportDirective;
class Declaration;
class CallableDeclaration;
class OverrideSpecifier;
class ContractDefinition;
class InheritanceSpecifier;
class UsingForDirective;
@ -63,6 +64,8 @@ class Statement;
class Block;
class PlaceholderStatement;
class IfStatement;
class TryCatchClause;
class TryStatement;
class BreakableStatement;
class WhileStatement;
class ForStatement;

View File

@ -22,11 +22,15 @@
#include <libsolidity/ast/ASTJsonConverter.h>
#include <libsolidity/ast/AST.h>
#include <libyul/AsmJsonConverter.h>
#include <libyul/AsmData.h>
#include <libyul/AsmPrinter.h>
#include <libdevcore/JSON.h>
#include <libdevcore/UTF8.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <vector>
#include <algorithm>
@ -124,11 +128,17 @@ void ASTJsonConverter::setJsonNode(
}
}
size_t ASTJsonConverter::sourceIndexFromLocation(SourceLocation const& _location) const
{
if (_location.source && m_sourceIndices.count(_location.source->name()))
return m_sourceIndices.at(_location.source->name());
else
return size_t(-1);
}
string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) const
{
int sourceIndex{-1};
if (_location.source && m_sourceIndices.count(_location.source->name()))
sourceIndex = m_sourceIndices.at(_location.source->name());
size_t sourceIndex = sourceIndexFromLocation(_location);
int length = -1;
if (_location.start >= 0 && _location.end >= 0)
length = _location.end - _location.start;
@ -245,7 +255,7 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
{
Json::Value tuple(Json::objectValue);
solAssert(symbolAlias.symbol, "");
tuple["foreign"] = nodeId(*symbolAlias.symbol);
tuple["foreign"] = toJson(*symbolAlias.symbol);
tuple["local"] = symbolAlias.alias ? Json::Value(*symbolAlias.alias) : Json::nullValue;
symbolAliases.append(tuple);
}
@ -260,6 +270,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()),
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
make_pair("baseContracts", toJson(_node.baseContracts())),
@ -326,15 +337,24 @@ bool ASTJsonConverter::visit(ParameterList const& _node)
return false;
}
bool ASTJsonConverter::visit(OverrideSpecifier const& _node)
{
setJsonNode(_node, "OverrideSpecifier", {
make_pair("overrides", toJson(_node.overrides()))
});
return false;
}
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("kind", _node.isConstructor() ? "constructor" : (_node.isFallback() ? "fallback" : "function")),
make_pair("kind", TokenTraits::toString(_node.kind())),
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
make_pair("superFunction", idOrNull(_node.annotation().superFunction)),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("virtual", _node.markedVirtual()),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("returnParameters", toJson(*_node.returnParameterList())),
make_pair("modifiers", toJson(_node.modifiers())),
@ -342,6 +362,8 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
make_pair("implemented", _node.isImplemented()),
make_pair("scope", idOrNull(_node.scope()))
};
if (!_node.annotation().baseFunctions.empty())
attributes.emplace_back(make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true)));
if (m_legacy)
attributes.emplace_back("isConstructor", _node.isConstructor());
setJsonNode(_node, "FunctionDefinition", std::move(attributes));
@ -356,6 +378,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
make_pair("constant", _node.isConstant()),
make_pair("stateVariable", _node.isStateVariable()),
make_pair("storageLocation", location(_node.referenceLocation())),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue),
make_pair("scope", idOrNull(_node.scope())),
@ -369,13 +392,18 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
bool ASTJsonConverter::visit(ModifierDefinition const& _node)
{
setJsonNode(_node, "ModifierDefinition", {
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("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("virtual", _node.markedVirtual()),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("body", toJson(_node.body()))
});
};
if (!_node.annotation().baseFunctions.empty())
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));
setJsonNode(_node, "ModifierDefinition", std::move(attributes));
return false;
}
@ -459,17 +487,22 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node)
bool ASTJsonConverter::visit(InlineAssembly const& _node)
{
Json::Value externalReferences(Json::arrayValue);
vector<pair<string, Json::Value>> externalReferences;
for (auto const& it: _node.annotation().externalReferences)
if (it.first)
{
Json::Value tuple(Json::objectValue);
tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it);
externalReferences.append(tuple);
}
externalReferences.emplace_back(make_pair(
it.first->name.str(),
inlineAssemblyIdentifierToJson(it)
));
Json::Value externalReferencesJson = Json::arrayValue;
for (auto&& it: boost::range::sort(externalReferences))
externalReferencesJson.append(std::move(it.second));
setJsonNode(_node, "InlineAssembly", {
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))),
make_pair("externalReferences", std::move(externalReferences))
m_legacy ?
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) :
make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))),
make_pair("externalReferences", std::move(externalReferencesJson))
});
return false;
}
@ -498,6 +531,25 @@ bool ASTJsonConverter::visit(IfStatement const& _node)
return false;
}
bool ASTJsonConverter::visit(TryCatchClause const& _node)
{
setJsonNode(_node, "TryCatchClause", {
make_pair("errorName", _node.errorName()),
make_pair("parameters", toJsonOrNull(_node.parameters())),
make_pair("block", toJson(_node.block()))
});
return false;
}
bool ASTJsonConverter::visit(TryStatement const& _node)
{
setJsonNode(_node, "TryStatement", {
make_pair("externalCall", toJson(_node.externalCall())),
make_pair("clauses", toJson(_node.clauses()))
});
return false;
}
bool ASTJsonConverter::visit(WhileStatement const& _node)
{
setJsonNode(
@ -646,7 +698,8 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("expression", toJson(_node.expression())),
make_pair("names", std::move(names)),
make_pair("arguments", toJson(_node.arguments()))
make_pair("arguments", toJson(_node.arguments())),
make_pair("tryCall", _node.annotation().tryCall)
};
if (m_legacy)
{
@ -693,6 +746,18 @@ bool ASTJsonConverter::visit(IndexAccess const& _node)
return false;
}
bool ASTJsonConverter::visit(IndexRangeAccess const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("baseExpression", toJson(_node.baseExpression())),
make_pair("startExpression", toJsonOrNull(_node.startExpression())),
make_pair("endExpression", toJsonOrNull(_node.endExpression())),
};
appendExpressionAttributes(attributes, _node.annotation());
setJsonNode(_node, "IndexRangeAccess", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(Identifier const& _node)
{
Json::Value overloads(Json::arrayValue);
@ -711,7 +776,7 @@ bool ASTJsonConverter::visit(Identifier const& _node)
bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair(m_legacy ? "value" : "typeName", _node.typeName().toString())
make_pair(m_legacy ? "value" : "typeName", toJson(_node.type()))
};
appendExpressionAttributes(attributes, _node.annotation());
setJsonNode(_node, "ElementaryTypeNameExpression", std::move(attributes));

View File

@ -81,6 +81,7 @@ public:
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
bool visit(ParameterList const& _node) override;
bool visit(OverrideSpecifier const& _node) override;
bool visit(FunctionDefinition const& _node) override;
bool visit(VariableDeclaration const& _node) override;
bool visit(ModifierDefinition const& _node) override;
@ -95,6 +96,8 @@ public:
bool visit(Block const& _node) override;
bool visit(PlaceholderStatement const& _node) override;
bool visit(IfStatement const& _node) override;
bool visit(TryCatchClause const& _node) override;
bool visit(TryStatement const& _node) override;
bool visit(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override;
bool visit(Continue const& _node) override;
@ -113,6 +116,7 @@ public:
bool visit(NewExpression const& _node) override;
bool visit(MemberAccess const& _node) override;
bool visit(IndexAccess const& _node) override;
bool visit(IndexRangeAccess const& _node) override;
bool visit(Identifier const& _node) override;
bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override;
@ -130,6 +134,7 @@ private:
std::string const& _nodeName,
std::vector<std::pair<std::string, Json::Value>>&& _attributes
);
size_t sourceIndexFromLocation(langutil::SourceLocation const& _location) const;
std::string sourceLocationToString(langutil::SourceLocation const& _location) const;
static std::string namePathToString(std::vector<ASTString> const& _namePath);
static Json::Value idOrNull(ASTNode const* _pt)

View File

@ -1,644 +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/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging.
*/
#include <libsolidity/ast/ASTPrinter.h>
#include <libsolidity/ast/AST.h>
#include <boost/algorithm/string/join.hpp>
#include <json/json.h>
using namespace std;
using namespace langutil;
namespace dev
{
namespace solidity
{
ASTPrinter::ASTPrinter(
ASTNode const& _ast,
string const& _source,
GasEstimator::ASTGasConsumption const& _gasCosts
): m_indentation(0), m_source(_source), m_ast(&_ast), m_gasCosts(_gasCosts)
{
}
void ASTPrinter::print(ostream& _stream)
{
m_ostream = &_stream;
m_ast->accept(*this);
m_ostream = nullptr;
}
bool ASTPrinter::visit(PragmaDirective const& _node)
{
writeLine("PragmaDirective");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ImportDirective const& _node)
{
writeLine("ImportDirective \"" + _node.path() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ContractDefinition const& _node)
{
writeLine("ContractDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(InheritanceSpecifier const& _node)
{
writeLine("InheritanceSpecifier");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(UsingForDirective const& _node)
{
writeLine("UsingForDirective");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(StructDefinition const& _node)
{
writeLine("StructDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(EnumDefinition const& _node)
{
writeLine("EnumDefinition \"" + _node.name() + "\"");
return goDeeper();
}
bool ASTPrinter::visit(EnumValue const& _node)
{
writeLine("EnumValue \"" + _node.name() + "\"");
return goDeeper();
}
bool ASTPrinter::visit(ParameterList const& _node)
{
writeLine("ParameterList");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(FunctionDefinition const& _node)
{
writeLine(
"FunctionDefinition \"" +
_node.name() +
"\"" +
(_node.isPublic() ? " - public" : "") +
(_node.stateMutability() == StateMutability::View ? " - const" : "")
);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(VariableDeclaration const& _node)
{
writeLine("VariableDeclaration \"" + _node.name() + "\"");
*m_ostream << indentation() << (
_node.annotation().type ?
string(" Type: ") + _node.annotation().type->toString() :
string(" Type unknown.")
) << "\n";
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ModifierDefinition const& _node)
{
writeLine("ModifierDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ModifierInvocation const& _node)
{
writeLine("ModifierInvocation \"" + _node.name()->name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(EventDefinition const& _node)
{
writeLine("EventDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ElementaryTypeName const& _node)
{
writeLine(string("ElementaryTypeName ") + _node.typeName().toString());
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(UserDefinedTypeName const& _node)
{
writeLine("UserDefinedTypeName \"" + boost::algorithm::join(_node.namePath(), ".") + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(FunctionTypeName const& _node)
{
writeLine("FunctionTypeName");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Mapping const& _node)
{
writeLine("Mapping");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ArrayTypeName const& _node)
{
writeLine("ArrayTypeName");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(InlineAssembly const& _node)
{
writeLine("InlineAssembly");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Block const& _node)
{
writeLine("Block");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(PlaceholderStatement const& _node)
{
writeLine("PlaceholderStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(IfStatement const& _node)
{
writeLine("IfStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(WhileStatement const& _node)
{
writeLine(_node.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ForStatement const& _node)
{
writeLine("ForStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Continue const& _node)
{
writeLine("Continue");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Break const& _node)
{
writeLine("Break");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Return const& _node)
{
writeLine("Return");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Throw const& _node)
{
writeLine("Throw");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(EmitStatement const& _node)
{
writeLine("EmitStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(VariableDeclarationStatement const& _node)
{
writeLine("VariableDeclarationStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ExpressionStatement const& _node)
{
writeLine("ExpressionStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Conditional const& _node)
{
writeLine("Conditional");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Assignment const& _node)
{
writeLine(string("Assignment using operator ") + TokenTraits::toString(_node.assignmentOperator()));
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(TupleExpression const& _node)
{
writeLine(string("TupleExpression"));
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(UnaryOperation const& _node)
{
writeLine(
string("UnaryOperation (") +
(_node.isPrefixOperation() ? "prefix" : "postfix") +
") " +
TokenTraits::toString(_node.getOperator())
);
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(BinaryOperation const& _node)
{
writeLine(string("BinaryOperation using operator ") + TokenTraits::toString(_node.getOperator()));
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(FunctionCall const& _node)
{
writeLine("FunctionCall");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(NewExpression const& _node)
{
writeLine("NewExpression");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(MemberAccess const& _node)
{
writeLine("MemberAccess to member " + _node.memberName());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(IndexAccess const& _node)
{
writeLine("IndexAccess");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Identifier const& _node)
{
writeLine(string("Identifier ") + _node.name());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ElementaryTypeNameExpression const& _node)
{
writeLine(string("ElementaryTypeNameExpression ") + _node.typeName().toString());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Literal const& _node)
{
char const* tokenString = TokenTraits::toString(_node.token());
if (!tokenString)
tokenString = "[no token]";
writeLine(string("Literal, token: ") + tokenString + " value: " + _node.value());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
void ASTPrinter::endVisit(PragmaDirective const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ImportDirective const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ContractDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(InheritanceSpecifier const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(UsingForDirective const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(StructDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EnumDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EnumValue const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ParameterList const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(FunctionDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(VariableDeclaration const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ModifierDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ModifierInvocation const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EventDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ElementaryTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(UserDefinedTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(FunctionTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Mapping const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ArrayTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(InlineAssembly const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Block const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(PlaceholderStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(IfStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(WhileStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ForStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Continue const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Break const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Return const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Throw const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EmitStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(VariableDeclarationStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ExpressionStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Conditional const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Assignment const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(TupleExpression const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(UnaryOperation const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(BinaryOperation const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(FunctionCall const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(NewExpression const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(MemberAccess const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(IndexAccess const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Identifier const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ElementaryTypeNameExpression const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Literal const&)
{
m_indentation--;
}
void ASTPrinter::printSourcePart(ASTNode const& _node)
{
if (m_gasCosts.count(&_node))
*m_ostream << indentation() << " Gas costs: " << m_gasCosts.at(&_node) << endl;
if (!m_source.empty())
{
SourceLocation const& location(_node.location());
*m_ostream <<
indentation() <<
" Source: " <<
Json::valueToQuotedString(m_source.substr(location.start, location.end - location.start).c_str()) <<
endl;
}
}
void ASTPrinter::printType(Expression const& _expression)
{
if (_expression.annotation().type)
*m_ostream << indentation() << " Type: " << _expression.annotation().type->toString() << "\n";
else
*m_ostream << indentation() << " Type unknown.\n";
}
string ASTPrinter::indentation() const
{
return string(m_indentation * 2, ' ');
}
void ASTPrinter::writeLine(string const& _line)
{
*m_ostream << indentation() << _line << endl;
}
}
}

View File

@ -1,155 +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/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/interface/GasEstimator.h>
#include <ostream>
namespace dev
{
namespace solidity
{
/**
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable) for debugging purposes.
*/
class ASTPrinter: public ASTConstVisitor
{
public:
/// Create a printer for the given abstract syntax tree. If the source is specified,
/// the corresponding parts of the source are printed with each node.
ASTPrinter(
ASTNode const& _ast,
std::string const& _source = std::string(),
GasEstimator::ASTGasConsumption const& _gasCosts = GasEstimator::ASTGasConsumption()
);
/// Output the string representation of the AST to _stream.
void print(std::ostream& _stream);
bool visit(PragmaDirective const& _node) override;
bool visit(ImportDirective const& _node) override;
bool visit(ContractDefinition const& _node) override;
bool visit(InheritanceSpecifier const& _node) override;
bool visit(UsingForDirective const& _node) override;
bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
bool visit(ParameterList const& _node) override;
bool visit(FunctionDefinition const& _node) override;
bool visit(VariableDeclaration const& _node) override;
bool visit(ModifierDefinition const& _node) override;
bool visit(ModifierInvocation const& _node) override;
bool visit(EventDefinition const& _node) override;
bool visit(ElementaryTypeName const& _node) override;
bool visit(UserDefinedTypeName const& _node) override;
bool visit(FunctionTypeName const& _node) override;
bool visit(Mapping const& _node) override;
bool visit(ArrayTypeName const& _node) override;
bool visit(InlineAssembly const& _node) override;
bool visit(Block const& _node) override;
bool visit(PlaceholderStatement const& _node) override;
bool visit(IfStatement const& _node) override;
bool visit(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override;
bool visit(Continue const& _node) override;
bool visit(Break const& _node) override;
bool visit(Return const& _node) override;
bool visit(Throw const& _node) override;
bool visit(EmitStatement const& _node) override;
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Conditional const& _node) override;
bool visit(Assignment const& _node) override;
bool visit(TupleExpression const& _node) override;
bool visit(UnaryOperation const& _node) override;
bool visit(BinaryOperation const& _node) override;
bool visit(FunctionCall const& _node) override;
bool visit(NewExpression const& _node) override;
bool visit(MemberAccess const& _node) override;
bool visit(IndexAccess const& _node) override;
bool visit(Identifier const& _node) override;
bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override;
void endVisit(PragmaDirective const&) override;
void endVisit(ImportDirective const&) override;
void endVisit(ContractDefinition const&) override;
void endVisit(InheritanceSpecifier const&) override;
void endVisit(UsingForDirective const&) override;
void endVisit(StructDefinition const&) override;
void endVisit(EnumDefinition const&) override;
void endVisit(EnumValue const&) override;
void endVisit(ParameterList const&) override;
void endVisit(FunctionDefinition const&) override;
void endVisit(VariableDeclaration const&) override;
void endVisit(ModifierDefinition const&) override;
void endVisit(ModifierInvocation const&) override;
void endVisit(EventDefinition const&) override;
void endVisit(ElementaryTypeName const&) override;
void endVisit(UserDefinedTypeName const&) override;
void endVisit(FunctionTypeName const&) override;
void endVisit(Mapping const&) override;
void endVisit(ArrayTypeName const&) override;
void endVisit(InlineAssembly const&) override;
void endVisit(Block const&) override;
void endVisit(PlaceholderStatement const&) override;
void endVisit(IfStatement const&) override;
void endVisit(WhileStatement const&) override;
void endVisit(ForStatement const&) override;
void endVisit(Continue const&) override;
void endVisit(Break const&) override;
void endVisit(Return const&) override;
void endVisit(Throw const&) override;
void endVisit(EmitStatement const&) override;
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Conditional const&) override;
void endVisit(Assignment const&) override;
void endVisit(TupleExpression const&) override;
void endVisit(UnaryOperation const&) override;
void endVisit(BinaryOperation const&) override;
void endVisit(FunctionCall const&) override;
void endVisit(NewExpression const&) override;
void endVisit(MemberAccess const&) override;
void endVisit(IndexAccess const&) override;
void endVisit(Identifier const&) override;
void endVisit(ElementaryTypeNameExpression const&) override;
void endVisit(Literal const&) override;
private:
void printSourcePart(ASTNode const& _node);
void printType(Expression const& _expression);
std::string indentation() const;
void writeLine(std::string const& _line);
bool goDeeper() { m_indentation++; return true; }
int m_indentation;
std::string m_source;
ASTNode const* m_ast;
GasEstimator::ASTGasConsumption m_gasCosts;
std::ostream* m_ostream = nullptr;
};
}
}

View File

@ -54,6 +54,7 @@ public:
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumValue& _node) { return visitNode(_node); }
virtual bool visit(ParameterList& _node) { return visitNode(_node); }
virtual bool visit(OverrideSpecifier& _node) { return visitNode(_node); }
virtual bool visit(FunctionDefinition& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclaration& _node) { return visitNode(_node); }
virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); }
@ -68,6 +69,8 @@ public:
virtual bool visit(Block& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); }
virtual bool visit(IfStatement& _node) { return visitNode(_node); }
virtual bool visit(TryCatchClause& _node) { return visitNode(_node); }
virtual bool visit(TryStatement& _node) { return visitNode(_node); }
virtual bool visit(WhileStatement& _node) { return visitNode(_node); }
virtual bool visit(ForStatement& _node) { return visitNode(_node); }
virtual bool visit(Continue& _node) { return visitNode(_node); }
@ -86,6 +89,7 @@ public:
virtual bool visit(NewExpression& _node) { return visitNode(_node); }
virtual bool visit(MemberAccess& _node) { return visitNode(_node); }
virtual bool visit(IndexAccess& _node) { return visitNode(_node); }
virtual bool visit(IndexRangeAccess& _node) { return visitNode(_node); }
virtual bool visit(Identifier& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); }
virtual bool visit(Literal& _node) { return visitNode(_node); }
@ -100,6 +104,7 @@ public:
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); }
virtual void endVisit(ParameterList& _node) { endVisitNode(_node); }
virtual void endVisit(OverrideSpecifier& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclaration& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); }
@ -114,6 +119,8 @@ public:
virtual void endVisit(Block& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement& _node) { endVisitNode(_node); }
virtual void endVisit(TryCatchClause& _node) { endVisitNode(_node); }
virtual void endVisit(TryStatement& _node) { endVisitNode(_node); }
virtual void endVisit(WhileStatement& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement& _node) { endVisitNode(_node); }
virtual void endVisit(Continue& _node) { endVisitNode(_node); }
@ -132,6 +139,7 @@ public:
virtual void endVisit(NewExpression& _node) { endVisitNode(_node); }
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); }
virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); }
virtual void endVisit(IndexRangeAccess& _node) { endVisitNode(_node); }
virtual void endVisit(Identifier& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); }
virtual void endVisit(Literal& _node) { endVisitNode(_node); }
@ -159,6 +167,7 @@ public:
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumValue const& _node) { return visitNode(_node); }
virtual bool visit(ParameterList const& _node) { return visitNode(_node); }
virtual bool visit(OverrideSpecifier const& _node) { return visitNode(_node); }
virtual bool visit(FunctionDefinition const& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclaration const& _node) { return visitNode(_node); }
virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); }
@ -172,6 +181,8 @@ public:
virtual bool visit(Block const& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement const& _node) { return visitNode(_node); }
virtual bool visit(IfStatement const& _node) { return visitNode(_node); }
virtual bool visit(TryCatchClause const& _node) { return visitNode(_node); }
virtual bool visit(TryStatement const& _node) { return visitNode(_node); }
virtual bool visit(WhileStatement const& _node) { return visitNode(_node); }
virtual bool visit(ForStatement const& _node) { return visitNode(_node); }
virtual bool visit(Continue const& _node) { return visitNode(_node); }
@ -191,6 +202,7 @@ public:
virtual bool visit(NewExpression const& _node) { return visitNode(_node); }
virtual bool visit(MemberAccess const& _node) { return visitNode(_node); }
virtual bool visit(IndexAccess const& _node) { return visitNode(_node); }
virtual bool visit(IndexRangeAccess const& _node) { return visitNode(_node); }
virtual bool visit(Identifier const& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); }
virtual bool visit(Literal const& _node) { return visitNode(_node); }
@ -205,6 +217,7 @@ public:
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }
virtual void endVisit(ParameterList const& _node) { endVisitNode(_node); }
virtual void endVisit(OverrideSpecifier const& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclaration const& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); }
@ -218,6 +231,8 @@ public:
virtual void endVisit(Block const& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(TryCatchClause const& _node) { endVisitNode(_node); }
virtual void endVisit(TryStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(WhileStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(Continue const& _node) { endVisitNode(_node); }
@ -237,6 +252,7 @@ public:
virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(IndexRangeAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(Identifier const& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(Literal const& _node) { endVisitNode(_node); }

View File

@ -187,10 +187,26 @@ void ParameterList::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void OverrideSpecifier::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
listAccept(m_overrides, _visitor);
_visitor.endVisit(*this);
}
void OverrideSpecifier::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
listAccept(m_overrides, _visitor);
_visitor.endVisit(*this);
}
void FunctionDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_overrides)
m_overrides->accept(_visitor);
m_parameters->accept(_visitor);
if (m_returnParameters)
m_returnParameters->accept(_visitor);
@ -205,6 +221,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_overrides)
m_overrides->accept(_visitor);
m_parameters->accept(_visitor);
if (m_returnParameters)
m_returnParameters->accept(_visitor);
@ -244,6 +262,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
if (_visitor.visit(*this))
{
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
@ -254,6 +274,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
if (_visitor.visit(*this))
{
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
@ -443,6 +465,48 @@ void IfStatement::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void TryCatchClause::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_parameters)
m_parameters->accept(_visitor);
m_block->accept(_visitor);
}
_visitor.endVisit(*this);
}
void TryCatchClause::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_parameters)
m_parameters->accept(_visitor);
m_block->accept(_visitor);
}
_visitor.endVisit(*this);
}
void TryStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_externalCall->accept(_visitor);
listAccept(m_clauses, _visitor);
}
_visitor.endVisit(*this);
}
void TryStatement::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_externalCall->accept(_visitor);
listAccept(m_clauses, _visitor);
}
_visitor.endVisit(*this);
}
void WhileStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
@ -765,6 +829,32 @@ void IndexAccess::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void IndexRangeAccess::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_base->accept(_visitor);
if (m_start)
m_start->accept(_visitor);
if (m_end)
m_end->accept(_visitor);
}
_visitor.endVisit(*this);
}
void IndexRangeAccess::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_base->accept(_visitor);
if (m_start)
m_start->accept(_visitor);
if (m_end)
m_end->accept(_visitor);
}
_visitor.endVisit(*this);
}
void Identifier::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);

View File

@ -21,6 +21,7 @@
#pragma once
#include <map>
#include <set>
namespace dev
{
@ -35,10 +36,11 @@ enum class ExperimentalFeature
TestOnlyAnalysis
};
static std::map<ExperimentalFeature, bool> const ExperimentalFeatureOnlyAnalysis =
static std::set<ExperimentalFeature> const ExperimentalFeatureWithoutWarning =
{
{ ExperimentalFeature::SMTChecker, true },
{ ExperimentalFeature::TestOnlyAnalysis, true },
ExperimentalFeature::ABIEncoderV2,
ExperimentalFeature::SMTChecker,
ExperimentalFeature::TestOnlyAnalysis,
};
static std::map<std::string, ExperimentalFeature> const ExperimentalFeatureNames =

View File

@ -31,6 +31,7 @@ InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{};
/// they rely on `byte` being available which we cannot guarantee in the static init context.
unique_ptr<ArrayType> TypeProvider::m_bytesStorage;
unique_ptr<ArrayType> TypeProvider::m_bytesMemory;
unique_ptr<ArrayType> TypeProvider::m_bytesCalldata;
unique_ptr<ArrayType> TypeProvider::m_stringStorage;
unique_ptr<ArrayType> TypeProvider::m_stringMemory;
@ -177,6 +178,7 @@ void TypeProvider::reset()
clearCache(m_inaccessibleDynamic);
clearCache(m_bytesStorage);
clearCache(m_bytesMemory);
clearCache(m_bytesCalldata);
clearCache(m_stringStorage);
clearCache(m_stringMemory);
clearCache(m_emptyTuple);
@ -200,7 +202,7 @@ inline T const* TypeProvider::createAndGet(Args&& ... _args)
return static_cast<T const*>(instance().m_generalTypes.back().get());
}
Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type, std::optional<StateMutability> _stateMutability)
{
solAssert(
TokenTraits::isElementaryTypeName(_type.token()),
@ -233,7 +235,14 @@ Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const&
case Token::UFixed:
return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned);
case Token::Address:
{
if (_stateMutability)
{
solAssert(*_stateMutability == StateMutability::Payable, "");
return payableAddress();
}
return address();
}
case Token::Bool:
return boolean();
case Token::Bytes:
@ -307,6 +316,13 @@ ArrayType const* TypeProvider::bytesMemory()
return m_bytesMemory.get();
}
ArrayType const* TypeProvider::bytesCalldata()
{
if (!m_bytesCalldata)
m_bytesCalldata = make_unique<ArrayType>(DataLocation::CallData, false);
return m_bytesCalldata.get();
}
ArrayType const* TypeProvider::stringStorage()
{
if (!m_stringStorage)
@ -494,6 +510,11 @@ ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseTy
return createAndGet<ArrayType>(_location, _baseType, _length);
}
ArraySliceType const* TypeProvider::arraySlice(ArrayType const& _arrayType)
{
return createAndGet<ArraySliceType>(_arrayType);
}
ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper)
{
return createAndGet<ContractType>(_contractDef, _isSuper);

View File

@ -55,7 +55,7 @@ public:
/// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type.
static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type);
static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type, std::optional<StateMutability> _stateMutability = {});
/// Converts a given elementary type name with optional data location
/// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage".
@ -69,6 +69,7 @@ public:
static ArrayType const* bytesStorage();
static ArrayType const* bytesMemory();
static ArrayType const* bytesCalldata();
static ArrayType const* stringStorage();
static ArrayType const* stringMemory();
@ -81,6 +82,8 @@ public:
/// Constructor for a fixed-size array type ("type[20]")
static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length);
static ArraySliceType const* arraySlice(ArrayType const& _arrayType);
static AddressType const* payableAddress() noexcept { return &m_payableAddress; }
static AddressType const* address() noexcept { return &m_address; }
@ -204,6 +207,7 @@ private:
/// These are lazy-initialized because they depend on `byte` being available.
static std::unique_ptr<ArrayType> m_bytesStorage;
static std::unique_ptr<ArrayType> m_bytesMemory;
static std::unique_ptr<ArrayType> m_bytesCalldata;
static std::unique_ptr<ArrayType> m_stringStorage;
static std::unique_ptr<ArrayType> m_stringMemory;

View File

@ -412,7 +412,9 @@ BoolResult AddressType::isImplicitlyConvertibleTo(Type const& _other) const
BoolResult AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
if (auto const* contractType = dynamic_cast<ContractType const*>(&_convertTo))
if (_convertTo.category() == category())
return true;
else if (auto const* contractType = dynamic_cast<ContractType const*>(&_convertTo))
return (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable();
return isImplicitlyConvertibleTo(_convertTo) ||
_convertTo.category() == Category::Integer ||
@ -600,6 +602,17 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other
else
return nullptr;
}
else if (Token::Exp == _operator)
{
if (auto otherIntType = dynamic_cast<IntegerType const*>(_other))
{
if (otherIntType->isSigned())
return TypeResult::err("Exponentiation power is not allowed to be a signed integer type.");
}
else if (dynamic_cast<FixedPointType const*>(_other))
return nullptr;
return this;
}
auto commonType = Type::commonType(this, _other); //might be an integer or fixed point
if (!commonType)
@ -610,14 +623,6 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other
return commonType;
if (TokenTraits::isBooleanOp(_operator))
return nullptr;
if (auto intType = dynamic_cast<IntegerType const*>(commonType))
{
if (Token::Exp == _operator && intType->isSigned())
return TypeResult::err("Exponentiation is not allowed for signed integer types.");
}
else if (dynamic_cast<FixedPointType const*>(commonType))
if (Token::Exp == _operator)
return nullptr;
return commonType;
}
@ -1464,8 +1469,9 @@ BoolResult ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const
bool ContractType::isPayable() const
{
auto receiveFunction = m_contract.receiveFunction();
auto fallbackFunction = m_contract.fallbackFunction();
return fallbackFunction && fallbackFunction->isPayable();
return receiveFunction || (fallbackFunction && fallbackFunction->isPayable());
}
TypeResult ContractType::unaryOperatorResult(Token _operator) const
@ -1783,10 +1789,17 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
if (isDynamicallySized() && location() == DataLocation::Storage)
{
members.emplace_back("push", TypeProvider::function(
TypePointers{},
TypePointers{baseType()},
TypePointers{TypeProvider::uint256()},
strings{},
strings{string()},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
));
members.emplace_back("push", TypeProvider::function(
TypePointers{baseType()},
TypePointers{},
strings{string()},
strings{},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
));
members.emplace_back("pop", TypeProvider::function(
@ -1871,6 +1884,30 @@ std::unique_ptr<ReferenceType> ArrayType::copyForLocation(DataLocation _location
return copy;
}
BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const
{
if (m_arrayType.location() == DataLocation::CallData && m_arrayType.isDynamicallySized() && m_arrayType == _other)
return true;
return (*this) == _other;
}
string ArraySliceType::richIdentifier() const
{
return m_arrayType.richIdentifier() + "_slice";
}
bool ArraySliceType::operator==(Type const& _other) const
{
if (auto const* other = dynamic_cast<ArraySliceType const*>(&_other))
return m_arrayType == other->m_arrayType;
return false;
}
string ArraySliceType::toString(bool _short) const
{
return m_arrayType.toString(_short) + " slice";
}
string ContractType::richIdentifier() const
{
return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id());
@ -2735,8 +2772,6 @@ bool FunctionType::operator==(Type const& _other) const
BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
if (m_kind == Kind::External && _convertTo == *TypeProvider::address())
return true;
return _convertTo.category() == category();
}
@ -2931,7 +2966,10 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
{
MemberList::MemberMap members;
if (m_kind == Kind::External)
{
members.emplace_back("selector", TypeProvider::fixedBytes(4));
members.emplace_back("address", TypeProvider::address());
}
if (m_kind != Kind::BareDelegateCall)
{
if (isPayable())

View File

@ -161,7 +161,7 @@ public:
enum class Category
{
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array,
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
FixedBytes, Contract, Struct, Function, Enum, Tuple,
Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic
@ -773,6 +773,35 @@ private:
mutable std::optional<TypeResult> m_interfaceType_library;
};
class ArraySliceType: public ReferenceType
{
public:
explicit ArraySliceType(ArrayType const& _arrayType): ReferenceType(_arrayType.location()), m_arrayType(_arrayType) {}
Category category() const override { return Category::ArraySlice; }
BoolResult isImplicitlyConvertibleTo(Type const& _other) const override;
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
unsigned calldataEncodedSize(bool) const override { solAssert(false, ""); }
unsigned calldataEncodedTailSize() const override { return 32; }
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
bool validForCalldata() const { return m_arrayType.validForCalldata(); }
ArrayType const& arrayType() const { return m_arrayType; }
u256 memoryDataSize() const override { solAssert(false, ""); }
std::unique_ptr<ReferenceType> copyForLocation(DataLocation, bool) const override { solAssert(false, ""); }
private:
ArrayType const& m_arrayType;
};
/**
* The type of a contract instance or library, there is one distinct type for each contract definition.
*/
@ -817,7 +846,8 @@ public:
/// See documentation of m_super
bool isSuper() const { return m_super; }
// @returns true if and only if the contract has a payable fallback function
// @returns true if and only if the contract has a receive ether function or a payable fallback function, i.e.
// if it has code that will be executed on plain ether transfers
bool isPayable() const;
ContractDefinition const& contractDefinition() const { return m_contract; }

View File

@ -35,7 +35,7 @@ void Compiler::compileContract(
bytes const& _metadata
)
{
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings);
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings);
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);
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
m_context.optimise(m_optimiserSettings);

View File

@ -24,6 +24,7 @@
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/EVMVersion.h>
#include <libevmasm/Assembly.h>
#include <functional>
@ -35,8 +36,9 @@ namespace solidity {
class Compiler
{
public:
explicit Compiler(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeContext(_evmVersion),
m_context(_evmVersion, &m_runtimeContext)
{ }
@ -79,6 +81,7 @@ 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

@ -411,7 +411,6 @@ void CompilerContext::appendInlineAssembly(
analyzerResult = yul::AsmAnalyzer(
analysisInfo,
errorReporter,
std::nullopt,
dialect,
identifierAccess.resolve
).analyze(*parserResult);

View File

@ -262,6 +262,8 @@ public:
ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); }
};
void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; }
private:
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
/// the first function definition that is overwritten by _function.

View File

@ -95,6 +95,26 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
m_context << Instruction::REVERT;
}
void CompilerUtils::returnDataToArray()
{
if (m_context.evmVersion().supportsReturndata())
{
m_context << Instruction::RETURNDATASIZE;
m_context.appendInlineAssembly(R"({
switch v case 0 {
v := 0x60
} default {
v := mload(0x40)
mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f))))
mstore(v, returndatasize())
returndatacopy(add(v, 0x20), 0, returndatasize())
}
})", {"v"});
}
else
pushZeroPointer();
}
void CompilerUtils::accessCalldataTail(Type const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
@ -984,6 +1004,17 @@ void CompilerUtils::convertType(
}
break;
}
case Type::Category::ArraySlice:
{
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
solAssert(_targetType == typeOnStack.arrayType(), "");
solUnimplementedAssert(
typeOnStack.arrayType().location() == DataLocation::CallData &&
typeOnStack.arrayType().isDynamicallySized(),
""
);
break;
}
case Type::Category::Struct:
{
solAssert(targetTypeCategory == stackTypeCategory, "");
@ -1115,16 +1146,8 @@ void CompilerUtils::convertType(
m_context << Instruction::ISZERO << Instruction::ISZERO;
break;
default:
if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address)
{
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted.");
// stack: <address> <function_id>
m_context << Instruction::POP;
}
else
{
// we used to allow conversions from function to address
solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address), "");
if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function)
{
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
@ -1145,7 +1168,6 @@ void CompilerUtils::convertType(
m_context
<< ((u256(1) << (8 * _targetType.storageBytes())) - 1)
<< Instruction::AND;
}
break;
}

View File

@ -67,6 +67,10 @@ public:
/// Stack post:
void revertWithStringData(Type const& _argumentType);
/// Allocates a new array and copies the return data to it.
/// If the EVM does not support return data, creates an empty array.
void returnDataToArray();
/// Computes the absolute calldata offset of a tail given a base reference and the (absolute)
/// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also
/// returns the array length on the stack.

View File

@ -35,6 +35,8 @@
#include <liblangutil/ErrorReporter.h>
#include <libdevcore/Whiskers.h>
#include <boost/range/adaptor/reversed.hpp>
#include <algorithm>
@ -335,6 +337,9 @@ namespace
// Helper function to check if any function is payable
bool hasPayableFunctions(ContractDefinition const& _contract)
{
if (_contract.receiveFunction())
return true;
FunctionDefinition const* fallback = _contract.fallbackFunction();
if (fallback && fallback->isPayable())
return true;
@ -361,6 +366,9 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
FunctionDefinition const* fallback = _contract.fallbackFunction();
solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions");
FunctionDefinition const* etherReceiver = _contract.receiveFunction();
solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have ether receiver functions");
bool needToAddCallvalueCheck = true;
if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary())
{
@ -368,11 +376,15 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
needToAddCallvalueCheck = false;
}
eth::AssemblyItem notFound = m_context.newTag();
// directly jump to fallback if the data is too short to contain a function selector
eth::AssemblyItem notFoundOrReceiveEther = m_context.newTag();
// If there is neither a fallback nor a receive ether function, we only need one label to jump to, which
// always reverts.
eth::AssemblyItem notFound = (!fallback && !etherReceiver) ? notFoundOrReceiveEther : m_context.newTag();
// directly jump to fallback or ether receiver if the data is too short to contain a function selector
// also guards against short data
m_context << u256(4) << Instruction::CALLDATASIZE << Instruction::LT;
m_context.appendConditionalJumpTo(notFound);
m_context.appendConditionalJumpTo(notFoundOrReceiveEther);
// retrieve the function signature hash from the calldata
if (!interfaceFunctions.empty())
@ -390,8 +402,27 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment);
}
m_context << notFound;
m_context << notFoundOrReceiveEther;
if (!fallback && !etherReceiver)
m_context.appendRevert();
else
{
if (etherReceiver)
{
// directly jump to fallback, if there is calldata
m_context << Instruction::CALLDATASIZE;
m_context.appendConditionalJumpTo(notFound);
solAssert(!_contract.isLibrary(), "");
solAssert(etherReceiver->isReceive(), "");
solAssert(FunctionType(*etherReceiver).parameterTypes().empty(), "");
solAssert(FunctionType(*etherReceiver).returnParameterTypes().empty(), "");
etherReceiver->accept(*this);
m_context << Instruction::STOP;
}
m_context << notFound;
if (fallback)
{
solAssert(!_contract.isLibrary(), "");
@ -407,6 +438,8 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
else
// TODO: error message here?
m_context.appendRevert();
}
for (auto const& it: interfaceFunctions)
{
@ -480,7 +513,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_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
}
bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
@ -493,9 +526,11 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
m_continueTags.clear();
if (_variableDeclaration.isConstant())
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration);
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
.appendConstStateVariableAccessor(_variableDeclaration);
else
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableAccessor(_variableDeclaration);
ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
.appendStateVariableAccessor(_variableDeclaration);
return false;
}
@ -532,8 +567,10 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_currentFunction = &_function;
m_modifierDepth = -1;
m_scopeStackHeight.clear();
m_context.setModifierDepth(0);
appendModifierOrFunctionCode();
m_context.setModifierDepth(0);
solAssert(m_returnTags.empty(), "");
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout
@ -581,7 +618,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
if (!_function.isConstructor())
{
solAssert(m_context.numberOfLocalVariables() == 0, "");
if (!_function.isFallback())
if (!_function.isFallback() && !_function.isReceive())
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
}
@ -763,6 +800,183 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
return false;
}
bool ContractCompiler::visit(TryStatement const& _tryStatement)
{
StackHeightChecker checker(m_context);
CompilerContext::LocationSetter locationSetter(m_context, _tryStatement);
compileExpression(_tryStatement.externalCall());
int const returnSize = static_cast<int>(_tryStatement.externalCall().annotation().type->sizeOnStack());
// Stack: [ return values] <success flag>
eth::AssemblyItem successTag = m_context.appendConditionalJump();
// Catch case.
m_context.adjustStackOffset(-returnSize);
handleCatch(_tryStatement.clauses());
eth::AssemblyItem endTag = m_context.appendJumpToNew();
m_context << successTag;
m_context.adjustStackOffset(returnSize);
{
// Success case.
// Stack: return values
TryCatchClause const& successClause = *_tryStatement.clauses().front();
if (successClause.parameters())
{
vector<TypePointer> exprTypes{_tryStatement.externalCall().annotation().type};
if (auto tupleType = dynamic_cast<TupleType const*>(exprTypes.front()))
exprTypes = tupleType->components();
vector<ASTPointer<VariableDeclaration>> const& params = successClause.parameters()->parameters();
solAssert(exprTypes.size() == params.size(), "");
for (size_t i = 0; i < exprTypes.size(); ++i)
solAssert(params[i] && exprTypes[i] && *params[i]->annotation().type == *exprTypes[i], "");
}
else
CompilerUtils(m_context).popStackSlots(returnSize);
_tryStatement.clauses().front()->accept(*this);
}
m_context << endTag;
checker.check();
return false;
}
void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _catchClauses)
{
// Stack is empty.
ASTPointer<TryCatchClause> structured{};
ASTPointer<TryCatchClause> fallback{};
for (size_t i = 1; i < _catchClauses.size(); ++i)
if (_catchClauses[i]->errorName() == "Error")
structured = _catchClauses[i];
else if (_catchClauses[i]->errorName().empty())
fallback = _catchClauses[i];
else
solAssert(false, "");
solAssert(_catchClauses.size() == size_t(1 + (structured ? 1 : 0) + (fallback ? 1 : 0)), "");
eth::AssemblyItem endTag = m_context.newTag();
eth::AssemblyItem fallbackTag = m_context.newTag();
if (structured)
{
solAssert(
structured->parameters() &&
structured->parameters()->parameters().size() == 1 &&
structured->parameters()->parameters().front() &&
*structured->parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(),
""
);
solAssert(m_context.evmVersion().supportsReturndata(), "");
string errorHash = FixedHash<4>(dev::keccak256("Error(string)")).hex();
// Try to decode the error message.
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
m_context << u256(0);
m_context.appendInlineAssembly(
Whiskers(R"({
data := mload(0x40)
mstore(data, 0)
for {} 1 {} {
if lt(returndatasize(), 0x44) { data := 0 break }
returndatacopy(0, 0, 4)
let sig := <getSig>
if iszero(eq(sig, 0x<ErrorSignature>)) { data := 0 break }
returndatacopy(data, 4, sub(returndatasize(), 4))
let offset := mload(data)
if or(
gt(offset, 0xffffffffffffffff),
gt(add(offset, 0x24), returndatasize())
) {
data := 0
break
}
let msg := add(data, offset)
let length := mload(msg)
if gt(length, 0xffffffffffffffff) { data := 0 break }
let end := add(add(msg, 0x20), length)
if gt(end, add(data, returndatasize())) { data := 0 break }
mstore(0x40, and(add(end, 0x1f), not(0x1f)))
data := msg
break
}
})")
("ErrorSignature", errorHash)
("getSig",
m_context.evmVersion().hasBitwiseShifting() ?
"shr(224, mload(0))" :
"div(mload(0), " + (u256(1) << 224).str() + ")"
).render(),
{"data"}
);
m_context << Instruction::DUP1;
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
m_context << Instruction::POP;
m_context.appendJumpTo(fallbackTag);
m_context.adjustStackOffset(1);
m_context << decodeSuccessTag;
structured->accept(*this);
m_context.appendJumpTo(endTag);
}
m_context << fallbackTag;
if (fallback)
{
if (fallback->parameters())
{
solAssert(m_context.evmVersion().supportsReturndata(), "");
solAssert(
fallback->parameters()->parameters().size() == 1 &&
fallback->parameters()->parameters().front() &&
*fallback->parameters()->parameters().front()->annotation().type == *TypeProvider::bytesMemory(),
""
);
CompilerUtils(m_context).returnDataToArray();
}
fallback->accept(*this);
}
else
{
// re-throw
if (m_context.evmVersion().supportsReturndata())
m_context.appendInlineAssembly(R"({
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
})");
else
m_context.appendRevert();
}
m_context << endTag;
}
bool ContractCompiler::visit(TryCatchClause const& _clause)
{
CompilerContext::LocationSetter locationSetter(m_context, _clause);
unsigned varSize = 0;
if (_clause.parameters())
for (ASTPointer<VariableDeclaration> const& varDecl: _clause.parameters()->parameters() | boost::adaptors::reversed)
{
solAssert(varDecl, "");
varSize += varDecl->annotation().type->sizeOnStack();
m_context.addVariable(*varDecl, varSize);
}
_clause.block().accept(*this);
m_context.removeVariablesAboveStackHeight(m_context.stackHeight() - varSize);
CompilerUtils(m_context).popStackSlots(varSize);
return false;
}
bool ContractCompiler::visit(IfStatement const& _ifStatement)
{
StackHeightChecker checker(m_context);
@ -1032,6 +1246,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
vector<VariableDeclaration const*> addedVariables;
m_modifierDepth++;
m_context.setModifierDepth(m_modifierDepth);
if (m_modifierDepth >= m_currentFunction->modifiers().size())
{
@ -1085,6 +1300,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
m_context.removeVariable(*var);
}
m_modifierDepth--;
m_context.setModifierDepth(m_modifierDepth);
}
void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable)
@ -1096,7 +1312,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con
void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType)
{
ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals);
ExpressionCompiler expressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals);
expressionCompiler.compile(_expression);
if (_targetType)
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);

View File

@ -24,6 +24,7 @@
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libevmasm/Assembly.h>
#include <functional>
#include <ostream>
@ -43,9 +44,11 @@ public:
explicit ContractCompiler(
ContractCompiler* _runtimeCompiler,
CompilerContext& _context,
OptimiserSettings _optimiserSettings
OptimiserSettings _optimiserSettings,
RevertStrings _revertStrings
):
m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeCompiler(_runtimeCompiler),
m_context(_context)
{
@ -104,6 +107,9 @@ private:
bool visit(VariableDeclaration const& _variableDeclaration) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(TryStatement const& _tryStatement) override;
void handleCatch(std::vector<ASTPointer<TryCatchClause>> const& _catchClauses);
bool visit(TryCatchClause const& _clause) override;
bool visit(IfStatement const& _ifStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
bool visit(ForStatement const& _forStatement) override;
@ -135,6 +141,7 @@ 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

@ -601,13 +601,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
break;
}
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
solAssert(!_functionCall.annotation().tryCall, "");
[[fallthrough]];
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
_functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments);
appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall);
break;
case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed.");
@ -635,12 +637,21 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
else
m_context << u256(0);
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO;
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true);
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
// Check if zero (reverted)
m_context << Instruction::DUP1 << Instruction::ISZERO;
if (_functionCall.annotation().tryCall)
{
// If this is a try call, return "<address> 1" in the success case and
// "0" in the error case.
AssemblyItem errorCase = m_context.appendConditionalJump();
m_context << u256(1);
m_context << errorCase;
}
else
// TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true);
break;
}
case FunctionType::Kind::SetGas:
@ -690,7 +701,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
true,
true
),
{}
{},
false
);
if (function.kind() == FunctionType::Kind::Transfer)
{
@ -706,16 +718,28 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
break;
case FunctionType::Kind::Revert:
{
if (!arguments.empty())
if (arguments.empty())
m_context.appendRevert();
else
{
// function-sel(Error(string)) + encoding
solAssert(arguments.size() == 1, "");
solAssert(function.parameterTypes().size() == 1, "");
if (m_revertStrings == RevertStrings::Strip)
{
if (!arguments.front()->annotation().isPure)
{
arguments.front()->accept(*this);
utils().popStackElement(*arguments.front()->annotation().type);
}
m_context.appendRevert();
}
else
{
arguments.front()->accept(*this);
utils().revertWithStringData(*arguments.front()->annotation().type);
}
else
m_context.appendRevert();
}
break;
}
case FunctionType::Kind::KECCAK256:
@ -850,20 +874,47 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << contractAddresses.at(function.kind());
for (unsigned i = function.sizeOnStack(); i > 0; --i)
m_context << swapInstruction(i);
appendExternalFunctionCall(function, arguments);
solAssert(!_functionCall.annotation().tryCall, "");
appendExternalFunctionCall(function, arguments, false);
break;
}
case FunctionType::Kind::ByteArrayPush:
case FunctionType::Kind::ArrayPush:
{
_functionCall.expression().accept(*this);
if (function.parameterTypes().size() == 0)
{
auto paramType = function.returnParameterTypes().at(0);
solAssert(paramType, "");
ArrayType const* arrayType =
function.kind() == FunctionType::Kind::ArrayPush ?
TypeProvider::array(DataLocation::Storage, paramType) :
TypeProvider::bytesStorage();
// stack: ArrayReference
m_context << u256(1) << Instruction::DUP2;
ArrayUtils(m_context).incrementDynamicArraySize(*arrayType);
// stack: ArrayReference 1 newLength
m_context << Instruction::SUB;
// stack: ArrayReference (newLength-1)
ArrayUtils(m_context).accessIndex(*arrayType, false);
if (arrayType->isByteArray())
setLValue<StorageByteArrayElement>(_functionCall);
else
setLValueToStorageItem(_functionCall);
}
else
{
solAssert(function.parameterTypes().size() == 1, "");
solAssert(!!function.parameterTypes()[0], "");
TypePointer paramType = function.parameterTypes()[0];
ArrayType const* arrayType =
function.kind() == FunctionType::Kind::ArrayPush ?
TypeProvider::array(DataLocation::Storage, paramType) :
TypeProvider::array(DataLocation::Storage);
TypeProvider::bytesStorage();
// stack: ArrayReference
arguments[0]->accept(*this);
@ -874,24 +925,23 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::DUP1;
ArrayUtils(m_context).incrementDynamicArraySize(*arrayType);
// stack: argValue ArrayReference newLength
m_context << Instruction::SWAP1;
// stack: argValue newLength ArrayReference
m_context << u256(1) << Instruction::DUP3 << Instruction::SUB;
// stack: argValue newLength ArrayReference (newLength-1)
m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB;
// stack: argValue ArrayReference (newLength-1)
ArrayUtils(m_context).accessIndex(*arrayType, false);
// stack: argValue newLength storageSlot slotOffset
utils().moveToStackTop(3, argType->sizeOnStack());
// stack: newLength storageSlot slotOffset argValue
// stack: argValue storageSlot slotOffset
utils().moveToStackTop(2, argType->sizeOnStack());
// stack: storageSlot slotOffset argValue
TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
solAssert(type, "");
utils().convertType(*argType, *type);
utils().moveToStackTop(1 + type->sizeOnStack());
utils().moveToStackTop(1 + type->sizeOnStack());
// stack: newLength argValue storageSlot slotOffset
// stack: argValue storageSlot slotOffset
if (function.kind() == FunctionType::Kind::ArrayPush)
StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true);
else
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
}
break;
}
case FunctionType::Kind::ArrayPop:
@ -954,6 +1004,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::Require:
{
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false);
bool haveReasonString = arguments.size() > 1 && m_revertStrings != RevertStrings::Strip;
if (arguments.size() > 1)
{
// Users probably expect the second argument to be evaluated
@ -961,9 +1014,20 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// function call.
solAssert(arguments.size() == 2, "");
solAssert(function.kind() == FunctionType::Kind::Require, "");
if (m_revertStrings == RevertStrings::Strip)
{
if (!arguments.at(1)->annotation().isPure)
{
arguments.at(1)->accept(*this);
utils().popStackElement(*arguments.at(1)->annotation().type);
}
}
else
{
arguments.at(1)->accept(*this);
utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack());
}
}
// Stack: <error string (unconverted)> <condition>
// jump if condition was met
m_context << Instruction::ISZERO << Instruction::ISZERO;
@ -971,7 +1035,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error
m_context.appendInvalid();
else if (arguments.size() > 1)
else if (haveReasonString)
{
utils().revertWithStringData(*arguments.at(1)->annotation().type);
// Here, the argument is consumed, but in the other branch, it is still there.
@ -981,7 +1045,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.appendRevert();
// the success branch
m_context << success;
if (arguments.size() > 1)
if (haveReasonString)
utils().popStackElement(*arguments.at(1)->annotation().type);
break;
}
@ -1094,10 +1158,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
else
targetTypes = TypePointers{_functionCall.annotation().type};
if (
*firstArgType == ArrayType(DataLocation::CallData) ||
*firstArgType == ArrayType(DataLocation::CallData, true)
auto referenceType = dynamic_cast<ReferenceType const*>(firstArgType);
referenceType && referenceType->dataStoredIn(DataLocation::CallData)
)
{
solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), "");
utils().convertType(*referenceType, *TypeProvider::bytesCalldata());
utils().abiDecode(targetTypes, false);
}
else
{
utils().convertType(*firstArgType, *TypeProvider::bytesMemory());
@ -1254,7 +1322,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
if (FunctionCall const* funCall = dynamic_cast<FunctionCall const*>(&_memberAccess.expression()))
if (auto const* addr = dynamic_cast<ElementaryTypeNameExpression const*>(&funCall->expression()))
if (
addr->typeName().token() == Token::Address &&
addr->type().typeName().token() == Token::Address &&
funCall->arguments().size() == 1
)
if (auto arg = dynamic_cast<Identifier const*>( funCall->arguments().front().get()))
@ -1340,6 +1408,13 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
/// need to store it as bytes4
utils().leftShiftNumberOnStack(224);
}
else if (member == "address")
{
auto const& functionType = dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type);
solAssert(functionType.kind() == FunctionType::Kind::External, "");
// stack: <address> <function_id>
m_context << Instruction::POP;
}
else
solAssert(
!!_memberAccess.expression().annotation().type->memberType(member),
@ -1490,7 +1565,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
m_context << Instruction::SWAP1 << Instruction::POP;
break;
case DataLocation::Storage:
setLValue<StorageArrayLength>(_memberAccess, type);
ArrayUtils(m_context).retrieveLength(type);
m_context << Instruction::SWAP1 << Instruction::POP;
break;
case DataLocation::Memory:
m_context << Instruction::MLOAD;
@ -1533,7 +1609,9 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
Type const& baseType = *_indexAccess.baseExpression().annotation().type;
if (baseType.category() == Type::Category::Mapping)
switch (baseType.category())
{
case Type::Category::Mapping:
{
// stack: storage_base_ref
TypePointer keyType = dynamic_cast<MappingType const&>(baseType).keyType();
@ -1564,8 +1642,20 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
m_context << Instruction::KECCAK256;
m_context << u256(0);
setLValueToStorageItem(_indexAccess);
break;
}
else if (baseType.category() == Type::Category::Array)
case Type::Category::ArraySlice:
{
auto const& arrayType = dynamic_cast<ArraySliceType const&>(baseType).arrayType();
solAssert(arrayType.location() == DataLocation::CallData && arrayType.isDynamicallySized(), "");
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
ArrayUtils(m_context).accessCallDataArrayElement(arrayType);
break;
}
case Type::Category::Array:
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
@ -1592,8 +1682,9 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
ArrayUtils(m_context).accessCallDataArrayElement(arrayType);
break;
}
break;
}
else if (baseType.category() == Type::Category::FixedBytes)
case Type::Category::FixedBytes:
{
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
@ -1608,15 +1699,59 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
m_context << Instruction::BYTE;
utils().leftShiftNumberOnStack(256 - 8);
break;
}
else if (baseType.category() == Type::Category::TypeType)
case Type::Category::TypeType:
{
solAssert(baseType.sizeOnStack() == 0, "");
solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, "");
// no-op - this seems to be a lone array type (`structType[];`)
break;
}
else
default:
solAssert(false, "Index access only allowed for mappings or arrays.");
break;
}
return false;
}
bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
{
CompilerContext::LocationSetter locationSetter(m_context, _indexAccess);
_indexAccess.baseExpression().accept(*this);
Type const& baseType = *_indexAccess.baseExpression().annotation().type;
ArrayType const *arrayType = dynamic_cast<ArrayType const*>(&baseType);
if (!arrayType)
if (ArraySliceType const* sliceType = dynamic_cast<ArraySliceType const*>(&baseType))
arrayType = &sliceType->arrayType();
solAssert(arrayType, "");
solUnimplementedAssert(arrayType->location() == DataLocation::CallData && arrayType->isDynamicallySized(), "");
if (_indexAccess.startExpression())
acceptAndConvert(*_indexAccess.startExpression(), *TypeProvider::uint256());
else
m_context << u256(0);
if (_indexAccess.endExpression())
acceptAndConvert(*_indexAccess.endExpression(), *TypeProvider::uint256());
else
m_context << Instruction::DUP2;
m_context.appendInlineAssembly(
Whiskers(R"({
if gt(sliceStart, sliceEnd) { revert(0, 0) }
if gt(sliceEnd, length) { revert(0, 0) }
offset := add(offset, mul(sliceStart, <stride>))
length := sub(sliceEnd, sliceStart)
})")("stride", toString(arrayType->calldataStride())).render(),
{"offset", "length", "sliceStart", "sliceEnd"}
);
m_context << Instruction::POP << Instruction::POP;
return false;
}
@ -1906,7 +2041,8 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
void ExpressionCompiler::appendExternalFunctionCall(
FunctionType const& _functionType,
vector<ASTPointer<Expression const>> const& _arguments
vector<ASTPointer<Expression const>> const& _arguments,
bool _tryCall
)
{
solAssert(
@ -1942,6 +2078,12 @@ void ExpressionCompiler::appendExternalFunctionCall(
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
if (_tryCall)
{
solAssert(!returnSuccessConditionAndReturndata, "");
solAssert(!_functionType.isBareCall(), "");
}
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
bool dynamicReturnSize = false;
@ -2113,17 +2255,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
(_functionType.gasSet() ? 1 : 0) +
(!_functionType.isBareCall() ? 1 : 0);
if (returnSuccessConditionAndReturndata)
m_context << swapInstruction(remainsSize);
else
eth::AssemblyItem endTag = m_context.newTag();
if (!returnSuccessConditionAndReturndata && !_tryCall)
{
//Propagate error condition (if CALL pushes 0 on stack).
// Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
m_context.appendConditionalRevert(true);
}
else
m_context << swapInstruction(remainsSize);
utils().popStackSlots(remainsSize);
// Only success flag is remaining on stack.
if (_tryCall)
{
m_context << Instruction::DUP1 << Instruction::ISZERO;
m_context.appendConditionalJumpTo(endTag);
m_context << Instruction::POP;
}
if (returnSuccessConditionAndReturndata)
{
// success condition is already there
@ -2131,24 +2283,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
// an internal helper function e.g. for ``send`` and ``transfer``. In that
// case we're only interested in the success condition, not the return data.
if (!_functionType.returnParameterTypes().empty())
{
if (haveReturndatacopy)
{
m_context << Instruction::RETURNDATASIZE;
m_context.appendInlineAssembly(R"({
switch v case 0 {
v := 0x60
} default {
v := mload(0x40)
mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f))))
mstore(v, returndatasize())
returndatacopy(add(v, 0x20), 0, returndatasize())
}
})", {"v"});
}
else
utils().pushZeroPointer();
}
utils().returnDataToArray();
}
else if (funKind == FunctionType::Kind::RIPEMD160)
{
@ -2202,6 +2337,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().abiDecode(returnTypes, true);
}
if (_tryCall)
{
// Success branch will reach this, failure branch will directly jump to endTag.
m_context << u256(1);
m_context << endTag;
}
}
void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)

View File

@ -25,6 +25,7 @@
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/LValue.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/Exceptions.h>
#include <liblangutil/SourceLocation.h>
#include <libdevcore/Common.h>
@ -55,8 +56,15 @@ class ArrayType;
class ExpressionCompiler: private ASTConstVisitor
{
public:
explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimiseOrderLiterals):
m_optimiseOrderLiterals(_optimiseOrderLiterals), m_context(_compilerContext) {}
ExpressionCompiler(
CompilerContext& _compilerContext,
RevertStrings _revertStrings,
bool _optimiseOrderLiterals
):
m_revertStrings(_revertStrings),
m_optimiseOrderLiterals(_optimiseOrderLiterals),
m_context(_compilerContext)
{}
/// Compile the given @a _expression and leave its value on the stack.
void compile(Expression const& _expression);
@ -80,6 +88,7 @@ private:
bool visit(NewExpression const& _newExpression) override;
bool visit(MemberAccess const& _memberAccess) override;
bool visit(IndexAccess const& _indexAccess) override;
bool visit(IndexRangeAccess const& _indexAccess) override;
void endVisit(Identifier const& _identifier) override;
void endVisit(Literal const& _literal) override;
@ -95,9 +104,12 @@ private:
/// @}
/// Appends code to call a function of the given type with the given arguments.
/// @param _tryCall if true, this is the external call of a try statement. In that case,
/// returns success flag on top of stack and does not revert on failure.
void appendExternalFunctionCall(
FunctionType const& _functionType,
std::vector<ASTPointer<Expression const>> const& _arguments
std::vector<ASTPointer<Expression const>> const& _arguments,
bool _tryCall
);
/// Appends code that evaluates a single expression and moves the result to memory. The memory offset is
/// expected to be on the stack and is updated by this call.
@ -126,6 +138,7 @@ 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;

View File

@ -478,36 +478,6 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer
m_context << Instruction::SWAP1 << Instruction::SSTORE;
}
StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType):
LValue(_compilerContext, _arrayType.memberType("length")),
m_arrayType(_arrayType)
{
solAssert(m_arrayType.isDynamicallySized(), "");
}
void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const
{
ArrayUtils(m_context).retrieveLength(m_arrayType);
if (_remove)
m_context << Instruction::SWAP1 << Instruction::POP;
}
void StorageArrayLength::storeValue(Type const&, SourceLocation const&, bool _move) const
{
if (_move)
m_context << Instruction::SWAP1;
else
m_context << Instruction::DUP2;
ArrayUtils(m_context).resizeDynamicArray(m_arrayType);
}
void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) const
{
solAssert(_removeReference, "");
ArrayUtils(m_context).clearDynamicArray(m_arrayType);
}
TupleObject::TupleObject(
CompilerContext& _compilerContext,
std::vector<std::unique_ptr<LValue>>&& _lvalues

View File

@ -171,31 +171,6 @@ public:
) const override;
};
/**
* Reference to the "length" member of a dynamically-sized array. This is an LValue with special
* semantics since assignments to it might reduce its length and thus arrays members have to be
* deleted.
*/
class StorageArrayLength: public LValue
{
public:
/// Constructs the LValue, assumes that the reference to the array head is already on the stack.
StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType);
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
private:
ArrayType const& m_arrayType;
};
/**
* Tuple object that can itself hold several LValues.
*/

View File

@ -595,6 +595,88 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_pop_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array) {
let oldLen := <fetchLength>(array)
if iszero(oldLen) { invalid() }
let newLen := sub(oldLen, 1)
let slot, offset := <indexAccess>(array, newLen)
<setToZero>(slot, offset)
sstore(array, newLen)
})")
("functionName", functionName)
("fetchLength", arrayLengthFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("setToZero", storageSetToZeroFunction(*_type.baseType()))
.render();
});
}
string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, value) {
let oldLen := <fetchLength>(array)
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
sstore(array, add(oldLen, 1))
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, value)
})")
("functionName", functionName)
("fetchLength", arrayLengthFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
.render();
});
}
string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array) -> slot, offset {
let oldLen := <fetchLength>(array)
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
sstore(array, add(oldLen, 1))
slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, <zeroValueFunction>())
})")
("functionName", functionName)
("fetchLength", arrayLengthFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("zeroValueFunction", zeroValueFunction(*_type.baseType()))
.render();
});
}
string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
{
string functionName = "clear_storage_range_" + _type.identifier();
@ -825,11 +907,6 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& /*_type*/)
{
solUnimplemented("Calldata arrays not yet implemented!");
}
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{
solAssert(!_type.isByteArray(), "");

View File

@ -121,6 +121,18 @@ public:
/// signature: (array, newLen)
std::string resizeDynamicArrayFunction(ArrayType const& _type);
/// @returns the name of a function that reduces the size of a storage array by one element
/// signature: (array)
std::string storageArrayPopFunction(ArrayType const& _type);
/// @returns the name of a function that pushes an element to a storage array
/// signature: (array, value)
std::string storageArrayPushFunction(ArrayType const& _type);
/// @returns the name of a function that pushes the base type's zero element to a storage array and returns storage slot and offset of the added element.
/// signature: (array) -> slot, offset
std::string storageArrayPushZeroFunction(ArrayType const& _type);
/// @returns the name of a function that will clear the storage area given
/// by the start and end (exclusive) parameters (slots).
/// signature: (start, end)

View File

@ -132,10 +132,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
return m_context.functionCollector()->createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
for { let return_flag := 1 } return_flag {} {
<body>
break
}
}
)");
t("functionName", functionName);
@ -267,6 +264,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
</cases>
default {}
}
if iszero(calldatasize()) { <receiveEther> }
<fallback>
)X");
t("shr224", m_utils.shiftRightFunction(224));
@ -313,6 +311,10 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
}
else
t("fallback", "revert(0, 0)");
if (FunctionDefinition const* etherReceiver = _contract.receiveFunction())
t("receiveEther", generateFunction(*etherReceiver) + "() stop()");
else
t("receiveEther", "");
return t.render();
}

View File

@ -296,7 +296,7 @@ void IRGeneratorForStatements::endVisit(Return const& _return)
expressionAsType(*value, *types.front()) <<
"\n";
}
m_code << "return_flag := 0\n" << "break\n";
m_code << "leave\n";
}
void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
@ -642,6 +642,47 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
break;
}
case FunctionType::Kind::ArrayPop:
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(
*dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression().annotation().type
);
defineExpression(_functionCall) <<
m_utils.storageArrayPopFunction(arrayType) <<
"(" <<
m_context.variable(_functionCall.expression()) <<
")\n";
break;
}
case FunctionType::Kind::ArrayPush:
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(
*dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression().annotation().type
);
if (arguments.empty())
{
auto slotName = m_context.newYulVariable();
auto offsetName = m_context.newYulVariable();
m_code << "let " << slotName << ", " << offsetName << " := " <<
m_utils.storageArrayPushZeroFunction(arrayType) <<
"(" << m_context.variable(_functionCall.expression()) << ")\n";
setLValue(_functionCall, make_unique<IRStorageItem>(
m_context.utils(),
slotName,
offsetName,
*arrayType.baseType()
));
}
else
m_code <<
m_utils.storageArrayPushFunction(arrayType) <<
"(" <<
m_context.variable(_functionCall.expression()) <<
", " <<
expressionAsType(*arguments.front(), *arrayType.baseType()) <<
")\n";
break;
}
default:
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
}
@ -718,6 +759,10 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{
solUnimplementedAssert(false, "");
}
else if (member == "address")
{
solUnimplementedAssert(false, "");
}
else
solAssert(
!!_memberAccess.expression().annotation().type->memberType(member),
@ -784,8 +829,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
solAssert(member == "length", "");
if (member == "length")
{
if (!type.isDynamicallySized())
defineExpression(_memberAccess) << type.length() << "\n";
else
@ -796,14 +841,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
//m_context << Instruction::SWAP1 << Instruction::POP;
break;
case DataLocation::Storage:
setLValue(_memberAccess, make_unique<IRStorageArrayLength>(
m_context.utils(),
m_context.variable(_memberAccess.expression()),
*_memberAccess.annotation().type,
type
));
{
string slot = m_context.variable(_memberAccess.expression());
defineExpression(_memberAccess) <<
m_utils.arrayLengthFunction(type) + "(" + slot + ")\n";
break;
}
case DataLocation::Memory:
defineExpression(_memberAccess) <<
"mload(" <<
@ -811,6 +854,20 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
")\n";
break;
}
}
else if (member == "pop")
{
solAssert(type.location() == DataLocation::Storage, "");
defineExpression(_memberAccess) << m_context.variable(_memberAccess.expression()) << "\n";
}
else if (member == "push")
{
solAssert(type.location() == DataLocation::Storage, "");
defineExpression(_memberAccess) << m_context.variable(_memberAccess.expression()) << "\n";
}
else
solAssert(false, "Invalid array member access.");
break;
}
case Type::Category::FixedBytes:
@ -937,6 +994,11 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
solAssert(false, "Index access only allowed for mappings or arrays.");
}
void IRGeneratorForStatements::endVisit(IndexRangeAccess const&)
{
solUnimplementedAssert(false, "Index range accesses not yet implemented.");
}
void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
@ -1340,7 +1402,7 @@ void IRGeneratorForStatements::generateLoop(
m_code << "for {\n";
if (_initExpression)
_initExpression->accept(*this);
m_code << "} return_flag {\n";
m_code << "} 1 {\n";
if (_loopExpression)
_loopExpression->accept(*this);
m_code << "}\n";
@ -1364,8 +1426,6 @@ void IRGeneratorForStatements::generateLoop(
_body.accept(*this);
m_code << "}\n";
// Bubble up the return condition.
m_code << "if iszero(return_flag) { break }\n";
}
Type const& IRGeneratorForStatements::type(Expression const& _expression)

View File

@ -63,6 +63,7 @@ public:
void endVisit(MemberAccess const& _memberAccess) override;
bool visit(InlineAssembly const& _inlineAsm) override;
void endVisit(IndexAccess const& _indexAccess) override;
void endVisit(IndexRangeAccess const& _indexRangeAccess) override;
void endVisit(Identifier const& _identifier) override;
bool visit(Literal const& _literal) override;

View File

@ -148,39 +148,6 @@ string IRStorageItem::setToZero() const
")\n";
}
IRStorageArrayLength::IRStorageArrayLength(
YulUtilFunctions _utils,
string _slot,
Type const& _type,
ArrayType const& _arrayType
):
IRLValue(std::move(_utils), &_type), m_arrayType(_arrayType), m_slot(move(_slot))
{
solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!");
}
string IRStorageArrayLength::retrieveValue() const
{
return m_utils.arrayLengthFunction(m_arrayType) + "(" + m_slot + ")";
}
string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const
{
solAssert(_type == *m_type, "Different type, but might not be an error.");
return m_utils.resizeDynamicArrayFunction(m_arrayType) +
"(" +
m_slot +
", " +
_value +
")\n";
}
string IRStorageArrayLength::setToZero() const
{
return storeValue("0", *TypeProvider::uint256());
}
IRMemoryItem::IRMemoryItem(
YulUtilFunctions _utils,
std::string _address,

View File

@ -110,30 +110,6 @@ private:
boost::variant<std::string, unsigned> const m_offset;
};
/**
* Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special
* semantics since assignments to it might reduce its length and thus the array's members have to be
* deleted.
*/
class IRStorageArrayLength: public IRLValue
{
public:
IRStorageArrayLength(
YulUtilFunctions _utils,
std::string _slot,
Type const& _type,
ArrayType const& _arrayType
);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
ArrayType const& m_arrayType;
std::string const m_slot;
};
class IRMemoryItem: public IRLValue
{
public:

View File

@ -31,11 +31,12 @@ BMC::BMC(
smt::EncodingContext& _context,
ErrorReporter& _errorReporter,
map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
smt::SMTSolverChoice _enabledSolvers
):
SMTEncoder(_context),
m_outerErrorReporter(_errorReporter),
m_interface(make_shared<smt::SMTPortfolio>(_smtlib2Responses, _enabledSolvers))
m_interface(make_shared<smt::SMTPortfolio>(_smtlib2Responses, _smtCallback, _enabledSolvers))
{
#if defined (HAVE_Z3) || defined (HAVE_CVC4)
if (_enabledSolvers.some())

View File

@ -57,6 +57,7 @@ public:
smt::EncodingContext& _context,
langutil::ErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
smt::SMTSolverChoice _enabledSolvers
);

View File

@ -36,6 +36,7 @@ CHC::CHC(
smt::EncodingContext& _context,
ErrorReporter& _errorReporter,
map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
smt::SMTSolverChoice _enabledSolvers
):
SMTEncoder(_context),
@ -43,16 +44,17 @@ CHC::CHC(
m_interface(
_enabledSolvers.z3 ?
dynamic_pointer_cast<smt::CHCSolverInterface>(make_shared<smt::Z3CHCInterface>()) :
dynamic_pointer_cast<smt::CHCSolverInterface>(make_shared<smt::CHCSmtLib2Interface>(_smtlib2Responses))
dynamic_pointer_cast<smt::CHCSolverInterface>(make_shared<smt::CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback))
),
#else
m_interface(make_shared<smt::CHCSmtLib2Interface>(_smtlib2Responses)),
m_interface(make_shared<smt::CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback)),
#endif
m_outerErrorReporter(_errorReporter),
m_enabledSolvers(_enabledSolvers)
{
(void)_smtlib2Responses;
(void)_enabledSolvers;
(void)_smtCallback;
}
void CHC::analyze(SourceUnit const& _source)
@ -688,12 +690,9 @@ string CHC::predicateName(ASTNode const* _node)
string prefix;
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
{
prefix = funDef->isConstructor() ?
"constructor" :
funDef->isFallback() ?
"fallback" :
"function_" + funDef->name();
prefix += "_";
prefix += TokenTraits::toString(funDef->kind());
if (!funDef->name().empty())
prefix += "_" + funDef->name() + "_";
}
return prefix + to_string(_node->id());
}

View File

@ -34,6 +34,8 @@
#include <libsolidity/formal/CHCSolverInterface.h>
#include <libsolidity/interface/ReadFile.h>
#include <set>
namespace dev
@ -48,6 +50,7 @@ public:
smt::EncodingContext& _context,
langutil::ErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
smt::SMTSolverChoice _enabledSolvers
);

View File

@ -34,9 +34,13 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::smt;
CHCSmtLib2Interface::CHCSmtLib2Interface(map<h256, string> const& _queryResponses):
m_smtlib2(make_shared<SMTLib2Interface>(_queryResponses)),
m_queryResponses(_queryResponses)
CHCSmtLib2Interface::CHCSmtLib2Interface(
map<h256, string> const& _queryResponses,
ReadCallback::Callback const& _smtCallback
):
m_smtlib2(make_shared<SMTLib2Interface>(_queryResponses, _smtCallback)),
m_queryResponses(_queryResponses),
m_smtCallback(_smtCallback)
{
reset();
}
@ -152,9 +156,12 @@ string CHCSmtLib2Interface::querySolver(string const& _input)
h256 inputHash = dev::keccak256(_input);
if (m_queryResponses.count(inputHash))
return m_queryResponses.at(inputHash);
else
if (m_smtCallback)
{
auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input);
if (result.success)
return result.responseOrErrorMessage;
}
m_unhandledQueries.push_back(_input);
return "unknown\n";
}
}

View File

@ -35,7 +35,10 @@ namespace smt
class CHCSmtLib2Interface: public CHCSolverInterface
{
public:
explicit CHCSmtLib2Interface(std::map<h256, std::string> const& _queryResponses);
explicit CHCSmtLib2Interface(
std::map<h256, std::string> const& _queryResponses,
ReadCallback::Callback const& _smtCallback
);
void reset();
@ -68,6 +71,8 @@ private:
std::map<h256, std::string> const& m_queryResponses;
std::vector<std::string> m_unhandledQueries;
ReadCallback::Callback m_smtCallback;
};
}

View File

@ -25,10 +25,11 @@ using namespace dev::solidity;
ModelChecker::ModelChecker(
ErrorReporter& _errorReporter,
map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
smt::SMTSolverChoice _enabledSolvers
):
m_bmc(m_context, _errorReporter, _smtlib2Responses, _enabledSolvers),
m_chc(m_context, _errorReporter, _smtlib2Responses, _enabledSolvers),
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
m_context()
{
}

View File

@ -49,6 +49,7 @@ public:
ModelChecker(
langutil::ErrorReporter& _errorReporter,
std::map<h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback = ReadCallback::Callback(),
smt::SMTSolverChoice _enabledSolvers = smt::SMTSolverChoice::All()
);

View File

@ -70,6 +70,7 @@ bool SMTEncoder::visit(ContractDefinition const& _contract)
for (auto const& function: resolvedFunctions)
if (
function->name() == baseFunction->name() &&
function->kind() == baseFunction->kind() &&
FunctionType(*function).asCallableFunction(false)->
hasEqualParameterTypes(*FunctionType(*baseFunction).asCallableFunction(false))
)
@ -915,6 +916,15 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
m_uninterpretedTerms.insert(&_indexAccess);
}
void SMTEncoder::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
createExpr(_indexRangeAccess);
m_errorReporter.warning(
_indexRangeAccess.location(),
"Assertion checker does not yet implement this expression."
);
}
void SMTEncoder::arrayAssignment()
{
m_arrayAssignmentHappened = true;

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