mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9339 from ethereum/develop
Merge develop into release for 0.6.11.
This commit is contained in:
commit
5ef660b17a
@ -323,7 +323,7 @@ jobs:
|
|||||||
- checkout
|
- checkout
|
||||||
- run:
|
- run:
|
||||||
name: Check for error codes
|
name: Check for error codes
|
||||||
command: ./scripts/fix_error_ids.py --check-only
|
command: ./scripts/error_codes.py --check
|
||||||
|
|
||||||
chk_pylint:
|
chk_pylint:
|
||||||
docker:
|
docker:
|
||||||
|
12
.travis.yml
12
.travis.yml
@ -40,6 +40,8 @@ env:
|
|||||||
- ENCRYPTION_LABEL="6d4541b72666"
|
- ENCRYPTION_LABEL="6d4541b72666"
|
||||||
- SOLC_BUILD_TYPE=RelWithDebInfo
|
- SOLC_BUILD_TYPE=RelWithDebInfo
|
||||||
- SOLC_EMSCRIPTEN=Off
|
- SOLC_EMSCRIPTEN=Off
|
||||||
|
# FIXME: Pushing solcjson.js to solc-bin disabled until we fix the cause of #9261
|
||||||
|
- SOLC_PUBLISH_EMSCRIPTEN=Off
|
||||||
- SOLC_INSTALL_DEPS_TRAVIS=On
|
- SOLC_INSTALL_DEPS_TRAVIS=On
|
||||||
- SOLC_RELEASE=On
|
- SOLC_RELEASE=On
|
||||||
- SOLC_TESTS=On
|
- SOLC_TESTS=On
|
||||||
@ -104,13 +106,13 @@ matrix:
|
|||||||
sudo: required
|
sudo: required
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
node_js:
|
node_js:
|
||||||
- "8"
|
- "10"
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
before_install:
|
before_install:
|
||||||
- nvm install 8
|
- nvm install 10
|
||||||
- nvm use 8
|
- nvm use 10
|
||||||
- docker pull ethereum/solidity-buildpack-deps:emsdk-1.39.15-1
|
- docker pull ethereum/solidity-buildpack-deps:emsdk-1.39.15-2
|
||||||
env:
|
env:
|
||||||
- SOLC_EMSCRIPTEN=On
|
- SOLC_EMSCRIPTEN=On
|
||||||
- SOLC_INSTALL_DEPS_TRAVIS=Off
|
- SOLC_INSTALL_DEPS_TRAVIS=Off
|
||||||
@ -213,7 +215,7 @@ deploy:
|
|||||||
# scripts because TravisCI doesn't provide much in the way of conditional logic.
|
# scripts because TravisCI doesn't provide much in the way of conditional logic.
|
||||||
|
|
||||||
- provider: script
|
- provider: script
|
||||||
script: test $SOLC_EMSCRIPTEN != On || (scripts/release_emscripten.sh)
|
script: test $SOLC_PUBLISH_EMSCRIPTEN != On || $SOLC_EMSCRIPTEN != On || (scripts/release_emscripten.sh)
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
branch:
|
branch:
|
||||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
|||||||
eth_policy()
|
eth_policy()
|
||||||
|
|
||||||
# project name and version should be set after cmake_policy CMP0048
|
# project name and version should be set after cmake_policy CMP0048
|
||||||
set(PROJECT_VERSION "0.6.10")
|
set(PROJECT_VERSION "0.6.11")
|
||||||
# OSX target needed in order to support std::visit
|
# OSX target needed in order to support std::visit
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
||||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||||
|
29
Changelog.md
29
Changelog.md
@ -1,3 +1,32 @@
|
|||||||
|
### 0.6.11 (2020-07-07)
|
||||||
|
|
||||||
|
Language Features:
|
||||||
|
* General: Add unit denomination ``gwei``
|
||||||
|
* Yul: Support ``linkersymbol`` builtin in standalone assembly mode to refer to library addresses.
|
||||||
|
* Yul: Support using string literals exceeding 32 bytes as literal arguments for builtins.
|
||||||
|
|
||||||
|
|
||||||
|
Compiler Features:
|
||||||
|
* NatSpec: Add fields ``kind`` and ``version`` to the JSON output.
|
||||||
|
* NatSpec: Inherit tags from unique base functions if derived function does not provide any.
|
||||||
|
* Commandline Interface: Prevent some incompatible commandline options from being used together.
|
||||||
|
* NatSpec: Support NatSpec comments on events.
|
||||||
|
* Yul Optimizer: Store knowledge about storage / memory after ``a := sload(x)`` / ``a := mload(x)``.
|
||||||
|
* SMTChecker: Support external calls to unknown code.
|
||||||
|
* Source Maps: Also tag jumps into and out of Yul functions as jumps into and out of functions.
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* NatSpec: Do not consider ``////`` and ``/***`` as NatSpec comments.
|
||||||
|
* Type Checker: Disallow constructor parameters with ``calldata`` data location.
|
||||||
|
* Type Checker: Do not disallow assigning to calldata variables.
|
||||||
|
* Type Checker: Fix internal error related to ``using for`` applied to non-libraries.
|
||||||
|
* Wasm backend: Fix code generation for for-loops with pre statements.
|
||||||
|
* Wasm backend: Properly support both ``i32.drop`` and ``i64.drop``, and remove ``drop``.
|
||||||
|
* Yul: Disallow the same variable to occur multiple times on the left-hand side of an assignment.
|
||||||
|
* Yul: Fix source location of variable multi-assignment.
|
||||||
|
|
||||||
|
|
||||||
### 0.6.10 (2020-06-11)
|
### 0.6.10 (2020-06-11)
|
||||||
|
|
||||||
Important Bugfixes:
|
Important Bugfixes:
|
||||||
|
@ -11,23 +11,11 @@ sourceUnit
|
|||||||
: (pragmaDirective | importDirective | structDefinition | enumDefinition | contractDefinition)* EOF ;
|
: (pragmaDirective | importDirective | structDefinition | enumDefinition | contractDefinition)* EOF ;
|
||||||
|
|
||||||
pragmaDirective
|
pragmaDirective
|
||||||
: 'pragma' pragmaName pragmaValue ';' ;
|
: 'pragma' pragmaName ( ~';' )* ';' ;
|
||||||
|
|
||||||
pragmaName
|
pragmaName
|
||||||
: identifier ;
|
: identifier ;
|
||||||
|
|
||||||
pragmaValue
|
|
||||||
: version | expression ;
|
|
||||||
|
|
||||||
version
|
|
||||||
: versionConstraint versionConstraint? ;
|
|
||||||
|
|
||||||
versionConstraint
|
|
||||||
: versionOperator? VersionLiteral ;
|
|
||||||
|
|
||||||
versionOperator
|
|
||||||
: '^' | '~' | '>=' | '>' | '<' | '<=' | '=' ;
|
|
||||||
|
|
||||||
importDirective
|
importDirective
|
||||||
: 'import' StringLiteralFragment ('as' identifier)? ';'
|
: 'import' StringLiteralFragment ('as' identifier)? ';'
|
||||||
| 'import' ('*' | identifier) ('as' identifier)? 'from' StringLiteralFragment ';'
|
| 'import' ('*' | identifier) ('as' identifier)? 'from' StringLiteralFragment ';'
|
||||||
@ -371,10 +359,10 @@ subAssembly
|
|||||||
: 'assembly' identifier assemblyBlock ;
|
: 'assembly' identifier assemblyBlock ;
|
||||||
|
|
||||||
numberLiteral
|
numberLiteral
|
||||||
: (DecimalNumber | HexNumber) NumberUnit? ;
|
: (DecimalNumber | HexNumber) (NumberUnit | Gwei)?;
|
||||||
|
|
||||||
identifier
|
identifier
|
||||||
: ('from' | 'calldata' | 'address' | Identifier) ;
|
: (Gwei | 'from' | 'calldata' | 'address' | Identifier) ;
|
||||||
|
|
||||||
BooleanLiteral
|
BooleanLiteral
|
||||||
: 'true' | 'false' ;
|
: 'true' | 'false' ;
|
||||||
@ -397,6 +385,8 @@ NumberUnit
|
|||||||
: 'wei' | 'szabo' | 'finney' | 'ether'
|
: 'wei' | 'szabo' | 'finney' | 'ether'
|
||||||
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years' ;
|
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years' ;
|
||||||
|
|
||||||
|
Gwei: 'gwei' ;
|
||||||
|
|
||||||
HexLiteralFragment
|
HexLiteralFragment
|
||||||
: 'hex' (('"' HexDigits? '"') | ('\'' HexDigits? '\'')) ;
|
: 'hex' (('"' HexDigits? '"') | ('\'' HexDigits? '\'')) ;
|
||||||
|
|
||||||
@ -473,9 +463,6 @@ fragment
|
|||||||
SingleQuotedStringCharacter
|
SingleQuotedStringCharacter
|
||||||
: ~['\r\n\\] | ('\\' .) ;
|
: ~['\r\n\\] | ('\\' .) ;
|
||||||
|
|
||||||
VersionLiteral
|
|
||||||
: [0-9]+ ( '.' [0-9]+ ('.' [0-9]+)? )? ;
|
|
||||||
|
|
||||||
WS
|
WS
|
||||||
: [ \t\r\n\u000C]+ -> skip ;
|
: [ \t\r\n\u000C]+ -> skip ;
|
||||||
|
|
||||||
|
@ -1109,6 +1109,10 @@
|
|||||||
"bugs": [],
|
"bugs": [],
|
||||||
"released": "2020-06-11"
|
"released": "2020-06-11"
|
||||||
},
|
},
|
||||||
|
"0.6.11": {
|
||||||
|
"bugs": [],
|
||||||
|
"released": "2020-07-07"
|
||||||
|
},
|
||||||
"0.6.2": {
|
"0.6.2": {
|
||||||
"bugs": [
|
"bugs": [
|
||||||
"MissingEscapingInFormatting",
|
"MissingEscapingInFormatting",
|
||||||
|
@ -314,6 +314,16 @@ you should fork Solidity and add your personal fork as a second remote:
|
|||||||
|
|
||||||
git remote add personal git@github.com:[username]/solidity.git
|
git remote add personal git@github.com:[username]/solidity.git
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This method will result in a prerelease build leading to e.g. a flag
|
||||||
|
being set in each bytecode produced by such a compiler.
|
||||||
|
If you want to re-build a released Solidity compiler, then
|
||||||
|
please use the source tarball on the github release page:
|
||||||
|
|
||||||
|
https://github.com/ethereum/solidity/releases/download/v0.X.Y/solidity_0.X.Y.tar.gz
|
||||||
|
|
||||||
|
(not the "Source code" provided by github).
|
||||||
|
|
||||||
Command-Line Build
|
Command-Line Build
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -42,10 +42,6 @@ The following example shows a contract and a function using all available tags.
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
NatSpec currently does NOT apply to public state variables (see
|
|
||||||
`solidity#3418 <https://github.com/ethereum/solidity/issues/3418>`__),
|
|
||||||
even if they are declared public and therefore do affect the ABI.
|
|
||||||
|
|
||||||
The Solidity compiler only interprets tags if they are external or
|
The Solidity compiler only interprets tags if they are external or
|
||||||
public. You are welcome to use similar comments for your internal and
|
public. You are welcome to use similar comments for your internal and
|
||||||
private functions, but those will not be parsed.
|
private functions, but those will not be parsed.
|
||||||
@ -60,7 +56,6 @@ The following example shows a contract and a function using all available tags.
|
|||||||
/// @notice You can use this contract for only the most basic simulation
|
/// @notice You can use this contract for only the most basic simulation
|
||||||
/// @dev All function calls are currently implemented without side effects
|
/// @dev All function calls are currently implemented without side effects
|
||||||
contract Tree {
|
contract Tree {
|
||||||
/// @author Mary A. Botanist
|
|
||||||
/// @notice Calculate tree age in years, rounded up, for live trees
|
/// @notice Calculate tree age in years, rounded up, for live trees
|
||||||
/// @dev The Alexandr N. Tetearing algorithm could increase precision
|
/// @dev The Alexandr N. Tetearing algorithm could increase precision
|
||||||
/// @param rings The number of rings from dendrochronological sample
|
/// @param rings The number of rings from dendrochronological sample
|
||||||
@ -84,10 +79,10 @@ in the same way as if it were tagged with ``@notice``.
|
|||||||
Tag Context
|
Tag Context
|
||||||
=========== =============================================================================== =============================
|
=========== =============================================================================== =============================
|
||||||
``@title`` A title that should describe the contract/interface contract, interface
|
``@title`` A title that should describe the contract/interface contract, interface
|
||||||
``@author`` The name of the author contract, interface, function
|
``@author`` The name of the author contract, interface
|
||||||
``@notice`` Explain to an end user what this does contract, interface, function, public state variable
|
``@notice`` Explain to an end user what this does contract, interface, function, public state variable, event
|
||||||
``@dev`` Explain to a developer any extra details contract, interface, function, state variable
|
``@dev`` Explain to a developer any extra details contract, interface, function, state variable, event
|
||||||
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function
|
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function, event
|
||||||
``@return`` Documents the return variables of a contract's function function, public state variable
|
``@return`` Documents the return variables of a contract's function function, public state variable
|
||||||
=========== =============================================================================== =============================
|
=========== =============================================================================== =============================
|
||||||
|
|
||||||
@ -127,9 +122,11 @@ documentation and you may read more at
|
|||||||
Inheritance Notes
|
Inheritance Notes
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Currently it is undefined whether a contract with a function having no
|
Functions without NatSpec will automatically inherit the documentation of their
|
||||||
NatSpec will inherit the NatSpec of a parent contract/interface for that
|
base function. Exceptions to this are:
|
||||||
same function.
|
|
||||||
|
* When the parameter names are different.
|
||||||
|
* When there is more than one base function.
|
||||||
|
|
||||||
.. _header-output:
|
.. _header-output:
|
||||||
|
|
||||||
@ -193,7 +190,6 @@ file should also be produced and should look like this:
|
|||||||
{
|
{
|
||||||
"age(uint256)" :
|
"age(uint256)" :
|
||||||
{
|
{
|
||||||
"author" : "Mary A. Botanist",
|
|
||||||
"details" : "The Alexandr N. Tetearing algorithm could increase precision",
|
"details" : "The Alexandr N. Tetearing algorithm could increase precision",
|
||||||
"params" :
|
"params" :
|
||||||
{
|
{
|
||||||
|
@ -7,11 +7,12 @@ Units and Globally Available Variables
|
|||||||
Ether Units
|
Ether Units
|
||||||
===========
|
===========
|
||||||
|
|
||||||
A literal number can take a suffix of ``wei``, ``finney``, ``szabo`` or ``ether`` to specify a subdenomination of Ether, where Ether numbers without a postfix are assumed to be Wei.
|
A literal number can take a suffix of ``wei``, ``gwei``, ``finney``, ``szabo`` or ``ether`` to specify a subdenomination of Ether, where Ether numbers without a postfix are assumed to be Wei.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
assert(1 wei == 1);
|
assert(1 wei == 1);
|
||||||
|
assert(1 gwei == 1e9);
|
||||||
assert(1 szabo == 1e12);
|
assert(1 szabo == 1e12);
|
||||||
assert(1 finney == 1e15);
|
assert(1 finney == 1e15);
|
||||||
assert(1 ether == 1e18);
|
assert(1 ether == 1e18);
|
||||||
|
40
docs/yul.rst
40
docs/yul.rst
@ -30,7 +30,7 @@ The design of Yul tries to achieve several goals:
|
|||||||
In order to achieve the first and second goal, Yul provides high-level constructs
|
In order to achieve the first and second goal, Yul provides high-level constructs
|
||||||
like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should
|
like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should
|
||||||
be sufficient for adequately representing the control flow for assembly programs.
|
be sufficient for adequately representing the control flow for assembly programs.
|
||||||
Therefore, no explicit statements for ``SWAP``, ``DUP``, ``JUMP`` and ``JUMPI``
|
Therefore, no explicit statements for ``SWAP``, ``DUP``, ``JUMPDEST``, ``JUMP`` and ``JUMPI``
|
||||||
are provided, because the first two obfuscate the data flow
|
are provided, because the first two obfuscate the data flow
|
||||||
and the last two obfuscate control flow. Furthermore, functional statements of
|
and the last two obfuscate control flow. Furthermore, functional statements of
|
||||||
the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like
|
the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like
|
||||||
@ -180,7 +180,11 @@ appropriate ``PUSHi`` instruction. In the following example,
|
|||||||
``3`` and ``2`` are added resulting in 5 and then the
|
``3`` and ``2`` are added resulting in 5 and then the
|
||||||
bitwise ``and`` with the string "abc" is computed.
|
bitwise ``and`` with the string "abc" is computed.
|
||||||
The final value is assigned to a local variable called ``x``.
|
The final value is assigned to a local variable called ``x``.
|
||||||
|
|
||||||
Strings are stored left-aligned and cannot be longer than 32 bytes.
|
Strings are stored left-aligned and cannot be longer than 32 bytes.
|
||||||
|
The limit does not apply to string literals passed to builtin functions that require
|
||||||
|
literal arguments (e.g. ``setimmutable`` or ``loadimmutable``). Those strings never end up in the
|
||||||
|
generated bytecode.
|
||||||
|
|
||||||
.. code-block:: yul
|
.. code-block:: yul
|
||||||
|
|
||||||
@ -284,6 +288,8 @@ variables at the same time. For this, the number and types of the
|
|||||||
values have to match.
|
values have to match.
|
||||||
If you want to assign the values returned from a function that has
|
If you want to assign the values returned from a function that has
|
||||||
multiple return parameters, you have to provide multiple variables.
|
multiple return parameters, you have to provide multiple variables.
|
||||||
|
The same variable may not occur multiple times on the left-hand side of
|
||||||
|
an assignment, e.g. ``x, x := f()`` is invalid.
|
||||||
|
|
||||||
.. code-block:: yul
|
.. code-block:: yul
|
||||||
|
|
||||||
@ -502,6 +508,8 @@ In variable declarations and assignments, the right-hand-side expression
|
|||||||
variables on the left-hand-side.
|
variables on the left-hand-side.
|
||||||
This is the only situation where an expression evaluating
|
This is the only situation where an expression evaluating
|
||||||
to more than one value is allowed.
|
to more than one value is allowed.
|
||||||
|
The same variable name cannot occur more than once in the left-hand-side of
|
||||||
|
an assignment or variable declaration.
|
||||||
|
|
||||||
Expressions that are also statements (i.e. at the block level) have to
|
Expressions that are also statements (i.e. at the block level) have to
|
||||||
evaluate to zero values.
|
evaluate to zero values.
|
||||||
@ -904,7 +912,7 @@ In some internal dialects, there are additional functions:
|
|||||||
datasize, dataoffset, datacopy
|
datasize, dataoffset, datacopy
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The functions ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``,
|
The functions ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``
|
||||||
are used to access other parts of a Yul object.
|
are used to access other parts of a Yul object.
|
||||||
|
|
||||||
``datasize`` and ``dataoffset`` can only take string literals (the names of other objects)
|
``datasize`` and ``dataoffset`` can only take string literals (the names of other objects)
|
||||||
@ -916,13 +924,37 @@ setimmutable, loadimmutable
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The functions ``setimmutable("name", value)`` and ``loadimmutable("name")`` are
|
The functions ``setimmutable("name", value)`` and ``loadimmutable("name")`` are
|
||||||
used for the immutable mechanism in Solidity and do not nicely map to pur Yul.
|
used for the immutable mechanism in Solidity and do not nicely map to pure Yul.
|
||||||
The function ``setimmutable`` assumes that the runtime code of a contract
|
The function ``setimmutable`` assumes that the runtime code of a contract
|
||||||
is currently copied to memory at offsot zero. The call to ``setimmutable("name", value)``
|
is currently copied to memory at offset zero. The call to ``setimmutable("name", value)``
|
||||||
will store ``value`` at all points in memory that contain a call to
|
will store ``value`` at all points in memory that contain a call to
|
||||||
``loadimmutable("name")``.
|
``loadimmutable("name")``.
|
||||||
|
|
||||||
|
|
||||||
|
linkersymbol
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The function ``linkersymbol("fq_library_name")`` is a placeholder for an address literal to be
|
||||||
|
substituted by the linker. Its first and only argument must be a string literal and represents the
|
||||||
|
fully qualified library name used with the ``--libraries`` option.
|
||||||
|
|
||||||
|
For example this code
|
||||||
|
|
||||||
|
.. code-block:: yul
|
||||||
|
|
||||||
|
let a := linkersymbol("file.sol:Math")
|
||||||
|
|
||||||
|
is equivalent to
|
||||||
|
|
||||||
|
.. code-block:: yul
|
||||||
|
|
||||||
|
let a := 0x1234567890123456789012345678901234567890
|
||||||
|
|
||||||
|
when the linker is invoked with ``--libraries "file.sol:Math:0x1234567890123456789012345678901234567890``
|
||||||
|
option.
|
||||||
|
|
||||||
|
See :ref:`Using the Commandline Compiler <commandline-compiler>` for details about the Solidity linker.
|
||||||
|
|
||||||
|
|
||||||
.. _yul-object:
|
.. _yul-object:
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
|
|||||||
setData(data);
|
setData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
size_t AssemblyItem::bytesRequired(size_t _addressLength) const
|
||||||
{
|
{
|
||||||
switch (m_type)
|
switch (m_type)
|
||||||
{
|
{
|
||||||
@ -69,7 +69,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
|||||||
case PushString:
|
case PushString:
|
||||||
return 1 + 32;
|
return 1 + 32;
|
||||||
case Push:
|
case Push:
|
||||||
return 1 + max<unsigned>(1, util::bytesRequired(data()));
|
return 1 + max<size_t>(1, util::bytesRequired(data()));
|
||||||
case PushSubSize:
|
case PushSubSize:
|
||||||
case PushProgramSize:
|
case PushProgramSize:
|
||||||
return 1 + 4; // worst case: a 16MB program
|
return 1 + 4; // worst case: a 16MB program
|
||||||
|
@ -133,7 +133,7 @@ public:
|
|||||||
|
|
||||||
/// @returns an upper bound for the number of bytes required by this item, assuming that
|
/// @returns an upper bound for the number of bytes required by this item, assuming that
|
||||||
/// the value of a jump tag takes @a _addressLength bytes.
|
/// the value of a jump tag takes @a _addressLength bytes.
|
||||||
unsigned bytesRequired(unsigned _addressLength) const;
|
size_t bytesRequired(size_t _addressLength) const;
|
||||||
size_t arguments() const;
|
size_t arguments() const;
|
||||||
size_t returnValues() const;
|
size_t returnValues() const;
|
||||||
size_t deposit() const { return returnValues() - arguments(); }
|
size_t deposit() const { return returnValues() - arguments(); }
|
||||||
|
@ -107,7 +107,7 @@ tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
|
|||||||
using size_type = string::size_type;
|
using size_type = string::size_type;
|
||||||
using diff_type = string::difference_type;
|
using diff_type = string::difference_type;
|
||||||
size_type searchPosition = min<size_type>(m_source.size(), size_type(_position));
|
size_type searchPosition = min<size_type>(m_source.size(), size_type(_position));
|
||||||
int lineNumber = count(m_source.begin(), m_source.begin() + diff_type(searchPosition), '\n');
|
int lineNumber = static_cast<int>(count(m_source.begin(), m_source.begin() + diff_type(searchPosition), '\n'));
|
||||||
size_type lineStart;
|
size_type lineStart;
|
||||||
if (searchPosition == 0)
|
if (searchPosition == 0)
|
||||||
lineStart = 0;
|
lineStart = 0;
|
||||||
|
@ -38,6 +38,7 @@ class Error;
|
|||||||
using ErrorList = std::vector<std::shared_ptr<Error const>>;
|
using ErrorList = std::vector<std::shared_ptr<Error const>>;
|
||||||
|
|
||||||
struct CompilerError: virtual util::Exception {};
|
struct CompilerError: virtual util::Exception {};
|
||||||
|
struct StackTooDeepError: virtual CompilerError {};
|
||||||
struct InternalCompilerError: virtual util::Exception {};
|
struct InternalCompilerError: virtual util::Exception {};
|
||||||
struct FatalError: virtual util::Exception {};
|
struct FatalError: virtual util::Exception {};
|
||||||
struct UnimplementedFeatureError: virtual util::Exception {};
|
struct UnimplementedFeatureError: virtual util::Exception {};
|
||||||
@ -61,10 +62,14 @@ struct InvalidAstError: virtual util::Exception {};
|
|||||||
* They are passed as the first parameter of error reporting functions.
|
* They are passed as the first parameter of error reporting functions.
|
||||||
* Suffix _error helps to find them in the sources.
|
* Suffix _error helps to find them in the sources.
|
||||||
* The struct ErrorId prevents incidental calls like typeError(3141) instead of typeError(3141_error).
|
* The struct ErrorId prevents incidental calls like typeError(3141) instead of typeError(3141_error).
|
||||||
* To create a new ID, one can add 0000_error and then run "python ./scripts/fix_error_ids.py"
|
* To create a new ID, one can add 0000_error and then run "python ./scripts/error_codes.py --fix"
|
||||||
* from the root of the repo.
|
* from the root of the repo.
|
||||||
*/
|
*/
|
||||||
struct ErrorId { unsigned long long error = 0; };
|
struct ErrorId
|
||||||
|
{
|
||||||
|
unsigned long long error = 0;
|
||||||
|
bool operator==(ErrorId const& _rhs) const { return error == _rhs.error; }
|
||||||
|
};
|
||||||
constexpr ErrorId operator"" _error(unsigned long long _error) { return ErrorId{ _error }; }
|
constexpr ErrorId operator"" _error(unsigned long long _error) { return ErrorId{ _error }; }
|
||||||
|
|
||||||
class Error: virtual public util::Exception
|
class Error: virtual public util::Exception
|
||||||
|
@ -179,7 +179,7 @@ bool Scanner::scanHexByte(char& o_scannedByte)
|
|||||||
rollback(i);
|
rollback(i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
x = x * 16 + d;
|
x = static_cast<char>(x * 16 + d);
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
o_scannedByte = x;
|
o_scannedByte = x;
|
||||||
@ -197,7 +197,7 @@ std::optional<unsigned> Scanner::scanUnicode()
|
|||||||
rollback(i);
|
rollback(i);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
x = x * 16 + static_cast<size_t>(d);
|
x = x * 16 + static_cast<unsigned>(d);
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
@ -313,7 +313,6 @@ size_t Scanner::scanSingleLineDocComment()
|
|||||||
{
|
{
|
||||||
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
|
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
|
||||||
size_t endPosition = m_source->position();
|
size_t endPosition = m_source->position();
|
||||||
advance(); //consume the last '/' at ///
|
|
||||||
|
|
||||||
skipWhitespaceExceptUnicodeLinebreak();
|
skipWhitespaceExceptUnicodeLinebreak();
|
||||||
|
|
||||||
@ -332,6 +331,8 @@ size_t Scanner::scanSingleLineDocComment()
|
|||||||
m_source->get(1) == '/' &&
|
m_source->get(1) == '/' &&
|
||||||
m_source->get(2) == '/')
|
m_source->get(2) == '/')
|
||||||
{
|
{
|
||||||
|
if (!m_source->isPastEndOfInput(4) && m_source->get(3) == '/')
|
||||||
|
break; // "////" is not a documentation comment
|
||||||
m_char = m_source->advanceAndGet(3);
|
m_char = m_source->advanceAndGet(3);
|
||||||
if (atEndOfLine())
|
if (atEndOfLine())
|
||||||
continue;
|
continue;
|
||||||
@ -353,7 +354,6 @@ size_t Scanner::scanSingleLineDocComment()
|
|||||||
|
|
||||||
Token Scanner::skipMultiLineComment()
|
Token Scanner::skipMultiLineComment()
|
||||||
{
|
{
|
||||||
advance();
|
|
||||||
while (!isSourcePastEndOfInput())
|
while (!isSourcePastEndOfInput())
|
||||||
{
|
{
|
||||||
char ch = m_char;
|
char ch = m_char;
|
||||||
@ -437,6 +437,11 @@ Token Scanner::scanSlash()
|
|||||||
return Token::Whitespace;
|
return Token::Whitespace;
|
||||||
else if (m_char == '/')
|
else if (m_char == '/')
|
||||||
{
|
{
|
||||||
|
advance(); //consume the last '/' at ///
|
||||||
|
|
||||||
|
// "////"
|
||||||
|
if (m_char == '/')
|
||||||
|
return skipSingleLineComment();
|
||||||
// doxygen style /// comment
|
// doxygen style /// comment
|
||||||
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||||
m_skippedComments[NextNext].location.source = m_source;
|
m_skippedComments[NextNext].location.source = m_source;
|
||||||
@ -462,11 +467,14 @@ Token Scanner::scanSlash()
|
|||||||
advance(); //skip the closing slash
|
advance(); //skip the closing slash
|
||||||
return Token::Whitespace;
|
return Token::Whitespace;
|
||||||
}
|
}
|
||||||
|
// "/***"
|
||||||
|
if (m_char == '*')
|
||||||
|
// "/***/" may be interpreted as empty natspec or skipped; skipping is simpler
|
||||||
|
return skipMultiLineComment();
|
||||||
// we actually have a multiline documentation comment
|
// we actually have a multiline documentation comment
|
||||||
Token comment;
|
|
||||||
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||||
m_skippedComments[NextNext].location.source = m_source;
|
m_skippedComments[NextNext].location.source = m_source;
|
||||||
comment = scanMultiLineDocComment();
|
Token comment = scanMultiLineDocComment();
|
||||||
m_skippedComments[NextNext].location.end = static_cast<int>(sourcePos());
|
m_skippedComments[NextNext].location.end = static_cast<int>(sourcePos());
|
||||||
m_skippedComments[NextNext].token = comment;
|
m_skippedComments[NextNext].token = comment;
|
||||||
if (comment == Token::Illegal)
|
if (comment == Token::Illegal)
|
||||||
@ -754,17 +762,16 @@ bool Scanner::isUnicodeLinebreak()
|
|||||||
if (0x0a <= m_char && m_char <= 0x0d)
|
if (0x0a <= m_char && m_char <= 0x0d)
|
||||||
// line feed, vertical tab, form feed, carriage return
|
// line feed, vertical tab, form feed, carriage return
|
||||||
return true;
|
return true;
|
||||||
else if (!m_source->isPastEndOfInput(1) && uint8_t(m_source->get(0)) == 0xc2 && uint8_t(m_source->get(1)) == 0x85)
|
if (!m_source->isPastEndOfInput(1) && uint8_t(m_source->get(0)) == 0xc2 && uint8_t(m_source->get(1)) == 0x85)
|
||||||
// NEL - U+0085, C2 85 in utf8
|
// NEL - U+0085, C2 85 in utf8
|
||||||
return true;
|
return true;
|
||||||
else if (!m_source->isPastEndOfInput(2) && uint8_t(m_source->get(0)) == 0xe2 && uint8_t(m_source->get(1)) == 0x80 && (
|
if (!m_source->isPastEndOfInput(2) && uint8_t(m_source->get(0)) == 0xe2 && uint8_t(m_source->get(1)) == 0x80 && (
|
||||||
uint8_t(m_source->get(2)) == 0xa8 || uint8_t(m_source->get(2)) == 0xa9
|
uint8_t(m_source->get(2)) == 0xa8 || uint8_t(m_source->get(2)) == 0xa9
|
||||||
))
|
))
|
||||||
// LS - U+2028, E2 80 A8 in utf8
|
// LS - U+2028, E2 80 A8 in utf8
|
||||||
// PS - U+2029, E2 80 A9 in utf8
|
// PS - U+2029, E2 80 A9 in utf8
|
||||||
return true;
|
return true;
|
||||||
else
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Token Scanner::scanString()
|
Token Scanner::scanString()
|
||||||
|
@ -232,6 +232,7 @@ namespace solidity::langutil
|
|||||||
\
|
\
|
||||||
/* Identifiers (not keywords or future reserved words). */ \
|
/* Identifiers (not keywords or future reserved words). */ \
|
||||||
T(Identifier, nullptr, 0) \
|
T(Identifier, nullptr, 0) \
|
||||||
|
T(SubGwei, "gwei", 0) \
|
||||||
\
|
\
|
||||||
/* Keywords reserved for future use. */ \
|
/* Keywords reserved for future use. */ \
|
||||||
K(After, "after", 0) \
|
K(After, "after", 0) \
|
||||||
|
@ -48,9 +48,9 @@ public:
|
|||||||
|
|
||||||
// Z3 "basic resources" limit.
|
// Z3 "basic resources" limit.
|
||||||
// This is used to make the runs more deterministic and platform/machine independent.
|
// This is used to make the runs more deterministic and platform/machine independent.
|
||||||
// The tests start failing for Z3 with less than 20000000,
|
// The tests start failing for Z3 with less than 10000000,
|
||||||
// so using double that.
|
// so using double that.
|
||||||
static int const resourceLimit = 40000000;
|
static int const resourceLimit = 20000000;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void declareFunction(std::string const& _name, Sort const& _sort);
|
void declareFunction(std::string const& _name, Sort const& _sort);
|
||||||
|
@ -151,13 +151,13 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
|
|||||||
if constexpr (is_same_v<T, FunctionDefinition const*>)
|
if constexpr (is_same_v<T, FunctionDefinition const*>)
|
||||||
{
|
{
|
||||||
error = 1686_error;
|
error = 1686_error;
|
||||||
message = "Function with same name and arguments defined twice.";
|
message = "Function with same name and parameter types defined twice.";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static_assert(is_same_v<T, EventDefinition const*>, "Expected \"FunctionDefinition const*\" or \"EventDefinition const*\"");
|
static_assert(is_same_v<T, EventDefinition const*>, "Expected \"FunctionDefinition const*\" or \"EventDefinition const*\"");
|
||||||
error = 5883_error;
|
error = 5883_error;
|
||||||
message = "Event with same name and arguments defined twice.";
|
message = "Event with same name and parameter types defined twice.";
|
||||||
}
|
}
|
||||||
|
|
||||||
ssl.limitSize(message);
|
ssl.limitSize(message);
|
||||||
|
@ -395,6 +395,15 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeclarationTypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||||
|
{
|
||||||
|
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
|
||||||
|
_usingFor.libraryName().annotation().referencedDeclaration
|
||||||
|
);
|
||||||
|
if (!library || !library->isLibrary())
|
||||||
|
m_errorReporter.fatalTypeError(4357_error, _usingFor.libraryName().location(), "Library name expected.");
|
||||||
|
}
|
||||||
|
|
||||||
bool DeclarationTypeChecker::check(ASTNode const& _node)
|
bool DeclarationTypeChecker::check(ASTNode const& _node)
|
||||||
{
|
{
|
||||||
auto watcher = m_errorReporter.errorWatcher();
|
auto watcher = m_errorReporter.errorWatcher();
|
||||||
|
@ -58,6 +58,7 @@ private:
|
|||||||
void endVisit(ArrayTypeName const& _typeName) override;
|
void endVisit(ArrayTypeName const& _typeName) override;
|
||||||
void endVisit(VariableDeclaration const& _variable) override;
|
void endVisit(VariableDeclaration const& _variable) override;
|
||||||
bool visit(StructDefinition const& _struct) override;
|
bool visit(StructDefinition const& _struct) override;
|
||||||
|
void endVisit(UsingForDirective const& _usingForDirective) override;
|
||||||
|
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
|
@ -32,6 +32,33 @@ using namespace solidity;
|
|||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
void copyMissingTags(StructurallyDocumentedAnnotation& _target, set<CallableDeclaration const*> const& _baseFunctions)
|
||||||
|
{
|
||||||
|
if (_baseFunctions.size() != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& sourceDoc = dynamic_cast<StructurallyDocumentedAnnotation const&>((*_baseFunctions.begin())->annotation());
|
||||||
|
|
||||||
|
set<string> existingTags;
|
||||||
|
|
||||||
|
for (auto const& iterator: _target.docTags)
|
||||||
|
existingTags.insert(iterator.first);
|
||||||
|
|
||||||
|
for (auto const& [tag, content]: sourceDoc.docTags)
|
||||||
|
if (tag != "inheritdoc" && !existingTags.count(tag))
|
||||||
|
_target.docTags.emplace(tag, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parameterNamesEqual(CallableDeclaration const& _a, CallableDeclaration const& _b)
|
||||||
|
{
|
||||||
|
return boost::range::equal(_a.parameters(), _b.parameters(), [](auto const& pa, auto const& pb) { return pa->name() == pb->name(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
|
bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
|
||||||
{
|
{
|
||||||
auto errorWatcher = m_errorReporter.errorWatcher();
|
auto errorWatcher = m_errorReporter.errorWatcher();
|
||||||
@ -79,6 +106,9 @@ bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
|
|||||||
"Documentation tag @title and @author is only allowed on contract definitions. "
|
"Documentation tag @title and @author is only allowed on contract definitions. "
|
||||||
"It will be disallowed in 0.7.0."
|
"It will be disallowed in 0.7.0."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (_variable.annotation().docTags.empty())
|
||||||
|
copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -142,6 +172,20 @@ void DocStringAnalyser::handleCallable(
|
|||||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
||||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
parseDocStrings(_node, _annotation, validTags, "functions");
|
||||||
checkParameters(_callable, _node, _annotation);
|
checkParameters(_callable, _node, _annotation);
|
||||||
|
|
||||||
|
if (
|
||||||
|
_annotation.docTags.empty() &&
|
||||||
|
_callable.annotation().baseFunctions.size() == 1 &&
|
||||||
|
parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin())
|
||||||
|
)
|
||||||
|
copyMissingTags(_annotation, _callable.annotation().baseFunctions);
|
||||||
|
|
||||||
|
if (_node.documentation() && _annotation.docTags.count("author") > 0)
|
||||||
|
m_errorReporter.warning(
|
||||||
|
9843_error, _node.documentation()->location(),
|
||||||
|
"Documentation tag @author is only allowed on contract definitions. "
|
||||||
|
"It will be disallowed in 0.7.0."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocStringAnalyser::parseDocStrings(
|
void DocStringAnalyser::parseDocStrings(
|
||||||
|
@ -123,11 +123,13 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
|||||||
return !error;
|
return !error;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode)
|
bool NameAndTypeResolver::resolveNamesAndTypes(SourceUnit& _source)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return resolveNamesAndTypesInternal(_node, _resolveInsideCode);
|
for (shared_ptr<ASTNode> const& node: _source.nodes())
|
||||||
|
if (!resolveNamesAndTypesInternal(*node, true))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
catch (langutil::FatalError const&)
|
catch (langutil::FatalError const&)
|
||||||
{
|
{
|
||||||
@ -135,6 +137,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi
|
|||||||
throw; // Something is weird here, rather throw again.
|
throw; // Something is weird here, rather throw again.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
|
bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
|
||||||
@ -227,13 +230,14 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
|
|||||||
bool success = true;
|
bool success = true;
|
||||||
setScope(contract->scope());
|
setScope(contract->scope());
|
||||||
solAssert(!!m_currentScope, "");
|
solAssert(!!m_currentScope, "");
|
||||||
|
solAssert(_resolveInsideCode, "");
|
||||||
|
|
||||||
m_globalContext.setCurrentContract(*contract);
|
m_globalContext.setCurrentContract(*contract);
|
||||||
updateDeclaration(*m_globalContext.currentSuper());
|
updateDeclaration(*m_globalContext.currentSuper());
|
||||||
updateDeclaration(*m_globalContext.currentThis());
|
updateDeclaration(*m_globalContext.currentThis());
|
||||||
|
|
||||||
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
|
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
|
||||||
if (!resolveNamesAndTypes(*baseContract, true))
|
if (!resolveNamesAndTypesInternal(*baseContract, true))
|
||||||
success = false;
|
success = false;
|
||||||
|
|
||||||
setScope(contract);
|
setScope(contract);
|
||||||
@ -254,23 +258,20 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
|
|||||||
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
||||||
{
|
{
|
||||||
setScope(contract);
|
setScope(contract);
|
||||||
if (!resolveNamesAndTypes(*node, false))
|
if (!resolveNamesAndTypesInternal(*node, false))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!_resolveInsideCode)
|
|
||||||
return success;
|
|
||||||
|
|
||||||
setScope(contract);
|
setScope(contract);
|
||||||
|
|
||||||
// now resolve references inside the code
|
// now resolve references inside the code
|
||||||
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
||||||
{
|
{
|
||||||
setScope(contract);
|
setScope(contract);
|
||||||
if (!resolveNamesAndTypes(*node, true))
|
if (!resolveNamesAndTypesInternal(*node, true))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
|
@ -65,12 +65,9 @@ public:
|
|||||||
bool registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope = nullptr);
|
bool registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope = nullptr);
|
||||||
/// Applies the effect of import directives.
|
/// Applies the effect of import directives.
|
||||||
bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits);
|
bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits);
|
||||||
/// Resolves all names and types referenced from the given AST Node.
|
/// Resolves all names and types referenced from the given Source Node.
|
||||||
/// This is usually only called at the contract level, but with a bit of care, it can also
|
|
||||||
/// be called at deeper levels.
|
|
||||||
/// @param _resolveInsideCode if false, does not descend into nodes that contain code.
|
|
||||||
/// @returns false in case of error.
|
/// @returns false in case of error.
|
||||||
bool resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode = true);
|
bool resolveNamesAndTypes(SourceUnit& _source);
|
||||||
/// Updates the given global declaration (used for "this"). Not to be used with declarations
|
/// Updates the given global declaration (used for "this"). Not to be used with declarations
|
||||||
/// that create their own scope.
|
/// that create their own scope.
|
||||||
/// @returns false in case of error.
|
/// @returns false in case of error.
|
||||||
|
@ -514,7 +514,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!_overriding.overrides())
|
if (!_overriding.overrides())
|
||||||
overrideError(_overriding, _super, 9456_error, "Overriding " + _overriding.astNodeName() + " is missing \"override\" specifier.");
|
overrideError(
|
||||||
|
_overriding,
|
||||||
|
_super,
|
||||||
|
9456_error,
|
||||||
|
"Overriding " + _overriding.astNodeName() + " is missing \"override\" specifier.",
|
||||||
|
"Overridden " + _overriding.astNodeName() + " is here:"
|
||||||
|
);
|
||||||
|
|
||||||
if (_super.isVariable())
|
if (_super.isVariable())
|
||||||
overrideError(
|
overrideError(
|
||||||
@ -536,7 +542,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
|||||||
if (_overriding.isVariable())
|
if (_overriding.isVariable())
|
||||||
{
|
{
|
||||||
if (_super.visibility() != Visibility::External)
|
if (_super.visibility() != Visibility::External)
|
||||||
overrideError(_overriding, _super, 5225_error, "Public state variables can only override functions with external visibility.");
|
overrideError(
|
||||||
|
_overriding,
|
||||||
|
_super,
|
||||||
|
5225_error,
|
||||||
|
"Public state variables can only override functions with external visibility.",
|
||||||
|
"Overridden function is here:"
|
||||||
|
);
|
||||||
solAssert(_overriding.visibility() == Visibility::External, "");
|
solAssert(_overriding.visibility() == Visibility::External, "");
|
||||||
}
|
}
|
||||||
else if (_overriding.visibility() != _super.visibility())
|
else if (_overriding.visibility() != _super.visibility())
|
||||||
@ -547,7 +559,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
|||||||
_super.visibility() == Visibility::External &&
|
_super.visibility() == Visibility::External &&
|
||||||
_overriding.visibility() == Visibility::Public
|
_overriding.visibility() == Visibility::Public
|
||||||
))
|
))
|
||||||
overrideError(_overriding, _super, 9098_error, "Overriding " + _overriding.astNodeName() + " visibility differs.");
|
overrideError(
|
||||||
|
_overriding,
|
||||||
|
_super,
|
||||||
|
9098_error,
|
||||||
|
"Overriding " + _overriding.astNodeName() + " visibility differs.",
|
||||||
|
"Overridden " + _overriding.astNodeName() + " is here:"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_super.isFunction())
|
if (_super.isFunction())
|
||||||
@ -558,7 +576,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
|||||||
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
|
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
|
||||||
|
|
||||||
if (!functionType->hasEqualReturnTypes(*superType))
|
if (!functionType->hasEqualReturnTypes(*superType))
|
||||||
overrideError(_overriding, _super, 4822_error, "Overriding " + _overriding.astNodeName() + " return types differ.");
|
overrideError(
|
||||||
|
_overriding,
|
||||||
|
_super,
|
||||||
|
4822_error,
|
||||||
|
"Overriding " + _overriding.astNodeName() + " return types differ.",
|
||||||
|
"Overridden " + _overriding.astNodeName() + " is here:"
|
||||||
|
);
|
||||||
|
|
||||||
// This is only relevant for a function overriding a function.
|
// This is only relevant for a function overriding a function.
|
||||||
if (_overriding.isFunction())
|
if (_overriding.isFunction())
|
||||||
|
@ -158,11 +158,22 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
|
|||||||
else if (_variable.isStateVariable())
|
else if (_variable.isStateVariable())
|
||||||
{
|
{
|
||||||
set<StructDefinition const*> structsSeen;
|
set<StructDefinition const*> structsSeen;
|
||||||
if (structureSizeEstimate(*_variable.type(), structsSeen) >= bigint(1) << 64)
|
TypeSet oversizedSubTypes;
|
||||||
|
if (structureSizeEstimate(*_variable.type(), structsSeen, oversizedSubTypes) >= bigint(1) << 64)
|
||||||
m_errorReporter.warning(
|
m_errorReporter.warning(
|
||||||
3408_error,
|
3408_error,
|
||||||
_variable.location(),
|
_variable.location(),
|
||||||
"Variable covers a large part of storage and thus makes collisions likely. "
|
"Variable " + util::escapeAndQuoteString(_variable.name()) +
|
||||||
|
" covers a large part of storage and thus makes collisions likely. "
|
||||||
|
"Either use mappings or dynamic arrays and allow their size to be increased only "
|
||||||
|
"in small quantities per transaction."
|
||||||
|
);
|
||||||
|
for (Type const* type: oversizedSubTypes)
|
||||||
|
m_errorReporter.warning(
|
||||||
|
7325_error,
|
||||||
|
_variable.location(),
|
||||||
|
"Type " + util::escapeAndQuoteString(type->canonicalName()) +
|
||||||
|
" has large size and thus makes collisions likely. "
|
||||||
"Either use mappings or dynamic arrays and allow their size to be increased only "
|
"Either use mappings or dynamic arrays and allow their size to be increased only "
|
||||||
"in small quantities per transaction."
|
"in small quantities per transaction."
|
||||||
);
|
);
|
||||||
@ -339,30 +350,43 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bigint StaticAnalyzer::structureSizeEstimate(Type const& _type, set<StructDefinition const*>& _structsSeen)
|
bigint StaticAnalyzer::structureSizeEstimate(
|
||||||
|
Type const& _type,
|
||||||
|
set<StructDefinition const*>& _structsSeen,
|
||||||
|
TypeSet& _oversizedSubTypes
|
||||||
|
)
|
||||||
{
|
{
|
||||||
switch (_type.category())
|
switch (_type.category())
|
||||||
{
|
{
|
||||||
case Type::Category::Array:
|
case Type::Category::Array:
|
||||||
{
|
{
|
||||||
auto const& t = dynamic_cast<ArrayType const&>(_type);
|
auto const& t = dynamic_cast<ArrayType const&>(_type);
|
||||||
return structureSizeEstimate(*t.baseType(), _structsSeen) * (t.isDynamicallySized() ? 1 : t.length());
|
bigint baseTypeSize = structureSizeEstimate(*t.baseType(), _structsSeen, _oversizedSubTypes);
|
||||||
|
if (baseTypeSize >= bigint(1) << 64)
|
||||||
|
_oversizedSubTypes.insert(t.baseType());
|
||||||
|
if (!t.isDynamicallySized())
|
||||||
|
return structureSizeEstimate(*t.baseType(), _structsSeen, _oversizedSubTypes) * t.length();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case Type::Category::Struct:
|
case Type::Category::Struct:
|
||||||
{
|
{
|
||||||
auto const& t = dynamic_cast<StructType const&>(_type);
|
auto const& t = dynamic_cast<StructType const&>(_type);
|
||||||
bigint size = 1;
|
bigint size = 1;
|
||||||
if (!_structsSeen.count(&t.structDefinition()))
|
if (_structsSeen.count(&t.structDefinition()))
|
||||||
{
|
return size;
|
||||||
_structsSeen.insert(&t.structDefinition());
|
_structsSeen.insert(&t.structDefinition());
|
||||||
for (auto const& m: t.members(nullptr))
|
for (auto const& m: t.members(nullptr))
|
||||||
size += structureSizeEstimate(*m.type, _structsSeen);
|
size += structureSizeEstimate(*m.type, _structsSeen, _oversizedSubTypes);
|
||||||
}
|
_structsSeen.erase(&t.structDefinition());
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
case Type::Category::Mapping:
|
case Type::Category::Mapping:
|
||||||
{
|
{
|
||||||
return structureSizeEstimate(*dynamic_cast<MappingType const&>(_type).valueType(), _structsSeen);
|
auto const* valueType = dynamic_cast<MappingType const&>(_type).valueType();
|
||||||
|
bigint valueTypeSize = structureSizeEstimate(*valueType, _structsSeen, _oversizedSubTypes);
|
||||||
|
if (valueTypeSize >= bigint(1) << 64)
|
||||||
|
_oversizedSubTypes.insert(valueType);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -73,8 +73,22 @@ private:
|
|||||||
bool visit(BinaryOperation const& _operation) override;
|
bool visit(BinaryOperation const& _operation) override;
|
||||||
bool visit(FunctionCall const& _functionCall) override;
|
bool visit(FunctionCall const& _functionCall) override;
|
||||||
|
|
||||||
|
struct TypeComp
|
||||||
|
{
|
||||||
|
bool operator()(Type const* lhs, Type const* rhs) const
|
||||||
|
{
|
||||||
|
solAssert(lhs && rhs, "");
|
||||||
|
return lhs->richIdentifier() < rhs->richIdentifier();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using TypeSet = std::set<Type const*, TypeComp>;
|
||||||
|
|
||||||
/// @returns the size of this type in storage, including all sub-types.
|
/// @returns the size of this type in storage, including all sub-types.
|
||||||
static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen);
|
static bigint structureSizeEstimate(
|
||||||
|
Type const& _type,
|
||||||
|
std::set<StructDefinition const*>& _structsSeen,
|
||||||
|
TypeSet& _oversizedSubTypes
|
||||||
|
);
|
||||||
|
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
|
||||||
|
@ -62,9 +62,11 @@ bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibrar
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
|
bool TypeChecker::checkTypeRequirements(SourceUnit const& _source)
|
||||||
{
|
{
|
||||||
_contract.accept(*this);
|
m_currentSourceUnit = &_source;
|
||||||
|
_source.accept(*this);
|
||||||
|
m_currentSourceUnit = nullptr;
|
||||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,15 +313,6 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
|
||||||
{
|
|
||||||
ContractDefinition const* library = dynamic_cast<ContractDefinition const*>(
|
|
||||||
_usingFor.libraryName().annotation().referencedDeclaration
|
|
||||||
);
|
|
||||||
if (!library || !library->isLibrary())
|
|
||||||
m_errorReporter.fatalTypeError(4357_error, _usingFor.libraryName().location(), "Library name expected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
|
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
|
||||||
{
|
{
|
||||||
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
|
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
|
||||||
@ -373,7 +366,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
_function.isPublic() &&
|
_function.isPublic() &&
|
||||||
!_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
|
||||||
!typeSupportedByOldABIEncoder(*type(var), _function.libraryFunction())
|
!typeSupportedByOldABIEncoder(*type(var), _function.libraryFunction())
|
||||||
)
|
)
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
@ -511,7 +504,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
|||||||
else if (_variable.visibility() >= Visibility::Public)
|
else if (_variable.visibility() >= Visibility::Public)
|
||||||
{
|
{
|
||||||
FunctionType getter(_variable);
|
FunctionType getter(_variable);
|
||||||
if (!_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
|
if (!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
|
||||||
{
|
{
|
||||||
vector<string> unsupportedTypes;
|
vector<string> unsupportedTypes;
|
||||||
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
|
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
|
||||||
@ -622,7 +615,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
|
|||||||
if (!type(*var)->interfaceType(false))
|
if (!type(*var)->interfaceType(false))
|
||||||
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
||||||
if (
|
if (
|
||||||
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
|
||||||
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
|
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
|
||||||
)
|
)
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
@ -666,17 +659,18 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
{
|
{
|
||||||
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
|
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
|
||||||
if (ref == _inlineAssembly.annotation().externalReferences.end())
|
if (ref == _inlineAssembly.annotation().externalReferences.end())
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
Declaration const* declaration = ref->second.declaration;
|
InlineAssemblyAnnotation::ExternalIdentifierInfo& identifierInfo = ref->second;
|
||||||
|
Declaration const* declaration = identifierInfo.declaration;
|
||||||
solAssert(!!declaration, "");
|
solAssert(!!declaration, "");
|
||||||
bool requiresStorage = ref->second.isSlot || ref->second.isOffset;
|
bool requiresStorage = identifierInfo.isSlot || identifierInfo.isOffset;
|
||||||
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
|
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||||
{
|
{
|
||||||
solAssert(var->type(), "Expected variable type!");
|
solAssert(var->type(), "Expected variable type!");
|
||||||
if (var->immutable())
|
if (var->immutable())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(3773_error, _identifier.location, "Assembly access to immutable variables is not supported.");
|
m_errorReporter.typeError(3773_error, _identifier.location, "Assembly access to immutable variables is not supported.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
if (var->isConstant())
|
if (var->isConstant())
|
||||||
{
|
{
|
||||||
@ -685,17 +679,17 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
if (var && !var->value())
|
if (var && !var->value())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(3224_error, _identifier.location, "Constant has no value.");
|
m_errorReporter.typeError(3224_error, _identifier.location, "Constant has no value.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (_context == yul::IdentifierContext::LValue)
|
else if (_context == yul::IdentifierContext::LValue)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(6252_error, _identifier.location, "Constant variables cannot be assigned to.");
|
m_errorReporter.typeError(6252_error, _identifier.location, "Constant variables cannot be assigned to.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (requiresStorage)
|
else if (requiresStorage)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(6617_error, _identifier.location, "The suffixes _offset and _slot can only be used on non-constant storage variables.");
|
m_errorReporter.typeError(6617_error, _identifier.location, "The suffixes _offset and _slot can only be used on non-constant storage variables.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get()))
|
else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get()))
|
||||||
{
|
{
|
||||||
@ -704,7 +698,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
_identifier.location,
|
_identifier.location,
|
||||||
"Constant variables with non-literal values cannot be forward referenced from inline assembly."
|
"Constant variables with non-literal values cannot be forward referenced from inline assembly."
|
||||||
);
|
);
|
||||||
return size_t(-1);
|
return false;
|
||||||
}
|
}
|
||||||
else if (!var || !type(*var)->isValueType() || (
|
else if (!var || !type(*var)->isValueType() || (
|
||||||
!dynamic_cast<Literal const*>(var->value().get()) &&
|
!dynamic_cast<Literal const*>(var->value().get()) &&
|
||||||
@ -712,7 +706,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
))
|
))
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(7615_error, _identifier.location, "Only direct number constants and references to such constants are supported by inline assembly.");
|
m_errorReporter.typeError(7615_error, _identifier.location, "Only direct number constants and references to such constants are supported by inline assembly.");
|
||||||
return size_t(-1);
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -723,33 +717,33 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
|
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(3622_error, _identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
|
m_errorReporter.typeError(3622_error, _identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (_context == yul::IdentifierContext::LValue)
|
else if (_context == yul::IdentifierContext::LValue)
|
||||||
{
|
{
|
||||||
if (var->isStateVariable())
|
if (var->isStateVariable())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(4713_error, _identifier.location, "State variables cannot be assigned to - you have to use \"sstore()\".");
|
m_errorReporter.typeError(4713_error, _identifier.location, "State variables cannot be assigned to - you have to use \"sstore()\".");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (ref->second.isOffset)
|
else if (identifierInfo.isOffset)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(9739_error, _identifier.location, "Only _slot can be assigned to.");
|
m_errorReporter.typeError(9739_error, _identifier.location, "Only _slot can be assigned to.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(ref->second.isSlot, "");
|
solAssert(identifierInfo.isSlot, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!var->isConstant() && var->isStateVariable())
|
else if (!var->isConstant() && var->isStateVariable())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(1408_error, _identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
|
m_errorReporter.typeError(1408_error, _identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (var->type()->dataStoredIn(DataLocation::Storage))
|
else if (var->type()->dataStoredIn(DataLocation::Storage))
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(9068_error, _identifier.location, "You have to use the _slot or _offset suffix to access storage reference variables.");
|
m_errorReporter.typeError(9068_error, _identifier.location, "You have to use the _slot or _offset suffix to access storage reference variables.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (var->type()->sizeOnStack() != 1)
|
else if (var->type()->sizeOnStack() != 1)
|
||||||
{
|
{
|
||||||
@ -757,21 +751,21 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
m_errorReporter.typeError(2370_error, _identifier.location, "Call data elements cannot be accessed directly. Copy to a local variable first or use \"calldataload\" or \"calldatacopy\" with manually determined offsets and sizes.");
|
m_errorReporter.typeError(2370_error, _identifier.location, "Call data elements cannot be accessed directly. Copy to a local variable first or use \"calldataload\" or \"calldatacopy\" with manually determined offsets and sizes.");
|
||||||
else
|
else
|
||||||
m_errorReporter.typeError(9857_error, _identifier.location, "Only types that use one stack slot are supported.");
|
m_errorReporter.typeError(9857_error, _identifier.location, "Only types that use one stack slot are supported.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (requiresStorage)
|
else if (requiresStorage)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(7944_error, _identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
|
m_errorReporter.typeError(7944_error, _identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (_context == yul::IdentifierContext::LValue)
|
else if (_context == yul::IdentifierContext::LValue)
|
||||||
{
|
{
|
||||||
if (dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
if (dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
|
|
||||||
m_errorReporter.typeError(1990_error, _identifier.location, "Only local variables can be assigned to in inline assembly.");
|
m_errorReporter.typeError(1990_error, _identifier.location, "Only local variables can be assigned to in inline assembly.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_context == yul::IdentifierContext::RValue)
|
if (_context == yul::IdentifierContext::RValue)
|
||||||
@ -780,7 +774,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
if (dynamic_cast<FunctionDefinition const*>(declaration))
|
if (dynamic_cast<FunctionDefinition const*>(declaration))
|
||||||
{
|
{
|
||||||
m_errorReporter.declarationError(2025_error, _identifier.location, "Access to functions is not allowed in inline assembly.");
|
m_errorReporter.declarationError(2025_error, _identifier.location, "Access to functions is not allowed in inline assembly.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
else if (dynamic_cast<VariableDeclaration const*>(declaration))
|
else if (dynamic_cast<VariableDeclaration const*>(declaration))
|
||||||
{
|
{
|
||||||
@ -790,14 +784,14 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
if (!contract->isLibrary())
|
if (!contract->isLibrary())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(4977_error, _identifier.location, "Expected a library.");
|
m_errorReporter.typeError(4977_error, _identifier.location, "Expected a library.");
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
}
|
}
|
||||||
ref->second.valueSize = 1;
|
identifierInfo.valueSize = 1;
|
||||||
return size_t(1);
|
return true;
|
||||||
};
|
};
|
||||||
solAssert(!_inlineAssembly.annotation().analysisInfo, "");
|
solAssert(!_inlineAssembly.annotation().analysisInfo, "");
|
||||||
_inlineAssembly.annotation().analysisInfo = make_shared<yul::AsmAnalysisInfo>();
|
_inlineAssembly.annotation().analysisInfo = make_shared<yul::AsmAnalysisInfo>();
|
||||||
@ -1342,7 +1336,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
|
|||||||
_conditional.location(),
|
_conditional.location(),
|
||||||
"True expression's type " +
|
"True expression's type " +
|
||||||
trueType->toString() +
|
trueType->toString() +
|
||||||
" doesn't match false expression's type " +
|
" does not match false expression's type " +
|
||||||
falseType->toString() +
|
falseType->toString() +
|
||||||
"."
|
"."
|
||||||
);
|
);
|
||||||
@ -1912,9 +1906,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
|
|||||||
bool const isPacked = _functionType->kind() == FunctionType::Kind::ABIEncodePacked;
|
bool const isPacked = _functionType->kind() == FunctionType::Kind::ABIEncodePacked;
|
||||||
solAssert(_functionType->padArguments() != isPacked, "ABI function with unexpected padding");
|
solAssert(_functionType->padArguments() != isPacked, "ABI function with unexpected padding");
|
||||||
|
|
||||||
bool const abiEncoderV2 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(
|
bool const abiEncoderV2 = experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2);
|
||||||
ExperimentalFeature::ABIEncoderV2
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for named arguments
|
// Check for named arguments
|
||||||
if (!_functionCall.names().empty())
|
if (!_functionCall.names().empty())
|
||||||
@ -2311,11 +2303,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
|||||||
{
|
{
|
||||||
case FunctionType::Kind::ABIDecode:
|
case FunctionType::Kind::ABIDecode:
|
||||||
{
|
{
|
||||||
bool const abiEncoderV2 =
|
returnTypes = typeCheckABIDecodeAndRetrieveReturnType(
|
||||||
m_currentContract->sourceUnit().annotation().experimentalFeatures.count(
|
_functionCall,
|
||||||
ExperimentalFeature::ABIEncoderV2
|
experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
|
||||||
);
|
);
|
||||||
returnTypes = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FunctionType::Kind::ABIEncode:
|
case FunctionType::Kind::ABIEncode:
|
||||||
@ -2713,6 +2704,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
|||||||
annotation.isPure = _memberAccess.expression().annotation().isPure;
|
annotation.isPure = _memberAccess.expression().annotation().isPure;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (exprType->category() == Type::Category::Module)
|
||||||
|
annotation.isPure = _memberAccess.expression().annotation().isPure;
|
||||||
|
|
||||||
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
|
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
|
||||||
// although every subexpression is, so leaving this limited for now.
|
// although every subexpression is, so leaving this limited for now.
|
||||||
@ -3062,7 +3055,8 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
|||||||
}
|
}
|
||||||
else if (dynamic_cast<TypeType const*>(annotation.type))
|
else if (dynamic_cast<TypeType const*>(annotation.type))
|
||||||
annotation.isPure = true;
|
annotation.isPure = true;
|
||||||
|
else if (dynamic_cast<ModuleType const*>(annotation.type))
|
||||||
|
annotation.isPure = true;
|
||||||
|
|
||||||
// Check for deprecated function names.
|
// Check for deprecated function names.
|
||||||
// The check is done here for the case without an actual function call.
|
// The check is done here for the case without an actual function call.
|
||||||
@ -3256,3 +3250,11 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
|
|||||||
|
|
||||||
m_errorReporter.typeError(errorId, _expression.location(), description);
|
m_errorReporter.typeError(errorId, _expression.location(), description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TypeChecker::experimentalFeatureActive(ExperimentalFeature _feature) const
|
||||||
|
{
|
||||||
|
solAssert(m_currentSourceUnit, "");
|
||||||
|
if (m_currentContract)
|
||||||
|
solAssert(m_currentSourceUnit == &m_currentContract->sourceUnit(), "");
|
||||||
|
return m_currentSourceUnit->annotation().experimentalFeatures.count(_feature);
|
||||||
|
}
|
||||||
|
@ -51,9 +51,9 @@ public:
|
|||||||
m_errorReporter(_errorReporter)
|
m_errorReporter(_errorReporter)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// Performs type checking on the given contract and all of its sub-nodes.
|
/// Performs type checking on the given source and all of its sub-nodes.
|
||||||
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
|
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
|
||||||
bool checkTypeRequirements(ASTNode const& _contract);
|
bool checkTypeRequirements(SourceUnit const& _source);
|
||||||
|
|
||||||
/// @returns the type of an expression and asserts that it is present.
|
/// @returns the type of an expression and asserts that it is present.
|
||||||
TypePointer const& type(Expression const& _expression) const;
|
TypePointer const& type(Expression const& _expression) const;
|
||||||
@ -111,7 +111,6 @@ private:
|
|||||||
);
|
);
|
||||||
|
|
||||||
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
||||||
void endVisit(UsingForDirective const& _usingFor) override;
|
|
||||||
void endVisit(ModifierDefinition const& _modifier) override;
|
void endVisit(ModifierDefinition const& _modifier) override;
|
||||||
bool visit(FunctionDefinition const& _function) override;
|
bool visit(FunctionDefinition const& _function) override;
|
||||||
bool visit(VariableDeclaration const& _variable) override;
|
bool visit(VariableDeclaration const& _variable) override;
|
||||||
@ -165,6 +164,9 @@ private:
|
|||||||
/// Runs type checks on @a _expression to infer its type and then checks that it is an LValue.
|
/// Runs type checks on @a _expression to infer its type and then checks that it is an LValue.
|
||||||
void requireLValue(Expression const& _expression, bool _ordinaryAssignment);
|
void requireLValue(Expression const& _expression, bool _ordinaryAssignment);
|
||||||
|
|
||||||
|
bool experimentalFeatureActive(ExperimentalFeature _feature) const;
|
||||||
|
|
||||||
|
SourceUnit const* m_currentSourceUnit = nullptr;
|
||||||
ContractDefinition const* m_currentContract = nullptr;
|
ContractDefinition const* m_currentContract = nullptr;
|
||||||
|
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
|
@ -492,12 +492,7 @@ DeclarationAnnotation& Declaration::annotation() const
|
|||||||
bool VariableDeclaration::isLValue() const
|
bool VariableDeclaration::isLValue() const
|
||||||
{
|
{
|
||||||
// Constant declared variables are Read-Only
|
// Constant declared variables are Read-Only
|
||||||
if (isConstant())
|
return !isConstant();
|
||||||
return false;
|
|
||||||
// External function arguments of reference type are Read-Only
|
|
||||||
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VariableDeclaration::isLocalVariable() const
|
bool VariableDeclaration::isLocalVariable() const
|
||||||
@ -593,6 +588,15 @@ bool VariableDeclaration::isInternalCallableParameter() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VariableDeclaration::isConstructorParameter() const
|
||||||
|
{
|
||||||
|
if (!isCallableOrCatchParameter())
|
||||||
|
return false;
|
||||||
|
if (auto const* function = dynamic_cast<FunctionDefinition const*>(scope()))
|
||||||
|
return function->isConstructor();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool VariableDeclaration::isLibraryFunctionParameter() const
|
bool VariableDeclaration::isLibraryFunctionParameter() const
|
||||||
{
|
{
|
||||||
if (!isCallableOrCatchParameter())
|
if (!isCallableOrCatchParameter())
|
||||||
@ -627,7 +631,7 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
|
|||||||
set<Location> locations{ Location::Memory };
|
set<Location> locations{ Location::Memory };
|
||||||
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
|
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
|
||||||
locations.insert(Location::Storage);
|
locations.insert(Location::Storage);
|
||||||
if (!isTryCatchParameter())
|
if (!isTryCatchParameter() && !isConstructorParameter())
|
||||||
locations.insert(Location::CallData);
|
locations.insert(Location::CallData);
|
||||||
|
|
||||||
return locations;
|
return locations;
|
||||||
|
@ -928,6 +928,7 @@ public:
|
|||||||
/// @returns true if this variable is a parameter or return parameter of an internal function
|
/// @returns true if this variable is a parameter or return parameter of an internal function
|
||||||
/// or a function type of internal visibility.
|
/// or a function type of internal visibility.
|
||||||
bool isInternalCallableParameter() const;
|
bool isInternalCallableParameter() const;
|
||||||
|
bool isConstructorParameter() const;
|
||||||
/// @returns true iff this variable is a parameter(or return parameter of a library function
|
/// @returns true iff this variable is a parameter(or return parameter of a library function
|
||||||
bool isLibraryFunctionParameter() const;
|
bool isLibraryFunctionParameter() const;
|
||||||
/// @returns true if the type of the variable does not need to be specified, i.e. it is declared
|
/// @returns true if the type of the variable does not need to be specified, i.e. it is declared
|
||||||
@ -2084,6 +2085,7 @@ public:
|
|||||||
{
|
{
|
||||||
None = static_cast<int>(Token::Illegal),
|
None = static_cast<int>(Token::Illegal),
|
||||||
Wei = static_cast<int>(Token::SubWei),
|
Wei = static_cast<int>(Token::SubWei),
|
||||||
|
Gwei = static_cast<int>(Token::SubGwei),
|
||||||
Szabo = static_cast<int>(Token::SubSzabo),
|
Szabo = static_cast<int>(Token::SubSzabo),
|
||||||
Finney = static_cast<int>(Token::SubFinney),
|
Finney = static_cast<int>(Token::SubFinney),
|
||||||
Ether = static_cast<int>(Token::SubEther),
|
Ether = static_cast<int>(Token::SubEther),
|
||||||
|
@ -1001,6 +1001,8 @@ Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _no
|
|||||||
|
|
||||||
if (subDenStr == "wei")
|
if (subDenStr == "wei")
|
||||||
return Literal::SubDenomination::Wei;
|
return Literal::SubDenomination::Wei;
|
||||||
|
else if (subDenStr == "gwei")
|
||||||
|
return Literal::SubDenomination::Gwei;
|
||||||
else if (subDenStr == "szabo")
|
else if (subDenStr == "szabo")
|
||||||
return Literal::SubDenomination::Szabo;
|
return Literal::SubDenomination::Szabo;
|
||||||
else if (subDenStr == "finney")
|
else if (subDenStr == "finney")
|
||||||
|
@ -311,10 +311,15 @@ TypePointer Type::commonType(Type const* _a, Type const* _b)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList const& Type::members(ContractDefinition const* _currentScope) const
|
MemberList const& Type::members(ASTNode const* _currentScope) const
|
||||||
{
|
{
|
||||||
if (!m_members[_currentScope])
|
if (!m_members[_currentScope])
|
||||||
{
|
{
|
||||||
|
solAssert(
|
||||||
|
_currentScope == nullptr ||
|
||||||
|
dynamic_cast<SourceUnit const*>(_currentScope) ||
|
||||||
|
dynamic_cast<ContractDefinition const*>(_currentScope),
|
||||||
|
"");
|
||||||
MemberList::MemberMap members = nativeMembers(_currentScope);
|
MemberList::MemberMap members = nativeMembers(_currentScope);
|
||||||
if (_currentScope)
|
if (_currentScope)
|
||||||
members += boundFunctions(*this, *_currentScope);
|
members += boundFunctions(*this, *_currentScope);
|
||||||
@ -344,8 +349,20 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
|
|||||||
return encodingType;
|
return encodingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition const& _scope)
|
MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _scope)
|
||||||
{
|
{
|
||||||
|
vector<UsingForDirective const*> usingForDirectives;
|
||||||
|
if (auto const* sourceUnit = dynamic_cast<SourceUnit const*>(&_scope))
|
||||||
|
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes());
|
||||||
|
else if (auto const* contract = dynamic_cast<ContractDefinition const*>(&_scope))
|
||||||
|
{
|
||||||
|
for (ContractDefinition const* contract: contract->annotation().linearizedBaseContracts)
|
||||||
|
usingForDirectives += contract->usingForDirectives();
|
||||||
|
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(contract->sourceUnit().nodes());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(false, "");
|
||||||
|
|
||||||
// Normalise data location of type.
|
// Normalise data location of type.
|
||||||
DataLocation typeLocation = DataLocation::Storage;
|
DataLocation typeLocation = DataLocation::Storage;
|
||||||
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
||||||
@ -353,38 +370,39 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
|
|||||||
|
|
||||||
set<Declaration const*> seenFunctions;
|
set<Declaration const*> seenFunctions;
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
for (ContractDefinition const* contract: _scope.annotation().linearizedBaseContracts)
|
|
||||||
for (UsingForDirective const* ufd: contract->usingForDirectives())
|
for (UsingForDirective const* ufd: usingForDirectives)
|
||||||
{
|
{
|
||||||
// Convert both types to pointers for comparison to see if the `using for`
|
// Convert both types to pointers for comparison to see if the `using for`
|
||||||
// directive applies.
|
// directive applies.
|
||||||
// Further down, we check more detailed for each function if `_type` is
|
// Further down, we check more detailed for each function if `_type` is
|
||||||
// convertible to the function parameter type.
|
// convertible to the function parameter type.
|
||||||
if (ufd->typeName() &&
|
if (ufd->typeName() &&
|
||||||
*TypeProvider::withLocationIfReference(typeLocation, &_type, true) !=
|
*TypeProvider::withLocationIfReference(typeLocation, &_type, true) !=
|
||||||
*TypeProvider::withLocationIfReference(
|
*TypeProvider::withLocationIfReference(
|
||||||
typeLocation,
|
typeLocation,
|
||||||
ufd->typeName()->annotation().type,
|
ufd->typeName()->annotation().type,
|
||||||
true
|
true
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
auto const& library = dynamic_cast<ContractDefinition const&>(
|
||||||
|
*ufd->libraryName().annotation().referencedDeclaration
|
||||||
|
);
|
||||||
|
for (FunctionDefinition const* function: library.definedFunctions())
|
||||||
|
{
|
||||||
|
if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function))
|
||||||
continue;
|
continue;
|
||||||
auto const& library = dynamic_cast<ContractDefinition const&>(
|
seenFunctions.insert(function);
|
||||||
*ufd->libraryName().annotation().referencedDeclaration
|
if (function->parameters().empty())
|
||||||
);
|
continue;
|
||||||
for (FunctionDefinition const* function: library.definedFunctions())
|
FunctionTypePointer fun =
|
||||||
{
|
dynamic_cast<FunctionType const&>(*function->typeViaContractName()).asBoundFunction();
|
||||||
if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function))
|
if (_type.isImplicitlyConvertibleTo(*fun->selfType()))
|
||||||
continue;
|
members.emplace_back(function->name(), fun, function);
|
||||||
seenFunctions.insert(function);
|
|
||||||
if (function->parameters().empty())
|
|
||||||
continue;
|
|
||||||
FunctionTypePointer fun =
|
|
||||||
dynamic_cast<FunctionType const&>(*function->typeViaContractName()).asBoundFunction();
|
|
||||||
if (_type.isImplicitlyConvertibleTo(*fun->selfType()))
|
|
||||||
members.emplace_back(function->name(), fun, function);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return members;
|
return members;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,7 +482,7 @@ bool AddressType::operator==(Type const& _other) const
|
|||||||
return other.m_stateMutability == m_stateMutability;
|
return other.m_stateMutability == m_stateMutability;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) const
|
MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members = {
|
MemberList::MemberMap members = {
|
||||||
{"balance", TypeProvider::uint256()},
|
{"balance", TypeProvider::uint256()},
|
||||||
@ -871,6 +889,9 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
|
|||||||
case Literal::SubDenomination::Wei:
|
case Literal::SubDenomination::Wei:
|
||||||
case Literal::SubDenomination::Second:
|
case Literal::SubDenomination::Second:
|
||||||
break;
|
break;
|
||||||
|
case Literal::SubDenomination::Gwei:
|
||||||
|
value *= bigint("1000000000");
|
||||||
|
break;
|
||||||
case Literal::SubDenomination::Szabo:
|
case Literal::SubDenomination::Szabo:
|
||||||
value *= bigint("1000000000000");
|
value *= bigint("1000000000000");
|
||||||
break;
|
break;
|
||||||
@ -1400,7 +1421,7 @@ TypeResult FixedBytesType::binaryOperatorResult(Token _operator, Type const* _ot
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap FixedBytesType::nativeMembers(ContractDefinition const*) const
|
MemberList::MemberMap FixedBytesType::nativeMembers(ASTNode const*) const
|
||||||
{
|
{
|
||||||
return MemberList::MemberMap{MemberList::Member{"length", TypeProvider::uint(8)}};
|
return MemberList::MemberMap{MemberList::Member{"length", TypeProvider::uint(8)}};
|
||||||
}
|
}
|
||||||
@ -1856,7 +1877,7 @@ string ArrayType::signatureInExternalFunction(bool _structsByName) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
|
MemberList::MemberMap ArrayType::nativeMembers(ASTNode const*) const
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
if (!isString())
|
if (!isString())
|
||||||
@ -2029,10 +2050,9 @@ string ContractType::canonicalName() const
|
|||||||
return m_contract.annotation().canonicalName;
|
return m_contract.annotation().canonicalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _contract) const
|
MemberList::MemberMap ContractType::nativeMembers(ASTNode const*) const
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
solAssert(_contract, "");
|
|
||||||
if (m_super)
|
if (m_super)
|
||||||
{
|
{
|
||||||
// add the most derived of all functions which are visible in derived contracts
|
// add the most derived of all functions which are visible in derived contracts
|
||||||
@ -2063,14 +2083,13 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!m_contract.isLibrary())
|
else if (!m_contract.isLibrary())
|
||||||
{
|
|
||||||
for (auto const& it: m_contract.interfaceFunctions())
|
for (auto const& it: m_contract.interfaceFunctions())
|
||||||
members.emplace_back(
|
members.emplace_back(
|
||||||
it.second->declaration().name(),
|
it.second->declaration().name(),
|
||||||
it.second->asExternallyCallableFunction(m_contract.isLibrary()),
|
it.second->asExternallyCallableFunction(m_contract.isLibrary()),
|
||||||
&it.second->declaration()
|
&it.second->declaration()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return members;
|
return members;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2241,7 +2260,7 @@ string StructType::toString(bool _short) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
MemberList::MemberMap StructType::nativeMembers(ASTNode const*) const
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
|
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
|
||||||
@ -3146,7 +3165,7 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _scope) const
|
MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const
|
||||||
{
|
{
|
||||||
switch (m_kind)
|
switch (m_kind)
|
||||||
{
|
{
|
||||||
@ -3165,7 +3184,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
|
|||||||
functionDefinition->isPartOfExternalInterface()
|
functionDefinition->isPartOfExternalInterface()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
solAssert(_scope->derivesFrom(*functionDefinition->annotation().contract), "");
|
auto const* contractScope = dynamic_cast<ContractDefinition const*>(_scope);
|
||||||
|
solAssert(contractScope && contractScope->derivesFrom(*functionDefinition->annotation().contract), "");
|
||||||
return {{"selector", TypeProvider::fixedBytes(4)}};
|
return {{"selector", TypeProvider::fixedBytes(4)}};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -3640,13 +3660,14 @@ vector<tuple<string, TypePointer>> TypeType::makeStackItems() const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _currentScope) const
|
MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) const
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
if (m_actualType->category() == Category::Contract)
|
if (m_actualType->category() == Category::Contract)
|
||||||
{
|
{
|
||||||
|
auto const* contractScope = dynamic_cast<ContractDefinition const*>(_currentScope);
|
||||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).contractDefinition();
|
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).contractDefinition();
|
||||||
bool inDerivingScope = _currentScope && _currentScope->derivesFrom(contract);
|
bool inDerivingScope = contractScope && contractScope->derivesFrom(contract);
|
||||||
|
|
||||||
for (auto const* declaration: contract.declarations())
|
for (auto const* declaration: contract.declarations())
|
||||||
{
|
{
|
||||||
@ -3748,7 +3769,7 @@ bool ModuleType::operator==(Type const& _other) const
|
|||||||
return &m_sourceUnit == &dynamic_cast<ModuleType const&>(_other).m_sourceUnit;
|
return &m_sourceUnit == &dynamic_cast<ModuleType const&>(_other).m_sourceUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap ModuleType::nativeMembers(ContractDefinition const*) const
|
MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const
|
||||||
{
|
{
|
||||||
MemberList::MemberMap symbols;
|
MemberList::MemberMap symbols;
|
||||||
for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols)
|
for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols)
|
||||||
@ -3789,7 +3810,7 @@ bool MagicType::operator==(Type const& _other) const
|
|||||||
return other.m_kind == m_kind;
|
return other.m_kind == m_kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
|
||||||
{
|
{
|
||||||
switch (m_kind)
|
switch (m_kind)
|
||||||
{
|
{
|
||||||
|
@ -315,9 +315,9 @@ public:
|
|||||||
|
|
||||||
/// Returns the list of all members of this type. Default implementation: no members apart from bound.
|
/// Returns the list of all members of this type. Default implementation: no members apart from bound.
|
||||||
/// @param _currentScope scope in which the members are accessed.
|
/// @param _currentScope scope in which the members are accessed.
|
||||||
MemberList const& members(ContractDefinition const* _currentScope) const;
|
MemberList const& members(ASTNode const* _currentScope) const;
|
||||||
/// Convenience method, returns the type of the given named member or an empty pointer if no such member exists.
|
/// Convenience method, returns the type of the given named member or an empty pointer if no such member exists.
|
||||||
TypePointer memberType(std::string const& _name, ContractDefinition const* _currentScope = nullptr) const
|
TypePointer memberType(std::string const& _name, ASTNode const* _currentScope = nullptr) const
|
||||||
{
|
{
|
||||||
return members(_currentScope).memberType(_name);
|
return members(_currentScope).memberType(_name);
|
||||||
}
|
}
|
||||||
@ -361,12 +361,12 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/// @returns a member list containing all members added to this type by `using for` directives.
|
/// @returns a member list containing all members added to this type by `using for` directives.
|
||||||
static MemberList::MemberMap boundFunctions(Type const& _type, ContractDefinition const& _scope);
|
static MemberList::MemberMap boundFunctions(Type const& _type, ASTNode const& _scope);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// @returns the members native to this type depending on the given context. This function
|
/// @returns the members native to this type depending on the given context. This function
|
||||||
/// is used (in conjunction with boundFunctions to fill m_members below.
|
/// is used (in conjunction with boundFunctions to fill m_members below.
|
||||||
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* /*_currentScope*/) const
|
virtual MemberList::MemberMap nativeMembers(ASTNode const* /*_currentScope*/) const
|
||||||
{
|
{
|
||||||
return MemberList::MemberMap();
|
return MemberList::MemberMap();
|
||||||
}
|
}
|
||||||
@ -379,7 +379,7 @@ protected:
|
|||||||
|
|
||||||
|
|
||||||
/// List of member types (parameterised by scape), will be lazy-initialized.
|
/// List of member types (parameterised by scape), will be lazy-initialized.
|
||||||
mutable std::map<ContractDefinition const*, std::unique_ptr<MemberList>> m_members;
|
mutable std::map<ASTNode const*, std::unique_ptr<MemberList>> m_members;
|
||||||
mutable std::optional<std::vector<std::tuple<std::string, TypePointer>>> m_stackItems;
|
mutable std::optional<std::vector<std::tuple<std::string, TypePointer>>> m_stackItems;
|
||||||
mutable std::optional<size_t> m_stackSize;
|
mutable std::optional<size_t> m_stackSize;
|
||||||
};
|
};
|
||||||
@ -408,7 +408,7 @@ public:
|
|||||||
bool isValueType() const override { return true; }
|
bool isValueType() const override { return true; }
|
||||||
bool nameable() const override { return true; }
|
bool nameable() const override { return true; }
|
||||||
|
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
|
||||||
|
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
std::string canonicalName() const override;
|
std::string canonicalName() const override;
|
||||||
@ -649,7 +649,7 @@ public:
|
|||||||
bool nameable() const override { return true; }
|
bool nameable() const override { return true; }
|
||||||
|
|
||||||
std::string toString(bool) const override { return "bytes" + util::toString(m_bytes); }
|
std::string toString(bool) const override { return "bytes" + util::toString(m_bytes); }
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
|
||||||
TypePointer encodingType() const override { return this; }
|
TypePointer encodingType() const override { return this; }
|
||||||
TypeResult interfaceType(bool) const override { return this; }
|
TypeResult interfaceType(bool) const override { return this; }
|
||||||
|
|
||||||
@ -786,7 +786,7 @@ public:
|
|||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
std::string canonicalName() const override;
|
std::string canonicalName() const override;
|
||||||
std::string signatureInExternalFunction(bool _structsByName) const override;
|
std::string signatureInExternalFunction(bool _structsByName) const override;
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
|
||||||
TypePointer encodingType() const override;
|
TypePointer encodingType() const override;
|
||||||
TypePointer decodingType() const override;
|
TypePointer decodingType() const override;
|
||||||
TypeResult interfaceType(bool _inLibrary) const override;
|
TypeResult interfaceType(bool _inLibrary) const override;
|
||||||
@ -889,7 +889,7 @@ public:
|
|||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
std::string canonicalName() const override;
|
std::string canonicalName() const override;
|
||||||
|
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
|
||||||
|
|
||||||
Type const* encodingType() const override;
|
Type const* encodingType() const override;
|
||||||
|
|
||||||
@ -949,7 +949,7 @@ public:
|
|||||||
bool nameable() const override { return true; }
|
bool nameable() const override { return true; }
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
|
||||||
|
|
||||||
Type const* encodingType() const override;
|
Type const* encodingType() const override;
|
||||||
TypeResult interfaceType(bool _inLibrary) const override;
|
TypeResult interfaceType(bool _inLibrary) const override;
|
||||||
@ -1220,7 +1220,7 @@ public:
|
|||||||
bool nameable() const override;
|
bool nameable() const override;
|
||||||
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
|
||||||
TypePointer encodingType() const override;
|
TypePointer encodingType() const override;
|
||||||
TypeResult interfaceType(bool _inLibrary) const override;
|
TypeResult interfaceType(bool _inLibrary) const override;
|
||||||
|
|
||||||
@ -1389,7 +1389,7 @@ public:
|
|||||||
bool canLiveOutsideStorage() const override { return false; }
|
bool canLiveOutsideStorage() const override { return false; }
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
|
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
|
||||||
|
|
||||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||||
protected:
|
protected:
|
||||||
@ -1441,7 +1441,7 @@ public:
|
|||||||
bool canBeStored() const override { return false; }
|
bool canBeStored() const override { return false; }
|
||||||
bool canLiveOutsideStorage() const override { return true; }
|
bool canLiveOutsideStorage() const override { return true; }
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
|
||||||
|
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
@ -1481,7 +1481,7 @@ public:
|
|||||||
bool canBeStored() const override { return false; }
|
bool canBeStored() const override { return false; }
|
||||||
bool canLiveOutsideStorage() const override { return true; }
|
bool canLiveOutsideStorage() const override { return true; }
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
|
||||||
|
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
|
@ -226,8 +226,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
|||||||
else
|
else
|
||||||
solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
|
solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
|
||||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
|
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
|
||||||
solAssert(
|
assertThrow(
|
||||||
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
|
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
|
||||||
|
StackTooDeepError,
|
||||||
"Stack too deep, try removing local variables."
|
"Stack too deep, try removing local variables."
|
||||||
);
|
);
|
||||||
// fetch target storage reference
|
// fetch target storage reference
|
||||||
|
@ -146,7 +146,7 @@ void CompilerContext::callYulFunction(
|
|||||||
m_externallyUsedYulFunctions.insert(_name);
|
m_externallyUsedYulFunctions.insert(_name);
|
||||||
auto const retTag = pushNewTag();
|
auto const retTag = pushNewTag();
|
||||||
CompilerUtils(*this).moveIntoStack(_inArgs);
|
CompilerUtils(*this).moveIntoStack(_inArgs);
|
||||||
appendJumpTo(namedTag(_name));
|
appendJumpTo(namedTag(_name), evmasm::AssemblyItem::JumpType::IntoFunction);
|
||||||
adjustStackOffset(static_cast<int>(_outArgs) - 1 - static_cast<int>(_inArgs));
|
adjustStackOffset(static_cast<int>(_outArgs) - 1 - static_cast<int>(_inArgs));
|
||||||
*this << retTag.tag();
|
*this << retTag.tag();
|
||||||
}
|
}
|
||||||
@ -384,12 +384,11 @@ void CompilerContext::appendInlineAssembly(
|
|||||||
yul::Identifier const& _identifier,
|
yul::Identifier const& _identifier,
|
||||||
yul::IdentifierContext,
|
yul::IdentifierContext,
|
||||||
bool _insideFunction
|
bool _insideFunction
|
||||||
) -> size_t
|
) -> bool
|
||||||
{
|
{
|
||||||
if (_insideFunction)
|
if (_insideFunction)
|
||||||
return numeric_limits<size_t>::max();
|
return false;
|
||||||
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name.str());
|
return contains(_localVariables, _identifier.name.str());
|
||||||
return it == _localVariables.end() ? numeric_limits<size_t>::max() : 1;
|
|
||||||
};
|
};
|
||||||
identifierAccess.generateCode = [&](
|
identifierAccess.generateCode = [&](
|
||||||
yul::Identifier const& _identifier,
|
yul::Identifier const& _identifier,
|
||||||
@ -405,7 +404,7 @@ void CompilerContext::appendInlineAssembly(
|
|||||||
stackDiff -= 1;
|
stackDiff -= 1;
|
||||||
if (stackDiff < 1 || stackDiff > 16)
|
if (stackDiff < 1 || stackDiff > 16)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_identifier.location) <<
|
errinfo_sourceLocation(_identifier.location) <<
|
||||||
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
|
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
|
||||||
);
|
);
|
||||||
|
@ -455,7 +455,11 @@ void CompilerUtils::encodeToMemory(
|
|||||||
// leave end_of_mem as dyn head pointer
|
// leave end_of_mem as dyn head pointer
|
||||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||||
dynPointers++;
|
dynPointers++;
|
||||||
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables.");
|
assertThrow(
|
||||||
|
(argSize + dynPointers) < 16,
|
||||||
|
StackTooDeepError,
|
||||||
|
"Stack too deep, try using fewer variables."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -507,8 +511,9 @@ void CompilerUtils::encodeToMemory(
|
|||||||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
||||||
{
|
{
|
||||||
// copy tail pointer (=mem_end - mem_start) to memory
|
// copy tail pointer (=mem_end - mem_start) to memory
|
||||||
solAssert(
|
assertThrow(
|
||||||
(2 + dynPointers) <= 16,
|
(2 + dynPointers) <= 16,
|
||||||
|
StackTooDeepError,
|
||||||
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
|
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
|
||||||
);
|
);
|
||||||
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
|
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
|
||||||
@ -1290,7 +1295,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
|
|||||||
// move variable starting from its top end in the stack
|
// move variable starting from its top end in the stack
|
||||||
if (stackPosition - size + 1 > 16)
|
if (stackPosition - size + 1 > 16)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_variable.location()) <<
|
errinfo_sourceLocation(_variable.location()) <<
|
||||||
util::errinfo_comment("Stack too deep, try removing local variables.")
|
util::errinfo_comment("Stack too deep, try removing local variables.")
|
||||||
);
|
);
|
||||||
@ -1300,7 +1305,11 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
|
|||||||
|
|
||||||
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
|
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
|
||||||
{
|
{
|
||||||
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables.");
|
assertThrow(
|
||||||
|
_stackDepth <= 16,
|
||||||
|
StackTooDeepError,
|
||||||
|
"Stack too deep, try removing local variables."
|
||||||
|
);
|
||||||
for (unsigned i = 0; i < _itemSize; ++i)
|
for (unsigned i = 0; i < _itemSize; ++i)
|
||||||
m_context << dupInstruction(_stackDepth);
|
m_context << dupInstruction(_stackDepth);
|
||||||
}
|
}
|
||||||
@ -1322,14 +1331,22 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
|
|||||||
|
|
||||||
void CompilerUtils::rotateStackUp(unsigned _items)
|
void CompilerUtils::rotateStackUp(unsigned _items)
|
||||||
{
|
{
|
||||||
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables.");
|
assertThrow(
|
||||||
|
_items - 1 <= 16,
|
||||||
|
StackTooDeepError,
|
||||||
|
"Stack too deep, try removing local variables."
|
||||||
|
);
|
||||||
for (unsigned i = 1; i < _items; ++i)
|
for (unsigned i = 1; i < _items; ++i)
|
||||||
m_context << swapInstruction(_items - i);
|
m_context << swapInstruction(_items - i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilerUtils::rotateStackDown(unsigned _items)
|
void CompilerUtils::rotateStackDown(unsigned _items)
|
||||||
{
|
{
|
||||||
solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables.");
|
assertThrow(
|
||||||
|
_items - 1 <= 16,
|
||||||
|
StackTooDeepError,
|
||||||
|
"Stack too deep, try removing local variables."
|
||||||
|
);
|
||||||
for (unsigned i = 1; i < _items; ++i)
|
for (unsigned i = 1; i < _items; ++i)
|
||||||
m_context << swapInstruction(i);
|
m_context << swapInstruction(i);
|
||||||
}
|
}
|
||||||
|
@ -634,7 +634,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
|
|||||||
|
|
||||||
if (stackLayout.size() > 17)
|
if (stackLayout.size() > 17)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_function.location()) <<
|
errinfo_sourceLocation(_function.location()) <<
|
||||||
errinfo_comment("Stack too deep, try removing local variables.")
|
errinfo_comment("Stack too deep, try removing local variables.")
|
||||||
);
|
);
|
||||||
@ -798,7 +798,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
solAssert(variable->type()->sizeOnStack() == 1, "");
|
solAssert(variable->type()->sizeOnStack() == 1, "");
|
||||||
if (stackDiff < 1 || stackDiff > 16)
|
if (stackDiff < 1 || stackDiff > 16)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||||
errinfo_comment("Stack too deep, try removing local variables.")
|
errinfo_comment("Stack too deep, try removing local variables.")
|
||||||
);
|
);
|
||||||
@ -831,7 +831,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable) - 1;
|
unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable) - 1;
|
||||||
if (stackDiff > 16 || stackDiff < 1)
|
if (stackDiff > 16 || stackDiff < 1)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||||
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
|
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
|
||||||
);
|
);
|
||||||
|
@ -226,7 +226,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
|||||||
solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), "");
|
solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), "");
|
||||||
if (retSizeOnStack > 15)
|
if (retSizeOnStack > 15)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_varDecl.location()) <<
|
errinfo_sourceLocation(_varDecl.location()) <<
|
||||||
errinfo_comment("Stack too deep.")
|
errinfo_comment("Stack too deep.")
|
||||||
);
|
);
|
||||||
@ -308,7 +308,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
|||||||
{
|
{
|
||||||
if (itemSize + lvalueSize > 16)
|
if (itemSize + lvalueSize > 16)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_assignment.location()) <<
|
errinfo_sourceLocation(_assignment.location()) <<
|
||||||
errinfo_comment("Stack too deep, try removing local variables.")
|
errinfo_comment("Stack too deep, try removing local variables.")
|
||||||
);
|
);
|
||||||
@ -1718,6 +1718,16 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
|||||||
solAssert(false, "Illegal fixed bytes member.");
|
solAssert(false, "Illegal fixed bytes member.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Type::Category::Module:
|
||||||
|
{
|
||||||
|
Type::Category category = _memberAccess.annotation().type->category();
|
||||||
|
solAssert(
|
||||||
|
category == Type::Category::TypeType ||
|
||||||
|
category == Type::Category::Module,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
solAssert(false, "Member access to unknown type.");
|
solAssert(false, "Member access to unknown type.");
|
||||||
}
|
}
|
||||||
@ -1933,6 +1943,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
|
|||||||
{
|
{
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
else if (dynamic_cast<ImportDirective const*>(declaration))
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
solAssert(false, "Identifier type not expected in expression context.");
|
solAssert(false, "Identifier type not expected in expression context.");
|
||||||
|
@ -47,7 +47,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
|
|||||||
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
|
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
|
||||||
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
|
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_location) <<
|
errinfo_sourceLocation(_location) <<
|
||||||
errinfo_comment("Stack too deep, try removing local variables.")
|
errinfo_comment("Stack too deep, try removing local variables.")
|
||||||
);
|
);
|
||||||
@ -61,7 +61,7 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo
|
|||||||
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
|
unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
|
||||||
if (stackDiff > 16)
|
if (stackDiff > 16)
|
||||||
BOOST_THROW_EXCEPTION(
|
BOOST_THROW_EXCEPTION(
|
||||||
CompilerError() <<
|
StackTooDeepError() <<
|
||||||
errinfo_sourceLocation(_location) <<
|
errinfo_sourceLocation(_location) <<
|
||||||
errinfo_comment("Stack too deep, try removing local variables.")
|
errinfo_comment("Stack too deep, try removing local variables.")
|
||||||
);
|
);
|
||||||
|
@ -1646,13 +1646,30 @@ string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType cons
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
|
string YulUtilFunctions::allocateMemoryStructFunction(StructType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "allocate_and_initialize_memory_struct_" + _type.identifier();
|
string functionName = "allocate_memory_struct_" + _type.identifier();
|
||||||
return m_functionCollector.createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>() -> memPtr {
|
function <functionName>() -> memPtr {
|
||||||
memPtr := <alloc>(<allocSize>)
|
memPtr := <alloc>(<allocSize>)
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
templ("functionName", functionName);
|
||||||
|
templ("alloc", allocationFunction());
|
||||||
|
templ("allocSize", _type.memoryDataSize().str());
|
||||||
|
|
||||||
|
return templ.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
|
||||||
|
{
|
||||||
|
string functionName = "allocate_and_zero_memory_struct_" + _type.identifier();
|
||||||
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
|
Whiskers templ(R"(
|
||||||
|
function <functionName>() -> memPtr {
|
||||||
|
memPtr := <allocStruct>()
|
||||||
let offset := memPtr
|
let offset := memPtr
|
||||||
<#member>
|
<#member>
|
||||||
mstore(offset, <zeroValue>())
|
mstore(offset, <zeroValue>())
|
||||||
@ -1661,10 +1678,9 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
|
|||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
templ("functionName", functionName);
|
templ("functionName", functionName);
|
||||||
templ("alloc", allocationFunction());
|
templ("allocStruct", allocateMemoryStructFunction(_type));
|
||||||
|
|
||||||
TypePointers const& members = _type.memoryMemberTypes();
|
TypePointers const& members = _type.memoryMemberTypes();
|
||||||
templ("allocSize", _type.memoryDataSize().str());
|
|
||||||
|
|
||||||
vector<map<string, string>> memberParams(members.size());
|
vector<map<string, string>> memberParams(members.size());
|
||||||
for (size_t i = 0; i < members.size(); ++i)
|
for (size_t i = 0; i < members.size(); ++i)
|
||||||
|
@ -286,8 +286,13 @@ public:
|
|||||||
/// signature: (length) -> memPtr
|
/// signature: (length) -> memPtr
|
||||||
std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type);
|
std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type);
|
||||||
|
|
||||||
|
/// @returns the name of a function that allocates a memory struct (no
|
||||||
|
/// initialization takes place).
|
||||||
|
/// signature: () -> memPtr
|
||||||
|
std::string allocateMemoryStructFunction(StructType const& _type);
|
||||||
|
|
||||||
/// @returns the name of a function that allocates and zeroes a memory struct.
|
/// @returns the name of a function that allocates and zeroes a memory struct.
|
||||||
/// signature: (members) -> memPtr
|
/// signature: () -> memPtr
|
||||||
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
|
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
|
||||||
|
|
||||||
/// @returns the name of the function that converts a value of type @a _from
|
/// @returns the name of the function that converts a value of type @a _from
|
||||||
|
@ -600,22 +600,30 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
|
|||||||
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(
|
solUnimplementedAssert(
|
||||||
_functionCall.annotation().kind == FunctionCallKind::FunctionCall ||
|
_functionCall.annotation().kind != FunctionCallKind::Unset,
|
||||||
_functionCall.annotation().kind == FunctionCallKind::TypeConversion,
|
|
||||||
"This type of function call is not yet implemented"
|
"This type of function call is not yet implemented"
|
||||||
);
|
);
|
||||||
|
|
||||||
Type const& funcType = type(_functionCall.expression());
|
|
||||||
|
|
||||||
if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
|
if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
|
||||||
{
|
{
|
||||||
solAssert(funcType.category() == Type::Category::TypeType, "Expected category to be TypeType");
|
solAssert(
|
||||||
|
_functionCall.expression().annotation().type->category() == Type::Category::TypeType,
|
||||||
|
"Expected category to be TypeType"
|
||||||
|
);
|
||||||
solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
|
solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
|
||||||
define(_functionCall, *_functionCall.arguments().front());
|
define(_functionCall, *_functionCall.arguments().front());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionTypePointer functionType = dynamic_cast<FunctionType const*>(&funcType);
|
FunctionTypePointer functionType = nullptr;
|
||||||
|
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
|
||||||
|
{
|
||||||
|
auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
|
||||||
|
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
|
||||||
|
functionType = structType.constructorType();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
|
||||||
|
|
||||||
TypePointers parameterTypes = functionType->parameterTypes();
|
TypePointers parameterTypes = functionType->parameterTypes();
|
||||||
vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments();
|
vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments();
|
||||||
@ -639,6 +647,34 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
arguments.push_back(callArguments[static_cast<size_t>(std::distance(callArgumentNames.begin(), it))]);
|
arguments.push_back(callArguments[static_cast<size_t>(std::distance(callArgumentNames.begin(), it))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
|
||||||
|
{
|
||||||
|
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
|
||||||
|
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
|
||||||
|
|
||||||
|
define(_functionCall) << m_utils.allocateMemoryStructFunction(structType) << "()\n";
|
||||||
|
|
||||||
|
MemberList::MemberMap members = structType.nativeMembers(nullptr);
|
||||||
|
|
||||||
|
solAssert(members.size() == arguments.size(), "Struct parameter mismatch.");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < arguments.size(); i++)
|
||||||
|
{
|
||||||
|
IRVariable converted = convert(*arguments[i], *parameterTypes[i]);
|
||||||
|
m_code <<
|
||||||
|
m_utils.writeToMemoryFunction(*functionType->parameterTypes()[i]) <<
|
||||||
|
"(add(" <<
|
||||||
|
IRVariable(_functionCall).part("mpos").name() <<
|
||||||
|
", " <<
|
||||||
|
structType.memoryOffsetOfMember(members[i].name) <<
|
||||||
|
"), " <<
|
||||||
|
converted.commaSeparatedList() <<
|
||||||
|
")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression());
|
auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression());
|
||||||
if (memberAccess)
|
if (memberAccess)
|
||||||
{
|
{
|
||||||
|
@ -197,8 +197,11 @@ void CHC::endVisit(ContractDefinition const& _contract)
|
|||||||
|
|
||||||
bool CHC::visit(FunctionDefinition const& _function)
|
bool CHC::visit(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
if (!shouldVisit(_function))
|
if (!_function.isImplemented())
|
||||||
|
{
|
||||||
|
connectBlocks(genesis(), summary(_function));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// This is the case for base constructor inlining.
|
// This is the case for base constructor inlining.
|
||||||
if (m_currentFunction)
|
if (m_currentFunction)
|
||||||
@ -243,7 +246,7 @@ bool CHC::visit(FunctionDefinition const& _function)
|
|||||||
|
|
||||||
void CHC::endVisit(FunctionDefinition const& _function)
|
void CHC::endVisit(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
if (!shouldVisit(_function))
|
if (!_function.isImplemented())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// This is the case for base constructor inlining.
|
// This is the case for base constructor inlining.
|
||||||
@ -474,11 +477,14 @@ void CHC::endVisit(FunctionCall const& _funCall)
|
|||||||
internalFunctionCall(_funCall);
|
internalFunctionCall(_funCall);
|
||||||
break;
|
break;
|
||||||
case FunctionType::Kind::External:
|
case FunctionType::Kind::External:
|
||||||
|
case FunctionType::Kind::BareStaticCall:
|
||||||
|
externalFunctionCall(_funCall);
|
||||||
|
SMTEncoder::endVisit(_funCall);
|
||||||
|
break;
|
||||||
case FunctionType::Kind::DelegateCall:
|
case FunctionType::Kind::DelegateCall:
|
||||||
case FunctionType::Kind::BareCall:
|
case FunctionType::Kind::BareCall:
|
||||||
case FunctionType::Kind::BareCallCode:
|
case FunctionType::Kind::BareCallCode:
|
||||||
case FunctionType::Kind::BareDelegateCall:
|
case FunctionType::Kind::BareDelegateCall:
|
||||||
case FunctionType::Kind::BareStaticCall:
|
|
||||||
case FunctionType::Kind::Creation:
|
case FunctionType::Kind::Creation:
|
||||||
case FunctionType::Kind::KECCAK256:
|
case FunctionType::Kind::KECCAK256:
|
||||||
case FunctionType::Kind::ECRecover:
|
case FunctionType::Kind::ECRecover:
|
||||||
@ -574,6 +580,35 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
|
|||||||
m_context.addAssertion(m_error.currentValue() == previousError);
|
m_context.addAssertion(m_error.currentValue() == previousError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CHC::externalFunctionCall(FunctionCall const& _funCall)
|
||||||
|
{
|
||||||
|
solAssert(m_currentContract, "");
|
||||||
|
|
||||||
|
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||||
|
auto kind = funType.kind();
|
||||||
|
solAssert(kind == FunctionType::Kind::External || kind == FunctionType::Kind::BareStaticCall, "");
|
||||||
|
|
||||||
|
auto const* function = functionCallToDefinition(_funCall);
|
||||||
|
if (!function)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto var: function->returnParameters())
|
||||||
|
m_context.variable(*var)->increaseIndex();
|
||||||
|
|
||||||
|
auto preCallState = currentStateVariables();
|
||||||
|
bool usesStaticCall = kind == FunctionType::Kind::BareStaticCall ||
|
||||||
|
function->stateMutability() == StateMutability::Pure ||
|
||||||
|
function->stateMutability() == StateMutability::View;
|
||||||
|
if (!usesStaticCall)
|
||||||
|
for (auto const* var: m_stateVariables)
|
||||||
|
m_context.variable(*var)->increaseIndex();
|
||||||
|
|
||||||
|
auto nondet = (*m_nondetInterfaces.at(m_currentContract))(preCallState + currentStateVariables());
|
||||||
|
m_context.addAssertion(nondet);
|
||||||
|
|
||||||
|
m_context.addAssertion(m_error.currentValue() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
void CHC::unknownFunctionCall(FunctionCall const&)
|
void CHC::unknownFunctionCall(FunctionCall const&)
|
||||||
{
|
{
|
||||||
/// Function calls are not handled at the moment,
|
/// Function calls are not handled at the moment,
|
||||||
@ -651,11 +686,6 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CHC::shouldVisit(FunctionDefinition const& _function) const
|
|
||||||
{
|
|
||||||
return _function.isImplemented();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CHC::setCurrentBlock(
|
void CHC::setCurrentBlock(
|
||||||
smt::SymbolicFunctionVariable const& _block,
|
smt::SymbolicFunctionVariable const& _block,
|
||||||
vector<smtutil::Expression> const* _arguments
|
vector<smtutil::Expression> const* _arguments
|
||||||
@ -710,10 +740,14 @@ smtutil::SortPointer CHC::constructorSort()
|
|||||||
|
|
||||||
smtutil::SortPointer CHC::interfaceSort()
|
smtutil::SortPointer CHC::interfaceSort()
|
||||||
{
|
{
|
||||||
return make_shared<smtutil::FunctionSort>(
|
solAssert(m_currentContract, "");
|
||||||
m_stateSorts,
|
return interfaceSort(*m_currentContract);
|
||||||
smtutil::SortProvider::boolSort
|
}
|
||||||
);
|
|
||||||
|
smtutil::SortPointer CHC::nondetInterfaceSort()
|
||||||
|
{
|
||||||
|
solAssert(m_currentContract, "");
|
||||||
|
return nondetInterfaceSort(*m_currentContract);
|
||||||
}
|
}
|
||||||
|
|
||||||
smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
||||||
@ -724,6 +758,15 @@ smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smtutil::SortPointer CHC::nondetInterfaceSort(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
auto sorts = stateSorts(_contract);
|
||||||
|
return make_shared<smtutil::FunctionSort>(
|
||||||
|
sorts + sorts,
|
||||||
|
smtutil::SortProvider::boolSort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
smtutil::SortPointer CHC::arity0FunctionSort()
|
smtutil::SortPointer CHC::arity0FunctionSort()
|
||||||
{
|
{
|
||||||
return make_shared<smtutil::FunctionSort>(
|
return make_shared<smtutil::FunctionSort>(
|
||||||
@ -778,7 +821,12 @@ smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, Contr
|
|||||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||||
return make_shared<smtutil::FunctionSort>(
|
return make_shared<smtutil::FunctionSort>(
|
||||||
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} + sorts + inputSorts + sorts + outputSorts,
|
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} +
|
||||||
|
sorts +
|
||||||
|
inputSorts +
|
||||||
|
sorts +
|
||||||
|
inputSorts +
|
||||||
|
outputSorts,
|
||||||
smtutil::SortProvider::boolSort
|
smtutil::SortProvider::boolSort
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -802,11 +850,48 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
|||||||
{
|
{
|
||||||
string suffix = base->name() + "_" + to_string(base->id());
|
string suffix = base->name() + "_" + to_string(base->id());
|
||||||
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
|
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
|
||||||
|
m_nondetInterfaces[base] = createSymbolicBlock(nondetInterfaceSort(*base), "nondet_interface_" + suffix);
|
||||||
|
|
||||||
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
|
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
|
||||||
if (!m_context.knownVariable(*var))
|
if (!m_context.knownVariable(*var))
|
||||||
createVariable(*var);
|
createVariable(*var);
|
||||||
|
|
||||||
|
/// Base nondeterministic interface that allows
|
||||||
|
/// 0 steps to be taken, used as base for the inductive
|
||||||
|
/// rule for each function.
|
||||||
|
auto const& iface = *m_nondetInterfaces.at(base);
|
||||||
|
auto state0 = stateVariablesAtIndex(0, *base);
|
||||||
|
addRule(iface(state0 + state0), "base_nondet");
|
||||||
|
|
||||||
for (auto const* function: base->definedFunctions())
|
for (auto const* function: base->definedFunctions())
|
||||||
|
{
|
||||||
|
for (auto var: function->parameters())
|
||||||
|
createVariable(*var);
|
||||||
|
for (auto var: function->returnParameters())
|
||||||
|
createVariable(*var);
|
||||||
|
for (auto const* var: function->localVariables())
|
||||||
|
createVariable(*var);
|
||||||
|
|
||||||
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
|
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
|
||||||
|
|
||||||
|
if (!base->isLibrary() && !base->isInterface() && !function->isConstructor())
|
||||||
|
{
|
||||||
|
auto state1 = stateVariablesAtIndex(1, *base);
|
||||||
|
auto state2 = stateVariablesAtIndex(2, *base);
|
||||||
|
|
||||||
|
auto nondetPre = iface(state0 + state1);
|
||||||
|
auto nondetPost = iface(state0 + state2);
|
||||||
|
|
||||||
|
vector<smtutil::Expression> args{m_error.currentValue()};
|
||||||
|
args += state1 +
|
||||||
|
applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); }) +
|
||||||
|
state2 +
|
||||||
|
applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }) +
|
||||||
|
applyMap(function->returnParameters(), [this](auto _var) { return valueAtIndex(*_var, 1); });
|
||||||
|
|
||||||
|
connectBlocks(nondetPre, nondetPost, (*m_summaries.at(base).at(function))(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -842,15 +927,22 @@ smtutil::Expression CHC::summary(ContractDefinition const&)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
smtutil::Expression CHC::summary(FunctionDefinition const& _function)
|
smtutil::Expression CHC::summary(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
vector<smtutil::Expression> args{m_error.currentValue()};
|
vector<smtutil::Expression> args{m_error.currentValue()};
|
||||||
auto contract = _function.annotation().contract;
|
auto contract = _function.annotation().contract;
|
||||||
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
|
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables(_contract);
|
||||||
args += applyMap(_function.parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); });
|
args += applyMap(_function.parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); });
|
||||||
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables(_contract);
|
||||||
|
args += applyMap(_function.parameters(), [this](auto _var) { return currentValue(*_var); });
|
||||||
args += applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
args += applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||||
return (*m_summaries.at(m_currentContract).at(&_function))(args);
|
return (*m_summaries.at(&_contract).at(&_function))(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
smtutil::Expression CHC::summary(FunctionDefinition const& _function)
|
||||||
|
{
|
||||||
|
solAssert(m_currentContract, "");
|
||||||
|
return summary(_function, *m_currentContract);
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||||
@ -893,13 +985,18 @@ vector<smtutil::Expression> CHC::initialStateVariables()
|
|||||||
return stateVariablesAtIndex(0);
|
return stateVariablesAtIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index)
|
vector<smtutil::Expression> CHC::initialStateVariables(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
solAssert(m_currentContract, "");
|
return stateVariablesAtIndex(0, _contract);
|
||||||
return applyMap(m_stateVariables, [&](auto _var) { return valueAtIndex(*_var, _index); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract)
|
vector<smtutil::Expression> CHC::stateVariablesAtIndex(int _index)
|
||||||
|
{
|
||||||
|
solAssert(m_currentContract, "");
|
||||||
|
return stateVariablesAtIndex(_index, *m_currentContract);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smtutil::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
return applyMap(
|
return applyMap(
|
||||||
stateVariablesIncludingInheritedAndPrivate(_contract),
|
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||||
@ -910,7 +1007,12 @@ vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, Contract
|
|||||||
vector<smtutil::Expression> CHC::currentStateVariables()
|
vector<smtutil::Expression> CHC::currentStateVariables()
|
||||||
{
|
{
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
return applyMap(m_stateVariables, [this](auto _var) { return currentValue(*_var); });
|
return currentStateVariables(*m_currentContract);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smtutil::Expression> CHC::currentStateVariables(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
return applyMap(stateVariablesIncludingInheritedAndPrivate(_contract), [this](auto _var) { return currentValue(*_var); });
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smtutil::Expression> CHC::currentFunctionVariables()
|
vector<smtutil::Expression> CHC::currentFunctionVariables()
|
||||||
@ -978,22 +1080,28 @@ smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
|
|||||||
m_error.increaseIndex();
|
m_error.increaseIndex();
|
||||||
vector<smtutil::Expression> args{m_error.currentValue()};
|
vector<smtutil::Expression> args{m_error.currentValue()};
|
||||||
auto const* contract = function->annotation().contract;
|
auto const* contract = function->annotation().contract;
|
||||||
|
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||||
|
bool otherContract = contract->isLibrary() ||
|
||||||
|
funType.kind() == FunctionType::Kind::External ||
|
||||||
|
funType.kind() == FunctionType::Kind::BareStaticCall;
|
||||||
|
|
||||||
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : currentStateVariables();
|
args += otherContract ? stateVariablesAtIndex(0, *contract) : currentStateVariables();
|
||||||
args += symbolicArguments(_funCall);
|
args += symbolicArguments(_funCall);
|
||||||
for (auto const& var: m_stateVariables)
|
if (!otherContract)
|
||||||
m_context.variable(*var)->increaseIndex();
|
for (auto const& var: m_stateVariables)
|
||||||
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
m_context.variable(*var)->increaseIndex();
|
||||||
|
args += otherContract ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||||
|
|
||||||
auto const& returnParams = function->returnParameters();
|
for (auto var: function->parameters() + function->returnParameters())
|
||||||
for (auto param: returnParams)
|
{
|
||||||
if (m_context.knownVariable(*param))
|
if (m_context.knownVariable(*var))
|
||||||
m_context.variable(*param)->increaseIndex();
|
m_context.variable(*var)->increaseIndex();
|
||||||
else
|
else
|
||||||
createVariable(*param);
|
createVariable(*var);
|
||||||
args += applyMap(function->returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
args.push_back(currentValue(*var));
|
||||||
|
}
|
||||||
|
|
||||||
if (contract->isLibrary())
|
if (otherContract)
|
||||||
return (*m_summaries.at(contract).at(function))(args);
|
return (*m_summaries.at(contract).at(function))(args);
|
||||||
|
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
|
@ -77,6 +77,7 @@ private:
|
|||||||
|
|
||||||
void visitAssert(FunctionCall const& _funCall);
|
void visitAssert(FunctionCall const& _funCall);
|
||||||
void internalFunctionCall(FunctionCall const& _funCall);
|
void internalFunctionCall(FunctionCall const& _funCall);
|
||||||
|
void externalFunctionCall(FunctionCall const& _funCall);
|
||||||
void unknownFunctionCall(FunctionCall const& _funCall);
|
void unknownFunctionCall(FunctionCall const& _funCall);
|
||||||
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
|
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
|
||||||
//@}
|
//@}
|
||||||
@ -95,7 +96,6 @@ private:
|
|||||||
void resetContractAnalysis();
|
void resetContractAnalysis();
|
||||||
void eraseKnowledge();
|
void eraseKnowledge();
|
||||||
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
||||||
bool shouldVisit(FunctionDefinition const& _function) const;
|
|
||||||
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smtutil::Expression> const* _arguments = nullptr);
|
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smtutil::Expression> const* _arguments = nullptr);
|
||||||
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
|
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
|
||||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
||||||
@ -106,7 +106,9 @@ private:
|
|||||||
static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract);
|
static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract);
|
||||||
smtutil::SortPointer constructorSort();
|
smtutil::SortPointer constructorSort();
|
||||||
smtutil::SortPointer interfaceSort();
|
smtutil::SortPointer interfaceSort();
|
||||||
|
smtutil::SortPointer nondetInterfaceSort();
|
||||||
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
|
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||||
|
static smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _const);
|
||||||
smtutil::SortPointer arity0FunctionSort();
|
smtutil::SortPointer arity0FunctionSort();
|
||||||
smtutil::SortPointer sort(FunctionDefinition const& _function);
|
smtutil::SortPointer sort(FunctionDefinition const& _function);
|
||||||
smtutil::SortPointer sort(ASTNode const* _block);
|
smtutil::SortPointer sort(ASTNode const* _block);
|
||||||
@ -149,10 +151,12 @@ private:
|
|||||||
/// @returns the symbolic values of the state variables at the beginning
|
/// @returns the symbolic values of the state variables at the beginning
|
||||||
/// of the current transaction.
|
/// of the current transaction.
|
||||||
std::vector<smtutil::Expression> initialStateVariables();
|
std::vector<smtutil::Expression> initialStateVariables();
|
||||||
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index);
|
std::vector<smtutil::Expression> initialStateVariables(ContractDefinition const& _contract);
|
||||||
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract);
|
std::vector<smtutil::Expression> stateVariablesAtIndex(int _index);
|
||||||
|
std::vector<smtutil::Expression> stateVariablesAtIndex(int _index, ContractDefinition const& _contract);
|
||||||
/// @returns the current symbolic values of the current state variables.
|
/// @returns the current symbolic values of the current state variables.
|
||||||
std::vector<smtutil::Expression> currentStateVariables();
|
std::vector<smtutil::Expression> currentStateVariables();
|
||||||
|
std::vector<smtutil::Expression> currentStateVariables(ContractDefinition const& _contract);
|
||||||
|
|
||||||
/// @returns the current symbolic values of the current function's
|
/// @returns the current symbolic values of the current function's
|
||||||
/// input and output parameters.
|
/// input and output parameters.
|
||||||
@ -173,6 +177,7 @@ private:
|
|||||||
smtutil::Expression summary(ContractDefinition const& _contract);
|
smtutil::Expression summary(ContractDefinition const& _contract);
|
||||||
/// @returns a predicate that defines a function summary.
|
/// @returns a predicate that defines a function summary.
|
||||||
smtutil::Expression summary(FunctionDefinition const& _function);
|
smtutil::Expression summary(FunctionDefinition const& _function);
|
||||||
|
smtutil::Expression summary(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Solver related.
|
/// Solver related.
|
||||||
@ -212,6 +217,12 @@ private:
|
|||||||
/// Single entry block for all functions.
|
/// Single entry block for all functions.
|
||||||
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
|
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
|
||||||
|
|
||||||
|
/// Nondeterministic interfaces.
|
||||||
|
/// These are used when the analyzed contract makes external calls to unknown code,
|
||||||
|
/// which means that the analyzed contract can potentially be called
|
||||||
|
/// nondeterministically.
|
||||||
|
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_nondetInterfaces;
|
||||||
|
|
||||||
/// Artificial Error predicate.
|
/// Artificial Error predicate.
|
||||||
/// Single error block for all assertions.
|
/// Single error block for all assertions.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
||||||
|
@ -32,12 +32,23 @@ EncodingContext::EncodingContext():
|
|||||||
void EncodingContext::reset()
|
void EncodingContext::reset()
|
||||||
{
|
{
|
||||||
resetAllVariables();
|
resetAllVariables();
|
||||||
|
resetSlackId();
|
||||||
m_expressions.clear();
|
m_expressions.clear();
|
||||||
m_globalContext.clear();
|
m_globalContext.clear();
|
||||||
m_state.reset();
|
m_state.reset();
|
||||||
m_assertions.clear();
|
m_assertions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EncodingContext::resetSlackId()
|
||||||
|
{
|
||||||
|
m_nextSlackId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned EncodingContext::newSlackId()
|
||||||
|
{
|
||||||
|
return m_nextSlackId++;
|
||||||
|
}
|
||||||
|
|
||||||
void EncodingContext::clear()
|
void EncodingContext::clear()
|
||||||
{
|
{
|
||||||
m_variables.clear();
|
m_variables.clear();
|
||||||
|
@ -40,6 +40,10 @@ public:
|
|||||||
/// alive because of state variables and inlined function calls.
|
/// alive because of state variables and inlined function calls.
|
||||||
/// To be used in the beginning of a root function visit.
|
/// To be used in the beginning of a root function visit.
|
||||||
void reset();
|
void reset();
|
||||||
|
/// Resets the fresh id for slack variables.
|
||||||
|
void resetSlackId();
|
||||||
|
/// Returns the current fresh slack id and increments it.
|
||||||
|
unsigned newSlackId();
|
||||||
/// Clears the entire context, erasing everything.
|
/// Clears the entire context, erasing everything.
|
||||||
/// To be used before a model checking engine starts.
|
/// To be used before a model checking engine starts.
|
||||||
void clear();
|
void clear();
|
||||||
@ -168,6 +172,9 @@ private:
|
|||||||
/// Whether to conjoin assertions in the assertion stack.
|
/// Whether to conjoin assertions in the assertion stack.
|
||||||
bool m_accumulateAssertions = true;
|
bool m_accumulateAssertions = true;
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
|
/// Fresh ids for slack variables to be created deterministically.
|
||||||
|
unsigned m_nextSlackId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1204,7 +1204,7 @@ pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
|
|||||||
smtutil::Expression const& _left,
|
smtutil::Expression const& _left,
|
||||||
smtutil::Expression const& _right,
|
smtutil::Expression const& _right,
|
||||||
TypePointer const& _commonType,
|
TypePointer const& _commonType,
|
||||||
Expression const&
|
Expression const& _operation
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
static set<Token> validOperators{
|
static set<Token> validOperators{
|
||||||
@ -1227,39 +1227,66 @@ pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
|
|||||||
else
|
else
|
||||||
intType = TypeProvider::uint256();
|
intType = TypeProvider::uint256();
|
||||||
|
|
||||||
smtutil::Expression valueNoMod(
|
auto valueUnbounded = [&]() -> smtutil::Expression {
|
||||||
_op == Token::Add ? _left + _right :
|
switch (_op)
|
||||||
_op == Token::Sub ? _left - _right :
|
{
|
||||||
_op == Token::Div ? division(_left, _right, *intType) :
|
case Token::Add: return _left + _right;
|
||||||
_op == Token::Mul ? _left * _right :
|
case Token::Sub: return _left - _right;
|
||||||
/*_op == Token::Mod*/ _left % _right
|
case Token::Mul: return _left * _right;
|
||||||
);
|
case Token::Div: return division(_left, _right, *intType);
|
||||||
|
case Token::Mod: return _left % _right;
|
||||||
|
default: solAssert(false, "");
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
if (_op == Token::Div || _op == Token::Mod)
|
if (_op == Token::Div || _op == Token::Mod)
|
||||||
|
{
|
||||||
m_context.addAssertion(_right != 0);
|
m_context.addAssertion(_right != 0);
|
||||||
|
|
||||||
|
// mod and unsigned division never underflow/overflow
|
||||||
|
if (_op == Token::Mod || !intType->isSigned())
|
||||||
|
return {valueUnbounded, valueUnbounded};
|
||||||
|
|
||||||
|
// The only case where division overflows is
|
||||||
|
// - type is signed
|
||||||
|
// - LHS is type.min
|
||||||
|
// - RHS is -1
|
||||||
|
// the result is then -(type.min), which wraps back to type.min
|
||||||
|
smtutil::Expression maxLeft = _left == smt::minValue(*intType);
|
||||||
|
smtutil::Expression minusOneRight = _right == -1;
|
||||||
|
smtutil::Expression wrap = smtutil::Expression::ite(maxLeft && minusOneRight, smt::minValue(*intType), valueUnbounded);
|
||||||
|
return {wrap, valueUnbounded};
|
||||||
|
}
|
||||||
|
|
||||||
auto symbMin = smt::minValue(*intType);
|
auto symbMin = smt::minValue(*intType);
|
||||||
auto symbMax = smt::maxValue(*intType);
|
auto symbMax = smt::maxValue(*intType);
|
||||||
|
|
||||||
smtutil::Expression intValueRange = (0 - symbMin) + symbMax + 1;
|
smtutil::Expression intValueRange = (0 - symbMin) + symbMax + 1;
|
||||||
|
string suffix = to_string(_operation.id()) + "_" + to_string(m_context.newSlackId());
|
||||||
|
smt::SymbolicIntVariable k(intType, intType, "k_" + suffix, m_context);
|
||||||
|
smt::SymbolicIntVariable m(intType, intType, "m_" + suffix, m_context);
|
||||||
|
|
||||||
|
// To wrap around valueUnbounded in case of overflow or underflow, we replace it with a k, given:
|
||||||
|
// 1. k + m * intValueRange = valueUnbounded
|
||||||
|
// 2. k is in range of the desired integer type
|
||||||
|
auto wrap = k.currentValue();
|
||||||
|
m_context.addAssertion(valueUnbounded == (k.currentValue() + intValueRange * m.currentValue()));
|
||||||
|
m_context.addAssertion(k.currentValue() >= symbMin);
|
||||||
|
m_context.addAssertion(k.currentValue() <= symbMax);
|
||||||
|
|
||||||
|
// TODO this could be refined:
|
||||||
|
// for unsigned types it's enough to check only the upper bound.
|
||||||
auto value = smtutil::Expression::ite(
|
auto value = smtutil::Expression::ite(
|
||||||
valueNoMod > symbMax,
|
valueUnbounded > symbMax,
|
||||||
valueNoMod % intValueRange,
|
wrap,
|
||||||
smtutil::Expression::ite(
|
smtutil::Expression::ite(
|
||||||
valueNoMod < symbMin,
|
valueUnbounded < symbMin,
|
||||||
valueNoMod % intValueRange,
|
wrap,
|
||||||
valueNoMod
|
valueUnbounded
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (intType->isSigned())
|
return {value, valueUnbounded};
|
||||||
value = smtutil::Expression::ite(
|
|
||||||
value > symbMax,
|
|
||||||
value - intValueRange,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
|
|
||||||
return {value, valueNoMod};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SMTEncoder::compareOperation(BinaryOperation const& _op)
|
void SMTEncoder::compareOperation(BinaryOperation const& _op)
|
||||||
|
@ -307,11 +307,6 @@ bool CompilerStack::analyze()
|
|||||||
if (source->ast && !syntaxChecker.checkSyntax(*source->ast))
|
if (source->ast && !syntaxChecker.checkSyntax(*source->ast))
|
||||||
noErrors = false;
|
noErrors = false;
|
||||||
|
|
||||||
DocStringAnalyser docStringAnalyser(m_errorReporter);
|
|
||||||
for (Source const* source: m_sourceOrder)
|
|
||||||
if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast))
|
|
||||||
noErrors = false;
|
|
||||||
|
|
||||||
m_globalContext = make_shared<GlobalContext>();
|
m_globalContext = make_shared<GlobalContext>();
|
||||||
// We need to keep the same resolver during the whole process.
|
// We need to keep the same resolver during the whole process.
|
||||||
NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter);
|
NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter);
|
||||||
@ -326,28 +321,28 @@ bool CompilerStack::analyze()
|
|||||||
if (source->ast && !resolver.performImports(*source->ast, sourceUnitsByName))
|
if (source->ast && !resolver.performImports(*source->ast, sourceUnitsByName))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// This is the main name and type resolution loop. Needs to be run for every contract, because
|
for (Source const* source: m_sourceOrder)
|
||||||
// the special variables "this" and "super" must be set appropriately.
|
if (source->ast && !resolver.resolveNamesAndTypes(*source->ast))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Store contract definitions.
|
||||||
for (Source const* source: m_sourceOrder)
|
for (Source const* source: m_sourceOrder)
|
||||||
if (source->ast)
|
if (source->ast)
|
||||||
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
|
for (
|
||||||
|
ContractDefinition const* contract:
|
||||||
|
ASTNode::filteredNodes<ContractDefinition>(source->ast->nodes())
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!resolver.resolveNamesAndTypes(*node))
|
// Note that we now reference contracts by their fully qualified names, and
|
||||||
return false;
|
// thus contracts can only conflict if declared in the same source file. This
|
||||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
// should already cause a double-declaration error elsewhere.
|
||||||
{
|
if (!m_contracts.count(contract->fullyQualifiedName()))
|
||||||
// Note that we now reference contracts by their fully qualified names, and
|
m_contracts[contract->fullyQualifiedName()].contract = contract;
|
||||||
// thus contracts can only conflict if declared in the same source file. This
|
else
|
||||||
// should already cause a double-declaration error elsewhere.
|
solAssert(
|
||||||
if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
|
m_errorReporter.hasErrors(),
|
||||||
m_contracts[contract->fullyQualifiedName()].contract = contract;
|
"Contract already present (name clash?), but no error was reported."
|
||||||
else
|
);
|
||||||
solAssert(
|
|
||||||
m_errorReporter.hasErrors(),
|
|
||||||
"Contract already present (name clash?), but no error was reported."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DeclarationTypeChecker declarationTypeChecker(m_errorReporter, m_evmVersion);
|
DeclarationTypeChecker declarationTypeChecker(m_errorReporter, m_evmVersion);
|
||||||
@ -367,6 +362,11 @@ bool CompilerStack::analyze()
|
|||||||
if (!contractLevelChecker.check(*contract))
|
if (!contractLevelChecker.check(*contract))
|
||||||
noErrors = false;
|
noErrors = false;
|
||||||
|
|
||||||
|
DocStringAnalyser docStringAnalyser(m_errorReporter);
|
||||||
|
for (Source const* source: m_sourceOrder)
|
||||||
|
if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast))
|
||||||
|
noErrors = false;
|
||||||
|
|
||||||
// New we run full type checks that go down to the expression level. This
|
// New we run full type checks that go down to the expression level. This
|
||||||
// cannot be done earlier, because we need cross-contract types and information
|
// cannot be done earlier, because we need cross-contract types and information
|
||||||
// about whether a contract is abstract for the `new` expression.
|
// about whether a contract is abstract for the `new` expression.
|
||||||
@ -376,11 +376,8 @@ bool CompilerStack::analyze()
|
|||||||
// which is only done one step later.
|
// which is only done one step later.
|
||||||
TypeChecker typeChecker(m_evmVersion, m_errorReporter);
|
TypeChecker typeChecker(m_evmVersion, m_errorReporter);
|
||||||
for (Source const* source: m_sourceOrder)
|
for (Source const* source: m_sourceOrder)
|
||||||
if (source->ast)
|
if (source->ast && !typeChecker.checkTypeRequirements(*source->ast))
|
||||||
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
|
noErrors = false;
|
||||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
|
||||||
if (!typeChecker.checkTypeRequirements(*contract))
|
|
||||||
noErrors = false;
|
|
||||||
|
|
||||||
if (noErrors)
|
if (noErrors)
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,10 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
|
|||||||
{
|
{
|
||||||
Json::Value doc;
|
Json::Value doc;
|
||||||
Json::Value methods(Json::objectValue);
|
Json::Value methods(Json::objectValue);
|
||||||
|
Json::Value events(Json::objectValue);
|
||||||
|
|
||||||
|
doc["version"] = Json::Value(c_natspecVersion);
|
||||||
|
doc["kind"] = Json::Value("user");
|
||||||
|
|
||||||
auto constructorDefinition(_contractDef.constructor());
|
auto constructorDefinition(_contractDef.constructor());
|
||||||
if (constructorDefinition)
|
if (constructorDefinition)
|
||||||
@ -77,7 +81,17 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto const& event: _contractDef.interfaceEvents())
|
||||||
|
{
|
||||||
|
string value = extractDoc(event->annotation().docTags, "notice");
|
||||||
|
if (!value.empty())
|
||||||
|
events[event->functionType(true)->externalSignature()]["notice"] = value;
|
||||||
|
}
|
||||||
|
|
||||||
doc["methods"] = methods;
|
doc["methods"] = methods;
|
||||||
|
if (!events.empty())
|
||||||
|
doc["events"] = events;
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
@ -87,6 +101,9 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
|
|||||||
Json::Value doc;
|
Json::Value doc;
|
||||||
Json::Value methods(Json::objectValue);
|
Json::Value methods(Json::objectValue);
|
||||||
|
|
||||||
|
doc["version"] = Json::Value(c_natspecVersion);
|
||||||
|
doc["kind"] = Json::Value("dev");
|
||||||
|
|
||||||
auto author = extractDoc(_contractDef.annotation().docTags, "author");
|
auto author = extractDoc(_contractDef.annotation().docTags, "author");
|
||||||
if (!author.empty())
|
if (!author.empty())
|
||||||
doc["author"] = author;
|
doc["author"] = author;
|
||||||
@ -135,9 +152,16 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
|
|||||||
stateVariables[varDecl->name()]["return"] = extractDoc(varDecl->annotation().docTags, "return");
|
stateVariables[varDecl->name()]["return"] = extractDoc(varDecl->annotation().docTags, "return");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Json::Value events(Json::objectValue);
|
||||||
|
for (auto const& event: _contractDef.events())
|
||||||
|
if (auto devDoc = devDocumentation(event->annotation().docTags); !devDoc.empty())
|
||||||
|
events[event->functionType(true)->externalSignature()] = devDoc;
|
||||||
|
|
||||||
doc["methods"] = methods;
|
doc["methods"] = methods;
|
||||||
if (!stateVariables.empty())
|
if (!stateVariables.empty())
|
||||||
doc["stateVariables"] = stateVariables;
|
doc["stateVariables"] = stateVariables;
|
||||||
|
if (!events.empty())
|
||||||
|
doc["events"] = events;
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ struct DocTag;
|
|||||||
class Natspec
|
class Natspec
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static unsigned int constexpr c_natspecVersion = 1;
|
||||||
|
|
||||||
/// Get the User documentation of the contract
|
/// Get the User documentation of the contract
|
||||||
/// @param _contractDef The contract definition
|
/// @param _contractDef The contract definition
|
||||||
/// @return A JSON representation of the contract's user documentation
|
/// @return A JSON representation of the contract's user documentation
|
||||||
|
@ -94,19 +94,12 @@ void DocStringParser::parse(string const& _docString, ErrorReporter& _errorRepor
|
|||||||
{
|
{
|
||||||
// we found a tag
|
// we found a tag
|
||||||
auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
|
auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
|
||||||
if (tagNameEndPos == end)
|
auto tagName = string(tagPos + 1, tagNameEndPos);
|
||||||
{
|
auto tagDataPos = (tagNameEndPos != end) ? tagNameEndPos + 1 : tagNameEndPos;
|
||||||
m_errorReporter->docstringParsingError(
|
currPos = parseDocTag(tagDataPos, end, tagName);
|
||||||
9222_error,
|
|
||||||
"End of tag " + string(tagPos, tagNameEndPos) + " not found"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos));
|
|
||||||
}
|
}
|
||||||
else if (!!m_lastTag) // continuation of the previous tag
|
else if (!!m_lastTag) // continuation of the previous tag
|
||||||
currPos = appendDocTag(currPos, end);
|
currPos = parseDocTagLine(currPos, end, true);
|
||||||
else if (currPos != end)
|
else if (currPos != end)
|
||||||
{
|
{
|
||||||
// if it begins without a tag then consider it as @notice
|
// if it begins without a tag then consider it as @notice
|
||||||
@ -127,7 +120,7 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo
|
|||||||
{
|
{
|
||||||
solAssert(!!m_lastTag, "");
|
solAssert(!!m_lastTag, "");
|
||||||
auto nlPos = find(_pos, _end, '\n');
|
auto nlPos = find(_pos, _end, '\n');
|
||||||
if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t')
|
if (_appending && _pos != _end && *_pos != ' ' && *_pos != '\t')
|
||||||
m_lastTag->content += " ";
|
m_lastTag->content += " ";
|
||||||
else if (!_appending)
|
else if (!_appending)
|
||||||
_pos = skipWhitespace(_pos, _end);
|
_pos = skipWhitespace(_pos, _end);
|
||||||
@ -179,13 +172,7 @@ DocStringParser::iter DocStringParser::parseDocTag(iter _pos, iter _end, string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return appendDocTag(_pos, _end);
|
return parseDocTagLine(_pos, _end, true);
|
||||||
}
|
|
||||||
|
|
||||||
DocStringParser::iter DocStringParser::appendDocTag(iter _pos, iter _end)
|
|
||||||
{
|
|
||||||
solAssert(!!m_lastTag, "");
|
|
||||||
return parseDocTagLine(_pos, _end, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocStringParser::newTag(string const& _tagName)
|
void DocStringParser::newTag(string const& _tagName)
|
||||||
|
@ -50,7 +50,6 @@ private:
|
|||||||
iter parseDocTagParam(iter _pos, iter _end);
|
iter parseDocTagParam(iter _pos, iter _end);
|
||||||
iter appendDocTagParam(iter _pos, iter _end);
|
iter appendDocTagParam(iter _pos, iter _end);
|
||||||
void parseDocString(std::string const& _string);
|
void parseDocString(std::string const& _string);
|
||||||
iter appendDocTag(iter _pos, iter _end);
|
|
||||||
/// Parses the doc tag named @a _tag, adds it to m_docTags and returns the position
|
/// Parses the doc tag named @a _tag, adds it to m_docTags and returns the position
|
||||||
/// after the tag.
|
/// after the tag.
|
||||||
iter parseDocTag(iter _pos, iter _end, std::string const& _tag);
|
iter parseDocTag(iter _pos, iter _end, std::string const& _tag);
|
||||||
|
@ -1825,11 +1825,16 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
|
|||||||
expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
|
expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
|
||||||
break;
|
break;
|
||||||
case Token::Number:
|
case Token::Number:
|
||||||
if (TokenTraits::isEtherSubdenomination(m_scanner->peekNextToken()))
|
if (
|
||||||
|
(m_scanner->peekNextToken() == Token::Identifier && m_scanner->peekLiteral() == "gwei") ||
|
||||||
|
TokenTraits::isEtherSubdenomination(m_scanner->peekNextToken())
|
||||||
|
)
|
||||||
{
|
{
|
||||||
ASTPointer<ASTString> literal = getLiteralAndAdvance();
|
ASTPointer<ASTString> literal = getLiteralAndAdvance();
|
||||||
nodeFactory.markEndPosition();
|
nodeFactory.markEndPosition();
|
||||||
Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->currentToken());
|
Token actualToken = m_scanner->currentToken() == Token::Identifier ? Token::SubGwei : m_scanner->currentToken();
|
||||||
|
|
||||||
|
Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(actualToken);
|
||||||
m_scanner->next();
|
m_scanner->next();
|
||||||
expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
|
expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ bytes solidity::util::fromHex(std::string const& _s, WhenError _throw)
|
|||||||
{
|
{
|
||||||
int h = fromHex(_s[s++], _throw);
|
int h = fromHex(_s[s++], _throw);
|
||||||
if (h != -1)
|
if (h != -1)
|
||||||
ret.push_back(h);
|
ret.push_back(static_cast<uint8_t>(h));
|
||||||
else
|
else
|
||||||
return bytes();
|
return bytes();
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ bytes solidity::util::fromHex(std::string const& _s, WhenError _throw)
|
|||||||
int h = fromHex(_s[i], _throw);
|
int h = fromHex(_s[i], _throw);
|
||||||
int l = fromHex(_s[i + 1], _throw);
|
int l = fromHex(_s[i + 1], _throw);
|
||||||
if (h != -1 && l != -1)
|
if (h != -1 && l != -1)
|
||||||
ret.push_back((uint8_t)(h * 16 + l));
|
ret.push_back(static_cast<uint8_t>(h * 16 + l));
|
||||||
else
|
else
|
||||||
return bytes();
|
return bytes();
|
||||||
}
|
}
|
||||||
@ -148,14 +148,14 @@ string solidity::util::getChecksummedAddress(string const& _addr)
|
|||||||
h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic()));
|
h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic()));
|
||||||
|
|
||||||
string ret = "0x";
|
string ret = "0x";
|
||||||
for (size_t i = 0; i < 40; ++i)
|
for (unsigned i = 0; i < 40; ++i)
|
||||||
{
|
{
|
||||||
char addressCharacter = s[i];
|
char addressCharacter = s[i];
|
||||||
unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf;
|
uint8_t nibble = hash[i / 2u] >> (4u * (1u - (i % 2u))) & 0xf;
|
||||||
if (nibble >= 8)
|
if (nibble >= 8)
|
||||||
ret += toupper(addressCharacter);
|
ret += static_cast<char>(toupper(addressCharacter));
|
||||||
else
|
else
|
||||||
ret += tolower(addressCharacter);
|
ret += static_cast<char>(tolower(addressCharacter));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -64,10 +64,44 @@ public:
|
|||||||
FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); }
|
FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); }
|
||||||
|
|
||||||
/// Explicitly construct, copying from a byte array.
|
/// Explicitly construct, copying from a byte array.
|
||||||
explicit FixedHash(bytes const& _b, ConstructFromHashType _t = FailIfDifferent) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min<unsigned>(_b.size(), N)); else { m_data.fill(0); if (_t != FailIfDifferent) { auto c = std::min<unsigned>(_b.size(), N); for (unsigned i = 0; i < c; ++i) m_data[_t == AlignRight ? N - 1 - i : i] = _b[_t == AlignRight ? _b.size() - 1 - i : i]; } } }
|
explicit FixedHash(bytes const& _array, ConstructFromHashType _sizeMismatchBehavior = FailIfDifferent)
|
||||||
|
{
|
||||||
|
if (_array.size() == N)
|
||||||
|
memcpy(m_data.data(), _array.data(), _array.size());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_data.fill(0);
|
||||||
|
if (_sizeMismatchBehavior != FailIfDifferent)
|
||||||
|
{
|
||||||
|
auto bytesToCopy = std::min<size_t>(_array.size(), N);
|
||||||
|
for (size_t i = 0; i < bytesToCopy; ++i)
|
||||||
|
if (_sizeMismatchBehavior == AlignRight)
|
||||||
|
m_data[N - 1 - i] = _array[_array.size() - 1 - i];
|
||||||
|
else
|
||||||
|
m_data[i] = _array[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Explicitly construct, copying from a byte array.
|
/// Explicitly construct, copying from a byte array.
|
||||||
explicit FixedHash(bytesConstRef _b, ConstructFromHashType _t = FailIfDifferent) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min<unsigned>(_b.size(), N)); else { m_data.fill(0); if (_t != FailIfDifferent) { auto c = std::min<unsigned>(_b.size(), N); for (unsigned i = 0; i < c; ++i) m_data[_t == AlignRight ? N - 1 - i : i] = _b[_t == AlignRight ? _b.size() - 1 - i : i]; } } }
|
explicit FixedHash(bytesConstRef _b, ConstructFromHashType _t = FailIfDifferent)
|
||||||
|
{
|
||||||
|
if (_b.size() == N)
|
||||||
|
memcpy(m_data.data(), _b.data(), std::min<size_t>(_b.size(), N));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_data.fill(0);
|
||||||
|
if (_t != FailIfDifferent)
|
||||||
|
{
|
||||||
|
auto c = std::min<size_t>(_b.size(), N);
|
||||||
|
for (size_t i = 0; i < c; ++i)
|
||||||
|
if (_t == AlignRight)
|
||||||
|
m_data[N - 1 - i] = _b[_b.size() - 1 - i];
|
||||||
|
else
|
||||||
|
m_data[i] = _b[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Explicitly construct, copying from a string.
|
/// Explicitly construct, copying from a string.
|
||||||
explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex, ConstructFromHashType _ht = FailIfDifferent): FixedHash(_t == FromHex ? fromHex(_s, WhenError::Throw) : solidity::util::asBytes(_s), _ht) {}
|
explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex, ConstructFromHashType _ht = FailIfDifferent): FixedHash(_t == FromHex ? fromHex(_s, WhenError::Throw) : solidity::util::asBytes(_s), _ht) {}
|
||||||
|
@ -69,25 +69,25 @@ static uint64_t const RC[24] = \
|
|||||||
#define REPEAT6(e) e e e e e e
|
#define REPEAT6(e) e e e e e e
|
||||||
#define REPEAT24(e) REPEAT6(e e e e)
|
#define REPEAT24(e) REPEAT6(e e e e)
|
||||||
#define REPEAT5(e) e e e e e
|
#define REPEAT5(e) e e e e e
|
||||||
#define FOR5(v, s, e) \
|
#define FOR5(type, v, s, e) \
|
||||||
v = 0; \
|
v = 0; \
|
||||||
REPEAT5(e; v += s;)
|
REPEAT5(e; v = static_cast<type>(v + s);)
|
||||||
|
|
||||||
/*** Keccak-f[1600] ***/
|
/*** Keccak-f[1600] ***/
|
||||||
static inline void keccakf(void* state) {
|
static inline void keccakf(void* state) {
|
||||||
uint64_t* a = (uint64_t*)state;
|
auto* a = static_cast<uint64_t*>(state);
|
||||||
uint64_t b[5] = {0};
|
uint64_t b[5] = {0};
|
||||||
|
|
||||||
for (int i = 0; i < 24; i++)
|
for (int i = 0; i < 24; i++)
|
||||||
{
|
{
|
||||||
uint8_t x, y;
|
uint8_t x, y;
|
||||||
// Theta
|
// Theta
|
||||||
FOR5(x, 1,
|
FOR5(uint8_t, x, 1,
|
||||||
b[x] = 0;
|
b[x] = 0;
|
||||||
FOR5(y, 5,
|
FOR5(uint8_t, y, 5,
|
||||||
b[x] ^= a[x + y]; ))
|
b[x] ^= a[x + y]; ))
|
||||||
FOR5(x, 1,
|
FOR5(uint8_t, x, 1,
|
||||||
FOR5(y, 5,
|
FOR5(uint8_t, y, 5,
|
||||||
a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); ))
|
a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); ))
|
||||||
// Rho and pi
|
// Rho and pi
|
||||||
uint64_t t = a[1];
|
uint64_t t = a[1];
|
||||||
@ -97,11 +97,12 @@ static inline void keccakf(void* state) {
|
|||||||
t = b[0];
|
t = b[0];
|
||||||
x++; )
|
x++; )
|
||||||
// Chi
|
// Chi
|
||||||
FOR5(y,
|
FOR5(uint8_t,
|
||||||
|
y,
|
||||||
5,
|
5,
|
||||||
FOR5(x, 1,
|
FOR5(uint8_t, x, 1,
|
||||||
b[x] = a[y + x];)
|
b[x] = a[y + x];)
|
||||||
FOR5(x, 1,
|
FOR5(uint8_t, x, 1,
|
||||||
a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]); ))
|
a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]); ))
|
||||||
// Iota
|
// Iota
|
||||||
a[0] ^= RC[i];
|
a[0] ^= RC[i];
|
||||||
|
@ -132,17 +132,11 @@ vector<YulString> AsmAnalyzer::operator()(Identifier const& _identifier)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool found = false;
|
bool found = m_resolver && m_resolver(
|
||||||
if (m_resolver)
|
_identifier,
|
||||||
{
|
yul::IdentifierContext::RValue,
|
||||||
bool insideFunction = m_currentScope->insideFunction();
|
m_currentScope->insideFunction()
|
||||||
size_t stackSize = m_resolver(_identifier, yul::IdentifierContext::RValue, insideFunction);
|
);
|
||||||
if (stackSize != size_t(-1))
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
yulAssert(stackSize == 1, "Invalid stack size of external reference.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found && watcher.ok())
|
if (!found && watcher.ok())
|
||||||
// Only add an error message if the callback did not do it.
|
// Only add an error message if the callback did not do it.
|
||||||
m_errorReporter.declarationError(8198_error, _identifier.location, "Identifier not found.");
|
m_errorReporter.declarationError(8198_error, _identifier.location, "Identifier not found.");
|
||||||
@ -173,6 +167,17 @@ void AsmAnalyzer::operator()(Assignment const& _assignment)
|
|||||||
size_t const numVariables = _assignment.variableNames.size();
|
size_t const numVariables = _assignment.variableNames.size();
|
||||||
yulAssert(numVariables >= 1, "");
|
yulAssert(numVariables >= 1, "");
|
||||||
|
|
||||||
|
set<YulString> variables;
|
||||||
|
for (auto const& _variableName: _assignment.variableNames)
|
||||||
|
if (!variables.insert(_variableName.name).second)
|
||||||
|
m_errorReporter.declarationError(
|
||||||
|
9005_error,
|
||||||
|
_assignment.location,
|
||||||
|
"Variable " +
|
||||||
|
_variableName.name.str() +
|
||||||
|
" occurs multiple times on the left-hand side of the assignment."
|
||||||
|
);
|
||||||
|
|
||||||
vector<YulString> types = std::visit(*this, *_assignment.value);
|
vector<YulString> types = std::visit(*this, *_assignment.value);
|
||||||
|
|
||||||
if (types.size() != numVariables)
|
if (types.size() != numVariables)
|
||||||
@ -270,7 +275,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
if (f->literalArguments)
|
if (f->literalArguments)
|
||||||
needsLiteralArguments = &f->literalArguments.value();
|
needsLiteralArguments = &f->literalArguments.value();
|
||||||
|
|
||||||
warnOnInstructions(_funCall);
|
validateInstructions(_funCall);
|
||||||
}
|
}
|
||||||
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
|
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
|
||||||
[&](Scope::Variable const&)
|
[&](Scope::Variable const&)
|
||||||
@ -288,7 +293,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
if (!warnOnInstructions(_funCall))
|
if (!validateInstructions(_funCall))
|
||||||
m_errorReporter.declarationError(4619_error, _funCall.functionName.location, "Function not found.");
|
m_errorReporter.declarationError(4619_error, _funCall.functionName.location, "Function not found.");
|
||||||
yulAssert(!watcher.ok(), "Expected a reported error.");
|
yulAssert(!watcher.ok(), "Expected a reported error.");
|
||||||
}
|
}
|
||||||
@ -307,10 +312,15 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
for (size_t i = _funCall.arguments.size(); i > 0; i--)
|
for (size_t i = _funCall.arguments.size(); i > 0; i--)
|
||||||
{
|
{
|
||||||
Expression const& arg = _funCall.arguments[i - 1];
|
Expression const& arg = _funCall.arguments[i - 1];
|
||||||
|
bool isLiteralArgument = needsLiteralArguments && (*needsLiteralArguments)[i - 1];
|
||||||
|
bool isStringLiteral = holds_alternative<Literal>(arg) && get<Literal>(arg).kind == LiteralKind::String;
|
||||||
|
|
||||||
argTypes.emplace_back(expectExpression(arg));
|
if (isLiteralArgument && isStringLiteral)
|
||||||
|
argTypes.emplace_back(expectUnlimitedStringLiteral(get<Literal>(arg)));
|
||||||
|
else
|
||||||
|
argTypes.emplace_back(expectExpression(arg));
|
||||||
|
|
||||||
if (needsLiteralArguments && (*needsLiteralArguments)[i - 1])
|
if (isLiteralArgument)
|
||||||
{
|
{
|
||||||
if (!holds_alternative<Literal>(arg))
|
if (!holds_alternative<Literal>(arg))
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
@ -439,6 +449,14 @@ YulString AsmAnalyzer::expectExpression(Expression const& _expr)
|
|||||||
return types.empty() ? m_dialect.defaultType : types.front();
|
return types.empty() ? m_dialect.defaultType : types.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
YulString AsmAnalyzer::expectUnlimitedStringLiteral(Literal const& _literal)
|
||||||
|
{
|
||||||
|
yulAssert(_literal.kind == LiteralKind::String, "");
|
||||||
|
yulAssert(m_dialect.validTypeForLiteral(LiteralKind::String, _literal.value, _literal.type), "");
|
||||||
|
|
||||||
|
return {_literal.type};
|
||||||
|
}
|
||||||
|
|
||||||
void AsmAnalyzer::expectBoolExpression(Expression const& _expr)
|
void AsmAnalyzer::expectBoolExpression(Expression const& _expr)
|
||||||
{
|
{
|
||||||
YulString type = expectExpression(_expr);
|
YulString type = expectExpression(_expr);
|
||||||
@ -478,12 +496,10 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueT
|
|||||||
else if (m_resolver)
|
else if (m_resolver)
|
||||||
{
|
{
|
||||||
bool insideFunction = m_currentScope->insideFunction();
|
bool insideFunction = m_currentScope->insideFunction();
|
||||||
size_t variableSize = m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction);
|
if (m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction))
|
||||||
if (variableSize != size_t(-1))
|
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
variableType = &m_dialect.defaultType;
|
variableType = &m_dialect.defaultType;
|
||||||
yulAssert(variableSize == 1, "Invalid stack size of external reference.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,16 +552,16 @@ void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, Sour
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsmAnalyzer::warnOnInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
|
bool AsmAnalyzer::validateInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
|
||||||
{
|
{
|
||||||
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));
|
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));
|
||||||
if (builtin)
|
if (builtin && builtin->instruction.has_value())
|
||||||
return warnOnInstructions(builtin->instruction.value(), _location);
|
return validateInstructions(builtin->instruction.value(), _location);
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation const& _location)
|
bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocation const& _location)
|
||||||
{
|
{
|
||||||
// We assume that returndatacopy, returndatasize and staticcall are either all available
|
// We assume that returndatacopy, returndatasize and staticcall are either all available
|
||||||
// or all not available.
|
// or all not available.
|
||||||
@ -553,9 +569,16 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
|
|||||||
// Similarly we assume bitwise shifting and create2 go together.
|
// Similarly we assume bitwise shifting and create2 go together.
|
||||||
yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), "");
|
yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), "");
|
||||||
|
|
||||||
auto errorForVM = [&](string const& vmKindMessage) {
|
// These instructions are disabled in the dialect.
|
||||||
|
yulAssert(
|
||||||
|
_instr != evmasm::Instruction::JUMP &&
|
||||||
|
_instr != evmasm::Instruction::JUMPI &&
|
||||||
|
_instr != evmasm::Instruction::JUMPDEST,
|
||||||
|
"");
|
||||||
|
|
||||||
|
auto errorForVM = [&](ErrorId _errorId, string const& vmKindMessage) {
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
7079_error,
|
_errorId,
|
||||||
_location,
|
_location,
|
||||||
"The \"" +
|
"The \"" +
|
||||||
boost::to_lower_copy(instructionInfo(_instr).name)
|
boost::to_lower_copy(instructionInfo(_instr).name)
|
||||||
@ -572,21 +595,21 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
|
|||||||
_instr == evmasm::Instruction::RETURNDATACOPY ||
|
_instr == evmasm::Instruction::RETURNDATACOPY ||
|
||||||
_instr == evmasm::Instruction::RETURNDATASIZE
|
_instr == evmasm::Instruction::RETURNDATASIZE
|
||||||
) && !m_evmVersion.supportsReturndata())
|
) && !m_evmVersion.supportsReturndata())
|
||||||
errorForVM("only available for Byzantium-compatible");
|
errorForVM(7756_error, "only available for Byzantium-compatible");
|
||||||
else if (_instr == evmasm::Instruction::STATICCALL && !m_evmVersion.hasStaticCall())
|
else if (_instr == evmasm::Instruction::STATICCALL && !m_evmVersion.hasStaticCall())
|
||||||
errorForVM("only available for Byzantium-compatible");
|
errorForVM(1503_error, "only available for Byzantium-compatible");
|
||||||
else if ((
|
else if ((
|
||||||
_instr == evmasm::Instruction::SHL ||
|
_instr == evmasm::Instruction::SHL ||
|
||||||
_instr == evmasm::Instruction::SHR ||
|
_instr == evmasm::Instruction::SHR ||
|
||||||
_instr == evmasm::Instruction::SAR
|
_instr == evmasm::Instruction::SAR
|
||||||
) && !m_evmVersion.hasBitwiseShifting())
|
) && !m_evmVersion.hasBitwiseShifting())
|
||||||
errorForVM("only available for Constantinople-compatible");
|
errorForVM(6612_error, "only available for Constantinople-compatible");
|
||||||
else if (_instr == evmasm::Instruction::CREATE2 && !m_evmVersion.hasCreate2())
|
else if (_instr == evmasm::Instruction::CREATE2 && !m_evmVersion.hasCreate2())
|
||||||
errorForVM("only available for Constantinople-compatible");
|
errorForVM(6166_error, "only available for Constantinople-compatible");
|
||||||
else if (_instr == evmasm::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash())
|
else if (_instr == evmasm::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash())
|
||||||
errorForVM("only available for Constantinople-compatible");
|
errorForVM(7110_error, "only available for Constantinople-compatible");
|
||||||
else if (_instr == evmasm::Instruction::CHAINID && !m_evmVersion.hasChainID())
|
else if (_instr == evmasm::Instruction::CHAINID && !m_evmVersion.hasChainID())
|
||||||
errorForVM("only available for Istanbul-compatible");
|
errorForVM(1561_error, "only available for Istanbul-compatible");
|
||||||
else if (_instr == evmasm::Instruction::PC)
|
else if (_instr == evmasm::Instruction::PC)
|
||||||
m_errorReporter.warning(
|
m_errorReporter.warning(
|
||||||
2450_error,
|
2450_error,
|
||||||
@ -596,20 +619,7 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
|
|||||||
"\" instruction is deprecated and will be removed in the next breaking release."
|
"\" instruction is deprecated and will be removed in the next breaking release."
|
||||||
);
|
);
|
||||||
else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance())
|
else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance())
|
||||||
errorForVM("only available for Istanbul-compatible");
|
errorForVM(3672_error, "only available for Istanbul-compatible");
|
||||||
else if (
|
|
||||||
_instr == evmasm::Instruction::JUMP ||
|
|
||||||
_instr == evmasm::Instruction::JUMPI ||
|
|
||||||
_instr == evmasm::Instruction::JUMPDEST
|
|
||||||
)
|
|
||||||
m_errorReporter.error(
|
|
||||||
4316_error,
|
|
||||||
Error::Type::SyntaxError,
|
|
||||||
_location,
|
|
||||||
"Jump instructions and labels are low-level EVM features that can lead to "
|
|
||||||
"incorrect stack access. Because of that they are disallowed in strict assembly. "
|
|
||||||
"Use functions, \"switch\", \"if\" or \"for\" statements instead."
|
|
||||||
);
|
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -97,6 +97,7 @@ private:
|
|||||||
/// Visits the expression, expects that it evaluates to exactly one value and
|
/// Visits the expression, expects that it evaluates to exactly one value and
|
||||||
/// returns the type. Reports errors on errors and returns the default type.
|
/// returns the type. Reports errors on errors and returns the default type.
|
||||||
YulString expectExpression(Expression const& _expr);
|
YulString expectExpression(Expression const& _expr);
|
||||||
|
YulString expectUnlimitedStringLiteral(Literal const& _literal);
|
||||||
/// Vists the expression and expects it to return a single boolean value.
|
/// Vists the expression and expects it to return a single boolean value.
|
||||||
/// Reports an error otherwise.
|
/// Reports an error otherwise.
|
||||||
void expectBoolExpression(Expression const& _expr);
|
void expectBoolExpression(Expression const& _expr);
|
||||||
@ -109,12 +110,12 @@ private:
|
|||||||
Scope& scope(Block const* _block);
|
Scope& scope(Block const* _block);
|
||||||
void expectValidType(YulString _type, langutil::SourceLocation const& _location);
|
void expectValidType(YulString _type, langutil::SourceLocation const& _location);
|
||||||
void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location);
|
void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location);
|
||||||
bool warnOnInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
|
|
||||||
bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);
|
|
||||||
|
|
||||||
bool warnOnInstructions(FunctionCall const& _functionCall)
|
bool validateInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
|
||||||
|
bool validateInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);
|
||||||
|
bool validateInstructions(FunctionCall const& _functionCall)
|
||||||
{
|
{
|
||||||
return warnOnInstructions(_functionCall.functionName.name.str(), _functionCall.functionName.location);
|
return validateInstructions(_functionCall.functionName.name.str(), _functionCall.functionName.location);
|
||||||
}
|
}
|
||||||
|
|
||||||
yul::ExternalIdentifierAccess::Resolver m_resolver;
|
yul::ExternalIdentifierAccess::Resolver m_resolver;
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
#include <libyul/AsmJsonConverter.h>
|
#include <libyul/AsmJsonConverter.h>
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
#include <liblangutil/Exceptions.h>
|
#include <libyul/Exceptions.h>
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -38,7 +38,7 @@ Json::Value AsmJsonConverter::operator()(Block const& _node) const
|
|||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(TypedName const& _node) const
|
Json::Value AsmJsonConverter::operator()(TypedName const& _node) const
|
||||||
{
|
{
|
||||||
solAssert(!_node.name.empty(), "Invalid variable name.");
|
yulAssert(!_node.name.empty(), "Invalid variable name.");
|
||||||
Json::Value ret = createAstNode(_node.location, "YulTypedName");
|
Json::Value ret = createAstNode(_node.location, "YulTypedName");
|
||||||
ret["name"] = _node.name.str();
|
ret["name"] = _node.name.str();
|
||||||
ret["type"] = _node.type.str();
|
ret["type"] = _node.type.str();
|
||||||
@ -51,7 +51,7 @@ Json::Value AsmJsonConverter::operator()(Literal const& _node) const
|
|||||||
switch (_node.kind)
|
switch (_node.kind)
|
||||||
{
|
{
|
||||||
case LiteralKind::Number:
|
case LiteralKind::Number:
|
||||||
solAssert(
|
yulAssert(
|
||||||
util::isValidDecimal(_node.value.str()) || util::isValidHex(_node.value.str()),
|
util::isValidDecimal(_node.value.str()) || util::isValidHex(_node.value.str()),
|
||||||
"Invalid number literal"
|
"Invalid number literal"
|
||||||
);
|
);
|
||||||
@ -71,7 +71,7 @@ Json::Value AsmJsonConverter::operator()(Literal const& _node) const
|
|||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(Identifier const& _node) const
|
Json::Value AsmJsonConverter::operator()(Identifier const& _node) const
|
||||||
{
|
{
|
||||||
solAssert(!_node.name.empty(), "Invalid identifier");
|
yulAssert(!_node.name.empty(), "Invalid identifier");
|
||||||
Json::Value ret = createAstNode(_node.location, "YulIdentifier");
|
Json::Value ret = createAstNode(_node.location, "YulIdentifier");
|
||||||
ret["name"] = _node.name.str();
|
ret["name"] = _node.name.str();
|
||||||
return ret;
|
return ret;
|
||||||
@ -79,7 +79,7 @@ Json::Value AsmJsonConverter::operator()(Identifier const& _node) const
|
|||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(Assignment const& _node) const
|
Json::Value AsmJsonConverter::operator()(Assignment const& _node) const
|
||||||
{
|
{
|
||||||
solAssert(_node.variableNames.size() >= 1, "Invalid assignment syntax");
|
yulAssert(_node.variableNames.size() >= 1, "Invalid assignment syntax");
|
||||||
Json::Value ret = createAstNode(_node.location, "YulAssignment");
|
Json::Value ret = createAstNode(_node.location, "YulAssignment");
|
||||||
for (auto const& var: _node.variableNames)
|
for (auto const& var: _node.variableNames)
|
||||||
ret["variableNames"].append((*this)(var));
|
ret["variableNames"].append((*this)(var));
|
||||||
@ -115,7 +115,7 @@ Json::Value AsmJsonConverter::operator()(VariableDeclaration const& _node) const
|
|||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const
|
Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const
|
||||||
{
|
{
|
||||||
solAssert(!_node.name.empty(), "Invalid function name.");
|
yulAssert(!_node.name.empty(), "Invalid function name.");
|
||||||
Json::Value ret = createAstNode(_node.location, "YulFunctionDefinition");
|
Json::Value ret = createAstNode(_node.location, "YulFunctionDefinition");
|
||||||
ret["name"] = _node.name.str();
|
ret["name"] = _node.name.str();
|
||||||
for (auto const& var: _node.parameters)
|
for (auto const& var: _node.parameters)
|
||||||
@ -128,7 +128,7 @@ Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const
|
|||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(If const& _node) const
|
Json::Value AsmJsonConverter::operator()(If const& _node) const
|
||||||
{
|
{
|
||||||
solAssert(_node.condition, "Invalid if condition.");
|
yulAssert(_node.condition, "Invalid if condition.");
|
||||||
Json::Value ret = createAstNode(_node.location, "YulIf");
|
Json::Value ret = createAstNode(_node.location, "YulIf");
|
||||||
ret["condition"] = std::visit(*this, *_node.condition);
|
ret["condition"] = std::visit(*this, *_node.condition);
|
||||||
ret["body"] = (*this)(_node.body);
|
ret["body"] = (*this)(_node.body);
|
||||||
@ -137,7 +137,7 @@ Json::Value AsmJsonConverter::operator()(If const& _node) const
|
|||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(Switch const& _node) const
|
Json::Value AsmJsonConverter::operator()(Switch const& _node) const
|
||||||
{
|
{
|
||||||
solAssert(_node.expression, "Invalid expression pointer.");
|
yulAssert(_node.expression, "Invalid expression pointer.");
|
||||||
Json::Value ret = createAstNode(_node.location, "YulSwitch");
|
Json::Value ret = createAstNode(_node.location, "YulSwitch");
|
||||||
ret["expression"] = std::visit(*this, *_node.expression);
|
ret["expression"] = std::visit(*this, *_node.expression);
|
||||||
for (auto const& var: _node.cases)
|
for (auto const& var: _node.cases)
|
||||||
@ -155,14 +155,14 @@ Json::Value AsmJsonConverter::operator()(Case const& _node) const
|
|||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(ForLoop const& _node) const
|
Json::Value AsmJsonConverter::operator()(ForLoop const& _node) const
|
||||||
{
|
{
|
||||||
solAssert(_node.condition, "Invalid for loop condition.");
|
yulAssert(_node.condition, "Invalid for loop condition.");
|
||||||
Json::Value ret = createAstNode(_node.location, "YulForLoop");
|
Json::Value ret = createAstNode(_node.location, "YulForLoop");
|
||||||
ret["pre"] = (*this)(_node.pre);
|
ret["pre"] = (*this)(_node.pre);
|
||||||
ret["condition"] = std::visit(*this, *_node.condition);
|
ret["condition"] = std::visit(*this, *_node.condition);
|
||||||
ret["post"] = (*this)(_node.post);
|
ret["post"] = (*this)(_node.post);
|
||||||
ret["body"] = (*this)(_node.body);
|
ret["body"] = (*this)(_node.body);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value AsmJsonConverter::operator()(Break const& _node) const
|
Json::Value AsmJsonConverter::operator()(Break const& _node) const
|
||||||
{
|
{
|
||||||
@ -196,7 +196,6 @@ Json::Value AsmJsonConverter::vectorOfVariantsToJson(vector<T> const& _vec) cons
|
|||||||
Json::Value ret{Json::arrayValue};
|
Json::Value ret{Json::arrayValue};
|
||||||
for (auto const& var: _vec)
|
for (auto const& var: _vec)
|
||||||
ret.append(std::visit(*this, var));
|
ret.append(std::visit(*this, var));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +175,8 @@ Statement Parser::parseStatement()
|
|||||||
case Token::Comma:
|
case Token::Comma:
|
||||||
case Token::AssemblyAssign:
|
case Token::AssemblyAssign:
|
||||||
{
|
{
|
||||||
std::vector<Identifier> variableNames;
|
Assignment assignment;
|
||||||
|
assignment.location = locationOf(elementary);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@ -197,7 +198,7 @@ Statement Parser::parseStatement()
|
|||||||
if (m_dialect.builtin(identifier.name))
|
if (m_dialect.builtin(identifier.name))
|
||||||
fatalParserError(6272_error, "Cannot assign to builtin function \"" + identifier.name.str() + "\".");
|
fatalParserError(6272_error, "Cannot assign to builtin function \"" + identifier.name.str() + "\".");
|
||||||
|
|
||||||
variableNames.emplace_back(identifier);
|
assignment.variableNames.emplace_back(identifier);
|
||||||
|
|
||||||
if (currentToken() != Token::Comma)
|
if (currentToken() != Token::Comma)
|
||||||
break;
|
break;
|
||||||
@ -207,10 +208,6 @@ Statement Parser::parseStatement()
|
|||||||
elementary = parseElementaryOperation();
|
elementary = parseElementaryOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assignment assignment;
|
|
||||||
assignment.location = std::get<Identifier>(elementary).location;
|
|
||||||
assignment.variableNames = std::move(variableNames);
|
|
||||||
|
|
||||||
expectToken(Token::AssemblyAssign);
|
expectToken(Token::AssemblyAssign);
|
||||||
|
|
||||||
assignment.value = make_unique<Expression>(parseExpression());
|
assignment.value = make_unique<Expression>(parseExpression());
|
||||||
@ -300,20 +297,6 @@ Expression Parser::parseExpression()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<evmasm::Instruction, string> const& Parser::instructionNames()
|
|
||||||
{
|
|
||||||
static map<evmasm::Instruction, string> s_instructionNames;
|
|
||||||
if (s_instructionNames.empty())
|
|
||||||
{
|
|
||||||
for (auto const& instr: instructions())
|
|
||||||
s_instructionNames[instr.second] = instr.first;
|
|
||||||
// set the ambiguous instructions to a clear default
|
|
||||||
s_instructionNames[evmasm::Instruction::SELFDESTRUCT] = "selfdestruct";
|
|
||||||
s_instructionNames[evmasm::Instruction::KECCAK256] = "keccak256";
|
|
||||||
}
|
|
||||||
return s_instructionNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
Parser::ElementaryOperation Parser::parseElementaryOperation()
|
Parser::ElementaryOperation Parser::parseElementaryOperation()
|
||||||
{
|
{
|
||||||
RecursionGuard recursionGuard(*this);
|
RecursionGuard recursionGuard(*this);
|
||||||
|
@ -86,7 +86,6 @@ protected:
|
|||||||
ForLoop parseForLoop();
|
ForLoop parseForLoop();
|
||||||
/// Parses a functional expression that has to push exactly one stack element
|
/// Parses a functional expression that has to push exactly one stack element
|
||||||
Expression parseExpression();
|
Expression parseExpression();
|
||||||
static std::map<evmasm::Instruction, std::string> const& instructionNames();
|
|
||||||
/// Parses an elementary operation, i.e. a literal, identifier, instruction or
|
/// Parses an elementary operation, i.e. a literal, identifier, instruction or
|
||||||
/// builtin functian call (only the name).
|
/// builtin functian call (only the name).
|
||||||
ElementaryOperation parseElementaryOperation();
|
ElementaryOperation parseElementaryOperation();
|
||||||
|
@ -108,7 +108,7 @@ void AssemblyStack::translate(AssemblyStack::Language _targetLanguage)
|
|||||||
if (m_language == _targetLanguage)
|
if (m_language == _targetLanguage)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
solAssert(
|
yulAssert(
|
||||||
m_language == Language::StrictAssembly && _targetLanguage == Language::Ewasm,
|
m_language == Language::StrictAssembly && _targetLanguage == Language::Ewasm,
|
||||||
"Invalid language combination"
|
"Invalid language combination"
|
||||||
);
|
);
|
||||||
@ -160,7 +160,7 @@ void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _evm15, bool _o
|
|||||||
dialect = &EVMDialectTyped::instance(m_evmVersion);
|
dialect = &EVMDialectTyped::instance(m_evmVersion);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
solAssert(false, "Invalid language.");
|
yulAssert(false, "Invalid language.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ string solidity::yul::reindent(string const& _code)
|
|||||||
auto const static countBraces = [](string const& _s) noexcept -> int
|
auto const static countBraces = [](string const& _s) noexcept -> int
|
||||||
{
|
{
|
||||||
auto const i = _s.find("//");
|
auto const i = _s.find("//");
|
||||||
auto const e = i == _s.npos ? end(_s) : next(begin(_s), i);
|
auto const e = i == _s.npos ? end(_s) : next(begin(_s), static_cast<ptrdiff_t>(i));
|
||||||
auto const opening = count_if(begin(_s), e, [](auto ch) { return ch == '{' || ch == '('; });
|
auto const opening = count_if(begin(_s), e, [](auto ch) { return ch == '{' || ch == '('; });
|
||||||
auto const closing = count_if(begin(_s), e, [](auto ch) { return ch == '}' || ch == ')'; });
|
auto const closing = count_if(begin(_s), e, [](auto ch) { return ch == '}' || ch == ')'; });
|
||||||
return opening - closing;
|
return opening - closing;
|
||||||
|
@ -71,10 +71,10 @@ public:
|
|||||||
{
|
{
|
||||||
// FNV hash - can be replaced by a better one, e.g. xxhash64
|
// FNV hash - can be replaced by a better one, e.g. xxhash64
|
||||||
std::uint64_t hash = emptyHash();
|
std::uint64_t hash = emptyHash();
|
||||||
for (auto c: v)
|
for (char c: v)
|
||||||
{
|
{
|
||||||
hash *= 1099511628211u;
|
hash *= 1099511628211u;
|
||||||
hash ^= c;
|
hash ^= static_cast<uint64_t>(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
|
@ -50,6 +50,7 @@ class AbstractAssembly
|
|||||||
public:
|
public:
|
||||||
using LabelID = size_t;
|
using LabelID = size_t;
|
||||||
using SubID = size_t;
|
using SubID = size_t;
|
||||||
|
enum class JumpType { Ordinary, IntoFunction, OutOfFunction };
|
||||||
|
|
||||||
virtual ~AbstractAssembly() = default;
|
virtual ~AbstractAssembly() = default;
|
||||||
|
|
||||||
@ -78,13 +79,13 @@ public:
|
|||||||
/// Append a jump instruction.
|
/// Append a jump instruction.
|
||||||
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
||||||
/// This is helpful to stack height analysis if there is no continuing control flow.
|
/// This is helpful to stack height analysis if there is no continuing control flow.
|
||||||
virtual void appendJump(int _stackDiffAfter) = 0;
|
virtual void appendJump(int _stackDiffAfter, JumpType _jumpType = JumpType::Ordinary) = 0;
|
||||||
|
|
||||||
/// Append a jump-to-immediate operation.
|
/// Append a jump-to-immediate operation.
|
||||||
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
||||||
virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter = 0) = 0;
|
virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter = 0, JumpType _jumpType = JumpType::Ordinary) = 0;
|
||||||
/// Append a jump-to-if-immediate operation.
|
/// Append a jump-to-if-immediate operation.
|
||||||
virtual void appendJumpToIf(LabelID _labelId) = 0;
|
virtual void appendJumpToIf(LabelID _labelId, JumpType _jumpType = JumpType::Ordinary) = 0;
|
||||||
/// Start a subroutine identified by @a _labelId that takes @a _arguments
|
/// Start a subroutine identified by @a _labelId that takes @a _arguments
|
||||||
/// stack slots as arguments.
|
/// stack slots as arguments.
|
||||||
virtual void appendBeginsub(LabelID _labelId, int _arguments) = 0;
|
virtual void appendBeginsub(LabelID _labelId, int _arguments) = 0;
|
||||||
@ -118,7 +119,7 @@ enum class IdentifierContext { LValue, RValue, VariableDeclaration };
|
|||||||
/// to inline assembly (not used in standalone assembly mode).
|
/// to inline assembly (not used in standalone assembly mode).
|
||||||
struct ExternalIdentifierAccess
|
struct ExternalIdentifierAccess
|
||||||
{
|
{
|
||||||
using Resolver = std::function<size_t(Identifier const&, IdentifierContext, bool /*_crossesFunctionBoundary*/)>;
|
using Resolver = std::function<bool(Identifier const&, IdentifierContext, bool /*_crossesFunctionBoundary*/)>;
|
||||||
/// Resolve an external reference given by the identifier in the given context.
|
/// Resolve an external reference given by the identifier in the given context.
|
||||||
/// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
|
/// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
|
||||||
Resolver resolve;
|
Resolver resolve;
|
||||||
|
@ -98,22 +98,22 @@ void EthAssemblyAdapter::appendLinkerSymbol(std::string const& _linkerSymbol)
|
|||||||
m_assembly.appendLibraryAddress(_linkerSymbol);
|
m_assembly.appendLibraryAddress(_linkerSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EthAssemblyAdapter::appendJump(int _stackDiffAfter)
|
void EthAssemblyAdapter::appendJump(int _stackDiffAfter, JumpType _jumpType)
|
||||||
{
|
{
|
||||||
appendInstruction(evmasm::Instruction::JUMP);
|
appendJumpInstruction(evmasm::Instruction::JUMP, _jumpType);
|
||||||
m_assembly.adjustDeposit(_stackDiffAfter);
|
m_assembly.adjustDeposit(_stackDiffAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType)
|
||||||
{
|
{
|
||||||
appendLabelReference(_labelId);
|
appendLabelReference(_labelId);
|
||||||
appendJump(_stackDiffAfter);
|
appendJump(_stackDiffAfter, _jumpType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId)
|
void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId, JumpType _jumpType)
|
||||||
{
|
{
|
||||||
appendLabelReference(_labelId);
|
appendLabelReference(_labelId);
|
||||||
appendInstruction(evmasm::Instruction::JUMPI);
|
appendJumpInstruction(evmasm::Instruction::JUMPI, _jumpType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EthAssemblyAdapter::appendBeginsub(LabelID, int)
|
void EthAssemblyAdapter::appendBeginsub(LabelID, int)
|
||||||
@ -143,14 +143,14 @@ pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::
|
|||||||
{
|
{
|
||||||
shared_ptr<evmasm::Assembly> assembly{make_shared<evmasm::Assembly>()};
|
shared_ptr<evmasm::Assembly> assembly{make_shared<evmasm::Assembly>()};
|
||||||
auto sub = m_assembly.newSub(assembly);
|
auto sub = m_assembly.newSub(assembly);
|
||||||
return {make_shared<EthAssemblyAdapter>(*assembly), size_t(sub.data())};
|
return {make_shared<EthAssemblyAdapter>(*assembly), static_cast<size_t>(sub.data())};
|
||||||
}
|
}
|
||||||
|
|
||||||
void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub)
|
void EthAssemblyAdapter::appendDataOffset(AbstractAssembly::SubID _sub)
|
||||||
{
|
{
|
||||||
auto it = m_dataHashBySubId.find(_sub);
|
auto it = m_dataHashBySubId.find(_sub);
|
||||||
if (it == m_dataHashBySubId.end())
|
if (it == m_dataHashBySubId.end())
|
||||||
m_assembly.pushSubroutineOffset(size_t(_sub));
|
m_assembly.pushSubroutineOffset(_sub);
|
||||||
else
|
else
|
||||||
m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second);
|
m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second);
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ void EthAssemblyAdapter::appendDataSize(AbstractAssembly::SubID _sub)
|
|||||||
{
|
{
|
||||||
auto it = m_dataHashBySubId.find(_sub);
|
auto it = m_dataHashBySubId.find(_sub);
|
||||||
if (it == m_dataHashBySubId.end())
|
if (it == m_dataHashBySubId.end())
|
||||||
m_assembly.pushSubroutineSize(size_t(_sub));
|
m_assembly.pushSubroutineSize(static_cast<size_t>(_sub));
|
||||||
else
|
else
|
||||||
m_assembly << u256(m_assembly.data(h256(it->second)).size());
|
m_assembly << u256(m_assembly.data(h256(it->second)).size());
|
||||||
}
|
}
|
||||||
@ -189,6 +189,25 @@ EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(evmasm::
|
|||||||
return LabelID(id);
|
return LabelID(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EthAssemblyAdapter::appendJumpInstruction(evmasm::Instruction _instruction, JumpType _jumpType)
|
||||||
|
{
|
||||||
|
yulAssert(_instruction == evmasm::Instruction::JUMP || _instruction == evmasm::Instruction::JUMPI, "");
|
||||||
|
evmasm::AssemblyItem jump(_instruction);
|
||||||
|
switch (_jumpType)
|
||||||
|
{
|
||||||
|
case JumpType::Ordinary:
|
||||||
|
yulAssert(jump.getJumpType() == evmasm::AssemblyItem::JumpType::Ordinary, "");
|
||||||
|
break;
|
||||||
|
case JumpType::IntoFunction:
|
||||||
|
jump.setJumpType(evmasm::AssemblyItem::JumpType::IntoFunction);
|
||||||
|
break;
|
||||||
|
case JumpType::OutOfFunction:
|
||||||
|
jump.setJumpType(evmasm::AssemblyItem::JumpType::OutOfFunction);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_assembly.append(std::move(jump));
|
||||||
|
}
|
||||||
|
|
||||||
void CodeGenerator::assemble(
|
void CodeGenerator::assemble(
|
||||||
Block const& _parsedData,
|
Block const& _parsedData,
|
||||||
AsmAnalysisInfo& _analysisInfo,
|
AsmAnalysisInfo& _analysisInfo,
|
||||||
@ -218,8 +237,9 @@ void CodeGenerator::assemble(
|
|||||||
}
|
}
|
||||||
catch (StackTooDeepError const& _e)
|
catch (StackTooDeepError const& _e)
|
||||||
{
|
{
|
||||||
yulAssert(
|
assertThrow(
|
||||||
false,
|
false,
|
||||||
|
langutil::StackTooDeepError,
|
||||||
"Stack too deep when compiling inline assembly" +
|
"Stack too deep when compiling inline assembly" +
|
||||||
(_e.comment() ? ": " + *_e.comment() : ".")
|
(_e.comment() ? ": " + *_e.comment() : ".")
|
||||||
);
|
);
|
||||||
|
@ -49,9 +49,9 @@ public:
|
|||||||
size_t newLabelId() override;
|
size_t newLabelId() override;
|
||||||
size_t namedLabel(std::string const& _name) override;
|
size_t namedLabel(std::string const& _name) override;
|
||||||
void appendLinkerSymbol(std::string const& _linkerSymbol) override;
|
void appendLinkerSymbol(std::string const& _linkerSymbol) override;
|
||||||
void appendJump(int _stackDiffAfter) override;
|
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
|
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
void appendJumpToIf(LabelID _labelId) override;
|
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||||
void appendBeginsub(LabelID, int) override;
|
void appendBeginsub(LabelID, int) override;
|
||||||
void appendJumpsub(LabelID, int, int) override;
|
void appendJumpsub(LabelID, int, int) override;
|
||||||
void appendReturnsub(int, int) override;
|
void appendReturnsub(int, int) override;
|
||||||
@ -66,6 +66,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static LabelID assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag);
|
static LabelID assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag);
|
||||||
|
void appendJumpInstruction(evmasm::Instruction _instruction, JumpType _jumpType);
|
||||||
|
|
||||||
evmasm::Assembly& m_assembly;
|
evmasm::Assembly& m_assembly;
|
||||||
std::map<SubID, u256> m_dataHashBySubId;
|
std::map<SubID, u256> m_dataHashBySubId;
|
||||||
|
@ -74,7 +74,7 @@ void EVMAssembly::appendLabelReference(LabelID _labelId)
|
|||||||
|
|
||||||
EVMAssembly::LabelID EVMAssembly::newLabelId()
|
EVMAssembly::LabelID EVMAssembly::newLabelId()
|
||||||
{
|
{
|
||||||
m_labelPositions[m_nextLabelId] = size_t(-1);
|
m_labelPositions[m_nextLabelId] = numeric_limits<size_t>::max();
|
||||||
return m_nextLabelId++;
|
return m_nextLabelId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,14 +91,14 @@ void EVMAssembly::appendLinkerSymbol(string const&)
|
|||||||
yulAssert(false, "Linker symbols not yet implemented.");
|
yulAssert(false, "Linker symbols not yet implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void EVMAssembly::appendJump(int _stackDiffAfter)
|
void EVMAssembly::appendJump(int _stackDiffAfter, JumpType)
|
||||||
{
|
{
|
||||||
yulAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
|
yulAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
|
||||||
appendInstruction(evmasm::Instruction::JUMP);
|
appendInstruction(evmasm::Instruction::JUMP);
|
||||||
m_stackHeight += _stackDiffAfter;
|
m_stackHeight += _stackDiffAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType)
|
||||||
{
|
{
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
{
|
{
|
||||||
@ -109,11 +109,11 @@ void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
appendLabelReference(_labelId);
|
appendLabelReference(_labelId);
|
||||||
appendJump(_stackDiffAfter);
|
appendJump(_stackDiffAfter, _jumpType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EVMAssembly::appendJumpToIf(LabelID _labelId)
|
void EVMAssembly::appendJumpToIf(LabelID _labelId, JumpType)
|
||||||
{
|
{
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
{
|
{
|
||||||
@ -165,7 +165,7 @@ evmasm::LinkerObject EVMAssembly::finalize()
|
|||||||
size_t referencePos = ref.first;
|
size_t referencePos = ref.first;
|
||||||
yulAssert(m_labelPositions.count(ref.second), "");
|
yulAssert(m_labelPositions.count(ref.second), "");
|
||||||
size_t labelPos = m_labelPositions.at(ref.second);
|
size_t labelPos = m_labelPositions.at(ref.second);
|
||||||
yulAssert(labelPos != size_t(-1), "Undefined but allocated label used.");
|
yulAssert(labelPos != numeric_limits<size_t>::max(), "Undefined but allocated label used.");
|
||||||
updateReference(referencePos, labelReferenceSize, u256(labelPos));
|
updateReference(referencePos, labelReferenceSize, u256(labelPos));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ evmasm::LinkerObject EVMAssembly::finalize()
|
|||||||
void EVMAssembly::setLabelToCurrentPosition(LabelID _labelId)
|
void EVMAssembly::setLabelToCurrentPosition(LabelID _labelId)
|
||||||
{
|
{
|
||||||
yulAssert(m_labelPositions.count(_labelId), "Label not found.");
|
yulAssert(m_labelPositions.count(_labelId), "Label not found.");
|
||||||
yulAssert(m_labelPositions[_labelId] == size_t(-1), "Label already set.");
|
yulAssert(m_labelPositions[_labelId] == numeric_limits<size_t>::max(), "Label already set.");
|
||||||
m_labelPositions[_labelId] = m_bytecode.size();
|
m_labelPositions[_labelId] = m_bytecode.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,11 +64,11 @@ public:
|
|||||||
|
|
||||||
/// Append a jump instruction.
|
/// Append a jump instruction.
|
||||||
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
||||||
void appendJump(int _stackDiffAfter) override;
|
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
/// Append a jump-to-immediate operation.
|
/// Append a jump-to-immediate operation.
|
||||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
|
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
/// Append a jump-to-if-immediate operation.
|
/// Append a jump-to-if-immediate operation.
|
||||||
void appendJumpToIf(LabelID _labelId) override;
|
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||||
/// Start a subroutine.
|
/// Start a subroutine.
|
||||||
void appendBeginsub(LabelID _labelId, int _arguments) override;
|
void appendBeginsub(LabelID _labelId, int _arguments) override;
|
||||||
/// Call a subroutine.
|
/// Call a subroutine.
|
||||||
|
@ -175,28 +175,29 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
|
|||||||
{
|
{
|
||||||
yulAssert(m_scope, "");
|
yulAssert(m_scope, "");
|
||||||
|
|
||||||
int const numVariables = _varDecl.variables.size();
|
size_t const numVariables = _varDecl.variables.size();
|
||||||
int heightAtStart = m_assembly.stackHeight();
|
auto heightAtStart = static_cast<size_t>(m_assembly.stackHeight());
|
||||||
if (_varDecl.value)
|
if (_varDecl.value)
|
||||||
{
|
{
|
||||||
std::visit(*this, *_varDecl.value);
|
std::visit(*this, *_varDecl.value);
|
||||||
expectDeposit(numVariables, heightAtStart);
|
expectDeposit(static_cast<int>(numVariables), static_cast<int>(heightAtStart));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_assembly.setSourceLocation(_varDecl.location);
|
m_assembly.setSourceLocation(_varDecl.location);
|
||||||
int variablesLeft = numVariables;
|
size_t variablesLeft = numVariables;
|
||||||
while (variablesLeft--)
|
while (variablesLeft--)
|
||||||
m_assembly.appendConstant(u256(0));
|
m_assembly.appendConstant(u256(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_assembly.setSourceLocation(_varDecl.location);
|
m_assembly.setSourceLocation(_varDecl.location);
|
||||||
bool atTopOfStack = true;
|
bool atTopOfStack = true;
|
||||||
for (int varIndex = numVariables - 1; varIndex >= 0; --varIndex)
|
for (size_t varIndex = 0; varIndex < numVariables; ++varIndex)
|
||||||
{
|
{
|
||||||
YulString varName = _varDecl.variables[varIndex].name;
|
size_t varIndexReverse = numVariables - 1 - varIndex;
|
||||||
|
YulString varName = _varDecl.variables[varIndexReverse].name;
|
||||||
auto& var = std::get<Scope::Variable>(m_scope->identifiers.at(varName));
|
auto& var = std::get<Scope::Variable>(m_scope->identifiers.at(varName));
|
||||||
m_context->variableStackHeights[&var] = heightAtStart + varIndex;
|
m_context->variableStackHeights[&var] = heightAtStart + varIndexReverse;
|
||||||
if (!m_allowStackOpt)
|
if (!m_allowStackOpt)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -214,10 +215,10 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
|
|||||||
atTopOfStack = false;
|
atTopOfStack = false;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int slot = *m_unusedStackSlots.begin();
|
auto slot = static_cast<size_t>(*m_unusedStackSlots.begin());
|
||||||
m_unusedStackSlots.erase(m_unusedStackSlots.begin());
|
m_unusedStackSlots.erase(m_unusedStackSlots.begin());
|
||||||
m_context->variableStackHeights[&var] = slot;
|
m_context->variableStackHeights[&var] = slot;
|
||||||
if (int heightDiff = variableHeightDiff(var, varName, true))
|
if (size_t heightDiff = variableHeightDiff(var, varName, true))
|
||||||
m_assembly.appendInstruction(evmasm::swapInstruction(heightDiff - 1));
|
m_assembly.appendInstruction(evmasm::swapInstruction(heightDiff - 1));
|
||||||
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
||||||
}
|
}
|
||||||
@ -240,7 +241,7 @@ void CodeTransform::operator()(Assignment const& _assignment)
|
|||||||
{
|
{
|
||||||
int height = m_assembly.stackHeight();
|
int height = m_assembly.stackHeight();
|
||||||
std::visit(*this, *_assignment.value);
|
std::visit(*this, *_assignment.value);
|
||||||
expectDeposit(_assignment.variableNames.size(), height);
|
expectDeposit(static_cast<int>(_assignment.variableNames.size()), height);
|
||||||
|
|
||||||
m_assembly.setSourceLocation(_assignment.location);
|
m_assembly.setSourceLocation(_assignment.location);
|
||||||
generateMultiAssignment(_assignment.variableNames);
|
generateMultiAssignment(_assignment.variableNames);
|
||||||
@ -263,7 +264,7 @@ void CodeTransform::operator()(FunctionCall const& _call)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_assembly.setSourceLocation(_call.location);
|
m_assembly.setSourceLocation(_call.location);
|
||||||
EVMAssembly::LabelID returnLabel(-1); // only used for evm 1.0
|
EVMAssembly::LabelID returnLabel(numeric_limits<EVMAssembly::LabelID>::max()); // only used for evm 1.0
|
||||||
if (!m_evm15)
|
if (!m_evm15)
|
||||||
{
|
{
|
||||||
returnLabel = m_assembly.newLabelId();
|
returnLabel = m_assembly.newLabelId();
|
||||||
@ -281,10 +282,18 @@ void CodeTransform::operator()(FunctionCall const& _call)
|
|||||||
visitExpression(arg);
|
visitExpression(arg);
|
||||||
m_assembly.setSourceLocation(_call.location);
|
m_assembly.setSourceLocation(_call.location);
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
m_assembly.appendJumpsub(functionEntryID(_call.functionName.name, *function), function->arguments.size(), function->returns.size());
|
m_assembly.appendJumpsub(
|
||||||
|
functionEntryID(_call.functionName.name, *function),
|
||||||
|
static_cast<int>(function->arguments.size()),
|
||||||
|
static_cast<int>(function->returns.size())
|
||||||
|
);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_assembly.appendJumpTo(functionEntryID(_call.functionName.name, *function), function->returns.size() - function->arguments.size() - 1);
|
m_assembly.appendJumpTo(
|
||||||
|
functionEntryID(_call.functionName.name, *function),
|
||||||
|
static_cast<int>(function->returns.size() - function->arguments.size()) - 1,
|
||||||
|
AbstractAssembly::JumpType::IntoFunction
|
||||||
|
);
|
||||||
m_assembly.appendLabel(returnLabel);
|
m_assembly.appendLabel(returnLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,7 +309,7 @@ void CodeTransform::operator()(Identifier const& _identifier)
|
|||||||
{
|
{
|
||||||
// TODO: opportunity for optimization: Do not DUP if this is the last reference
|
// TODO: opportunity for optimization: Do not DUP if this is the last reference
|
||||||
// to the top most element of the stack
|
// to the top most element of the stack
|
||||||
if (int heightDiff = variableHeightDiff(_var, _identifier.name, false))
|
if (size_t heightDiff = variableHeightDiff(_var, _identifier.name, false))
|
||||||
m_assembly.appendInstruction(evmasm::dupInstruction(heightDiff));
|
m_assembly.appendInstruction(evmasm::dupInstruction(heightDiff));
|
||||||
else
|
else
|
||||||
// Store something to balance the stack
|
// Store something to balance the stack
|
||||||
@ -407,7 +416,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
|||||||
int const stackHeightBefore = m_assembly.stackHeight();
|
int const stackHeightBefore = m_assembly.stackHeight();
|
||||||
|
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size());
|
m_assembly.appendBeginsub(functionEntryID(_function.name, function), static_cast<int>(_function.parameters.size()));
|
||||||
else
|
else
|
||||||
m_assembly.appendLabel(functionEntryID(_function.name, function));
|
m_assembly.appendLabel(functionEntryID(_function.name, function));
|
||||||
|
|
||||||
@ -465,15 +474,15 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
|||||||
// modified parallel to the actual stack.
|
// modified parallel to the actual stack.
|
||||||
vector<int> stackLayout;
|
vector<int> stackLayout;
|
||||||
if (!m_evm15)
|
if (!m_evm15)
|
||||||
stackLayout.push_back(_function.returnVariables.size()); // Move return label to the top
|
stackLayout.push_back(static_cast<int>(_function.returnVariables.size())); // Move return label to the top
|
||||||
stackLayout += vector<int>(_function.parameters.size(), -1); // discard all arguments
|
stackLayout += vector<int>(_function.parameters.size(), -1); // discard all arguments
|
||||||
|
|
||||||
for (size_t i = 0; i < _function.returnVariables.size(); ++i)
|
for (size_t i = 0; i < _function.returnVariables.size(); ++i)
|
||||||
stackLayout.push_back(i); // Move return values down, but keep order.
|
stackLayout.push_back(static_cast<int>(i)); // Move return values down, but keep order.
|
||||||
|
|
||||||
if (stackLayout.size() > 17)
|
if (stackLayout.size() > 17)
|
||||||
{
|
{
|
||||||
StackTooDeepError error(_function.name, YulString{}, stackLayout.size() - 17);
|
StackTooDeepError error(_function.name, YulString{}, static_cast<int>(stackLayout.size()) - 17);
|
||||||
error << errinfo_comment(
|
error << errinfo_comment(
|
||||||
"The function " +
|
"The function " +
|
||||||
_function.name.str() +
|
_function.name.str() +
|
||||||
@ -481,11 +490,11 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
|||||||
to_string(stackLayout.size() - 17) +
|
to_string(stackLayout.size() - 17) +
|
||||||
" parameters or return variables too many to fit the stack size."
|
" parameters or return variables too many to fit the stack size."
|
||||||
);
|
);
|
||||||
stackError(std::move(error), m_assembly.stackHeight() - _function.parameters.size());
|
stackError(std::move(error), m_assembly.stackHeight() - static_cast<int>(_function.parameters.size()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while (!stackLayout.empty() && stackLayout.back() != int(stackLayout.size() - 1))
|
while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1))
|
||||||
if (stackLayout.back() < 0)
|
if (stackLayout.back() < 0)
|
||||||
{
|
{
|
||||||
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
||||||
@ -493,17 +502,20 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_assembly.appendInstruction(evmasm::swapInstruction(stackLayout.size() - stackLayout.back() - 1));
|
m_assembly.appendInstruction(evmasm::swapInstruction(stackLayout.size() - static_cast<size_t>(stackLayout.back()) - 1));
|
||||||
swap(stackLayout[stackLayout.back()], stackLayout.back());
|
swap(stackLayout[static_cast<size_t>(stackLayout.back())], stackLayout.back());
|
||||||
}
|
}
|
||||||
for (int i = 0; size_t(i) < stackLayout.size(); ++i)
|
for (size_t i = 0; i < stackLayout.size(); ++i)
|
||||||
yulAssert(i == stackLayout[i], "Error reshuffling stack.");
|
yulAssert(i == static_cast<size_t>(stackLayout[i]), "Error reshuffling stack.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
m_assembly.appendReturnsub(_function.returnVariables.size(), stackHeightBefore);
|
m_assembly.appendReturnsub(static_cast<int>(_function.returnVariables.size()), stackHeightBefore);
|
||||||
else
|
else
|
||||||
m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size());
|
m_assembly.appendJump(
|
||||||
|
stackHeightBefore - static_cast<int>(_function.returnVariables.size()),
|
||||||
|
AbstractAssembly::JumpType::OutOfFunction
|
||||||
|
);
|
||||||
m_assembly.setStackHeight(stackHeightBefore);
|
m_assembly.setStackHeight(stackHeightBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -683,7 +695,7 @@ void CodeTransform::generateAssignment(Identifier const& _variableName)
|
|||||||
if (auto var = m_scope->lookup(_variableName.name))
|
if (auto var = m_scope->lookup(_variableName.name))
|
||||||
{
|
{
|
||||||
Scope::Variable const& _var = std::get<Scope::Variable>(*var);
|
Scope::Variable const& _var = std::get<Scope::Variable>(*var);
|
||||||
if (int heightDiff = variableHeightDiff(_var, _variableName.name, true))
|
if (size_t heightDiff = variableHeightDiff(_var, _variableName.name, true))
|
||||||
m_assembly.appendInstruction(evmasm::swapInstruction(heightDiff - 1));
|
m_assembly.appendInstruction(evmasm::swapInstruction(heightDiff - 1));
|
||||||
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
||||||
decreaseReference(_variableName.name, _var);
|
decreaseReference(_variableName.name, _var);
|
||||||
@ -698,12 +710,12 @@ void CodeTransform::generateAssignment(Identifier const& _variableName)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString _varName, bool _forSwap)
|
size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString _varName, bool _forSwap)
|
||||||
{
|
{
|
||||||
yulAssert(m_context->variableStackHeights.count(&_var), "");
|
yulAssert(m_context->variableStackHeights.count(&_var), "");
|
||||||
int heightDiff = m_assembly.stackHeight() - m_context->variableStackHeights[&_var];
|
size_t heightDiff = static_cast<size_t>(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var];
|
||||||
yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable.");
|
yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable.");
|
||||||
int limit = _forSwap ? 17 : 16;
|
size_t limit = _forSwap ? 17 : 16;
|
||||||
if (heightDiff > limit)
|
if (heightDiff > limit)
|
||||||
{
|
{
|
||||||
m_stackErrors.emplace_back(_varName, heightDiff - limit);
|
m_stackErrors.emplace_back(_varName, heightDiff - limit);
|
||||||
@ -723,4 +735,3 @@ void CodeTransform::expectDeposit(int _deposit, int _oldHeight) const
|
|||||||
{
|
{
|
||||||
yulAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
|
yulAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ struct StackTooDeepError: virtual YulException
|
|||||||
struct CodeTransformContext
|
struct CodeTransformContext
|
||||||
{
|
{
|
||||||
std::map<Scope::Function const*, AbstractAssembly::LabelID> functionEntryIDs;
|
std::map<Scope::Function const*, AbstractAssembly::LabelID> functionEntryIDs;
|
||||||
std::map<Scope::Variable const*, int> variableStackHeights;
|
std::map<Scope::Variable const*, size_t> variableStackHeights;
|
||||||
std::map<Scope::Variable const*, unsigned> variableReferences;
|
std::map<Scope::Variable const*, unsigned> variableReferences;
|
||||||
|
|
||||||
struct JumpInfo
|
struct JumpInfo
|
||||||
@ -200,7 +200,7 @@ private:
|
|||||||
/// Determines the stack height difference to the given variables. Throws
|
/// Determines the stack height difference to the given variables. Throws
|
||||||
/// if it is not yet in scope or the height difference is too large. Returns
|
/// if it is not yet in scope or the height difference is too large. Returns
|
||||||
/// the (positive) stack height difference otherwise.
|
/// the (positive) stack height difference otherwise.
|
||||||
int variableHeightDiff(Scope::Variable const& _var, YulString _name, bool _forSwap);
|
size_t variableHeightDiff(Scope::Variable const& _var, YulString _name, bool _forSwap);
|
||||||
|
|
||||||
void expectDeposit(int _deposit, int _oldHeight) const;
|
void expectDeposit(int _deposit, int _oldHeight) const;
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
|||||||
evmasm::InstructionInfo info = evmasm::instructionInfo(_instruction);
|
evmasm::InstructionInfo info = evmasm::instructionInfo(_instruction);
|
||||||
BuiltinFunctionForEVM f;
|
BuiltinFunctionForEVM f;
|
||||||
f.name = YulString{_name};
|
f.name = YulString{_name};
|
||||||
f.parameters.resize(info.args);
|
f.parameters.resize(static_cast<size_t>(info.args));
|
||||||
f.returns.resize(info.ret);
|
f.returns.resize(static_cast<size_t>(info.ret));
|
||||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||||
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
||||||
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
|
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
|
||||||
@ -93,7 +93,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
|
|||||||
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void(Expression const&)>)> _generateCode
|
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void(Expression const&)>)> _generateCode
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
solAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
|
yulAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
|
||||||
|
|
||||||
YulString name{std::move(_name)};
|
YulString name{std::move(_name)};
|
||||||
BuiltinFunctionForEVM f;
|
BuiltinFunctionForEVM f;
|
||||||
@ -114,18 +114,31 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
|
|||||||
map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVersion, bool _objectAccess)
|
map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVersion, bool _objectAccess)
|
||||||
{
|
{
|
||||||
map<YulString, BuiltinFunctionForEVM> builtins;
|
map<YulString, BuiltinFunctionForEVM> builtins;
|
||||||
|
// NOTE: Parser::instructions() will filter JUMPDEST and PUSHnn too
|
||||||
for (auto const& instr: Parser::instructions())
|
for (auto const& instr: Parser::instructions())
|
||||||
if (
|
if (
|
||||||
!evmasm::isDupInstruction(instr.second) &&
|
!evmasm::isDupInstruction(instr.second) &&
|
||||||
!evmasm::isSwapInstruction(instr.second) &&
|
!evmasm::isSwapInstruction(instr.second) &&
|
||||||
|
!evmasm::isPushInstruction(instr.second) &&
|
||||||
instr.second != evmasm::Instruction::JUMP &&
|
instr.second != evmasm::Instruction::JUMP &&
|
||||||
instr.second != evmasm::Instruction::JUMPI &&
|
instr.second != evmasm::Instruction::JUMPI &&
|
||||||
|
instr.second != evmasm::Instruction::JUMPDEST &&
|
||||||
_evmVersion.hasOpcode(instr.second)
|
_evmVersion.hasOpcode(instr.second)
|
||||||
)
|
)
|
||||||
builtins.emplace(createEVMFunction(instr.first, instr.second));
|
builtins.emplace(createEVMFunction(instr.first, instr.second));
|
||||||
|
|
||||||
if (_objectAccess)
|
if (_objectAccess)
|
||||||
{
|
{
|
||||||
|
builtins.emplace(createFunction("linkersymbol", 1, 1, SideEffects{}, {true}, [](
|
||||||
|
FunctionCall const& _call,
|
||||||
|
AbstractAssembly& _assembly,
|
||||||
|
BuiltinContext&,
|
||||||
|
function<void(Expression const&)>
|
||||||
|
) {
|
||||||
|
yulAssert(_call.arguments.size() == 1, "");
|
||||||
|
Expression const& arg = _call.arguments.front();
|
||||||
|
_assembly.appendLinkerSymbol(std::get<Literal>(arg).value.str());
|
||||||
|
}));
|
||||||
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {true}, [](
|
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {true}, [](
|
||||||
FunctionCall const& _call,
|
FunctionCall const& _call,
|
||||||
AbstractAssembly& _assembly,
|
AbstractAssembly& _assembly,
|
||||||
@ -196,7 +209,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
|||||||
BuiltinContext&,
|
BuiltinContext&,
|
||||||
std::function<void(Expression const&)> _visitExpression
|
std::function<void(Expression const&)> _visitExpression
|
||||||
) {
|
) {
|
||||||
solAssert(_call.arguments.size() == 2, "");
|
yulAssert(_call.arguments.size() == 2, "");
|
||||||
|
|
||||||
_visitExpression(_call.arguments[1]);
|
_visitExpression(_call.arguments[1]);
|
||||||
_assembly.setSourceLocation(_call.location);
|
_assembly.setSourceLocation(_call.location);
|
||||||
@ -216,7 +229,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
|||||||
BuiltinContext&,
|
BuiltinContext&,
|
||||||
std::function<void(Expression const&)>
|
std::function<void(Expression const&)>
|
||||||
) {
|
) {
|
||||||
solAssert(_call.arguments.size() == 1, "");
|
yulAssert(_call.arguments.size() == 1, "");
|
||||||
_assembly.appendImmutable(std::get<Literal>(_call.arguments.front()).value.str());
|
_assembly.appendImmutable(std::get<Literal>(_call.arguments.front()).value.str());
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
@ -91,7 +91,11 @@ void GasMeterVisitor::operator()(Literal const& _lit)
|
|||||||
m_runGas += evmasm::GasMeter::runGas(evmasm::Instruction::PUSH1);
|
m_runGas += evmasm::GasMeter::runGas(evmasm::Instruction::PUSH1);
|
||||||
m_dataGas +=
|
m_dataGas +=
|
||||||
singleByteDataGas() +
|
singleByteDataGas() +
|
||||||
size_t(evmasm::GasMeter::dataGas(toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation, m_dialect.evmVersion()));
|
static_cast<size_t>(evmasm::GasMeter::dataGas(
|
||||||
|
toCompactBigEndian(valueOfLiteral(_lit), 1),
|
||||||
|
m_isCreation,
|
||||||
|
m_dialect.evmVersion()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GasMeterVisitor::operator()(Identifier const&)
|
void GasMeterVisitor::operator()(Identifier const&)
|
||||||
|
@ -70,25 +70,25 @@ void NoOutputAssembly::appendLinkerSymbol(string const&)
|
|||||||
yulAssert(false, "Linker symbols not yet implemented.");
|
yulAssert(false, "Linker symbols not yet implemented.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void NoOutputAssembly::appendJump(int _stackDiffAfter)
|
void NoOutputAssembly::appendJump(int _stackDiffAfter, JumpType)
|
||||||
{
|
{
|
||||||
yulAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
|
yulAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
|
||||||
appendInstruction(evmasm::Instruction::JUMP);
|
appendInstruction(evmasm::Instruction::JUMP);
|
||||||
m_stackHeight += _stackDiffAfter;
|
m_stackHeight += _stackDiffAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NoOutputAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
void NoOutputAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType)
|
||||||
{
|
{
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
m_stackHeight += _stackDiffAfter;
|
m_stackHeight += _stackDiffAfter;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
appendLabelReference(_labelId);
|
appendLabelReference(_labelId);
|
||||||
appendJump(_stackDiffAfter);
|
appendJump(_stackDiffAfter, _jumpType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NoOutputAssembly::appendJumpToIf(LabelID _labelId)
|
void NoOutputAssembly::appendJumpToIf(LabelID _labelId, JumpType)
|
||||||
{
|
{
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
m_stackHeight--;
|
m_stackHeight--;
|
||||||
|
@ -58,9 +58,9 @@ public:
|
|||||||
LabelID namedLabel(std::string const& _name) override;
|
LabelID namedLabel(std::string const& _name) override;
|
||||||
void appendLinkerSymbol(std::string const& _name) override;
|
void appendLinkerSymbol(std::string const& _name) override;
|
||||||
|
|
||||||
void appendJump(int _stackDiffAfter) override;
|
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
|
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||||
void appendJumpToIf(LabelID _labelId) override;
|
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||||
void appendBeginsub(LabelID _labelId, int _arguments) override;
|
void appendBeginsub(LabelID _labelId, int _arguments) override;
|
||||||
void appendJumpsub(LabelID _labelId, int _arguments, int _returns) override;
|
void appendJumpsub(LabelID _labelId, int _arguments, int _returns) override;
|
||||||
void appendReturnsub(int _returns, int _stackDiffAfter) override;
|
void appendReturnsub(int _returns, int _stackDiffAfter) override;
|
||||||
|
@ -376,12 +376,12 @@ bytes BinaryTransform::operator()(BuiltinCall const& _call)
|
|||||||
if (_call.functionName == "dataoffset")
|
if (_call.functionName == "dataoffset")
|
||||||
{
|
{
|
||||||
string name = get<StringLiteral>(_call.arguments.at(0)).value;
|
string name = get<StringLiteral>(_call.arguments.at(0)).value;
|
||||||
return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).first);
|
return toBytes(Opcode::I64Const) + lebEncodeSigned(static_cast<int64_t>(m_subModulePosAndSize.at(name).first));
|
||||||
}
|
}
|
||||||
else if (_call.functionName == "datasize")
|
else if (_call.functionName == "datasize")
|
||||||
{
|
{
|
||||||
string name = get<StringLiteral>(_call.arguments.at(0)).value;
|
string name = get<StringLiteral>(_call.arguments.at(0)).value;
|
||||||
return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).second);
|
return toBytes(Opcode::I64Const) + lebEncodeSigned(static_cast<int64_t>(m_subModulePosAndSize.at(name).second));
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes args = visit(_call.arguments);
|
bytes args = visit(_call.arguments);
|
||||||
@ -390,7 +390,7 @@ bytes BinaryTransform::operator()(BuiltinCall const& _call)
|
|||||||
return toBytes(Opcode::Unreachable);
|
return toBytes(Opcode::Unreachable);
|
||||||
else if (_call.functionName == "nop")
|
else if (_call.functionName == "nop")
|
||||||
return toBytes(Opcode::Nop);
|
return toBytes(Opcode::Nop);
|
||||||
else if (_call.functionName == "drop")
|
else if (_call.functionName == "i32.drop" || _call.functionName == "i64.drop")
|
||||||
return toBytes(Opcode::Drop);
|
return toBytes(Opcode::Drop);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,10 @@ string TextTransform::operator()(wasm::GlobalVariable const& _identifier)
|
|||||||
string TextTransform::operator()(wasm::BuiltinCall const& _builtinCall)
|
string TextTransform::operator()(wasm::BuiltinCall const& _builtinCall)
|
||||||
{
|
{
|
||||||
string args = joinTransformed(_builtinCall.arguments);
|
string args = joinTransformed(_builtinCall.arguments);
|
||||||
return "(" + _builtinCall.functionName + (args.empty() ? "" : " " + args) + ")";
|
string funcName = _builtinCall.functionName;
|
||||||
|
if (funcName == "i32.drop" || funcName == "i64.drop")
|
||||||
|
funcName = "drop";
|
||||||
|
return "(" + funcName + (args.empty() ? "" : " " + args) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
string TextTransform::operator()(wasm::FunctionCall const& _functionCall)
|
string TextTransform::operator()(wasm::FunctionCall const& _functionCall)
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
#include <libyul/backends/wasm/WasmCodeTransform.h>
|
#include <libyul/backends/wasm/WasmCodeTransform.h>
|
||||||
|
|
||||||
|
#include <libyul/backends/wasm/WasmDialect.h>
|
||||||
|
|
||||||
#include <libyul/optimiser/NameCollector.h>
|
#include <libyul/optimiser/NameCollector.h>
|
||||||
|
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
@ -40,7 +42,8 @@ wasm::Module WasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _
|
|||||||
{
|
{
|
||||||
wasm::Module module;
|
wasm::Module module;
|
||||||
|
|
||||||
WasmCodeTransform transform(_dialect, _ast);
|
TypeInfo typeInfo(_dialect, _ast);
|
||||||
|
WasmCodeTransform transform(_dialect, _ast, typeInfo);
|
||||||
|
|
||||||
for (auto const& statement: _ast.statements)
|
for (auto const& statement: _ast.statements)
|
||||||
{
|
{
|
||||||
@ -70,14 +73,18 @@ wasm::Expression WasmCodeTransform::generateMultiAssignment(
|
|||||||
if (_variableNames.size() == 1)
|
if (_variableNames.size() == 1)
|
||||||
return { std::move(assignment) };
|
return { std::move(assignment) };
|
||||||
|
|
||||||
allocateGlobals(_variableNames.size() - 1);
|
vector<wasm::Type> typesForGlobals;
|
||||||
|
for (size_t i = 1; i < _variableNames.size(); ++i)
|
||||||
|
typesForGlobals.push_back(translatedType(m_typeInfo.typeOfVariable(YulString(_variableNames[i]))));
|
||||||
|
vector<size_t> allocatedIndices = allocateGlobals(typesForGlobals);
|
||||||
|
yulAssert(allocatedIndices.size() == _variableNames.size() - 1, "");
|
||||||
|
|
||||||
wasm::Block block;
|
wasm::Block block;
|
||||||
block.statements.emplace_back(move(assignment));
|
block.statements.emplace_back(move(assignment));
|
||||||
for (size_t i = 1; i < _variableNames.size(); ++i)
|
for (size_t i = 1; i < _variableNames.size(); ++i)
|
||||||
block.statements.emplace_back(wasm::LocalAssignment{
|
block.statements.emplace_back(wasm::LocalAssignment{
|
||||||
move(_variableNames.at(i)),
|
move(_variableNames.at(i)),
|
||||||
make_unique<wasm::Expression>(wasm::GlobalVariable{m_globalVariables.at(i - 1).variableName})
|
make_unique<wasm::Expression>(wasm::GlobalVariable{m_globalVariables.at(allocatedIndices[i - 1]).variableName})
|
||||||
});
|
});
|
||||||
return { std::move(block) };
|
return { std::move(block) };
|
||||||
}
|
}
|
||||||
@ -88,7 +95,7 @@ wasm::Expression WasmCodeTransform::operator()(VariableDeclaration const& _varDe
|
|||||||
for (auto const& var: _varDecl.variables)
|
for (auto const& var: _varDecl.variables)
|
||||||
{
|
{
|
||||||
variableNames.emplace_back(var.name.str());
|
variableNames.emplace_back(var.name.str());
|
||||||
m_localVariables.emplace_back(wasm::VariableDeclaration{variableNames.back(), wasm::Type::i64});
|
m_localVariables.emplace_back(wasm::VariableDeclaration{variableNames.back(), translatedType(var.type)});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_varDecl.value)
|
if (_varDecl.value)
|
||||||
@ -112,8 +119,6 @@ wasm::Expression WasmCodeTransform::operator()(ExpressionStatement const& _state
|
|||||||
|
|
||||||
wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
|
wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
|
||||||
{
|
{
|
||||||
bool typeConversionNeeded = false;
|
|
||||||
|
|
||||||
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
|
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
|
||||||
{
|
{
|
||||||
if (_call.functionName.name.str().substr(0, 4) == "eth.")
|
if (_call.functionName.name.str().substr(0, 4) == "eth.")
|
||||||
@ -133,7 +138,6 @@ wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
|
|||||||
imp.paramTypes.emplace_back(translatedType(param));
|
imp.paramTypes.emplace_back(translatedType(param));
|
||||||
m_functionsToImport[builtin->name] = std::move(imp);
|
m_functionsToImport[builtin->name] = std::move(imp);
|
||||||
}
|
}
|
||||||
typeConversionNeeded = true;
|
|
||||||
}
|
}
|
||||||
else if (builtin->literalArguments && contains(builtin->literalArguments.value(), true))
|
else if (builtin->literalArguments && contains(builtin->literalArguments.value(), true))
|
||||||
{
|
{
|
||||||
@ -147,18 +151,10 @@ wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
|
|||||||
return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)};
|
return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
return wasm::BuiltinCall{
|
||||||
wasm::BuiltinCall call{
|
|
||||||
_call.functionName.name.str(),
|
_call.functionName.name.str(),
|
||||||
injectTypeConversionIfNeeded(visit(_call.arguments), builtin->parameters)
|
visit(_call.arguments)
|
||||||
};
|
};
|
||||||
if (!builtin->returns.empty() && !builtin->returns.front().empty() && builtin->returns.front() != "i64"_yulstring)
|
|
||||||
{
|
|
||||||
yulAssert(builtin->returns.front() == "i32"_yulstring, "Invalid type " + builtin->returns.front().str());
|
|
||||||
call = wasm::BuiltinCall{"i64.extend_i32_u", make_vector<wasm::Expression>(std::move(call))};
|
|
||||||
}
|
|
||||||
return {std::move(call)};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this function returns multiple values, then the first one will
|
// If this function returns multiple values, then the first one will
|
||||||
@ -166,13 +162,7 @@ wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
|
|||||||
// The values have to be used right away in an assignment or variable declaration,
|
// The values have to be used right away in an assignment or variable declaration,
|
||||||
// so it is handled there.
|
// so it is handled there.
|
||||||
|
|
||||||
wasm::FunctionCall funCall{_call.functionName.name.str(), visit(_call.arguments)};
|
return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)};
|
||||||
if (typeConversionNeeded)
|
|
||||||
// Inject type conversion if needed on the fly. This is just a temporary measure
|
|
||||||
// and can be removed once we have proper types in Yul.
|
|
||||||
return injectTypeConversionIfNeeded(std::move(funCall));
|
|
||||||
else
|
|
||||||
return {std::move(funCall)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wasm::Expression WasmCodeTransform::operator()(Identifier const& _identifier)
|
wasm::Expression WasmCodeTransform::operator()(Identifier const& _identifier)
|
||||||
@ -182,30 +172,40 @@ wasm::Expression WasmCodeTransform::operator()(Identifier const& _identifier)
|
|||||||
|
|
||||||
wasm::Expression WasmCodeTransform::operator()(Literal const& _literal)
|
wasm::Expression WasmCodeTransform::operator()(Literal const& _literal)
|
||||||
{
|
{
|
||||||
u256 value = valueOfLiteral(_literal);
|
return makeLiteral(translatedType(_literal.type), valueOfLiteral(_literal));
|
||||||
yulAssert(value <= numeric_limits<uint64_t>::max(), "Literal too large: " + value.str());
|
|
||||||
return wasm::Literal{static_cast<uint64_t>(value)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wasm::Expression WasmCodeTransform::operator()(If const& _if)
|
wasm::Expression WasmCodeTransform::operator()(If const& _if)
|
||||||
{
|
{
|
||||||
// TODO converting i64 to i32 might not always be needed.
|
yul::Type conditionType = m_typeInfo.typeOf(*_if.condition);
|
||||||
|
|
||||||
vector<wasm::Expression> args;
|
wasm::Expression condition;
|
||||||
args.emplace_back(visitReturnByValue(*_if.condition));
|
if (conditionType == "i32"_yulstring)
|
||||||
args.emplace_back(wasm::Literal{static_cast<uint64_t>(0)});
|
condition = visitReturnByValue(*_if.condition);
|
||||||
return wasm::If{
|
else if (conditionType == "i64"_yulstring)
|
||||||
make_unique<wasm::Expression>(wasm::BuiltinCall{"i64.ne", std::move(args)}),
|
{
|
||||||
visit(_if.body.statements),
|
vector<wasm::Expression> args;
|
||||||
{}
|
args.emplace_back(visitReturnByValue(*_if.condition));
|
||||||
};
|
args.emplace_back(makeLiteral(translatedType("i64"_yulstring), 0));
|
||||||
|
|
||||||
|
// NOTE: `if` in wasm requires an i32 argument
|
||||||
|
condition = wasm::BuiltinCall{"i64.ne", std::move(args)};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
yulAssert(false, "Invalid condition type");
|
||||||
|
|
||||||
|
return wasm::If{make_unique<wasm::Expression>(move(condition)), visit(_if.body.statements), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
wasm::Expression WasmCodeTransform::operator()(Switch const& _switch)
|
wasm::Expression WasmCodeTransform::operator()(Switch const& _switch)
|
||||||
{
|
{
|
||||||
|
yul::Type expressionType = m_typeInfo.typeOf(*_switch.expression);
|
||||||
|
YulString eq_instruction = YulString(expressionType.str() + ".eq");
|
||||||
|
yulAssert(WasmDialect::instance().builtin(eq_instruction), "");
|
||||||
|
|
||||||
wasm::Block block;
|
wasm::Block block;
|
||||||
string condition = m_nameDispenser.newName("condition"_yulstring).str();
|
string condition = m_nameDispenser.newName("condition"_yulstring).str();
|
||||||
m_localVariables.emplace_back(wasm::VariableDeclaration{condition, wasm::Type::i64});
|
m_localVariables.emplace_back(wasm::VariableDeclaration{condition, translatedType(expressionType)});
|
||||||
block.statements.emplace_back(wasm::LocalAssignment{condition, visit(*_switch.expression)});
|
block.statements.emplace_back(wasm::LocalAssignment{condition, visit(*_switch.expression)});
|
||||||
|
|
||||||
vector<wasm::Expression>* currentBlock = &block.statements;
|
vector<wasm::Expression>* currentBlock = &block.statements;
|
||||||
@ -214,7 +214,7 @@ wasm::Expression WasmCodeTransform::operator()(Switch const& _switch)
|
|||||||
Case const& c = _switch.cases.at(i);
|
Case const& c = _switch.cases.at(i);
|
||||||
if (c.value)
|
if (c.value)
|
||||||
{
|
{
|
||||||
wasm::BuiltinCall comparison{"i64.eq", make_vector<wasm::Expression>(
|
wasm::BuiltinCall comparison{eq_instruction.str(), make_vector<wasm::Expression>(
|
||||||
wasm::LocalVariable{condition},
|
wasm::LocalVariable{condition},
|
||||||
visitReturnByValue(*c.value)
|
visitReturnByValue(*c.value)
|
||||||
)};
|
)};
|
||||||
@ -253,11 +253,16 @@ wasm::Expression WasmCodeTransform::operator()(ForLoop const& _for)
|
|||||||
string continueLabel = newLabel();
|
string continueLabel = newLabel();
|
||||||
m_breakContinueLabelNames.push({breakLabel, continueLabel});
|
m_breakContinueLabelNames.push({breakLabel, continueLabel});
|
||||||
|
|
||||||
|
yul::Type conditionType = m_typeInfo.typeOf(*_for.condition);
|
||||||
|
YulString eqz_instruction = YulString(conditionType.str() + ".eqz");
|
||||||
|
yulAssert(WasmDialect::instance().builtin(eqz_instruction), "");
|
||||||
|
|
||||||
|
std::vector<wasm::Expression> statements = visit(_for.pre.statements);
|
||||||
|
|
||||||
wasm::Loop loop;
|
wasm::Loop loop;
|
||||||
loop.labelName = newLabel();
|
loop.labelName = newLabel();
|
||||||
loop.statements = visit(_for.pre.statements);
|
|
||||||
loop.statements.emplace_back(wasm::BranchIf{wasm::Label{breakLabel}, make_unique<wasm::Expression>(
|
loop.statements.emplace_back(wasm::BranchIf{wasm::Label{breakLabel}, make_unique<wasm::Expression>(
|
||||||
wasm::BuiltinCall{"i64.eqz", make_vector<wasm::Expression>(
|
wasm::BuiltinCall{eqz_instruction.str(), make_vector<wasm::Expression>(
|
||||||
visitReturnByValue(*_for.condition)
|
visitReturnByValue(*_for.condition)
|
||||||
)}
|
)}
|
||||||
)});
|
)});
|
||||||
@ -265,7 +270,8 @@ wasm::Expression WasmCodeTransform::operator()(ForLoop const& _for)
|
|||||||
loop.statements += visit(_for.post.statements);
|
loop.statements += visit(_for.post.statements);
|
||||||
loop.statements.emplace_back(wasm::Branch{wasm::Label{loop.labelName}});
|
loop.statements.emplace_back(wasm::Branch{wasm::Label{loop.labelName}});
|
||||||
|
|
||||||
return { wasm::Block{breakLabel, make_vector<wasm::Expression>(move(loop))} };
|
statements += make_vector<wasm::Expression>(move(loop));
|
||||||
|
return wasm::Block{breakLabel, move(statements)};
|
||||||
}
|
}
|
||||||
|
|
||||||
wasm::Expression WasmCodeTransform::operator()(Break const&)
|
wasm::Expression WasmCodeTransform::operator()(Break const&)
|
||||||
@ -325,11 +331,11 @@ wasm::FunctionDefinition WasmCodeTransform::translateFunction(yul::FunctionDefin
|
|||||||
wasm::FunctionDefinition fun;
|
wasm::FunctionDefinition fun;
|
||||||
fun.name = _fun.name.str();
|
fun.name = _fun.name.str();
|
||||||
for (auto const& param: _fun.parameters)
|
for (auto const& param: _fun.parameters)
|
||||||
fun.parameters.push_back({param.name.str(), wasm::Type::i64});
|
fun.parameters.push_back({param.name.str(), translatedType(param.type)});
|
||||||
for (auto const& retParam: _fun.returnVariables)
|
for (auto const& retParam: _fun.returnVariables)
|
||||||
fun.locals.emplace_back(wasm::VariableDeclaration{retParam.name.str(), wasm::Type::i64});
|
fun.locals.emplace_back(wasm::VariableDeclaration{retParam.name.str(), translatedType(retParam.type)});
|
||||||
if (!_fun.returnVariables.empty())
|
if (!_fun.returnVariables.empty())
|
||||||
fun.returnType = wasm::Type::i64;
|
fun.returnType = translatedType(_fun.returnVariables[0].type);
|
||||||
|
|
||||||
yulAssert(m_localVariables.empty(), "");
|
yulAssert(m_localVariables.empty(), "");
|
||||||
yulAssert(m_functionBodyLabel.empty(), "");
|
yulAssert(m_functionBodyLabel.empty(), "");
|
||||||
@ -347,10 +353,15 @@ wasm::FunctionDefinition WasmCodeTransform::translateFunction(yul::FunctionDefin
|
|||||||
{
|
{
|
||||||
// First return variable is returned directly, the others are stored
|
// First return variable is returned directly, the others are stored
|
||||||
// in globals.
|
// in globals.
|
||||||
allocateGlobals(_fun.returnVariables.size() - 1);
|
vector<wasm::Type> typesForGlobals;
|
||||||
|
for (size_t i = 1; i < _fun.returnVariables.size(); ++i)
|
||||||
|
typesForGlobals.push_back(translatedType(_fun.returnVariables[i].type));
|
||||||
|
vector<size_t> allocatedIndices = allocateGlobals(typesForGlobals);
|
||||||
|
yulAssert(allocatedIndices.size() == _fun.returnVariables.size() - 1, "");
|
||||||
|
|
||||||
for (size_t i = 1; i < _fun.returnVariables.size(); ++i)
|
for (size_t i = 1; i < _fun.returnVariables.size(); ++i)
|
||||||
fun.body.emplace_back(wasm::GlobalAssignment{
|
fun.body.emplace_back(wasm::GlobalAssignment{
|
||||||
m_globalVariables.at(i - 1).variableName,
|
m_globalVariables.at(allocatedIndices[i - 1]).variableName,
|
||||||
make_unique<wasm::Expression>(wasm::LocalVariable{_fun.returnVariables.at(i).name.str()})
|
make_unique<wasm::Expression>(wasm::LocalVariable{_fun.returnVariables.at(i).name.str()})
|
||||||
});
|
});
|
||||||
fun.body.emplace_back(wasm::LocalVariable{_fun.returnVariables.front().name.str()});
|
fun.body.emplace_back(wasm::LocalVariable{_fun.returnVariables.front().name.str()});
|
||||||
@ -358,52 +369,50 @@ wasm::FunctionDefinition WasmCodeTransform::translateFunction(yul::FunctionDefin
|
|||||||
return fun;
|
return fun;
|
||||||
}
|
}
|
||||||
|
|
||||||
wasm::Expression WasmCodeTransform::injectTypeConversionIfNeeded(wasm::FunctionCall _call) const
|
|
||||||
{
|
|
||||||
wasm::FunctionImport const& import = m_functionsToImport.at(YulString{_call.functionName});
|
|
||||||
for (size_t i = 0; i < _call.arguments.size(); ++i)
|
|
||||||
if (import.paramTypes.at(i) == wasm::Type::i32)
|
|
||||||
_call.arguments[i] = wasm::BuiltinCall{"i32.wrap_i64", make_vector<wasm::Expression>(std::move(_call.arguments[i]))};
|
|
||||||
else
|
|
||||||
yulAssert(import.paramTypes.at(i) == wasm::Type::i64, "Invalid Wasm type");
|
|
||||||
|
|
||||||
if (import.returnType && *import.returnType != wasm::Type::i64)
|
|
||||||
{
|
|
||||||
yulAssert(*import.returnType == wasm::Type::i32, "Invalid Wasm type");
|
|
||||||
return wasm::BuiltinCall{"i64.extend_i32_u", make_vector<wasm::Expression>(std::move(_call))};
|
|
||||||
}
|
|
||||||
return {std::move(_call)};
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<wasm::Expression> WasmCodeTransform::injectTypeConversionIfNeeded(
|
|
||||||
vector<wasm::Expression> _arguments,
|
|
||||||
vector<Type> const& _parameterTypes
|
|
||||||
) const
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < _arguments.size(); ++i)
|
|
||||||
if (_parameterTypes.at(i) == "i32"_yulstring)
|
|
||||||
_arguments[i] = wasm::BuiltinCall{"i32.wrap_i64", make_vector<wasm::Expression>(std::move(_arguments[i]))};
|
|
||||||
else
|
|
||||||
yulAssert(
|
|
||||||
_parameterTypes.at(i).empty() || _parameterTypes.at(i) == "i64"_yulstring,
|
|
||||||
"Unknown type " + _parameterTypes.at(i).str()
|
|
||||||
);
|
|
||||||
|
|
||||||
return _arguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
string WasmCodeTransform::newLabel()
|
string WasmCodeTransform::newLabel()
|
||||||
{
|
{
|
||||||
return m_nameDispenser.newName("label_"_yulstring).str();
|
return m_nameDispenser.newName("label_"_yulstring).str();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WasmCodeTransform::allocateGlobals(size_t _amount)
|
vector<size_t> WasmCodeTransform::allocateGlobals(vector<wasm::Type> const& _typesForGlobals)
|
||||||
{
|
{
|
||||||
while (m_globalVariables.size() < _amount)
|
map<wasm::Type, size_t> availableGlobals;
|
||||||
m_globalVariables.emplace_back(wasm::GlobalVariableDeclaration{
|
for (wasm::GlobalVariableDeclaration const& global: m_globalVariables)
|
||||||
m_nameDispenser.newName("global_"_yulstring).str(),
|
++availableGlobals[global.type];
|
||||||
wasm::Type::i64
|
|
||||||
});
|
map<wasm::Type, size_t> neededGlobals;
|
||||||
|
for (wasm::Type const& type: _typesForGlobals)
|
||||||
|
++neededGlobals[type];
|
||||||
|
|
||||||
|
for (auto [type, neededGlobalCount]: neededGlobals)
|
||||||
|
while (availableGlobals[type] < neededGlobalCount)
|
||||||
|
{
|
||||||
|
m_globalVariables.emplace_back(wasm::GlobalVariableDeclaration{
|
||||||
|
m_nameDispenser.newName("global_"_yulstring).str(),
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
|
||||||
|
++availableGlobals[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<size_t> allocatedIndices;
|
||||||
|
map<wasm::Type, size_t> nextGlobal;
|
||||||
|
for (wasm::Type const& type: _typesForGlobals)
|
||||||
|
{
|
||||||
|
while (m_globalVariables[nextGlobal[type]].type != type)
|
||||||
|
++nextGlobal[type];
|
||||||
|
|
||||||
|
allocatedIndices.push_back(nextGlobal[type]++);
|
||||||
|
}
|
||||||
|
|
||||||
|
yulAssert(all_of(
|
||||||
|
allocatedIndices.begin(),
|
||||||
|
allocatedIndices.end(),
|
||||||
|
[this](size_t index){ return index < m_globalVariables.size(); }
|
||||||
|
), "");
|
||||||
|
yulAssert(allocatedIndices.size() == set<size_t>(allocatedIndices.begin(), allocatedIndices.end()).size(), "Indices not unique");
|
||||||
|
yulAssert(allocatedIndices.size() == _typesForGlobals.size(), "");
|
||||||
|
return allocatedIndices;
|
||||||
}
|
}
|
||||||
|
|
||||||
wasm::Type WasmCodeTransform::translatedType(yul::Type _yulType)
|
wasm::Type WasmCodeTransform::translatedType(yul::Type _yulType)
|
||||||
@ -415,3 +424,19 @@ wasm::Type WasmCodeTransform::translatedType(yul::Type _yulType)
|
|||||||
else
|
else
|
||||||
yulAssert(false, "This Yul type does not have a corresponding type in Wasm.");
|
yulAssert(false, "This Yul type does not have a corresponding type in Wasm.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wasm::Literal WasmCodeTransform::makeLiteral(wasm::Type _type, u256 _value)
|
||||||
|
{
|
||||||
|
if (_type == wasm::Type::i32)
|
||||||
|
{
|
||||||
|
yulAssert(_value <= numeric_limits<uint32_t>::max(), "Literal too large: " + _value.str());
|
||||||
|
return wasm::Literal{static_cast<uint32_t>(_value)};
|
||||||
|
}
|
||||||
|
else if (_type == wasm::Type::i64)
|
||||||
|
{
|
||||||
|
yulAssert(_value <= numeric_limits<uint64_t>::max(), "Literal too large: " + _value.str());
|
||||||
|
return wasm::Literal{static_cast<uint64_t>(_value)};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
yulAssert(false, "Invalid Wasm literal type");
|
||||||
|
}
|
||||||
|
@ -24,6 +24,9 @@
|
|||||||
#include <libyul/AsmDataForward.h>
|
#include <libyul/AsmDataForward.h>
|
||||||
#include <libyul/Dialect.h>
|
#include <libyul/Dialect.h>
|
||||||
#include <libyul/optimiser/NameDispenser.h>
|
#include <libyul/optimiser/NameDispenser.h>
|
||||||
|
#include <libyul/optimiser/TypeInfo.h>
|
||||||
|
|
||||||
|
#include <libsolutil/Common.h>
|
||||||
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -56,10 +59,12 @@ public:
|
|||||||
private:
|
private:
|
||||||
WasmCodeTransform(
|
WasmCodeTransform(
|
||||||
Dialect const& _dialect,
|
Dialect const& _dialect,
|
||||||
Block const& _ast
|
Block const& _ast,
|
||||||
|
TypeInfo& _typeInfo
|
||||||
):
|
):
|
||||||
m_dialect(_dialect),
|
m_dialect(_dialect),
|
||||||
m_nameDispenser(_dialect, _ast)
|
m_nameDispenser(_dialect, _ast),
|
||||||
|
m_typeInfo(_typeInfo)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
std::unique_ptr<wasm::Expression> visit(yul::Expression const& _expression);
|
std::unique_ptr<wasm::Expression> visit(yul::Expression const& _expression);
|
||||||
@ -79,17 +84,13 @@ private:
|
|||||||
|
|
||||||
wasm::FunctionDefinition translateFunction(yul::FunctionDefinition const& _funDef);
|
wasm::FunctionDefinition translateFunction(yul::FunctionDefinition const& _funDef);
|
||||||
|
|
||||||
wasm::Expression injectTypeConversionIfNeeded(wasm::FunctionCall _call) const;
|
|
||||||
std::vector<wasm::Expression> injectTypeConversionIfNeeded(
|
|
||||||
std::vector<wasm::Expression> _arguments,
|
|
||||||
std::vector<yul::Type> const& _parameterTypes
|
|
||||||
) const;
|
|
||||||
|
|
||||||
std::string newLabel();
|
std::string newLabel();
|
||||||
/// Makes sure that there are at least @a _amount global variables.
|
/// Selects a subset of global variables matching specified sequence of variable types.
|
||||||
void allocateGlobals(size_t _amount);
|
/// Defines more global variables of a given type if there's not enough.
|
||||||
|
std::vector<size_t> allocateGlobals(std::vector<wasm::Type> const& _typesForGlobals);
|
||||||
|
|
||||||
static wasm::Type translatedType(yul::Type _yulType);
|
static wasm::Type translatedType(yul::Type _yulType);
|
||||||
|
static wasm::Literal makeLiteral(wasm::Type _type, u256 _value);
|
||||||
|
|
||||||
Dialect const& m_dialect;
|
Dialect const& m_dialect;
|
||||||
NameDispenser m_nameDispenser;
|
NameDispenser m_nameDispenser;
|
||||||
@ -99,6 +100,7 @@ private:
|
|||||||
std::map<YulString, wasm::FunctionImport> m_functionsToImport;
|
std::map<YulString, wasm::FunctionImport> m_functionsToImport;
|
||||||
std::string m_functionBodyLabel;
|
std::string m_functionBodyLabel;
|
||||||
std::stack<std::pair<std::string, std::string>> m_breakContinueLabelNames;
|
std::stack<std::pair<std::string, std::string>> m_breakContinueLabelNames;
|
||||||
|
TypeInfo& m_typeInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -91,9 +91,9 @@ WasmDialect::WasmDialect()
|
|||||||
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
|
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
|
||||||
|
|
||||||
// Drop is actually overloaded for all types, but Yul does not support that.
|
// Drop is actually overloaded for all types, but Yul does not support that.
|
||||||
// Because of that, we introduce "i32.drop".
|
// Because of that, we introduce "i32.drop" and "i64.drop".
|
||||||
addFunction("drop", {i64}, {});
|
|
||||||
addFunction("i32.drop", {i32}, {});
|
addFunction("i32.drop", {i32}, {});
|
||||||
|
addFunction("i64.drop", {i64}, {});
|
||||||
|
|
||||||
addFunction("nop", {}, {});
|
addFunction("nop", {}, {});
|
||||||
addFunction("unreachable", {}, {}, false);
|
addFunction("unreachable", {}, {}, false);
|
||||||
@ -122,7 +122,7 @@ BuiltinFunction const* WasmDialect::discardFunction(YulString _type) const
|
|||||||
if (_type == "i32"_yulstring)
|
if (_type == "i32"_yulstring)
|
||||||
return builtin("i32.drop"_yulstring);
|
return builtin("i32.drop"_yulstring);
|
||||||
yulAssert(_type == "i64"_yulstring, "");
|
yulAssert(_type == "i64"_yulstring, "");
|
||||||
return builtin("drop"_yulstring);
|
return builtin("i64.drop"_yulstring);
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltinFunction const* WasmDialect::equalityFunction(YulString _type) const
|
BuiltinFunction const* WasmDialect::equalityFunction(YulString _type) const
|
||||||
|
@ -112,7 +112,7 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
yulAssert(varDecl.variables.size() == 1, "");
|
yulAssert(varDecl.variables.size() == 1, "");
|
||||||
auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name);
|
auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name);
|
||||||
vector<Statement> ret;
|
vector<Statement> ret;
|
||||||
for (int i = 0; i < 3; i++)
|
for (size_t i = 0; i < 3; i++)
|
||||||
ret.emplace_back(VariableDeclaration{
|
ret.emplace_back(VariableDeclaration{
|
||||||
varDecl.location,
|
varDecl.location,
|
||||||
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
||||||
@ -143,7 +143,7 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
auto newRhs = expandValue(*varDecl.value);
|
auto newRhs = expandValue(*varDecl.value);
|
||||||
auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name);
|
auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name);
|
||||||
vector<Statement> ret;
|
vector<Statement> ret;
|
||||||
for (int i = 0; i < 4; i++)
|
for (size_t i = 0; i < 4; i++)
|
||||||
ret.emplace_back(VariableDeclaration{
|
ret.emplace_back(VariableDeclaration{
|
||||||
varDecl.location,
|
varDecl.location,
|
||||||
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
||||||
@ -172,7 +172,7 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
yulAssert(assignment.variableNames.size() == 1, "");
|
yulAssert(assignment.variableNames.size() == 1, "");
|
||||||
auto newLhs = generateU64IdentifierNames(assignment.variableNames[0].name);
|
auto newLhs = generateU64IdentifierNames(assignment.variableNames[0].name);
|
||||||
vector<Statement> ret;
|
vector<Statement> ret;
|
||||||
for (int i = 0; i < 3; i++)
|
for (size_t i = 0; i < 3; i++)
|
||||||
ret.emplace_back(Assignment{
|
ret.emplace_back(Assignment{
|
||||||
assignment.location,
|
assignment.location,
|
||||||
{Identifier{assignment.location, newLhs[i]}},
|
{Identifier{assignment.location, newLhs[i]}},
|
||||||
@ -203,7 +203,7 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
auto newRhs = expandValue(*assignment.value);
|
auto newRhs = expandValue(*assignment.value);
|
||||||
YulString lhsName = assignment.variableNames[0].name;
|
YulString lhsName = assignment.variableNames[0].name;
|
||||||
vector<Statement> ret;
|
vector<Statement> ret;
|
||||||
for (int i = 0; i < 4; i++)
|
for (size_t i = 0; i < 4; i++)
|
||||||
ret.emplace_back(Assignment{
|
ret.emplace_back(Assignment{
|
||||||
assignment.location,
|
assignment.location,
|
||||||
{Identifier{assignment.location, m_variableMapping.at(lhsName)[i]}},
|
{Identifier{assignment.location, m_variableMapping.at(lhsName)[i]}},
|
||||||
@ -382,7 +382,7 @@ std::vector<Statement> WordSizeTransform::handleSwitch(Switch& _switch)
|
|||||||
array<YulString, 4> WordSizeTransform::generateU64IdentifierNames(YulString const& _s)
|
array<YulString, 4> WordSizeTransform::generateU64IdentifierNames(YulString const& _s)
|
||||||
{
|
{
|
||||||
yulAssert(m_variableMapping.find(_s) == m_variableMapping.end(), "");
|
yulAssert(m_variableMapping.find(_s) == m_variableMapping.end(), "");
|
||||||
for (int i = 0; i < 4; i++)
|
for (size_t i = 0; i < 4; i++)
|
||||||
m_variableMapping[_s][i] = m_nameDispenser.newName(YulString{_s.str() + "_" + to_string(i)});
|
m_variableMapping[_s][i] = m_nameDispenser.newName(YulString{_s.str() + "_" + to_string(i)});
|
||||||
return m_variableMapping[_s];
|
return m_variableMapping[_s];
|
||||||
}
|
}
|
||||||
@ -392,19 +392,20 @@ array<unique_ptr<Expression>, 4> WordSizeTransform::expandValue(Expression const
|
|||||||
array<unique_ptr<Expression>, 4> ret;
|
array<unique_ptr<Expression>, 4> ret;
|
||||||
if (holds_alternative<Identifier>(_e))
|
if (holds_alternative<Identifier>(_e))
|
||||||
{
|
{
|
||||||
Identifier const& id = std::get<Identifier>(_e);
|
auto const& id = std::get<Identifier>(_e);
|
||||||
for (int i = 0; i < 4; i++)
|
for (size_t i = 0; i < 4; i++)
|
||||||
ret[i] = make_unique<Expression>(Identifier{id.location, m_variableMapping.at(id.name)[i]});
|
ret[i] = make_unique<Expression>(Identifier{id.location, m_variableMapping.at(id.name)[i]});
|
||||||
}
|
}
|
||||||
else if (holds_alternative<Literal>(_e))
|
else if (holds_alternative<Literal>(_e))
|
||||||
{
|
{
|
||||||
Literal const& lit = std::get<Literal>(_e);
|
auto const& lit = std::get<Literal>(_e);
|
||||||
u256 val = valueOfLiteral(lit);
|
u256 val = valueOfLiteral(lit);
|
||||||
for (int i = 3; i >= 0; i--)
|
for (size_t exprIndex = 0; exprIndex < 4; ++exprIndex)
|
||||||
{
|
{
|
||||||
|
size_t exprIndexReverse = 3 - exprIndex;
|
||||||
u256 currentVal = val & std::numeric_limits<uint64_t>::max();
|
u256 currentVal = val & std::numeric_limits<uint64_t>::max();
|
||||||
val >>= 64;
|
val >>= 64;
|
||||||
ret[i] = make_unique<Expression>(
|
ret[exprIndexReverse] = make_unique<Expression>(
|
||||||
Literal{
|
Literal{
|
||||||
lit.location,
|
lit.location,
|
||||||
LiteralKind::Number,
|
LiteralKind::Number,
|
||||||
@ -426,4 +427,3 @@ vector<Expression> WordSizeTransform::expandValueToVector(Expression const& _e)
|
|||||||
ret.emplace_back(std::move(*val));
|
ret.emplace_back(std::move(*val));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +253,21 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
|
|||||||
// assignment to slot contents denoted by "name"
|
// assignment to slot contents denoted by "name"
|
||||||
m_memory.eraseValue(name);
|
m_memory.eraseValue(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_value && _variables.size() == 1)
|
||||||
|
{
|
||||||
|
YulString variable = *_variables.begin();
|
||||||
|
if (!movableChecker.referencedVariables().count(variable))
|
||||||
|
{
|
||||||
|
// This might erase additional knowledge about the slot.
|
||||||
|
// On the other hand, if we knew the value in the slot
|
||||||
|
// already, then the sload() / mload() would have been replaced by a variable anyway.
|
||||||
|
if (auto key = isSimpleLoad(evmasm::Instruction::MLOAD, *_value))
|
||||||
|
m_memory.set(*key, variable);
|
||||||
|
else if (auto key = isSimpleLoad(evmasm::Instruction::SLOAD, *_value))
|
||||||
|
m_storage.set(*key, variable);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataFlowAnalyzer::pushScope(bool _functionScope)
|
void DataFlowAnalyzer::pushScope(bool _functionScope)
|
||||||
@ -401,3 +416,25 @@ std::optional<pair<YulString, YulString>> DataFlowAnalyzer::isSimpleStore(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<YulString> DataFlowAnalyzer::isSimpleLoad(
|
||||||
|
evmasm::Instruction _load,
|
||||||
|
Expression const& _expression
|
||||||
|
) const
|
||||||
|
{
|
||||||
|
yulAssert(
|
||||||
|
_load == evmasm::Instruction::MLOAD ||
|
||||||
|
_load == evmasm::Instruction::SLOAD,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
if (holds_alternative<FunctionCall>(_expression))
|
||||||
|
{
|
||||||
|
FunctionCall const& funCall = std::get<FunctionCall>(_expression);
|
||||||
|
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&m_dialect))
|
||||||
|
if (auto const* builtin = dialect->builtin(funCall.functionName.name))
|
||||||
|
if (builtin->instruction == _load)
|
||||||
|
if (holds_alternative<Identifier>(funCall.arguments.at(0)))
|
||||||
|
return std::get<Identifier>(funCall.arguments.at(0)).name;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -142,11 +142,20 @@ protected:
|
|||||||
/// Returns true iff the variable is in scope.
|
/// Returns true iff the variable is in scope.
|
||||||
bool inScope(YulString _variableName) const;
|
bool inScope(YulString _variableName) const;
|
||||||
|
|
||||||
|
/// Checks if the statement is sstore(a, b) / mstore(a, b)
|
||||||
|
/// where a and b are variables and returns these variables in that case.
|
||||||
std::optional<std::pair<YulString, YulString>> isSimpleStore(
|
std::optional<std::pair<YulString, YulString>> isSimpleStore(
|
||||||
evmasm::Instruction _store,
|
evmasm::Instruction _store,
|
||||||
ExpressionStatement const& _statement
|
ExpressionStatement const& _statement
|
||||||
) const;
|
) const;
|
||||||
|
|
||||||
|
/// Checks if the expression is sload(a) / mload(a)
|
||||||
|
/// where a is a variable and returns the variable in that case.
|
||||||
|
std::optional<YulString> isSimpleLoad(
|
||||||
|
evmasm::Instruction _load,
|
||||||
|
Expression const& _expression
|
||||||
|
) const;
|
||||||
|
|
||||||
Dialect const& m_dialect;
|
Dialect const& m_dialect;
|
||||||
/// Side-effects of user-defined functions. Worst-case side-effects are assumed
|
/// Side-effects of user-defined functions. Worst-case side-effects are assumed
|
||||||
/// if this is not provided or the function is not found.
|
/// if this is not provided or the function is not found.
|
||||||
|
@ -51,10 +51,10 @@ void DeadCodeEliminator::operator()(Block& _block)
|
|||||||
tie(controlFlowChange, index) = TerminationFinder{m_dialect}.firstUnconditionalControlFlowChange(_block.statements);
|
tie(controlFlowChange, index) = TerminationFinder{m_dialect}.firstUnconditionalControlFlowChange(_block.statements);
|
||||||
|
|
||||||
// Erase everything after the terminating statement that is not a function definition.
|
// Erase everything after the terminating statement that is not a function definition.
|
||||||
if (controlFlowChange != TerminationFinder::ControlFlow::FlowOut && index != size_t(-1))
|
if (controlFlowChange != TerminationFinder::ControlFlow::FlowOut && index != std::numeric_limits<size_t>::max())
|
||||||
_block.statements.erase(
|
_block.statements.erase(
|
||||||
remove_if(
|
remove_if(
|
||||||
_block.statements.begin() + index + 1,
|
_block.statements.begin() + static_cast<ptrdiff_t>(index) + 1,
|
||||||
_block.statements.end(),
|
_block.statements.end(),
|
||||||
[] (Statement const& _s) { return !holds_alternative<yul::FunctionDefinition>(_s); }
|
[] (Statement const& _s) { return !holds_alternative<yul::FunctionDefinition>(_s); }
|
||||||
),
|
),
|
||||||
@ -63,4 +63,3 @@ void DeadCodeEliminator::operator()(Block& _block)
|
|||||||
|
|
||||||
ASTModifier::operator()(_block);
|
ASTModifier::operator()(_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ void ExpressionJoiner::decrementLatestStatementPointer()
|
|||||||
void ExpressionJoiner::resetLatestStatementPointer()
|
void ExpressionJoiner::resetLatestStatementPointer()
|
||||||
{
|
{
|
||||||
m_currentBlock = nullptr;
|
m_currentBlock = nullptr;
|
||||||
m_latestStatementInBlock = size_t(-1);
|
m_latestStatementInBlock = numeric_limits<size_t>::max();
|
||||||
}
|
}
|
||||||
|
|
||||||
Statement* ExpressionJoiner::latestStatement()
|
Statement* ExpressionJoiner::latestStatement()
|
||||||
|
@ -194,7 +194,7 @@ void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
|
|||||||
|
|
||||||
void IntroduceControlFlowSSA::operator()(ForLoop& _for)
|
void IntroduceControlFlowSSA::operator()(ForLoop& _for)
|
||||||
{
|
{
|
||||||
(*this)(_for.pre);
|
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
|
||||||
|
|
||||||
Assignments assignments;
|
Assignments assignments;
|
||||||
assignments(_for.body);
|
assignments(_for.body);
|
||||||
@ -357,11 +357,7 @@ void PropagateValues::operator()(Assignment& _assignment)
|
|||||||
|
|
||||||
void PropagateValues::operator()(ForLoop& _for)
|
void PropagateValues::operator()(ForLoop& _for)
|
||||||
{
|
{
|
||||||
// This will clear the current value in case of a reassignment inside the
|
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
|
||||||
// init part, although the new variable would still be in scope inside the whole loop.
|
|
||||||
// This small inefficiency is fine if we move the pre part of all for loops out
|
|
||||||
// of the for loop.
|
|
||||||
(*this)(_for.pre);
|
|
||||||
|
|
||||||
Assignments assignments;
|
Assignments assignments;
|
||||||
assignments(_for.body);
|
assignments(_for.body);
|
||||||
|
@ -85,7 +85,7 @@ class NameDispenser;
|
|||||||
*
|
*
|
||||||
* TODO Which transforms are required to keep this idempotent?
|
* TODO Which transforms are required to keep this idempotent?
|
||||||
*
|
*
|
||||||
* Prerequisite: Disambiguator.
|
* Prerequisite: Disambiguator, ForLoopInitRewriter.
|
||||||
*/
|
*/
|
||||||
class SSATransform: public ASTModifier
|
class SSATransform: public ASTModifier
|
||||||
{
|
{
|
||||||
|
@ -180,7 +180,7 @@ pair<TerminationFinder::ControlFlow, size_t> TerminationFinder::firstUncondition
|
|||||||
if (controlFlow != ControlFlow::FlowOut)
|
if (controlFlow != ControlFlow::FlowOut)
|
||||||
return {controlFlow, i};
|
return {controlFlow, i};
|
||||||
}
|
}
|
||||||
return {ControlFlow::FlowOut, size_t(-1)};
|
return {ControlFlow::FlowOut, numeric_limits<size_t>::max()};
|
||||||
}
|
}
|
||||||
|
|
||||||
TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement)
|
TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement)
|
||||||
|
@ -178,14 +178,14 @@ bool StackCompressor::run(
|
|||||||
eliminateVariables(
|
eliminateVariables(
|
||||||
_dialect,
|
_dialect,
|
||||||
std::get<Block>(_object.code->statements.at(0)),
|
std::get<Block>(_object.code->statements.at(0)),
|
||||||
stackSurplus.at({}),
|
static_cast<size_t>(stackSurplus.at({})),
|
||||||
allowMSizeOptimzation
|
allowMSizeOptimzation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 1; i < _object.code->statements.size(); ++i)
|
for (size_t i = 1; i < _object.code->statements.size(); ++i)
|
||||||
{
|
{
|
||||||
FunctionDefinition& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
|
auto& fun = std::get<FunctionDefinition>(_object.code->statements[i]);
|
||||||
if (!stackSurplus.count(fun.name))
|
if (!stackSurplus.count(fun.name))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ bool StackCompressor::run(
|
|||||||
eliminateVariables(
|
eliminateVariables(
|
||||||
_dialect,
|
_dialect,
|
||||||
fun,
|
fun,
|
||||||
stackSurplus.at(fun.name),
|
static_cast<size_t>(stackSurplus.at(fun.name)),
|
||||||
allowMSizeOptimzation
|
allowMSizeOptimzation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -34,5 +34,5 @@ else
|
|||||||
BUILD_DIR="$1"
|
BUILD_DIR="$1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker run -v $(pwd):/root/project -w /root/project ethereum/solidity-buildpack-deps:emsdk-1.39.15-1 \
|
docker run -v $(pwd):/root/project -w /root/project ethereum/solidity-buildpack-deps:emsdk-1.39.15-2 \
|
||||||
./scripts/travis-emscripten/build_emscripten.sh $BUILD_DIR
|
./scripts/travis-emscripten/build_emscripten.sh $BUILD_DIR
|
||||||
|
261
scripts/error_codes.py
Executable file
261
scripts/error_codes.py
Executable file
@ -0,0 +1,261 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import getopt
|
||||||
|
import sys
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
ENCODING = "utf-8"
|
||||||
|
SOURCE_FILE_PATTERN = r"\b\d+_error\b"
|
||||||
|
|
||||||
|
|
||||||
|
def read_file(file_name):
|
||||||
|
content = None
|
||||||
|
try:
|
||||||
|
with open(file_name, "r", encoding=ENCODING) as f:
|
||||||
|
content = f.read()
|
||||||
|
finally:
|
||||||
|
if content == None:
|
||||||
|
print(f"Error reading: {file_name}")
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(file_name, content):
|
||||||
|
with open(file_name, "w", encoding=ENCODING) as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def in_comment(source, pos):
|
||||||
|
slash_slash_pos = source.rfind("//", 0, pos)
|
||||||
|
lf_pos = source.rfind("\n", 0, pos)
|
||||||
|
if slash_slash_pos > lf_pos:
|
||||||
|
return True
|
||||||
|
slash_star_pos = source.rfind("/*", 0, pos)
|
||||||
|
star_slash_pos = source.rfind("*/", 0, pos)
|
||||||
|
return slash_star_pos > star_slash_pos
|
||||||
|
|
||||||
|
|
||||||
|
def find_ids_in_source_file(file_name, ids):
|
||||||
|
source = read_file(file_name)
|
||||||
|
for m in re.finditer(SOURCE_FILE_PATTERN, source):
|
||||||
|
if in_comment(source, m.start()):
|
||||||
|
continue
|
||||||
|
underscore_pos = m.group(0).index("_")
|
||||||
|
id = m.group(0)[0:underscore_pos]
|
||||||
|
if id in ids:
|
||||||
|
ids[id] += 1
|
||||||
|
else:
|
||||||
|
ids[id] = 1
|
||||||
|
|
||||||
|
|
||||||
|
def get_used_ids(file_names):
|
||||||
|
used_ids = {}
|
||||||
|
for file_name in file_names:
|
||||||
|
find_ids_in_source_file(file_name, used_ids)
|
||||||
|
return used_ids
|
||||||
|
|
||||||
|
|
||||||
|
def get_next_id(available_ids):
|
||||||
|
assert len(available_ids) > 0, "Out of IDs"
|
||||||
|
next_id = random.choice(list(available_ids))
|
||||||
|
available_ids.remove(next_id)
|
||||||
|
return next_id
|
||||||
|
|
||||||
|
|
||||||
|
def fix_ids_in_file(file_name, available_ids, used_ids):
|
||||||
|
source = read_file(file_name)
|
||||||
|
|
||||||
|
k = 0
|
||||||
|
destination = []
|
||||||
|
for m in re.finditer(SOURCE_FILE_PATTERN, source):
|
||||||
|
destination.extend(source[k:m.start()])
|
||||||
|
|
||||||
|
underscore_pos = m.group(0).index("_")
|
||||||
|
id = m.group(0)[0:underscore_pos]
|
||||||
|
|
||||||
|
# incorrect id or id has a duplicate somewhere
|
||||||
|
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or used_ids[id] > 1):
|
||||||
|
assert id in used_ids
|
||||||
|
new_id = get_next_id(available_ids)
|
||||||
|
assert new_id not in used_ids
|
||||||
|
used_ids[id] -= 1
|
||||||
|
else:
|
||||||
|
new_id = id
|
||||||
|
|
||||||
|
destination.extend(new_id + "_error")
|
||||||
|
k = m.end()
|
||||||
|
|
||||||
|
destination.extend(source[k:])
|
||||||
|
|
||||||
|
destination = ''.join(destination)
|
||||||
|
if source != destination:
|
||||||
|
write_file(file_name, destination)
|
||||||
|
print(f"Fixed file: {file_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def fix_ids(used_ids, file_names):
|
||||||
|
available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys()
|
||||||
|
for file_name in file_names:
|
||||||
|
fix_ids_in_file(file_name, available_ids, used_ids)
|
||||||
|
|
||||||
|
|
||||||
|
def find_files(top_dir, sub_dirs, extensions):
|
||||||
|
"""Builds a list of files with given extensions in specified subdirectories"""
|
||||||
|
|
||||||
|
source_file_names = []
|
||||||
|
for dir in sub_dirs:
|
||||||
|
for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")):
|
||||||
|
for file_name in file_names:
|
||||||
|
_, ext = path.splitext(file_name)
|
||||||
|
if ext in extensions:
|
||||||
|
source_file_names.append(path.join(root, file_name))
|
||||||
|
|
||||||
|
return source_file_names
|
||||||
|
|
||||||
|
|
||||||
|
def find_ids_in_test_file(file_name):
|
||||||
|
source = read_file(file_name)
|
||||||
|
pattern = r"^// (.*Error|Warning) \d\d\d\d:"
|
||||||
|
return {m.group(0)[-5:-1] for m in re.finditer(pattern, source, flags=re.MULTILINE)}
|
||||||
|
|
||||||
|
|
||||||
|
def find_ids_in_test_files(file_names):
|
||||||
|
used_ids = set()
|
||||||
|
for file_name in file_names:
|
||||||
|
used_ids |= find_ids_in_test_file(file_name)
|
||||||
|
return used_ids
|
||||||
|
|
||||||
|
|
||||||
|
def print_ids(ids):
|
||||||
|
for k, id in enumerate(sorted(ids)):
|
||||||
|
if k % 10 > 0:
|
||||||
|
print(" ", end="")
|
||||||
|
elif k > 0:
|
||||||
|
print()
|
||||||
|
print(id, end="")
|
||||||
|
|
||||||
|
|
||||||
|
def examine_id_coverage(top_dir, used_ids):
|
||||||
|
test_sub_dirs = [
|
||||||
|
path.join("test", "libsolidity", "errorRecoveryTests"),
|
||||||
|
path.join("test", "libsolidity", "smtCheckerTests"),
|
||||||
|
path.join("test", "libsolidity", "syntaxTests")
|
||||||
|
]
|
||||||
|
test_file_names = find_files(
|
||||||
|
top_dir,
|
||||||
|
test_sub_dirs,
|
||||||
|
[".sol"]
|
||||||
|
)
|
||||||
|
covered_ids = find_ids_in_test_files(test_file_names)
|
||||||
|
|
||||||
|
print(f"IDs in source files: {len(used_ids)}")
|
||||||
|
print(f"IDs in test files : {len(covered_ids)} ({len(covered_ids) - len(used_ids)})")
|
||||||
|
print()
|
||||||
|
|
||||||
|
unused_covered_ids = covered_ids - used_ids
|
||||||
|
if len(unused_covered_ids) != 0:
|
||||||
|
print("Error. The following error codes found in tests, but not in sources:")
|
||||||
|
print_ids(unused_covered_ids)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
used_uncovered_ids = used_ids - covered_ids
|
||||||
|
if len(used_uncovered_ids) != 0:
|
||||||
|
print("The following error codes found in sources, but not in tests:")
|
||||||
|
print_ids(used_uncovered_ids)
|
||||||
|
print("\n\nPlease make sure to add appropriate tests.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
# pylint: disable=too-many-branches, too-many-locals, too-many-statements
|
||||||
|
|
||||||
|
check = False
|
||||||
|
fix = False
|
||||||
|
no_confirm = False
|
||||||
|
examine_coverage = False
|
||||||
|
next = False
|
||||||
|
opts, args = getopt.getopt(argv, "", ["check", "fix", "no-confirm", "examine-coverage", "next"])
|
||||||
|
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt == '--check':
|
||||||
|
check = True
|
||||||
|
elif opt == "--fix":
|
||||||
|
fix = True
|
||||||
|
elif opt == '--no-confirm':
|
||||||
|
no_confirm = True
|
||||||
|
elif opt == '--examine-coverage':
|
||||||
|
examine_coverage = True
|
||||||
|
elif opt == '--next':
|
||||||
|
next = True
|
||||||
|
|
||||||
|
if [check, fix, examine_coverage, next].count(True) != 1:
|
||||||
|
print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
cwd = os.getcwd()
|
||||||
|
|
||||||
|
source_file_names = find_files(
|
||||||
|
cwd,
|
||||||
|
["libevmasm", "liblangutil", "libsolc", "libsolidity", "libsolutil", "libyul", "solc"],
|
||||||
|
[".h", ".cpp"]
|
||||||
|
)
|
||||||
|
used_ids = get_used_ids(source_file_names)
|
||||||
|
|
||||||
|
ok = True
|
||||||
|
for id in sorted(used_ids):
|
||||||
|
if len(id) != 4:
|
||||||
|
print(f"ID {id} length != 4")
|
||||||
|
ok = False
|
||||||
|
if id[0] == "0":
|
||||||
|
print(f"ID {id} starts with zero")
|
||||||
|
ok = False
|
||||||
|
if used_ids[id] > 1:
|
||||||
|
print(f"ID {id} appears {used_ids[id]} times")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
if examine_coverage:
|
||||||
|
if not ok:
|
||||||
|
print("Incorrect IDs have to be fixed before applying --examine-coverage")
|
||||||
|
res = examine_id_coverage(cwd, used_ids.keys())
|
||||||
|
exit(res)
|
||||||
|
|
||||||
|
random.seed()
|
||||||
|
|
||||||
|
if next:
|
||||||
|
if not ok:
|
||||||
|
print("Incorrect IDs have to be fixed before applying --next")
|
||||||
|
available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys()
|
||||||
|
next_id = get_next_id(available_ids)
|
||||||
|
print(f"Next ID: {next_id}")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
print("No incorrect IDs found")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if check:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
assert fix, "Unexpected state, should not come here without --fix"
|
||||||
|
|
||||||
|
if not no_confirm:
|
||||||
|
answer = input(
|
||||||
|
"\nDo you want to fix incorrect IDs?\n"
|
||||||
|
"Please commit current changes first, and review the results when the script finishes.\n"
|
||||||
|
"[Y/N]? "
|
||||||
|
)
|
||||||
|
while len(answer) == 0 or answer not in "YNyn":
|
||||||
|
answer = input("[Y/N]? ")
|
||||||
|
if answer not in "yY":
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
fix_ids(used_ids, source_file_names)
|
||||||
|
print("Fixing completed")
|
||||||
|
exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(sys.argv[1:])
|
@ -1,175 +0,0 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
import random
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
import sys
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
ENCODING = "utf-8"
|
|
||||||
PATTERN = r"\b\d+_error\b"
|
|
||||||
|
|
||||||
|
|
||||||
def read_file(file_name):
|
|
||||||
content = None
|
|
||||||
try:
|
|
||||||
with open(file_name, "r", encoding=ENCODING) as f:
|
|
||||||
content = f.read()
|
|
||||||
finally:
|
|
||||||
if content == None:
|
|
||||||
print(f"Error reading: {file_name}")
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def write_file(file_name, content):
|
|
||||||
with open(file_name, "w", encoding=ENCODING) as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
|
|
||||||
def in_comment(source, pos):
|
|
||||||
slash_slash_pos = source.rfind("//", 0, pos)
|
|
||||||
lf_pos = source.rfind("\n", 0, pos)
|
|
||||||
if slash_slash_pos > lf_pos:
|
|
||||||
return True
|
|
||||||
slash_star_pos = source.rfind("/*", 0, pos)
|
|
||||||
star_slash_pos = source.rfind("*/", 0, pos)
|
|
||||||
return slash_star_pos > star_slash_pos
|
|
||||||
|
|
||||||
|
|
||||||
def find_ids_in_file(file_name, ids):
|
|
||||||
source = read_file(file_name)
|
|
||||||
for m in re.finditer(PATTERN, source):
|
|
||||||
if in_comment(source, m.start()):
|
|
||||||
continue
|
|
||||||
underscore_pos = m.group(0).index("_")
|
|
||||||
id = m.group(0)[0:underscore_pos]
|
|
||||||
if id in ids:
|
|
||||||
ids[id] += 1
|
|
||||||
else:
|
|
||||||
ids[id] = 1
|
|
||||||
|
|
||||||
|
|
||||||
def get_used_ids(file_names):
|
|
||||||
used_ids = {}
|
|
||||||
for file_name in file_names:
|
|
||||||
find_ids_in_file(file_name, used_ids)
|
|
||||||
return used_ids
|
|
||||||
|
|
||||||
|
|
||||||
def get_id(available_ids, used_ids):
|
|
||||||
while len(available_ids) > 0:
|
|
||||||
k = random.randrange(len(available_ids))
|
|
||||||
id = list(available_ids.keys())[k]
|
|
||||||
del available_ids[id]
|
|
||||||
if id not in used_ids:
|
|
||||||
return id
|
|
||||||
assert False, "Out of IDs"
|
|
||||||
|
|
||||||
|
|
||||||
def fix_ids_in_file(file_name, available_ids, used_ids):
|
|
||||||
source = read_file(file_name)
|
|
||||||
|
|
||||||
k = 0
|
|
||||||
destination = []
|
|
||||||
for m in re.finditer(PATTERN, source):
|
|
||||||
destination.extend(source[k:m.start()])
|
|
||||||
|
|
||||||
underscore_pos = m.group(0).index("_")
|
|
||||||
id = m.group(0)[0:underscore_pos]
|
|
||||||
|
|
||||||
# incorrect id or id has a duplicate somewhere
|
|
||||||
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or used_ids[id] > 1):
|
|
||||||
assert id in used_ids
|
|
||||||
new_id = get_id(available_ids, used_ids)
|
|
||||||
used_ids[id] -= 1
|
|
||||||
else:
|
|
||||||
new_id = id
|
|
||||||
|
|
||||||
destination.extend(new_id + "_error")
|
|
||||||
k = m.end()
|
|
||||||
|
|
||||||
destination.extend(source[k:])
|
|
||||||
|
|
||||||
destination = ''.join(destination)
|
|
||||||
if source != destination:
|
|
||||||
write_file(file_name, destination)
|
|
||||||
print(f"Fixed file: {file_name}")
|
|
||||||
|
|
||||||
|
|
||||||
def fix_ids(used_ids, file_names):
|
|
||||||
available_ids = {str(id): None for id in range(1000, 10000)}
|
|
||||||
for file_name in file_names:
|
|
||||||
fix_ids_in_file(file_name, available_ids, used_ids)
|
|
||||||
|
|
||||||
|
|
||||||
def find_source_files(top_dir):
|
|
||||||
"""Builds the list of .h and .cpp files in top_dir directory"""
|
|
||||||
|
|
||||||
source_file_names = []
|
|
||||||
dirs = ['libevmasm', 'liblangutil', 'libsolc', 'libsolidity', 'libsolutil', 'libyul', 'solc']
|
|
||||||
|
|
||||||
for dir in dirs:
|
|
||||||
for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")):
|
|
||||||
for file_name in file_names:
|
|
||||||
_, ext = path.splitext(file_name)
|
|
||||||
if ext in [".h", ".cpp"]:
|
|
||||||
source_file_names.append(path.join(root, file_name))
|
|
||||||
|
|
||||||
return source_file_names
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
|
||||||
check_only = False
|
|
||||||
noconfirm = False
|
|
||||||
opts, args = getopt.getopt(argv, "", ["check-only", "noconfirm"])
|
|
||||||
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt == '--check-only':
|
|
||||||
check_only = True
|
|
||||||
elif opt == '--noconfirm':
|
|
||||||
noconfirm = True
|
|
||||||
|
|
||||||
random.seed()
|
|
||||||
cwd = os.getcwd()
|
|
||||||
|
|
||||||
source_file_names = find_source_files(cwd)
|
|
||||||
|
|
||||||
used_ids = get_used_ids(source_file_names)
|
|
||||||
|
|
||||||
ok = True
|
|
||||||
for id in sorted(used_ids):
|
|
||||||
if len(id) != 4:
|
|
||||||
print(f"ID {id} length != 4")
|
|
||||||
ok = False
|
|
||||||
if id[0] == "0":
|
|
||||||
print(f"ID {id} starts with zero")
|
|
||||||
ok = False
|
|
||||||
if used_ids[id] > 1:
|
|
||||||
print(f"ID {id} appears {used_ids[id]} times")
|
|
||||||
ok = False
|
|
||||||
|
|
||||||
if ok:
|
|
||||||
print("No incorrect IDs found")
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
if check_only:
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
if not noconfirm:
|
|
||||||
answer = input(
|
|
||||||
"\nDo you want to fix incorrect IDs?\n"
|
|
||||||
"Please commit current changes first, and review the results when the script finishes.\n"
|
|
||||||
"[Y/N]? "
|
|
||||||
)
|
|
||||||
while len(answer) == 0 or answer not in "YNyn":
|
|
||||||
answer = input("[Y/N]? ")
|
|
||||||
if answer not in "yY":
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
fix_ids(used_ids, source_file_names)
|
|
||||||
print("Fixing completed")
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(sys.argv[1:])
|
|
39
scripts/get_nightly_version.sh
Executable file
39
scripts/get_nightly_version.sh
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# Prints the exact version string that would be used to describe a nightly
|
||||||
|
# build of the compiler.
|
||||||
|
#
|
||||||
|
# The documentation for solidity is hosted at:
|
||||||
|
#
|
||||||
|
# https://solidity.readthedocs.org
|
||||||
|
#
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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/>
|
||||||
|
#
|
||||||
|
# (c) 2017 solidity contributors.
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
script_dir="$(dirname "$0")"
|
||||||
|
|
||||||
|
solidity_version=$("${script_dir}/get_version.sh")
|
||||||
|
last_commit_timestamp=$(git log -1 --date=iso --format=%ad HEAD)
|
||||||
|
last_commit_date=$(date --date="$last_commit_timestamp" --utc +%Y.%-m.%-d)
|
||||||
|
last_commit_hash=$(git rev-parse --short=8 HEAD)
|
||||||
|
|
||||||
|
echo "v${solidity_version}-nightly.${last_commit_date}+commit.${last_commit_hash}"
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
# Bash script to execute the Solidity tests.
|
# Prints version of the Solidity compiler that the source code corresponds to.
|
||||||
#
|
#
|
||||||
# The documentation for solidity is hosted at:
|
# The documentation for solidity is hosted at:
|
||||||
#
|
#
|
||||||
|
@ -170,7 +170,7 @@ case $(uname -s) in
|
|||||||
# Debian
|
# Debian
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
Debian*)
|
Debian*|Raspbian)
|
||||||
#Debian
|
#Debian
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
install_z3=""
|
install_z3=""
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
# Bash script to execute the Solidity tests.
|
# Bash script to execute the Solidity tests using the emscripten binary.
|
||||||
#
|
#
|
||||||
# The documentation for solidity is hosted at:
|
# The documentation for solidity is hosted at:
|
||||||
#
|
#
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
# Bash script to determine the percantage of tests that are compilable via Yul.
|
# Bash script to determine the percentage of tests that are compilable via Yul.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./yul_coverage.sh [--no-stats] [--successful] [--internal-compiler-errors]
|
# ./yul_coverage.sh [--no-stats] [--successful] [--internal-compiler-errors]
|
||||||
|
@ -58,6 +58,8 @@
|
|||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <boost/filesystem/operations.hpp>
|
#include <boost/filesystem/operations.hpp>
|
||||||
|
#include <boost/range/adaptor/transformed.hpp>
|
||||||
|
#include <boost/range/adaptor/filtered.hpp>
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
#ifdef _WIN32 // windows
|
#ifdef _WIN32 // windows
|
||||||
@ -1129,6 +1131,21 @@ bool CommandLineInterface::processInput()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector<string> const exclusiveModes = {
|
||||||
|
g_argStandardJSON,
|
||||||
|
g_argLink,
|
||||||
|
g_argAssemble,
|
||||||
|
g_argStrictAssembly,
|
||||||
|
g_argYul,
|
||||||
|
g_argImportAst,
|
||||||
|
};
|
||||||
|
if (countEnabledOptions(exclusiveModes) > 1)
|
||||||
|
{
|
||||||
|
serr() << "The following options are mutually exclusive: " << joinOptionNames(exclusiveModes) << ". ";
|
||||||
|
serr() << "Select at most one." << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_args.count(g_argStandardJSON))
|
if (m_args.count(g_argStandardJSON))
|
||||||
{
|
{
|
||||||
vector<string> inputFiles;
|
vector<string> inputFiles;
|
||||||
@ -1174,6 +1191,27 @@ bool CommandLineInterface::processInput()
|
|||||||
|
|
||||||
if (m_args.count(g_argAssemble) || m_args.count(g_argStrictAssembly) || m_args.count(g_argYul))
|
if (m_args.count(g_argAssemble) || m_args.count(g_argStrictAssembly) || m_args.count(g_argYul))
|
||||||
{
|
{
|
||||||
|
vector<string> const nonAssemblyModeOptions = {
|
||||||
|
// TODO: The list is not complete. Add more.
|
||||||
|
g_argOutputDir,
|
||||||
|
g_argGas,
|
||||||
|
g_argCombinedJson,
|
||||||
|
g_strOptimizeYul,
|
||||||
|
g_strNoOptimizeYul,
|
||||||
|
};
|
||||||
|
if (countEnabledOptions(nonAssemblyModeOptions) >= 1)
|
||||||
|
{
|
||||||
|
auto optionEnabled = [&](string const& name){ return m_args.count(name) > 0; };
|
||||||
|
auto enabledOptions = boost::copy_range<vector<string>>(nonAssemblyModeOptions | boost::adaptors::filtered(optionEnabled));
|
||||||
|
|
||||||
|
serr() << "The following options are invalid in assembly mode: ";
|
||||||
|
serr() << joinOptionNames(enabledOptions) << ".";
|
||||||
|
if (m_args.count(g_strOptimizeYul) || m_args.count(g_strNoOptimizeYul))
|
||||||
|
serr() << " Optimization is disabled by default and can be enabled with --" << g_argOptimize << "." << endl;
|
||||||
|
serr() << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// switch to assembly mode
|
// switch to assembly mode
|
||||||
m_onlyAssemble = true;
|
m_onlyAssemble = true;
|
||||||
using Input = yul::AssemblyStack::Language;
|
using Input = yul::AssemblyStack::Language;
|
||||||
@ -1181,16 +1219,6 @@ bool CommandLineInterface::processInput()
|
|||||||
Input inputLanguage = m_args.count(g_argYul) ? Input::Yul : (m_args.count(g_argStrictAssembly) ? Input::StrictAssembly : Input::Assembly);
|
Input inputLanguage = m_args.count(g_argYul) ? Input::Yul : (m_args.count(g_argStrictAssembly) ? Input::StrictAssembly : Input::Assembly);
|
||||||
Machine targetMachine = Machine::EVM;
|
Machine targetMachine = Machine::EVM;
|
||||||
bool optimize = m_args.count(g_argOptimize);
|
bool optimize = m_args.count(g_argOptimize);
|
||||||
if (m_args.count(g_strOptimizeYul))
|
|
||||||
{
|
|
||||||
serr() << "--" << g_strOptimizeYul << " is invalid in assembly mode. Use --" << g_argOptimize << " instead." << endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_args.count(g_strNoOptimizeYul))
|
|
||||||
{
|
|
||||||
serr() << "--" << g_strNoOptimizeYul << " is invalid in assembly mode. Optimization is disabled by default and can be enabled with --" << g_argOptimize << "." << endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
optional<string> yulOptimiserSteps;
|
optional<string> yulOptimiserSteps;
|
||||||
if (m_args.count(g_strYulOptimizations))
|
if (m_args.count(g_strYulOptimizations))
|
||||||
@ -1273,6 +1301,13 @@ bool CommandLineInterface::processInput()
|
|||||||
|
|
||||||
return assemble(inputLanguage, targetMachine, optimize, yulOptimiserSteps);
|
return assemble(inputLanguage, targetMachine, optimize, yulOptimiserSteps);
|
||||||
}
|
}
|
||||||
|
else if (countEnabledOptions({g_strYulDialect, g_argMachine}) >= 1)
|
||||||
|
{
|
||||||
|
serr() << "--" << g_strYulDialect << " and --" << g_argMachine << " ";
|
||||||
|
serr() << "are only valid in assembly mode." << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_args.count(g_argLink))
|
if (m_args.count(g_argLink))
|
||||||
{
|
{
|
||||||
// switch to linker mode
|
// switch to linker mode
|
||||||
@ -1879,4 +1914,21 @@ void CommandLineInterface::outputCompilationResults()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t CommandLineInterface::countEnabledOptions(vector<string> const& _optionNames) const
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
for (string const& _option: _optionNames)
|
||||||
|
count += m_args.count(_option);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
string CommandLineInterface::joinOptionNames(vector<string> const& _optionNames, string _separator)
|
||||||
|
{
|
||||||
|
return boost::algorithm::join(
|
||||||
|
_optionNames | boost::adaptors::transformed([](string const& _option){ return "--" + _option; }),
|
||||||
|
_separator
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,9 @@ private:
|
|||||||
/// @arg _json json string to be written
|
/// @arg _json json string to be written
|
||||||
void createJson(std::string const& _fileName, std::string const& _json);
|
void createJson(std::string const& _fileName, std::string const& _json);
|
||||||
|
|
||||||
|
size_t countEnabledOptions(std::vector<std::string> const& _optionNames) const;
|
||||||
|
static std::string joinOptionNames(std::vector<std::string> const& _optionNames, std::string _separator = ", ");
|
||||||
|
|
||||||
bool m_error = false; ///< If true, some error occurred.
|
bool m_error = false; ///< If true, some error occurred.
|
||||||
|
|
||||||
bool m_onlyAssemble = false;
|
bool m_onlyAssemble = false;
|
||||||
|
@ -58,10 +58,10 @@ int parseUnsignedInteger(string::iterator& _it, string::iterator _end)
|
|||||||
|
|
||||||
CommonSyntaxTest::CommonSyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion):
|
CommonSyntaxTest::CommonSyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion):
|
||||||
EVMVersionRestrictedTestCase(_filename),
|
EVMVersionRestrictedTestCase(_filename),
|
||||||
|
m_sources(m_reader.sources().sources),
|
||||||
|
m_expectations(parseExpectations(m_reader.stream())),
|
||||||
m_evmVersion(_evmVersion)
|
m_evmVersion(_evmVersion)
|
||||||
{
|
{
|
||||||
m_sources = m_reader.sources();
|
|
||||||
m_expectations = parseExpectations(m_reader.stream());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCase::TestResult CommonSyntaxTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
TestCase::TestResult CommonSyntaxTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
||||||
@ -94,12 +94,10 @@ void CommonSyntaxTest::printSource(ostream& _stream, string const& _linePrefix,
|
|||||||
if (m_sources.empty())
|
if (m_sources.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool outputSourceNames = true;
|
bool outputSourceNames = (m_sources.size() != 1 || !m_sources.begin()->first.empty());
|
||||||
if (m_sources.size() == 1 && m_sources.begin()->first.empty())
|
|
||||||
outputSourceNames = false;
|
|
||||||
|
|
||||||
if (_formatted)
|
for (auto const& [name, source]: m_sources)
|
||||||
for (auto const& [name, source]: m_sources)
|
if (_formatted)
|
||||||
{
|
{
|
||||||
if (source.empty())
|
if (source.empty())
|
||||||
continue;
|
continue;
|
||||||
@ -139,8 +137,7 @@ void CommonSyntaxTest::printSource(ostream& _stream, string const& _linePrefix,
|
|||||||
}
|
}
|
||||||
_stream << formatting::RESET;
|
_stream << formatting::RESET;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
for (auto const& [name, source]: m_sources)
|
|
||||||
{
|
{
|
||||||
if (outputSourceNames)
|
if (outputSourceNames)
|
||||||
_stream << _linePrefix << "==== Source: " + name << " ====" << endl;
|
_stream << _linePrefix << "==== Source: " + name << " ====" << endl;
|
||||||
@ -165,8 +162,10 @@ void CommonSyntaxTest::printErrorList(
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
AnsiColorized scope(_stream, _formatted, {BOLD, (error.type == "Warning") ? YELLOW : RED});
|
AnsiColorized scope(_stream, _formatted, {BOLD, (error.type == "Warning") ? YELLOW : RED});
|
||||||
_stream << _linePrefix;
|
_stream << _linePrefix << error.type;
|
||||||
_stream << error.type << ": ";
|
if (error.errorId.has_value())
|
||||||
|
_stream << ' ' << error.errorId->error;
|
||||||
|
_stream << ": ";
|
||||||
}
|
}
|
||||||
if (!error.sourceName.empty() || error.locationStart >= 0 || error.locationEnd >= 0)
|
if (!error.sourceName.empty() || error.locationStart >= 0 || error.locationEnd >= 0)
|
||||||
{
|
{
|
||||||
@ -206,13 +205,17 @@ vector<SyntaxTestError> CommonSyntaxTest::parseExpectations(istream& _stream)
|
|||||||
if (it == line.end()) continue;
|
if (it == line.end()) continue;
|
||||||
|
|
||||||
auto typeBegin = it;
|
auto typeBegin = it;
|
||||||
while (it != line.end() && *it != ':')
|
while (it != line.end() && isalpha(*it))
|
||||||
++it;
|
++it;
|
||||||
string errorType(typeBegin, it);
|
string errorType(typeBegin, it);
|
||||||
|
|
||||||
// skip colon
|
skipWhitespace(it, line.end());
|
||||||
if (it != line.end()) it++;
|
|
||||||
|
|
||||||
|
optional<ErrorId> errorId;
|
||||||
|
if (it != line.end() && isdigit(*it))
|
||||||
|
errorId = ErrorId{static_cast<unsigned long long>(parseUnsignedInteger(it, line.end()))};
|
||||||
|
|
||||||
|
expect(it, line.end(), ':');
|
||||||
skipWhitespace(it, line.end());
|
skipWhitespace(it, line.end());
|
||||||
|
|
||||||
int locationStart = -1;
|
int locationStart = -1;
|
||||||
@ -242,6 +245,7 @@ vector<SyntaxTestError> CommonSyntaxTest::parseExpectations(istream& _stream)
|
|||||||
string errorMessage(it, line.end());
|
string errorMessage(it, line.end());
|
||||||
expectations.emplace_back(SyntaxTestError{
|
expectations.emplace_back(SyntaxTestError{
|
||||||
move(errorType),
|
move(errorType),
|
||||||
|
move(errorId),
|
||||||
move(errorMessage),
|
move(errorMessage),
|
||||||
move(sourceName),
|
move(sourceName),
|
||||||
locationStart,
|
locationStart,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user