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: t_ems_solcjs:
docker: docker:
- image: circleci/node:10 - image: ethereum/solidity-buildpack-deps:ubuntu1904
environment: environment:
TERM: xterm TERM: xterm
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
at: /tmp/workspace at: /tmp/workspace
- run:
name: Install test dependencies
command: |
apt-get update
apt-get install -qqy --no-install-recommends nodejs npm cvc4
- run: - run:
name: Test solcjs name: Test solcjs
command: | command: |
node --version node --version
npm --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: t_ems_compile_ext_gnosis:
docker: docker:
@ -716,7 +721,7 @@ workflows:
# basic checks # basic checks
- chk_spelling: *workflow_trigger_on_tags - chk_spelling: *workflow_trigger_on_tags
- chk_coding_style: *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_buglist: *workflow_trigger_on_tags
- chk_proofs: *workflow_trigger_on_tags - chk_proofs: *workflow_trigger_on_tags

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.5.14") set(PROJECT_VERSION "0.6.0")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
include(TestBigEndian) 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) ### 0.5.14 (2019-12-09)
Language Features: Language Features:
@ -42,7 +86,6 @@ Compiler Features:
* TypeChecker: List possible candidates when overload resolution fails. * TypeChecker: List possible candidates when overload resolution fails.
* TypeChecker: Disallow variables of library types. * TypeChecker: Disallow variables of library types.
Bugfixes: Bugfixes:
* Code Generator: Fixed a faulty assert that would wrongly trigger for array sizes exceeding unsigned integer. * 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. * 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 * Code Generator: Fix internal error when trying to convert ``super`` to a different type
### 0.5.12 (2019-10-01) ### 0.5.12 (2019-10-01)
Language Features: Language Features:

View File

@ -10,7 +10,9 @@ include(CheckCXXCompilerFlag)
# #
function(eth_add_cxx_compiler_flag_if_supported FLAG) function(eth_add_cxx_compiler_flag_if_supported FLAG)
# Remove leading - or / from the flag name. # 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}) check_cxx_compiler_flag(${FLAG} ${name})
if(${name}) if(${name})
add_compile_options(${FLAG}) 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 { library OldLibrary {
function someFunction(uint8 a) public returns(bool); function someFunction(uint8 a) public returns(bool);
@ -437,7 +438,7 @@ New version:
function f(uint y) external { function f(uint y) external {
x = y; x = y;
} }
function() payable external {} receive() payable external {}
} }
contract New { 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. 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: A function description is a JSON object with the fields:
- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-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. - ``name``: the name of the function;
- ``inputs``: an array of objects, each of which contains: - ``inputs``: an array of objects, each of which contains:
* ``name``: the name of the parameter. * ``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``. - ``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). - ``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. 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:: .. note::
Sending non-zero Ether to non-payable function will revert the transaction. Sending non-zero Ether to non-payable function will revert the transaction.
@ -539,8 +532,8 @@ As an example, the code
contract Test { contract Test {
struct S { uint a; uint[] b; T[] c; } struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; } struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public; function f(S memory s, T memory t, uint a) public {}
function g() public returns (S memory s, T memory t, uint a); function g() public returns (S memory s, T memory t, uint a) {}
} }
would result in the JSON: 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 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. 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:: .. code::
pragma solidity >=0.4.16 <0.7.0; 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 declares any variables, the scope of these variables is extended into the
body (including the condition and the post-iteration part). 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. The following example computes the sum of an area in memory.
.. code:: .. code::
@ -571,6 +580,11 @@ statement.
If you call a function that returns multiple values, you have to assign 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)``. 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. The following example implements the power function by square-and-multiply.
.. code:: .. code::
@ -763,14 +777,13 @@ Grammar::
AssemblyExpression | AssemblyExpression |
AssemblyLocalDefinition | AssemblyLocalDefinition |
AssemblyAssignment | AssemblyAssignment |
AssemblyStackAssignment |
LabelDefinition |
AssemblyIf | AssemblyIf |
AssemblySwitch | AssemblySwitch |
AssemblyFunctionDefinition | AssemblyFunctionDefinition |
AssemblyFor | AssemblyFor |
'break' | 'break' |
'continue' | 'continue' |
'leave' |
SubAssembly SubAssembly
AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral
AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral
@ -780,8 +793,6 @@ Grammar::
AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression
IdentifierOrList = Identifier | '(' IdentifierList ')' IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)* IdentifierList = Identifier ( ',' Identifier)*
AssemblyStackAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblyIf = 'if' AssemblyExpression AssemblyBlock AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression AssemblyCase* AssemblySwitch = 'switch' AssemblyExpression AssemblyCase*
( 'default' AssemblyBlock )? ( 'default' AssemblyBlock )?

View File

