mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9339 from ethereum/develop
Merge develop into release for 0.6.11.
This commit is contained in:
commit
5ef660b17a
@ -323,7 +323,7 @@ jobs:
|
||||
- checkout
|
||||
- run:
|
||||
name: Check for error codes
|
||||
command: ./scripts/fix_error_ids.py --check-only
|
||||
command: ./scripts/error_codes.py --check
|
||||
|
||||
chk_pylint:
|
||||
docker:
|
||||
|
12
.travis.yml
12
.travis.yml
@ -40,6 +40,8 @@ env:
|
||||
- ENCRYPTION_LABEL="6d4541b72666"
|
||||
- SOLC_BUILD_TYPE=RelWithDebInfo
|
||||
- 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_RELEASE=On
|
||||
- SOLC_TESTS=On
|
||||
@ -104,13 +106,13 @@ matrix:
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
node_js:
|
||||
- "8"
|
||||
- "10"
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- nvm install 8
|
||||
- nvm use 8
|
||||
- docker pull ethereum/solidity-buildpack-deps:emsdk-1.39.15-1
|
||||
- nvm install 10
|
||||
- nvm use 10
|
||||
- docker pull ethereum/solidity-buildpack-deps:emsdk-1.39.15-2
|
||||
env:
|
||||
- SOLC_EMSCRIPTEN=On
|
||||
- SOLC_INSTALL_DEPS_TRAVIS=Off
|
||||
@ -213,7 +215,7 @@ deploy:
|
||||
# scripts because TravisCI doesn't provide much in the way of conditional logic.
|
||||
|
||||
- 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
|
||||
on:
|
||||
branch:
|
||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# 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
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||
|
29
Changelog.md
29
Changelog.md
@ -1,3 +1,32 @@
|
||||
### 0.6.11 (2020-07-07)
|
||||
|
||||
Language Features:
|
||||
* General: Add unit denomination ``gwei``
|
||||
* Yul: Support ``linkersymbol`` builtin in standalone assembly mode to refer to library addresses.
|
||||
* Yul: Support using string literals exceeding 32 bytes as literal arguments for builtins.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* NatSpec: Add fields ``kind`` and ``version`` to the JSON output.
|
||||
* NatSpec: Inherit tags from unique base functions if derived function does not provide any.
|
||||
* Commandline Interface: Prevent some incompatible commandline options from being used together.
|
||||
* NatSpec: Support NatSpec comments on events.
|
||||
* Yul Optimizer: Store knowledge about storage / memory after ``a := sload(x)`` / ``a := mload(x)``.
|
||||
* SMTChecker: Support external calls to unknown code.
|
||||
* Source Maps: Also tag jumps into and out of Yul functions as jumps into and out of functions.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* NatSpec: Do not consider ``////`` and ``/***`` as NatSpec comments.
|
||||
* Type Checker: Disallow constructor parameters with ``calldata`` data location.
|
||||
* Type Checker: Do not disallow assigning to calldata variables.
|
||||
* Type Checker: Fix internal error related to ``using for`` applied to non-libraries.
|
||||
* Wasm backend: Fix code generation for for-loops with pre statements.
|
||||
* Wasm backend: Properly support both ``i32.drop`` and ``i64.drop``, and remove ``drop``.
|
||||
* Yul: Disallow the same variable to occur multiple times on the left-hand side of an assignment.
|
||||
* Yul: Fix source location of variable multi-assignment.
|
||||
|
||||
|
||||
### 0.6.10 (2020-06-11)
|
||||
|
||||
Important Bugfixes:
|
||||
|
@ -11,23 +11,11 @@ sourceUnit
|
||||
: (pragmaDirective | importDirective | structDefinition | enumDefinition | contractDefinition)* EOF ;
|
||||
|
||||
pragmaDirective
|
||||
: 'pragma' pragmaName pragmaValue ';' ;
|
||||
: 'pragma' pragmaName ( ~';' )* ';' ;
|
||||
|
||||
pragmaName
|
||||
: identifier ;
|
||||
|
||||
pragmaValue
|
||||
: version | expression ;
|
||||
|
||||
version
|
||||
: versionConstraint versionConstraint? ;
|
||||
|
||||
versionConstraint
|
||||
: versionOperator? VersionLiteral ;
|
||||
|
||||
versionOperator
|
||||
: '^' | '~' | '>=' | '>' | '<' | '<=' | '=' ;
|
||||
|
||||
importDirective
|
||||
: 'import' StringLiteralFragment ('as' identifier)? ';'
|
||||
| 'import' ('*' | identifier) ('as' identifier)? 'from' StringLiteralFragment ';'
|
||||
@ -371,10 +359,10 @@ subAssembly
|
||||
: 'assembly' identifier assemblyBlock ;
|
||||
|
||||
numberLiteral
|
||||
: (DecimalNumber | HexNumber) NumberUnit? ;
|
||||
: (DecimalNumber | HexNumber) (NumberUnit | Gwei)?;
|
||||
|
||||
identifier
|
||||
: ('from' | 'calldata' | 'address' | Identifier) ;
|
||||
: (Gwei | 'from' | 'calldata' | 'address' | Identifier) ;
|
||||
|
||||
BooleanLiteral
|
||||
: 'true' | 'false' ;
|
||||
@ -397,6 +385,8 @@ NumberUnit
|
||||
: 'wei' | 'szabo' | 'finney' | 'ether'
|
||||
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years' ;
|
||||
|
||||
Gwei: 'gwei' ;
|
||||
|
||||
HexLiteralFragment
|
||||
: 'hex' (('"' HexDigits? '"') | ('\'' HexDigits? '\'')) ;
|
||||
|
||||
@ -473,9 +463,6 @@ fragment
|
||||
SingleQuotedStringCharacter
|
||||
: ~['\r\n\\] | ('\\' .) ;
|
||||
|
||||
VersionLiteral
|
||||
: [0-9]+ ( '.' [0-9]+ ('.' [0-9]+)? )? ;
|
||||
|
||||
WS
|
||||
: [ \t\r\n\u000C]+ -> skip ;
|
||||
|
||||
|
@ -1109,6 +1109,10 @@
|
||||
"bugs": [],
|
||||
"released": "2020-06-11"
|
||||
},
|
||||
"0.6.11": {
|
||||
"bugs": [],
|
||||
"released": "2020-07-07"
|
||||
},
|
||||
"0.6.2": {
|
||||
"bugs": [
|
||||
"MissingEscapingInFormatting",
|
||||
|
@ -314,6 +314,16 @@ you should fork Solidity and add your personal fork as a second remote:
|
||||
|
||||
git remote add personal git@github.com:[username]/solidity.git
|
||||
|
||||
.. 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
|
||||
------------------
|
||||
|
||||
|
@ -42,10 +42,6 @@ The following example shows a contract and a function using all available tags.
|
||||
|
||||
.. 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
|
||||
public. You are welcome to use similar comments for your internal and
|
||||
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
|
||||
/// @dev All function calls are currently implemented without side effects
|
||||
contract Tree {
|
||||
/// @author Mary A. Botanist
|
||||
/// @notice Calculate tree age in years, rounded up, for live trees
|
||||
/// @dev The Alexandr N. Tetearing algorithm could increase precision
|
||||
/// @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
|
||||
=========== =============================================================================== =============================
|
||||
``@title`` A title that should describe the contract/interface contract, interface
|
||||
``@author`` The name of the author contract, interface, function
|
||||
``@notice`` Explain to an end user what this does contract, interface, function, public state variable
|
||||
``@dev`` Explain to a developer any extra details contract, interface, function, state variable
|
||||
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function
|
||||
``@author`` The name of the author contract, interface
|
||||
``@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, event
|
||||
``@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
|
||||
=========== =============================================================================== =============================
|
||||
|
||||
@ -127,9 +122,11 @@ documentation and you may read more at
|
||||
Inheritance Notes
|
||||
-----------------
|
||||
|
||||
Currently it is undefined whether a contract with a function having no
|
||||
NatSpec will inherit the NatSpec of a parent contract/interface for that
|
||||
same function.
|
||||
Functions without NatSpec will automatically inherit the documentation of their
|
||||
base function. Exceptions to this are:
|
||||
|
||||
* When the parameter names are different.
|
||||
* When there is more than one base function.
|
||||
|
||||
.. _header-output:
|
||||
|
||||
@ -193,7 +190,6 @@ file should also be produced and should look like this:
|
||||
{
|
||||
"age(uint256)" :
|
||||
{
|
||||
"author" : "Mary A. Botanist",
|
||||
"details" : "The Alexandr N. Tetearing algorithm could increase precision",
|
||||
"params" :
|
||||
{
|
||||
|
@ -7,11 +7,12 @@ Units and Globally Available Variables
|
||||
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 gwei == 1e9);
|
||||
assert(1 szabo == 1e12);
|
||||
assert(1 finney == 1e15);
|
||||
assert(1 ether == 1e18);
|
||||
|
40
docs/yul.rst
40
docs/yul.rst
@ -30,7 +30,7 @@ The design of Yul tries to achieve several goals:
|
||||
In order to achieve the first and second goal, Yul provides high-level constructs
|
||||
like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should
|
||||
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
|
||||
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
|
||||
@ -180,7 +180,11 @@ appropriate ``PUSHi`` instruction. In the following example,
|
||||
``3`` and ``2`` are added resulting in 5 and then the
|
||||
bitwise ``and`` with the string "abc" is computed.
|
||||
The final value is assigned to a local variable called ``x``.
|
||||
|
||||
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
|
||||
|
||||
@ -284,6 +288,8 @@ variables at the same time. For this, the number and types of the
|
||||
values have to match.
|
||||
If you want to assign the values returned from a function that has
|
||||
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
|
||||
|
||||
@ -502,6 +508,8 @@ In variable declarations and assignments, the right-hand-side expression
|
||||
variables on the left-hand-side.
|
||||
This is the only situation where an expression evaluating
|
||||
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
|
||||
evaluate to zero values.
|
||||
@ -904,7 +912,7 @@ In some internal dialects, there are additional functions:
|
||||
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.
|
||||
|
||||
``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
|
||||
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
|
||||
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
|
||||
``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:
|
||||
|
||||
|
@ -59,7 +59,7 @@ void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
|
||||
setData(data);
|
||||
}
|
||||
|
||||
unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
||||
size_t AssemblyItem::bytesRequired(size_t _addressLength) const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
@ -69,7 +69,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
||||
case PushString:
|
||||
return 1 + 32;
|
||||
case Push:
|
||||
return 1 + max<unsigned>(1, util::bytesRequired(data()));
|
||||
return 1 + max<size_t>(1, util::bytesRequired(data()));
|
||||
case PushSubSize:
|
||||
case PushProgramSize:
|
||||
return 1 + 4; // worst case: a 16MB program
|
||||
|
@ -133,7 +133,7 @@ public:
|
||||
|
||||
/// @returns an upper bound for the number of bytes required by this item, assuming that
|
||||
/// 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 returnValues() const;
|
||||
size_t deposit() const { return returnValues() - arguments(); }
|
||||
|
@ -107,7 +107,7 @@ tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
|
||||
using size_type = string::size_type;
|
||||
using diff_type = string::difference_type;
|
||||
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;
|
||||
if (searchPosition == 0)
|
||||
lineStart = 0;
|
||||
|
@ -38,6 +38,7 @@ class Error;
|
||||
using ErrorList = std::vector<std::shared_ptr<Error const>>;
|
||||
|
||||
struct CompilerError: virtual util::Exception {};
|
||||
struct StackTooDeepError: virtual CompilerError {};
|
||||
struct InternalCompilerError: virtual util::Exception {};
|
||||
struct FatalError: 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.
|
||||
* Suffix _error helps to find them in the sources.
|
||||
* 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.
|
||||
*/
|
||||
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 }; }
|
||||
|
||||
class Error: virtual public util::Exception
|
||||
|
@ -179,7 +179,7 @@ bool Scanner::scanHexByte(char& o_scannedByte)
|
||||
rollback(i);
|
||||
return false;
|
||||
}
|
||||
x = x * 16 + d;
|
||||
x = static_cast<char>(x * 16 + d);
|
||||
advance();
|
||||
}
|
||||
o_scannedByte = x;
|
||||
@ -197,7 +197,7 @@ std::optional<unsigned> Scanner::scanUnicode()
|
||||
rollback(i);
|
||||
return {};
|
||||
}
|
||||
x = x * 16 + static_cast<size_t>(d);
|
||||
x = x * 16 + static_cast<unsigned>(d);
|
||||
advance();
|
||||
}
|
||||
return x;
|
||||
@ -313,7 +313,6 @@ size_t Scanner::scanSingleLineDocComment()
|
||||
{
|
||||
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
|
||||
size_t endPosition = m_source->position();
|
||||
advance(); //consume the last '/' at ///
|
||||
|
||||
skipWhitespaceExceptUnicodeLinebreak();
|
||||
|
||||
@ -332,6 +331,8 @@ size_t Scanner::scanSingleLineDocComment()
|
||||
m_source->get(1) == '/' &&
|
||||
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);
|
||||
if (atEndOfLine())
|
||||
continue;
|
||||
@ -353,7 +354,6 @@ size_t Scanner::scanSingleLineDocComment()
|
||||
|
||||
Token Scanner::skipMultiLineComment()
|
||||
{
|
||||
advance();
|
||||
while (!isSourcePastEndOfInput())
|
||||
{
|
||||
char ch = m_char;
|
||||
@ -437,6 +437,11 @@ Token Scanner::scanSlash()
|
||||
return Token::Whitespace;
|
||||
else if (m_char == '/')
|
||||
{
|
||||
advance(); //consume the last '/' at ///
|
||||
|
||||
// "////"
|
||||
if (m_char == '/')
|
||||
return skipSingleLineComment();
|
||||
// doxygen style /// comment
|
||||
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||
m_skippedComments[NextNext].location.source = m_source;
|
||||
@ -462,11 +467,14 @@ Token Scanner::scanSlash()
|
||||
advance(); //skip the closing slash
|
||||
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
|
||||
Token comment;
|
||||
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||
m_skippedComments[NextNext].location.source = m_source;
|
||||
comment = scanMultiLineDocComment();
|
||||
Token comment = scanMultiLineDocComment();
|
||||
m_skippedComments[NextNext].location.end = static_cast<int>(sourcePos());
|
||||
m_skippedComments[NextNext].token = comment;
|
||||
if (comment == Token::Illegal)
|
||||
@ -754,17 +762,16 @@ bool Scanner::isUnicodeLinebreak()
|
||||
if (0x0a <= m_char && m_char <= 0x0d)
|
||||
// line feed, vertical tab, form feed, carriage return
|
||||
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
|
||||
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
|
||||
))
|
||||
// LS - U+2028, E2 80 A8 in utf8
|
||||
// PS - U+2029, E2 80 A9 in utf8
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Token Scanner::scanString()
|
||||
|
@ -232,6 +232,7 @@ namespace solidity::langutil
|
||||
\
|
||||
/* Identifiers (not keywords or future reserved words). */ \
|
||||
T(Identifier, nullptr, 0) \
|
||||
T(SubGwei, "gwei", 0) \
|
||||
\
|
||||
/* Keywords reserved for future use. */ \
|
||||
K(After, "after", 0) \
|
||||
|
@ -48,9 +48,9 @@ public:
|
||||
|
||||
// Z3 "basic resources" limit.
|
||||
// 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.
|
||||
static int const resourceLimit = 40000000;
|
||||
static int const resourceLimit = 20000000;
|
||||
|
||||
private:
|
||||
void declareFunction(std::string const& _name, Sort const& _sort);
|
||||
|
@ -151,13 +151,13 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
|
||||
if constexpr (is_same_v<T, FunctionDefinition const*>)
|
||||
{
|
||||
error = 1686_error;
|
||||
message = "Function with same name and arguments defined twice.";
|
||||
message = "Function with same name and parameter types defined twice.";
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(is_same_v<T, EventDefinition const*>, "Expected \"FunctionDefinition const*\" or \"EventDefinition const*\"");
|
||||
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);
|
||||
|
@ -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)
|
||||
{
|
||||
auto watcher = m_errorReporter.errorWatcher();
|
||||
|
@ -58,6 +58,7 @@ private:
|
||||
void endVisit(ArrayTypeName const& _typeName) override;
|
||||
void endVisit(VariableDeclaration const& _variable) override;
|
||||
bool visit(StructDefinition const& _struct) override;
|
||||
void endVisit(UsingForDirective const& _usingForDirective) override;
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
|
@ -32,6 +32,33 @@ using namespace solidity;
|
||||
using namespace solidity::langutil;
|
||||
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)
|
||||
{
|
||||
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. "
|
||||
"It will be disallowed in 0.7.0."
|
||||
);
|
||||
|
||||
if (_variable.annotation().docTags.empty())
|
||||
copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -142,6 +172,20 @@ void DocStringAnalyser::handleCallable(
|
||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
||||
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(
|
||||
|
@ -123,11 +123,13 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode)
|
||||
bool NameAndTypeResolver::resolveNamesAndTypes(SourceUnit& _source)
|
||||
{
|
||||
try
|
||||
{
|
||||
return resolveNamesAndTypesInternal(_node, _resolveInsideCode);
|
||||
for (shared_ptr<ASTNode> const& node: _source.nodes())
|
||||
if (!resolveNamesAndTypesInternal(*node, true))
|
||||
return false;
|
||||
}
|
||||
catch (langutil::FatalError const&)
|
||||
{
|
||||
@ -135,6 +137,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi
|
||||
throw; // Something is weird here, rather throw again.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
|
||||
@ -227,13 +230,14 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
|
||||
bool success = true;
|
||||
setScope(contract->scope());
|
||||
solAssert(!!m_currentScope, "");
|
||||
solAssert(_resolveInsideCode, "");
|
||||
|
||||
m_globalContext.setCurrentContract(*contract);
|
||||
updateDeclaration(*m_globalContext.currentSuper());
|
||||
updateDeclaration(*m_globalContext.currentThis());
|
||||
|
||||
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
|
||||
if (!resolveNamesAndTypes(*baseContract, true))
|
||||
if (!resolveNamesAndTypesInternal(*baseContract, true))
|
||||
success = false;
|
||||
|
||||
setScope(contract);
|
||||
@ -254,23 +258,20 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
|
||||
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
||||
{
|
||||
setScope(contract);
|
||||
if (!resolveNamesAndTypes(*node, false))
|
||||
if (!resolveNamesAndTypesInternal(*node, false))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
if (!_resolveInsideCode)
|
||||
return success;
|
||||
|
||||
setScope(contract);
|
||||
|
||||
// now resolve references inside the code
|
||||
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
||||
{
|
||||
setScope(contract);
|
||||
if (!resolveNamesAndTypes(*node, true))
|
||||
if (!resolveNamesAndTypesInternal(*node, true))
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
|
@ -65,12 +65,9 @@ public:
|
||||
bool registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope = nullptr);
|
||||
/// Applies the effect of import directives.
|
||||
bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits);
|
||||
/// Resolves all names and types referenced from the given AST 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.
|
||||
/// Resolves all names and types referenced from the given Source Node.
|
||||
/// @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
|
||||
/// that create their own scope.
|
||||
/// @returns false in case of error.
|
||||
|
@ -514,7 +514,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
||||
);
|
||||
|
||||
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())
|
||||
overrideError(
|
||||
@ -536,7 +542,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
||||
if (_overriding.isVariable())
|
||||
{
|
||||
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, "");
|
||||
}
|
||||
else if (_overriding.visibility() != _super.visibility())
|
||||
@ -547,7 +559,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
||||
_super.visibility() == Visibility::External &&
|
||||
_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())
|
||||
@ -558,7 +576,13 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
||||
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
|
||||
|
||||
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.
|
||||
if (_overriding.isFunction())
|
||||
|
@ -158,11 +158,22 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
|
||||
else if (_variable.isStateVariable())
|
||||
{
|
||||
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(
|
||||
3408_error,
|
||||
_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 "
|
||||
"in small quantities per transaction."
|
||||
);
|
||||
@ -339,30 +350,43 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
|
||||
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())
|
||||
{
|
||||
case Type::Category::Array:
|
||||
{
|
||||
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:
|
||||
{
|
||||
auto const& t = dynamic_cast<StructType const&>(_type);
|
||||
bigint size = 1;
|
||||
if (!_structsSeen.count(&t.structDefinition()))
|
||||
{
|
||||
_structsSeen.insert(&t.structDefinition());
|
||||
for (auto const& m: t.members(nullptr))
|
||||
size += structureSizeEstimate(*m.type, _structsSeen);
|
||||
}
|
||||
if (_structsSeen.count(&t.structDefinition()))
|
||||
return size;
|
||||
_structsSeen.insert(&t.structDefinition());
|
||||
for (auto const& m: t.members(nullptr))
|
||||
size += structureSizeEstimate(*m.type, _structsSeen, _oversizedSubTypes);
|
||||
_structsSeen.erase(&t.structDefinition());
|
||||
return size;
|
||||
}
|
||||
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:
|
||||
break;
|
||||
|
@ -73,8 +73,22 @@ private:
|
||||
bool visit(BinaryOperation const& _operation) 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.
|
||||
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;
|
||||
|
||||
|
@ -62,9 +62,11 @@ bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibrar
|
||||
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());
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
|
||||
@ -373,7 +366,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
}
|
||||
if (
|
||||
_function.isPublic() &&
|
||||
!_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
||||
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
|
||||
!typeSupportedByOldABIEncoder(*type(var), _function.libraryFunction())
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
@ -511,7 +504,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
else if (_variable.visibility() >= Visibility::Public)
|
||||
{
|
||||
FunctionType getter(_variable);
|
||||
if (!_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
|
||||
if (!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
|
||||
{
|
||||
vector<string> unsupportedTypes;
|
||||
for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes())
|
||||
@ -622,7 +615,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
|
||||
if (!type(*var)->interfaceType(false))
|
||||
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
||||
if (
|
||||
!_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
|
||||
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
|
||||
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
@ -666,17 +659,18 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
|
||||
if (ref == _inlineAssembly.annotation().externalReferences.end())
|
||||
return numeric_limits<size_t>::max();
|
||||
Declaration const* declaration = ref->second.declaration;
|
||||
return false;
|
||||
InlineAssemblyAnnotation::ExternalIdentifierInfo& identifierInfo = ref->second;
|
||||
Declaration const* declaration = identifierInfo.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))
|
||||
{
|
||||
solAssert(var->type(), "Expected variable type!");
|
||||
if (var->immutable())
|
||||
{
|
||||
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())
|
||||
{
|
||||
@ -685,17 +679,17 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (var && !var->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)
|
||||
{
|
||||
m_errorReporter.typeError(6252_error, _identifier.location, "Constant variables cannot be assigned to.");
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
else if (requiresStorage)
|
||||
{
|
||||
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()))
|
||||
{
|
||||
@ -704,7 +698,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
_identifier.location,
|
||||
"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() || (
|
||||
!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.");
|
||||
return size_t(-1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -723,33 +717,33 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
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.");
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
else if (_context == yul::IdentifierContext::LValue)
|
||||
{
|
||||
if (var->isStateVariable())
|
||||
{
|
||||
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.");
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
solAssert(ref->second.isSlot, "");
|
||||
solAssert(identifierInfo.isSlot, "");
|
||||
}
|
||||
}
|
||||
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.");
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
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.");
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
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.");
|
||||
else
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.");
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_context == yul::IdentifierContext::RValue)
|
||||
@ -780,7 +774,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (dynamic_cast<FunctionDefinition const*>(declaration))
|
||||
{
|
||||
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))
|
||||
{
|
||||
@ -790,14 +784,14 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (!contract->isLibrary())
|
||||
{
|
||||
m_errorReporter.typeError(4977_error, _identifier.location, "Expected a library.");
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
return numeric_limits<size_t>::max();
|
||||
return false;
|
||||
}
|
||||
ref->second.valueSize = 1;
|
||||
return size_t(1);
|
||||
identifierInfo.valueSize = 1;
|
||||
return true;
|
||||
};
|
||||
solAssert(!_inlineAssembly.annotation().analysisInfo, "");
|
||||
_inlineAssembly.annotation().analysisInfo = make_shared<yul::AsmAnalysisInfo>();
|
||||
@ -1342,7 +1336,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
|
||||
_conditional.location(),
|
||||
"True expression's type " +
|
||||
trueType->toString() +
|
||||
" doesn't match false expression's type " +
|
||||
" does not match false expression's type " +
|
||||
falseType->toString() +
|
||||
"."
|
||||
);
|
||||
@ -1912,9 +1906,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
|
||||
bool const isPacked = _functionType->kind() == FunctionType::Kind::ABIEncodePacked;
|
||||
solAssert(_functionType->padArguments() != isPacked, "ABI function with unexpected padding");
|
||||
|
||||
bool const abiEncoderV2 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(
|
||||
ExperimentalFeature::ABIEncoderV2
|
||||
);
|
||||
bool const abiEncoderV2 = experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2);
|
||||
|
||||
// Check for named arguments
|
||||
if (!_functionCall.names().empty())
|
||||
@ -2311,11 +2303,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
case FunctionType::Kind::ABIDecode:
|
||||
{
|
||||
bool const abiEncoderV2 =
|
||||
m_currentContract->sourceUnit().annotation().experimentalFeatures.count(
|
||||
ExperimentalFeature::ABIEncoderV2
|
||||
);
|
||||
returnTypes = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2);
|
||||
returnTypes = typeCheckABIDecodeAndRetrieveReturnType(
|
||||
_functionCall,
|
||||
experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::ABIEncode:
|
||||
@ -2713,6 +2704,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
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
|
||||
// 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))
|
||||
annotation.isPure = true;
|
||||
|
||||
else if (dynamic_cast<ModuleType const*>(annotation.type))
|
||||
annotation.isPure = true;
|
||||
|
||||
// Check for deprecated function names.
|
||||
// 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);
|
||||
}
|
||||
|
||||
bool TypeChecker::experimentalFeatureActive(ExperimentalFeature _feature) const
|
||||
{
|
||||
solAssert(m_currentSourceUnit, "");
|
||||
if (m_currentContract)
|
||||
solAssert(m_currentSourceUnit == &m_currentContract->sourceUnit(), "");
|
||||
return m_currentSourceUnit->annotation().experimentalFeatures.count(_feature);
|
||||
}
|
||||
|
@ -51,9 +51,9 @@ public:
|
||||
m_errorReporter(_errorReporter)
|
||||
{}
|
||||
|
||||
/// 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
|
||||
bool checkTypeRequirements(ASTNode const& _contract);
|
||||
bool checkTypeRequirements(SourceUnit const& _source);
|
||||
|
||||
/// @returns the type of an expression and asserts that it is present.
|
||||
TypePointer const& type(Expression const& _expression) const;
|
||||
@ -111,7 +111,6 @@ private:
|
||||
);
|
||||
|
||||
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
||||
void endVisit(UsingForDirective const& _usingFor) override;
|
||||
void endVisit(ModifierDefinition const& _modifier) override;
|
||||
bool visit(FunctionDefinition const& _function) 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.
|
||||
void requireLValue(Expression const& _expression, bool _ordinaryAssignment);
|
||||
|
||||
bool experimentalFeatureActive(ExperimentalFeature _feature) const;
|
||||
|
||||
SourceUnit const* m_currentSourceUnit = nullptr;
|
||||
ContractDefinition const* m_currentContract = nullptr;
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
|
@ -492,12 +492,7 @@ DeclarationAnnotation& Declaration::annotation() const
|
||||
bool VariableDeclaration::isLValue() const
|
||||
{
|
||||
// Constant declared variables are Read-Only
|
||||
if (isConstant())
|
||||
return false;
|
||||
// External function arguments of reference type are Read-Only
|
||||
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
||||
return false;
|
||||
return true;
|
||||
return !isConstant();
|
||||
}
|
||||
|
||||
bool VariableDeclaration::isLocalVariable() const
|
||||
@ -593,6 +588,15 @@ bool VariableDeclaration::isInternalCallableParameter() const
|
||||
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
|
||||
{
|
||||
if (!isCallableOrCatchParameter())
|
||||
@ -627,7 +631,7 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
|
||||
set<Location> locations{ Location::Memory };
|
||||
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
|
||||
locations.insert(Location::Storage);
|
||||
if (!isTryCatchParameter())
|
||||
if (!isTryCatchParameter() && !isConstructorParameter())
|
||||
locations.insert(Location::CallData);
|
||||
|
||||
return locations;
|
||||
|
@ -928,6 +928,7 @@ public:
|
||||
/// @returns true if this variable is a parameter or return parameter of an internal function
|
||||
/// or a function type of internal visibility.
|
||||
bool isInternalCallableParameter() const;
|
||||
bool isConstructorParameter() const;
|
||||
/// @returns true iff this variable is a parameter(or return parameter of a library function
|
||||
bool isLibraryFunctionParameter() const;
|
||||
/// @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),
|
||||
Wei = static_cast<int>(Token::SubWei),
|
||||
Gwei = static_cast<int>(Token::SubGwei),
|
||||
Szabo = static_cast<int>(Token::SubSzabo),
|
||||
Finney = static_cast<int>(Token::SubFinney),
|
||||
Ether = static_cast<int>(Token::SubEther),
|
||||
|
@ -1001,6 +1001,8 @@ Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _no
|
||||
|
||||
if (subDenStr == "wei")
|
||||
return Literal::SubDenomination::Wei;
|
||||
else if (subDenStr == "gwei")
|
||||
return Literal::SubDenomination::Gwei;
|
||||
else if (subDenStr == "szabo")
|
||||
return Literal::SubDenomination::Szabo;
|
||||
else if (subDenStr == "finney")
|
||||
|
@ -311,10 +311,15 @@ TypePointer Type::commonType(Type const* _a, Type const* _b)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MemberList const& Type::members(ContractDefinition const* _currentScope) const
|
||||
MemberList const& Type::members(ASTNode const* _currentScope) const
|
||||
{
|
||||
if (!m_members[_currentScope])
|
||||
{
|
||||
solAssert(
|
||||
_currentScope == nullptr ||
|
||||
dynamic_cast<SourceUnit const*>(_currentScope) ||
|
||||
dynamic_cast<ContractDefinition const*>(_currentScope),
|
||||
"");
|
||||
MemberList::MemberMap members = nativeMembers(_currentScope);
|
||||
if (_currentScope)
|
||||
members += boundFunctions(*this, *_currentScope);
|
||||
@ -344,8 +349,20 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
|
||||
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.
|
||||
DataLocation typeLocation = DataLocation::Storage;
|
||||
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
||||
@ -353,38 +370,39 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
|
||||
|
||||
set<Declaration const*> seenFunctions;
|
||||
MemberList::MemberMap members;
|
||||
for (ContractDefinition const* contract: _scope.annotation().linearizedBaseContracts)
|
||||
for (UsingForDirective const* ufd: contract->usingForDirectives())
|
||||
{
|
||||
// Convert both types to pointers for comparison to see if the `using for`
|
||||
// directive applies.
|
||||
// Further down, we check more detailed for each function if `_type` is
|
||||
// convertible to the function parameter type.
|
||||
if (ufd->typeName() &&
|
||||
*TypeProvider::withLocationIfReference(typeLocation, &_type, true) !=
|
||||
*TypeProvider::withLocationIfReference(
|
||||
typeLocation,
|
||||
ufd->typeName()->annotation().type,
|
||||
true
|
||||
)
|
||||
|
||||
for (UsingForDirective const* ufd: usingForDirectives)
|
||||
{
|
||||
// Convert both types to pointers for comparison to see if the `using for`
|
||||
// directive applies.
|
||||
// Further down, we check more detailed for each function if `_type` is
|
||||
// convertible to the function parameter type.
|
||||
if (ufd->typeName() &&
|
||||
*TypeProvider::withLocationIfReference(typeLocation, &_type, true) !=
|
||||
*TypeProvider::withLocationIfReference(
|
||||
typeLocation,
|
||||
ufd->typeName()->annotation().type,
|
||||
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;
|
||||
auto const& library = dynamic_cast<ContractDefinition const&>(
|
||||
*ufd->libraryName().annotation().referencedDeclaration
|
||||
);
|
||||
for (FunctionDefinition const* function: library.definedFunctions())
|
||||
{
|
||||
if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function))
|
||||
continue;
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -464,7 +482,7 @@ bool AddressType::operator==(Type const& _other) const
|
||||
return other.m_stateMutability == m_stateMutability;
|
||||
}
|
||||
|
||||
MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) const
|
||||
MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const
|
||||
{
|
||||
MemberList::MemberMap members = {
|
||||
{"balance", TypeProvider::uint256()},
|
||||
@ -871,6 +889,9 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
|
||||
case Literal::SubDenomination::Wei:
|
||||
case Literal::SubDenomination::Second:
|
||||
break;
|
||||
case Literal::SubDenomination::Gwei:
|
||||
value *= bigint("1000000000");
|
||||
break;
|
||||
case Literal::SubDenomination::Szabo:
|
||||
value *= bigint("1000000000000");
|
||||
break;
|
||||
@ -1400,7 +1421,7 @@ TypeResult FixedBytesType::binaryOperatorResult(Token _operator, Type const* _ot
|
||||
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)}};
|
||||
}
|
||||
@ -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;
|
||||
if (!isString())
|
||||
@ -2029,10 +2050,9 @@ string ContractType::canonicalName() const
|
||||
return m_contract.annotation().canonicalName;
|
||||
}
|
||||
|
||||
MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _contract) const
|
||||
MemberList::MemberMap ContractType::nativeMembers(ASTNode const*) const
|
||||
{
|
||||
MemberList::MemberMap members;
|
||||
solAssert(_contract, "");
|
||||
if (m_super)
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
for (auto const& it: m_contract.interfaceFunctions())
|
||||
members.emplace_back(
|
||||
it.second->declaration().name(),
|
||||
it.second->asExternallyCallableFunction(m_contract.isLibrary()),
|
||||
&it.second->declaration()
|
||||
);
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
@ -2241,7 +2260,7 @@ string StructType::toString(bool _short) const
|
||||
return ret;
|
||||
}
|
||||
|
||||
MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
||||
MemberList::MemberMap StructType::nativeMembers(ASTNode const*) const
|
||||
{
|
||||
MemberList::MemberMap 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)
|
||||
{
|
||||
@ -3165,7 +3184,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
|
||||
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)}};
|
||||
}
|
||||
else
|
||||
@ -3640,13 +3660,14 @@ vector<tuple<string, TypePointer>> TypeType::makeStackItems() const
|
||||
return {};
|
||||
}
|
||||
|
||||
MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _currentScope) const
|
||||
MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) const
|
||||
{
|
||||
MemberList::MemberMap members;
|
||||
if (m_actualType->category() == Category::Contract)
|
||||
{
|
||||
auto const* contractScope = dynamic_cast<ContractDefinition const*>(_currentScope);
|
||||
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())
|
||||
{
|
||||
@ -3748,7 +3769,7 @@ bool ModuleType::operator==(Type const& _other) const
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
||||
MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
|
||||
{
|
||||
switch (m_kind)
|
||||
{
|
||||
|
@ -315,9 +315,9 @@ public:
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
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);
|
||||
}
|
||||
@ -361,12 +361,12 @@ public:
|
||||
|
||||
private:
|
||||
/// @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:
|
||||
/// @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.
|
||||
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* /*_currentScope*/) const
|
||||
virtual MemberList::MemberMap nativeMembers(ASTNode const* /*_currentScope*/) const
|
||||
{
|
||||
return MemberList::MemberMap();
|
||||
}
|
||||
@ -379,7 +379,7 @@ protected:
|
||||
|
||||
|
||||
/// 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<size_t> m_stackSize;
|
||||
};
|
||||
@ -408,7 +408,7 @@ public:
|
||||
bool isValueType() 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 canonicalName() const override;
|
||||
@ -649,7 +649,7 @@ public:
|
||||
bool nameable() const override { return true; }
|
||||
|
||||
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; }
|
||||
TypeResult interfaceType(bool) const override { return this; }
|
||||
|
||||
@ -786,7 +786,7 @@ public:
|
||||
std::string toString(bool _short) const override;
|
||||
std::string canonicalName() 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 decodingType() const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
@ -889,7 +889,7 @@ public:
|
||||
std::string toString(bool _short) 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;
|
||||
|
||||
@ -949,7 +949,7 @@ public:
|
||||
bool nameable() const override { return true; }
|
||||
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;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
@ -1220,7 +1220,7 @@ public:
|
||||
bool nameable() const override;
|
||||
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||
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;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
|
||||
@ -1389,7 +1389,7 @@ public:
|
||||
bool canLiveOutsideStorage() const override { return false; }
|
||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
|
||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
|
||||
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
protected:
|
||||
@ -1441,7 +1441,7 @@ public:
|
||||
bool canBeStored() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
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;
|
||||
|
||||
@ -1481,7 +1481,7 @@ public:
|
||||
bool canBeStored() const override { return false; }
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
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;
|
||||
|
||||
|
@ -226,8 +226,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
else
|
||||
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>...
|
||||
solAssert(
|
||||
assertThrow(
|
||||
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep, try removing local variables."
|
||||
);
|
||||
// fetch target storage reference
|
||||
|
@ -146,7 +146,7 @@ void CompilerContext::callYulFunction(
|
||||
m_externallyUsedYulFunctions.insert(_name);
|
||||
auto const retTag = pushNewTag();
|
||||
CompilerUtils(*this).moveIntoStack(_inArgs);
|
||||
appendJumpTo(namedTag(_name));
|
||||
appendJumpTo(namedTag(_name), evmasm::AssemblyItem::JumpType::IntoFunction);
|
||||
adjustStackOffset(static_cast<int>(_outArgs) - 1 - static_cast<int>(_inArgs));
|
||||
*this << retTag.tag();
|
||||
}
|
||||
@ -384,12 +384,11 @@ void CompilerContext::appendInlineAssembly(
|
||||
yul::Identifier const& _identifier,
|
||||
yul::IdentifierContext,
|
||||
bool _insideFunction
|
||||
) -> size_t
|
||||
) -> bool
|
||||
{
|
||||
if (_insideFunction)
|
||||
return numeric_limits<size_t>::max();
|
||||
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name.str());
|
||||
return it == _localVariables.end() ? numeric_limits<size_t>::max() : 1;
|
||||
return false;
|
||||
return contains(_localVariables, _identifier.name.str());
|
||||
};
|
||||
identifierAccess.generateCode = [&](
|
||||
yul::Identifier const& _identifier,
|
||||
@ -405,7 +404,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
stackDiff -= 1;
|
||||
if (stackDiff < 1 || stackDiff > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_identifier.location) <<
|
||||
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
|
||||
);
|
||||
|
@ -455,7 +455,11 @@ void CompilerUtils::encodeToMemory(
|
||||
// leave end_of_mem as dyn head pointer
|
||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||
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
|
||||
{
|
||||
@ -507,8 +511,9 @@ void CompilerUtils::encodeToMemory(
|
||||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
||||
{
|
||||
// copy tail pointer (=mem_end - mem_start) to memory
|
||||
solAssert(
|
||||
assertThrow(
|
||||
(2 + dynPointers) <= 16,
|
||||
StackTooDeepError,
|
||||
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
|
||||
);
|
||||
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
|
||||
if (stackPosition - size + 1 > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_variable.location()) <<
|
||||
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)
|
||||
{
|
||||
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)
|
||||
m_context << dupInstruction(_stackDepth);
|
||||
}
|
||||
@ -1322,14 +1331,22 @@ void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize)
|
||||
|
||||
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)
|
||||
m_context << swapInstruction(_items - i);
|
||||
}
|
||||
|
||||
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)
|
||||
m_context << swapInstruction(i);
|
||||
}
|
||||
|
@ -634,7 +634,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
|
||||
|
||||
if (stackLayout.size() > 17)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_function.location()) <<
|
||||
errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
@ -798,7 +798,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
solAssert(variable->type()->sizeOnStack() == 1, "");
|
||||
if (stackDiff < 1 || stackDiff > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||
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;
|
||||
if (stackDiff > 16 || stackDiff < 1)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
|
||||
);
|
||||
|
@ -226,7 +226,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
||||
solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), "");
|
||||
if (retSizeOnStack > 15)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_varDecl.location()) <<
|
||||
errinfo_comment("Stack too deep.")
|
||||
);
|
||||
@ -308,7 +308,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||
{
|
||||
if (itemSize + lvalueSize > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_assignment.location()) <<
|
||||
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.");
|
||||
break;
|
||||
}
|
||||
case Type::Category::Module:
|
||||
{
|
||||
Type::Category category = _memberAccess.annotation().type->category();
|
||||
solAssert(
|
||||
category == Type::Category::TypeType ||
|
||||
category == Type::Category::Module,
|
||||
""
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solAssert(false, "Member access to unknown type.");
|
||||
}
|
||||
@ -1933,6 +1943,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else if (dynamic_cast<ImportDirective const*>(declaration))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(false, "Identifier type not expected in expression context.");
|
||||
|
@ -47,7 +47,7 @@ void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
|
||||
unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
|
||||
if (stackPos + 1 > 16) //@todo correct this by fetching earlier or moving to memory
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_location) <<
|
||||
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;
|
||||
if (stackDiff > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
CompilerError() <<
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_location) <<
|
||||
errinfo_comment("Stack too deep, try removing local variables.")
|
||||
);
|
||||
|
@ -1646,13 +1646,30 @@ string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType cons
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
|
||||
string YulUtilFunctions::allocateMemoryStructFunction(StructType const& _type)
|
||||
{
|
||||
string functionName = "allocate_and_initialize_memory_struct_" + _type.identifier();
|
||||
string functionName = "allocate_memory_struct_" + _type.identifier();
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>() -> memPtr {
|
||||
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
|
||||
<#member>
|
||||
mstore(offset, <zeroValue>())
|
||||
@ -1661,10 +1678,9 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("alloc", allocationFunction());
|
||||
templ("allocStruct", allocateMemoryStructFunction(_type));
|
||||
|
||||
TypePointers const& members = _type.memoryMemberTypes();
|
||||
templ("allocSize", _type.memoryDataSize().str());
|
||||
|
||||
vector<map<string, string>> memberParams(members.size());
|
||||
for (size_t i = 0; i < members.size(); ++i)
|
||||
|
@ -286,8 +286,13 @@ public:
|
||||
/// signature: (length) -> memPtr
|
||||
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.
|
||||
/// signature: (members) -> memPtr
|
||||
/// signature: () -> memPtr
|
||||
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
|
||||
|
||||
/// @returns the name of the function that converts a value of type @a _from
|
||||
|
@ -600,22 +600,30 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
|
||||
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
{
|
||||
solUnimplementedAssert(
|
||||
_functionCall.annotation().kind == FunctionCallKind::FunctionCall ||
|
||||
_functionCall.annotation().kind == FunctionCallKind::TypeConversion,
|
||||
_functionCall.annotation().kind != FunctionCallKind::Unset,
|
||||
"This type of function call is not yet implemented"
|
||||
);
|
||||
|
||||
Type const& funcType = type(_functionCall.expression());
|
||||
|
||||
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");
|
||||
define(_functionCall, *_functionCall.arguments().front());
|
||||
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();
|
||||
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))]);
|
||||
}
|
||||
|
||||
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());
|
||||
if (memberAccess)
|
||||
{
|
||||
|
@ -197,8 +197,11 @@ void CHC::endVisit(ContractDefinition const& _contract)
|
||||
|
||||
bool CHC::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (!shouldVisit(_function))
|
||||
if (!_function.isImplemented())
|
||||
{
|
||||
connectBlocks(genesis(), summary(_function));
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is the case for base constructor inlining.
|
||||
if (m_currentFunction)
|
||||
@ -243,7 +246,7 @@ bool CHC::visit(FunctionDefinition const& _function)
|
||||
|
||||
void CHC::endVisit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (!shouldVisit(_function))
|
||||
if (!_function.isImplemented())
|
||||
return;
|
||||
|
||||
// This is the case for base constructor inlining.
|
||||
@ -474,11 +477,14 @@ void CHC::endVisit(FunctionCall const& _funCall)
|
||||
internalFunctionCall(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::External:
|
||||
case FunctionType::Kind::BareStaticCall:
|
||||
externalFunctionCall(_funCall);
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::DelegateCall:
|
||||
case FunctionType::Kind::BareCall:
|
||||
case FunctionType::Kind::BareCallCode:
|
||||
case FunctionType::Kind::BareDelegateCall:
|
||||
case FunctionType::Kind::BareStaticCall:
|
||||
case FunctionType::Kind::Creation:
|
||||
case FunctionType::Kind::KECCAK256:
|
||||
case FunctionType::Kind::ECRecover:
|
||||
@ -574,6 +580,35 @@ void CHC::internalFunctionCall(FunctionCall const& _funCall)
|
||||
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&)
|
||||
{
|
||||
/// 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(
|
||||
smt::SymbolicFunctionVariable const& _block,
|
||||
vector<smtutil::Expression> const* _arguments
|
||||
@ -710,10 +740,14 @@ smtutil::SortPointer CHC::constructorSort()
|
||||
|
||||
smtutil::SortPointer CHC::interfaceSort()
|
||||
{
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
m_stateSorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
solAssert(m_currentContract, "");
|
||||
return interfaceSort(*m_currentContract);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::nondetInterfaceSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
return nondetInterfaceSort(*m_currentContract);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
@ -778,7 +821,12 @@ smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, Contr
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
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
|
||||
);
|
||||
}
|
||||
@ -802,11 +850,48 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||
{
|
||||
string suffix = base->name() + "_" + to_string(base->id());
|
||||
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
|
||||
m_nondetInterfaces[base] = createSymbolicBlock(nondetInterfaceSort(*base), "nondet_interface_" + suffix);
|
||||
|
||||
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
|
||||
if (!m_context.knownVariable(*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 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));
|
||||
|
||||
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()};
|
||||
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 += 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); });
|
||||
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)
|
||||
@ -893,13 +985,18 @@ vector<smtutil::Expression> CHC::initialStateVariables()
|
||||
return stateVariablesAtIndex(0);
|
||||
}
|
||||
|
||||
vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index)
|
||||
vector<smtutil::Expression> CHC::initialStateVariables(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
return applyMap(m_stateVariables, [&](auto _var) { return valueAtIndex(*_var, _index); });
|
||||
return stateVariablesAtIndex(0, _contract);
|
||||
}
|
||||
|
||||
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(
|
||||
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
@ -910,7 +1007,12 @@ vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, Contract
|
||||
vector<smtutil::Expression> CHC::currentStateVariables()
|
||||
{
|
||||
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()
|
||||
@ -978,22 +1080,28 @@ smtutil::Expression CHC::predicate(FunctionCall const& _funCall)
|
||||
m_error.increaseIndex();
|
||||
vector<smtutil::Expression> args{m_error.currentValue()};
|
||||
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);
|
||||
for (auto const& var: m_stateVariables)
|
||||
m_context.variable(*var)->increaseIndex();
|
||||
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||
if (!otherContract)
|
||||
for (auto const& var: m_stateVariables)
|
||||
m_context.variable(*var)->increaseIndex();
|
||||
args += otherContract ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||
|
||||
auto const& returnParams = function->returnParameters();
|
||||
for (auto param: returnParams)
|
||||
if (m_context.knownVariable(*param))
|
||||
m_context.variable(*param)->increaseIndex();
|
||||
for (auto var: function->parameters() + function->returnParameters())
|
||||
{
|
||||
if (m_context.knownVariable(*var))
|
||||
m_context.variable(*var)->increaseIndex();
|
||||
else
|
||||
createVariable(*param);
|
||||
args += applyMap(function->returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||
createVariable(*var);
|
||||
args.push_back(currentValue(*var));
|
||||
}
|
||||
|
||||
if (contract->isLibrary())
|
||||
if (otherContract)
|
||||
return (*m_summaries.at(contract).at(function))(args);
|
||||
|
||||
solAssert(m_currentContract, "");
|
||||
|
@ -77,6 +77,7 @@ private:
|
||||
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
void internalFunctionCall(FunctionCall const& _funCall);
|
||||
void externalFunctionCall(FunctionCall const& _funCall);
|
||||
void unknownFunctionCall(FunctionCall const& _funCall);
|
||||
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
|
||||
//@}
|
||||
@ -95,7 +96,6 @@ private:
|
||||
void resetContractAnalysis();
|
||||
void eraseKnowledge();
|
||||
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);
|
||||
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
|
||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
||||
@ -106,7 +106,9 @@ private:
|
||||
static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract);
|
||||
smtutil::SortPointer constructorSort();
|
||||
smtutil::SortPointer interfaceSort();
|
||||
smtutil::SortPointer nondetInterfaceSort();
|
||||
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||
static smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _const);
|
||||
smtutil::SortPointer arity0FunctionSort();
|
||||
smtutil::SortPointer sort(FunctionDefinition const& _function);
|
||||
smtutil::SortPointer sort(ASTNode const* _block);
|
||||
@ -149,10 +151,12 @@ private:
|
||||
/// @returns the symbolic values of the state variables at the beginning
|
||||
/// of the current transaction.
|
||||
std::vector<smtutil::Expression> initialStateVariables();
|
||||
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index);
|
||||
std::vector<smtutil::Expression> stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract);
|
||||
std::vector<smtutil::Expression> initialStateVariables(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.
|
||||
std::vector<smtutil::Expression> currentStateVariables();
|
||||
std::vector<smtutil::Expression> currentStateVariables(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns the current symbolic values of the current function's
|
||||
/// input and output parameters.
|
||||
@ -173,6 +177,7 @@ private:
|
||||
smtutil::Expression summary(ContractDefinition const& _contract);
|
||||
/// @returns a predicate that defines a function summary.
|
||||
smtutil::Expression summary(FunctionDefinition const& _function);
|
||||
smtutil::Expression summary(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||
//@}
|
||||
|
||||
/// Solver related.
|
||||
@ -212,6 +217,12 @@ private:
|
||||
/// Single entry block for all functions.
|
||||
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.
|
||||
/// Single error block for all assertions.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
||||
|
@ -32,12 +32,23 @@ EncodingContext::EncodingContext():
|
||||
void EncodingContext::reset()
|
||||
{
|
||||
resetAllVariables();
|
||||
resetSlackId();
|
||||
m_expressions.clear();
|
||||
m_globalContext.clear();
|
||||
m_state.reset();
|
||||
m_assertions.clear();
|
||||
}
|
||||
|
||||
void EncodingContext::resetSlackId()
|
||||
{
|
||||
m_nextSlackId = 0;
|
||||
}
|
||||
|
||||
unsigned EncodingContext::newSlackId()
|
||||
{
|
||||
return m_nextSlackId++;
|
||||
}
|
||||
|
||||
void EncodingContext::clear()
|
||||
{
|
||||
m_variables.clear();
|
||||
|
@ -40,6 +40,10 @@ public:
|
||||
/// alive because of state variables and inlined function calls.
|
||||
/// To be used in the beginning of a root function visit.
|
||||
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.
|
||||
/// To be used before a model checking engine starts.
|
||||
void clear();
|
||||
@ -168,6 +172,9 @@ private:
|
||||
/// Whether to conjoin assertions in the assertion stack.
|
||||
bool m_accumulateAssertions = true;
|
||||
//@}
|
||||
|
||||
/// Fresh ids for slack variables to be created deterministically.
|
||||
unsigned m_nextSlackId = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1204,7 +1204,7 @@ pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
|
||||
smtutil::Expression const& _left,
|
||||
smtutil::Expression const& _right,
|
||||
TypePointer const& _commonType,
|
||||
Expression const&
|
||||
Expression const& _operation
|
||||
)
|
||||
{
|
||||
static set<Token> validOperators{
|
||||
@ -1227,39 +1227,66 @@ pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
|
||||
else
|
||||
intType = TypeProvider::uint256();
|
||||
|
||||
smtutil::Expression valueNoMod(
|
||||
_op == Token::Add ? _left + _right :
|
||||
_op == Token::Sub ? _left - _right :
|
||||
_op == Token::Div ? division(_left, _right, *intType) :
|
||||
_op == Token::Mul ? _left * _right :
|
||||
/*_op == Token::Mod*/ _left % _right
|
||||
);
|
||||
auto valueUnbounded = [&]() -> smtutil::Expression {
|
||||
switch (_op)
|
||||
{
|
||||
case Token::Add: return _left + _right;
|
||||
case Token::Sub: return _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)
|
||||
{
|
||||
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 symbMax = smt::maxValue(*intType);
|
||||
|
||||
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(
|
||||
valueNoMod > symbMax,
|
||||
valueNoMod % intValueRange,
|
||||
valueUnbounded > symbMax,
|
||||
wrap,
|
||||
smtutil::Expression::ite(
|
||||
valueNoMod < symbMin,
|
||||
valueNoMod % intValueRange,
|
||||
valueNoMod
|
||||
valueUnbounded < symbMin,
|
||||
wrap,
|
||||
valueUnbounded
|
||||
)
|
||||
);
|
||||
|
||||
if (intType->isSigned())
|
||||
value = smtutil::Expression::ite(
|
||||
value > symbMax,
|
||||
value - intValueRange,
|
||||
value
|
||||
);
|
||||
|
||||
return {value, valueNoMod};
|
||||
return {value, valueUnbounded};
|
||||
}
|
||||
|
||||
void SMTEncoder::compareOperation(BinaryOperation const& _op)
|
||||
|
@ -307,11 +307,6 @@ bool CompilerStack::analyze()
|
||||
if (source->ast && !syntaxChecker.checkSyntax(*source->ast))
|
||||
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>();
|
||||
// We need to keep the same resolver during the whole process.
|
||||
NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter);
|
||||
@ -326,28 +321,28 @@ bool CompilerStack::analyze()
|
||||
if (source->ast && !resolver.performImports(*source->ast, sourceUnitsByName))
|
||||
return false;
|
||||
|
||||
// This is the main name and type resolution loop. Needs to be run for every contract, because
|
||||
// the special variables "this" and "super" must be set appropriately.
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !resolver.resolveNamesAndTypes(*source->ast))
|
||||
return false;
|
||||
|
||||
// Store contract definitions.
|
||||
for (Source const* source: m_sourceOrder)
|
||||
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))
|
||||
return false;
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
{
|
||||
// Note that we now reference contracts by their fully qualified names, and
|
||||
// thus contracts can only conflict if declared in the same source file. This
|
||||
// should already cause a double-declaration error elsewhere.
|
||||
if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
|
||||
m_contracts[contract->fullyQualifiedName()].contract = contract;
|
||||
else
|
||||
solAssert(
|
||||
m_errorReporter.hasErrors(),
|
||||
"Contract already present (name clash?), but no error was reported."
|
||||
);
|
||||
}
|
||||
|
||||
// Note that we now reference contracts by their fully qualified names, and
|
||||
// thus contracts can only conflict if declared in the same source file. This
|
||||
// should already cause a double-declaration error elsewhere.
|
||||
if (!m_contracts.count(contract->fullyQualifiedName()))
|
||||
m_contracts[contract->fullyQualifiedName()].contract = contract;
|
||||
else
|
||||
solAssert(
|
||||
m_errorReporter.hasErrors(),
|
||||
"Contract already present (name clash?), but no error was reported."
|
||||
);
|
||||
}
|
||||
|
||||
DeclarationTypeChecker declarationTypeChecker(m_errorReporter, m_evmVersion);
|
||||
@ -367,6 +362,11 @@ bool CompilerStack::analyze()
|
||||
if (!contractLevelChecker.check(*contract))
|
||||
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
|
||||
// cannot be done earlier, because we need cross-contract types and information
|
||||
// about whether a contract is abstract for the `new` expression.
|
||||
@ -376,11 +376,8 @@ bool CompilerStack::analyze()
|
||||
// which is only done one step later.
|
||||
TypeChecker typeChecker(m_evmVersion, m_errorReporter);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast)
|
||||
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
if (!typeChecker.checkTypeRequirements(*contract))
|
||||
noErrors = false;
|
||||
if (source->ast && !typeChecker.checkTypeRequirements(*source->ast))
|
||||
noErrors = false;
|
||||
|
||||
if (noErrors)
|
||||
{
|
||||
|
@ -36,6 +36,10 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
|
||||
{
|
||||
Json::Value doc;
|
||||
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());
|
||||
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;
|
||||
if (!events.empty())
|
||||
doc["events"] = events;
|
||||
|
||||
return doc;
|
||||
}
|
||||
@ -87,6 +101,9 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
|
||||
Json::Value doc;
|
||||
Json::Value methods(Json::objectValue);
|
||||
|
||||
doc["version"] = Json::Value(c_natspecVersion);
|
||||
doc["kind"] = Json::Value("dev");
|
||||
|
||||
auto author = extractDoc(_contractDef.annotation().docTags, "author");
|
||||
if (!author.empty())
|
||||
doc["author"] = author;
|
||||
@ -135,9 +152,16 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
|
||||
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;
|
||||
if (!stateVariables.empty())
|
||||
doc["stateVariables"] = stateVariables;
|
||||
if (!events.empty())
|
||||
doc["events"] = events;
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
@ -40,6 +40,8 @@ struct DocTag;
|
||||
class Natspec
|
||||
{
|
||||
public:
|
||||
static unsigned int constexpr c_natspecVersion = 1;
|
||||
|
||||
/// Get the User documentation of the contract
|
||||
/// @param _contractDef The contract definition
|
||||
/// @return A JSON representation of the contract's user documentation
|
||||
|
@ -94,19 +94,12 @@ void DocStringParser::parse(string const& _docString, ErrorReporter& _errorRepor
|
||||
{
|
||||
// we found a tag
|
||||
auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
|
||||
if (tagNameEndPos == end)
|
||||
{
|
||||
m_errorReporter->docstringParsingError(
|
||||
9222_error,
|
||||
"End of tag " + string(tagPos, tagNameEndPos) + " not found"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos));
|
||||
auto tagName = string(tagPos + 1, tagNameEndPos);
|
||||
auto tagDataPos = (tagNameEndPos != end) ? tagNameEndPos + 1 : tagNameEndPos;
|
||||
currPos = parseDocTag(tagDataPos, end, tagName);
|
||||
}
|
||||
else if (!!m_lastTag) // continuation of the previous tag
|
||||
currPos = appendDocTag(currPos, end);
|
||||
currPos = parseDocTagLine(currPos, end, true);
|
||||
else if (currPos != end)
|
||||
{
|
||||
// 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, "");
|
||||
auto nlPos = find(_pos, _end, '\n');
|
||||
if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t')
|
||||
if (_appending && _pos != _end && *_pos != ' ' && *_pos != '\t')
|
||||
m_lastTag->content += " ";
|
||||
else if (!_appending)
|
||||
_pos = skipWhitespace(_pos, _end);
|
||||
@ -179,13 +172,7 @@ DocStringParser::iter DocStringParser::parseDocTag(iter _pos, iter _end, string
|
||||
}
|
||||
}
|
||||
else
|
||||
return appendDocTag(_pos, _end);
|
||||
}
|
||||
|
||||
DocStringParser::iter DocStringParser::appendDocTag(iter _pos, iter _end)
|
||||
{
|
||||
solAssert(!!m_lastTag, "");
|
||||
return parseDocTagLine(_pos, _end, true);
|
||||
return parseDocTagLine(_pos, _end, true);
|
||||
}
|
||||
|
||||
void DocStringParser::newTag(string const& _tagName)
|
||||
|
@ -50,7 +50,6 @@ private:
|
||||
iter parseDocTagParam(iter _pos, iter _end);
|
||||
iter appendDocTagParam(iter _pos, iter _end);
|
||||
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
|
||||
/// after the tag.
|
||||
iter parseDocTag(iter _pos, iter _end, std::string const& _tag);
|
||||
|
@ -1825,11 +1825,16 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
|
||||
expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
|
||||
break;
|
||||
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();
|
||||
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();
|
||||
expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ bytes solidity::util::fromHex(std::string const& _s, WhenError _throw)
|
||||
{
|
||||
int h = fromHex(_s[s++], _throw);
|
||||
if (h != -1)
|
||||
ret.push_back(h);
|
||||
ret.push_back(static_cast<uint8_t>(h));
|
||||
else
|
||||
return bytes();
|
||||
}
|
||||
@ -115,7 +115,7 @@ bytes solidity::util::fromHex(std::string const& _s, WhenError _throw)
|
||||
int h = fromHex(_s[i], _throw);
|
||||
int l = fromHex(_s[i + 1], _throw);
|
||||
if (h != -1 && l != -1)
|
||||
ret.push_back((uint8_t)(h * 16 + l));
|
||||
ret.push_back(static_cast<uint8_t>(h * 16 + l));
|
||||
else
|
||||
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()));
|
||||
|
||||
string ret = "0x";
|
||||
for (size_t i = 0; i < 40; ++i)
|
||||
for (unsigned i = 0; i < 40; ++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)
|
||||
ret += toupper(addressCharacter);
|
||||
ret += static_cast<char>(toupper(addressCharacter));
|
||||
else
|
||||
ret += tolower(addressCharacter);
|
||||
ret += static_cast<char>(tolower(addressCharacter));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -64,10 +64,44 @@ public:
|
||||
FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); }
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex, ConstructFromHashType _ht = FailIfDifferent): FixedHash(_t == FromHex ? fromHex(_s, WhenError::Throw) : solidity::util::asBytes(_s), _ht) {}
|
||||
|
@ -69,25 +69,25 @@ static uint64_t const RC[24] = \
|
||||
#define REPEAT6(e) e e e e e e
|
||||
#define REPEAT24(e) REPEAT6(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; \
|
||||
REPEAT5(e; v += s;)
|
||||
REPEAT5(e; v = static_cast<type>(v + s);)
|
||||
|
||||
/*** Keccak-f[1600] ***/
|
||||
static inline void keccakf(void* state) {
|
||||
uint64_t* a = (uint64_t*)state;
|
||||
auto* a = static_cast<uint64_t*>(state);
|
||||
uint64_t b[5] = {0};
|
||||
|
||||
for (int i = 0; i < 24; i++)
|
||||
{
|
||||
uint8_t x, y;
|
||||
// Theta
|
||||
FOR5(x, 1,
|
||||
FOR5(uint8_t, x, 1,
|
||||
b[x] = 0;
|
||||
FOR5(y, 5,
|
||||
FOR5(uint8_t, y, 5,
|
||||
b[x] ^= a[x + y]; ))
|
||||
FOR5(x, 1,
|
||||
FOR5(y, 5,
|
||||
FOR5(uint8_t, x, 1,
|
||||
FOR5(uint8_t, y, 5,
|
||||
a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); ))
|
||||
// Rho and pi
|
||||
uint64_t t = a[1];
|
||||
@ -97,11 +97,12 @@ static inline void keccakf(void* state) {
|
||||
t = b[0];
|
||||
x++; )
|
||||
// Chi
|
||||
FOR5(y,
|
||||
FOR5(uint8_t,
|
||||
y,
|
||||
5,
|
||||
FOR5(x, 1,
|
||||
FOR5(uint8_t, x, 1,
|
||||
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]); ))
|
||||
// Iota
|
||||
a[0] ^= RC[i];
|
||||
|
@ -132,17 +132,11 @@ vector<YulString> AsmAnalyzer::operator()(Identifier const& _identifier)
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
if (m_resolver)
|
||||
{
|
||||
bool 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.");
|
||||
}
|
||||
}
|
||||
bool found = m_resolver && m_resolver(
|
||||
_identifier,
|
||||
yul::IdentifierContext::RValue,
|
||||
m_currentScope->insideFunction()
|
||||
);
|
||||
if (!found && watcher.ok())
|
||||
// Only add an error message if the callback did not do it.
|
||||
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();
|
||||
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);
|
||||
|
||||
if (types.size() != numVariables)
|
||||
@ -270,7 +275,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
if (f->literalArguments)
|
||||
needsLiteralArguments = &f->literalArguments.value();
|
||||
|
||||
warnOnInstructions(_funCall);
|
||||
validateInstructions(_funCall);
|
||||
}
|
||||
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
|
||||
[&](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.");
|
||||
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--)
|
||||
{
|
||||
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))
|
||||
m_errorReporter.typeError(
|
||||
@ -439,6 +449,14 @@ YulString AsmAnalyzer::expectExpression(Expression const& _expr)
|
||||
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)
|
||||
{
|
||||
YulString type = expectExpression(_expr);
|
||||
@ -478,12 +496,10 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueT
|
||||
else if (m_resolver)
|
||||
{
|
||||
bool insideFunction = m_currentScope->insideFunction();
|
||||
size_t variableSize = m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction);
|
||||
if (variableSize != size_t(-1))
|
||||
if (m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction))
|
||||
{
|
||||
found = true;
|
||||
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));
|
||||
if (builtin)
|
||||
return warnOnInstructions(builtin->instruction.value(), _location);
|
||||
if (builtin && builtin->instruction.has_value())
|
||||
return validateInstructions(builtin->instruction.value(), _location);
|
||||
else
|
||||
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
|
||||
// or all not available.
|
||||
@ -553,9 +569,16 @@ bool AsmAnalyzer::warnOnInstructions(evmasm::Instruction _instr, SourceLocation
|
||||
// Similarly we assume bitwise shifting and create2 go together.
|
||||
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(
|
||||
7079_error,
|
||||
_errorId,
|
||||
_location,
|
||||
"The \"" +
|
||||
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::RETURNDATASIZE
|
||||
) && !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())
|
||||
errorForVM("only available for Byzantium-compatible");
|
||||
errorForVM(1503_error, "only available for Byzantium-compatible");
|
||||
else if ((
|
||||
_instr == evmasm::Instruction::SHL ||
|
||||
_instr == evmasm::Instruction::SHR ||
|
||||
_instr == evmasm::Instruction::SAR
|
||||
) && !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())
|
||||
errorForVM("only available for Constantinople-compatible");
|
||||
errorForVM(6166_error, "only available for Constantinople-compatible");
|
||||
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())
|
||||
errorForVM("only available for Istanbul-compatible");
|
||||
errorForVM(1561_error, "only available for Istanbul-compatible");
|
||||
else if (_instr == evmasm::Instruction::PC)
|
||||
m_errorReporter.warning(
|
||||
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."
|
||||
);
|
||||
else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance())
|
||||
errorForVM("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."
|
||||
);
|
||||
errorForVM(3672_error, "only available for Istanbul-compatible");
|
||||
else
|
||||
return false;
|
||||
|
||||
|
@ -97,6 +97,7 @@ private:
|
||||
/// Visits the expression, expects that it evaluates to exactly one value and
|
||||
/// returns the type. Reports errors on errors and returns the default type.
|
||||
YulString expectExpression(Expression const& _expr);
|
||||
YulString expectUnlimitedStringLiteral(Literal const& _literal);
|
||||
/// Vists the expression and expects it to return a single boolean value.
|
||||
/// Reports an error otherwise.
|
||||
void expectBoolExpression(Expression const& _expr);
|
||||
@ -109,12 +110,12 @@ private:
|
||||
Scope& scope(Block const* _block);
|
||||
void expectValidType(YulString _type, 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;
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
#include <libyul/AsmJsonConverter.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
using namespace std;
|
||||
@ -38,7 +38,7 @@ Json::Value AsmJsonConverter::operator()(Block 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");
|
||||
ret["name"] = _node.name.str();
|
||||
ret["type"] = _node.type.str();
|
||||
@ -51,7 +51,7 @@ Json::Value AsmJsonConverter::operator()(Literal const& _node) const
|
||||
switch (_node.kind)
|
||||
{
|
||||
case LiteralKind::Number:
|
||||
solAssert(
|
||||
yulAssert(
|
||||
util::isValidDecimal(_node.value.str()) || util::isValidHex(_node.value.str()),
|
||||
"Invalid number literal"
|
||||
);
|
||||
@ -71,7 +71,7 @@ Json::Value AsmJsonConverter::operator()(Literal 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");
|
||||
ret["name"] = _node.name.str();
|
||||
return ret;
|
||||
@ -79,7 +79,7 @@ Json::Value AsmJsonConverter::operator()(Identifier 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");
|
||||
for (auto const& var: _node.variableNames)
|
||||
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
|
||||
{
|
||||
solAssert(!_node.name.empty(), "Invalid function name.");
|
||||
yulAssert(!_node.name.empty(), "Invalid function name.");
|
||||
Json::Value ret = createAstNode(_node.location, "YulFunctionDefinition");
|
||||
ret["name"] = _node.name.str();
|
||||
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
|
||||
{
|
||||
solAssert(_node.condition, "Invalid if condition.");
|
||||
yulAssert(_node.condition, "Invalid if condition.");
|
||||
Json::Value ret = createAstNode(_node.location, "YulIf");
|
||||
ret["condition"] = std::visit(*this, *_node.condition);
|
||||
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
|
||||
{
|
||||
solAssert(_node.expression, "Invalid expression pointer.");
|
||||
yulAssert(_node.expression, "Invalid expression pointer.");
|
||||
Json::Value ret = createAstNode(_node.location, "YulSwitch");
|
||||
ret["expression"] = std::visit(*this, *_node.expression);
|
||||
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
|
||||
{
|
||||
solAssert(_node.condition, "Invalid for loop condition.");
|
||||
yulAssert(_node.condition, "Invalid for loop condition.");
|
||||
Json::Value ret = createAstNode(_node.location, "YulForLoop");
|
||||
ret["pre"] = (*this)(_node.pre);
|
||||
ret["condition"] = std::visit(*this, *_node.condition);
|
||||
ret["post"] = (*this)(_node.post);
|
||||
ret["body"] = (*this)(_node.body);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
for (auto const& var: _vec)
|
||||
ret.append(std::visit(*this, var));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,8 @@ Statement Parser::parseStatement()
|
||||
case Token::Comma:
|
||||
case Token::AssemblyAssign:
|
||||
{
|
||||
std::vector<Identifier> variableNames;
|
||||
Assignment assignment;
|
||||
assignment.location = locationOf(elementary);
|
||||
|
||||
while (true)
|
||||
{
|
||||
@ -197,7 +198,7 @@ Statement Parser::parseStatement()
|
||||
if (m_dialect.builtin(identifier.name))
|
||||
fatalParserError(6272_error, "Cannot assign to builtin function \"" + identifier.name.str() + "\".");
|
||||
|
||||
variableNames.emplace_back(identifier);
|
||||
assignment.variableNames.emplace_back(identifier);
|
||||
|
||||
if (currentToken() != Token::Comma)
|
||||
break;
|
||||
@ -207,10 +208,6 @@ Statement Parser::parseStatement()
|
||||
elementary = parseElementaryOperation();
|
||||
}
|
||||
|
||||
Assignment assignment;
|
||||
assignment.location = std::get<Identifier>(elementary).location;
|
||||
assignment.variableNames = std::move(variableNames);
|
||||
|
||||
expectToken(Token::AssemblyAssign);
|
||||
|
||||
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()
|
||||
{
|
||||
RecursionGuard recursionGuard(*this);
|
||||
|
@ -86,7 +86,6 @@ protected:
|
||||
ForLoop parseForLoop();
|
||||
/// Parses a functional expression that has to push exactly one stack element
|
||||
Expression parseExpression();
|
||||
static std::map<evmasm::Instruction, std::string> const& instructionNames();
|
||||
/// Parses an elementary operation, i.e. a literal, identifier, instruction or
|
||||
/// builtin functian call (only the name).
|
||||
ElementaryOperation parseElementaryOperation();
|
||||
|
@ -108,7 +108,7 @@ void AssemblyStack::translate(AssemblyStack::Language _targetLanguage)
|
||||
if (m_language == _targetLanguage)
|
||||
return;
|
||||
|
||||
solAssert(
|
||||
yulAssert(
|
||||
m_language == Language::StrictAssembly && _targetLanguage == Language::Ewasm,
|
||||
"Invalid language combination"
|
||||
);
|
||||
@ -160,7 +160,7 @@ void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _evm15, bool _o
|
||||
dialect = &EVMDialectTyped::instance(m_evmVersion);
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Invalid language.");
|
||||
yulAssert(false, "Invalid language.");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ string solidity::yul::reindent(string const& _code)
|
||||
auto const static countBraces = [](string const& _s) noexcept -> int
|
||||
{
|
||||
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 closing = count_if(begin(_s), e, [](auto ch) { return ch == '}' || ch == ')'; });
|
||||
return opening - closing;
|
||||
|
@ -71,10 +71,10 @@ public:
|
||||
{
|
||||
// FNV hash - can be replaced by a better one, e.g. xxhash64
|
||||
std::uint64_t hash = emptyHash();
|
||||
for (auto c: v)
|
||||
for (char c: v)
|
||||
{
|
||||
hash *= 1099511628211u;
|
||||
hash ^= c;
|
||||
hash ^= static_cast<uint64_t>(c);
|
||||
}
|
||||
|
||||
return hash;
|
||||
|
@ -50,6 +50,7 @@ class AbstractAssembly
|
||||
public:
|
||||
using LabelID = size_t;
|
||||
using SubID = size_t;
|
||||
enum class JumpType { Ordinary, IntoFunction, OutOfFunction };
|
||||
|
||||
virtual ~AbstractAssembly() = default;
|
||||
|
||||
@ -78,13 +79,13 @@ public:
|
||||
/// Append a jump instruction.
|
||||
/// @param _stackDiffAfter the stack adjustment after this instruction.
|
||||
/// 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.
|
||||
/// @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.
|
||||
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
|
||||
/// stack slots as arguments.
|
||||
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).
|
||||
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.
|
||||
/// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
|
||||
Resolver resolve;
|
||||
|
@ -98,22 +98,22 @@ void EthAssemblyAdapter::appendLinkerSymbol(std::string const& _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);
|
||||
}
|
||||
|
||||
void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
||||
void EthAssemblyAdapter::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType)
|
||||
{
|
||||
appendLabelReference(_labelId);
|
||||
appendJump(_stackDiffAfter);
|
||||
appendJump(_stackDiffAfter, _jumpType);
|
||||
}
|
||||
|
||||
void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId)
|
||||
void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId, JumpType _jumpType)
|
||||
{
|
||||
appendLabelReference(_labelId);
|
||||
appendInstruction(evmasm::Instruction::JUMPI);
|
||||
appendJumpInstruction(evmasm::Instruction::JUMPI, _jumpType);
|
||||
}
|
||||
|
||||
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>()};
|
||||
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)
|
||||
{
|
||||
auto it = m_dataHashBySubId.find(_sub);
|
||||
if (it == m_dataHashBySubId.end())
|
||||
m_assembly.pushSubroutineOffset(size_t(_sub));
|
||||
m_assembly.pushSubroutineOffset(_sub);
|
||||
else
|
||||
m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second);
|
||||
}
|
||||
@ -159,7 +159,7 @@ void EthAssemblyAdapter::appendDataSize(AbstractAssembly::SubID _sub)
|
||||
{
|
||||
auto it = m_dataHashBySubId.find(_sub);
|
||||
if (it == m_dataHashBySubId.end())
|
||||
m_assembly.pushSubroutineSize(size_t(_sub));
|
||||
m_assembly.pushSubroutineSize(static_cast<size_t>(_sub));
|
||||
else
|
||||
m_assembly << u256(m_assembly.data(h256(it->second)).size());
|
||||
}
|
||||
@ -189,6 +189,25 @@ EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(evmasm::
|
||||
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(
|
||||
Block const& _parsedData,
|
||||
AsmAnalysisInfo& _analysisInfo,
|
||||
@ -218,8 +237,9 @@ void CodeGenerator::assemble(
|
||||
}
|
||||
catch (StackTooDeepError const& _e)
|
||||
{
|
||||
yulAssert(
|
||||
assertThrow(
|
||||
false,
|
||||
langutil::StackTooDeepError,
|
||||
"Stack too deep when compiling inline assembly" +
|
||||
(_e.comment() ? ": " + *_e.comment() : ".")
|
||||
);
|
||||
|
@ -49,9 +49,9 @@ public:
|
||||
size_t newLabelId() override;
|
||||
size_t namedLabel(std::string const& _name) override;
|
||||
void appendLinkerSymbol(std::string const& _linkerSymbol) override;
|
||||
void appendJump(int _stackDiffAfter) override;
|
||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
|
||||
void appendJumpToIf(LabelID _labelId) override;
|
||||
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||
void appendBeginsub(LabelID, int) override;
|
||||
void appendJumpsub(LabelID, int, int) override;
|
||||
void appendReturnsub(int, int) override;
|
||||
@ -66,6 +66,7 @@ public:
|
||||
|
||||
private:
|
||||
static LabelID assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag);
|
||||
void appendJumpInstruction(evmasm::Instruction _instruction, JumpType _jumpType);
|
||||
|
||||
evmasm::Assembly& m_assembly;
|
||||
std::map<SubID, u256> m_dataHashBySubId;
|
||||
|
@ -74,7 +74,7 @@ void EVMAssembly::appendLabelReference(LabelID _labelId)
|
||||
|
||||
EVMAssembly::LabelID EVMAssembly::newLabelId()
|
||||
{
|
||||
m_labelPositions[m_nextLabelId] = size_t(-1);
|
||||
m_labelPositions[m_nextLabelId] = numeric_limits<size_t>::max();
|
||||
return m_nextLabelId++;
|
||||
}
|
||||
|
||||
@ -91,14 +91,14 @@ void EVMAssembly::appendLinkerSymbol(string const&)
|
||||
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");
|
||||
appendInstruction(evmasm::Instruction::JUMP);
|
||||
m_stackHeight += _stackDiffAfter;
|
||||
}
|
||||
|
||||
void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
||||
void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType)
|
||||
{
|
||||
if (m_evm15)
|
||||
{
|
||||
@ -109,11 +109,11 @@ void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
||||
else
|
||||
{
|
||||
appendLabelReference(_labelId);
|
||||
appendJump(_stackDiffAfter);
|
||||
appendJump(_stackDiffAfter, _jumpType);
|
||||
}
|
||||
}
|
||||
|
||||
void EVMAssembly::appendJumpToIf(LabelID _labelId)
|
||||
void EVMAssembly::appendJumpToIf(LabelID _labelId, JumpType)
|
||||
{
|
||||
if (m_evm15)
|
||||
{
|
||||
@ -165,7 +165,7 @@ evmasm::LinkerObject EVMAssembly::finalize()
|
||||
size_t referencePos = ref.first;
|
||||
yulAssert(m_labelPositions.count(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));
|
||||
}
|
||||
|
||||
@ -177,7 +177,7 @@ evmasm::LinkerObject EVMAssembly::finalize()
|
||||
void EVMAssembly::setLabelToCurrentPosition(LabelID _labelId)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -64,11 +64,11 @@ public:
|
||||
|
||||
/// Append a jump 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.
|
||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
|
||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||
/// Append a jump-to-if-immediate operation.
|
||||
void appendJumpToIf(LabelID _labelId) override;
|
||||
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||
/// Start a subroutine.
|
||||
void appendBeginsub(LabelID _labelId, int _arguments) override;
|
||||
/// Call a subroutine.
|
||||
|
@ -175,28 +175,29 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
yulAssert(m_scope, "");
|
||||
|
||||
int const numVariables = _varDecl.variables.size();
|
||||
int heightAtStart = m_assembly.stackHeight();
|
||||
size_t const numVariables = _varDecl.variables.size();
|
||||
auto heightAtStart = static_cast<size_t>(m_assembly.stackHeight());
|
||||
if (_varDecl.value)
|
||||
{
|
||||
std::visit(*this, *_varDecl.value);
|
||||
expectDeposit(numVariables, heightAtStart);
|
||||
expectDeposit(static_cast<int>(numVariables), static_cast<int>(heightAtStart));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_assembly.setSourceLocation(_varDecl.location);
|
||||
int variablesLeft = numVariables;
|
||||
size_t variablesLeft = numVariables;
|
||||
while (variablesLeft--)
|
||||
m_assembly.appendConstant(u256(0));
|
||||
}
|
||||
|
||||
m_assembly.setSourceLocation(_varDecl.location);
|
||||
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));
|
||||
m_context->variableStackHeights[&var] = heightAtStart + varIndex;
|
||||
m_context->variableStackHeights[&var] = heightAtStart + varIndexReverse;
|
||||
if (!m_allowStackOpt)
|
||||
continue;
|
||||
|
||||
@ -214,10 +215,10 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
|
||||
atTopOfStack = false;
|
||||
else
|
||||
{
|
||||
int slot = *m_unusedStackSlots.begin();
|
||||
auto slot = static_cast<size_t>(*m_unusedStackSlots.begin());
|
||||
m_unusedStackSlots.erase(m_unusedStackSlots.begin());
|
||||
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::Instruction::POP);
|
||||
}
|
||||
@ -240,7 +241,7 @@ void CodeTransform::operator()(Assignment const& _assignment)
|
||||
{
|
||||
int height = m_assembly.stackHeight();
|
||||
std::visit(*this, *_assignment.value);
|
||||
expectDeposit(_assignment.variableNames.size(), height);
|
||||
expectDeposit(static_cast<int>(_assignment.variableNames.size()), height);
|
||||
|
||||
m_assembly.setSourceLocation(_assignment.location);
|
||||
generateMultiAssignment(_assignment.variableNames);
|
||||
@ -263,7 +264,7 @@ void CodeTransform::operator()(FunctionCall const& _call)
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
returnLabel = m_assembly.newLabelId();
|
||||
@ -281,10 +282,18 @@ void CodeTransform::operator()(FunctionCall const& _call)
|
||||
visitExpression(arg);
|
||||
m_assembly.setSourceLocation(_call.location);
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -300,7 +309,7 @@ void CodeTransform::operator()(Identifier const& _identifier)
|
||||
{
|
||||
// TODO: opportunity for optimization: Do not DUP if this is the last reference
|
||||
// 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));
|
||||
else
|
||||
// Store something to balance the stack
|
||||
@ -407,7 +416,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
int const stackHeightBefore = m_assembly.stackHeight();
|
||||
|
||||
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
|
||||
m_assembly.appendLabel(functionEntryID(_function.name, function));
|
||||
|
||||
@ -465,15 +474,15 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
// modified parallel to the actual stack.
|
||||
vector<int> stackLayout;
|
||||
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
|
||||
|
||||
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)
|
||||
{
|
||||
StackTooDeepError error(_function.name, YulString{}, stackLayout.size() - 17);
|
||||
StackTooDeepError error(_function.name, YulString{}, static_cast<int>(stackLayout.size()) - 17);
|
||||
error << errinfo_comment(
|
||||
"The function " +
|
||||
_function.name.str() +
|
||||
@ -481,11 +490,11 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
to_string(stackLayout.size() - 17) +
|
||||
" 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
|
||||
{
|
||||
while (!stackLayout.empty() && stackLayout.back() != int(stackLayout.size() - 1))
|
||||
while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1))
|
||||
if (stackLayout.back() < 0)
|
||||
{
|
||||
m_assembly.appendInstruction(evmasm::Instruction::POP);
|
||||
@ -493,17 +502,20 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_assembly.appendInstruction(evmasm::swapInstruction(stackLayout.size() - stackLayout.back() - 1));
|
||||
swap(stackLayout[stackLayout.back()], stackLayout.back());
|
||||
m_assembly.appendInstruction(evmasm::swapInstruction(stackLayout.size() - static_cast<size_t>(stackLayout.back()) - 1));
|
||||
swap(stackLayout[static_cast<size_t>(stackLayout.back())], stackLayout.back());
|
||||
}
|
||||
for (int i = 0; size_t(i) < stackLayout.size(); ++i)
|
||||
yulAssert(i == stackLayout[i], "Error reshuffling stack.");
|
||||
for (size_t i = 0; i < stackLayout.size(); ++i)
|
||||
yulAssert(i == static_cast<size_t>(stackLayout[i]), "Error reshuffling stack.");
|
||||
}
|
||||
}
|
||||
if (m_evm15)
|
||||
m_assembly.appendReturnsub(_function.returnVariables.size(), stackHeightBefore);
|
||||
m_assembly.appendReturnsub(static_cast<int>(_function.returnVariables.size()), stackHeightBefore);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -683,7 +695,7 @@ void CodeTransform::generateAssignment(Identifier const& _variableName)
|
||||
if (auto var = m_scope->lookup(_variableName.name))
|
||||
{
|
||||
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::Instruction::POP);
|
||||
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), "");
|
||||
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.");
|
||||
int limit = _forSwap ? 17 : 16;
|
||||
size_t limit = _forSwap ? 17 : 16;
|
||||
if (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.");
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ struct StackTooDeepError: virtual YulException
|
||||
struct CodeTransformContext
|
||||
{
|
||||
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;
|
||||
|
||||
struct JumpInfo
|
||||
@ -200,7 +200,7 @@ private:
|
||||
/// 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
|
||||
/// 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;
|
||||
|
||||
|
@ -63,8 +63,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
||||
evmasm::InstructionInfo info = evmasm::instructionInfo(_instruction);
|
||||
BuiltinFunctionForEVM f;
|
||||
f.name = YulString{_name};
|
||||
f.parameters.resize(info.args);
|
||||
f.returns.resize(info.ret);
|
||||
f.parameters.resize(static_cast<size_t>(info.args));
|
||||
f.returns.resize(static_cast<size_t>(info.ret));
|
||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_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
|
||||
)
|
||||
{
|
||||
solAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
|
||||
yulAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
|
||||
|
||||
YulString name{std::move(_name)};
|
||||
BuiltinFunctionForEVM f;
|
||||
@ -114,18 +114,31 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
|
||||
map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVersion, bool _objectAccess)
|
||||
{
|
||||
map<YulString, BuiltinFunctionForEVM> builtins;
|
||||
// NOTE: Parser::instructions() will filter JUMPDEST and PUSHnn too
|
||||
for (auto const& instr: Parser::instructions())
|
||||
if (
|
||||
!evmasm::isDupInstruction(instr.second) &&
|
||||
!evmasm::isSwapInstruction(instr.second) &&
|
||||
!evmasm::isPushInstruction(instr.second) &&
|
||||
instr.second != evmasm::Instruction::JUMP &&
|
||||
instr.second != evmasm::Instruction::JUMPI &&
|
||||
instr.second != evmasm::Instruction::JUMPDEST &&
|
||||
_evmVersion.hasOpcode(instr.second)
|
||||
)
|
||||
builtins.emplace(createEVMFunction(instr.first, instr.second));
|
||||
|
||||
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}, [](
|
||||
FunctionCall const& _call,
|
||||
AbstractAssembly& _assembly,
|
||||
@ -196,7 +209,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
BuiltinContext&,
|
||||
std::function<void(Expression const&)> _visitExpression
|
||||
) {
|
||||
solAssert(_call.arguments.size() == 2, "");
|
||||
yulAssert(_call.arguments.size() == 2, "");
|
||||
|
||||
_visitExpression(_call.arguments[1]);
|
||||
_assembly.setSourceLocation(_call.location);
|
||||
@ -216,7 +229,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
BuiltinContext&,
|
||||
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());
|
||||
}
|
||||
));
|
||||
|
@ -91,7 +91,11 @@ void GasMeterVisitor::operator()(Literal const& _lit)
|
||||
m_runGas += evmasm::GasMeter::runGas(evmasm::Instruction::PUSH1);
|
||||
m_dataGas +=
|
||||
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&)
|
||||
|
@ -70,25 +70,25 @@ void NoOutputAssembly::appendLinkerSymbol(string const&)
|
||||
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");
|
||||
appendInstruction(evmasm::Instruction::JUMP);
|
||||
m_stackHeight += _stackDiffAfter;
|
||||
}
|
||||
|
||||
void NoOutputAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
|
||||
void NoOutputAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType)
|
||||
{
|
||||
if (m_evm15)
|
||||
m_stackHeight += _stackDiffAfter;
|
||||
else
|
||||
{
|
||||
appendLabelReference(_labelId);
|
||||
appendJump(_stackDiffAfter);
|
||||
appendJump(_stackDiffAfter, _jumpType);
|
||||
}
|
||||
}
|
||||
|
||||
void NoOutputAssembly::appendJumpToIf(LabelID _labelId)
|
||||
void NoOutputAssembly::appendJumpToIf(LabelID _labelId, JumpType)
|
||||
{
|
||||
if (m_evm15)
|
||||
m_stackHeight--;
|
||||
|
@ -58,9 +58,9 @@ public:
|
||||
LabelID namedLabel(std::string const& _name) override;
|
||||
void appendLinkerSymbol(std::string const& _name) override;
|
||||
|
||||
void appendJump(int _stackDiffAfter) override;
|
||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
|
||||
void appendJumpToIf(LabelID _labelId) override;
|
||||
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
|
||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||
void appendBeginsub(LabelID _labelId, int _arguments) override;
|
||||
void appendJumpsub(LabelID _labelId, int _arguments, int _returns) override;
|
||||
void appendReturnsub(int _returns, int _stackDiffAfter) override;
|
||||
|
@ -376,12 +376,12 @@ bytes BinaryTransform::operator()(BuiltinCall const& _call)
|
||||
if (_call.functionName == "dataoffset")
|
||||
{
|
||||
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")
|
||||
{
|
||||
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);
|
||||
@ -390,7 +390,7 @@ bytes BinaryTransform::operator()(BuiltinCall const& _call)
|
||||
return toBytes(Opcode::Unreachable);
|
||||
else if (_call.functionName == "nop")
|
||||
return toBytes(Opcode::Nop);
|
||||
else if (_call.functionName == "drop")
|
||||
else if (_call.functionName == "i32.drop" || _call.functionName == "i64.drop")
|
||||
return toBytes(Opcode::Drop);
|
||||
else
|
||||
{
|
||||
|
@ -94,7 +94,10 @@ string TextTransform::operator()(wasm::GlobalVariable const& _identifier)
|
||||
string TextTransform::operator()(wasm::BuiltinCall const& _builtinCall)
|
||||
{
|
||||
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)
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
#include <libyul/backends/wasm/WasmCodeTransform.h>
|
||||
|
||||
#include <libyul/backends/wasm/WasmDialect.h>
|
||||
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
|
||||
#include <libyul/AsmData.h>
|
||||
@ -40,7 +42,8 @@ wasm::Module WasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _
|
||||
{
|
||||
wasm::Module module;
|
||||
|
||||
WasmCodeTransform transform(_dialect, _ast);
|
||||
TypeInfo typeInfo(_dialect, _ast);
|
||||
WasmCodeTransform transform(_dialect, _ast, typeInfo);
|
||||
|
||||
for (auto const& statement: _ast.statements)
|
||||
{
|
||||
@ -70,14 +73,18 @@ wasm::Expression WasmCodeTransform::generateMultiAssignment(
|
||||
if (_variableNames.size() == 1)
|
||||
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;
|
||||
block.statements.emplace_back(move(assignment));
|
||||
for (size_t i = 1; i < _variableNames.size(); ++i)
|
||||
block.statements.emplace_back(wasm::LocalAssignment{
|
||||
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) };
|
||||
}
|
||||
@ -88,7 +95,7 @@ wasm::Expression WasmCodeTransform::operator()(VariableDeclaration const& _varDe
|
||||
for (auto const& var: _varDecl.variables)
|
||||
{
|
||||
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)
|
||||
@ -112,8 +119,6 @@ wasm::Expression WasmCodeTransform::operator()(ExpressionStatement const& _state
|
||||
|
||||
wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
|
||||
{
|
||||
bool typeConversionNeeded = false;
|
||||
|
||||
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
|
||||
{
|
||||
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));
|
||||
m_functionsToImport[builtin->name] = std::move(imp);
|
||||
}
|
||||
typeConversionNeeded = 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)};
|
||||
}
|
||||
else
|
||||
{
|
||||
wasm::BuiltinCall call{
|
||||
return wasm::BuiltinCall{
|
||||
_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
|
||||
@ -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,
|
||||
// so it is handled there.
|
||||
|
||||
wasm::FunctionCall funCall{_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)};
|
||||
return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)};
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
u256 value = valueOfLiteral(_literal);
|
||||
yulAssert(value <= numeric_limits<uint64_t>::max(), "Literal too large: " + value.str());
|
||||
return wasm::Literal{static_cast<uint64_t>(value)};
|
||||
return makeLiteral(translatedType(_literal.type), valueOfLiteral(_literal));
|
||||
}
|
||||
|
||||
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;
|
||||
args.emplace_back(visitReturnByValue(*_if.condition));
|
||||
args.emplace_back(wasm::Literal{static_cast<uint64_t>(0)});
|
||||
return wasm::If{
|
||||
make_unique<wasm::Expression>(wasm::BuiltinCall{"i64.ne", std::move(args)}),
|
||||
visit(_if.body.statements),
|
||||
{}
|
||||
};
|
||||
wasm::Expression condition;
|
||||
if (conditionType == "i32"_yulstring)
|
||||
condition = visitReturnByValue(*_if.condition);
|
||||
else if (conditionType == "i64"_yulstring)
|
||||
{
|
||||
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)
|
||||
{
|
||||
yul::Type expressionType = m_typeInfo.typeOf(*_switch.expression);
|
||||
YulString eq_instruction = YulString(expressionType.str() + ".eq");
|
||||
yulAssert(WasmDialect::instance().builtin(eq_instruction), "");
|
||||
|
||||
wasm::Block block;
|
||||
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)});
|
||||
|
||||
vector<wasm::Expression>* currentBlock = &block.statements;
|
||||
@ -214,7 +214,7 @@ wasm::Expression WasmCodeTransform::operator()(Switch const& _switch)
|
||||
Case const& c = _switch.cases.at(i);
|
||||
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},
|
||||
visitReturnByValue(*c.value)
|
||||
)};
|
||||
@ -253,11 +253,16 @@ wasm::Expression WasmCodeTransform::operator()(ForLoop const& _for)
|
||||
string continueLabel = newLabel();
|
||||
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;
|
||||
loop.labelName = newLabel();
|
||||
loop.statements = visit(_for.pre.statements);
|
||||
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)
|
||||
)}
|
||||
)});
|
||||
@ -265,7 +270,8 @@ wasm::Expression WasmCodeTransform::operator()(ForLoop const& _for)
|
||||
loop.statements += visit(_for.post.statements);
|
||||
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&)
|
||||
@ -325,11 +331,11 @@ wasm::FunctionDefinition WasmCodeTransform::translateFunction(yul::FunctionDefin
|
||||
wasm::FunctionDefinition fun;
|
||||
fun.name = _fun.name.str();
|
||||
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)
|
||||
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())
|
||||
fun.returnType = wasm::Type::i64;
|
||||
fun.returnType = translatedType(_fun.returnVariables[0].type);
|
||||
|
||||
yulAssert(m_localVariables.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
|
||||
// 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)
|
||||
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()})
|
||||
});
|
||||
fun.body.emplace_back(wasm::LocalVariable{_fun.returnVariables.front().name.str()});
|
||||
@ -358,52 +369,50 @@ wasm::FunctionDefinition WasmCodeTransform::translateFunction(yul::FunctionDefin
|
||||
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()
|
||||
{
|
||||
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)
|
||||
m_globalVariables.emplace_back(wasm::GlobalVariableDeclaration{
|
||||
m_nameDispenser.newName("global_"_yulstring).str(),
|
||||
wasm::Type::i64
|
||||
});
|
||||
map<wasm::Type, size_t> availableGlobals;
|
||||
for (wasm::GlobalVariableDeclaration const& global: m_globalVariables)
|
||||
++availableGlobals[global.type];
|
||||
|
||||
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)
|
||||
@ -415,3 +424,19 @@ wasm::Type WasmCodeTransform::translatedType(yul::Type _yulType)
|
||||
else
|
||||
yulAssert(false, "This Yul type does not have a corresponding type in Wasm.");
|
||||
}
|
||||
|
||||
wasm::Literal WasmCodeTransform::makeLiteral(wasm::Type _type, u256 _value)
|
||||
{
|
||||
if (_type == wasm::Type::i32)
|
||||
{
|
||||
yulAssert(_value <= numeric_limits<uint32_t>::max(), "Literal too large: " + _value.str());
|
||||
return wasm::Literal{static_cast<uint32_t>(_value)};
|
||||
}
|
||||
else if (_type == wasm::Type::i64)
|
||||
{
|
||||
yulAssert(_value <= numeric_limits<uint64_t>::max(), "Literal too large: " + _value.str());
|
||||
return wasm::Literal{static_cast<uint64_t>(_value)};
|
||||
}
|
||||
else
|
||||
yulAssert(false, "Invalid Wasm literal type");
|
||||
}
|
||||
|
@ -24,6 +24,9 @@
|
||||
#include <libyul/AsmDataForward.h>
|
||||
#include <libyul/Dialect.h>
|
||||
#include <libyul/optimiser/NameDispenser.h>
|
||||
#include <libyul/optimiser/TypeInfo.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
|
||||
#include <stack>
|
||||
#include <map>
|
||||
@ -56,10 +59,12 @@ public:
|
||||
private:
|
||||
WasmCodeTransform(
|
||||
Dialect const& _dialect,
|
||||
Block const& _ast
|
||||
Block const& _ast,
|
||||
TypeInfo& _typeInfo
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_nameDispenser(_dialect, _ast)
|
||||
m_nameDispenser(_dialect, _ast),
|
||||
m_typeInfo(_typeInfo)
|
||||
{}
|
||||
|
||||
std::unique_ptr<wasm::Expression> visit(yul::Expression const& _expression);
|
||||
@ -79,17 +84,13 @@ private:
|
||||
|
||||
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();
|
||||
/// Makes sure that there are at least @a _amount global variables.
|
||||
void allocateGlobals(size_t _amount);
|
||||
/// Selects a subset of global variables matching specified sequence of variable types.
|
||||
/// 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::Literal makeLiteral(wasm::Type _type, u256 _value);
|
||||
|
||||
Dialect const& m_dialect;
|
||||
NameDispenser m_nameDispenser;
|
||||
@ -99,6 +100,7 @@ private:
|
||||
std::map<YulString, wasm::FunctionImport> m_functionsToImport;
|
||||
std::string m_functionBodyLabel;
|
||||
std::stack<std::pair<std::string, std::string>> m_breakContinueLabelNames;
|
||||
TypeInfo& m_typeInfo;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -91,9 +91,9 @@ WasmDialect::WasmDialect()
|
||||
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
|
||||
|
||||
// Drop is actually overloaded for all types, but Yul does not support that.
|
||||
// Because of that, we introduce "i32.drop".
|
||||
addFunction("drop", {i64}, {});
|
||||
// Because of that, we introduce "i32.drop" and "i64.drop".
|
||||
addFunction("i32.drop", {i32}, {});
|
||||
addFunction("i64.drop", {i64}, {});
|
||||
|
||||
addFunction("nop", {}, {});
|
||||
addFunction("unreachable", {}, {}, false);
|
||||
@ -122,7 +122,7 @@ BuiltinFunction const* WasmDialect::discardFunction(YulString _type) const
|
||||
if (_type == "i32"_yulstring)
|
||||
return builtin("i32.drop"_yulstring);
|
||||
yulAssert(_type == "i64"_yulstring, "");
|
||||
return builtin("drop"_yulstring);
|
||||
return builtin("i64.drop"_yulstring);
|
||||
}
|
||||
|
||||
BuiltinFunction const* WasmDialect::equalityFunction(YulString _type) const
|
||||
|
@ -112,7 +112,7 @@ void WordSizeTransform::operator()(Block& _block)
|
||||
yulAssert(varDecl.variables.size() == 1, "");
|
||||
auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name);
|
||||
vector<Statement> ret;
|
||||
for (int i = 0; i < 3; i++)
|
||||
for (size_t i = 0; i < 3; i++)
|
||||
ret.emplace_back(VariableDeclaration{
|
||||
varDecl.location,
|
||||
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
||||
@ -143,7 +143,7 @@ void WordSizeTransform::operator()(Block& _block)
|
||||
auto newRhs = expandValue(*varDecl.value);
|
||||
auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name);
|
||||
vector<Statement> ret;
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
ret.emplace_back(VariableDeclaration{
|
||||
varDecl.location,
|
||||
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
||||
@ -172,7 +172,7 @@ void WordSizeTransform::operator()(Block& _block)
|
||||
yulAssert(assignment.variableNames.size() == 1, "");
|
||||
auto newLhs = generateU64IdentifierNames(assignment.variableNames[0].name);
|
||||
vector<Statement> ret;
|
||||
for (int i = 0; i < 3; i++)
|
||||
for (size_t i = 0; i < 3; i++)
|
||||
ret.emplace_back(Assignment{
|
||||
assignment.location,
|
||||
{Identifier{assignment.location, newLhs[i]}},
|
||||
@ -203,7 +203,7 @@ void WordSizeTransform::operator()(Block& _block)
|
||||
auto newRhs = expandValue(*assignment.value);
|
||||
YulString lhsName = assignment.variableNames[0].name;
|
||||
vector<Statement> ret;
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
ret.emplace_back(Assignment{
|
||||
assignment.location,
|
||||
{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)
|
||||
{
|
||||
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)});
|
||||
return m_variableMapping[_s];
|
||||
}
|
||||
@ -392,19 +392,20 @@ array<unique_ptr<Expression>, 4> WordSizeTransform::expandValue(Expression const
|
||||
array<unique_ptr<Expression>, 4> ret;
|
||||
if (holds_alternative<Identifier>(_e))
|
||||
{
|
||||
Identifier const& id = std::get<Identifier>(_e);
|
||||
for (int i = 0; i < 4; i++)
|
||||
auto const& id = std::get<Identifier>(_e);
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
ret[i] = make_unique<Expression>(Identifier{id.location, m_variableMapping.at(id.name)[i]});
|
||||
}
|
||||
else if (holds_alternative<Literal>(_e))
|
||||
{
|
||||
Literal const& lit = std::get<Literal>(_e);
|
||||
auto const& lit = std::get<Literal>(_e);
|
||||
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();
|
||||
val >>= 64;
|
||||
ret[i] = make_unique<Expression>(
|
||||
ret[exprIndexReverse] = make_unique<Expression>(
|
||||
Literal{
|
||||
lit.location,
|
||||
LiteralKind::Number,
|
||||
@ -426,4 +427,3 @@ vector<Expression> WordSizeTransform::expandValueToVector(Expression const& _e)
|
||||
ret.emplace_back(std::move(*val));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -253,6 +253,21 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
|
||||
// assignment to slot contents denoted by "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)
|
||||
@ -401,3 +416,25 @@ std::optional<pair<YulString, YulString>> DataFlowAnalyzer::isSimpleStore(
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<YulString> DataFlowAnalyzer::isSimpleLoad(
|
||||
evmasm::Instruction _load,
|
||||
Expression const& _expression
|
||||
) const
|
||||
{
|
||||
yulAssert(
|
||||
_load == evmasm::Instruction::MLOAD ||
|
||||
_load == evmasm::Instruction::SLOAD,
|
||||
""
|
||||
);
|
||||
if (holds_alternative<FunctionCall>(_expression))
|
||||
{
|
||||
FunctionCall const& funCall = std::get<FunctionCall>(_expression);
|
||||
if (EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&m_dialect))
|
||||
if (auto const* builtin = dialect->builtin(funCall.functionName.name))
|
||||
if (builtin->instruction == _load)
|
||||
if (holds_alternative<Identifier>(funCall.arguments.at(0)))
|
||||
return std::get<Identifier>(funCall.arguments.at(0)).name;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -142,11 +142,20 @@ protected:
|
||||
/// Returns true iff the variable is in scope.
|
||||
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(
|
||||
evmasm::Instruction _store,
|
||||
ExpressionStatement const& _statement
|
||||
) 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;
|
||||
/// Side-effects of user-defined functions. Worst-case side-effects are assumed
|
||||
/// if this is not provided or the function is not found.
|
||||
|
@ -51,10 +51,10 @@ void DeadCodeEliminator::operator()(Block& _block)
|
||||
tie(controlFlowChange, index) = TerminationFinder{m_dialect}.firstUnconditionalControlFlowChange(_block.statements);
|
||||
|
||||
// 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(
|
||||
remove_if(
|
||||
_block.statements.begin() + index + 1,
|
||||
_block.statements.begin() + static_cast<ptrdiff_t>(index) + 1,
|
||||
_block.statements.end(),
|
||||
[] (Statement const& _s) { return !holds_alternative<yul::FunctionDefinition>(_s); }
|
||||
),
|
||||
@ -63,4 +63,3 @@ void DeadCodeEliminator::operator()(Block& _block)
|
||||
|
||||
ASTModifier::operator()(_block);
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ void ExpressionJoiner::decrementLatestStatementPointer()
|
||||
void ExpressionJoiner::resetLatestStatementPointer()
|
||||
{
|
||||
m_currentBlock = nullptr;
|
||||
m_latestStatementInBlock = size_t(-1);
|
||||
m_latestStatementInBlock = numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
Statement* ExpressionJoiner::latestStatement()
|
||||
|
@ -194,7 +194,7 @@ void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
|
||||
|
||||
void IntroduceControlFlowSSA::operator()(ForLoop& _for)
|
||||
{
|
||||
(*this)(_for.pre);
|
||||
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_for.body);
|
||||
@ -357,11 +357,7 @@ void PropagateValues::operator()(Assignment& _assignment)
|
||||
|
||||
void PropagateValues::operator()(ForLoop& _for)
|
||||
{
|
||||
// This will clear the current value in case of a reassignment inside the
|
||||
// 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);
|
||||
yulAssert(_for.pre.statements.empty(), "For loop init rewriter not run.");
|
||||
|
||||
Assignments assignments;
|
||||
assignments(_for.body);
|
||||
|
@ -85,7 +85,7 @@ class NameDispenser;
|
||||
*
|
||||
* TODO Which transforms are required to keep this idempotent?
|
||||
*
|
||||
* Prerequisite: Disambiguator.
|
||||
* Prerequisite: Disambiguator, ForLoopInitRewriter.
|
||||
*/
|
||||
class SSATransform: public ASTModifier
|
||||
{
|
||||
|
@ -180,7 +180,7 @@ pair<TerminationFinder::ControlFlow, size_t> TerminationFinder::firstUncondition
|
||||
if (controlFlow != ControlFlow::FlowOut)
|
||||
return {controlFlow, i};
|
||||
}
|
||||
return {ControlFlow::FlowOut, size_t(-1)};
|
||||
return {ControlFlow::FlowOut, numeric_limits<size_t>::max()};
|
||||
}
|
||||
|
||||
TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement)
|
||||
|
@ -178,14 +178,14 @@ bool StackCompressor::run(
|
||||
eliminateVariables(
|
||||
_dialect,
|
||||
std::get<Block>(_object.code->statements.at(0)),
|
||||
stackSurplus.at({}),
|
||||
static_cast<size_t>(stackSurplus.at({})),
|
||||
allowMSizeOptimzation
|
||||
);
|
||||
}
|
||||
|
||||
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))
|
||||
continue;
|
||||
|
||||
@ -193,7 +193,7 @@ bool StackCompressor::run(
|
||||
eliminateVariables(
|
||||
_dialect,
|
||||
fun,
|
||||
stackSurplus.at(fun.name),
|
||||
static_cast<size_t>(stackSurplus.at(fun.name)),
|
||||
allowMSizeOptimzation
|
||||
);
|
||||
}
|
||||
|
@ -34,5 +34,5 @@ else
|
||||
BUILD_DIR="$1"
|
||||
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
|
||||
|
261
scripts/error_codes.py
Executable file
261
scripts/error_codes.py
Executable file
@ -0,0 +1,261 @@
|
||||
#! /usr/bin/env python3
|
||||
import random
|
||||
import re
|
||||
import os
|
||||
import getopt
|
||||
import sys
|
||||
from os import path
|
||||
|
||||
ENCODING = "utf-8"
|
||||
SOURCE_FILE_PATTERN = r"\b\d+_error\b"
|
||||
|
||||
|
||||
def read_file(file_name):
|
||||
content = None
|
||||
try:
|
||||
with open(file_name, "r", encoding=ENCODING) as f:
|
||||
content = f.read()
|
||||
finally:
|
||||
if content == None:
|
||||
print(f"Error reading: {file_name}")
|
||||
return content
|
||||
|
||||
|
||||
def write_file(file_name, content):
|
||||
with open(file_name, "w", encoding=ENCODING) as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def in_comment(source, pos):
|
||||
slash_slash_pos = source.rfind("//", 0, pos)
|
||||
lf_pos = source.rfind("\n", 0, pos)
|
||||
if slash_slash_pos > lf_pos:
|
||||
return True
|
||||
slash_star_pos = source.rfind("/*", 0, pos)
|
||||
star_slash_pos = source.rfind("*/", 0, pos)
|
||||
return slash_star_pos > star_slash_pos
|
||||
|
||||
|
||||
def find_ids_in_source_file(file_name, ids):
|
||||
source = read_file(file_name)
|
||||
for m in re.finditer(SOURCE_FILE_PATTERN, source):
|
||||
if in_comment(source, m.start()):
|
||||
continue
|
||||
underscore_pos = m.group(0).index("_")
|
||||
id = m.group(0)[0:underscore_pos]
|
||||
if id in ids:
|
||||
ids[id] += 1
|
||||
else:
|
||||
ids[id] = 1
|
||||
|
||||
|
||||
def get_used_ids(file_names):
|
||||
used_ids = {}
|
||||
for file_name in file_names:
|
||||
find_ids_in_source_file(file_name, used_ids)
|
||||
return used_ids
|
||||
|
||||
|
||||
def get_next_id(available_ids):
|
||||
assert len(available_ids) > 0, "Out of IDs"
|
||||
next_id = random.choice(list(available_ids))
|
||||
available_ids.remove(next_id)
|
||||
return next_id
|
||||
|
||||
|
||||
def fix_ids_in_file(file_name, available_ids, used_ids):
|
||||
source = read_file(file_name)
|
||||
|
||||
k = 0
|
||||
destination = []
|
||||
for m in re.finditer(SOURCE_FILE_PATTERN, source):
|
||||
destination.extend(source[k:m.start()])
|
||||
|
||||
underscore_pos = m.group(0).index("_")
|
||||
id = m.group(0)[0:underscore_pos]
|
||||
|
||||
# incorrect id or id has a duplicate somewhere
|
||||
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or used_ids[id] > 1):
|
||||
assert id in used_ids
|
||||
new_id = get_next_id(available_ids)
|
||||
assert new_id not in used_ids
|
||||
used_ids[id] -= 1
|
||||
else:
|
||||
new_id = id
|
||||
|
||||
destination.extend(new_id + "_error")
|
||||
k = m.end()
|
||||
|
||||
destination.extend(source[k:])
|
||||
|
||||
destination = ''.join(destination)
|
||||
if source != destination:
|
||||
write_file(file_name, destination)
|
||||
print(f"Fixed file: {file_name}")
|
||||
|
||||
|
||||
def fix_ids(used_ids, file_names):
|
||||
available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys()
|
||||
for file_name in file_names:
|
||||
fix_ids_in_file(file_name, available_ids, used_ids)
|
||||
|
||||
|
||||
def find_files(top_dir, sub_dirs, extensions):
|
||||
"""Builds a list of files with given extensions in specified subdirectories"""
|
||||
|
||||
source_file_names = []
|
||||
for dir in sub_dirs:
|
||||
for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")):
|
||||
for file_name in file_names:
|
||||
_, ext = path.splitext(file_name)
|
||||
if ext in extensions:
|
||||
source_file_names.append(path.join(root, file_name))
|
||||
|
||||
return source_file_names
|
||||
|
||||
|
||||
def find_ids_in_test_file(file_name):
|
||||
source = read_file(file_name)
|
||||
pattern = r"^// (.*Error|Warning) \d\d\d\d:"
|
||||
return {m.group(0)[-5:-1] for m in re.finditer(pattern, source, flags=re.MULTILINE)}
|
||||
|
||||
|
||||
def find_ids_in_test_files(file_names):
|
||||
used_ids = set()
|
||||
for file_name in file_names:
|
||||
used_ids |= find_ids_in_test_file(file_name)
|
||||
return used_ids
|
||||
|
||||
|
||||
def print_ids(ids):
|
||||
for k, id in enumerate(sorted(ids)):
|
||||
if k % 10 > 0:
|
||||
print(" ", end="")
|
||||
elif k > 0:
|
||||
print()
|
||||
print(id, end="")
|
||||
|
||||
|
||||
def examine_id_coverage(top_dir, used_ids):
|
||||
test_sub_dirs = [
|
||||
path.join("test", "libsolidity", "errorRecoveryTests"),
|
||||
path.join("test", "libsolidity", "smtCheckerTests"),
|
||||
path.join("test", "libsolidity", "syntaxTests")
|
||||
]
|
||||
test_file_names = find_files(
|
||||
top_dir,
|
||||
test_sub_dirs,
|
||||
[".sol"]
|
||||
)
|
||||
covered_ids = find_ids_in_test_files(test_file_names)
|
||||
|
||||
print(f"IDs in source files: {len(used_ids)}")
|
||||
print(f"IDs in test files : {len(covered_ids)} ({len(covered_ids) - len(used_ids)})")
|
||||
print()
|
||||
|
||||
unused_covered_ids = covered_ids - used_ids
|
||||
if len(unused_covered_ids) != 0:
|
||||
print("Error. The following error codes found in tests, but not in sources:")
|
||||
print_ids(unused_covered_ids)
|
||||
return 1
|
||||
|
||||
used_uncovered_ids = used_ids - covered_ids
|
||||
if len(used_uncovered_ids) != 0:
|
||||
print("The following error codes found in sources, but not in tests:")
|
||||
print_ids(used_uncovered_ids)
|
||||
print("\n\nPlease make sure to add appropriate tests.")
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv):
|
||||
# pylint: disable=too-many-branches, too-many-locals, too-many-statements
|
||||
|
||||
check = False
|
||||
fix = False
|
||||
no_confirm = False
|
||||
examine_coverage = False
|
||||
next = False
|
||||
opts, args = getopt.getopt(argv, "", ["check", "fix", "no-confirm", "examine-coverage", "next"])
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '--check':
|
||||
check = True
|
||||
elif opt == "--fix":
|
||||
fix = True
|
||||
elif opt == '--no-confirm':
|
||||
no_confirm = True
|
||||
elif opt == '--examine-coverage':
|
||||
examine_coverage = True
|
||||
elif opt == '--next':
|
||||
next = True
|
||||
|
||||
if [check, fix, examine_coverage, next].count(True) != 1:
|
||||
print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next")
|
||||
exit(1)
|
||||
|
||||
cwd = os.getcwd()
|
||||
|
||||
source_file_names = find_files(
|
||||
cwd,
|
||||
["libevmasm", "liblangutil", "libsolc", "libsolidity", "libsolutil", "libyul", "solc"],
|
||||
[".h", ".cpp"]
|
||||
)
|
||||
used_ids = get_used_ids(source_file_names)
|
||||
|
||||
ok = True
|
||||
for id in sorted(used_ids):
|
||||
if len(id) != 4:
|
||||
print(f"ID {id} length != 4")
|
||||
ok = False
|
||||
if id[0] == "0":
|
||||
print(f"ID {id} starts with zero")
|
||||
ok = False
|
||||
if used_ids[id] > 1:
|
||||
print(f"ID {id} appears {used_ids[id]} times")
|
||||
ok = False
|
||||
|
||||
if examine_coverage:
|
||||
if not ok:
|
||||
print("Incorrect IDs have to be fixed before applying --examine-coverage")
|
||||
res = examine_id_coverage(cwd, used_ids.keys())
|
||||
exit(res)
|
||||
|
||||
random.seed()
|
||||
|
||||
if next:
|
||||
if not ok:
|
||||
print("Incorrect IDs have to be fixed before applying --next")
|
||||
available_ids = {str(id) for id in range(1000, 10000)} - used_ids.keys()
|
||||
next_id = get_next_id(available_ids)
|
||||
print(f"Next ID: {next_id}")
|
||||
exit(0)
|
||||
|
||||
if ok:
|
||||
print("No incorrect IDs found")
|
||||
exit(0)
|
||||
|
||||
if check:
|
||||
exit(1)
|
||||
|
||||
assert fix, "Unexpected state, should not come here without --fix"
|
||||
|
||||
if not no_confirm:
|
||||
answer = input(
|
||||
"\nDo you want to fix incorrect IDs?\n"
|
||||
"Please commit current changes first, and review the results when the script finishes.\n"
|
||||
"[Y/N]? "
|
||||
)
|
||||
while len(answer) == 0 or answer not in "YNyn":
|
||||
answer = input("[Y/N]? ")
|
||||
if answer not in "yY":
|
||||
exit(1)
|
||||
|
||||
fix_ids(used_ids, source_file_names)
|
||||
print("Fixing completed")
|
||||
exit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
@ -1,175 +0,0 @@
|
||||
#! /usr/bin/env python3
|
||||
import random
|
||||
import re
|
||||
import os
|
||||
import getopt
|
||||
import sys
|
||||
from os import path
|
||||
|
||||
ENCODING = "utf-8"
|
||||
PATTERN = r"\b\d+_error\b"
|
||||
|
||||
|
||||
def read_file(file_name):
|
||||
content = None
|
||||
try:
|
||||
with open(file_name, "r", encoding=ENCODING) as f:
|
||||
content = f.read()
|
||||
finally:
|
||||
if content == None:
|
||||
print(f"Error reading: {file_name}")
|
||||
return content
|
||||
|
||||
|
||||
def write_file(file_name, content):
|
||||
with open(file_name, "w", encoding=ENCODING) as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def in_comment(source, pos):
|
||||
slash_slash_pos = source.rfind("//", 0, pos)
|
||||
lf_pos = source.rfind("\n", 0, pos)
|
||||
if slash_slash_pos > lf_pos:
|
||||
return True
|
||||
slash_star_pos = source.rfind("/*", 0, pos)
|
||||
star_slash_pos = source.rfind("*/", 0, pos)
|
||||
return slash_star_pos > star_slash_pos
|
||||
|
||||
|
||||
def find_ids_in_file(file_name, ids):
|
||||
source = read_file(file_name)
|
||||
for m in re.finditer(PATTERN, source):
|
||||
if in_comment(source, m.start()):
|
||||
continue
|
||||
underscore_pos = m.group(0).index("_")
|
||||
id = m.group(0)[0:underscore_pos]
|
||||
if id in ids:
|
||||
ids[id] += 1
|
||||
else:
|
||||
ids[id] = 1
|
||||
|
||||
|
||||
def get_used_ids(file_names):
|
||||
used_ids = {}
|
||||
for file_name in file_names:
|
||||
find_ids_in_file(file_name, used_ids)
|
||||
return used_ids
|
||||
|
||||
|
||||
def get_id(available_ids, used_ids):
|
||||
while len(available_ids) > 0:
|
||||
k = random.randrange(len(available_ids))
|
||||
id = list(available_ids.keys())[k]
|
||||
del available_ids[id]
|
||||
if id not in used_ids:
|
||||
return id
|
||||
assert False, "Out of IDs"
|
||||
|
||||
|
||||
def fix_ids_in_file(file_name, available_ids, used_ids):
|
||||
source = read_file(file_name)
|
||||
|
||||
k = 0
|
||||
destination = []
|
||||
for m in re.finditer(PATTERN, source):
|
||||
destination.extend(source[k:m.start()])
|
||||
|
||||
underscore_pos = m.group(0).index("_")
|
||||
id = m.group(0)[0:underscore_pos]
|
||||
|
||||
# incorrect id or id has a duplicate somewhere
|
||||
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or used_ids[id] > 1):
|
||||
assert id in used_ids
|
||||
new_id = get_id(available_ids, used_ids)
|
||||
used_ids[id] -= 1
|
||||
else:
|
||||
new_id = id
|
||||
|
||||
destination.extend(new_id + "_error")
|
||||
k = m.end()
|
||||
|
||||
destination.extend(source[k:])
|
||||
|
||||
destination = ''.join(destination)
|
||||
if source != destination:
|
||||
write_file(file_name, destination)
|
||||
print(f"Fixed file: {file_name}")
|
||||
|
||||
|
||||
def fix_ids(used_ids, file_names):
|
||||
available_ids = {str(id): None for id in range(1000, 10000)}
|
||||
for file_name in file_names:
|
||||
fix_ids_in_file(file_name, available_ids, used_ids)
|
||||
|
||||
|
||||
def find_source_files(top_dir):
|
||||
"""Builds the list of .h and .cpp files in top_dir directory"""
|
||||
|
||||
source_file_names = []
|
||||
dirs = ['libevmasm', 'liblangutil', 'libsolc', 'libsolidity', 'libsolutil', 'libyul', 'solc']
|
||||
|
||||
for dir in dirs:
|
||||
for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")):
|
||||
for file_name in file_names:
|
||||
_, ext = path.splitext(file_name)
|
||||
if ext in [".h", ".cpp"]:
|
||||
source_file_names.append(path.join(root, file_name))
|
||||
|
||||
return source_file_names
|
||||
|
||||
|
||||
def main(argv):
|
||||
check_only = False
|
||||
noconfirm = False
|
||||
opts, args = getopt.getopt(argv, "", ["check-only", "noconfirm"])
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '--check-only':
|
||||
check_only = True
|
||||
elif opt == '--noconfirm':
|
||||
noconfirm = True
|
||||
|
||||
random.seed()
|
||||
cwd = os.getcwd()
|
||||
|
||||
source_file_names = find_source_files(cwd)
|
||||
|
||||
used_ids = get_used_ids(source_file_names)
|
||||
|
||||
ok = True
|
||||
for id in sorted(used_ids):
|
||||
if len(id) != 4:
|
||||
print(f"ID {id} length != 4")
|
||||
ok = False
|
||||
if id[0] == "0":
|
||||
print(f"ID {id} starts with zero")
|
||||
ok = False
|
||||
if used_ids[id] > 1:
|
||||
print(f"ID {id} appears {used_ids[id]} times")
|
||||
ok = False
|
||||
|
||||
if ok:
|
||||
print("No incorrect IDs found")
|
||||
exit(0)
|
||||
|
||||
if check_only:
|
||||
exit(1)
|
||||
|
||||
if not noconfirm:
|
||||
answer = input(
|
||||
"\nDo you want to fix incorrect IDs?\n"
|
||||
"Please commit current changes first, and review the results when the script finishes.\n"
|
||||
"[Y/N]? "
|
||||
)
|
||||
while len(answer) == 0 or answer not in "YNyn":
|
||||
answer = input("[Y/N]? ")
|
||||
if answer not in "yY":
|
||||
exit(1)
|
||||
|
||||
fix_ids(used_ids, source_file_names)
|
||||
print("Fixing completed")
|
||||
exit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
39
scripts/get_nightly_version.sh
Executable file
39
scripts/get_nightly_version.sh
Executable file
@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# Prints the exact version string that would be used to describe a nightly
|
||||
# build of the compiler.
|
||||
#
|
||||
# The documentation for solidity is hosted at:
|
||||
#
|
||||
# https://solidity.readthedocs.org
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
# This file is part of solidity.
|
||||
#
|
||||
# solidity is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# solidity is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with solidity. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
# (c) 2017 solidity contributors.
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
set -e
|
||||
|
||||
script_dir="$(dirname "$0")"
|
||||
|
||||
solidity_version=$("${script_dir}/get_version.sh")
|
||||
last_commit_timestamp=$(git log -1 --date=iso --format=%ad HEAD)
|
||||
last_commit_date=$(date --date="$last_commit_timestamp" --utc +%Y.%-m.%-d)
|
||||
last_commit_hash=$(git rev-parse --short=8 HEAD)
|
||||
|
||||
echo "v${solidity_version}-nightly.${last_commit_date}+commit.${last_commit_hash}"
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
# 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:
|
||||
#
|
||||
|
@ -170,7 +170,7 @@ case $(uname -s) in
|
||||
# Debian
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
Debian*)
|
||||
Debian*|Raspbian)
|
||||
#Debian
|
||||
. /etc/os-release
|
||||
install_z3=""
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/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:
|
||||
#
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/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:
|
||||
# ./yul_coverage.sh [--no-stats] [--successful] [--internal-compiler-errors]
|
||||
|
@ -58,6 +58,8 @@
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/adaptor/filtered.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#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))
|
||||
{
|
||||
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))
|
||||
{
|
||||
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
|
||||
m_onlyAssemble = true;
|
||||
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);
|
||||
Machine targetMachine = Machine::EVM;
|
||||
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;
|
||||
if (m_args.count(g_strYulOptimizations))
|
||||
@ -1273,6 +1301,13 @@ bool CommandLineInterface::processInput()
|
||||
|
||||
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))
|
||||
{
|
||||
// switch to linker mode
|
||||
@ -1879,4 +1914,21 @@ void CommandLineInterface::outputCompilationResults()
|
||||
}
|
||||
}
|
||||
|
||||
size_t CommandLineInterface::countEnabledOptions(vector<string> const& _optionNames) const
|
||||
{
|
||||
size_t count = 0;
|
||||
for (string const& _option: _optionNames)
|
||||
count += m_args.count(_option);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
string CommandLineInterface::joinOptionNames(vector<string> const& _optionNames, string _separator)
|
||||
{
|
||||
return boost::algorithm::join(
|
||||
_optionNames | boost::adaptors::transformed([](string const& _option){ return "--" + _option; }),
|
||||
_separator
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -103,6 +103,9 @@ private:
|
||||
/// @arg _json json string to be written
|
||||
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_onlyAssemble = false;
|
||||
|
@ -58,10 +58,10 @@ int parseUnsignedInteger(string::iterator& _it, string::iterator _end)
|
||||
|
||||
CommonSyntaxTest::CommonSyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion):
|
||||
EVMVersionRestrictedTestCase(_filename),
|
||||
m_sources(m_reader.sources().sources),
|
||||
m_expectations(parseExpectations(m_reader.stream())),
|
||||
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)
|
||||
@ -94,12 +94,10 @@ void CommonSyntaxTest::printSource(ostream& _stream, string const& _linePrefix,
|
||||
if (m_sources.empty())
|
||||
return;
|
||||
|
||||
bool outputSourceNames = true;
|
||||
if (m_sources.size() == 1 && m_sources.begin()->first.empty())
|
||||
outputSourceNames = false;
|
||||
bool outputSourceNames = (m_sources.size() != 1 || !m_sources.begin()->first.empty());
|
||||
|
||||
if (_formatted)
|
||||
for (auto const& [name, source]: m_sources)
|
||||
for (auto const& [name, source]: m_sources)
|
||||
if (_formatted)
|
||||
{
|
||||
if (source.empty())
|
||||
continue;
|
||||
@ -139,8 +137,7 @@ void CommonSyntaxTest::printSource(ostream& _stream, string const& _linePrefix,
|
||||
}
|
||||
_stream << formatting::RESET;
|
||||
}
|
||||
else
|
||||
for (auto const& [name, source]: m_sources)
|
||||
else
|
||||
{
|
||||
if (outputSourceNames)
|
||||
_stream << _linePrefix << "==== Source: " + name << " ====" << endl;
|
||||
@ -165,8 +162,10 @@ void CommonSyntaxTest::printErrorList(
|
||||
{
|
||||
{
|
||||
AnsiColorized scope(_stream, _formatted, {BOLD, (error.type == "Warning") ? YELLOW : RED});
|
||||
_stream << _linePrefix;
|
||||
_stream << error.type << ": ";
|
||||
_stream << _linePrefix << error.type;
|
||||
if (error.errorId.has_value())
|
||||
_stream << ' ' << error.errorId->error;
|
||||
_stream << ": ";
|
||||
}
|
||||
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;
|
||||
|
||||
auto typeBegin = it;
|
||||
while (it != line.end() && *it != ':')
|
||||
while (it != line.end() && isalpha(*it))
|
||||
++it;
|
||||
string errorType(typeBegin, it);
|
||||
|
||||
// skip colon
|
||||
if (it != line.end()) it++;
|
||||
skipWhitespace(it, line.end());
|
||||
|
||||
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());
|
||||
|
||||
int locationStart = -1;
|
||||
@ -242,6 +245,7 @@ vector<SyntaxTestError> CommonSyntaxTest::parseExpectations(istream& _stream)
|
||||
string errorMessage(it, line.end());
|
||||
expectations.emplace_back(SyntaxTestError{
|
||||
move(errorType),
|
||||
move(errorId),
|
||||
move(errorMessage),
|
||||
move(sourceName),
|
||||
locationStart,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user