Merge pull request #9339 from ethereum/develop

Merge develop into release for 0.6.11.
This commit is contained in:
chriseth 2020-07-07 15:34:38 +02:00 committed by GitHub
commit 5ef660b17a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2036 changed files with 7966 additions and 5084 deletions

View File

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

View File

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

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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:])

View File

@ -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
View 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}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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