@ -6,38 +6,49 @@
Abstract Contracts 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; pragma solidity >=0.4.0 <0.7.0;
contract Feline { abstract contract Feline {
function utterance() public returns (bytes32); 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; pragma solidity >=0.4.0 <0.7.0;
contract Feline { abstract contract Feline {
function utterance() public returns (bytes32); function utterance() public virtual returns (bytes32);
} }
contract Cat is Feline { 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):: Example of function without implementation (a function declaration)::
function foo(address) external returns (address); 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; 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. 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. 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>`_ 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. 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 A constructor is optional. Only one constructor is allowed, which means
overloading is not supported. 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 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 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. 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. // See the next section for details.
owner = msg.sender; 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 // to `TokenCreator` and assume that the type of
// the calling contract is `TokenCreator`, there is // 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); creator = TokenCreator(msg.sender);
name = _name; name = _name;
} }
function changeName(bytes32 newName) public { function changeName(bytes32 newName) public {
// Only the creator can alter the name -- // Only the creator can alter the name.
// the comparison is possible since contracts // We compare the contract based on its
// are explicitly convertible to addresses. // address which can be retrieved by
// explicit conversion to address.
if (msg.sender == address(creator)) if (msg.sender == address(creator))
name = newName; name = newName;
} }
@ -94,9 +97,9 @@ This means that cyclic creation dependencies are impossible.
returns (OwnedToken tokenAddress) returns (OwnedToken tokenAddress)
{ {
// Create a new `Token` contract and return its address. // Create a new `Token` contract and return its address.
// From the JavaScript side, the return type is // From the JavaScript side, the return type
// `address`, as this is the closest type available in // of this function is `address`, as this is
// the ABI. // the closest type available in the ABI.
return new OwnedToken(name); 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 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, 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 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 change with Serenity). The Log and its event data is not accessible from within
contracts (not even from the contract that created them). contracts (not even from the contract that created them).
It is possible to request a simple payment verification (SPV) for logs, so if It is possible to request a Merkle proof for logs, so if
an external entity supplies a contract with such a verification, it can check 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 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. because the contract can only see the last 256 block hashes.

View File

@ -6,9 +6,14 @@
Function Modifiers Function Modifiers
****************** ******************
Modifiers can be used to easily change the behaviour of functions. For example, Modifiers can be used to change the behaviour of functions in a declarative way.
they can automatically check a condition prior to executing the function. Modifiers are For example,
inheritable properties of contracts and may be overridden by derived contracts. 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 Function Parameters and Return Variables
======================================== ========================================
As in JavaScript, functions may take parameters as input. Unlike in JavaScript Functions take typed parameters as input and may, unlike in many other
and C, functions may also return an arbitrary number of values as output. languages, also return an arbitrary number of values as output.
Function Parameters Function Parameters
------------------- -------------------
@ -21,7 +21,7 @@ Function parameters are declared the same way as variables, and the name of
unused parameters can be omitted. unused parameters can be omitted.
For example, if you want your contract to accept one kind of external call 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; 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 An :ref:`external function<external-function-calls>` cannot accept a
multi-dimensional array as an input multi-dimensional array as an input
parameter. This functionality is possible if you enable the new 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 An :ref:`internal function<external-function-calls>` can accept a
multi-dimensional array without enabling the feature. 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. The names of return variables can be omitted.
Return variables can be used as any other local variable and they 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 You can either explicitly assign to return variables and
then leave the function using ``return;``, then leave the function using ``return;``,
@ -96,7 +97,7 @@ return variables and then using ``return;`` to leave the function.
.. note:: .. note::
You cannot return some types from non-internal functions, notably You cannot return some types from non-internal functions, notably
multi-dimensional dynamic arrays and structs. If you enable the 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 ABIEncoderV2;`` to your source file then more types are available, but
``mapping`` types are still limited to inside a single contract and you ``mapping`` types are still limited to inside a single contract and you
cannot transfer them. 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. 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 .. index:: ! view function, function;view
@ -120,7 +122,7 @@ Functions can be declared ``view`` in which case they promise not to modify the
.. note:: .. note::
If the compiler's EVM target is Byzantium or newer (default) the opcode 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 to stay unmodified as part of the EVM execution. For library ``view`` functions
``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``. ``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 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 not do state-changing operations, but it cannot check that the contract that will be called
at runtime is actually of that type. 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 A contract can have at most one ``receive`` function, declared using
arguments, cannot return anything and has to have ``external`` visibility. ``receive() external payable { ... }``
It is executed on a call to the contract if none of the other (without the ``function`` keyword).
functions match the given function identifier (or if no data was supplied at This function cannot have arguments, cannot return anything and must have
all). ``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
Furthermore, this function is executed whenever the contract receives plain on plain Ether transfers (e.g. via `.send()` or `.transfer()`). If no such
Ether (without data). To receive Ether and add it to the total balance of the contract, the fallback function function exists, but a payable :ref:`fallback function <fallback-function>`
must be marked ``payable``. If no such function exists, the contract cannot receive exists, the fallback function will be called on a plain Ether transfer. If
Ether through regular transactions and throws an exception. 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 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 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 - Calling an external function which consumes a large amount of gas
- Sending Ether - 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:: .. warning::
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) 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 throw an exception, sending back the Ether (this was different
before Solidity v0.4.0). So if you want your contract to receive Ether, 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:: .. 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``. 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:: .. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
any payload supplied with the call. 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, // Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable` // because the fallback function does not have the `payable`
// modifier. // modifier.
function() external { x = 1; } fallback() external { x = 1; }
uint x; 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 // This function is called for plain Ether transfers, i.e.
// to get it back. // for every call with empty calldata.
contract Sink { receive() external payable { x = 2; y = msg.value; }
function() external payable { } uint x;
uint y;
} }
contract Caller { contract Caller {
@ -305,14 +369,27 @@ Like any function, the fallback function can execute complex operations as long
// results in test.x becoming == 1. // results in test.x becoming == 1.
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable // 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 // fallback function.
// intermediate conversion to ``uint160`` to even allow calling ``send`` on it. // It has to be converted to the ``address payable`` type to even allow calling ``send`` on it.
address payable testPayable = address(uint160(address(test))); 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. // the transfer will fail, i.e. this returns false here.
return testPayable.send(2 ether); 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 .. index:: ! overload

View File

@ -6,9 +6,18 @@ Inheritance
Solidity supports multiple inheritance including polymorphism. Solidity supports multiple inheritance including polymorphism.
All function calls are virtual, which means that the most derived function Polymorphism means that a function call (internal and external)
is called, except when the contract name is explicitly given or the always executes the function of the same name (and parameter types)
``super`` keyword is used. 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 When a contract inherits from other contracts, only a single
contract is created on the blockchain, and the code from all the base contracts 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 to functions of base contracts also just use internal function calls
(``super.f(..)`` will use JUMP and not a message call). (``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 The general inheritance system is very similar to
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_, `Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_,
especially concerning multiple inheritance, but there are also 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 // internal functions and state variables. These cannot be
// accessed externally via `this`, though. // accessed externally via `this`, though.
contract Mortal is Owned { contract Mortal is Owned {
function kill() public { function kill() virtual public {
if (msg.sender == owner) selfdestruct(owner); 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 // interface known to the compiler. Note the function
// without body. If a contract does not implement all // without body. If a contract does not implement all
// functions it can only be used as an interface. // functions it can only be used as an interface.
contract Config { abstract contract Config {
function lookup(uint id) public returns (address adr); function lookup(uint id) public virtual returns (address adr);
} }
contract NameReg { abstract contract NameReg {
function register(bytes32 name) public; function register(bytes32 name) public virtual;
function unregister() public; function unregister() public virtual;
} }
@ -74,7 +87,7 @@ Details are given in the following example.
// types of output parameters, that causes an error. // types of output parameters, that causes an error.
// Both local and message-based function calls take these overrides // Both local and message-based function calls take these overrides
// into account. // into account.
function kill() public { function kill() public virtual override {
if (msg.sender == owner) { if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister(); NameReg(config.lookup(1)).unregister();
@ -94,6 +107,7 @@ Details are given in the following example.
if (msg.sender == owner) info = newInfo; if (msg.sender == owner) info = newInfo;
} }
function kill() public override (Mortal, Named) { Named.kill(); }
function get() public view returns(uint r) { return info; } function get() public view returns(uint r) { return info; }
uint info; uint info;
@ -111,26 +125,26 @@ seen in the following example::
} }
contract mortal is owned { contract mortal is owned {
function kill() public { function kill() public virtual {
if (msg.sender == owner) selfdestruct(owner); if (msg.sender == owner) selfdestruct(owner);
} }
} }
contract Base1 is mortal { 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 { 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 { 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 A call to ``Final.kill()`` will call ``Base2.kill`` because we specify it
derived override, but this function will bypass explicitly in the final override, but this function will bypass
``Base1.kill``, basically because it does not even know about ``Base1.kill``. The way around this is to use ``super``::
``Base1``. The way around this is to use ``super``::
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.7.0;
@ -140,21 +154,22 @@ derived override, but this function will bypass
} }
contract mortal is owned { contract mortal is owned {
function kill() public { function kill() virtual public {
if (msg.sender == owner) selfdestruct(owner); if (msg.sender == owner) selfdestruct(owner);
} }
} }
contract Base1 is mortal { 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 { 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 { contract Final is Base1, Base2 {
function kill() public override(Base1, Base2) { super.kill(); }
} }
If ``Base2`` calls a function of ``super``, it does not simply 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 although its type is known. This is similar for ordinary
virtual method lookup. 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 .. index:: ! constructor
.. _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 There is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, with
the usual semantics known from C or JavaScript. 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 Parentheses can *not* be omitted for conditionals, but curly brances can be omitted
around single-statement bodies. around single-statement bodies.
@ -227,7 +231,7 @@ groupings of expressions.
pragma solidity >0.4.23 <0.7.0; pragma solidity >0.4.23 <0.7.0;
contract C { contract C {
uint[] data; uint index;
function f() public pure returns (uint, bool, uint) { function f() public pure returns (uint, bool, uint) {
return (7, true, 2); return (7, true, 2);
@ -240,7 +244,7 @@ groupings of expressions.
// Common trick to swap values -- does not work for non-value storage types. // Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x); (x, y) = (y, x);
// Components can be left out (also for variable declarations). // 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:: .. 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. 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`` ``assert`` and ``require``
-------------------------- --------------------------
@ -488,3 +492,94 @@ In the above example, ``revert("Not enough Ether provided.");`` returns the foll
.. note:: .. note::
There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which 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. 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)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' 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 )* )? ( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}' '{' ContractPart* '}'
@ -62,12 +62,14 @@ StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable' StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}' Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement | Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return | ( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';' Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+
CatchClause = 'catch' Identifier? ParameterList Block
WhileStatement = 'while' '(' Expression ')' Statement WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_' PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement SimpleStatement = VariableDefinition | ExpressionStatement
@ -86,6 +88,7 @@ Expression
= Expression ('++' | '--') = Expression ('++' | '--')
| NewExpression | NewExpression
| IndexAccess | IndexAccess
| IndexRangeAccess
| MemberAccess | MemberAccess
| FunctionCall | FunctionCall
| '(' Expression ')' | '(' Expression ')'
@ -123,6 +126,7 @@ FunctionCallArguments = '{' NameValueList? '}'
NewExpression = 'new' TypeName NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']' IndexAccess = Expression '[' Expression? ']'
IndexRangeAccess = Expression '[' Expression? ':' Expression? ']'
BooleanLiteral = 'true' | 'false' BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)? 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 breaking changes. These releases always have versions of the form
``0.x.0`` or ``x.0.0``. ``0.x.0`` or ``x.0.0``.
The version pragma is used as follows:: The version pragma is used as follows: ``pragma solidity ^0.5.2;``
pragma solidity ^0.5.2;
A source file with the line above does not compile with a compiler earlier than version 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 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 documentation to more safely interact with the contract and verify its source
code. code.
The compiler appends a Swarm hash of the metadata file to the end of the The compiler appends by default the IPFS hash of the metadata file to the end
bytecode (for details, see below) of each contract, so that you can retrieve of the bytecode (for details, see below) of each contract, so that you can
the file in an authenticated way without having to resort to a centralized retrieve the file in an authenticated way without having to resort to a
data provider. 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 You have to publish the metadata file to IPFS, Swarm, or another service so
others can access it. You create the file by using the ``solc --metadata`` 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 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 IPFS and Swarm references to the source code, so you have to upload all source
the metadata file. files and the metadata file.
The metadata file has the following format. The example below is presented in a The metadata file has the following format. The example below is presented in a
human-readable way. Properly formatted metadata should use quotes correctly, human-readable way. Properly formatted metadata should use quotes correctly,
@ -86,7 +88,9 @@ explanatory purposes.
}, },
metadata: { metadata: {
// Reflects the setting used in the input json, defaults to false // 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 // Required for Solidity: File and name of the contract or library this
// metadata is created for. // metadata is created for.
@ -111,8 +115,8 @@ explanatory purposes.
} }
.. warning:: .. warning::
Since the bytecode of the resulting contract contains the metadata hash, any Since the bytecode of the resulting contract contains the metadata hash by default, any
change to the metadata results in a change of the bytecode. This includes 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 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 sources used, a single whitespace change results in different metadata, and
different bytecode. 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, 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 `CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might
contain more keys (see below) and the beginning of that 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 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:: to the end of the deployed bytecode::
0xa2 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> 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding>
0x00 0x32 0x00 0x32
So in order to retrieve the data, the end of the deployed bytecode can be checked 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 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 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:: .. note::
The CBOR mapping can also contain other keys, so it is better to fully 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 For example, if any experimental features that affect code generation
are used, the mapping will also contain ``"experimental": true``. are used, the mapping will also contain ``"experimental": true``.
.. note:: .. note::
The compiler currently uses the "swarm version 1" hash of the metadata, The compiler currently uses the IPFS hash of the metadata by default, but
but this might change in the future, so do not rely on this sequence it may also use the bzzr1 hash or some other hash in the future, so do
to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '1'``. We might also not rely on this sequence to start with ``0xa2 0x64 'i' 'p' 'f' 's'``. We
add additional data to this CBOR structure, so the might also add additional data to this CBOR structure, so the best option
best option is to use a proper CBOR parser. is to use a proper CBOR parser.
Usage for Automatic Interface Generation and NatSpec Usage for Automatic Interface Generation and NatSpec
==================================================== ====================================================
The metadata is used in the following way: A component that wants to interact 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 with a contract (e.g. Mist or any wallet) retrieves the code of the contract,
the Swarm hash of a file which is then retrieved. from that the IPFS/Swarm hash of a file which is then retrieved. That file
That file is JSON-decoded into a structure like above. is JSON-decoded into a structure like above.
The component can then use the ABI to automatically generate a rudimentary The component can then use the ABI to automatically generate a rudimentary
user interface for the contract. user interface for the contract.
@ -177,7 +181,7 @@ For additional information, read :doc:`Ethereum Natural Language Specification (
Usage for Source Code Verification 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. via the link in the metadata file.
The compiler of the correct version (which is checked to be part of the "official" compilers) 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 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 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 important for static analysis tools that operate on bytecode level and
for displaying the current position in the source code inside a debugger 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. Both kinds of source mappings use integer identifiers to refer to source files.
The identifier of a source file is stored in 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. index mentioned above.
The encoding in the source mapping for the bytecode is more complicated: 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 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). 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 ``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. 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 In order to compress these source mappings especially for bytecode, the
following rules are used: 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``, ``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``, ``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``, ``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``,
``unchecked``. ``unchecked``, ``virtual``.
Language Grammar Language Grammar
================ ================

View File

@ -49,7 +49,7 @@ The following example shows a contract and a function using all available tags.
.. code:: solidity .. code:: solidity
pragma solidity ^0.5.6; pragma solidity >=0.5.0 <0.7.0;
/// @title A simulator for trees /// @title A simulator for trees
/// @author Larry A. Gardner /// @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; owner = msg.sender;
} }
function() external { fallback() external {
TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
} }
} }
@ -278,7 +278,8 @@ a ``mapping``.
mapping (uint => uint)[] array; mapping (uint => uint)[] array;
function allocate(uint _newMaps) public { 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 { 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 assembly.rst
miscellaneous.rst miscellaneous.rst
050-breaking-changes.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 Function modifiers can be used to amend the semantics of functions in a declarative way
(see :ref:`modifiers` in the contracts section). (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; pragma solidity >=0.4.22 <0.7.0;

View File

@ -91,18 +91,18 @@ Yes::
pragma solidity >=0.4.0 <0.7.0; pragma solidity >=0.4.0 <0.7.0;
contract A { abstract contract A {
function spam() public pure; function spam() public virtual pure;
function ham() public pure; function ham() public virtual pure;
} }
contract B is A { 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: Functions should be grouped according to their visibility and ordered:
- constructor - constructor
- receive function (if exists)
- fallback function (if exists) - fallback function (if exists)
- external - external
- public - public
@ -290,7 +291,11 @@ Yes::
// ... // ...
} }
function() external { receive() external payable {
// ...
}
fallback() external {
// ... // ...
} }
@ -322,7 +327,10 @@ No::
// External functions // External functions
// ... // ...
function() external { fallback() external {
// ...
}
receive() external payable {
// ... // ...
} }
@ -384,20 +392,29 @@ No::
y = 2; y = 2;
long_variable = 3; long_variable = 3;
Don't include a whitespace in the fallback function: Don't include a whitespace in the receive and fallback functions:
Yes:: Yes::
function() external { receive() external payable {
...
}
fallback() external {
... ...
} }
No:: No::
function () external { receive () external payable {
... ...
} }
fallback () external {
...
}
Control Structures 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. 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``. 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) if (keyIndex > 0)
return true; return true;
else { else {
keyIndex = self.keys.length++; self.keys.push();
keyIndex = self.keys.length;
self.data[key].keyIndex = keyIndex + 1; self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key; self.keys[keyIndex].key = key;
self.size++; 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 x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element 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 delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary / // The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated: // 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>`. 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. 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). Accessing an array past its end causes a failing assertion. Methods ``.push()`` and ``.push(value)`` can be used
method or increase the ``.length`` :ref:`member <array-members>` to add elements. 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 ``bytes`` and ``strings`` as Arrays
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -214,25 +215,18 @@ Array Members
**length**: **length**:
Arrays have a ``length`` member that contains their number of elements. 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. 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**: **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**: **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. 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:: .. note::
Increasing the length of a storage array has constant gas costs because Increasing the length of a storage array by calling ``push()``
storage is assumed to be zero-initialised, while decreasing has constant gas costs because storage is zero-initialised,
the length has at least linear cost (but in most cases worse than linear), 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 because it includes explicitly clearing the removed
elements similar to calling :ref:`delete<delete>` on them. elements similar to calling :ref:`delete<delete>` on them.
@ -292,7 +286,13 @@ Array Members
function changeFlagArraySize(uint newSize) public { function changeFlagArraySize(uint newSize) public {
// if the new size is smaller, removed array elements will be cleared // 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 { function clear() public {
@ -300,7 +300,7 @@ Array Members
delete m_pairsOfFlags; delete m_pairsOfFlags;
delete m_aLotOfIntegers; delete m_aLotOfIntegers;
// identical effect here // identical effect here
m_pairsOfFlags.length = 0; m_pairsOfFlags = new bool[2][](0);
} }
bytes m_byteData; bytes m_byteData;
@ -309,13 +309,15 @@ Array Members
// byte arrays ("bytes") are different as they are stored without padding, // byte arrays ("bytes") are different as they are stored without padding,
// but can be treated identical to "uint8[]" // but can be treated identical to "uint8[]"
m_byteData = data; m_byteData = data;
m_byteData.length += 7; for (uint i = 0; i < 7; i++)
m_byteData.push();
m_byteData[3] = 0x08; m_byteData[3] = 0x08;
delete m_byteData[2]; delete m_byteData[2];
} }
function addFlag(bool[2] memory flag) public returns (uint) { 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) { 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
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
Exponentiation is only available for unsigned types. Please take care that the types Exponentiation is only available for unsigned types in the exponent. The resulting type
you are using are large enough to hold the result and prepare for potential wrapping behaviour. 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::
Note that ``0**0`` is defined by the EVM as ``1``. 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: Type conversions:
Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable``
not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``). must be explicit via ``payable(<address>)``.
:ref:`Address literals<address_literals>` can be implicitly converted to ``address payable``. :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 Explicit conversions to and from ``address`` are allowed for integers, integer literals, ``bytes20`` and contract types with the following
caveat: 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. 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``. 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. 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:: .. note::
It might very well be that you do not need to care about the distinction between ``address`` 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, 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. reverts on failure.
.. note:: .. 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`` * ``send``
@ -309,10 +312,12 @@ Every :ref:`contract<contracts>` defines its own type.
You can implicitly convert contracts to contracts they inherit from. You can implicitly convert contracts to contracts they inherit from.
Contracts can be explicitly converted to and from the ``address`` type. Contracts can be explicitly converted to and from the ``address`` type.
Explicit conversion to and from the ``address payable`` type Explicit conversion to and from the ``address payable`` type is only possible
is only possible if the contract type has a payable fallback function. if the contract type has a payable fallback function. The conversion is still
The conversion is still performed using ``address(x)`` and not performed using ``address(x)``. If the contract type does not have a payable
using ``address payable(x)``. You can find more information in the section about 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>`. the :ref:`address type<address>`.
.. note:: .. note::
@ -498,7 +503,14 @@ terminate the string literal. Newline only terminates the string literal if it i
Hexadecimal Literals 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. 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: 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 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 parameter types are identical, their return types are identical,
their internal/external property is identical and the state mutability of ``A`` 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: 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>` * ``.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. * ``.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. * ``.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 { contract Example {
function f() public payable returns (bytes4) { function f() public payable returns (bytes4) {
assert(this.f.address == address(this));
return this.f.selector; 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. 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. 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``. 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 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, // Affects type checking and code generation. Can be homestead,
// tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin // tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin
"evmVersion": "byzantium", "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 settings (optional)
"metadata": { "metadata": {
// Use only literal content and not URLs (false by default) // 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, // Addresses of the libraries. If not all libraries are given here,
// it can result in unlinked objects whose output data is different. // it can result in unlinked objects whose output data is different.

View File

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

View File

@ -23,6 +23,7 @@
#pragma once #pragma once
#include <iterator>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
#include <vector> #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)); std::move(_b.begin(), _b.end(), std::back_inserter(_a));
return _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 /// 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()); _a.insert(_b.begin(), _b.end());
return _a; return _a;
} }
/// Concatenate the contents of a container onto a set, move variant. /// 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) for (auto&& x: _b)
_a.insert(std::move(x)); _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); ret += std::forward<U>(_b);
return ret; return ret;
} }
/// Remove one set from another one.
template <class T> /// Remove the elements of a container from a set.
inline std::set<T>& operator-=(std::set<T>& _a, std::set<T> const& _b) 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) for (auto const& x: _b)
_a.erase(x); _a.erase(x);
@ -109,6 +141,21 @@ inline std::set<T>& operator-=(std::set<T>& _a, std::set<T> const& _b)
namespace dev 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. // String conversion functions, mainly to/from hex/nibble/byte representations.
enum class WhenError 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); 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 /// 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 /// elements. If that function returns a vector, the element is replaced by
/// the returned vector. During the iteration, the original vector is only valid /// 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); m_items.emplace_back(_i);
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty()) if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty())
m_items.back().setLocation(m_currentSourceLocation); m_items.back().setLocation(m_currentSourceLocation);
m_items.back().m_modifierDepth = m_currentModifierDepth;
return back(); return back();
} }

View File

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

View File

@ -146,6 +146,8 @@ public:
std::string toAssemblyText() const; std::string toAssemblyText() const;
size_t m_modifierDepth = 0;
private: private:
AssemblyItemType m_type; AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation 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::NoError: return "No error.";
case ScannerError::IllegalToken: return "Invalid token."; 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::IllegalHexDigit: return "Hexadecimal digit missing or invalid.";
case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator."; case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator.";
case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence."; case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence.";
@ -784,13 +784,25 @@ Token Scanner::scanHexString()
char const quote = m_char; char const quote = m_char;
advance(); // consume quote advance(); // consume quote
LiteralScope literal(this, LITERAL_TYPE_STRING); LiteralScope literal(this, LITERAL_TYPE_STRING);
bool allowUnderscore = false;
while (m_char != quote && !isSourcePastEndOfInput()) while (m_char != quote && !isSourcePastEndOfInput())
{ {
char c = m_char; char c = m_char;
if (!scanHexByte(c))
// can only return false if hex-byte is incomplete (only one hex digit instead of two) 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); return setError(ScannerError::IllegalHexString);
addLiteralChar(c);
} }
if (m_char != quote) 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) + "." "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_token = _baseType;
m_firstNumber = _first; m_firstNumber = _first;
m_secondNumber = _second; m_secondNumber = _second;

View File

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

View File

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

View File

@ -34,10 +34,18 @@
extern "C" { extern "C" {
#endif #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. /// @param _context The readContext passed to solidity_compile. Can be NULL.
typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); /// @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. /// 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 /// 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. /// a "Standard Output JSON". Both are to be UTF-8 encoded.
/// ///
/// The pointer returned must not be freed by the caller. /// @param _input The input JSON to process.
char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) SOLC_NOEXCEPT; /// @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. /// Frees up any allocated memory.
/// ///

View File

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

View File

@ -26,6 +26,7 @@
#include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/analysis/TypeChecker.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string/predicate.hpp>
using namespace std; using namespace std;
@ -33,20 +34,113 @@ using namespace dev;
using namespace langutil; using namespace langutil;
using namespace dev::solidity; 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) bool ContractLevelChecker::check(ContractDefinition const& _contract)
{ {
checkDuplicateFunctions(_contract); checkDuplicateFunctions(_contract);
checkDuplicateEvents(_contract); checkDuplicateEvents(_contract);
checkIllegalOverrides(_contract); checkIllegalOverrides(_contract);
checkAbstractFunctions(_contract); checkAmbiguousOverrides(_contract);
checkBaseConstructorArguments(_contract); checkBaseConstructorArguments(_contract);
checkConstructor(_contract); checkAbstractFunctions(_contract);
checkFallbackFunction(_contract);
checkExternalTypeClashes(_contract); checkExternalTypeClashes(_contract);
checkHashCollisions(_contract); checkHashCollisions(_contract);
checkLibraryRequirements(_contract); checkLibraryRequirements(_contract);
checkBaseABICompatibility(_contract); checkBaseABICompatibility(_contract);
checkPayableFallbackWithoutReceive(_contract);
return Error::containsOnlyWarnings(m_errorReporter.errors()); return Error::containsOnlyWarnings(m_errorReporter.errors());
} }
@ -58,6 +152,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
map<string, vector<FunctionDefinition const*>> functions; map<string, vector<FunctionDefinition const*>> functions;
FunctionDefinition const* constructor = nullptr; FunctionDefinition const* constructor = nullptr;
FunctionDefinition const* fallback = nullptr; FunctionDefinition const* fallback = nullptr;
FunctionDefinition const* receive = nullptr;
for (FunctionDefinition const* function: _contract.definedFunctions()) for (FunctionDefinition const* function: _contract.definedFunctions())
if (function->isConstructor()) if (function->isConstructor())
{ {
@ -79,6 +174,16 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
); );
fallback = function; 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 else
{ {
solAssert(!function->name().empty(), ""); solAssert(!function->name().empty(), "");
@ -111,9 +216,7 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
SecondarySourceLocation ssl; SecondarySourceLocation ssl;
for (size_t j = i + 1; j < overloads.size(); ++j) for (size_t j = i + 1; j < overloads.size(); ++j)
if (FunctionType(*overloads[i]).asCallableFunction(false)->hasEqualParameterTypes( if (hasEqualNameAndParameters(*overloads[i], *overloads[j]))
*FunctionType(*overloads[j]).asCallableFunction(false))
)
{ {
ssl.append("Other declaration is here:", overloads[j]->location()); ssl.append("Other declaration is here:", overloads[j]->location());
reported.insert(j); reported.insert(j);
@ -135,82 +238,215 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
void ContractLevelChecker::checkIllegalOverrides(ContractDefinition const& _contract) 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 FunctionMultiSet const& inheritedFuncs = inheritedFunctions(_contract);
// into the types ModifierMultiSet const& inheritedMods = inheritedModifiers(_contract);
map<string, vector<FunctionDefinition const*>> functions;
map<string, ModifierDefinition const*> modifiers;
// We search from derived to base, so the stored item causes the error. for (auto const* stateVar: _contract.stateVariables())
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
{ {
for (FunctionDefinition const* function: contract->definedFunctions()) if (!stateVar->isPublic())
{ continue;
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.");
for (FunctionDefinition const* overriding: functions[name]) bool found = false;
checkFunctionOverride(*overriding, *function); for (
auto it = find_if(inheritedFuncs.begin(), inheritedFuncs.end(), MatchByName{stateVar->name()});
functions[name].push_back(function); it != inheritedFuncs.end();
} it = find_if(++it, inheritedFuncs.end(), MatchByName{stateVar->name()})
for (ModifierDefinition const* modifier: contract->functionModifiers()) )
{ {
string const& name = modifier->name(); if (!hasEqualNameAndParameters(*stateVar, **it))
ModifierDefinition const*& override = modifiers[name]; continue;
if (!override)
override = modifier; if ((*it)->visibility() != Declaration::Visibility::External)
else if (ModifierType(*override) != ModifierType(*modifier)) overrideError(*stateVar, **it, "Public state variables can only override functions with external visibility.");
m_errorReporter.typeError(override->location(), "Override changes modifier signature."); else
if (!functions[name].empty()) checkOverride(*stateVar, **it);
m_errorReporter.typeError(override->location(), "Override changes modifier to function.");
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;
if (contains_if(inheritedMods, MatchByName{function->name()}))
m_errorReporter.typeError(function->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); static_assert(
FunctionTypePointer superType = FunctionType(_super).asCallableFunction(false); 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)) static_assert(
return; std::is_same<FunctionDefinition, U>::value ||
if (!functionType->hasEqualReturnTypes(*superType)) std::is_same<ModifierDefinition, U>::value,
overrideError(_function, _super, "Overriding function return types differ."); "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) string overridingName;
_function.annotation().superFunction = &_super; 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. // Visibility change from external to public is fine.
// Any other change is disallowed. // Any other change is disallowed.
if (!( if (!(
_super.visibility() == FunctionDefinition::Visibility::External && _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.");
}
// 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(
_overriding,
_super,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
stateMutabilityToString(_overriding.stateMutability()) +
"\"."
);
if (!_overriding.isImplemented() && _super.isImplemented())
overrideError(
_overriding,
_super,
"Overriding an implemented function with an unimplemented function is not allowed."
);
}
} }
if (_function.stateMutability() != _super.stateMutability())
overrideError(
_function,
_super,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
} }
void ContractLevelChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message) 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(Declaration const& _overriding, Declaration const& _super, string _message, string _secondaryMsg)
{ {
m_errorReporter.typeError( m_errorReporter.typeError(
function.location(), _overriding.location(),
SecondarySourceLocation().append("Overridden function is here:", super.location()), SecondarySourceLocation().append(_secondaryMsg, _super.location()),
message _message
); );
} }
@ -230,11 +466,6 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
}); });
if (it == overloads.end()) if (it == overloads.end())
overloads.emplace_back(_type, _implemented); 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) else if (_implemented)
it->second = true; 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. // 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& it: functions)
for (auto const& funAndFlag: it.second) for (auto const& funAndFlag: it.second)
if (!funAndFlag.second) if (!funAndFlag.second)
@ -266,6 +499,32 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
_contract.annotation().unimplementedFunctions.push_back(function); _contract.annotation().unimplementedFunctions.push_back(function);
break; 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) void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract)
{ {
map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations; 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 #pragma once
#include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/ASTForward.h>
#include <liblangutil/SourceLocation.h>
#include <map> #include <map>
#include <functional>
#include <set>
namespace langutil namespace langutil
{ {
@ -41,6 +44,7 @@ namespace solidity
class ContractLevelChecker class ContractLevelChecker
{ {
public: public:
/// @param _errorReporter provides the error logging functionality. /// @param _errorReporter provides the error logging functionality.
explicit ContractLevelChecker(langutil::ErrorReporter& _errorReporter): explicit ContractLevelChecker(langutil::ErrorReporter& _errorReporter):
m_errorReporter(_errorReporter) m_errorReporter(_errorReporter)
@ -51,6 +55,22 @@ public:
bool check(ContractDefinition const& _contract); bool check(ContractDefinition const& _contract);
private: 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 /// Checks that two functions defined in this contract with the same name have different
/// arguments and that there is at most one constructor. /// arguments and that there is at most one constructor.
void checkDuplicateFunctions(ContractDefinition const& _contract); void checkDuplicateFunctions(ContractDefinition const& _contract);
@ -58,19 +78,33 @@ private:
template <class T> template <class T>
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message); void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
void checkIllegalOverrides(ContractDefinition const& _contract); void checkIllegalOverrides(ContractDefinition const& _contract);
/// Reports a type error with an appropriate message if overridden function signature differs. /// Performs various checks related to @a _overriding overriding @a _super like
/// Also stores the direct super function in the AST annotations. /// different return type, invalid visibility change, etc.
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); /// Works on functions, modifiers and public state variables.
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); /// 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); 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 checkBaseConstructorArguments(ContractDefinition const& _contract);
void annotateBaseConstructorArguments( void annotateBaseConstructorArguments(
ContractDefinition const& _currentContract, ContractDefinition const& _currentContract,
FunctionDefinition const* _baseConstructor, FunctionDefinition const* _baseConstructor,
ASTNode const* _argumentNode ASTNode const* _argumentNode
); );
void checkConstructor(ContractDefinition const& _contract);
void checkFallbackFunction(ContractDefinition const& _contract);
/// Checks that different functions with external visibility end up having different /// Checks that different functions with external visibility end up having different
/// external argument types (i.e. different signature). /// external argument types (i.e. different signature).
void checkExternalTypeClashes(ContractDefinition const& _contract); void checkExternalTypeClashes(ContractDefinition const& _contract);
@ -80,8 +114,35 @@ private:
void checkLibraryRequirements(ContractDefinition const& _contract); void checkLibraryRequirements(ContractDefinition const& _contract);
/// Checks base contracts for ABI compatibility /// Checks base contracts for ABI compatibility
void checkBaseABICompatibility(ContractDefinition const& _contract); 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; 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; 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) bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
{ {
solAssert(!!m_currentNode, ""); solAssert(!!m_currentNode, "");
@ -384,7 +396,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
_variableDeclaration.value().get() _variableDeclaration.value().get()
); );
// Function arguments are considered to be immediately assigned as well (they are "externally assigned"). // 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( m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration, _variableDeclaration,
VariableOccurrence::Kind::Assignment, VariableOccurrence::Kind::Assignment,

View File

@ -45,6 +45,7 @@ private:
// Visits for constructing the control flow. // Visits for constructing the control flow.
bool visit(BinaryOperation const& _operation) override; bool visit(BinaryOperation const& _operation) override;
bool visit(Conditional const& _conditional) override; bool visit(Conditional const& _conditional) override;
bool visit(TryStatement const& _tryStatement) override;
bool visit(IfStatement const& _ifStatement) override; bool visit(IfStatement const& _ifStatement) override;
bool visit(ForStatement const& _forStatement) override; bool visit(ForStatement const& _forStatement) override;
bool visit(WhileStatement const& _whileStatement) override; bool visit(WhileStatement const& _whileStatement) override;
@ -98,12 +99,27 @@ private:
return result; 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. /// 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. /// If @a _endNode is nullptr, a new node is creates and used as end node.
/// Sets the merge destination as current node. /// Sets the merge destination as current node.
/// Note: @a _endNode may be one of the nodes in @a _nodes. /// Note: @a _endNode may be one of the nodes in @a _nodes.
template<size_t n> template<typename C>
void mergeFlow(std::array<CFGNode*, n> const& _nodes, CFGNode* _endNode = nullptr) void mergeFlow(C const& _nodes, CFGNode* _endNode = nullptr)
{ {
CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode; CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode;
for (auto& node: _nodes) for (auto& node: _nodes)

View File

@ -129,9 +129,36 @@ void DocStringAnalyser::parseDocStrings(
m_errorOccured = true; m_errorOccured = true;
_annotation.docTags = parser.tags(); _annotation.docTags = parser.tags();
} }
size_t returnTagsVisited = 0;
for (auto const& docTag: _annotation.docTags) for (auto const& docTag: _annotation.docTags)
{
if (!_validTags.count(docTag.first)) 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) void DocStringAnalyser::appendError(string const& _description)

View File

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

View File

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

View File

@ -60,6 +60,27 @@ void PostTypeChecker::endVisit(ContractDefinition const&)
m_constVariableDependencies.clear(); 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) bool PostTypeChecker::visit(VariableDeclaration const& _variable)
{ {
solAssert(!m_currentConstVariable, ""); 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: * 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 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) * @TODO factor out each use-case into an individual class (but do the traversal only once)
*/ */
class PostTypeChecker: private ASTConstVisitor class PostTypeChecker: private ASTConstVisitor
@ -53,6 +54,7 @@ private:
bool visit(ContractDefinition const& _contract) override; bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override; void endVisit(ContractDefinition const& _contract) override;
void endVisit(OverrideSpecifier const& _overrideSpecifier) override;
bool visit(VariableDeclaration const& _variable) override; bool visit(VariableDeclaration const& _variable) override;
void endVisit(VariableDeclaration const& _variable) override; void endVisit(VariableDeclaration const& _variable) override;

View File

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

View File

@ -94,6 +94,9 @@ private:
/// Adds a new error to the list of errors. /// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, std::string const& _description); 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. /// 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); 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) for (auto const& var: m_localVarUseCount)
if (var.second == 0) if (var.second == 0)
{ {
if (var.first.second->isCallableParameter()) if (var.first.second->isCallableOrCatchParameter())
m_errorReporter.warning( m_errorReporter.warning(
var.first.second->location(), 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 else
m_errorReporter.warning(var.first.second->location(), "Unused local variable."); 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); auto feature = ExperimentalFeatureNames.at(literal);
m_sourceUnit->annotation().experimentalFeatures.insert(feature); 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."); 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()) if (_function.noVisibilitySpecified())
{ {
string suggestedVisibility = _function.isFallback() || m_isInterface ? "external" : "public"; string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public";
m_errorReporter.syntaxError( m_errorReporter.syntaxError(
_function.location(), _function.location(),
"No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?" "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()) for (auto const& n: _contract.subNodes())
n->accept(*this); n->accept(*this);
return false; return false;
} }
@ -137,19 +136,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
); );
if (arguments.size() >= 1) if (arguments.size() >= 1)
{ if (
BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()); !type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) &&
!type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata())
if (!result) )
m_errorReporter.typeErrorConcatenateDescriptions( m_errorReporter.typeError(
arguments.front()->location(), arguments.front()->location(),
"Invalid type for argument in function call. " "The first argument to \"abi.decode\" must be implicitly convertible to "
"Invalid implicit conversion from " + "bytes memory or bytes calldata, but is of type " +
type(*arguments.front())->toString() + type(*arguments.front())->toString() +
" to bytes memory requested.", "."
result.message()
); );
}
if (arguments.size() < 2) if (arguments.size() < 2)
return {}; return {};
@ -330,11 +327,20 @@ bool TypeChecker::visit(StructDefinition const& _struct)
bool TypeChecker::visit(FunctionDefinition const& _function) bool TypeChecker::visit(FunctionDefinition const& _function)
{ {
bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library; 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 (_function.isPayable())
{ {
if (isLibraryFunction) if (isLibraryFunction)
m_errorReporter.typeError(_function.location(), "Library functions cannot be payable."); 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."); m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable.");
} }
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) { auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) {
@ -426,8 +432,19 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
_function.body().accept(*this); _function.body().accept(*this);
else if (_function.isConstructor()) else if (_function.isConstructor())
m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared."); m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared.");
else if (isLibraryFunction && _function.visibility() <= FunctionDefinition::Visibility::Internal) else if (isLibraryFunction)
m_errorReporter.typeError(_function.location(), "Internal library function must be implemented if declared."); 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; return false;
} }
@ -438,7 +455,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
// * or inside of a struct definition. // * or inside of a struct definition.
if ( if (
m_scope->isInterface() m_scope->isInterface()
&& !_variable.isCallableParameter() && !_variable.isCallableOrCatchParameter()
&& !m_insideStruct && !m_insideStruct
) )
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces."); m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
@ -743,7 +760,6 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
yul::AsmAnalyzer analyzer( yul::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo, *_inlineAssembly.annotation().analysisInfo,
m_errorReporter, m_errorReporter,
Error::Type::SyntaxError,
_inlineAssembly.dialect(), _inlineAssembly.dialect(),
identifierAccess identifierAccess
); );
@ -761,6 +777,133 @@ bool TypeChecker::visit(IfStatement const& _ifStatement)
return false; 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) bool TypeChecker::visit(WhileStatement const& _whileStatement)
{ {
expectType(_whileStatement.condition(), *TypeProvider::boolean()); 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 " "might overflow. Silence this warning by converting the literal to the "
"expected type." "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() variableDeclaration->location()
); );
m_errorReporter.typeError( m_errorReporter.typeError(
_functionCall.location(), ssl, _functionCall.location(),
ssl,
"Explicit type conversion not allowed from non-payable \"address\" to \"" + "Explicit type conversion not allowed from non-payable \"address\" to \"" +
resultType->toString() + resultType->toString() +
"\", which has a payable fallback function." "\", 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 else
m_errorReporter.typeError( m_errorReporter.typeError(
_functionCall.location(), _functionCall.location(),
@ -1510,11 +1686,14 @@ 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()); {
resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address(); bool payable = false;
} if (argType->category() != Type::Category::Address)
payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress());
resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address();
}
} }
return resultType; return resultType;
} }
@ -1550,6 +1729,71 @@ void TypeChecker::typeCheckFunctionCall(
typeCheckFunctionGeneralChecks(_functionCall, _functionType); 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( void TypeChecker::typeCheckABIEncodeFunctions(
FunctionCall const& _functionCall, FunctionCall const& _functionCall,
FunctionTypePointer _functionType FunctionTypePointer _functionType
@ -1871,7 +2115,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation(); FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
FunctionTypePointer functionType = nullptr; 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()) switch (expressionType->category())
{ {
case Type::Category::Function: case Type::Category::Function:
@ -1885,6 +2129,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
functionType && functionType &&
functionType->isPure(); functionType->isPure();
if (
functionType->kind() == FunctionType::Kind::ArrayPush ||
functionType->kind() == FunctionType::Kind::ByteArrayPush
)
funcCallAnno.isLValue = functionType->parameterTypes().empty();
break; break;
case Type::Category::TypeType: case Type::Category::TypeType:
@ -1992,21 +2242,10 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
if (contract->isInterface()) if (contract->isInterface())
m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface."); 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()) if (!contract->constructorIsPublic())
m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); 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, ""); solAssert(!!m_scope, "");
m_scope->annotation().contractDependencies.insert(contract); 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)) if (auto const* structType = dynamic_cast<StructType const*>(exprType))
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
else if (exprType->category() == Type::Category::Array) else if (exprType->category() == Type::Category::Array)
{ annotation.isLValue = false;
auto const& arrayType(dynamic_cast<ArrayType const&>(*exprType));
annotation.isLValue = (
memberName == "length" &&
arrayType.location() == DataLocation::Storage &&
arrayType.isDynamicallySized()
);
}
else if (exprType->category() == Type::Category::FixedBytes) else if (exprType->category() == Type::Category::FixedBytes)
annotation.isLValue = false; annotation.isLValue = false;
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType)) 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(); Expression const* index = _access.indexExpression();
switch (baseType->category()) 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: case Type::Category::Array:
{ {
ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType); ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType);
@ -2321,6 +2561,50 @@ bool TypeChecker::visit(IndexAccess const& _access)
return false; 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) bool TypeChecker::visit(Identifier const& _identifier)
{ {
IdentifierAnnotation& annotation = _identifier.annotation(); IdentifierAnnotation& annotation = _identifier.annotation();
@ -2433,7 +2717,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) 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; _expr.annotation().isPure = true;
} }
@ -2581,17 +2865,9 @@ void TypeChecker::requireLValue(Expression const& _expression)
if (structType->dataStoredIn(DataLocation::CallData)) if (structType->dataStoredIn(DataLocation::CallData))
return "Calldata structs are read-only."; 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") if (memberAccess->memberName() == "length")
switch (arrayType->location()) return "Member \"length\" is read-only and cannot be used to resize arrays.";
{
case DataLocation::Memory:
return "Memory arrays cannot be resized.";
case DataLocation::CallData:
return "Calldata arrays cannot be resized.";
case DataLocation::Storage:
break;
}
} }
if (auto identifier = dynamic_cast<Identifier const*>(&_expression)) if (auto identifier = dynamic_cast<Identifier const*>(&_expression))

View File

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

View File

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

View File

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

View File

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

View File

@ -388,13 +388,15 @@ public:
ASTPointer<ASTString> const& _documentation, ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<ASTNode>> const& _subNodes, std::vector<ASTPointer<ASTNode>> const& _subNodes,
ContractKind _contractKind = ContractKind::Contract ContractKind _contractKind = ContractKind::Contract,
bool _abstract = false
): ):
Declaration(_location, _name), Declaration(_location, _name),
Documented(_documentation), Documented(_documentation),
m_baseContracts(_baseContracts), m_baseContracts(_baseContracts),
m_subNodes(_subNodes), m_subNodes(_subNodes),
m_contractKind(_contractKind) m_contractKind(_contractKind),
m_abstract(_abstract)
{} {}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
@ -433,6 +435,9 @@ public:
/// Returns the fallback function or nullptr if no fallback function was specified. /// Returns the fallback function or nullptr if no fallback function was specified.
FunctionDefinition const* fallbackFunction() const; 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(); } std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
TypePointer type() const override; TypePointer type() const override;
@ -441,10 +446,13 @@ public:
ContractKind contractKind() const { return m_contractKind; } ContractKind contractKind() const { return m_contractKind; }
bool abstract() const { return m_abstract; }
private: private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts; std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<ASTNode>> m_subNodes; std::vector<ASTPointer<ASTNode>> m_subNodes;
ContractKind m_contractKind; 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<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents; 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 * None of the parameters is allowed to contain mappings (not even recursively
* inside structs). * inside structs).
*/ */
@ -596,22 +604,59 @@ public:
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
Declaration::Visibility _visibility, Declaration::Visibility _visibility,
ASTPointer<ParameterList> const& _parameters, ASTPointer<ParameterList> const& _parameters,
bool _isVirtual = false,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>() ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>()
): ):
Declaration(_location, _name, _visibility), Declaration(_location, _name, _visibility),
m_parameters(_parameters), 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(); } 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(); } std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
ParameterList const& parameterList() const { return *m_parameters; } ParameterList const& parameterList() const { return *m_parameters; }
ASTPointer<ParameterList> const& returnParameterList() const { return m_returnParameters; } 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: protected:
ASTPointer<ParameterList> m_parameters; ASTPointer<ParameterList> m_parameters;
ASTPointer<OverrideSpecifier> m_overrides;
ASTPointer<ParameterList> m_returnParameters; 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 class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional
@ -622,36 +667,43 @@ public:
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
Declaration::Visibility _visibility, Declaration::Visibility _visibility,
StateMutability _stateMutability, StateMutability _stateMutability,
bool _isConstructor, Token _kind,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<ASTString> const& _documentation, ASTPointer<ASTString> const& _documentation,
ASTPointer<ParameterList> const& _parameters, ASTPointer<ParameterList> const& _parameters,
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers, std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
ASTPointer<ParameterList> const& _returnParameters, ASTPointer<ParameterList> const& _returnParameters,
ASTPointer<Block> const& _body ASTPointer<Block> const& _body
): ):
CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters), CallableDeclaration(_location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters),
Documented(_documentation), Documented(_documentation),
ImplementationOptional(_body != nullptr), ImplementationOptional(_body != nullptr),
m_stateMutability(_stateMutability), m_stateMutability(_stateMutability),
m_isConstructor(_isConstructor), m_kind(_kind),
m_functionModifiers(_modifiers), m_functionModifiers(_modifiers),
m_body(_body) m_body(_body)
{} {
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
StateMutability stateMutability() const { return m_stateMutability; } StateMutability stateMutability() const { return m_stateMutability; }
bool isConstructor() const { return m_isConstructor; } bool isOrdinary() const { return m_kind == Token::Function; }
bool isFallback() const { return !m_isConstructor && name().empty(); } 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; } bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; } std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
Block const& body() const { solAssert(m_body, ""); return *m_body; } Block const& body() const { solAssert(m_body, ""); return *m_body; }
bool isVisibleInContract() const override 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 /// @returns the external signature of the function
/// That consists of the name of the function followed by the types of the /// That consists of the name of the function followed by the types of the
@ -668,9 +720,15 @@ public:
FunctionDefinitionAnnotation& annotation() const override; FunctionDefinitionAnnotation& annotation() const override;
bool virtualSemantics() const override
{
return
CallableDeclaration::virtualSemantics() ||
annotation().contract->isInterface();
}
private: private:
StateMutability m_stateMutability; StateMutability m_stateMutability;
bool m_isConstructor; Token const m_kind;
std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers; std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers;
ASTPointer<Block> m_body; ASTPointer<Block> m_body;
}; };
@ -693,6 +751,7 @@ public:
bool _isStateVar = false, bool _isStateVar = false,
bool _isIndexed = false, bool _isIndexed = false,
bool _isConstant = false, bool _isConstant = false,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
Location _referenceLocation = Location::Unspecified Location _referenceLocation = Location::Unspecified
): ):
Declaration(_sourceLocation, _name, _visibility), Declaration(_sourceLocation, _name, _visibility),
@ -701,8 +760,10 @@ public:
m_isStateVariable(_isStateVar), m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed), m_isIndexed(_isIndexed),
m_isConstant(_isConstant), m_isConstant(_isConstant),
m_overrides(_overrides),
m_location(_referenceLocation) {} m_location(_referenceLocation) {}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
@ -716,9 +777,12 @@ public:
/// (or function type name or event) or declared inside a function body. /// (or function type name or event) or declared inside a function body.
bool isLocalVariable() const; bool isLocalVariable() const;
/// @returns true if this variable is a parameter or return parameter of a function. /// @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. /// @returns true if this variable is a return parameter of a function.
bool isReturnParameter() const; 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. /// @returns true if this variable is a local variable or return parameter.
bool isLocalOrReturn() const; bool isLocalOrReturn() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function. /// @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 isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; } bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; } bool isConstant() const { return m_isConstant; }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
Location referenceLocation() const { return m_location; } Location referenceLocation() const { return m_location; }
/// @returns a set of allowed storage locations for the variable. /// @returns a set of allowed storage locations for the variable.
std::set<Location> allowedDataLocations() const; std::set<Location> allowedDataLocations() const;
@ -760,10 +825,11 @@ private:
/// Initially assigned value, can be missing. For local variables, this is stored inside /// Initially assigned value, can be missing. For local variables, this is stored inside
/// VariableDeclarationStatement and not here. /// VariableDeclarationStatement and not here.
ASTPointer<Expression> m_value; ASTPointer<Expression> m_value;
bool m_isStateVariable; ///< Whether or not this is a contract state variable bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
bool m_isIndexed; ///< Whether this is an indexed variable (used by events). bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
bool m_isConstant; ///< Whether the variable is a compile-time constant. bool m_isConstant = false; ///< Whether the variable is a compile-time constant.
Location m_location; ///< Location of the variable if it is of reference type. 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& _name,
ASTPointer<ASTString> const& _documentation, ASTPointer<ASTString> const& _documentation,
ASTPointer<ParameterList> const& _parameters, ASTPointer<ParameterList> const& _parameters,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<Block> const& _body ASTPointer<Block> const& _body
): ):
CallableDeclaration(_location, _name, Visibility::Internal, _parameters), CallableDeclaration(_location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
Documented(_documentation), Documented(_documentation),
m_body(_body) m_body(_body)
{ {
@ -1150,6 +1218,76 @@ private:
ASTPointer<Statement> m_falseBody; ///< "else" part, optional 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). * Statement in which a break statement is legal (abstract class).
*/ */
@ -1618,6 +1756,32 @@ private:
ASTPointer<Expression> m_index; 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 * Primary expression, i.e. an expression that cannot be divided any further. Examples are literals
* or variable references. * or variable references.
@ -1658,16 +1822,21 @@ private:
class ElementaryTypeNameExpression: public PrimaryExpression class ElementaryTypeNameExpression: public PrimaryExpression
{ {
public: public:
ElementaryTypeNameExpression(SourceLocation const& _location, ElementaryTypeNameToken const& _type): ElementaryTypeNameExpression(
PrimaryExpression(_location), m_typeToken(_type) SourceLocation const& _location,
{} ASTPointer<ElementaryTypeName> const& _type
):
PrimaryExpression(_location),
m_type(_type)
{
}
void accept(ASTVisitor& _visitor) override; void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override; void accept(ASTConstVisitor& _visitor) const override;
ElementaryTypeNameToken const& typeName() const { return m_typeToken; } ElementaryTypeName const& type() const { return *m_type; }
private: 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; 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 /// The set of functions/modifiers/events this callable overrides.
/// in the linearized inheritance hierarchy. std::set<CallableDeclaration const*> baseFunctions;
FunctionDefinition const* superFunction = nullptr;
}; };
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 struct FunctionCallAnnotation: ExpressionAnnotation
{ {
FunctionCallKind kind = FunctionCallKind::Unset; 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 ImportDirective;
class Declaration; class Declaration;
class CallableDeclaration; class CallableDeclaration;
class OverrideSpecifier;
class ContractDefinition; class ContractDefinition;
class InheritanceSpecifier; class InheritanceSpecifier;
class UsingForDirective; class UsingForDirective;
@ -63,6 +64,8 @@ class Statement;
class Block; class Block;
class PlaceholderStatement; class PlaceholderStatement;
class IfStatement; class IfStatement;
class TryCatchClause;
class TryStatement;
class BreakableStatement; class BreakableStatement;
class WhileStatement; class WhileStatement;
class ForStatement; class ForStatement;

View File

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

View File

@ -81,6 +81,7 @@ public:
bool visit(EnumDefinition const& _node) override; bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override; bool visit(EnumValue const& _node) override;
bool visit(ParameterList const& _node) override; bool visit(ParameterList const& _node) override;
bool visit(OverrideSpecifier const& _node) override;
bool visit(FunctionDefinition const& _node) override; bool visit(FunctionDefinition const& _node) override;
bool visit(VariableDeclaration const& _node) override; bool visit(VariableDeclaration const& _node) override;
bool visit(ModifierDefinition const& _node) override; bool visit(ModifierDefinition const& _node) override;
@ -95,6 +96,8 @@ public:
bool visit(Block const& _node) override; bool visit(Block const& _node) override;
bool visit(PlaceholderStatement const& _node) override; bool visit(PlaceholderStatement const& _node) override;
bool visit(IfStatement 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(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override; bool visit(ForStatement const& _node) override;
bool visit(Continue const& _node) override; bool visit(Continue const& _node) override;
@ -113,6 +116,7 @@ public:
bool visit(NewExpression const& _node) override; bool visit(NewExpression const& _node) override;
bool visit(MemberAccess const& _node) override; bool visit(MemberAccess const& _node) override;
bool visit(IndexAccess const& _node) override; bool visit(IndexAccess const& _node) override;
bool visit(IndexRangeAccess const& _node) override;
bool visit(Identifier const& _node) override; bool visit(Identifier const& _node) override;
bool visit(ElementaryTypeNameExpression const& _node) override; bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override; bool visit(Literal const& _node) override;
@ -130,6 +134,7 @@ private:
std::string const& _nodeName, std::string const& _nodeName,
std::vector<std::pair<std::string, Json::Value>>&& _attributes 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; std::string sourceLocationToString(langutil::SourceLocation const& _location) const;
static std::string namePathToString(std::vector<ASTString> const& _namePath); static std::string namePathToString(std::vector<ASTString> const& _namePath);
static Json::Value idOrNull(ASTNode const* _pt) 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(EnumDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumValue& _node) { return visitNode(_node); } virtual bool visit(EnumValue& _node) { return visitNode(_node); }
virtual bool visit(ParameterList& _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(FunctionDefinition& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclaration& _node) { return visitNode(_node); } virtual bool visit(VariableDeclaration& _node) { return visitNode(_node); }
virtual bool visit(ModifierDefinition& _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(Block& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); } virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); }
virtual bool visit(IfStatement& _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(WhileStatement& _node) { return visitNode(_node); }
virtual bool visit(ForStatement& _node) { return visitNode(_node); } virtual bool visit(ForStatement& _node) { return visitNode(_node); }
virtual bool visit(Continue& _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(NewExpression& _node) { return visitNode(_node); }
virtual bool visit(MemberAccess& _node) { return visitNode(_node); } virtual bool visit(MemberAccess& _node) { return visitNode(_node); }
virtual bool visit(IndexAccess& _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(Identifier& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); }
virtual bool visit(Literal& _node) { return visitNode(_node); } virtual bool visit(Literal& _node) { return visitNode(_node); }
@ -100,6 +104,7 @@ public:
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); } virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); } virtual void endVisit(EnumValue& _node) { endVisitNode(_node); }
virtual void endVisit(ParameterList& _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(FunctionDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclaration& _node) { endVisitNode(_node); } virtual void endVisit(VariableDeclaration& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierDefinition& _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(Block& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); } virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement& _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(WhileStatement& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement& _node) { endVisitNode(_node); } virtual void endVisit(ForStatement& _node) { endVisitNode(_node); }
virtual void endVisit(Continue& _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(NewExpression& _node) { endVisitNode(_node); }
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); }
virtual void endVisit(IndexAccess& _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(Identifier& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); }
virtual void endVisit(Literal& _node) { endVisitNode(_node); } virtual void endVisit(Literal& _node) { endVisitNode(_node); }
@ -159,6 +167,7 @@ public:
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); } virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumValue 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(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(FunctionDefinition const& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclaration const& _node) { return visitNode(_node); } virtual bool visit(VariableDeclaration const& _node) { return visitNode(_node); }
virtual bool visit(ModifierDefinition 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(Block const& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement 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(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(WhileStatement const& _node) { return visitNode(_node); }
virtual bool visit(ForStatement const& _node) { return visitNode(_node); } virtual bool visit(ForStatement const& _node) { return visitNode(_node); }
virtual bool visit(Continue 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(NewExpression const& _node) { return visitNode(_node); }
virtual bool visit(MemberAccess 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(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(Identifier const& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); }
virtual bool visit(Literal const& _node) { return visitNode(_node); } virtual bool visit(Literal const& _node) { return visitNode(_node); }
@ -205,6 +217,7 @@ public:
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); } virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); } virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }
virtual void endVisit(ParameterList 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(FunctionDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclaration const& _node) { endVisitNode(_node); } virtual void endVisit(VariableDeclaration const& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierDefinition 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(Block const& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement const& _node) { endVisitNode(_node); } virtual void endVisit(PlaceholderStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement 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(WhileStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); } virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(Continue 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(NewExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(IndexAccess 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(Identifier const& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(Literal const& _node) { endVisitNode(_node); } virtual void endVisit(Literal const& _node) { endVisitNode(_node); }

View File

@ -187,10 +187,26 @@ void ParameterList::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this); _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) void FunctionDefinition::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_overrides)
m_overrides->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
if (m_returnParameters) if (m_returnParameters)
m_returnParameters->accept(_visitor); m_returnParameters->accept(_visitor);
@ -205,6 +221,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
if (m_overrides)
m_overrides->accept(_visitor);
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
if (m_returnParameters) if (m_returnParameters)
m_returnParameters->accept(_visitor); m_returnParameters->accept(_visitor);
@ -244,6 +262,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor); m_body->accept(_visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
@ -254,6 +274,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_parameters->accept(_visitor); m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor); m_body->accept(_visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
@ -443,6 +465,48 @@ void IfStatement::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this); _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) void WhileStatement::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))
@ -765,6 +829,32 @@ void IndexAccess::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this); _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) void Identifier::accept(ASTVisitor& _visitor)
{ {
_visitor.visit(*this); _visitor.visit(*this);

View File

@ -21,6 +21,7 @@
#pragma once #pragma once
#include <map> #include <map>
#include <set>
namespace dev namespace dev
{ {
@ -35,10 +36,11 @@ enum class ExperimentalFeature
TestOnlyAnalysis TestOnlyAnalysis
}; };
static std::map<ExperimentalFeature, bool> const ExperimentalFeatureOnlyAnalysis = static std::set<ExperimentalFeature> const ExperimentalFeatureWithoutWarning =
{ {
{ ExperimentalFeature::SMTChecker, true }, ExperimentalFeature::ABIEncoderV2,
{ ExperimentalFeature::TestOnlyAnalysis, true }, ExperimentalFeature::SMTChecker,
ExperimentalFeature::TestOnlyAnalysis,
}; };
static std::map<std::string, ExperimentalFeature> const ExperimentalFeatureNames = 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. /// 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_bytesStorage;
unique_ptr<ArrayType> TypeProvider::m_bytesMemory; unique_ptr<ArrayType> TypeProvider::m_bytesMemory;
unique_ptr<ArrayType> TypeProvider::m_bytesCalldata;
unique_ptr<ArrayType> TypeProvider::m_stringStorage; unique_ptr<ArrayType> TypeProvider::m_stringStorage;
unique_ptr<ArrayType> TypeProvider::m_stringMemory; unique_ptr<ArrayType> TypeProvider::m_stringMemory;
@ -177,6 +178,7 @@ void TypeProvider::reset()
clearCache(m_inaccessibleDynamic); clearCache(m_inaccessibleDynamic);
clearCache(m_bytesStorage); clearCache(m_bytesStorage);
clearCache(m_bytesMemory); clearCache(m_bytesMemory);
clearCache(m_bytesCalldata);
clearCache(m_stringStorage); clearCache(m_stringStorage);
clearCache(m_stringMemory); clearCache(m_stringMemory);
clearCache(m_emptyTuple); clearCache(m_emptyTuple);
@ -200,7 +202,7 @@ inline T const* TypeProvider::createAndGet(Args&& ... _args)
return static_cast<T const*>(instance().m_generalTypes.back().get()); 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( solAssert(
TokenTraits::isElementaryTypeName(_type.token()), TokenTraits::isElementaryTypeName(_type.token()),
@ -233,7 +235,14 @@ Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const&
case Token::UFixed: case Token::UFixed:
return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned); return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned);
case Token::Address: case Token::Address:
{
if (_stateMutability)
{
solAssert(*_stateMutability == StateMutability::Payable, "");
return payableAddress();
}
return address(); return address();
}
case Token::Bool: case Token::Bool:
return boolean(); return boolean();
case Token::Bytes: case Token::Bytes:
@ -307,6 +316,13 @@ ArrayType const* TypeProvider::bytesMemory()
return m_bytesMemory.get(); 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() ArrayType const* TypeProvider::stringStorage()
{ {
if (!m_stringStorage) if (!m_stringStorage)
@ -494,6 +510,11 @@ ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseTy
return createAndGet<ArrayType>(_location, _baseType, _length); 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) ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper)
{ {
return createAndGet<ContractType>(_contractDef, _isSuper); return createAndGet<ContractType>(_contractDef, _isSuper);

View File

@ -55,7 +55,7 @@ public:
/// @name Factory functions /// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type. /// 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 /// 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". /// 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* bytesStorage();
static ArrayType const* bytesMemory(); static ArrayType const* bytesMemory();
static ArrayType const* bytesCalldata();
static ArrayType const* stringStorage(); static ArrayType const* stringStorage();
static ArrayType const* stringMemory(); static ArrayType const* stringMemory();
@ -81,6 +82,8 @@ public:
/// Constructor for a fixed-size array type ("type[20]") /// Constructor for a fixed-size array type ("type[20]")
static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length); 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* payableAddress() noexcept { return &m_payableAddress; }
static AddressType const* address() noexcept { return &m_address; } static AddressType const* address() noexcept { return &m_address; }
@ -204,6 +207,7 @@ private:
/// These are lazy-initialized because they depend on `byte` being available. /// These are lazy-initialized because they depend on `byte` being available.
static std::unique_ptr<ArrayType> m_bytesStorage; static std::unique_ptr<ArrayType> m_bytesStorage;
static std::unique_ptr<ArrayType> m_bytesMemory; 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_stringStorage;
static std::unique_ptr<ArrayType> m_stringMemory; 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 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 (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable();
return isImplicitlyConvertibleTo(_convertTo) || return isImplicitlyConvertibleTo(_convertTo) ||
_convertTo.category() == Category::Integer || _convertTo.category() == Category::Integer ||
@ -600,6 +602,17 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other
else else
return nullptr; 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 auto commonType = Type::commonType(this, _other); //might be an integer or fixed point
if (!commonType) if (!commonType)
@ -610,14 +623,6 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other
return commonType; return commonType;
if (TokenTraits::isBooleanOp(_operator)) if (TokenTraits::isBooleanOp(_operator))
return nullptr; 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; return commonType;
} }
@ -1464,8 +1469,9 @@ BoolResult ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const
bool ContractType::isPayable() const bool ContractType::isPayable() const
{ {
auto receiveFunction = m_contract.receiveFunction();
auto fallbackFunction = m_contract.fallbackFunction(); auto fallbackFunction = m_contract.fallbackFunction();
return fallbackFunction && fallbackFunction->isPayable(); return receiveFunction || (fallbackFunction && fallbackFunction->isPayable());
} }
TypeResult ContractType::unaryOperatorResult(Token _operator) const TypeResult ContractType::unaryOperatorResult(Token _operator) const
@ -1783,10 +1789,17 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
if (isDynamicallySized() && location() == DataLocation::Storage) if (isDynamicallySized() && location() == DataLocation::Storage)
{ {
members.emplace_back("push", TypeProvider::function( members.emplace_back("push", TypeProvider::function(
TypePointers{},
TypePointers{baseType()}, TypePointers{baseType()},
TypePointers{TypeProvider::uint256()}, strings{},
strings{string()}, strings{string()},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
));
members.emplace_back("push", TypeProvider::function(
TypePointers{baseType()},
TypePointers{},
strings{string()}, strings{string()},
strings{},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
)); ));
members.emplace_back("pop", TypeProvider::function( members.emplace_back("pop", TypeProvider::function(
@ -1871,6 +1884,30 @@ std::unique_ptr<ReferenceType> ArrayType::copyForLocation(DataLocation _location
return copy; 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 string ContractType::richIdentifier() const
{ {
return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id()); return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id());
@ -2735,8 +2772,6 @@ bool FunctionType::operator==(Type const& _other) const
BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{ {
if (m_kind == Kind::External && _convertTo == *TypeProvider::address())
return true;
return _convertTo.category() == category(); return _convertTo.category() == category();
} }
@ -2931,7 +2966,10 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
{ {
MemberList::MemberMap members; MemberList::MemberMap members;
if (m_kind == Kind::External) if (m_kind == Kind::External)
{
members.emplace_back("selector", TypeProvider::fixedBytes(4)); members.emplace_back("selector", TypeProvider::fixedBytes(4));
members.emplace_back("address", TypeProvider::address());
}
if (m_kind != Kind::BareDelegateCall) if (m_kind != Kind::BareDelegateCall)
{ {
if (isPayable()) if (isPayable())

View File

@ -161,7 +161,7 @@ public:
enum class Category enum class Category
{ {
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
FixedBytes, Contract, Struct, Function, Enum, Tuple, FixedBytes, Contract, Struct, Function, Enum, Tuple,
Mapping, TypeType, Modifier, Magic, Module, Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic InaccessibleDynamic
@ -773,6 +773,35 @@ private:
mutable std::optional<TypeResult> m_interfaceType_library; 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. * 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 /// See documentation of m_super
bool isSuper() const { return 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; bool isPayable() const;
ContractDefinition const& contractDefinition() const { return m_contract; } ContractDefinition const& contractDefinition() const { return m_contract; }

View File

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

View File

@ -24,6 +24,7 @@
#include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/interface/OptimiserSettings.h> #include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libevmasm/Assembly.h> #include <libevmasm/Assembly.h>
#include <functional> #include <functional>
@ -35,8 +36,9 @@ namespace solidity {
class Compiler class Compiler
{ {
public: public:
explicit Compiler(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeContext(_evmVersion), m_runtimeContext(_evmVersion),
m_context(_evmVersion, &m_runtimeContext) m_context(_evmVersion, &m_runtimeContext)
{ } { }
@ -79,6 +81,7 @@ public:
private: private:
OptimiserSettings const m_optimiserSettings; OptimiserSettings const m_optimiserSettings;
RevertStrings const m_revertStrings;
CompilerContext m_runtimeContext; CompilerContext m_runtimeContext;
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
CompilerContext m_context; CompilerContext m_context;

View File

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

View File

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

View File

@ -95,6 +95,26 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
m_context << Instruction::REVERT; 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) void CompilerUtils::accessCalldataTail(Type const& _type)
{ {
solAssert(_type.dataStoredIn(DataLocation::CallData), ""); solAssert(_type.dataStoredIn(DataLocation::CallData), "");
@ -984,6 +1004,17 @@ void CompilerUtils::convertType(
} }
break; 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: case Type::Category::Struct:
{ {
solAssert(targetTypeCategory == stackTypeCategory, ""); solAssert(targetTypeCategory == stackTypeCategory, "");
@ -1115,37 +1146,28 @@ void CompilerUtils::convertType(
m_context << Instruction::ISZERO << Instruction::ISZERO; m_context << Instruction::ISZERO << Instruction::ISZERO;
break; break;
default: default:
if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address) // 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); FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted."); FunctionType const& targetType = dynamic_cast<FunctionType const&>(_targetType);
solAssert(
// stack: <address> <function_id> typeOnStack.isImplicitlyConvertibleTo(targetType) &&
m_context << Instruction::POP; typeOnStack.sizeOnStack() == targetType.sizeOnStack() &&
(typeOnStack.kind() == FunctionType::Kind::Internal || typeOnStack.kind() == FunctionType::Kind::External) &&
typeOnStack.kind() == targetType.kind(),
"Invalid function type conversion requested."
);
} }
else else
{ // All other types should not be convertible to non-equal types.
if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function) solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
{
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
FunctionType const& targetType = dynamic_cast<FunctionType const&>(_targetType);
solAssert(
typeOnStack.isImplicitlyConvertibleTo(targetType) &&
typeOnStack.sizeOnStack() == targetType.sizeOnStack() &&
(typeOnStack.kind() == FunctionType::Kind::Internal || typeOnStack.kind() == FunctionType::Kind::External) &&
typeOnStack.kind() == targetType.kind(),
"Invalid function type conversion requested."
);
}
else
// All other types should not be convertible to non-equal types.
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
if (_cleanupNeeded && _targetType.canBeStored() && _targetType.storageBytes() < 32) if (_cleanupNeeded && _targetType.canBeStored() && _targetType.storageBytes() < 32)
m_context m_context
<< ((u256(1) << (8 * _targetType.storageBytes())) - 1) << ((u256(1) << (8 * _targetType.storageBytes())) - 1)
<< Instruction::AND; << Instruction::AND;
}
break; break;
} }

View File

@ -67,6 +67,10 @@ public:
/// Stack post: /// Stack post:
void revertWithStringData(Type const& _argumentType); 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) /// 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 /// 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. /// returns the array length on the stack.

View File

@ -35,6 +35,8 @@
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <libdevcore/Whiskers.h>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <algorithm> #include <algorithm>
@ -335,6 +337,9 @@ namespace
// Helper function to check if any function is payable // Helper function to check if any function is payable
bool hasPayableFunctions(ContractDefinition const& _contract) bool hasPayableFunctions(ContractDefinition const& _contract)
{ {
if (_contract.receiveFunction())
return true;
FunctionDefinition const* fallback = _contract.fallbackFunction(); FunctionDefinition const* fallback = _contract.fallbackFunction();
if (fallback && fallback->isPayable()) if (fallback && fallback->isPayable())
return true; return true;
@ -361,6 +366,9 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
FunctionDefinition const* fallback = _contract.fallbackFunction(); FunctionDefinition const* fallback = _contract.fallbackFunction();
solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions"); 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; bool needToAddCallvalueCheck = true;
if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary()) if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary())
{ {
@ -368,11 +376,15 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
needToAddCallvalueCheck = false; needToAddCallvalueCheck = false;
} }
eth::AssemblyItem notFound = m_context.newTag(); eth::AssemblyItem notFoundOrReceiveEther = m_context.newTag();
// directly jump to fallback if the data is too short to contain a function selector // 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 // also guards against short data
m_context << u256(4) << Instruction::CALLDATASIZE << Instruction::LT; m_context << u256(4) << Instruction::CALLDATASIZE << Instruction::LT;
m_context.appendConditionalJumpTo(notFound); m_context.appendConditionalJumpTo(notFoundOrReceiveEther);
// retrieve the function signature hash from the calldata // retrieve the function signature hash from the calldata
if (!interfaceFunctions.empty()) if (!interfaceFunctions.empty())
@ -390,23 +402,44 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment); appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment);
} }
m_context << notFound; m_context << notFoundOrReceiveEther;
if (fallback) if (!fallback && !etherReceiver)
{
solAssert(!_contract.isLibrary(), "");
if (!fallback->isPayable() && needToAddCallvalueCheck)
appendCallValueCheck();
solAssert(fallback->isFallback(), "");
solAssert(FunctionType(*fallback).parameterTypes().empty(), "");
solAssert(FunctionType(*fallback).returnParameterTypes().empty(), "");
fallback->accept(*this);
m_context << Instruction::STOP;
}
else
// TODO: error message here?
m_context.appendRevert(); 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(), "");
if (!fallback->isPayable() && needToAddCallvalueCheck)
appendCallValueCheck();
solAssert(fallback->isFallback(), "");
solAssert(FunctionType(*fallback).parameterTypes().empty(), "");
solAssert(FunctionType(*fallback).returnParameterTypes().empty(), "");
fallback->accept(*this);
m_context << Instruction::STOP;
}
else
// TODO: error message here?
m_context.appendRevert();
}
for (auto const& it: interfaceFunctions) 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."); solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
for (VariableDeclaration const* variable: _contract.stateVariables()) for (VariableDeclaration const* variable: _contract.stateVariables())
if (variable->value() && !variable->isConstant()) if (variable->value() && !variable->isConstant())
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable);
} }
bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
@ -493,9 +526,11 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration)
m_continueTags.clear(); m_continueTags.clear();
if (_variableDeclaration.isConstant()) if (_variableDeclaration.isConstant())
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration); ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
.appendConstStateVariableAccessor(_variableDeclaration);
else else
ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableAccessor(_variableDeclaration); ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals)
.appendStateVariableAccessor(_variableDeclaration);
return false; return false;
} }
@ -532,8 +567,10 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
m_currentFunction = &_function; m_currentFunction = &_function;
m_modifierDepth = -1; m_modifierDepth = -1;
m_scopeStackHeight.clear(); m_scopeStackHeight.clear();
m_context.setModifierDepth(0);
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();
m_context.setModifierDepth(0);
solAssert(m_returnTags.empty(), ""); solAssert(m_returnTags.empty(), "");
// Now we need to re-shuffle the stack. For this we keep a record of the stack layout // 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()) if (!_function.isConstructor())
{ {
solAssert(m_context.numberOfLocalVariables() == 0, ""); solAssert(m_context.numberOfLocalVariables() == 0, "");
if (!_function.isFallback()) if (!_function.isFallback() && !_function.isReceive())
m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
} }
@ -763,6 +800,183 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
return false; 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) bool ContractCompiler::visit(IfStatement const& _ifStatement)
{ {
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
@ -1032,6 +1246,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
vector<VariableDeclaration const*> addedVariables; vector<VariableDeclaration const*> addedVariables;
m_modifierDepth++; m_modifierDepth++;
m_context.setModifierDepth(m_modifierDepth);
if (m_modifierDepth >= m_currentFunction->modifiers().size()) if (m_modifierDepth >= m_currentFunction->modifiers().size())
{ {
@ -1085,6 +1300,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
m_context.removeVariable(*var); m_context.removeVariable(*var);
} }
m_modifierDepth--; m_modifierDepth--;
m_context.setModifierDepth(m_modifierDepth);
} }
void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable)
@ -1096,7 +1312,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con
void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType)
{ {
ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals); ExpressionCompiler expressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals);
expressionCompiler.compile(_expression); expressionCompiler.compile(_expression);
if (_targetType) if (_targetType)
CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType);

View File

@ -24,6 +24,7 @@
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/CompilerContext.h> #include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libevmasm/Assembly.h> #include <libevmasm/Assembly.h>
#include <functional> #include <functional>
#include <ostream> #include <ostream>
@ -43,9 +44,11 @@ public:
explicit ContractCompiler( explicit ContractCompiler(
ContractCompiler* _runtimeCompiler, ContractCompiler* _runtimeCompiler,
CompilerContext& _context, CompilerContext& _context,
OptimiserSettings _optimiserSettings OptimiserSettings _optimiserSettings,
RevertStrings _revertStrings
): ):
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeCompiler(_runtimeCompiler), m_runtimeCompiler(_runtimeCompiler),
m_context(_context) m_context(_context)
{ {
@ -104,6 +107,9 @@ private:
bool visit(VariableDeclaration const& _variableDeclaration) override; bool visit(VariableDeclaration const& _variableDeclaration) override;
bool visit(FunctionDefinition const& _function) override; bool visit(FunctionDefinition const& _function) override;
bool visit(InlineAssembly const& _inlineAssembly) 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(IfStatement const& _ifStatement) override;
bool visit(WhileStatement const& _whileStatement) override; bool visit(WhileStatement const& _whileStatement) override;
bool visit(ForStatement const& _forStatement) override; bool visit(ForStatement const& _forStatement) override;
@ -135,6 +141,7 @@ private:
void storeStackHeight(ASTNode const* _node); void storeStackHeight(ASTNode const* _node);
OptimiserSettings const m_optimiserSettings; OptimiserSettings const m_optimiserSettings;
RevertStrings const m_revertStrings;
/// Pointer to the runtime compiler in case this is a creation compiler. /// Pointer to the runtime compiler in case this is a creation compiler.
ContractCompiler* m_runtimeCompiler = nullptr; ContractCompiler* m_runtimeCompiler = nullptr;
CompilerContext& m_context; CompilerContext& m_context;

View File

@ -601,13 +601,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.adjustStackOffset(returnParametersSize - parameterSize - 1); m_context.adjustStackOffset(returnParametersSize - parameterSize - 1);
break; break;
} }
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall: case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall: case FunctionType::Kind::BareStaticCall:
solAssert(!_functionCall.annotation().tryCall, "");
[[fallthrough]];
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments); appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall);
break; break;
case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareCallCode:
solAssert(false, "Callcode has been removed."); solAssert(false, "Callcode has been removed.");
@ -635,12 +637,21 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
else else
m_context << u256(0); m_context << u256(0);
m_context << Instruction::CREATE; 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()) if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP; 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; break;
} }
case FunctionType::Kind::SetGas: case FunctionType::Kind::SetGas:
@ -690,7 +701,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
true, true,
true true
), ),
{} {},
false
); );
if (function.kind() == FunctionType::Kind::Transfer) if (function.kind() == FunctionType::Kind::Transfer)
{ {
@ -706,16 +718,28 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
break; break;
case FunctionType::Kind::Revert: case FunctionType::Kind::Revert:
{ {
if (!arguments.empty()) if (arguments.empty())
m_context.appendRevert();
else
{ {
// function-sel(Error(string)) + encoding // function-sel(Error(string)) + encoding
solAssert(arguments.size() == 1, ""); solAssert(arguments.size() == 1, "");
solAssert(function.parameterTypes().size() == 1, ""); solAssert(function.parameterTypes().size() == 1, "");
arguments.front()->accept(*this); if (m_revertStrings == RevertStrings::Strip)
utils().revertWithStringData(*arguments.front()->annotation().type); {
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; break;
} }
case FunctionType::Kind::KECCAK256: case FunctionType::Kind::KECCAK256:
@ -850,48 +874,74 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << contractAddresses.at(function.kind()); m_context << contractAddresses.at(function.kind());
for (unsigned i = function.sizeOnStack(); i > 0; --i) for (unsigned i = function.sizeOnStack(); i > 0; --i)
m_context << swapInstruction(i); m_context << swapInstruction(i);
appendExternalFunctionCall(function, arguments); solAssert(!_functionCall.annotation().tryCall, "");
appendExternalFunctionCall(function, arguments, false);
break; break;
} }
case FunctionType::Kind::ByteArrayPush: case FunctionType::Kind::ByteArrayPush:
case FunctionType::Kind::ArrayPush: case FunctionType::Kind::ArrayPush:
{ {
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
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);
// stack: ArrayReference if (function.parameterTypes().size() == 0)
arguments[0]->accept(*this); {
TypePointer const& argType = arguments[0]->annotation().type; auto paramType = function.returnParameterTypes().at(0);
// stack: ArrayReference argValue solAssert(paramType, "");
utils().moveToStackTop(argType->sizeOnStack(), 1);
// stack: argValue ArrayReference ArrayType const* arrayType =
m_context << Instruction::DUP1; function.kind() == FunctionType::Kind::ArrayPush ?
ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); TypeProvider::array(DataLocation::Storage, paramType) :
// stack: argValue ArrayReference newLength TypeProvider::bytesStorage();
m_context << Instruction::SWAP1;
// stack: argValue newLength ArrayReference // stack: ArrayReference
m_context << u256(1) << Instruction::DUP3 << Instruction::SUB; m_context << u256(1) << Instruction::DUP2;
// stack: argValue newLength ArrayReference (newLength-1) ArrayUtils(m_context).incrementDynamicArraySize(*arrayType);
ArrayUtils(m_context).accessIndex(*arrayType, false); // stack: ArrayReference 1 newLength
// stack: argValue newLength storageSlot slotOffset m_context << Instruction::SUB;
utils().moveToStackTop(3, argType->sizeOnStack()); // stack: ArrayReference (newLength-1)
// stack: newLength storageSlot slotOffset argValue ArrayUtils(m_context).accessIndex(*arrayType, false);
TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
solAssert(type, ""); if (arrayType->isByteArray())
utils().convertType(*argType, *type); setLValue<StorageByteArrayElement>(_functionCall);
utils().moveToStackTop(1 + type->sizeOnStack()); else
utils().moveToStackTop(1 + type->sizeOnStack()); setLValueToStorageItem(_functionCall);
// stack: newLength argValue storageSlot slotOffset }
if (function.kind() == FunctionType::Kind::ArrayPush)
StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true);
else else
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); {
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::bytesStorage();
// stack: ArrayReference
arguments[0]->accept(*this);
TypePointer const& argType = arguments[0]->annotation().type;
// stack: ArrayReference argValue
utils().moveToStackTop(argType->sizeOnStack(), 1);
// stack: argValue ArrayReference
m_context << Instruction::DUP1;
ArrayUtils(m_context).incrementDynamicArraySize(*arrayType);
// stack: argValue ArrayReference newLength
m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB;
// stack: argValue ArrayReference (newLength-1)
ArrayUtils(m_context).accessIndex(*arrayType, false);
// 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: 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; break;
} }
case FunctionType::Kind::ArrayPop: case FunctionType::Kind::ArrayPop:
@ -954,6 +1004,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::Require: case FunctionType::Kind::Require:
{ {
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false);
bool haveReasonString = arguments.size() > 1 && m_revertStrings != RevertStrings::Strip;
if (arguments.size() > 1) if (arguments.size() > 1)
{ {
// Users probably expect the second argument to be evaluated // Users probably expect the second argument to be evaluated
@ -961,8 +1014,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// function call. // function call.
solAssert(arguments.size() == 2, ""); solAssert(arguments.size() == 2, "");
solAssert(function.kind() == FunctionType::Kind::Require, ""); solAssert(function.kind() == FunctionType::Kind::Require, "");
arguments.at(1)->accept(*this); if (m_revertStrings == RevertStrings::Strip)
utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack()); {
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> // Stack: <error string (unconverted)> <condition>
// jump if condition was met // jump if condition was met
@ -971,7 +1035,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Assert) if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error // condition was not met, flag an error
m_context.appendInvalid(); m_context.appendInvalid();
else if (arguments.size() > 1) else if (haveReasonString)
{ {
utils().revertWithStringData(*arguments.at(1)->annotation().type); utils().revertWithStringData(*arguments.at(1)->annotation().type);
// Here, the argument is consumed, but in the other branch, it is still there. // 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(); m_context.appendRevert();
// the success branch // the success branch
m_context << success; m_context << success;
if (arguments.size() > 1) if (haveReasonString)
utils().popStackElement(*arguments.at(1)->annotation().type); utils().popStackElement(*arguments.at(1)->annotation().type);
break; break;
} }
@ -1094,10 +1158,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
else else
targetTypes = TypePointers{_functionCall.annotation().type}; targetTypes = TypePointers{_functionCall.annotation().type};
if ( if (
*firstArgType == ArrayType(DataLocation::CallData) || auto referenceType = dynamic_cast<ReferenceType const*>(firstArgType);
*firstArgType == ArrayType(DataLocation::CallData, true) referenceType && referenceType->dataStoredIn(DataLocation::CallData)
) )
{
solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), "");
utils().convertType(*referenceType, *TypeProvider::bytesCalldata());
utils().abiDecode(targetTypes, false); utils().abiDecode(targetTypes, false);
}
else else
{ {
utils().convertType(*firstArgType, *TypeProvider::bytesMemory()); 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 (FunctionCall const* funCall = dynamic_cast<FunctionCall const*>(&_memberAccess.expression()))
if (auto const* addr = dynamic_cast<ElementaryTypeNameExpression const*>(&funCall->expression())) if (auto const* addr = dynamic_cast<ElementaryTypeNameExpression const*>(&funCall->expression()))
if ( if (
addr->typeName().token() == Token::Address && addr->type().typeName().token() == Token::Address &&
funCall->arguments().size() == 1 funCall->arguments().size() == 1
) )
if (auto arg = dynamic_cast<Identifier const*>( funCall->arguments().front().get())) 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 /// need to store it as bytes4
utils().leftShiftNumberOnStack(224); 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 else
solAssert( solAssert(
!!_memberAccess.expression().annotation().type->memberType(member), !!_memberAccess.expression().annotation().type->memberType(member),
@ -1490,7 +1565,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
m_context << Instruction::SWAP1 << Instruction::POP; m_context << Instruction::SWAP1 << Instruction::POP;
break; break;
case DataLocation::Storage: case DataLocation::Storage:
setLValue<StorageArrayLength>(_memberAccess, type); ArrayUtils(m_context).retrieveLength(type);
m_context << Instruction::SWAP1 << Instruction::POP;
break; break;
case DataLocation::Memory: case DataLocation::Memory:
m_context << Instruction::MLOAD; m_context << Instruction::MLOAD;
@ -1533,90 +1609,149 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
Type const& baseType = *_indexAccess.baseExpression().annotation().type; Type const& baseType = *_indexAccess.baseExpression().annotation().type;
if (baseType.category() == Type::Category::Mapping) switch (baseType.category())
{ {
// stack: storage_base_ref case Type::Category::Mapping:
TypePointer keyType = dynamic_cast<MappingType const&>(baseType).keyType();
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
if (keyType->isDynamicallySized())
{ {
_indexAccess.indexExpression()->accept(*this); // stack: storage_base_ref
utils().fetchFreeMemoryPointer(); TypePointer keyType = dynamic_cast<MappingType const&>(baseType).keyType();
// stack: base index mem solAssert(_indexAccess.indexExpression(), "Index expression expected.");
// note: the following operations must not allocate memory! if (keyType->isDynamicallySized())
utils().packedEncode(
TypePointers{_indexAccess.indexExpression()->annotation().type},
TypePointers{keyType}
);
m_context << Instruction::SWAP1;
utils().storeInMemoryDynamic(*TypeProvider::uint256());
utils().toSizeAfterFreeMemoryPointer();
}
else
{
m_context << u256(0); // memory position
appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression());
m_context << Instruction::SWAP1;
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
utils().storeInMemoryDynamic(*TypeProvider::uint256());
m_context << u256(0);
}
m_context << Instruction::KECCAK256;
m_context << u256(0);
setLValueToStorageItem(_indexAccess);
}
else if (baseType.category() == Type::Category::Array)
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
// stack layout: <base_ref> [<length>] <index>
switch (arrayType.location())
{
case DataLocation::Storage:
ArrayUtils(m_context).accessIndex(arrayType);
if (arrayType.isByteArray())
{ {
solAssert(!arrayType.isString(), "Index access to string is not allowed."); _indexAccess.indexExpression()->accept(*this);
setLValue<StorageByteArrayElement>(_indexAccess); utils().fetchFreeMemoryPointer();
// stack: base index mem
// note: the following operations must not allocate memory!
utils().packedEncode(
TypePointers{_indexAccess.indexExpression()->annotation().type},
TypePointers{keyType}
);
m_context << Instruction::SWAP1;
utils().storeInMemoryDynamic(*TypeProvider::uint256());
utils().toSizeAfterFreeMemoryPointer();
} }
else else
setLValueToStorageItem(_indexAccess); {
break; m_context << u256(0); // memory position
case DataLocation::Memory: appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression());
ArrayUtils(m_context).accessIndex(arrayType); m_context << Instruction::SWAP1;
setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
break; utils().storeInMemoryDynamic(*TypeProvider::uint256());
case DataLocation::CallData: m_context << u256(0);
ArrayUtils(m_context).accessCallDataArrayElement(arrayType); }
m_context << Instruction::KECCAK256;
m_context << u256(0);
setLValueToStorageItem(_indexAccess);
break; break;
} }
} case Type::Category::ArraySlice:
else if (baseType.category() == Type::Category::FixedBytes) {
{ auto const& arrayType = dynamic_cast<ArraySliceType const&>(baseType).arrayType();
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType); solAssert(arrayType.location() == DataLocation::CallData && arrayType.isDynamicallySized(), "");
solAssert(_indexAccess.indexExpression(), "Index expression expected."); solAssert(_indexAccess.indexExpression(), "Index expression expected.");
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
// stack layout: <value> <index> ArrayUtils(m_context).accessCallDataArrayElement(arrayType);
// check out-of-bounds access break;
m_context << u256(fixedBytesType.numBytes());
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
m_context.appendConditionalInvalid();
m_context << Instruction::BYTE; }
utils().leftShiftNumberOnStack(256 - 8); case Type::Category::Array:
} {
else if (baseType.category() == Type::Category::TypeType) ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
{ solAssert(_indexAccess.indexExpression(), "Index expression expected.");
solAssert(baseType.sizeOnStack() == 0, "");
solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
// no-op - this seems to be a lone array type (`structType[];`) // stack layout: <base_ref> [<length>] <index>
switch (arrayType.location())
{
case DataLocation::Storage:
ArrayUtils(m_context).accessIndex(arrayType);
if (arrayType.isByteArray())
{
solAssert(!arrayType.isString(), "Index access to string is not allowed.");
setLValue<StorageByteArrayElement>(_indexAccess);
}
else
setLValueToStorageItem(_indexAccess);
break;
case DataLocation::Memory:
ArrayUtils(m_context).accessIndex(arrayType);
setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray());
break;
case DataLocation::CallData:
ArrayUtils(m_context).accessCallDataArrayElement(arrayType);
break;
}
break;
}
case Type::Category::FixedBytes:
{
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
// stack layout: <value> <index>
// check out-of-bounds access
m_context << u256(fixedBytesType.numBytes());
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
m_context.appendConditionalInvalid();
m_context << Instruction::BYTE;
utils().leftShiftNumberOnStack(256 - 8);
break;
}
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;
}
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 else
solAssert(false, "Index access only allowed for mappings or arrays."); 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; return false;
} }
@ -1906,7 +2041,8 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExternalFunctionCall(
FunctionType const& _functionType, FunctionType const& _functionType,
vector<ASTPointer<Expression const>> const& _arguments vector<ASTPointer<Expression const>> const& _arguments,
bool _tryCall
) )
{ {
solAssert( solAssert(
@ -1942,6 +2078,12 @@ void ExpressionCompiler::appendExternalFunctionCall(
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); 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(); bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0; unsigned retSize = 0;
bool dynamicReturnSize = false; bool dynamicReturnSize = false;
@ -2113,17 +2255,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
(_functionType.gasSet() ? 1 : 0) + (_functionType.gasSet() ? 1 : 0) +
(!_functionType.isBareCall() ? 1 : 0); (!_functionType.isBareCall() ? 1 : 0);
if (returnSuccessConditionAndReturndata) eth::AssemblyItem endTag = m_context.newTag();
m_context << swapInstruction(remainsSize);
else 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 << Instruction::ISZERO;
m_context.appendConditionalRevert(true); m_context.appendConditionalRevert(true);
} }
else
m_context << swapInstruction(remainsSize);
utils().popStackSlots(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) if (returnSuccessConditionAndReturndata)
{ {
// success condition is already there // success condition is already there
@ -2131,24 +2283,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
// an internal helper function e.g. for ``send`` and ``transfer``. In that // 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. // case we're only interested in the success condition, not the return data.
if (!_functionType.returnParameterTypes().empty()) if (!_functionType.returnParameterTypes().empty())
{ utils().returnDataToArray();
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();
}
} }
else if (funKind == FunctionType::Kind::RIPEMD160) else if (funKind == FunctionType::Kind::RIPEMD160)
{ {
@ -2202,6 +2337,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().abiDecode(returnTypes, true); 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) void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)

View File

@ -25,6 +25,7 @@
#include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/LValue.h> #include <libsolidity/codegen/LValue.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
@ -55,8 +56,15 @@ class ArrayType;
class ExpressionCompiler: private ASTConstVisitor class ExpressionCompiler: private ASTConstVisitor
{ {
public: public:
explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimiseOrderLiterals): ExpressionCompiler(
m_optimiseOrderLiterals(_optimiseOrderLiterals), m_context(_compilerContext) {} 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. /// Compile the given @a _expression and leave its value on the stack.
void compile(Expression const& _expression); void compile(Expression const& _expression);
@ -80,6 +88,7 @@ private:
bool visit(NewExpression const& _newExpression) override; bool visit(NewExpression const& _newExpression) override;
bool visit(MemberAccess const& _memberAccess) override; bool visit(MemberAccess const& _memberAccess) override;
bool visit(IndexAccess const& _indexAccess) override; bool visit(IndexAccess const& _indexAccess) override;
bool visit(IndexRangeAccess const& _indexAccess) override;
void endVisit(Identifier const& _identifier) override; void endVisit(Identifier const& _identifier) override;
void endVisit(Literal const& _literal) 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. /// 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( void appendExternalFunctionCall(
FunctionType const& _functionType, 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 /// 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. /// 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. /// @returns the CompilerUtils object containing the current context.
CompilerUtils utils(); CompilerUtils utils();
RevertStrings m_revertStrings;
bool m_optimiseOrderLiterals; bool m_optimiseOrderLiterals;
CompilerContext& m_context; CompilerContext& m_context;
std::unique_ptr<LValue> m_currentLValue; std::unique_ptr<LValue> m_currentLValue;

View File

@ -478,36 +478,6 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer
m_context << Instruction::SWAP1 << Instruction::SSTORE; 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( TupleObject::TupleObject(
CompilerContext& _compilerContext, CompilerContext& _compilerContext,
std::vector<std::unique_ptr<LValue>>&& _lvalues std::vector<std::unique_ptr<LValue>>&& _lvalues

View File

@ -171,31 +171,6 @@ public:
) const override; ) 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. * 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 YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
{ {
string functionName = "clear_storage_range_" + _type.identifier(); 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) string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{ {
solAssert(!_type.isByteArray(), ""); solAssert(!_type.isByteArray(), "");

View File

@ -121,6 +121,18 @@ public:
/// signature: (array, newLen) /// signature: (array, newLen)
std::string resizeDynamicArrayFunction(ArrayType const& _type); 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 /// @returns the name of a function that will clear the storage area given
/// by the start and end (exclusive) parameters (slots). /// by the start and end (exclusive) parameters (slots).
/// signature: (start, end) /// signature: (start, end)

View File

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

View File

@ -296,7 +296,7 @@ void IRGeneratorForStatements::endVisit(Return const& _return)
expressionAsType(*value, *types.front()) << expressionAsType(*value, *types.front()) <<
"\n"; "\n";
} }
m_code << "return_flag := 0\n" << "break\n"; m_code << "leave\n";
} }
void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation) void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
@ -640,6 +640,47 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
array << array <<
"))\n"; "))\n";
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; break;
} }
default: default:
@ -718,6 +759,10 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{ {
solUnimplementedAssert(false, ""); solUnimplementedAssert(false, "");
} }
else if (member == "address")
{
solUnimplementedAssert(false, "");
}
else else
solAssert( solAssert(
!!_memberAccess.expression().annotation().type->memberType(member), !!_memberAccess.expression().annotation().type->memberType(member),
@ -784,33 +829,45 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{ {
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type); auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
solAssert(member == "length", ""); if (member == "length")
{
if (!type.isDynamicallySized()) if (!type.isDynamicallySized())
defineExpression(_memberAccess) << type.length() << "\n"; defineExpression(_memberAccess) << type.length() << "\n";
else
switch (type.location())
{
case DataLocation::CallData:
solUnimplementedAssert(false, "");
//m_context << Instruction::SWAP1 << Instruction::POP;
break;
case DataLocation::Storage:
{
string slot = m_context.variable(_memberAccess.expression());
defineExpression(_memberAccess) <<
m_utils.arrayLengthFunction(type) + "(" + slot + ")\n";
break;
}
case DataLocation::Memory:
defineExpression(_memberAccess) <<
"mload(" <<
m_context.variable(_memberAccess.expression()) <<
")\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 else
switch (type.location()) solAssert(false, "Invalid array member access.");
{
case DataLocation::CallData:
solUnimplementedAssert(false, "");
//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
));
break;
case DataLocation::Memory:
defineExpression(_memberAccess) <<
"mload(" <<
m_context.variable(_memberAccess.expression()) <<
")\n";
break;
}
break; break;
} }
case Type::Category::FixedBytes: case Type::Category::FixedBytes:
@ -937,6 +994,11 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
solAssert(false, "Index access only allowed for mappings or arrays."); 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) void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{ {
Declaration const* declaration = _identifier.annotation().referencedDeclaration; Declaration const* declaration = _identifier.annotation().referencedDeclaration;
@ -1340,7 +1402,7 @@ void IRGeneratorForStatements::generateLoop(
m_code << "for {\n"; m_code << "for {\n";
if (_initExpression) if (_initExpression)
_initExpression->accept(*this); _initExpression->accept(*this);
m_code << "} return_flag {\n"; m_code << "} 1 {\n";
if (_loopExpression) if (_loopExpression)
_loopExpression->accept(*this); _loopExpression->accept(*this);
m_code << "}\n"; m_code << "}\n";
@ -1364,8 +1426,6 @@ void IRGeneratorForStatements::generateLoop(
_body.accept(*this); _body.accept(*this);
m_code << "}\n"; m_code << "}\n";
// Bubble up the return condition.
m_code << "if iszero(return_flag) { break }\n";
} }
Type const& IRGeneratorForStatements::type(Expression const& _expression) Type const& IRGeneratorForStatements::type(Expression const& _expression)

View File

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

View File

@ -148,39 +148,6 @@ string IRStorageItem::setToZero() const
")\n"; ")\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( IRMemoryItem::IRMemoryItem(
YulUtilFunctions _utils, YulUtilFunctions _utils,
std::string _address, std::string _address,

View File

@ -110,30 +110,6 @@ private:
boost::variant<std::string, unsigned> const m_offset; 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 class IRMemoryItem: public IRLValue
{ {
public: public:

View File

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

View File

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

View File

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

View File

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

View File

@ -34,9 +34,13 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
using namespace dev::solidity::smt; using namespace dev::solidity::smt;
CHCSmtLib2Interface::CHCSmtLib2Interface(map<h256, string> const& _queryResponses): CHCSmtLib2Interface::CHCSmtLib2Interface(
m_smtlib2(make_shared<SMTLib2Interface>(_queryResponses)), map<h256, string> const& _queryResponses,
m_queryResponses(_queryResponses) ReadCallback::Callback const& _smtCallback
):
m_smtlib2(make_shared<SMTLib2Interface>(_queryResponses, _smtCallback)),
m_queryResponses(_queryResponses),
m_smtCallback(_smtCallback)
{ {
reset(); reset();
} }
@ -152,9 +156,12 @@ string CHCSmtLib2Interface::querySolver(string const& _input)
h256 inputHash = dev::keccak256(_input); h256 inputHash = dev::keccak256(_input);
if (m_queryResponses.count(inputHash)) if (m_queryResponses.count(inputHash))
return m_queryResponses.at(inputHash); return m_queryResponses.at(inputHash);
else if (m_smtCallback)
{ {
m_unhandledQueries.push_back(_input); auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input);
return "unknown\n"; 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 class CHCSmtLib2Interface: public CHCSolverInterface
{ {
public: 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(); void reset();
@ -68,6 +71,8 @@ private:
std::map<h256, std::string> const& m_queryResponses; std::map<h256, std::string> const& m_queryResponses;
std::vector<std::string> m_unhandledQueries; std::vector<std::string> m_unhandledQueries;
ReadCallback::Callback m_smtCallback;
}; };
} }

View File

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

View File

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

View File

@ -70,6 +70,7 @@ bool SMTEncoder::visit(ContractDefinition const& _contract)
for (auto const& function: resolvedFunctions) for (auto const& function: resolvedFunctions)
if ( if (
function->name() == baseFunction->name() && function->name() == baseFunction->name() &&
function->kind() == baseFunction->kind() &&
FunctionType(*function).asCallableFunction(false)-> FunctionType(*function).asCallableFunction(false)->
hasEqualParameterTypes(*FunctionType(*baseFunction).asCallableFunction(false)) hasEqualParameterTypes(*FunctionType(*baseFunction).asCallableFunction(false))
) )
@ -915,6 +916,15 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
m_uninterpretedTerms.insert(&_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() void SMTEncoder::arrayAssignment()
{ {
m_arrayAssignmentHappened = true; m_arrayAssignmentHappened = true;

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