Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth 2020-09-01 11:57:45 +02:00
commit ff0ec61e1e
398 changed files with 6045 additions and 1431 deletions

View File

@ -21,8 +21,8 @@ parameters:
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:7a4d5271b5552139d9f2caefc50d42f401bf74132cf8f253e199e11c80ab42de"
ubuntu-1604-clang-ossfuzz-docker-image:
type: string
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-2
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:efaabb3c143f64171be596932c62013bcfd7f73b1fbcb832025a34dd2b6e6922"
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-3
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:6fa6914bd81abcac4b162c738e6ff05d87cefe7655e3859c7a827e5a8ec20dc7"
emscripten-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc"
@ -487,7 +487,7 @@ jobs:
<<: *build_ubuntu2004
environment:
CMAKE_BUILD_TYPE: Debug
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF
CMAKE_OPTIONS: -DCMAKE_CXX_STANDARD=20 -DUSE_CVC4=OFF
MAKEFLAGS: -j 10
steps:
- checkout
@ -500,7 +500,6 @@ jobs:
CC: clang
CXX: clang++
TERM: xterm
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
MAKEFLAGS: -j 3
steps:
- checkout
@ -893,13 +892,6 @@ workflows:
- t_ubu_release_cli: *workflow_ubuntu2004_release
- t_ubu_release_soltest: *workflow_ubuntu2004_release
# ASan build and tests
- b_ubu_asan: *workflow_trigger_on_tags
- b_ubu_asan_clang: *workflow_trigger_on_tags
- t_ubu_asan_constantinople: *workflow_ubuntu2004_asan
- t_ubu_asan_constantinople_clang: *workflow_ubuntu2004_asan_clang
- t_ubu_asan_cli: *workflow_ubuntu2004_asan
# Emscripten build and selected tests
- b_ems: *workflow_trigger_on_tags
- t_ems_solcjs: *workflow_emscripten
@ -926,3 +918,10 @@ workflows:
# Code Coverage enabled build and tests
- b_ubu_codecov: *workflow_trigger_on_tags
- t_ubu_codecov: *workflow_ubuntu2004_codecov
# ASan build and tests
- b_ubu_asan: *workflow_trigger_on_tags
- b_ubu_asan_clang: *workflow_trigger_on_tags
- t_ubu_asan_constantinople: *workflow_ubuntu2004_asan
- t_ubu_asan_constantinople_clang: *workflow_ubuntu2004_asan_clang
- t_ubu_asan_cli: *workflow_ubuntu2004_asan

View File

@ -5,17 +5,37 @@
### 0.7.1 (unreleased)
Language Features:
* Allow function definitions outside of contracts, behaving much like internal library functions.
Compiler Features:
* SMTChecker: Add underflow and overflow as verification conditions in the CHC engine.
* Standard JSON Interface: Do not run EVM bytecode code generation, if only Yul IR or EWasm output is requested.
* Yul: Report error when using non-string literals for ``datasize()``, ``dataoffset()``, ``linkersymbol()``, ``loadimmutable()``, ``setimmutable()``.
* Yul Optimizer: LoopInvariantCodeMotion can move reading operations outside for-loops as long as the affected area is not modified inside the loop.
* SMTChecker: Support conditional operator.
* SMTChecker: Support bitwise or, xor and not operators.
Bugfixes:
* AST: Remove ``null`` member values also when the compiler is used in standard-json-mode.
* Immutables: Properly treat complex assignment and increment/decrement as both reading and writing and thus disallow it everywhere for immutable variables.
* Optimizer: Keep side-effects of ``x`` in ``byte(a, shr(b, x))`` even if the constants ``a`` and ``b`` would make the expression zero unconditionally. This optimizer rule is very hard if not impossible to trigger in a way that it can result in invalid code, though.
* Scanner: Fix bug where whitespace would be allowed within the ``->`` token (e.g. ``function f() - > x {}`` becomes invalid in inline assembly and Yul).
* SMTChecker: Fix internal error in BMC function inlining.
* SMTChecker: Fix internal error on array implicit conversion.
* SMTChecker: Fix internal error on fixed bytes index access.
* SMTChecker: Fix internal error on lvalue unary operators with tuples.
* SMTChecker: Fix internal error on tuple assignment.
* SMTChecker: Fix internal error on tuples of one element that have tuple type.
* SMTChecker: Fix soundness of array ``pop``.
* References Resolver: Fix internal bug when using constructor for library.
* Yul Optimizer: Make function inlining order more resilient to whether or not unrelated source files are present.
* Yul Optimizer: Ensure that Yul keywords are not mistakenly used by the NameDispenser and VarNameCleaners. The bug would manifest as uncompilable code.
* Type Checker: Disallow signed literals as exponent in exponentiation operator.
* Allow `type(Contract).name` for abstract contracts and interfaces.
* Type Checker: Disallow structs containing nested mapping in memory as parameters for library functions.
* Type Checker: Disallow ``using for`` directive inside interfaces.
* Immutables: Disallow assigning immutables more than once during their declaration.
### 0.7.0 (2020-07-28)

View File

@ -6,6 +6,9 @@
- [ ] Readthedocs account, access to the Solidity project
- [ ] Write access to https://github.com/ethereum/homebrew-ethereum
### Documentation check
- [ ] Run `make linkcheck` from within `docs/` and fix any broken links it finds. Ignore false positives caused by `href` anchors and dummy links not meant to work.
### Blog Post
- [ ] Create a post on https://github.com/ethereum/solidity-blog and explain some of the new features or concepts.

View File

@ -157,9 +157,10 @@ elseif (DEFINED MSVC)
add_compile_options(/wd4800) # disable forcing value to bool 'true' or 'false' (performance warning) (4800)
add_compile_options(-D_WIN32_WINNT=0x0600) # declare Windows Vista API requirement
add_compile_options(-DNOMINMAX) # undefine windows.h MAX && MIN macros cause it cause conflicts with std::min && std::max functions
add_compile_options(/utf-8) # enable utf-8 encoding (solves warning 4819)
add_compile_options(/utf-8) # enable utf-8 encoding (solves warning 4819)
add_compile_options(-DBOOST_REGEX_NO_LIB) # disable automatic boost::regex library selection
add_compile_options(-D_REGEX_MAX_STACK_COUNT=200000L) # increase std::regex recursion depth limit
add_compile_options(/permissive-) # specify standards conformance mode to the compiler
# disable empty object file warning
set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221")

View File

@ -1,3 +1,8 @@
# Require C++17.
set(CMAKE_CXX_STANDARD 17) # This requires at least CMake 3.8 to accept this C++17 flag.
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT CMAKE_TOOLCHAIN_FILE)
# Use default toolchain file if none is provided.
set(

View File

@ -1,4 +0,0 @@
# Require C++20.
set(CMAKE_CXX_STANDARD 20) # This requires at least CMake 3.12 to understand this C++20 flag
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_EXTENSIONS OFF)

View File

@ -1,4 +0,0 @@
# Require C++17.
set(CMAKE_CXX_STANDARD 17) # This requires at least CMake 3.8 to accept this C++17 flag.
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_EXTENSIONS OFF)

View File

@ -1,487 +0,0 @@
// Copyright 2020 Gonçalo Sá <goncalo.sa@consensys.net>
// Copyright 2016-2019 Federico Bond <federicobond@gmail.com>
// Licensed under the MIT license. See LICENSE file in the project root for details.
// This grammar is much less strict than what Solidity currently parses
// to allow this to pass with older versions of Solidity.
grammar Solidity;
sourceUnit
: (pragmaDirective | importDirective | structDefinition | enumDefinition | contractDefinition)* EOF ;
pragmaDirective
: 'pragma' pragmaName ( ~';' )* ';' ;
pragmaName
: identifier ;
importDirective
: 'import' StringLiteralFragment ('as' identifier)? ';'
| 'import' ('*' | identifier) ('as' identifier)? 'from' StringLiteralFragment ';'
| 'import' '{' importDeclaration ( ',' importDeclaration )* '}' 'from' StringLiteralFragment ';' ;
importDeclaration
: identifier ('as' identifier)? ;
contractDefinition
: 'abstract'? ( 'contract' | 'interface' | 'library' ) identifier
( 'is' inheritanceSpecifier (',' inheritanceSpecifier )* )?
'{' contractPart* '}' ;
inheritanceSpecifier
: userDefinedTypeName ( '(' expressionList? ')' )? ;
contractPart
: stateVariableDeclaration
| usingForDeclaration
| structDefinition
| modifierDefinition
| functionDefinition
| eventDefinition
| enumDefinition ;
stateVariableDeclaration
: typeName
( PublicKeyword | InternalKeyword | PrivateKeyword | ConstantKeyword | ImmutableKeyword | overrideSpecifier )*
identifier ('=' expression)? ';' ;
overrideSpecifier : 'override' ( '(' userDefinedTypeName (',' userDefinedTypeName)* ')' )? ;
usingForDeclaration
: 'using' identifier 'for' ('*' | typeName) ';' ;
structDefinition
: 'struct' identifier
'{' ( variableDeclaration ';' (variableDeclaration ';')* )? '}' ;
modifierDefinition
: 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* ( ';' | block ) ;
functionDefinition
: functionDescriptor parameterList modifierList returnParameters? ( ';' | block ) ;
functionDescriptor
: 'function' ( identifier | ReceiveKeyword | FallbackKeyword )?
| ConstructorKeyword
| FallbackKeyword
| ReceiveKeyword ;
returnParameters
: 'returns' parameterList ;
modifierList
: ( modifierInvocation | stateMutability | ExternalKeyword
| PublicKeyword | InternalKeyword | PrivateKeyword | VirtualKeyword | overrideSpecifier )* ;
modifierInvocation
: identifier ( '(' expressionList? ')' )? ;
eventDefinition
: 'event' identifier eventParameterList AnonymousKeyword? ';' ;
enumDefinition
: 'enum' identifier '{' enumValue? (',' enumValue)* '}' ;
enumValue
: identifier ;
parameterList
: '(' ( parameter (',' parameter)* )? ')' ;
parameter
: typeName storageLocation? identifier? ;
eventParameterList
: '(' ( eventParameter (',' eventParameter)* )? ')' ;
eventParameter
: typeName IndexedKeyword? identifier? ;
variableDeclaration
: typeName storageLocation? identifier ;
typeName
: elementaryTypeName
| userDefinedTypeName
| mapping
| typeName '[' expression? ']'
| functionTypeName ;
userDefinedTypeName
: identifier ( '.' identifier )* ;
mapping
: 'mapping' '(' mappingKey '=>' typeName ')' ;
mappingKey
: elementaryTypeName
| userDefinedTypeName ;
functionTypeName
: 'function' parameterList modifierList returnParameters? ;
storageLocation
: 'memory' | 'storage' | 'calldata';
stateMutability
: PureKeyword | ConstantKeyword | ViewKeyword | PayableKeyword ;
block
: '{' statement* '}' ;
statement
: ifStatement
| tryStatement
| whileStatement
| forStatement
| block
| inlineAssemblyStatement
| doWhileStatement
| continueStatement
| breakStatement
| returnStatement
| throwStatement
| emitStatement
| simpleStatement ;
expressionStatement
: expression ';' ;
ifStatement
: 'if' '(' expression ')' statement ( 'else' statement )? ;
tryStatement : 'try' expression returnParameters? block catchClause+ ;
// In reality catch clauses still are not processed as below
// the identifier can only be a set string: "Error". But plans
// of the Solidity team include possible expansion so we'll
// leave this as is, befitting with the Solidity docs.
catchClause : 'catch' ( identifier? parameterList )? block ;
whileStatement
: 'while' '(' expression ')' statement ;
forStatement
: 'for' '(' ( simpleStatement | ';' ) ( expressionStatement | ';' ) expression? ')' statement ;
simpleStatement
: ( variableDeclarationStatement | expressionStatement ) ;
inlineAssemblyStatement
: 'assembly' StringLiteralFragment? assemblyBlock ;
doWhileStatement
: 'do' statement 'while' '(' expression ')' ';' ;
continueStatement
: 'continue' ';' ;
breakStatement
: 'break' ';' ;
returnStatement
: 'return' expression? ';' ;
// throw is no longer supported by latest Solidity.
throwStatement
: 'throw' ';' ;
emitStatement
: 'emit' functionCall ';' ;
// 'var' is no longer supported by latest Solidity.
variableDeclarationStatement
: ( 'var' identifierList | variableDeclaration | '(' variableDeclarationList ')' ) ( '=' expression )? ';';
variableDeclarationList
: variableDeclaration? (',' variableDeclaration? )* ;
identifierList
: '(' ( identifier? ',' )* identifier? ')' ;
elementaryTypeName
: 'address' PayableKeyword? | 'bool' | 'string' | 'var' | Int | Uint | 'byte' | Byte | Fixed | Ufixed ;
Int
: 'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' | 'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' | 'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' | 'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256' ;
Uint
: 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' | 'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' | 'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' | 'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256' ;
Byte
: 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32' ;
Fixed
: 'fixed' | ( 'fixed' [0-9]+ 'x' [0-9]+ ) ;
Ufixed
: 'ufixed' | ( 'ufixed' [0-9]+ 'x' [0-9]+ ) ;
expression
: expression ('++' | '--')
| 'new' typeName
| expression '[' expression? ']'
| expression '[' expression? ':' expression? ']'
| expression '.' identifier
| expression '{' nameValueList '}'
| expression '(' functionCallArguments ')'
| PayableKeyword '(' expression ')'
| '(' expression ')'
| ('++' | '--') expression
| ('+' | '-') expression
| ('after' | 'delete') expression
| '!' expression
| '~' expression
| expression '**' expression
| expression ('*' | '/' | '%') expression
| expression ('+' | '-') expression
| expression ('<<' | '>>') expression
| expression '&' expression
| expression '^' expression
| expression '|' expression
| expression ('<' | '>' | '<=' | '>=') expression
| expression ('==' | '!=') expression
| expression '&&' expression
| expression '||' expression
| expression '?' expression ':' expression
| expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') expression
| primaryExpression ;
primaryExpression
: BooleanLiteral
| numberLiteral
| hexLiteral
| stringLiteral
| unicodeStringLiteral
| identifier ('[' ']')?
| TypeKeyword
| tupleExpression
| typeNameExpression ('[' ']')? ;
expressionList
: expression (',' expression)* ;
nameValueList
: nameValue (',' nameValue)* ','? ;
nameValue
: identifier ':' expression ;
functionCallArguments
: '{' nameValueList? '}'
| expressionList? ;
functionCall
: expression '(' functionCallArguments ')' ;
tupleExpression
: '(' ( expression? ( ',' expression? )* ) ')'
| '[' ( expression ( ',' expression )* )? ']' ;
typeNameExpression
: elementaryTypeName
| userDefinedTypeName ;
assemblyItem
: identifier
| assemblyBlock
| assemblyExpression
| assemblyLocalDefinition
| assemblyAssignment
| assemblyStackAssignment
| labelDefinition
| assemblySwitch
| assemblyFunctionDefinition
| assemblyFor
| assemblyIf
| BreakKeyword
| ContinueKeyword
| LeaveKeyword
| subAssembly
| numberLiteral
| stringLiteral
| hexLiteral ;
assemblyBlock
: '{' assemblyItem* '}' ;
assemblyExpression
: assemblyCall | assemblyLiteral | assemblyIdentifier ;
assemblyCall
: ( 'return' | 'address' | 'byte' | identifier ) ( '(' assemblyExpression? ( ',' assemblyExpression )* ')' )? ;
assemblyLocalDefinition
: 'let' assemblyIdentifierList ( ':=' assemblyExpression )? ;
assemblyAssignment
: assemblyIdentifierList ':=' assemblyExpression ;
assemblyIdentifierList
: assemblyIdentifier ( ',' assemblyIdentifier )* ;
assemblyIdentifier
: identifier ( '.' identifier )* ;
assemblyStackAssignment
: '=:' identifier ;
labelDefinition
: identifier ':' ;
assemblySwitch
: 'switch' assemblyExpression assemblyCase* ;
assemblyCase
: 'case' assemblyLiteral assemblyType? assemblyBlock
| 'default' assemblyBlock ;
assemblyFunctionDefinition
: 'function' identifier '(' assemblyTypedVariableList? ')'
assemblyFunctionReturns? assemblyBlock ;
assemblyFunctionReturns
: ( '-' '>' assemblyTypedVariableList ) ;
assemblyFor
: 'for' assemblyBlock assemblyExpression assemblyBlock assemblyBlock ;
assemblyIf
: 'if' assemblyExpression assemblyBlock ;
assemblyLiteral
: ( stringLiteral | DecimalNumber | HexNumber | hexLiteral | BooleanLiteral ) assemblyType? ;
assemblyTypedVariableList
: identifier assemblyType? ( ',' assemblyTypedVariableList )? ;
assemblyType
: ':' identifier ;
subAssembly
: 'assembly' identifier assemblyBlock ;
// 'finney' and 'szabo' are no longer supported as denominations by latest Solidity.
numberLiteral
: (DecimalNumber | HexNumber) (NumberUnit | Gwei | Finney | Szabo)?;
identifier
: (Gwei | Finney | Szabo | 'from' | 'calldata' | 'address' | Identifier) ;
BooleanLiteral
: 'true' | 'false' ;
DecimalNumber
: ( DecimalDigits | (DecimalDigits? '.' DecimalDigits) ) ( [eE] '-'? DecimalDigits )? ;
fragment
DecimalDigits
: [0-9] ( '_'? [0-9] )* ;
HexNumber
: '0' [xX] HexDigits ;
fragment
HexDigits
: HexCharacter ( '_'? HexCharacter )* ;
NumberUnit
: 'wei' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years' ;
Gwei: 'gwei' ;
Szabo: 'szabo' ;
Finney: 'finney' ;
HexLiteralFragment
: 'hex' (('"' HexDigits? '"') | ('\'' HexDigits? '\'')) ;
hexLiteral : HexLiteralFragment+ ;
fragment
HexPair
: HexCharacter HexCharacter ;
fragment
HexCharacter
: [0-9A-Fa-f] ;
ReservedKeyword
: 'after'
| 'case'
| 'default'
| 'final'
| 'in'
| 'inline'
| 'let'
| 'match'
| 'null'
| 'of'
| 'relocatable'
| 'static'
| 'switch'
| 'typeof' ;
AnonymousKeyword : 'anonymous' ;
BreakKeyword : 'break' ;
ConstantKeyword : 'constant' ;
ImmutableKeyword : 'immutable' ;
ContinueKeyword : 'continue' ;
LeaveKeyword : 'leave' ;
ExternalKeyword : 'external' ;
IndexedKeyword : 'indexed' ;
InternalKeyword : 'internal' ;
PayableKeyword : 'payable' ;
PrivateKeyword : 'private' ;
PublicKeyword : 'public' ;
VirtualKeyword : 'virtual' ;
PureKeyword : 'pure' ;
TypeKeyword : 'type' ;
ViewKeyword : 'view' ;
ConstructorKeyword : 'constructor' ;
FallbackKeyword : 'fallback' ;
ReceiveKeyword : 'receive' ;
Identifier
: IdentifierStart IdentifierPart* ;
fragment
IdentifierStart
: [a-zA-Z$_] ;
fragment
IdentifierPart
: [a-zA-Z0-9$_] ;
stringLiteral
: StringLiteralFragment+ ;
StringLiteralFragment
: '"' DoubleQuotedStringCharacter* '"'
| '\'' SingleQuotedStringCharacter* '\'' ;
unicodeStringLiteral
: UnicodeStringLiteralFragment+ ;
UnicodeStringLiteralFragment
: 'unicode"' DoubleQuotedStringCharacter* '"'
| 'unicode\'' SingleQuotedStringCharacter* '\'' ;
fragment
DoubleQuotedStringCharacter
: ~["\r\n\\] | ('\\' .) ;
fragment
SingleQuotedStringCharacter
: ~['\r\n\\] | ('\\' .) ;
WS
: [ \t\r\n\u000C]+ -> skip ;
COMMENT
: '/*' .*? '*/' -> channel(HIDDEN) ;
LINE_COMMENT
: '//' ~[\r\n]* -> channel(HIDDEN) ;

View File

@ -43,7 +43,7 @@ Solidity Logo License
:alt: Creative Commons License
The Solidity logo is distributed and licensed under a `Creative Commons
Attribution 4.0 International License <http://creativecommons.org/licenses/by/4.0/>`_.
Attribution 4.0 International License <https://creativecommons.org/licenses/by/4.0/>`_.
This is the most permissive Creative Commons license and allows reuse
and modifications for any purpose.

View File

@ -39,7 +39,9 @@ def setup(sphinx):
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
extensions = [ 'sphinx_a4doc' ]
a4_base_path = os.path.dirname(__file__) + '/grammar'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@ -83,7 +85,7 @@ else:
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build', 'contracts', 'types', 'examples']
exclude_patterns = ['_build', 'contracts', 'types', 'examples', 'grammar']
# The reST default role (used for this markup: `text`) to use for all
# documents.

View File

@ -21,7 +21,7 @@ is copied to all the places in the code where they are accessed. For these value
can sometimes be cheaper than immutable values.
Not all types for constants and immutables are implemented at this time. The only supported types are
`strings <strings>`_ (only for constants) and `value types <value-types>`_.
:ref:`strings <strings>` (only for constants) and :ref:`value types <value-types>`.
::

View File

@ -160,6 +160,6 @@ where the long hexadecimal number is equal to
Additional Resources for Understanding Events
==============================================
- `Javascript documentation <https://github.com/ethereum/wiki/wiki/JavaScript-API#contract-events>`_
- `Example usage of events <https://github.com/debris/smart-exchange/blob/master/lib/contracts/SmartExchange.sol>`_
- `How to access them in js <https://github.com/debris/smart-exchange/blob/master/lib/exchange_transactions.js>`_
- `Javascript documentation <https://github.com/ethereum/web3.js/blob/1.x/docs/web3-eth-contract.rst#events>`_
- `Example usage of events <https://github.com/ethchange/smart-exchange/blob/master/lib/contracts/SmartExchange.sol>`_
- `How to access them in js <https://github.com/ethchange/smart-exchange/blob/master/lib/exchange_transactions.js>`_

View File

@ -18,7 +18,7 @@ if they are marked ``virtual``. For details, please see
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >0.6.99 <0.8.0;
pragma solidity >0.7.0 <0.8.0;
contract owned {
constructor() { owner = msg.sender; }

View File

@ -6,6 +6,34 @@
Functions
*********
Functions can be defined inside and outside of contracts.
Functions outside of a contract, also called "free functions", always have implicit ``internal``
:ref:`visibility<visibility-and-getters>`. Their code is included in all contracts
that call them, similar to internal library functions.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >0.7.0 <0.8.0;
function sum(uint[] memory _arr) pure returns (uint s) {
for (uint i = 0; i < _arr.length; i++)
s += _arr[i];
}
contract ArrayExample {
bool found;
function f(uint[] memory _arr) public {
// This calls the free function internally.
// The compiler will add its code to the contract.
uint s = sum(_arr);
require(s >= 10);
found = true;
}
}
.. _function-parameters-return-variables:
Function Parameters and Return Variables

View File

@ -331,7 +331,7 @@ Modifier Overriding
===================
Function modifiers can override each other. This works in the same way as
`function overriding <function-overriding>`_ (except that there is no overloading for modifiers). The
:ref:`function overriding <function-overriding>` (except that there is no overloading for modifiers). The
``virtual`` keyword must be used on the overridden modifier
and the ``override`` keyword must be used in the overriding modifier:

View File

@ -102,7 +102,7 @@ The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest`` (or its wrapper ``scripts/soltest.sh``),
as well as command line tests and compilation tests.
The test system automatically tries try to discover the location of the ``evmone`` library
The test system automatically tries to discover the location of the ``evmone`` library
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS. If it is not found, tests that
use it are skipped. These tests are ``libsolididty/semanticTests``, ``libsolidity/GasCosts``,
@ -254,7 +254,7 @@ inside the input. We have a specialized binary called ``solfuzzer`` which takes
and fails whenever it encounters an internal compiler error, segmentation fault or similar, but
does not fail if e.g., the code contains an error. This way, fuzzing tools can find internal problems in the compiler.
We mainly use `AFL <http://lcamtuf.coredump.cx/afl/>`_ for fuzzing. You need to download and
We mainly use `AFL <https://lcamtuf.coredump.cx/afl/>`_ for fuzzing. You need to download and
install the AFL packages from your repositories (afl, afl-clang) or build them manually.
Next, build Solidity (or just the ``solfuzzer`` binary) with AFL as your compiler:
@ -388,7 +388,7 @@ local slang and references, making your language as clear to all readers as poss
Title Case for Headings
-----------------------
Use `title case <http://titlecase.com>`_ for headings. This means capitalise all principal words in
Use `title case <https://titlecase.com>`_ for headings. This means capitalise all principal words in
titles, but not articles, conjunctions, and prepositions unless they start the
title.

View File

@ -114,7 +114,7 @@ In general, ECDSA signatures consist of two parameters,
parameter called ``v``, that you can use to verify which
account's private key was used to sign the message, and
the transaction's sender. Solidity provides a built-in
function `ecrecover <mathematical-and-cryptographic-functions>`_ that
function :ref:`ecrecover <mathematical-and-cryptographic-functions>` that
accepts a message along with the ``r``, ``s`` and ``v`` parameters
and returns the address that was used to sign the message.
@ -127,7 +127,7 @@ apart. You can do this on the client-side, but doing it inside
the smart contract means you only need to send one signature
parameter rather than three. Splitting apart a byte array into
its constituent parts is a mess, so we use
`inline assembly <assembly>`_ to do the job in the ``splitSignature``
:doc:`inline assembly <assembly>` to do the job in the ``splitSignature``
function (the third function in the full contract at the end of this section).
Computing the Message Hash

View File

@ -2,5 +2,12 @@
Language Grammar
****************
.. literalinclude:: Solidity.g4
:language: antlr
.. a4:autogrammar:: Solidity
:only-reachable-from: Solidity.sourceUnit
:undocumented:
:cc-to-dash:
.. a4:autogrammar:: SolidityLexer
:only-reachable-from: Solidity.sourceUnit
:fragments:
:cc-to-dash:

511
docs/grammar/Solidity.g4 Normal file
View File

@ -0,0 +1,511 @@
/**
* Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform.
*/
grammar Solidity;
options { tokenVocab=SolidityLexer; }
/**
* On top level, Solidity allows pragmas, import directives, and
* definitions of contracts, interfaces, libraries, structs and enums.
*/
sourceUnit: (
pragmaDirective
| importDirective
| contractDefinition
| interfaceDefinition
| libraryDefinition
| functionDefinition
| structDefinition
| enumDefinition
)* EOF;
//@doc: inline
pragmaDirective: Pragma PragmaToken+ PragmaSemicolon;
/**
* Import directives import identifiers from different files.
*/
importDirective:
Import (
(path (As unitAlias=identifier)?)
| (symbolAliases From path)
| (Mul As unitAlias=identifier From path)
) Semicolon;
//@doc: inline
//@doc:name aliases
importAliases: symbol=identifier (As alias=identifier)?;
/**
* Path of a file to be imported.
*/
path: NonEmptyStringLiteral;
/**
* List of aliases for symbols to be imported.
*/
symbolAliases: LBrace aliases+=importAliases (Comma aliases+=importAliases)* RBrace;
/**
* Top-level definition of a contract.
*/
contractDefinition:
Abstract? Contract name=identifier
inheritanceSpecifierList?
LBrace contractBodyElement* RBrace;
/**
* Top-level definition of an interface.
*/
interfaceDefinition:
Interface name=identifier
inheritanceSpecifierList?
LBrace contractBodyElement* RBrace;
/**
* Top-level definition of a library.
*/
libraryDefinition: Library name=identifier LBrace contractBodyElement* RBrace;
//@doc:inline
inheritanceSpecifierList:
Is inheritanceSpecifiers+=inheritanceSpecifier
(Comma inheritanceSpecifiers+=inheritanceSpecifier)*?;
/**
* Inheritance specifier for contracts and interfaces.
* Can optionally supply base constructor arguments.
*/
inheritanceSpecifier: name=userDefinedTypeName arguments=callArgumentList?;
/**
* Declarations that can be used in contracts, interfaces and libraries.
*
* Note that interfaces and libraries may not contain constructors, interfaces may not contain state variables
* and libraries may not contain fallback, receive functions nor non-constant state variables.
*/
contractBodyElement:
constructorDefinition
| functionDefinition
| modifierDefinition
| fallbackReceiveFunctionDefinition
| structDefinition
| enumDefinition
| stateVariableDeclaration
| eventDefinition
| usingDirective;
//@doc:inline
namedArgument: name=identifier Colon value=expression;
/**
* Arguments when calling a function or a similar callable object.
* The arguments are either given as comma separated list or as map of named arguments.
*/
callArgumentList: LParen ((expression (Comma expression)*)? | LBrace (namedArgument (Comma namedArgument)*)? RBrace) RParen;
/**
* Qualified name of a user defined type.
*/
userDefinedTypeName: identifier (Period identifier)*;
/**
* Call to a modifier. If the modifier takes no arguments, the argument list can be skipped entirely
* (including opening and closing parentheses).
*/
modifierInvocation: identifier callArgumentList?;
/**
* Visibility for functions and function types.
*/
visibility: Internal | External | Private | Public;
/**
* A list of parameters, such as function arguments or return values.
*/
parameterList: parameters+=parameterDeclaration (Comma parameters+=parameterDeclaration)*;
//@doc:inline
parameterDeclaration: type=typeName location=dataLocation? name=identifier?;
/**
* Definition of a constructor.
* Must always supply an implementation.
* Note that specifying internal or public visibility is deprecated.
*/
constructorDefinition
locals[boolean payableSet = false, boolean visibilitySet = false]
:
Constructor LParen (arguments=parameterList)? RParen
(
modifierInvocation
| {!$payableSet}? Payable {$payableSet = true;}
| {!$visibilitySet}? Internal {$visibilitySet = true;}
| {!$visibilitySet}? Public {$visibilitySet = true;}
)*
body=block;
/**
* State mutability for function types.
* The default mutability 'non-payable' is assumed if no mutability is specified.
*/
stateMutability: Pure | View | Payable;
/**
* An override specifier used for functions, modifiers or state variables.
* In cases where there are ambiguous declarations in several base contracts being overridden,
* a complete list of base contracts has to be given.
*/
overrideSpecifier: Override (LParen overrides+=userDefinedTypeName (Comma overrides+=userDefinedTypeName)* RParen)?;
/**
* The definition of contract, library and interface functions.
* Depending on the context in which the function is defined, further restrictions may apply,
* e.g. functions in interfaces have to be unimplemented, i.e. may not contain a body block.
*/
functionDefinition
locals[
boolean visibilitySet = false,
boolean mutabilitySet = false,
boolean virtualSet = false,
boolean overrideSpecifierSet = false
]
:
Function (identifier | Fallback | Receive)
LParen (arguments=parameterList)? RParen
(
{!$visibilitySet}? visibility {$visibilitySet = true;}
| {!$mutabilitySet}? stateMutability {$mutabilitySet = true;}
| modifierInvocation
| {!$virtualSet}? Virtual {$virtualSet = true;}
| {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;}
)*
(Returns LParen returnParameters=parameterList RParen)?
(Semicolon | body=block);
/**
* The definition of a modifier.
* Note that within the body block of a modifier, the underscore cannot be used as identifier,
* but is used as placeholder statement for the body of a function to which the modifier is applied.
*/
modifierDefinition
locals[
boolean virtualSet = false,
boolean overrideSpecifierSet = false
]
:
Modifier name=identifier
(LParen (arguments=parameterList)? RParen)?
(
{!$virtualSet}? Virtual {$virtualSet = true;}
| {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;}
)*
(Semicolon | body=block);
/**
* Definitions of the special fallback and receive functions.
*/
fallbackReceiveFunctionDefinition
locals[
boolean visibilitySet = false,
boolean mutabilitySet = false,
boolean virtualSet = false,
boolean overrideSpecifierSet = false
]
:
kind=(Fallback | Receive) LParen RParen
(
{!$visibilitySet}? visibility {$visibilitySet = true;}
| {!$mutabilitySet}? stateMutability {$mutabilitySet = true;}
| modifierInvocation
| {!$virtualSet}? Virtual {$virtualSet = true;}
| {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;}
)*
(Semicolon | body=block);
/**
* Definition of a struct. Can occur at top-level within a source unit or within a contract, library or interface.
*/
structDefinition: Struct name=identifier LBrace members=structMember+ RBrace;
/**
* The declaration of a named struct member.
*/
structMember: type=typeName name=identifier Semicolon;
/**
* Definition of an enum. Can occur at top-level within a source unit or within a contract, library or interface.
*/
enumDefinition: Enum name=identifier LBrace enumValues+=identifier (Comma enumValues+=identifier)* RBrace;
/**
* The declaration of a state variable.
*/
stateVariableDeclaration
locals [boolean constantnessSet = false, boolean visibilitySet = false, boolean overrideSpecifierSet = false]
:
type=typeName
(
{!$visibilitySet}? Public {$visibilitySet = true;}
| {!$visibilitySet}? Private {$visibilitySet = true;}
| {!$visibilitySet}? Internal {$visibilitySet = true;}
| {!$constantnessSet}? Constant {$constantnessSet = true;}
| {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;}
| {!$constantnessSet}? Immutable {$constantnessSet = true;}
)*
name=identifier
(Assign initialValue=expression)?
Semicolon;
/**
* Parameter of an event.
*/
eventParameter: type=typeName Indexed? name=identifier?;
/**
* Definition of an event. Can occur in contracts, libraries or interfaces.
*/
eventDefinition:
Event name=identifier
LParen (parameters+=eventParameter (Comma parameters+=eventParameter)*)? RParen
Anonymous?
Semicolon;
/**
* Using directive to bind library functions to types.
* Can occur within contracts and libraries.
*/
usingDirective: Using userDefinedTypeName For (Mul | typeName) Semicolon;
/**
* A type name can be an elementary type, a function type, a mapping type, a user-defined type
* (e.g. a contract or struct) or an array type.
*/
typeName: elementaryTypeName[true] | functionTypeName | mappingType | userDefinedTypeName | typeName LBrack expression? RBrack;
elementaryTypeName[boolean allowAddressPayable]: Address | {$allowAddressPayable}? Address Payable | Bool | String | Bytes | SignedIntegerType | UnsignedIntegerType | FixedBytes | Fixed | Ufixed;
functionTypeName
locals [boolean visibilitySet = false, boolean mutabilitySet = false]
:
Function LParen (arguments=parameterList)? RParen
(
{!$visibilitySet}? visibility {$visibilitySet = true;}
| {!$mutabilitySet}? stateMutability {$mutabilitySet = true;}
)*
(Returns LParen returnParameters=parameterList RParen)?;
/**
* The declaration of a single variable.
*/
variableDeclaration: type=typeName location=dataLocation? name=identifier;
dataLocation: Memory | Storage | Calldata;
/**
* Complex expression.
* Can be an index access, an index range access, a member access, a function call (with optional function call options),
* a type conversion, an unary or binary expression, a comparison or assignment, a ternary expression,
* a new-expression (i.e. a contract creation or the allocation of a dynamic memory array),
* a tuple, an inline array or a primary expression (i.e. an identifier, literal or type name).
*/
expression:
expression LBrack index=expression? RBrack # IndexAccess
| expression LBrack start=expression? Colon end=expression? RBrack # IndexRangeAccess
| expression Period (identifier | Address) # MemberAccess
| expression LBrace (namedArgument (Comma namedArgument)*)? RBrace # FunctionCallOptions
| expression callArgumentList # FunctionCall
| Payable callArgumentList # PayableConversion
| Type LParen typeName RParen # MetaType
| (Inc | Dec | Not | BitNot | Delete | Sub) expression # UnaryPrefixOperation
| expression (Inc | Dec) # UnarySuffixOperation
|<assoc=right> expression Exp expression # ExpOperation
| expression (Mul | Div | Mod) expression # MulDivModOperation
| expression (Add | Sub) expression # AddSubOperation
| expression (Shl | Sar | Shr) expression # ShiftOperation
| expression BitAnd expression # BitAndOperation
| expression BitXor expression # BitXorOperation
| expression BitOr expression # BitOrOperation
| expression (LessThan | GreaterThan | LessThanOrEqual | GreaterThanOrEqual) expression # OrderComparison
| expression (Equal | NotEqual) expression # EqualityComparison
| expression And expression # AndOperation
| expression Or expression # OrOperation
|<assoc=right> expression Conditional expression Colon expression # Conditional
|<assoc=right> expression assignOp expression # Assignment
| New typeName # NewExpression
| tupleExpression # Tuple
| inlineArrayExpression # InlineArray
| (
identifier
| literal
| elementaryTypeName[false]
| userDefinedTypeName
) # PrimaryExpression
;
//@doc:inline
assignOp: Assign | AssignBitOr | AssignBitXor | AssignBitAnd | AssignShl | AssignSar | AssignShr | AssignAdd | AssignSub | AssignMul | AssignDiv | AssignMod;
tupleExpression: LParen (expression? ( Comma expression?)* ) RParen;
/**
* An inline array expression denotes a statically sized array of the common type of the contained expressions.
*/
inlineArrayExpression: LBrack (expression ( Comma expression)* ) RBrack;
/**
* Besides regular non-keyword Identifiers, the 'from' keyword can also occur as identifier outside of import statements.
*/
identifier: Identifier | From;
literal: stringLiteral | numberLiteral | booleanLiteral | hexStringLiteral | unicodeStringLiteral;
booleanLiteral: True | False;
/**
* A full string literal consists of either one or several consecutive quoted strings.
*/
stringLiteral: StringLiteral+;
/**
* A full hex string literal that consists of either one or several consecutive hex strings.
*/
hexStringLiteral: HexString+;
/**
* A full unicode string literal that consists of either one or several consecutive unicode strings.
*/
unicodeStringLiteral: UnicodeStringLiteral+;
/**
* Number literals can be decimal or hexadecimal numbers with an optional unit.
*/
numberLiteral: (DecimalNumber | HexNumber) NumberUnit?;
/**
* A curly-braced block of statements. Opens its own scope.
*/
block: LBrace statement* RBrace;
statement:
block
| simpleStatement
| ifStatement
| forStatement
| whileStatement
| doWhileStatement
| continueStatement
| breakStatement
| tryStatement
| returnStatement
| emitStatement
| assemblyStatement
;
//@doc:inline
simpleStatement: variableDeclarationStatement | expressionStatement;
/**
* If statement with optional else part.
*/
ifStatement: If LParen expression RParen statement (Else statement)?;
/**
* For statement with optional init, condition and post-loop part.
*/
forStatement: For LParen (simpleStatement | Semicolon) (expressionStatement | Semicolon) expression? RParen statement;
whileStatement: While LParen expression RParen statement;
doWhileStatement: Do statement While LParen expression RParen Semicolon;
/**
* A continue statement. Only allowed inside for, while or do-while loops.
*/
continueStatement: Continue Semicolon;
/**
* A break statement. Only allowed inside for, while or do-while loops.
*/
breakStatement: Break Semicolon;
/**
* A try statement. The contained expression needs to be an external function call or a contract creation.
*/
tryStatement: Try expression (Returns LParen returnParameters=parameterList RParen)? block catchClause+;
/**
* The catch clause of a try statement.
*/
catchClause: Catch (identifier? LParen (arguments=parameterList) RParen)? block;
returnStatement: Return expression? Semicolon;
/**
* An emit statement. The contained expression needs to refer to an event.
*/
emitStatement: Emit expression callArgumentList Semicolon;
/**
* An inline assembly block.
* The contents of an inline assembly block use a separate scanner/lexer, i.e. the set of keywords and
* allowed identifiers is different inside an inline assembly block.
*/
assemblyStatement: Assembly AssemblyDialect? AssemblyLBrace yulStatement* YulRBrace;
//@doc:inline
variableDeclarationList: variableDeclarations+=variableDeclaration (Comma variableDeclarations+=variableDeclaration)*;
/**
* A tuple of variable names to be used in variable declarations.
* May contain empty fields.
*/
variableDeclarationTuple:
LParen
(Comma* variableDeclarations+=variableDeclaration)
(Comma (variableDeclarations+=variableDeclaration)?)*
RParen;
/**
* A variable declaration statement.
* A single variable may be declared without initial value, whereas a tuple of variables can only be
* declared with initial value.
*/
variableDeclarationStatement: ((variableDeclaration (Assign expression)?) | (variableDeclarationTuple Assign expression)) Semicolon;
expressionStatement: expression Semicolon;
mappingType: Mapping LParen key=mappingKeyType DoubleArrow value=typeName RParen;
/**
* Only elementary types or user defined types are viable as mapping keys.
*/
mappingKeyType: elementaryTypeName[false] | userDefinedTypeName;
/**
* A Yul statement within an inline assembly block.
* continue and break statements are only valid within for loops.
* leave statements are only valid within function bodies.
*/
yulStatement:
yulBlock
| yulVariableDeclaration
| yulAssignment
| yulFunctionCall
| yulIfStatement
| yulForStatement
| yulSwitchStatement
| YulLeave
| YulBreak
| YulContinue
| yulFunctionDefinition;
yulBlock: YulLBrace yulStatement* YulRBrace;
/**
* The declaration of one or more Yul variables with optional initial value.
* If multiple variables are declared, only a function call is a valid initial value.
*/
yulVariableDeclaration:
(YulLet variables+=YulIdentifier (YulAssign yulExpression)?)
| (YulLet variables+=YulIdentifier (YulComma variables+=YulIdentifier)* (YulAssign yulFunctionCall)?);
/**
* Any expression can be assigned to a single Yul variable, whereas
* multi-assignments require a function call on the right-hand side.
*/
yulAssignment: yulPath YulAssign yulExpression | (yulPath (YulComma yulPath)+) YulAssign yulFunctionCall;
yulIfStatement: YulIf cond=yulExpression body=yulBlock;
yulForStatement: YulFor init=yulBlock cond=yulExpression post=yulBlock body=yulBlock;
//@doc:inline
yulSwitchCase: YulCase yulLiteral yulBlock;
/**
* A Yul switch statement can consist of only a default-case (deprecated) or
* one or more non-default cases optionally followed by a default-case.
*/
yulSwitchStatement:
YulSwitch yulExpression
(
(yulSwitchCase+ (YulDefault yulBlock)?)
| (YulDefault yulBlock)
);
yulFunctionDefinition:
YulFunction YulIdentifier
YulLParen (arguments+=YulIdentifier (YulComma arguments+=YulIdentifier)*)? YulRParen
(YulArrow returnParameters+=YulIdentifier (YulComma returnParameters+=YulIdentifier)*)?
body=yulBlock;
/**
* While only identifiers without dots can be declared within inline assembly,
* paths containing dots can refer to declarations outside the inline assembly block.
*/
yulPath: YulIdentifier (YulPeriod YulIdentifier)*;
/**
* A call to a function with return values can only occur as right-hand side of an assignment or
* a variable declaration.
*/
yulFunctionCall: (YulIdentifier | YulEVMBuiltin) YulLParen (yulExpression (YulComma yulExpression)*)? YulRParen;
yulBoolean: YulTrue | YulFalse;
yulLiteral: YulDecimalNumber | YulStringLiteral | YulHexNumber | yulBoolean;
yulExpression: yulPath | yulFunctionCall | yulLiteral;

View File

@ -0,0 +1,334 @@
lexer grammar SolidityLexer;
/**
* Keywords reserved for future use in Solidity.
*/
ReservedKeywords:
'after' | 'alias' | 'apply' | 'auto' | 'case' | 'copyof' | 'default' | 'define' | 'final'
| 'implements' | 'in' | 'inline' | 'let' | 'macro' | 'match' | 'mutable' | 'null' | 'of'
| 'partial' | 'promise' | 'reference' | 'relocatable' | 'sealed' | 'sizeof' | 'static'
| 'supports' | 'switch' | 'typedef' | 'typeof' | 'unchecked' | 'var';
Pragma: 'pragma' -> pushMode(PragmaMode);
Abstract: 'abstract';
Anonymous: 'anonymous';
Address: 'address';
As: 'as';
Assembly: 'assembly' -> pushMode(AssemblyBlockMode);
Bool: 'bool';
Break: 'break';
Bytes: 'bytes';
Calldata: 'calldata';
Catch: 'catch';
Constant: 'constant';
Constructor: 'constructor';
Continue: 'continue';
Contract: 'contract';
Delete: 'delete';
Do: 'do';
Else: 'else';
Emit: 'emit';
Enum: 'enum';
Event: 'event';
External: 'external';
Fallback: 'fallback';
False: 'false';
Fixed: 'fixed' | ('fixed' [0-9]+ 'x' [0-9]+);
From: 'from';
/**
* Bytes types of fixed length.
* byte is an alias of bytes1.
*/
FixedBytes:
'byte' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' |
'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' |
'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' |
'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32';
For: 'for';
Function: 'function';
Hex: 'hex';
If: 'if';
Immutable: 'immutable';
Import: 'import';
Indexed: 'indexed';
Interface: 'interface';
Internal: 'internal';
Is: 'is';
Library: 'library';
Mapping: 'mapping';
Memory: 'memory';
Modifier: 'modifier';
New: 'new';
/**
* Unit denomination for numbers.
*/
NumberUnit: 'wei' | 'gwei' | 'ether' | 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years';
Override: 'override';
Payable: 'payable';
Private: 'private';
Public: 'public';
Pure: 'pure';
Receive: 'receive';
Return: 'return';
Returns: 'returns';
/**
* Sized signed integer types.
* int is an alias of int256.
*/
SignedIntegerType:
'int' | 'int8' | 'int16' | 'int24' | 'int32' | 'int40' | 'int48' | 'int56' | 'int64' |
'int72' | 'int80' | 'int88' | 'int96' | 'int104' | 'int112' | 'int120' | 'int128' |
'int136' | 'int144' | 'int152' | 'int160' | 'int168' | 'int176' | 'int184' | 'int192' |
'int200' | 'int208' | 'int216' | 'int224' | 'int232' | 'int240' | 'int248' | 'int256';
Storage: 'storage';
String: 'string';
Struct: 'struct';
True: 'true';
Try: 'try';
Type: 'type';
Ufixed: 'ufixed' | ('ufixed' [0-9]+ 'x' [0-9]+);
/**
* Sized unsigned integer types.
* uint is an alias of uint256.
*/
UnsignedIntegerType:
'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' | 'uint56' | 'uint64' |
'uint72' | 'uint80' | 'uint88' | 'uint96' | 'uint104' | 'uint112' | 'uint120' | 'uint128' |
'uint136' | 'uint144' | 'uint152' | 'uint160' | 'uint168' | 'uint176' | 'uint184' | 'uint192' |
'uint200' | 'uint208' | 'uint216' | 'uint224' | 'uint232' | 'uint240' | 'uint248' | 'uint256';
Using: 'using';
View: 'view';
Virtual: 'virtual';
While: 'while';
LParen: '(';
RParen: ')';
LBrack: '[';
RBrack: ']';
LBrace: '{';
RBrace: '}';
Colon: ':';
Semicolon: ';';
Period: '.';
Conditional: '?';
DoubleArrow: '=>';
RightArrow: '->';
Assign: '=';
AssignBitOr: '|=';
AssignBitXor: '^=';
AssignBitAnd: '&=';
AssignShl: '<<=';
AssignSar: '>>=';
AssignShr: '>>>=';
AssignAdd: '+=';
AssignSub: '-=';
AssignMul: '*=';
AssignDiv: '/=';
AssignMod: '%=';
Comma: ',';
Or: '||';
And: '&&';
BitOr: '|';
BitXor: '^';
BitAnd: '&';
Shl: '<<';
Sar: '>>';
Shr: '>>>';
Add: '+';
Sub: '-';
Mul: '*';
Div: '/';
Mod: '%';
Exp: '**';
Equal: '==';
NotEqual: '!=';
LessThan: '<';
GreaterThan: '>';
LessThanOrEqual: '<=';
GreaterThanOrEqual: '>=';
Not: '!';
BitNot: '~';
Inc: '++';
Dec: '--';
/**
* A single quoted string literal restricted to printable characters.
*/
StringLiteral: '"' DoubleQuotedStringCharacter* '"' | '\'' SingleQuotedStringCharacter* '\'';
/**
* A single non-empty quoted string literal.
*/
NonEmptyStringLiteral: '"' DoubleQuotedStringCharacter+ '"' | '\'' SingleQuotedStringCharacter+ '\'';
// Note that this will also be used for Yul string literals.
//@doc:inline
fragment DoubleQuotedStringCharacter: DoubleQuotedPrintable | EscapeSequence;
// Note that this will also be used for Yul string literals.
//@doc:inline
fragment SingleQuotedStringCharacter: SingleQuotedPrintable | EscapeSequence;
/**
* Any printable character except single quote or back slash.
*/
fragment SingleQuotedPrintable: [\u0020-\u0026\u0028-\u005B\u005D-\u007E];
/**
* Any printable character except double quote or back slash.
*/
fragment DoubleQuotedPrintable: [\u0020-\u0021\u0023-\u005B\u005D-\u007E];
/**
* Escape sequence.
* Apart from common single character escape sequences, line breaks can be escaped
* as well as four hex digit unicode escapes \\uXXXX and two digit hex escape sequences \\xXX are allowed.
*/
fragment EscapeSequence:
'\\' (
['"\\bfnrtv\n\r]
| 'u' HexCharacter HexCharacter HexCharacter HexCharacter
| 'x' HexCharacter HexCharacter
);
/**
* A single quoted string literal allowing arbitrary unicode characters.
*/
UnicodeStringLiteral:
'unicode"' DoubleQuotedUnicodeStringCharacter* '"'
| 'unicode\'' SingleQuotedUnicodeStringCharacter* '\'';
//@doc:inline
fragment DoubleQuotedUnicodeStringCharacter: ~["\r\n\\] | EscapeSequence;
//@doc:inline
fragment SingleQuotedUnicodeStringCharacter: ~['\r\n\\] | EscapeSequence;
/**
* Hex strings need to consist of an even number of hex digits that may be grouped using underscores.
*/
HexString: 'hex' (('"' EvenHexDigits? '"') | ('\'' EvenHexDigits? '\''));
/**
* Hex numbers consist of a prefix and an arbitrary number of hex digits that may be delimited by underscores.
*/
HexNumber: '0' 'x' HexDigits;
//@doc:inline
fragment HexDigits: HexCharacter ('_'? HexCharacter)*;
//@doc:inline
fragment EvenHexDigits: HexCharacter HexCharacter ('_'? HexCharacter HexCharacter)*;
//@doc:inline
fragment HexCharacter: [0-9A-Fa-f];
/**
* A decimal number literal consists of decimal digits that may be delimited by underscores and
* an optional positive or negative exponent.
* If the digits contain a decimal point, the literal has fixed point type.
*/
DecimalNumber: (DecimalDigits | (DecimalDigits? '.' DecimalDigits)) ([eE] '-'? DecimalDigits)?;
//@doc:inline
fragment DecimalDigits: [0-9] ('_'? [0-9])* ;
/**
* An identifier in solidity has to start with a letter, a dollar-sign or an underscore and
* may additionally contain numbers after the first symbol.
*/
Identifier: IdentifierStart IdentifierPart*;
//@doc:inline
fragment IdentifierStart: [a-zA-Z$_];
//@doc:inline
fragment IdentifierPart: [a-zA-Z0-9$_];
WS: [ \t\r\n\u000C]+ -> skip ;
COMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
LINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN);
mode AssemblyBlockMode;
//@doc:inline
AssemblyDialect: '"evmasm"';
AssemblyLBrace: '{' -> popMode, pushMode(YulMode);
AssemblyBlockWS: [ \t\r\n\u000C]+ -> skip ;
AssemblyBlockCOMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
AssemblyBlockLINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN) ;
mode YulMode;
YulBreak: 'break';
YulCase: 'case';
YulContinue: 'continue';
YulDefault: 'default';
YulFalse: 'false';
YulFor: 'for';
YulFunction: 'function';
YulIf: 'if';
YulLeave: 'leave';
YulLet: 'let';
YulSwitch: 'switch';
YulTrue: 'true';
/**
* Builtin functions in the EVM Yul dialect.
*/
YulEVMBuiltin:
'stop' | 'add' | 'sub' | 'mul' | 'div' | 'sdiv' | 'mod' | 'smod' | 'exp' | 'not'
| 'lt' | 'gt' | 'slt' | 'sgt' | 'eq' | 'iszero' | 'and' | 'or' | 'xor' | 'byte'
| 'shl' | 'shr' | 'sar' | 'addmod' | 'mulmod' | 'signextend' | 'keccak256'
| 'pop' | 'mload' | 'mstore' | 'mstore8' | 'sload' | 'sstore' | 'msize' | 'gas'
| 'address' | 'balance' | 'selfbalance' | 'caller' | 'callvalue' | 'calldataload'
| 'calldatasize' | 'calldatacopy' | 'extcodesize' | 'extcodecopy' | 'returndatasize'
| 'returndatacopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode'
| 'delegatecall' | 'staticcall' | 'return' | 'revert' | 'selfdestruct' | 'invalid'
| 'log0' | 'log1' | 'log2' | 'log3' | 'log4' | 'chainid' | 'origin' | 'gasprice'
| 'blockhash' | 'coinbase' | 'timestamp' | 'number' | 'difficulty' | 'gaslimit';
YulLBrace: '{' -> pushMode(YulMode);
YulRBrace: '}' -> popMode;
YulLParen: '(';
YulRParen: ')';
YulAssign: ':=';
YulPeriod: '.';
YulComma: ',';
YulArrow: '->';
/**
* Yul identifiers consist of letters, dollar signs, underscores and numbers, but may not start with a number.
* In inline assembly there cannot be dots in user-defined identifiers. Instead see yulPath for expressions
* consisting of identifiers with dots.
*/
YulIdentifier: YulIdentifierStart YulIdentifierPart*;
//@doc:inline
fragment YulIdentifierStart: [a-zA-Z$_];
//@doc:inline
fragment YulIdentifierPart: [a-zA-Z0-9$_];
/**
* Hex literals in Yul consist of a prefix and one or more hexadecimal digits.
*/
YulHexNumber: '0' 'x' [0-9a-fA-F]+;
/**
* Decimal literals in Yul may be zero or any sequence of decimal digits without leading zeroes.
*/
YulDecimalNumber: '0' | ([1-9] [0-9]*);
/**
* String literals in Yul consist of one or more double-quoted or single-quoted strings
* that may contain escape sequences and printable characters except unescaped line breaks or
* unescaped double-quotes or single-quotes, respectively.
*/
YulStringLiteral:
'"' DoubleQuotedStringCharacter* '"'
| '\'' SingleQuotedStringCharacter* '\'';
YulWS: [ \t\r\n\u000C]+ -> skip ;
YulCOMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
YulLINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN) ;
mode PragmaMode;
/**
* Pragma token. Can contain any kind of symbol except a semicolon.
* Note that currently the solidity parser only allows a subset of this.
*/
//@doc:name pragma-token
//@doc:no-diagram
PragmaToken: ~[;]+;
PragmaSemicolon: ';' -> popMode;
PragmaWS: [ \t\r\n\u000C]+ -> skip ;
PragmaCOMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
PragmaLINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN) ;

View File

@ -72,10 +72,10 @@ Community volunteers help translate this documentation into several languages.
They have varying degrees of completeness and up-to-dateness. The English
version stands as a reference.
* `French <http://solidity-fr.readthedocs.io>`_ (in progress)
* `French <https://solidity-fr.readthedocs.io>`_ (in progress)
* `Italian <https://github.com/damianoazzolini/solidity>`_ (in progress)
* `Japanese <https://solidity-jp.readthedocs.io>`_
* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress)
* `Korean <https://solidity-kr.readthedocs.io>`_ (in progress)
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
* `Simplified Chinese <https://learnblockchain.cn/docs/solidity/>`_ (in progress)
* `Spanish <https://solidity-es.readthedocs.io>`_

View File

@ -205,7 +205,7 @@ The following are dependencies for all builds of Solidity:
| `cvc4`_ (Optional) | For use with SMT checker. |
+-----------------------------------+-------------------------------------------------------+
.. _cvc4: http://cvc4.cs.stanford.edu/web/
.. _cvc4: https://cvc4.cs.stanford.edu/web/
.. _Git: https://git-scm.com/download
.. _Boost: https://www.boost.org
.. _CMake: https://cmake.org/download/
@ -243,10 +243,10 @@ command-line builds:
sudo xcodebuild -license accept
Our OS X build script uses `the Homebrew <http://brew.sh>`_
Our OS X build script uses `the Homebrew <https://brew.sh>`_
package manager for installing external dependencies.
Here's how to `uninstall Homebrew
<https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/FAQ.md#how-do-i-uninstall-homebrew>`_,
<https://docs.brew.sh/FAQ#how-do-i-uninstall-homebrew>`_,
if you ever want to start again from scratch.
Prerequisites - Windows

View File

@ -285,7 +285,7 @@ likely it will be.
since it is not up to the submitter of a transaction, but up to the miners to determine in which block the transaction is included.
If you want to schedule future calls of your contract, you can use
the `alarm clock <http://www.ethereum-alarm-clock.com/>`_ or a similar oracle service.
the `alarm clock <https://www.ethereum-alarm-clock.com/>`_ or a similar oracle service.
.. _the-ethereum-virtual-machine:

View File

@ -5,7 +5,7 @@ Layout of a Solidity Source File
Source files can contain an arbitrary number of
:ref:`contract definitions<contract_structure>`, import_ directives,
:ref:`pragma directives<pragma>` and
:ref:`struct<structs>` and :ref:`enum<enums>` definitions.
:ref:`struct<structs>`, :ref:`enum<enums>` and :ref:`function<functions>` definitions.
.. index:: ! license, spdx
@ -22,7 +22,7 @@ Every source file should start with a comment indicating its license:
The compiler does not validate that the license is part of the
`list allowed by SPDX <https://spdx.org/licenses/>`_, but
it does include the supplied string in the `bytecode metadata <metadata>`_.
it does include the supplied string in the :ref:`bytecode metadata <metadata>`.
If you do not want to specify a license or if the source code is
not open-source, please use the special value ``UNLICENSED``.

View File

@ -202,6 +202,6 @@ This automatically verifies the metadata since its hash is part of the bytecode.
Excess data corresponds to the constructor input data, which should be decoded
according to the interface and presented to the user.
In the repository `source-verify <https://github.com/ethereum/source-verify>`_
In the repository `sourcify <https://github.com/ethereum/sourcify>`_
(`npm package <https://www.npmjs.com/package/source-verify>`_) you can see
example code that shows how to use this feature.

View File

@ -36,7 +36,7 @@ for the purposes of NatSpec.
- For Vyper, use ``"""`` indented to the inner contents with bare
comments. See `Vyper
documentation <https://vyper.readthedocs.io/en/latest/structure-of-a-contract.html#natspec-metadata>`__.
documentation <https://vyper.readthedocs.io/en/latest/natspec.html>`__.
The following example shows a contract and a function using all available tags.
@ -195,8 +195,8 @@ JSON file as output:
}
Note that the key by which to find the methods is the function's
canonical signature as defined in the `Contract
ABI <Ethereum-Contract-ABI#signature>`__ and not simply the function's
canonical signature as defined in the :ref:`Contract
ABI <abi_function_selector>` and not simply the function's
name.
.. _header-developer-doc:

View File

@ -1,2 +1,3 @@
sphinx_rtd_theme>=0.3.1
pygments-lexer-solidity>=0.5.1
sphinx-a4doc>=1.2.1

View File

@ -75,25 +75,14 @@ Solidity Integrations
* `Vim Solidity <https://github.com/tomlion/vim-solidity/>`_
Plugin for the Vim editor providing syntax highlighting.
* `Vim Syntastic <https://github.com/scrooloose/syntastic>`_
* `Vim Syntastic <https://github.com/vim-syntastic/syntastic>`_
Plugin for the Vim editor providing compile checking.
* Visual Studio Code:
* `Visual Studio Code extension <http://juan.blanco.ws/solidity-contracts-in-visual-studio-code/>`_
* `Visual Studio Code extension <https://juan.blanco.ws/solidity-contracts-in-visual-studio-code/>`_
Solidity plugin for Microsoft Visual Studio Code that includes syntax highlighting and the Solidity compiler.
Discontinued:
* `Mix IDE <https://github.com/ethereum/mix/>`_
Qt based IDE for designing, debugging and testing solidity smart contracts.
* `Ethereum Studio <https://live.ether.camp/>`_
Specialized web IDE that also provides shell access to a complete Ethereum environment.
* `Visual Studio Extension <https://visualstudiogallery.msdn.microsoft.com/96221853-33c4-4531-bdd5-d2ea5acc4799/>`_
Solidity plugin for Microsoft Visual Studio that includes the Solidity compiler.
Solidity Tools
~~~~~~~~~~~~~~
@ -142,8 +131,5 @@ Solidity Tools
Third-Party Solidity Parsers and Grammars
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* `solidity-parser <https://github.com/ConsenSys/solidity-parser>`_
Solidity parser for JavaScript
* `Solidity Grammar for ANTLR 4 <https://github.com/federicobond/solidity-antlr4>`_
Solidity grammar for the ANTLR 4 parser generator
* `Solidity Parser for JavaScript <https://github.com/solidity-parser/parser>`_
A Solidity parser for JS built on top of a robust ANTLR4 grammar.

View File

@ -259,7 +259,7 @@ more special edge cases for signed numbers.
Try to use ``require`` to limit the size of inputs to a reasonable range and use the
:ref:`SMT checker<smt_checker>` to find potential overflows, or use a library like
`SafeMath <https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol>`_
`SafeMath <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol>`_
if you want all overflows to cause a revert.
Code such as ``require((balanceOf[_to] + _value) >= balanceOf[_to])`` can also help you check if values are what you expect.

View File

@ -43,12 +43,14 @@ visibility.
Functions
=========
Functions are the executable units of code within a contract.
Functions are the executable units of code. Functions are usually
defined inside a contract, but they can also be defined outside of
contracts.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.8.0;
pragma solidity >0.7.0 <0.8.0;
contract SimpleAuction {
function bid() public payable { // Function
@ -56,6 +58,11 @@ Functions are the executable units of code within a contract.
}
}
// Helper function defined outside of a contract
function helper(uint x) pure returns (uint) {
return x * 2;
}
:ref:`function-calls` can happen internally or externally
and have different levels of :ref:`visibility<visibility-and-getters>`
towards other contracts. :ref:`Functions<functions>` accept :ref:`parameters and return variables<function-parameters-return-variables>` to pass parameters

View File

@ -1145,11 +1145,11 @@ NatSpec
Solidity contracts can have a form of comments that are the basis of the
Ethereum Natural Language Specification Format.
Add comments above functions or contracts following `doxygen <http://www.doxygen.nl>`_ notation
Add comments above functions or contracts following `doxygen <https://www.doxygen.nl>`_ notation
of one or multiple lines starting with ``///`` or a
multiline comment starting with ``/**`` and ending with ``*/``.
For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments
For example, the contract from :ref:`a simple smart contract <simple-smart-contract>` with the comments
added looks like the one below::
// SPDX-License-Identifier: GPL-3.0
@ -1176,6 +1176,6 @@ added looks like the one below::
}
}
It is recommended that Solidity contracts are fully annotated using `NatSpec <natspec>`_ for all public interfaces (everything in the ABI).
It is recommended that Solidity contracts are fully annotated using :ref:`NatSpec <natspec>` for all public interfaces (everything in the ABI).
Please see the section about `NatSpec <natspec>`_ for a detailed explanation.
Please see the section about :ref:`NatSpec <natspec>` for a detailed explanation.

View File

@ -160,6 +160,8 @@ more details on error handling and when to use which function.
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
.. _mathematical-and-cryptographic-functions:
Mathematical and Cryptographic Functions
----------------------------------------
@ -193,17 +195,17 @@ Mathematical and Cryptographic Functions
``ecrecover`` returns an ``address``, and not an ``address payable``. See :ref:`address payable<address>` for
conversion, in case you need to transfer funds to the recovered address.
For further details, read `example usage <https://ethereum.stackexchange.com/q/1777/222>`_.
For further details, read `example usage <https://ethereum.stackexchange.com/questions/1777/workflow-on-signing-a-string-with-private-key-followed-by-signature-verificatio>`_.
.. warning::
If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without
requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed
for _transaction_ signatures (see `EIP-2 <http://eips.ethereum.org/EIPS/eip-2#specification>`_), but
for _transaction_ signatures (see `EIP-2 <https://eips.ethereum.org/EIPS/eip-2#specification>`_), but
the ecrecover function remained unchanged.
This is usually not a problem unless you require signatures to be unique or
use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/v2.3.0/api/cryptography#ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue.
use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.com/contracts/2.x/api/cryptography#ECDSA>`_ that you can use as a wrapper for ``ecrecover`` without this issue.
.. note::

View File

@ -441,11 +441,11 @@ Output Description
},
"deployedBytecode": {
..., // The same layout as above.
"immutableReferences": [
"immutableReferences": {
// There are two references to the immutable with AST ID 3, both 32 bytes long. One is
// at bytecode offset 42, the other at bytecode offset 80.
"3": [{ "start": 42, "length": 32 }, { "start": 80, "length": 32 }]
]
}
},
// The list of function hashes
"methodIdentifiers": {

View File

@ -476,9 +476,8 @@ which are explained in their own chapter.
TypeName = Identifier
TypedIdentifierList = Identifier ( ':' TypeName )? ( ',' Identifier ( ':' TypeName )? )*
Literal =
(NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ( ':' TypeName )?
(NumberLiteral | StringLiteral | TrueLiteral | FalseLiteral) ( ':' TypeName )?
NumberLiteral = HexNumber | DecimalNumber
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
TrueLiteral = 'true'
FalseLiteral = 'false'
@ -688,8 +687,6 @@ We will use a destructuring notation for the AST nodes.
L'[$parami] = vi and L'[$reti] = 0 for all i.
Let G'', L'', mode = E(Gn, L', block)
G'', Ln, L''[$ret1], ..., L''[$retm]
E(G, L, l: HexLiteral) = G, L, hexString(l),
where hexString decodes l from hex and left-aligns it into 32 bytes
E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l),
where utf8EncodeLeftAligned performs a utf8 encoding of l
and aligns it left into 32 bytes

View File

@ -155,8 +155,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
);
break;
default:
bool invMem = SemanticInformation::invalidatesMemory(_item.instruction());
bool invStor = SemanticInformation::invalidatesStorage(_item.instruction());
bool invMem =
SemanticInformation::memory(_item.instruction()) == SemanticInformation::Write;
bool invStor =
SemanticInformation::storage(_item.instruction()) == SemanticInformation::Write;
// We could be a bit more fine-grained here (CALL only invalidates part of
// memory, etc), but we do not for now.
if (invMem)
@ -420,4 +422,3 @@ KnownState::Id KnownState::tagUnion(set<u256> _tags)
return id;
}
}

View File

@ -233,7 +233,7 @@ bool SemanticInformation::movable(Instruction _instruction)
return true;
}
bool SemanticInformation::sideEffectFree(Instruction _instruction)
bool SemanticInformation::canBeRemoved(Instruction _instruction)
{
// These are not really functional.
assertThrow(!isDupInstruction(_instruction) && !isSwapInstruction(_instruction), AssemblyException, "");
@ -241,15 +241,15 @@ bool SemanticInformation::sideEffectFree(Instruction _instruction)
return !instructionInfo(_instruction).sideEffects;
}
bool SemanticInformation::sideEffectFreeIfNoMSize(Instruction _instruction)
bool SemanticInformation::canBeRemovedIfNoMSize(Instruction _instruction)
{
if (_instruction == Instruction::KECCAK256 || _instruction == Instruction::MLOAD)
return true;
else
return sideEffectFree(_instruction);
return canBeRemoved(_instruction);
}
bool SemanticInformation::invalidatesMemory(Instruction _instruction)
SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction)
{
switch (_instruction)
{
@ -263,13 +263,47 @@ bool SemanticInformation::invalidatesMemory(Instruction _instruction)
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::STATICCALL:
return true;
return SemanticInformation::Write;
case Instruction::CREATE:
case Instruction::CREATE2:
case Instruction::KECCAK256:
case Instruction::MLOAD:
case Instruction::MSIZE:
case Instruction::RETURN:
case Instruction::REVERT:
case Instruction::LOG0:
case Instruction::LOG1:
case Instruction::LOG2:
case Instruction::LOG3:
case Instruction::LOG4:
return SemanticInformation::Read;
default:
return false;
return SemanticInformation::None;
}
}
bool SemanticInformation::invalidatesStorage(Instruction _instruction)
bool SemanticInformation::movableApartFromEffects(Instruction _instruction)
{
switch (_instruction)
{
case Instruction::EXTCODEHASH:
case Instruction::EXTCODESIZE:
case Instruction::RETURNDATASIZE:
case Instruction::BALANCE:
case Instruction::SELFBALANCE:
case Instruction::SLOAD:
case Instruction::KECCAK256:
case Instruction::MLOAD:
return true;
default:
return movable(_instruction);
}
}
SemanticInformation::Effect SemanticInformation::storage(Instruction _instruction)
{
switch (_instruction)
{
@ -279,9 +313,45 @@ bool SemanticInformation::invalidatesStorage(Instruction _instruction)
case Instruction::CREATE:
case Instruction::CREATE2:
case Instruction::SSTORE:
return true;
return SemanticInformation::Write;
case Instruction::SLOAD:
case Instruction::STATICCALL:
return SemanticInformation::Read;
default:
return false;
return SemanticInformation::None;
}
}
SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruction)
{
switch (_instruction)
{
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::CREATE:
case Instruction::CREATE2:
case Instruction::SELFDESTRUCT:
case Instruction::STATICCALL: // because it can affect returndatasize
// Strictly speaking, log0, .., log4 writes to the state, but the EVM cannot read it, so they
// are just marked as having 'other side effects.'
return SemanticInformation::Write;
case Instruction::EXTCODESIZE:
case Instruction::EXTCODEHASH:
case Instruction::RETURNDATASIZE:
case Instruction::BALANCE:
case Instruction::SELFBALANCE:
case Instruction::RETURNDATACOPY:
case Instruction::EXTCODECOPY:
// PC and GAS are specifically excluded here. Instructions such as CALLER, CALLVALUE,
// ADDRESS are excluded because they cannot change during execution.
return SemanticInformation::Read;
default:
return SemanticInformation::None;
}
}

View File

@ -36,6 +36,15 @@ class AssemblyItem;
*/
struct SemanticInformation
{
/// Corresponds to the effect that a YUL-builtin has on a generic data location (storage, memory
/// and other blockchain state).
enum Effect
{
None,
Read,
Write
};
/// @returns true if the given items starts a new block for common subexpression analysis.
/// @param _msizeImportant if false, consider an operation non-breaking if its only side-effect is that it modifies msize.
static bool breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant);
@ -57,20 +66,23 @@ struct SemanticInformation
/// without altering the semantics. This means it cannot depend on storage or memory,
/// cannot have any side-effects, but it can depend on a call-constant state of the blockchain.
static bool movable(Instruction _instruction);
/// If true, the expressions in this code can be moved or copied (together with their arguments)
/// across control flow branches and instructions as long as these instructions' 'effects' do
/// not influence the 'effects' of the aforementioned expressions.
static bool movableApartFromEffects(Instruction _instruction);
/// @returns true if the instruction can be removed without changing the semantics.
/// This does not mean that it has to be deterministic or retrieve information from
/// somewhere else than purely the values of its arguments.
static bool sideEffectFree(Instruction _instruction);
static bool canBeRemoved(Instruction _instruction);
/// @returns true if the instruction can be removed without changing the semantics.
/// This does not mean that it has to be deterministic or retrieve information from
/// somewhere else than purely the values of its arguments.
/// If true, the instruction is still allowed to influence the value returned by the
/// msize instruction.
static bool sideEffectFreeIfNoMSize(Instruction _instruction);
/// @returns true if the given instruction modifies memory.
static bool invalidatesMemory(Instruction _instruction);
/// @returns true if the given instruction modifies storage (even indirectly).
static bool invalidatesStorage(Instruction _instruction);
static bool canBeRemovedIfNoMSize(Instruction _instruction);
static Effect memory(Instruction _instruction);
static Effect storage(Instruction _instruction);
static Effect otherState(Instruction _instruction);
static bool invalidInPureFunctions(Instruction _instruction);
static bool invalidInViewFunctions(Instruction _instruction);
};

View File

@ -23,6 +23,7 @@
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SourceLocation.h>
#include <cstdlib>
#include <memory>
using namespace std;

View File

@ -94,6 +94,8 @@ void ParserBase::expectToken(Token _value, bool _advance)
void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance)
{
solAssert(m_inParserRecovery, "The function is supposed to be called during parser recovery only.");
Token tok = m_scanner->currentToken();
if (tok != _value)
{
@ -103,24 +105,20 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN
m_scanner->next();
string const expectedToken = ParserBase::tokenName(_value);
string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + " instead.";
if (m_scanner->currentToken() == Token::EOS)
{
// rollback to where the token started, and raise exception to be caught at a higher level.
m_scanner->setPosition(static_cast<size_t>(startPosition));
m_inParserRecovery = true;
string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + " instead.";
fatalParserError(1957_error, errorLoc, msg);
}
else
{
if (m_inParserRecovery)
parserWarning(3796_error, "Recovered in " + _currentNodeName + " at " + expectedToken + ".");
else
parserError(1054_error, errorLoc, msg + "Recovered at next " + expectedToken);
parserWarning(3796_error, "Recovered in " + _currentNodeName + " at " + expectedToken + ".");
m_inParserRecovery = false;
}
}
else if (m_inParserRecovery)
else
{
string expectedToken = ParserBase::tokenName(_value);
parserWarning(3347_error, "Recovered in " + _currentNodeName + " at " + expectedToken + ".");

View File

@ -540,7 +540,7 @@ void Scanner::scanToken()
if (m_char == '=')
token = selectToken(Token::Equal);
else if (m_char == '>')
token = selectToken(Token::Arrow);
token = selectToken(Token::DoubleArrow);
else
token = Token::Assign;
break;
@ -563,12 +563,14 @@ void Scanner::scanToken()
token = Token::Add;
break;
case '-':
// - -- -=
// - -- -= ->
advance();
if (m_char == '-')
token = selectToken(Token::Dec);
else if (m_char == '=')
token = selectToken(Token::AssignSub);
else if (m_char == '>')
token = selectToken(Token::RightArrow);
else
token = Token::Sub;
break;
@ -678,7 +680,7 @@ void Scanner::scanToken()
else
token = setError(ScannerError::IllegalToken);
}
else if (token == Token::Unicode)
else if (token == Token::Unicode && m_kind != ScannerKind::Yul)
{
// reset
m = 0;
@ -967,7 +969,17 @@ tuple<Token, unsigned, unsigned> Scanner::scanIdentifierOrKeyword()
while (isIdentifierPart(m_char) || (m_char == '.' && m_kind == ScannerKind::Yul))
addLiteralCharAndAdvance();
literal.complete();
return TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal);
auto const token = TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal);
if (m_kind == ScannerKind::Yul)
{
// Turn Solidity identifier into a Yul keyword
if (m_tokens[NextNext].literal == "leave")
return std::make_tuple(Token::Leave, 0, 0);
// Turn non-Yul keywords into identifiers.
if (!TokenTraits::isYulKeyword(std::get<0>(token)))
return std::make_tuple(Token::Identifier, 0, 0);
}
return token;
}
} // namespace solidity::langutil

View File

@ -152,6 +152,11 @@ static Token keywordByName(string const& _name)
return it == keywords.end() ? Token::Identifier : it->second;
}
bool isYulKeyword(string const& _literal)
{
return _literal == "leave" || isYulKeyword(keywordByName(_literal));
}
tuple<Token, unsigned int, unsigned int> fromIdentifierOrKeyword(string const& _literal)
{
auto positionM = find_if(_literal.begin(), _literal.end(), ::isdigit);

View File

@ -83,7 +83,8 @@ namespace solidity::langutil
T(Semicolon, ";", 0) \
T(Period, ".", 0) \
T(Conditional, "?", 3) \
T(Arrow, "=>", 0) \
T(DoubleArrow, "=>", 0) \
T(RightArrow, "->", 0) \
\
/* Assignment operators. */ \
/* IsAssignmentOp() relies on this block of enum values being */ \
@ -268,6 +269,9 @@ namespace solidity::langutil
K(Unchecked, "unchecked", 0) \
K(Var, "var", 0) \
\
/* Yul-specific tokens, but not keywords. */ \
T(Leave, "leave", 0) \
\
/* Illegal token - not able to scan. */ \
T(Illegal, "ILLEGAL", 0) \
\
@ -316,6 +320,15 @@ namespace TokenTraits
constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; }
constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Unchecked); }
constexpr bool isYulKeyword(Token tok)
{
return tok == Token::Function || tok == Token::Let || tok == Token::If || tok == Token::Switch || tok == Token::Case ||
tok == Token::Default || tok == Token::For || tok == Token::Break || tok == Token::Continue || tok == Token::Leave ||
tok == Token::TrueLiteral || tok == Token::FalseLiteral || tok == Token::HexStringLiteral || tok == Token::Hex;
}
bool isYulKeyword(std::string const& _literal);
inline Token AssignmentToBinaryOp(Token op)
{
solAssert(isAssignmentOp(op) && op != Token::Assign, "");

View File

@ -188,8 +188,14 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]);
else if (n == "mod")
return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]);
else if (n == "bvnot")
return m_context.mkExpr(CVC4::kind::BITVECTOR_NOT, arguments[0]);
else if (n == "bvand")
return m_context.mkExpr(CVC4::kind::BITVECTOR_AND, arguments[0], arguments[1]);
else if (n == "bvor")
return m_context.mkExpr(CVC4::kind::BITVECTOR_OR, arguments[0], arguments[1]);
else if (n == "bvxor")
return m_context.mkExpr(CVC4::kind::BITVECTOR_XOR, arguments[0], arguments[1]);
else if (n == "int2bv")
{
size_t size = std::stoul(_expr.arguments[1].name);

View File

@ -95,7 +95,10 @@ public:
{"*", 2},
{"/", 2},
{"mod", 2},
{"bvnot", 1},
{"bvand", 2},
{"bvor", 2},
{"bvxor", 2},
{"int2bv", 2},
{"bv2int", 1},
{"select", 2},
@ -286,11 +289,26 @@ public:
auto intSort = _a.sort;
return Expression("mod", {std::move(_a), std::move(_b)}, intSort);
}
friend Expression operator~(Expression _a)
{
auto bvSort = _a.sort;
return Expression("bvnot", {std::move(_a)}, bvSort);
}
friend Expression operator&(Expression _a, Expression _b)
{
auto bvSort = _a.sort;
return Expression("bvand", {std::move(_a), std::move(_b)}, bvSort);
}
friend Expression operator^(Expression _a, Expression _b)
{
auto bvSort = _a.sort;
return Expression("bvxor", {std::move(_a), std::move(_b)}, bvSort);
}
friend Expression operator|(Expression _a, Expression _b)
{
auto bvSort = _a.sort;
return Expression("bvor", {std::move(_a), std::move(_b)}, bvSort);
}
Expression operator()(std::vector<Expression> _arguments) const
{
smtAssert(

View File

@ -181,8 +181,14 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
return arguments[0] / arguments[1];
else if (n == "mod")
return z3::mod(arguments[0], arguments[1]);
else if (n == "bvnot")
return ~arguments[0];
else if (n == "bvand")
return arguments[0] & arguments[1];
else if (n == "bvor")
return arguments[0] | arguments[1];
else if (n == "bvxor")
return arguments[0] ^ arguments[1];
else if (n == "int2bv")
{
size_t size = std::stoul(_expr.arguments[1].name);

View File

@ -49,9 +49,7 @@ 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 10000000,
// so using double that.
static int const resourceLimit = 20000000;
static int const resourceLimit = 12500000;
private:
void declareFunction(std::string const& _name, Sort const& _sort);

View File

@ -134,15 +134,26 @@ vector<Declaration const*> GlobalContext::declarations() const
MagicVariableDeclaration const* GlobalContext::currentThis() const
{
if (!m_thisPointer[m_currentContract])
m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>(magicVariableToID("this"), "this", TypeProvider::contract(*m_currentContract));
{
Type const* type = TypeProvider::emptyTuple();
if (m_currentContract)
type = TypeProvider::contract(*m_currentContract);
m_thisPointer[m_currentContract] =
make_shared<MagicVariableDeclaration>(magicVariableToID("this"), "this", type);
}
return m_thisPointer[m_currentContract].get();
}
MagicVariableDeclaration const* GlobalContext::currentSuper() const
{
if (!m_superPointer[m_currentContract])
m_superPointer[m_currentContract] = make_shared<MagicVariableDeclaration>(magicVariableToID("super"), "super", TypeProvider::contract(*m_currentContract, true));
{
Type const* type = TypeProvider::emptyTuple();
if (m_currentContract)
type = TypeProvider::contract(*m_currentContract, true);
m_superPointer[m_currentContract] =
make_shared<MagicVariableDeclaration>(magicVariableToID("super"), "super", type);
}
return m_superPointer[m_currentContract].get();
}

View File

@ -46,6 +46,7 @@ class GlobalContext: private boost::noncopyable
public:
GlobalContext();
void setCurrentContract(ContractDefinition const& _contract);
void resetCurrentContract() { m_currentContract = nullptr; }
MagicVariableDeclaration const* currentThis() const;
MagicVariableDeclaration const* currentSuper() const;

View File

@ -34,10 +34,12 @@ void ImmutableValidator::analyze()
for (ContractDefinition const* contract: linearizedContracts)
for (VariableDeclaration const* stateVar: contract->stateVariables())
if (stateVar->value())
{
m_initializedStateVariables.emplace(stateVar);
for (ContractDefinition const* contract: linearizedContracts)
for (VariableDeclaration const* stateVar: contract->stateVariables())
if (stateVar->value())
stateVar->value()->accept(*this);
solAssert(m_initializedStateVariables.emplace(stateVar).second, "");
}
for (ContractDefinition const* contract: linearizedContracts)
if (contract->constructor())
@ -163,41 +165,44 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
return;
if (_expression.annotation().willBeWrittenTo && _expression.annotation().lValueOfOrdinaryAssignment)
// If this is not an ordinary assignment, we write and read at the same time.
bool write = _expression.annotation().willBeWrittenTo;
bool read = !_expression.annotation().willBeWrittenTo || !_expression.annotation().lValueOfOrdinaryAssignment;
if (write)
{
if (!m_currentConstructor)
m_errorReporter.typeError(
1581_error,
_expression.location(),
"Immutable variables can only be initialized inline or assigned directly in the constructor."
"Cannot write to immutable here: Immutable variables can only be initialized inline or assigned directly in the constructor."
);
else if (m_currentConstructor->annotation().contract->id() != _variableReference.annotation().contract->id())
m_errorReporter.typeError(
7484_error,
_expression.location(),
"Immutable variables must be initialized in the constructor of the contract they are defined in."
"Cannot write to immutable here: Immutable variables must be initialized in the constructor of the contract they are defined in."
);
else if (m_inLoop)
m_errorReporter.typeError(
6672_error,
_expression.location(),
"Immutable variables can only be initialized once, not in a while statement."
"Cannot write to immutable here: Immutable variables cannot be initialized inside a loop."
);
else if (m_inBranch)
m_errorReporter.typeError(
4599_error,
_expression.location(),
"Immutable variables must be initialized unconditionally, not in an if statement."
"Cannot write to immutable here: Immutable variables cannot be initialized inside an if statement."
);
if (!m_initializedStateVariables.emplace(&_variableReference).second)
else if (m_initializedStateVariables.count(&_variableReference))
m_errorReporter.typeError(
1574_error,
_expression.location(),
"Immutable state variable already initialized."
);
m_initializedStateVariables.emplace(&_variableReference);
}
else if (m_inConstructionContext)
if (read && m_inConstructionContext)
m_errorReporter.typeError(
7733_error,
_expression.location(),

View File

@ -275,6 +275,12 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
if (!resolveNamesAndTypesInternal(*node, true))
success = false;
}
// make "this" and "super" invisible.
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, true, true);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, true, true);
m_globalContext.resetCurrentContract();
return success;
}
else
@ -548,6 +554,10 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
void DeclarationRegistrationHelper::endVisit(ContractDefinition&)
{
// make "this" and "super" invisible.
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, true, true);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, true, true);
m_globalContext.resetCurrentContract();
m_currentContract = nullptr;
closeCurrentScope();
}

View File

@ -277,7 +277,7 @@ struct EventOutsideEmitChecker: public PostTypeChecker::Checker
bool visit(FunctionCall const& _functionCall) override
{
if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
return true;
if (FunctionTypePointer const functionType = dynamic_cast<FunctionTypePointer const>(_functionCall.expression().annotation().type))

View File

@ -325,6 +325,17 @@ void ReferencesResolver::resolveInheritDoc(StructuredDocumentation const& _docum
vector<string> path;
boost::split(path, name, boost::is_any_of("."));
if (any_of(path.begin(), path.end(), [](auto& _str) { return _str.empty(); }))
{
m_errorReporter.docstringParsingError(
5967_error,
_documentation.location(),
"Documentation tag @inheritdoc reference \"" +
name +
"\" is malformed."
);
return;
}
Declaration const* result = m_resolver.pathFromCurrentScope(path);
if (result == nullptr)

View File

@ -292,7 +292,7 @@ bool StaticAnalyzer::visit(BinaryOperation const& _operation)
bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
{
if (_functionCall.annotation().kind == FunctionCallKind::FunctionCall)
if (*_functionCall.annotation().kind == FunctionCallKind::FunctionCall)
{
auto functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
solAssert(functionType, "");
@ -311,6 +311,7 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
);
}
if (
m_currentContract &&
m_currentContract->isLibrary() &&
functionType->kind() == FunctionType::Kind::DelegateCall &&
functionType->declaration().scope() == m_currentContract

View File

@ -295,7 +295,7 @@ bool SyntaxChecker::visit(PlaceholderStatement const&)
bool SyntaxChecker::visit(ContractDefinition const& _contract)
{
m_isInterface = _contract.isInterface();
m_currentContractKind = _contract.contractKind();
ASTString const& contractName = _contract.name();
for (FunctionDefinition const* function: _contract.definedFunctions())
@ -309,19 +309,41 @@ bool SyntaxChecker::visit(ContractDefinition const& _contract)
return true;
}
void SyntaxChecker::endVisit(ContractDefinition const&)
{
m_currentContractKind = std::nullopt;
}
bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
if (!_function.isConstructor() && _function.noVisibilitySpecified())
solAssert(_function.isFree() == (m_currentContractKind == std::nullopt), "");
if (!_function.isFree() && !_function.isConstructor() && _function.noVisibilitySpecified())
{
string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public";
string suggestedVisibility =
_function.isFallback() ||
_function.isReceive() ||
m_currentContractKind == ContractKind::Interface
? "external" : "public";
m_errorReporter.syntaxError(
4937_error,
_function.location(),
"No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?"
);
}
else if (_function.isFree())
{
if (!_function.noVisibilitySpecified())
m_errorReporter.syntaxError(
4126_error,
_function.location(),
"Free functions cannot have visibility."
);
if (!_function.isImplemented())
m_errorReporter.typeError(4668_error, _function.location(), "Free functions must be implemented.");
}
if (m_isInterface && !_function.modifiers().empty())
if (m_currentContractKind == ContractKind::Interface && !_function.modifiers().empty())
m_errorReporter.syntaxError(5842_error, _function.location(), "Functions in interfaces cannot have modifiers.");
else if (!_function.isImplemented() && !_function.modifiers().empty())
m_errorReporter.syntaxError(2668_error, _function.location(), "Functions without implementation cannot have modifiers.");
@ -342,23 +364,6 @@ bool SyntaxChecker::visit(FunctionTypeName const& _node)
return true;
}
bool SyntaxChecker::visit(VariableDeclarationStatement const& _statement)
{
// Report if none of the variable components in the tuple have a name (only possible via deprecated "var")
if (std::all_of(
_statement.declarations().begin(),
_statement.declarations().end(),
[](ASTPointer<VariableDeclaration> const& declaration) { return declaration == nullptr; }
))
m_errorReporter.syntaxError(
3299_error,
_statement.location(),
"The use of the \"var\" keyword is disallowed. The declaration part of the statement can be removed, since it is empty."
);
return true;
}
bool SyntaxChecker::visit(StructDefinition const& _struct)
{
if (_struct.members().empty())

View File

@ -83,11 +83,10 @@ private:
bool visit(PlaceholderStatement const& _placeholderStatement) override;
bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(FunctionTypeName const& _node) override;
bool visit(VariableDeclarationStatement const& _statement) override;
bool visit(StructDefinition const& _struct) override;
bool visit(Literal const& _literal) override;
@ -102,7 +101,7 @@ private:
bool m_versionPragmaFound = false;
int m_inLoopDepth = 0;
bool m_isInterface = false;
std::optional<ContractKind> m_currentContractKind;
SourceUnit const* m_sourceUnit = nullptr;
};

View File

@ -271,6 +271,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
{
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
solAssert(base, "Base contract not available.");
solAssert(m_currentContract, "");
if (m_currentContract->isInterface() && !base->isInterface())
m_errorReporter.typeError(6536_error, _inheritance.location(), "Interfaces can only inherit from other interfaces.");
@ -327,7 +328,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
{
if (_function.markedVirtual())
{
if (_function.isConstructor())
if (_function.isFree())
m_errorReporter.syntaxError(4493_error, _function.location(), "Free functions cannot be virtual.");
else if (_function.isConstructor())
m_errorReporter.typeError(7001_error, _function.location(), "Constructors cannot be virtual.");
else if (_function.annotation().contract->isInterface())
m_errorReporter.warning(5815_error, _function.location(), "Interface functions are implicitly \"virtual\"");
@ -336,12 +339,16 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
else if (_function.libraryFunction())
m_errorReporter.typeError(7801_error, _function.location(), "Library functions cannot be \"virtual\".");
}
if (_function.overrides() && _function.isFree())
m_errorReporter.syntaxError(1750_error, _function.location(), "Free functions cannot override.");
if (_function.isPayable())
{
if (_function.libraryFunction())
m_errorReporter.typeError(7708_error, _function.location(), "Library functions cannot be payable.");
if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
else if (_function.isFree())
m_errorReporter.typeError(9559_error, _function.location(), "Free functions cannot be payable.");
else if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
m_errorReporter.typeError(5587_error, _function.location(), "\"internal\" and \"private\" functions cannot be payable.");
}
@ -415,9 +422,13 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
set<Declaration const*> modifiers;
for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers())
{
auto baseContracts = dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts;
// Delete first base which is just the main contract itself
baseContracts.erase(baseContracts.begin());
vector<ContractDefinition const*> baseContracts;
if (auto contract = dynamic_cast<ContractDefinition const*>(_function.scope()))
{
baseContracts = contract->annotation().linearizedBaseContracts;
// Delete first base which is just the main contract itself
baseContracts.erase(baseContracts.begin());
}
visitManually(
*modifier,
@ -432,7 +443,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
else
modifiers.insert(decl);
}
if (m_currentContract->isInterface())
solAssert(_function.isFree() == !m_currentContract, "");
if (!m_currentContract)
{
solAssert(!_function.isConstructor(), "");
solAssert(!_function.isFallback(), "");
solAssert(!_function.isReceive(), "");
}
else if (m_currentContract->isInterface())
{
if (_function.isImplemented())
m_errorReporter.typeError(4726_error, _function.location(), "Functions in interfaces cannot have an implementation.");
@ -445,6 +464,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
else if (m_currentContract->contractKind() == ContractKind::Library)
if (_function.isConstructor())
m_errorReporter.typeError(7634_error, _function.location(), "Constructor cannot be defined in libraries.");
if (_function.isImplemented())
_function.body().accept(*this);
else if (_function.isConstructor())
@ -452,7 +472,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
else if (_function.libraryFunction())
m_errorReporter.typeError(9231_error, _function.location(), "Library functions must be implemented if declared.");
else if (!_function.virtualSemantics())
m_errorReporter.typeError(5424_error, _function.location(), "Functions without implementation must be marked virtual.");
{
if (_function.isFree())
solAssert(m_errorReporter.hasErrors(), "");
else
m_errorReporter.typeError(5424_error, _function.location(), "Functions without implementation must be marked virtual.");
}
if (_function.isFallback())
@ -591,7 +616,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (_variable.isStateVariable())
m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true));
else
m_errorReporter.warning(2332_error, _variable.typeName().location(), collisionMessage(varType->canonicalName(), false));
m_errorReporter.warning(2332_error, _variable.typeName().location(), collisionMessage(varType->toString(true), false));
}
vector<Type const*> oversizedSubtypes = frontend::oversizedSubtypes(*varType);
for (Type const* subtype: oversizedSubtypes)
@ -885,7 +910,7 @@ bool TypeChecker::visit(IfStatement const& _ifStatement)
void TypeChecker::endVisit(TryStatement const& _tryStatement)
{
FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall());
if (!externalCall || externalCall->annotation().kind != FunctionCallKind::FunctionCall)
if (!externalCall || *externalCall->annotation().kind != FunctionCallKind::FunctionCall)
{
m_errorReporter.typeError(
5347_error,
@ -1095,7 +1120,7 @@ void TypeChecker::endVisit(Return const& _return)
void TypeChecker::endVisit(EmitStatement const& _emit)
{
if (
_emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall ||
*_emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall ||
type(_emit.eventCall().expression())->category() != Type::Category::Function ||
dynamic_cast<FunctionType const&>(*type(_emit.eventCall().expression())).kind() != FunctionType::Kind::Event
)
@ -1591,7 +1616,7 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
FunctionCall const& _functionCall
)
{
solAssert(_functionCall.annotation().kind == FunctionCallKind::TypeConversion, "");
solAssert(*_functionCall.annotation().kind == FunctionCallKind::TypeConversion, "");
TypePointer const& expressionType = type(_functionCall.expression());
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
@ -1726,7 +1751,9 @@ void TypeChecker::typeCheckFunctionCall(
if (_functionType->kind() == FunctionType::Kind::Declaration)
{
solAssert(_functionType->declaration().annotation().contract, "");
if (
m_currentContract &&
m_currentContract->derivesFrom(*_functionType->declaration().annotation().contract) &&
!dynamic_cast<FunctionDefinition const&>(_functionType->declaration()).isImplemented()
)
@ -1956,8 +1983,10 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
bool const isPositionalCall = _functionCall.names().empty();
bool const isVariadic = _functionType->takesArbitraryParameters();
auto functionCallKind = *_functionCall.annotation().kind;
solAssert(
!isVariadic || _functionCall.annotation().kind == FunctionCallKind::FunctionCall,
!isVariadic || functionCallKind == FunctionCallKind::FunctionCall,
"Struct constructor calls cannot be variadic."
);
@ -1972,7 +2001,7 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
)
{
bool const isStructConstructorCall =
_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
functionCallKind == FunctionCallKind::StructConstructorCall;
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
string msg = isVariadic ?
@ -2235,13 +2264,14 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
default:
m_errorReporter.fatalTypeError(5704_error, _functionCall.location(), "Type is not callable");
funcCallAnno.kind = FunctionCallKind::Unset;
// Unreachable, because fatalTypeError throws. We don't set kind, but that's okay because the switch below
// is never reached. And, even if it was, SetOnce would trigger an assertion violation and not UB.
funcCallAnno.isPure = argumentsArePure;
break;
}
// Determine return types
switch (funcCallAnno.kind)
switch (*funcCallAnno.kind)
{
case FunctionCallKind::TypeConversion:
funcCallAnno.type = typeCheckTypeConversionAndRetrieveReturnType(_functionCall);
@ -2291,7 +2321,6 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
break;
}
case FunctionCallKind::Unset: // fall-through
default:
// for non-callables, ensure error reported and annotate node to void function
solAssert(m_errorReporter.hasErrors(), "");
@ -2452,18 +2481,23 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
if (contract->abstract())
m_errorReporter.typeError(4614_error, _newExpression.location(), "Cannot instantiate an abstract contract.");
solAssert(!!m_currentContract, "");
m_currentContract->annotation().contractDependencies.insert(contract);
solAssert(
!contract->annotation().linearizedBaseContracts.empty(),
"Linearized base contracts not yet available."
);
if (contractDependenciesAreCyclic(*m_currentContract))
m_errorReporter.typeError(
4579_error,
_newExpression.location(),
"Circular reference for contract creation (cannot create instance of derived or same contract)."
if (m_currentContract)
{
// TODO this is not properly detecting creation-cycles if they go through
// internal library functions or free functions. It will be caught at
// code generation time, but it would of course be better to catch it here.
m_currentContract->annotation().contractDependencies.insert(contract);
solAssert(
!contract->annotation().linearizedBaseContracts.empty(),
"Linearized base contracts not yet available."
);
if (contractDependenciesAreCyclic(*m_currentContract))
m_errorReporter.typeError(
4579_error,
_newExpression.location(),
"Circular reference for contract creation (cannot create instance of derived or same contract)."
);
}
_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
}
@ -2505,7 +2539,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
// Retrieve the types of the arguments if this is used to call a function.
auto const& arguments = _memberAccess.annotation().arguments;
MemberList::MemberMap possibleMembers = exprType->members(m_currentContract).membersByName(memberName);
MemberList::MemberMap possibleMembers = exprType->members(currentDefinitionScope()).membersByName(memberName);
size_t const initialMemberCount = possibleMembers.size();
if (initialMemberCount > 1 && arguments)
{
@ -2531,7 +2565,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
DataLocation::Storage,
exprType
);
if (!storageType->members(m_currentContract).membersByName(memberName).empty())
if (!storageType->members(currentDefinitionScope()).membersByName(memberName).empty())
m_errorReporter.fatalTypeError(
4994_error,
_memberAccess.location(),
@ -2689,8 +2723,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
{
annotation.isPure = true;
ContractType const& accessedContractType = dynamic_cast<ContractType const&>(*magicType->typeArgument());
m_currentContract->annotation().contractDependencies.insert(&accessedContractType.contractDefinition());
if (
memberName == "runtimeCode" &&
!accessedContractType.immutableVariables().empty()
@ -2701,12 +2733,21 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
"\"runtimeCode\" is not available for contracts containing immutable variables."
);
if (contractDependenciesAreCyclic(*m_currentContract))
m_errorReporter.typeError(
4224_error,
_memberAccess.location(),
"Circular reference for contract code access."
);
if (m_currentContract)
{
// TODO in the same way as with ``new``,
// this is not properly detecting creation-cycles if they go through
// internal library functions or free functions. It will be caught at
// code generation time, but it would of course be better to catch it here.
m_currentContract->annotation().contractDependencies.insert(&accessedContractType.contractDefinition());
if (contractDependenciesAreCyclic(*m_currentContract))
m_errorReporter.typeError(
4224_error,
_memberAccess.location(),
"Circular reference for contract code access."
);
}
}
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
annotation.isPure = true;
@ -3107,6 +3148,16 @@ void TypeChecker::endVisit(Literal const& _literal)
_literal.annotation().isPure = true;
}
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
{
if (m_currentContract->isInterface())
m_errorReporter.typeError(
9088_error,
_usingFor.location(),
"The \"using for\" directive is not allowed inside interfaces."
);
}
bool TypeChecker::contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set<ContractDefinition const*> const& _seenContracts

View File

@ -143,6 +143,7 @@ private:
bool visit(Identifier const& _identifier) override;
void endVisit(ElementaryTypeNameExpression const& _expr) override;
void endVisit(Literal const& _literal) override;
void endVisit(UsingForDirective const& _usingForDirective) override;
bool contractDependenciesAreCyclic(
ContractDefinition const& _contract,
@ -167,6 +168,16 @@ private:
bool experimentalFeatureActive(ExperimentalFeature _feature) const;
/// @returns the current scope that can have function or type definitions.
/// This is either a contract or a source unit.
ASTNode const* currentDefinitionScope() const
{
if (m_currentContract)
return m_currentContract;
else
return m_currentSourceUnit;
}
SourceUnit const* m_currentSourceUnit = nullptr;
ContractDefinition const* m_currentContract = nullptr;

View File

@ -128,28 +128,12 @@ private:
bool ViewPureChecker::check()
{
vector<ContractDefinition const*> contracts;
for (auto const& node: m_ast)
{
SourceUnit const* source = dynamic_cast<SourceUnit const*>(node.get());
solAssert(source, "");
contracts += source->filteredNodes<ContractDefinition>(source->nodes());
}
// Check modifiers first to infer their state mutability.
for (auto const& contract: contracts)
for (ModifierDefinition const* mod: contract->functionModifiers())
mod->accept(*this);
for (auto const& contract: contracts)
contract->accept(*this);
for (auto const& source: m_ast)
source->accept(*this);
return !m_errors;
}
bool ViewPureChecker::visit(FunctionDefinition const& _funDef)
{
solAssert(!m_currentFunction, "");
@ -304,12 +288,31 @@ void ViewPureChecker::reportMutability(
m_currentFunction->stateMutability() == StateMutability::Pure ||
m_currentFunction->stateMutability() == StateMutability::NonPayable,
""
);
);
}
ViewPureChecker::MutabilityAndLocation const& ViewPureChecker::modifierMutability(
ModifierDefinition const& _modifier
)
{
if (!m_inferredMutability.count(&_modifier))
{
MutabilityAndLocation bestMutabilityAndLocation{};
FunctionDefinition const* currentFunction = nullptr;
swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation);
swap(currentFunction, m_currentFunction);
_modifier.accept(*this);
swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation);
swap(currentFunction, m_currentFunction);
}
return m_inferredMutability.at(&_modifier);
}
void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
{
if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
return;
StateMutability mutability = dynamic_cast<FunctionType const&>(*_functionCall.expression().annotation().type).stateMutability();
@ -429,8 +432,7 @@ void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
solAssert(_modifier.name(), "");
if (ModifierDefinition const* mod = dynamic_cast<decltype(mod)>(_modifier.name()->annotation().referencedDeclaration))
{
solAssert(m_inferredMutability.count(mod), "");
auto const& mutAndLocation = m_inferredMutability.at(mod);
MutabilityAndLocation const& mutAndLocation = modifierMutability(*mod);
reportMutability(mutAndLocation.mutability, _modifier.location(), mutAndLocation.location);
}
else

View File

@ -71,6 +71,9 @@ private:
std::optional<langutil::SourceLocation> const& _nestedLocation = {}
);
/// Determines the mutability of modifier if not already cached.
MutabilityAndLocation const& modifierMutability(ModifierDefinition const& _modifier);
std::vector<std::shared_ptr<ASTNode>> const& m_ast;
langutil::ErrorReporter& m_errorReporter;

View File

@ -292,7 +292,7 @@ bool FunctionDefinition::libraryFunction() const
Visibility FunctionDefinition::defaultVisibility() const
{
solAssert(!isConstructor(), "");
return Declaration::defaultVisibility();
return isFree() ? Visibility::Internal : Declaration::defaultVisibility();
}
FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
@ -338,7 +338,7 @@ TypePointer FunctionDefinition::type() const
TypePointer FunctionDefinition::typeViaContractName() const
{
if (annotation().contract->isLibrary())
if (libraryFunction())
{
if (isPublic())
return FunctionType(*this).asExternallyCallableFunction(true);
@ -374,7 +374,9 @@ FunctionDefinition const& FunctionDefinition::resolveVirtual(
if (_searchStart == nullptr && !virtualSemantics())
return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
solAssert(!isFree(), "");
solAssert(isOrdinary(), "");
solAssert(!libraryFunction(), "");
FunctionType const* functionType = TypeProvider::function(*this)->asExternallyCallableFunction(false);
@ -603,9 +605,8 @@ bool VariableDeclaration::isLibraryFunctionParameter() const
if (!isCallableOrCatchParameter())
return false;
if (auto const* funDef = dynamic_cast<FunctionDefinition const*>(scope()))
return dynamic_cast<ContractDefinition const&>(*funDef->scope()).isLibrary();
else
return false;
return funDef->libraryFunction();
return false;
}
bool VariableDeclaration::isEventParameter() const

View File

@ -774,6 +774,7 @@ public:
ASTPointer<ASTString> const& _name,
Visibility _visibility,
StateMutability _stateMutability,
bool _free,
Token _kind,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
@ -787,6 +788,7 @@ public:
StructurallyDocumented(_documentation),
ImplementationOptional(_body != nullptr),
m_stateMutability(_stateMutability),
m_free(_free),
m_kind(_kind),
m_functionModifiers(std::move(_modifiers)),
m_body(_body)
@ -804,6 +806,7 @@ public:
bool isConstructor() const { return m_kind == Token::Constructor; }
bool isFallback() const { return m_kind == Token::Fallback; }
bool isReceive() const { return m_kind == Token::Receive; }
bool isFree() const { return m_free; }
Token kind() const { return m_kind; }
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
@ -815,6 +818,7 @@ public:
}
bool isVisibleViaContractTypeAccess() const override
{
solAssert(!isFree(), "");
return isOrdinary() && visibility() >= Visibility::Public;
}
bool isPartOfExternalInterface() const override { return isOrdinary() && isPublic(); }
@ -850,6 +854,7 @@ public:
private:
StateMutability m_stateMutability;
bool m_free;
Token const m_kind;
std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers;
ASTPointer<Block> m_body;

View File

@ -27,6 +27,8 @@
#include <libsolidity/ast/ASTEnums.h>
#include <libsolidity/ast/ExperimentalFeatures.h>
#include <libsolutil/SetOnce.h>
#include <map>
#include <memory>
#include <optional>
@ -234,9 +236,6 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation
{
/// Referenced declaration, set during reference resolution stage.
Declaration const* referencedDeclaration = nullptr;
/// Stores a reference to the current contract.
/// This is needed because types of base contracts change depending on the context.
ContractDefinition const* contractScope = nullptr;
};
struct ExpressionAnnotation: ASTAnnotation
@ -285,7 +284,6 @@ struct BinaryOperationAnnotation: ExpressionAnnotation
enum class FunctionCallKind
{
Unset,
FunctionCall,
TypeConversion,
StructConstructorCall
@ -293,7 +291,7 @@ enum class FunctionCallKind
struct FunctionCallAnnotation: ExpressionAnnotation
{
FunctionCallKind kind = FunctionCallKind::Unset;
util::SetOnce<FunctionCallKind> kind;
/// If true, this is the external call of a try statement.
bool tryCall = false;
};

View File

@ -203,13 +203,13 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifie
void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node)
{
_stream << util::jsonPrettyPrint(util::removeNullMembers(toJson(_node)));
_stream << util::jsonPrettyPrint(toJson(_node));
}
Json::Value&& ASTJsonConverter::toJson(ASTNode const& _node)
Json::Value ASTJsonConverter::toJson(ASTNode const& _node)
{
_node.accept(*this);
return std::move(m_currentValue);
return util::removeNullMembers(std::move(m_currentValue));
}
bool ASTJsonConverter::visit(SourceUnit const& _node)
@ -360,7 +360,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("kind", TokenTraits::toString(_node.kind())),
make_pair("kind", _node.isFree() ? "freeFunction" : TokenTraits::toString(_node.kind())),
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
make_pair("visibility", Declaration::visibilityToString(visibility)),
make_pair("virtual", _node.markedVirtual()),
@ -467,7 +467,6 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node)
setJsonNode(_node, "UserDefinedTypeName", {
make_pair("name", namePathToString(_node.namePath())),
make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)),
make_pair("contractScope", idOrNull(_node.annotation().contractScope)),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
});
return false;
@ -726,13 +725,16 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
make_pair("arguments", toJson(_node.arguments())),
make_pair("tryCall", _node.annotation().tryCall)
};
FunctionCallKind nodeKind = *_node.annotation().kind;
if (m_legacy)
{
attributes.emplace_back("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall);
attributes.emplace_back("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion);
attributes.emplace_back("isStructConstructorCall", nodeKind == FunctionCallKind::StructConstructorCall);
attributes.emplace_back("type_conversion", nodeKind == FunctionCallKind::TypeConversion);
}
else
attributes.emplace_back("kind", functionCallKind(_node.annotation().kind));
attributes.emplace_back("kind", functionCallKind(nodeKind));
appendExpressionAttributes(attributes, _node.annotation());
setJsonNode(_node, "FunctionCall", std::move(attributes));
return false;

View File

@ -58,7 +58,7 @@ public:
);
/// Output the json representation of the AST to _stream.
void print(std::ostream& _stream, ASTNode const& _node);
Json::Value&& toJson(ASTNode const& _node);
Json::Value toJson(ASTNode const& _node);
template <class T>
Json::Value toJson(std::vector<ASTPointer<T>> const& _nodes)
{

View File

@ -381,6 +381,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
astAssert(_node["kind"].isString(), "Expected 'kind' to be a string!");
Token kind;
bool freeFunction = false;
string kindStr = member(_node, "kind").asString();
if (kindStr == "constructor")
@ -391,17 +392,27 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
kind = Token::Fallback;
else if (kindStr == "receive")
kind = Token::Receive;
else if (kindStr == "freeFunction")
{
kind = Token::Function;
freeFunction = true;
}
else
astAssert(false, "Expected 'kind' to be one of [constructor, function, fallback, receive]");
std::vector<ASTPointer<ModifierInvocation>> modifiers;
for (auto& mod: member(_node, "modifiers"))
modifiers.push_back(createModifierInvocation(mod));
Visibility vis = Visibility::Default;
if (!freeFunction)
vis = visibility(_node);
return createASTNode<FunctionDefinition>(
_node,
memberAsASTString(_node, "name"),
kind == Token::Constructor ? Visibility::Default : visibility(_node),
vis,
stateMutability(_node),
freeFunction,
kind,
memberAsBool(_node, "virtual"),
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),

View File

@ -712,6 +712,8 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other
return TypeResult::err("Exponent is fractional.");
if (!rationalNumberType->integerType())
return TypeResult::err("Exponent too large.");
if (rationalNumberType->isNegative())
return TypeResult::err("Exponentiation power is not allowed to be a negative integer literal.");
}
return this;
}
@ -2461,52 +2463,56 @@ TypeResult StructType::interfaceType(bool _inLibrary) const
TypeResult result{TypePointer{}};
if (recursive() && !(_inLibrary && location() == DataLocation::Storage))
return TypeResult::err(
"Recursive structs can only be passed as storage pointers to libraries, "
"not as memory objects to contract functions."
);
util::BreadthFirstSearch<StructDefinition const*> breadthFirstSearch{{&m_struct}};
breadthFirstSearch.run(
[&](StructDefinition const* _struct, auto&& _addChild) {
// Check that all members have interface types.
// Return an error if at least one struct member does not have a type.
// This might happen, for example, if the type of the member does not exist.
for (ASTPointer<VariableDeclaration> const& variable: _struct->members())
[&](StructDefinition const* _struct, auto&& _addChild)
{
// Check that all members have interface types.
// Return an error if at least one struct member does not have a type.
// This might happen, for example, if the type of the member does not exist.
for (ASTPointer<VariableDeclaration> const& variable: _struct->members())
{
// If the struct member does not have a type return false.
// A TypeError is expected in this case.
if (!variable->annotation().type)
{
// If the struct member does not have a type return false.
// A TypeError is expected in this case.
if (!variable->annotation().type)
result = TypeResult::err("Invalid type!");
breadthFirstSearch.abort();
return;
}
Type const* memberType = variable->annotation().type;
while (
memberType->category() == Type::Category::Array ||
memberType->category() == Type::Category::Mapping
)
{
if (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
memberType = arrayType->finalBaseType(false);
else if (auto mappingType = dynamic_cast<MappingType const*>(memberType))
memberType = mappingType->valueType();
}
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
_addChild(&innerStruct->structDefinition());
else
{
auto iType = memberType->interfaceType(_inLibrary);
if (!iType.get())
{
result = TypeResult::err("Invalid type!");
solAssert(!iType.message().empty(), "Expected detailed error message!");
result = iType;
breadthFirstSearch.abort();
return;
}
Type const* memberType = variable->annotation().type;
while (dynamic_cast<ArrayType const*>(memberType))
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType();
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
{
if (innerStruct->recursive() && !(_inLibrary && location() == DataLocation::Storage))
{
result = TypeResult::err(
"Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions."
);
breadthFirstSearch.abort();
return;
}
else
_addChild(&innerStruct->structDefinition());
}
else
{
auto iType = memberType->interfaceType(_inLibrary);
if (!iType.get())
{
solAssert(!iType.message().empty(), "Expected detailed error message!");
result = iType;
breadthFirstSearch.abort();
return;
}
}
}
}
}
);
@ -3151,10 +3157,8 @@ string FunctionType::toString(bool _short) const
{
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(m_declaration);
solAssert(functionDefinition, "");
auto const* contract = dynamic_cast<ContractDefinition const*>(functionDefinition->scope());
solAssert(contract, "");
name += contract->annotation().canonicalName;
name += '.';
if (auto const* contract = dynamic_cast<ContractDefinition const*>(functionDefinition->scope()))
name += contract->annotation().canonicalName + ".";
name += functionDefinition->name();
}
name += '(';
@ -3275,7 +3279,10 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
{
// Note that m_declaration might also be a state variable!
solAssert(m_declaration, "Declaration needed to determine interface function type.");
bool isLibraryFunction = kind() != Kind::Event && dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary();
bool isLibraryFunction = false;
if (kind() != Kind::Event)
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_declaration->scope()))
isLibraryFunction = contract->isLibrary();
util::Result<TypePointers> paramTypes =
transformParametersToExternal(m_parameterTypes, isLibraryFunction);
@ -3569,7 +3576,10 @@ string FunctionType::externalSignature() const
}
// "inLibrary" is only relevant if this is not an event.
bool const inLibrary = kind() != Kind::Event && dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary();
bool inLibrary = false;
if (kind() != Kind::Event)
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_declaration->scope()))
inLibrary = contract->isLibrary();
auto extParams = transformParametersToExternal(m_parameterTypes, inLibrary);
@ -4073,6 +4083,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
else
return MemberList::MemberMap({
{"interfaceId", TypeProvider::fixedBytes(4)},
{"name", TypeProvider::stringMemory()},
});
}
else if (m_typeArgument->category() == Type::Category::Integer)

View File

@ -404,7 +404,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions");
FunctionDefinition const* etherReceiver = _contract.receiveFunction();
solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have ether receiver functions");
solAssert(!_contract.isLibrary() || !etherReceiver, "Libraries can't have ether receiver functions");
bool needToAddCallvalueCheck = true;
if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary())

View File

@ -492,8 +492,10 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
auto functionCallKind = *_functionCall.annotation().kind;
CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
if (functionCallKind == FunctionCallKind::TypeConversion)
{
solAssert(_functionCall.arguments().size() == 1, "");
solAssert(_functionCall.names().empty(), "");
@ -517,7 +519,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
FunctionTypePointer functionType;
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@ -548,7 +550,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(found, "");
}
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@ -1734,9 +1736,17 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
Type::Category category = _memberAccess.annotation().type->category();
solAssert(
category == Type::Category::TypeType ||
category == Type::Category::Module,
category == Type::Category::Module ||
category == Type::Category::Function,
""
);
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
{
auto const* funDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration);
solAssert(funDef && funDef->isFree(), "");
solAssert(funType->kind() == FunctionType::Kind::Internal, "");
utils().pushCombinedFunctionEntryLabel(*funDef);
}
break;
}
default:

View File

@ -597,6 +597,139 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
});
}
string YulUtilFunctions::overflowCheckedIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
string functionName = "checked_exp_" + _type.identifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent) -> power {
base := <baseCleanupFunction>(base)
exponent := <exponentCleanupFunction>(exponent)
<?signed>
power := <exp>(base, exponent, <minValue>, <maxValue>)
<!signed>
power := <exp>(base, exponent, <maxValue>)
</signed>
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("exp", _type.isSigned() ? overflowCheckedSignedExpFunction() : overflowCheckedUnsignedExpFunction())
("maxValue", toCompactHexWithPrefix(_type.max()))
("minValue", toCompactHexWithPrefix(_type.min()))
("baseCleanupFunction", cleanupFunction(_type))
("exponentCleanupFunction", cleanupFunction(_exponentType))
.render();
});
}
string YulUtilFunctions::overflowCheckedUnsignedExpFunction()
{
string functionName = "checked_exp_unsigned";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent, max) -> power {
// This function currently cannot be inlined because of the
// "leave" statements. We have to improve the optimizer.
// Note that 0**0 == 1
if iszero(exponent) { power := 1 leave }
if iszero(base) { power := 0 leave }
power := 1
for { } gt(exponent, 1) {}
{
// overflow check for base * base
if gt(base, div(max, base)) { revert(0, 0) }
if and(exponent, 1)
{
// no check needed here because base >= power
power := mul(power, base)
}
base := mul(base, base)
exponent := <shr_1>(exponent)
}
if gt(power, div(max, base)) { revert(0, 0) }
power := mul(power, base)
}
)")
("functionName", functionName)
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::overflowCheckedSignedExpFunction()
{
string functionName = "checked_exp_signed";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent, min, max) -> power {
// Currently, `leave` avoids this function being inlined.
// We have to improve the optimizer.
// Note that 0**0 == 1
switch exponent
case 0 { power := 1 leave }
case 1 { power := base leave }
if iszero(base) { power := 0 leave }
power := 1
// We pull out the first iteration because it is the only one in which
// base can be negative.
// Exponent is at least 2 here.
// overflow check for base * base
switch sgt(base, 0)
case 1 { if gt(base, div(max, base)) { revert(0, 0) } }
case 0 { if slt(base, sdiv(max, base)) { revert(0, 0) } }
if and(exponent, 1)
{
power := base
}
base := mul(base, base)
exponent := <shr_1>(exponent)
// Below this point, base is always positive.
for { } gt(exponent, 1) {}
{
// overflow check for base * base
if gt(base, div(max, base)) { revert(0, 0) }
if and(exponent, 1)
{
// No checks for power := mul(power, base) needed, because the check
// for base * base above is sufficient, since:
// |power| <= base (proof by induction) and thus:
// |power * base| <= base * base <= max <= |min|
power := mul(power, base)
}
base := mul(base, base)
exponent := <shr_1>(exponent)
}
if and(sgt(power, 0), gt(power, div(max, base))) { revert(0, 0) }
if and(slt(power, 0), slt(power, sdiv(min, base))) { revert(0, 0) }
power := mul(power, base)
}
)")
("functionName", functionName)
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
string functionName = "extract_byte_array_length";
@ -1911,8 +2044,23 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
break;
}
case Type::Category::Struct:
solUnimplementedAssert(false, "Struct conversion not implemented.");
{
solAssert(toCategory == Type::Category::Struct, "");
auto const& fromStructType = dynamic_cast<StructType const &>(_from);
auto const& toStructType = dynamic_cast<StructType const &>(_to);
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
solUnimplementedAssert(!fromStructType.isDynamicallyEncoded(), "");
solUnimplementedAssert(toStructType.location() == DataLocation::Memory, "");
solUnimplementedAssert(fromStructType.location() == DataLocation::CallData, "");
body = Whiskers(R"(
converted := <abiDecode>(value, calldatasize())
)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder(
{&toStructType}
)).render();
break;
}
case Type::Category::FixedBytes:
{
FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from);

View File

@ -125,6 +125,21 @@ public:
/// signature: (x, y) -> diff
std::string overflowCheckedIntSubFunction(IntegerType const& _type);
/// @returns the name of the exponentiation function.
/// signature: (base, exponent) -> power
std::string overflowCheckedIntExpFunction(IntegerType const& _type, IntegerType const& _exponentType);
/// Generic unsigned checked exponentiation function.
/// Reverts if the result is larger than max.
/// signature: (base, exponent, max) -> power
std::string overflowCheckedUnsignedExpFunction();
/// Generic signed checked exponentiation function.
/// Reverts if the result is smaller than min or larger than max.
/// The code relies on max <= |min| and min < 0.
/// signature: (base, exponent, min, max) -> power
std::string overflowCheckedSignedExpFunction();
/// @returns the name of a function that fetches the length of the given
/// array
/// signature: (array) -> length

View File

@ -98,8 +98,9 @@ public:
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const
std::pair<u256, unsigned> storageLocationOfStateVariable(VariableDeclaration const& _varDecl) const
{
solAssert(isStateVariable(_varDecl), "");
return m_stateVariables.at(&_varDecl);
}

View File

@ -305,7 +305,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
string code;
auto const& location = m_context.storageLocationOfVariable(_varDecl);
auto const& location = m_context.storageLocationOfStateVariable(_varDecl);
code += Whiskers(R"(
let slot := <slot>
let offset := <offset>

View File

@ -75,18 +75,30 @@ struct CopyTranslate: public yul::ASTCopier
{
solAssert(reference.isOffset != reference.isSlot, "");
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(*varDecl);
string value;
if (varDecl->isStateVariable())
value =
reference.isSlot ?
m_context.storageLocationOfStateVariable(*varDecl).first.str() :
to_string(m_context.storageLocationOfStateVariable(*varDecl).second);
else
{
solAssert(varDecl->isLocalVariable(), "");
if (reference.isSlot)
value = IRVariable{*varDecl}.part("slot").name();
else if (varDecl->type()->isValueType())
value = IRVariable{*varDecl}.part("offset").name();
else
{
solAssert(!IRVariable{*varDecl}.hasPart("offset"), "");
value = "0";
}
}
string const value = reference.isSlot ?
slot_offset.first.str() :
to_string(slot_offset.second);
return yul::Literal{
_identifier.location,
yul::LiteralKind::Number,
yul::YulString{value},
{}
};
if (isdigit(value.front()))
return yul::Literal{_identifier.location, yul::LiteralKind::Number, yul::YulString{value}, {}};
else
return yul::Identifier{_identifier.location, yul::YulString{value}};
}
}
return ASTCopier::operator()(_identifier);
@ -152,8 +164,8 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
_varDecl.immutable() ?
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first),
m_context.storageLocationOfVariable(_varDecl).second
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
m_context.storageLocationOfStateVariable(_varDecl).second
}},
*_varDecl.value()
);
@ -277,6 +289,7 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
solAssert(rightIntermediateType->isValueType(), "Compound operators only available for value types.");
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
solAssert(binaryOperator != Token::Exp, "");
if (TokenTraits::isShiftOp(binaryOperator))
{
solAssert(type(_assignment) == leftIntermediate.type(), "");
@ -593,11 +606,17 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
solAssert(false, "Unknown comparison operator.");
define(_binOp) << expr << "\n";
}
else if (TokenTraits::isShiftOp(op))
else if (TokenTraits::isShiftOp(op) || op == Token::Exp)
{
IRVariable left = convert(_binOp.leftExpression(), *commonType);
IRVariable right = convert(_binOp.rightExpression(), *type(_binOp.rightExpression()).mobileType());
define(_binOp) << shiftOperation(_binOp.getOperator(), left, right) << "\n";
if (op == Token::Exp)
define(_binOp) << m_utils.overflowCheckedIntExpFunction(
dynamic_cast<IntegerType const&>(left.type()),
dynamic_cast<IntegerType const&>(right.type())
) << "(" << left.name() << ", " << right.name() << ")\n";
else
define(_binOp) << shiftOperation(_binOp.getOperator(), left, right) << "\n";
}
else
{
@ -624,12 +643,9 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{
solUnimplementedAssert(
_functionCall.annotation().kind != FunctionCallKind::Unset,
"This type of function call is not yet implemented"
);
auto functionCallKind = *_functionCall.annotation().kind;
if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
if (functionCallKind == FunctionCallKind::TypeConversion)
{
solAssert(
_functionCall.expression().annotation().type->category() == Type::Category::TypeType,
@ -641,7 +657,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
FunctionTypePointer functionType = nullptr;
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@ -672,7 +688,7 @@ 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)
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@ -2022,8 +2038,8 @@ void IRGeneratorForStatements::handleVariableReference(
setLValue(_referencingExpression, IRLValue{
*_variable.annotation().type,
IRLValue::Storage{
toCompactHexWithPrefix(m_context.storageLocationOfVariable(_variable).first),
m_context.storageLocationOfVariable(_variable).second
toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_variable).first),
m_context.storageLocationOfStateVariable(_variable).second
}
});
else

View File

@ -53,6 +53,17 @@ IRVariable IRVariable::part(string const& _name) const
solAssert(false, "Invalid stack item name: " + _name);
}
bool IRVariable::hasPart(std::string const& _name) const
{
for (auto const& [itemName, itemType]: m_type.stackItems())
if (itemName == _name)
{
solAssert(itemName.empty() || itemType, "");
return true;
}
return false;
}
vector<string> IRVariable::stackSlots() const
{
vector<string> result;

View File

@ -71,6 +71,10 @@ public:
/// in ``m_type.stackItems()`` and may again occupy multiple stack slots.
IRVariable part(std::string const& _slot) const;
/// @returns true if variable contains @a _name component
/// @a _name name of the component that is being checked
bool hasPart(std::string const& _name) const;
/// @returns a vector containing the names of the stack slots of the variable.
std::vector<std::string> stackSlots() const;

View File

@ -198,6 +198,24 @@ bool BMC::visit(IfStatement const& _node)
return false;
}
bool BMC::visit(Conditional const& _op)
{
m_context.pushSolver();
_op.condition().accept(*this);
if (isRootFunction())
addVerificationTarget(
VerificationTarget::Type::ConstantCondition,
expr(_op.condition()),
&_op.condition()
);
m_context.popSolver();
SMTEncoder::visit(_op);
return false;
}
// Here we consider the execution of two branches:
// Branch 1 assumes the loop condition to be true and executes the loop once,
// after resetting touched variables.
@ -337,8 +355,9 @@ void BMC::endVisit(UnaryOperation const& _op)
void BMC::endVisit(FunctionCall const& _funCall)
{
solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, "");
if (_funCall.annotation().kind != FunctionCallKind::FunctionCall)
auto functionCallKind = *_funCall.annotation().kind;
if (functionCallKind != FunctionCallKind::FunctionCall)
{
SMTEncoder::endVisit(_funCall);
return;
@ -498,8 +517,37 @@ pair<smtutil::Expression, smtutil::Expression> BMC::arithmeticOperation(
auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression);
auto const* intType = dynamic_cast<IntegerType const*>(_commonType);
if (!intType)
intType = TypeProvider::uint256();
// Mod does not need underflow/overflow checks.
if (_op == Token::Mod)
return values;
VerificationTarget::Type type;
// The order matters here:
// If _op is Div and intType is signed, we only care about overflow.
if (_op == Token::Div)
{
if (intType->isSigned())
// Signed division can only overflow.
type = VerificationTarget::Type::Overflow;
else
// Unsigned division cannot underflow/overflow.
return values;
}
else if (intType->isSigned())
type = VerificationTarget::Type::UnderOverflow;
else if (_op == Token::Sub)
type = VerificationTarget::Type::Underflow;
else if (_op == Token::Add || _op == Token::Mul)
type = VerificationTarget::Type::Overflow;
else
solAssert(false, "");
addVerificationTarget(
VerificationTarget::Type::UnderOverflow,
type,
values.second,
&_expression
);
@ -605,12 +653,19 @@ void BMC::checkUnderflow(BMCVerificationTarget& _target, smtutil::Expression con
_target.type == VerificationTarget::Type::UnderOverflow,
""
);
IntegerType const* intType = nullptr;
if (auto const* type = dynamic_cast<IntegerType const*>(_target.expression->annotation().type))
intType = type;
else
if (
m_solvedTargets.count(_target.expression) && (
m_solvedTargets.at(_target.expression).count(VerificationTarget::Type::Underflow) ||
m_solvedTargets.at(_target.expression).count(VerificationTarget::Type::UnderOverflow)
)
)
return;
auto const* intType = dynamic_cast<IntegerType const*>(_target.expression->annotation().type);
if (!intType)
intType = TypeProvider::uint256();
solAssert(intType, "");
checkCondition(
_target.constraints && _constraints && _target.value < smt::minValue(*intType),
_target.callStack,
@ -631,13 +686,19 @@ void BMC::checkOverflow(BMCVerificationTarget& _target, smtutil::Expression cons
_target.type == VerificationTarget::Type::UnderOverflow,
""
);
IntegerType const* intType = nullptr;
if (auto const* type = dynamic_cast<IntegerType const*>(_target.expression->annotation().type))
intType = type;
else
if (
m_solvedTargets.count(_target.expression) && (
m_solvedTargets.at(_target.expression).count(VerificationTarget::Type::Overflow) ||
m_solvedTargets.at(_target.expression).count(VerificationTarget::Type::UnderOverflow)
)
)
return;
auto const* intType = dynamic_cast<IntegerType const*>(_target.expression->annotation().type);
if (!intType)
intType = TypeProvider::uint256();
solAssert(intType, "");
checkCondition(
_target.constraints && _constraints && _target.value > smt::maxValue(*intType),
_target.callStack,

View File

@ -84,6 +84,7 @@ private:
bool visit(FunctionDefinition const& _node) override;
void endVisit(FunctionDefinition const& _node) override;
bool visit(IfStatement const& _node) override;
bool visit(Conditional const& _node) override;
bool visit(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override;
void endVisit(UnaryOperation const& _node) override;

View File

@ -147,7 +147,7 @@ void CHC::endVisit(ContractDefinition const& _contract)
else
inlineConstructorHierarchy(_contract);
connectBlocks(m_currentBlock, summary(_contract), m_error.currentValue() == 0);
connectBlocks(m_currentBlock, summary(_contract));
clearIndices(m_currentContract, nullptr);
vector<smtutil::Expression> symbArgs = currentFunctionVariables(*m_currentContract);
@ -424,9 +424,9 @@ bool CHC::visit(ForStatement const& _for)
void CHC::endVisit(FunctionCall const& _funCall)
{
solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, "");
auto functionCallKind = *_funCall.annotation().kind;
if (_funCall.annotation().kind != FunctionCallKind::FunctionCall)
if (functionCallKind != FunctionCallKind::FunctionCall)
{
SMTEncoder::endVisit(_funCall);
return;
@ -452,6 +452,9 @@ void CHC::endVisit(FunctionCall const& _funCall)
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::Creation:
SMTEncoder::endVisit(_funCall);
unknownFunctionCall(_funCall);
break;
case FunctionType::Kind::KECCAK256:
case FunctionType::Kind::ECRecover:
case FunctionType::Kind::SHA256:
@ -459,9 +462,7 @@ void CHC::endVisit(FunctionCall const& _funCall)
case FunctionType::Kind::BlockHash:
case FunctionType::Kind::AddMod:
case FunctionType::Kind::MulMod:
SMTEncoder::endVisit(_funCall);
unknownFunctionCall(_funCall);
break;
[[fallthrough]];
default:
SMTEncoder::endVisit(_funCall);
break;
@ -601,14 +602,74 @@ void CHC::makeArrayPopVerificationTarget(FunctionCall const& _arrayPop)
auto previousError = m_error.currentValue();
m_error.increaseIndex();
addArrayPopVerificationTarget(&_arrayPop, m_error.currentValue());
connectBlocks(
m_currentBlock,
m_currentFunction->isConstructor() ? summary(*m_currentContract) : summary(*m_currentFunction),
currentPathConditions() && symbArray->length() <= 0 && m_error.currentValue() == newErrorId(_arrayPop)
addVerificationTarget(&_arrayPop, VerificationTarget::Type::PopEmptyArray, m_error.currentValue());
smtutil::Expression target = (symbArray->length() <= 0) && (m_error.currentValue() == newErrorId(_arrayPop));
m_context.addAssertion((m_error.currentValue() == previousError) || target);
}
pair<smtutil::Expression, smtutil::Expression> CHC::arithmeticOperation(
Token _op,
smtutil::Expression const& _left,
smtutil::Expression const& _right,
TypePointer const& _commonType,
frontend::Expression const& _expression
)
{
auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression);
IntegerType const* intType = nullptr;
if (auto const* type = dynamic_cast<IntegerType const*>(_commonType))
intType = type;
else
intType = TypeProvider::uint256();
// Mod does not need underflow/overflow checks.
// Div only needs overflow check for signed types.
if (_op == Token::Mod || (_op == Token::Div && !intType->isSigned()))
return values;
auto previousError = m_error.currentValue();
m_error.increaseIndex();
VerificationTarget::Type targetType;
unsigned errorId = newErrorId(_expression);
optional<smtutil::Expression> target;
if (_op == Token::Div)
{
targetType = VerificationTarget::Type::Overflow;
target = values.second > intType->maxValue() && m_error.currentValue() == errorId;
}
else if (intType->isSigned())
{
unsigned secondErrorId = newErrorId(_expression);
targetType = VerificationTarget::Type::UnderOverflow;
target = (values.second < intType->minValue() && m_error.currentValue() == errorId) ||
(values.second > intType->maxValue() && m_error.currentValue() == secondErrorId);
}
else if (_op == Token::Sub)
{
targetType = VerificationTarget::Type::Underflow;
target = values.second < intType->minValue() && m_error.currentValue() == errorId;
}
else if (_op == Token::Add || _op == Token::Mul)
{
targetType = VerificationTarget::Type::Overflow;
target = values.second > intType->maxValue() && m_error.currentValue() == errorId;
}
else
solAssert(false, "");
addVerificationTarget(
&_expression,
targetType,
m_error.currentValue()
);
m_context.addAssertion(m_error.currentValue() == previousError);
m_context.addAssertion((m_error.currentValue() == previousError) || *target);
return values;
}
void CHC::resetSourceAnalysis()
@ -1174,26 +1235,25 @@ void CHC::addVerificationTarget(
m_verificationTargets.emplace(_scope, CHCVerificationTarget{{_type, _from, _constraints}, _errorId});
}
void CHC::addAssertVerificationTarget(ASTNode const* _scope, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId)
{
addVerificationTarget(_scope, VerificationTarget::Type::Assert, _from, _constraints, _errorId);
}
void CHC::addArrayPopVerificationTarget(ASTNode const* _scope, smtutil::Expression _errorId)
void CHC::addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smtutil::Expression _errorId)
{
solAssert(m_currentContract, "");
solAssert(m_currentFunction, "");
if (m_currentFunction->isConstructor())
addVerificationTarget(_scope, VerificationTarget::Type::PopEmptyArray, summary(*m_currentContract), smtutil::Expression(true), _errorId);
if (!m_currentFunction || m_currentFunction->isConstructor())
addVerificationTarget(_scope, _type, summary(*m_currentContract), smtutil::Expression(true), _errorId);
else
{
auto iface = (*m_interfaces.at(m_currentContract))(initialStateVariables());
auto sum = summary(*m_currentFunction);
addVerificationTarget(_scope, VerificationTarget::Type::PopEmptyArray, iface, sum, _errorId);
addVerificationTarget(_scope, _type, iface, sum, _errorId);
}
}
void CHC::addAssertVerificationTarget(ASTNode const* _scope, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId)
{
addVerificationTarget(_scope, VerificationTarget::Type::Assert, _from, _constraints, _errorId);
}
void CHC::checkVerificationTargets()
{
for (auto const& [scope, target]: m_verificationTargets)
@ -1203,8 +1263,12 @@ void CHC::checkVerificationTargets()
else
{
string satMsg;
string satMsgUnderflow;
string satMsgOverflow;
string unknownMsg;
ErrorId errorReporterId;
ErrorId underflowErrorId = 3944_error;
ErrorId overflowErrorId = 4984_error;
if (target.type == VerificationTarget::Type::PopEmptyArray)
{
@ -1213,12 +1277,51 @@ void CHC::checkVerificationTargets()
unknownMsg = "Empty array \"pop\" might happen here.";
errorReporterId = 2529_error;
}
else if (
target.type == VerificationTarget::Type::Underflow ||
target.type == VerificationTarget::Type::Overflow ||
target.type == VerificationTarget::Type::UnderOverflow
)
{
auto const* expr = dynamic_cast<Expression const*>(scope);
solAssert(expr, "");
auto const* intType = dynamic_cast<IntegerType const*>(expr->annotation().type);
if (!intType)
intType = TypeProvider::uint256();
satMsgUnderflow = "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ") happens here";
satMsgOverflow = "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ") happens here";
if (target.type == VerificationTarget::Type::Underflow)
{
satMsg = satMsgUnderflow;
errorReporterId = underflowErrorId;
}
else if (target.type == VerificationTarget::Type::Overflow)
{
satMsg = satMsgOverflow;
errorReporterId = overflowErrorId;
}
}
else
solAssert(false, "");
auto it = m_errorIds.find(scope->id());
solAssert(it != m_errorIds.end(), "");
checkAndReportTarget(scope, target, it->second, errorReporterId, satMsg, unknownMsg);
unsigned errorId = it->second;
if (target.type != VerificationTarget::Type::UnderOverflow)
checkAndReportTarget(scope, target, errorId, errorReporterId, satMsg, unknownMsg);
else
{
auto specificTarget = target;
specificTarget.type = VerificationTarget::Type::Underflow;
checkAndReportTarget(scope, specificTarget, errorId, underflowErrorId, satMsgUnderflow, unknownMsg);
++it;
solAssert(it != m_errorIds.end(), "");
specificTarget.type = VerificationTarget::Type::Overflow;
checkAndReportTarget(scope, specificTarget, it->second, overflowErrorId, satMsgOverflow, unknownMsg);
}
}
}
}

View File

@ -84,6 +84,14 @@ private:
void externalFunctionCall(FunctionCall const& _funCall);
void unknownFunctionCall(FunctionCall const& _funCall);
void makeArrayPopVerificationTarget(FunctionCall const& _arrayPop) override;
/// Creates underflow/overflow verification targets.
std::pair<smtutil::Expression, smtutil::Expression> arithmeticOperation(
Token _op,
smtutil::Expression const& _left,
smtutil::Expression const& _right,
TypePointer const& _commonType,
Expression const& _expression
) override;
//@}
struct IdCompare
@ -197,8 +205,8 @@ private:
std::pair<smtutil::CheckResult, smtutil::CHCSolverInterface::CexGraph> query(smtutil::Expression const& _query, langutil::SourceLocation const& _location);
void addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId);
void addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smtutil::Expression _errorId);
void addAssertVerificationTarget(ASTNode const* _scope, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId);
void addArrayPopVerificationTarget(ASTNode const* _scope, smtutil::Expression _errorId);
void checkVerificationTargets();
// Forward declaration. Definition is below.

View File

@ -420,8 +420,11 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
_tuple.location(),
"Assertion checker does not yet implement inline arrays."
);
else if (_tuple.annotation().type->category() == Type::Category::Tuple)
else if (_tuple.components().size() == 1)
defineExpr(_tuple, expr(*_tuple.components().front()));
else
{
solAssert(_tuple.annotation().type->category() == Type::Category::Tuple, "");
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
@ -445,28 +448,25 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
}
}
}
else
{
/// Parenthesized expressions are also TupleExpression regardless their type.
auto const& components = _tuple.components();
solAssert(components.size() == 1, "");
defineExpr(_tuple, expr(*components.front()));
}
}
void SMTEncoder::endVisit(UnaryOperation const& _op)
{
if (TokenTraits::isBitOp(_op.getOperator()))
return bitwiseNotOperation(_op);
if (_op.annotation().type->category() == Type::Category::RationalNumber)
return;
createExpr(_op);
auto const* subExpr = innermostTuple(_op.subExpression());
switch (_op.getOperator())
{
case Token::Not: // !
{
solAssert(smt::isBool(_op.annotation().type->category()), "");
defineExpr(_op, !expr(_op.subExpression()));
defineExpr(_op, !expr(*subExpr));
break;
}
case Token::Inc: // ++ (pre- or postfix)
@ -474,8 +474,8 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
{
auto cat = _op.annotation().type->category();
solAssert(smt::isInteger(cat) || smt::isFixedPoint(cat), "");
solAssert(_op.subExpression().annotation().willBeWrittenTo, "");
if (auto identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
solAssert(subExpr->annotation().willBeWrittenTo, "");
if (auto identifier = dynamic_cast<Identifier const*>(subExpr))
{
auto decl = identifierToVariable(*identifier);
solAssert(decl, "");
@ -484,12 +484,12 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
assignment(*decl, newValue);
}
else if (dynamic_cast<IndexAccess const*>(&_op.subExpression()))
else if (dynamic_cast<IndexAccess const*>(subExpr))
{
auto innerValue = expr(_op.subExpression());
auto innerValue = expr(*subExpr);
auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
arrayIndexAssignment(_op.subExpression(), newValue);
arrayIndexAssignment(*subExpr, newValue);
}
else
m_errorReporter.warning(
@ -502,25 +502,24 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
}
case Token::Sub: // -
{
defineExpr(_op, 0 - expr(_op.subExpression()));
defineExpr(_op, 0 - expr(*subExpr));
break;
}
case Token::Delete:
{
auto const& subExpr = _op.subExpression();
if (auto decl = identifierToVariable(subExpr))
if (auto decl = identifierToVariable(*subExpr))
{
m_context.newValue(*decl);
m_context.setZeroValue(*decl);
}
else
{
solAssert(m_context.knownExpression(subExpr), "");
auto const& symbVar = m_context.expression(subExpr);
solAssert(m_context.knownExpression(*subExpr), "");
auto const& symbVar = m_context.expression(*subExpr);
symbVar->increaseIndex();
m_context.setZeroValue(*symbVar);
if (dynamic_cast<IndexAccess const*>(&_op.subExpression()))
arrayIndexAssignment(_op.subExpression(), symbVar->currentValue());
if (dynamic_cast<IndexAccess const*>(subExpr))
arrayIndexAssignment(*subExpr, symbVar->currentValue());
else
m_errorReporter.warning(
2683_error,
@ -579,11 +578,33 @@ void SMTEncoder::endVisit(BinaryOperation const& _op)
);
}
bool SMTEncoder::visit(Conditional const& _op)
{
_op.condition().accept(*this);
auto indicesEndTrue = visitBranch(&_op.trueExpression(), expr(_op.condition()));
auto touchedVars = touchedVariables(_op.trueExpression());
auto indicesEndFalse = visitBranch(&_op.falseExpression(), !expr(_op.condition()));
touchedVars += touchedVariables(_op.falseExpression());
mergeVariables(touchedVars, expr(_op.condition()), indicesEndTrue, indicesEndFalse);
defineExpr(_op, smtutil::Expression::ite(
expr(_op.condition()),
expr(_op.trueExpression()),
expr(_op.falseExpression())
));
return false;
}
void SMTEncoder::endVisit(FunctionCall const& _funCall)
{
solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, "");
auto functionCallKind = *_funCall.annotation().kind;
createExpr(_funCall);
if (_funCall.annotation().kind == FunctionCallKind::StructConstructorCall)
if (functionCallKind == FunctionCallKind::StructConstructorCall)
{
m_errorReporter.warning(
4639_error,
@ -593,7 +614,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
return;
}
if (_funCall.annotation().kind == FunctionCallKind::TypeConversion)
if (functionCallKind == FunctionCallKind::TypeConversion)
{
visitTypeConversion(_funCall);
return;
@ -753,7 +774,7 @@ void SMTEncoder::endVisit(ElementaryTypeNameExpression const& _typeName)
void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)
{
solAssert(_funCall.annotation().kind == FunctionCallKind::TypeConversion, "");
solAssert(*_funCall.annotation().kind == FunctionCallKind::TypeConversion, "");
solAssert(_funCall.arguments().size() == 1, "");
auto argument = _funCall.arguments().front();
unsigned argSize = argument->annotation().type->storageBytes();
@ -1083,25 +1104,22 @@ void SMTEncoder::arrayPop(FunctionCall const& _funCall)
auto oldElements = symbArray->elements();
auto oldLength = symbArray->length();
m_context.addAssertion(oldLength > 0);
symbArray->increaseIndex();
m_context.addAssertion(symbArray->elements() == oldElements);
auto newLength = smtutil::Expression::ite(
oldLength == 0,
smt::maxValue(*TypeProvider::uint256()),
oldLength - 1
oldLength > 0,
oldLength - 1,
0
);
m_context.addAssertion(symbArray->length() == oldLength - 1);
m_context.addAssertion(symbArray->length() == newLength);
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
}
void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression const& _array)
{
Expression const* expr = &_expr;
if (auto const* tupleExpr = dynamic_cast<TupleExpression const*>(expr))
expr = innermostTuple(*tupleExpr);
Expression const* expr = innermostTuple(_expr);
if (auto const* id = dynamic_cast<Identifier const*>(expr))
{
@ -1385,20 +1403,7 @@ void SMTEncoder::bitwiseOperation(BinaryOperation const& _op)
auto commonType = _op.annotation().commonType;
solAssert(commonType, "");
unsigned bvSize = 256;
bool isSigned = false;
if (auto const* intType = dynamic_cast<IntegerType const*>(commonType))
{
bvSize = intType->numBits();
isSigned = intType->isSigned();
}
else if (auto const* fixedType = dynamic_cast<FixedPointType const*>(commonType))
{
bvSize = fixedType->numBits();
isSigned = fixedType->isSigned();
}
else if (auto const* fixedBytesType = dynamic_cast<FixedBytesType const*>(commonType))
bvSize = fixedBytesType->numBytes() * 8;
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(commonType);
auto bvLeft = smtutil::Expression::int2bv(expr(_op.leftExpression(), commonType), bvSize);
auto bvRight = smtutil::Expression::int2bv(expr(_op.rightExpression(), commonType), bvSize);
@ -1406,18 +1411,26 @@ void SMTEncoder::bitwiseOperation(BinaryOperation const& _op)
optional<smtutil::Expression> result;
if (_op.getOperator() == Token::BitAnd)
result = bvLeft & bvRight;
// TODO implement the other operators
else
m_errorReporter.warning(
1093_error,
_op.location(),
"Assertion checker does not yet implement this bitwise operator."
);
else if (_op.getOperator() == Token::BitOr)
result = bvLeft | bvRight;
else if (_op.getOperator() == Token::BitXor)
result = bvLeft ^ bvRight;
solAssert(result, "");
if (result)
defineExpr(_op, smtutil::Expression::bv2int(*result, isSigned));
}
void SMTEncoder::bitwiseNotOperation(UnaryOperation const& _op)
{
solAssert(_op.getOperator() == Token::BitNot, "");
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_op.annotation().type);
auto bvOperand = smtutil::Expression::int2bv(expr(_op.subExpression(), _op.annotation().type), bvSize);
defineExpr(_op, smtutil::Expression::bv2int(~bvOperand, isSigned));
}
smtutil::Expression SMTEncoder::division(smtutil::Expression _left, smtutil::Expression _right, IntegerType const& _type)
{
// Signed division in SMTLIB2 rounds differently for negative division.
@ -1443,9 +1456,7 @@ void SMTEncoder::assignment(
"Tuple assignments should be handled by tupleAssignment."
);
Expression const* left = &_left;
if (auto const* tuple = dynamic_cast<TupleExpression const*>(left))
left = innermostTuple(*tuple);
Expression const* left = innermostTuple(_left);
if (!smt::isSupportedType(_type->category()))
{
@ -1473,15 +1484,16 @@ void SMTEncoder::assignment(
void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _right)
{
auto lTuple = dynamic_cast<TupleExpression const*>(innermostTuple(dynamic_cast<TupleExpression const&>(_left)));
auto lTuple = dynamic_cast<TupleExpression const*>(innermostTuple(_left));
solAssert(lTuple, "");
Expression const* right = innermostTuple(_right);
auto const& lComponents = lTuple->components();
// If both sides are tuple expressions, we individually and potentially
// recursively assign each pair of components.
// This is because of potential type conversion.
if (auto rTuple = dynamic_cast<TupleExpression const*>(&_right))
if (auto rTuple = dynamic_cast<TupleExpression const*>(right))
{
auto const& rComponents = rTuple->components();
solAssert(lComponents.size() == rComponents.size(), "");
@ -1502,13 +1514,13 @@ void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _rig
}
else
{
auto rType = dynamic_cast<TupleType const*>(_right.annotation().type);
auto rType = dynamic_cast<TupleType const*>(right->annotation().type);
solAssert(rType, "");
auto const& rComponents = rType->components();
solAssert(lComponents.size() == rComponents.size(), "");
auto symbRight = expr(_right);
auto symbRight = expr(*right);
solAssert(symbRight.sort->kind == smtutil::Kind::Tuple, "");
for (unsigned i = 0; i < lComponents.size(); ++i)
@ -1899,10 +1911,12 @@ Expression const* SMTEncoder::leftmostBase(IndexAccess const& _indexAccess)
return base;
}
Expression const* SMTEncoder::innermostTuple(TupleExpression const& _tuple)
Expression const* SMTEncoder::innermostTuple(Expression const& _expr)
{
solAssert(!_tuple.isInlineArray(), "");
TupleExpression const* tuple = &_tuple;
auto const* tuple = dynamic_cast<TupleExpression const*>(&_expr);
if (!tuple || tuple->isInlineArray())
return &_expr;
Expression const* expr = tuple;
while (tuple && !tuple->isInlineArray() && tuple->components().size() == 1)
{
@ -1948,7 +1962,7 @@ string SMTEncoder::extraComment()
FunctionDefinition const* SMTEncoder::functionCallToDefinition(FunctionCall const& _funCall)
{
if (_funCall.annotation().kind != FunctionCallKind::FunctionCall)
if (*_funCall.annotation().kind != FunctionCallKind::FunctionCall)
return nullptr;
FunctionDefinition const* funDef = nullptr;

View File

@ -54,8 +54,9 @@ public:
/// @returns the leftmost identifier in a multi-d IndexAccess.
static Expression const* leftmostBase(IndexAccess const& _indexAccess);
/// @returns the innermost element in a chain of 1-tuples.
static Expression const* innermostTuple(TupleExpression const& _tuple);
/// @returns the innermost element in a chain of 1-tuples if applicable,
/// otherwise _expr.
static Expression const* innermostTuple(Expression const& _expr);
/// @returns the FunctionDefinition of a FunctionCall
/// if possible or nullptr.
@ -83,6 +84,7 @@ protected:
void endVisit(UnaryOperation const& _node) override;
bool visit(BinaryOperation const& _node) override;
void endVisit(BinaryOperation const& _node) override;
bool visit(Conditional const& _node) override;
void endVisit(FunctionCall const& _node) override;
bool visit(ModifierInvocation const& _node) override;
void endVisit(Identifier const& _node) override;
@ -114,6 +116,7 @@ protected:
void compareOperation(BinaryOperation const& _op);
void booleanOperation(BinaryOperation const& _op);
void bitwiseOperation(BinaryOperation const& _op);
void bitwiseNotOperation(UnaryOperation const& _op);
void initContract(ContractDefinition const& _contract);
void initFunction(FunctionDefinition const& _function);

View File

@ -92,13 +92,35 @@ SortPointer smtSort(frontend::Type const& _type)
string tupleName;
if (
auto arrayType = dynamic_cast<ArrayType const*>(&_type);
(arrayType && arrayType->isString()) ||
(arrayType && (arrayType->isString() || arrayType->isByteArray())) ||
_type.category() == frontend::Type::Category::ArraySlice ||
_type.category() == frontend::Type::Category::StringLiteral
)
tupleName = "bytes_tuple";
tupleName = "bytes";
else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
{
auto baseType = arrayType->baseType();
// Solidity allows implicit conversion also when assigning arrays.
// So if the base type potentially has a size, that size cannot go
// in the tuple's name.
if (auto tupleSort = dynamic_pointer_cast<TupleSort>(array->range))
tupleName = tupleSort->name;
else if (
baseType->category() == frontend::Type::Category::Integer ||
baseType->category() == frontend::Type::Category::FixedPoint
)
tupleName = "uint";
else if (baseType->category() == frontend::Type::Category::FixedBytes)
tupleName = "fixedbytes";
else
tupleName = arrayType->baseType()->toString(true);
tupleName += "[]";
}
else
tupleName = _type.toString(true) + "_tuple";
tupleName = _type.toString(true);
tupleName += "_tuple";
return make_shared<TupleSort>(
tupleName,
@ -410,6 +432,18 @@ smtutil::Expression zeroValue(frontend::TypePointer const& _type)
return 0;
}
pair<unsigned, bool> typeBvSizeAndSignedness(frontend::TypePointer const& _type)
{
if (auto const* intType = dynamic_cast<IntegerType const*>(_type))
return {intType->numBits(), intType->isSigned()};
else if (auto const* fixedType = dynamic_cast<FixedPointType const*>(_type))
return {fixedType->numBits(), fixedType->isSigned()};
else if (auto const* fixedBytesType = dynamic_cast<FixedBytesType const*>(_type))
return {fixedBytesType->numBytes() * 8, false};
else
solAssert(false, "");
}
void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context)
{
setSymbolicUnknownValue(_variable.currentValue(), _variable.type(), _context);

View File

@ -67,6 +67,8 @@ smtutil::Expression minValue(frontend::IntegerType const& _type);
smtutil::Expression maxValue(frontend::IntegerType const& _type);
smtutil::Expression zeroValue(frontend::TypePointer const& _type);
std::pair<unsigned, bool> typeBvSizeAndSignedness(frontend::TypePointer const& type);
void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _context);
void setSymbolicZeroValue(smtutil::Expression _expr, frontend::TypePointer const& _type, EncodingContext& _context);
void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context);

View File

@ -107,8 +107,11 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
case Token::Enum:
nodes.push_back(parseEnumDefinition());
break;
case Token::Function:
nodes.push_back(parseFunctionDefinition(true));
break;
default:
fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum definition.");
fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/function definition.");
}
}
solAssert(m_recursionDepth == 0, "");
@ -548,7 +551,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _isStateVari
return result;
}
ASTPointer<ASTNode> Parser::parseFunctionDefinition()
ASTPointer<ASTNode> Parser::parseFunctionDefinition(bool _freeFunction)
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
@ -607,6 +610,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
name,
header.visibility,
header.stateMutability,
_freeFunction,
kind,
header.isVirtual,
header.overrides,
@ -1046,7 +1050,7 @@ ASTPointer<Mapping> Parser::parseMapping()
}
else
fatalParserError(1005_error, "Expected elementary type name or identifier for mapping key type");
expectToken(Token::Arrow);
expectToken(Token::DoubleArrow);
ASTPointer<TypeName> valueType = parseTypeName();
nodeFactory.markEndPosition();
expectToken(Token::RParen);
@ -1102,7 +1106,7 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */
m_inParserRecovery = true;
}
if (m_parserErrorRecovery)
if (m_inParserRecovery)
expectTokenOrConsumeUntil(Token::RBrace, "Block");
else
expectToken(Token::RBrace);

View File

@ -92,7 +92,7 @@ private:
ASTPointer<OverrideSpecifier> parseOverrideSpecifier();
StateMutability parseStateMutability();
FunctionHeaderParserResult parseFunctionHeader(bool _isStateVariable);
ASTPointer<ASTNode> parseFunctionDefinition();
ASTPointer<ASTNode> parseFunctionDefinition(bool _freeFunction = false);
ASTPointer<StructDefinition> parseStructDefinition();
ASTPointer<EnumDefinition> parseEnumDefinition();
ASTPointer<EnumValue> parseEnumValue();

View File

@ -22,6 +22,7 @@ set(sources
LazyInit.h
picosha2.h
Result.h
SetOnce.h
StringUtils.cpp
StringUtils.h
SwarmHash.cpp

View File

@ -78,6 +78,7 @@ template <class U, class... T> std::set<T...>& operator+=(std::set<T...>& _a, U&
_a.insert(std::move(x));
return _a;
}
/// Concatenate two vectors of elements.
template <class T>
inline std::vector<T> operator+(std::vector<T> const& _a, std::vector<T> const& _b)
@ -86,6 +87,7 @@ inline std::vector<T> operator+(std::vector<T> const& _a, std::vector<T> const&
ret += _b;
return ret;
}
/// Concatenate two vectors of elements, moving them.
template <class T>
inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b)
@ -97,6 +99,7 @@ inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b)
ret += std::move(_b);
return ret;
}
/// Concatenate something to a sets of elements.
template <class T, class U>
inline std::set<T> operator+(std::set<T> const& _a, U&& _b)
@ -105,6 +108,7 @@ inline std::set<T> operator+(std::set<T> const& _a, U&& _b)
ret += std::forward<U>(_b);
return ret;
}
/// Concatenate something to a sets of elements, move variant.
template <class T, class U>
inline std::set<T> operator+(std::set<T>&& _a, U&& _b)

86
libsolutil/SetOnce.h Normal file
View File

@ -0,0 +1,86 @@
/*
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/>.
*/
#pragma once
#include <libsolutil/Assertions.h>
#include <libsolutil/Exceptions.h>
#include <memory>
#include <optional>
#include <utility>
namespace solidity::util
{
DEV_SIMPLE_EXCEPTION(BadSetOnceReassignment);
DEV_SIMPLE_EXCEPTION(BadSetOnceAccess);
/// A class that stores a value that can only be set once
/// \tparam T the type of the stored value
template<typename T>
class SetOnce
{
public:
/// Initializes the class to have no stored value.
SetOnce() = default;
// Not copiable
SetOnce(SetOnce const&) = delete;
SetOnce(SetOnce&&) = delete;
// Not movable
SetOnce& operator=(SetOnce const&) = delete;
SetOnce& operator=(SetOnce&&) = delete;
/// @brief Sets the stored value to \p _newValue
/// @throws BadSetOnceReassignment when the stored value has already been set
/// @return `*this`
constexpr SetOnce& operator=(T _newValue) &
{
assertThrow(
!m_value.has_value(),
BadSetOnceReassignment,
"Attempt to reassign to a SetOnce that already has a value."
);
m_value.emplace(std::move(_newValue));
return *this;
}
/// @return A reference to the stored value. The returned reference has the same lifetime as `*this`.
/// @throws BadSetOnceAccess when the stored value has not yet been set
T const& operator*() const
{
assertThrow(
m_value.has_value(),
BadSetOnceAccess,
"Attempt to access the value of a SetOnce that does not have a value."
);
return m_value.value();
}
/// @return A reference to the stored value. The referent of the returned pointer has the same lifetime as `*this`.
/// @throws BadSetOnceAccess when the stored value has not yet been set
T const* operator->() const { return std::addressof(**this); }
private:
std::optional<T> m_value = std::nullopt;
};
}

View File

@ -116,26 +116,24 @@ Statement Parser::parseStatement()
{
Statement stmt{createWithLocation<Break>()};
checkBreakContinuePosition("break");
m_scanner->next();
advance();
return stmt;
}
case Token::Continue:
{
Statement stmt{createWithLocation<Continue>()};
checkBreakContinuePosition("continue");
m_scanner->next();
advance();
return stmt;
}
case Token::Leave:
{
Statement stmt{createWithLocation<Leave>()};
if (!m_insideFunction)
m_errorReporter.syntaxError(8149_error, currentLocation(), "Keyword \"leave\" can only be used inside a function.");
advance();
return stmt;
}
case Token::Identifier:
if (currentLiteral() == "leave")
{
Statement stmt{createWithLocation<Leave>()};
if (!m_insideFunction)
m_errorReporter.syntaxError(8149_error, currentLocation(), "Keyword \"leave\" can only be used inside a function.");
m_scanner->next();
return stmt;
}
break;
default:
break;
}
@ -284,12 +282,6 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
switch (currentToken())
{
case Token::Identifier:
case Token::Return:
case Token::Byte:
case Token::Bool:
case Token::Address:
case Token::Var:
case Token::In:
{
YulString literal{currentLiteral()};
if (m_dialect.builtin(literal))
@ -345,6 +337,9 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
ret = std::move(literal);
break;
}
case Token::HexStringLiteral:
fatalParserError(3772_error, "Hex literals are not valid in this context.");
break;
default:
fatalParserError(1856_error, "Literal or identifier expected.");
}
@ -401,10 +396,9 @@ FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::Comma);
}
expectToken(Token::RParen);
if (currentToken() == Token::Sub)
if (currentToken() == Token::RightArrow)
{
expectToken(Token::Sub);
expectToken(Token::GreaterThan);
expectToken(Token::RightArrow);
while (true)
{
funDef.returnVariables.emplace_back(parseTypedName());
@ -473,24 +467,10 @@ TypedName Parser::parseTypedName()
YulString Parser::expectAsmIdentifier()
{
YulString name{currentLiteral()};
switch (currentToken())
{
case Token::Return:
case Token::Byte:
case Token::Address:
case Token::Bool:
case Token::Identifier:
case Token::Var:
case Token::In:
break;
default:
expectToken(Token::Identifier);
break;
}
if (m_dialect.builtin(name))
if (currentToken() == Token::Identifier && m_dialect.builtin(name))
fatalParserError(5568_error, "Cannot use builtin function name \"" + name.str() + "\" as identifier name.");
advance();
// NOTE: We keep the expectation here to ensure the correct source location for the error above.
expectToken(Token::Identifier);
return name;
}

View File

@ -18,6 +18,7 @@
#pragma once
#include <algorithm>
#include <set>
namespace solidity::yul
@ -30,6 +31,20 @@ namespace solidity::yul
*/
struct SideEffects
{
/// Corresponds to the effect that a Yul-builtin has on a generic data location (storage, memory
/// and other blockchain state).
enum Effect
{
None,
Read,
Write
};
friend Effect operator+(Effect const& _a, Effect const& _b)
{
return static_cast<Effect>(std::max(static_cast<int>(_a), static_cast<int>(_b)));
}
/// If true, expressions in this code can be freely moved and copied without altering the
/// semantics.
/// At statement level, it means that functions containing this code can be
@ -38,22 +53,34 @@ struct SideEffects
/// This means it cannot depend on storage or memory, cannot have any side-effects,
/// but it can depend on state that is constant across an EVM-call.
bool movable = true;
/// If true, the expressions in this code can be moved or copied (together with their arguments)
/// across control flow branches and instructions as long as these instructions' 'effects' do
/// not influence the 'effects' of the aforementioned expressions.
bool movableApartFromEffects = true;
/// If true, the code can be removed without changing the semantics.
bool sideEffectFree = true;
bool canBeRemoved = true;
/// If true, the code can be removed without changing the semantics as long as
/// the whole program does not contain the msize instruction.
bool sideEffectFreeIfNoMSize = true;
/// If false, storage is guaranteed to be unchanged by the code under all
/// circumstances.
bool invalidatesStorage = false;
/// If false, memory is guaranteed to be unchanged by the code under all
/// circumstances.
bool invalidatesMemory = false;
bool canBeRemovedIfNoMSize = true;
/// If false, the code calls a for-loop or a recursive function, and therefore potentially loops
/// infinitely. All builtins are set to true by default, even `invalid()`.
bool cannotLoop = true;
/// Can write, read or have no effect on the blockchain state, when the value of `otherState` is
/// `Write`, `Read` or `None` respectively.
Effect otherState = None;
/// Can write, read or have no effect on storage, when the value of `storage` is `Write`, `Read`
/// or `None` respectively. When the value is `Write`, the expression can invalidate storage,
/// potentially indirectly through external calls.
Effect storage = None;
/// Can write, read or have no effect on memory, when the value of `memory` is `Write`, `Read`
/// or `None` respectively. Note that, when the value is `Read`, the expression can have an
/// effect on `msize()`.
Effect memory = None;
/// @returns the worst-case side effects.
static SideEffects worst()
{
return SideEffects{false, false, false, true, true};
return SideEffects{false, false, false, false, false, Write, Write, Write};
}
/// @returns the combined side effects of two pieces of code.
@ -61,10 +88,13 @@ struct SideEffects
{
return SideEffects{
movable && _other.movable,
sideEffectFree && _other.sideEffectFree,
sideEffectFreeIfNoMSize && _other.sideEffectFreeIfNoMSize,
invalidatesStorage || _other.invalidatesStorage,
invalidatesMemory || _other.invalidatesMemory
movableApartFromEffects && _other.movableApartFromEffects,
canBeRemoved && _other.canBeRemoved,
canBeRemovedIfNoMSize && _other.canBeRemovedIfNoMSize,
cannotLoop && _other.cannotLoop,
otherState + _other.otherState,
storage + _other.storage,
memory + _other.memory
};
}
@ -79,10 +109,13 @@ struct SideEffects
{
return
movable == _other.movable &&
sideEffectFree == _other.sideEffectFree &&
sideEffectFreeIfNoMSize == _other.sideEffectFreeIfNoMSize &&
invalidatesStorage == _other.invalidatesStorage &&
invalidatesMemory == _other.invalidatesMemory;
movableApartFromEffects == _other.movableApartFromEffects &&
canBeRemoved == _other.canBeRemoved &&
canBeRemovedIfNoMSize == _other.canBeRemovedIfNoMSize &&
cannotLoop == _other.cannotLoop &&
otherState == _other.otherState &&
storage == _other.storage &&
memory == _other.memory;
}
};

View File

@ -190,7 +190,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
"datacopy",
3,
0,
SideEffects{false, false, false, false, true},
SideEffects{false, true, false, false, true, SideEffects::None, SideEffects::None, SideEffects::Write},
{},
[](
FunctionCall const& _call,
@ -206,7 +206,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
"setimmutable",
2,
0,
SideEffects{false, false, false, false, true},
SideEffects{false, false, false, false, true, SideEffects::None, SideEffects::None, SideEffects::Write},
{LiteralKind::String, std::nullopt},
[](
FunctionCall const& _call,
@ -281,12 +281,20 @@ EVMDialect const& EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion _
SideEffects EVMDialect::sideEffectsOfInstruction(evmasm::Instruction _instruction)
{
auto translate = [](evmasm::SemanticInformation::Effect _e) -> SideEffects::Effect
{
return static_cast<SideEffects::Effect>(_e);
};
return SideEffects{
evmasm::SemanticInformation::movable(_instruction),
evmasm::SemanticInformation::sideEffectFree(_instruction),
evmasm::SemanticInformation::sideEffectFreeIfNoMSize(_instruction),
evmasm::SemanticInformation::invalidatesStorage(_instruction),
evmasm::SemanticInformation::invalidatesMemory(_instruction)
evmasm::SemanticInformation::movableApartFromEffects(_instruction),
evmasm::SemanticInformation::canBeRemoved(_instruction),
evmasm::SemanticInformation::canBeRemovedIfNoMSize(_instruction),
true, // cannotLoop
translate(evmasm::SemanticInformation::otherState(_instruction)),
translate(evmasm::SemanticInformation::storage(_instruction)),
translate(evmasm::SemanticInformation::memory(_instruction)),
};
}

View File

@ -90,7 +90,7 @@ wasm::Expression WasmCodeTransform::generateMultiAssignment(
return { std::move(block) };
}
wasm::Expression WasmCodeTransform::operator()(VariableDeclaration const& _varDecl)
wasm::Expression WasmCodeTransform::operator()(yul::VariableDeclaration const& _varDecl)
{
vector<string> variableNames;
for (auto const& var: _varDecl.variables)
@ -105,7 +105,7 @@ wasm::Expression WasmCodeTransform::operator()(VariableDeclaration const& _varDe
return wasm::BuiltinCall{"nop", {}};
}
wasm::Expression WasmCodeTransform::operator()(Assignment const& _assignment)
wasm::Expression WasmCodeTransform::operator()(yul::Assignment const& _assignment)
{
vector<string> variableNames;
for (auto const& var: _assignment.variableNames)
@ -113,12 +113,12 @@ wasm::Expression WasmCodeTransform::operator()(Assignment const& _assignment)
return generateMultiAssignment(move(variableNames), visit(*_assignment.value));
}
wasm::Expression WasmCodeTransform::operator()(ExpressionStatement const& _statement)
wasm::Expression WasmCodeTransform::operator()(yul::ExpressionStatement const& _statement)
{
return visitReturnByValue(_statement.expression);
}
wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
wasm::Expression WasmCodeTransform::operator()(yul::FunctionCall const& _call)
{
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
{
@ -164,17 +164,17 @@ wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)};
}
wasm::Expression WasmCodeTransform::operator()(Identifier const& _identifier)
wasm::Expression WasmCodeTransform::operator()(yul::Identifier const& _identifier)
{
return wasm::LocalVariable{_identifier.name.str()};
}
wasm::Expression WasmCodeTransform::operator()(Literal const& _literal)
wasm::Expression WasmCodeTransform::operator()(yul::Literal const& _literal)
{
return makeLiteral(translatedType(_literal.type), valueOfLiteral(_literal));
}
wasm::Expression WasmCodeTransform::operator()(If const& _if)
wasm::Expression WasmCodeTransform::operator()(yul::If const& _if)
{
yul::Type conditionType = m_typeInfo.typeOf(*_if.condition);
@ -196,7 +196,7 @@ wasm::Expression WasmCodeTransform::operator()(If const& _if)
return wasm::If{make_unique<wasm::Expression>(move(condition)), visit(_if.body.statements), {}};
}
wasm::Expression WasmCodeTransform::operator()(Switch const& _switch)
wasm::Expression WasmCodeTransform::operator()(yul::Switch const& _switch)
{
yul::Type expressionType = m_typeInfo.typeOf(*_switch.expression);
YulString eq_instruction = YulString(expressionType.str() + ".eq");
@ -240,13 +240,13 @@ wasm::Expression WasmCodeTransform::operator()(Switch const& _switch)
return { std::move(block) };
}
wasm::Expression WasmCodeTransform::operator()(FunctionDefinition const&)
wasm::Expression WasmCodeTransform::operator()(yul::FunctionDefinition const&)
{
yulAssert(false, "Should not have visited here.");
return {};
}
wasm::Expression WasmCodeTransform::operator()(ForLoop const& _for)
wasm::Expression WasmCodeTransform::operator()(yul::ForLoop const& _for)
{
string breakLabel = newLabel();
string continueLabel = newLabel();
@ -273,23 +273,25 @@ wasm::Expression WasmCodeTransform::operator()(ForLoop const& _for)
return wasm::Block{breakLabel, move(statements)};
}
wasm::Expression WasmCodeTransform::operator()(Break const&)
wasm::Expression WasmCodeTransform::operator()(yul::Break const&)
{
yulAssert(m_breakContinueLabelNames.size() > 0, "");
return wasm::Branch{wasm::Label{m_breakContinueLabelNames.top().first}};
}
wasm::Expression WasmCodeTransform::operator()(Continue const&)
wasm::Expression WasmCodeTransform::operator()(yul::Continue const&)
{
yulAssert(m_breakContinueLabelNames.size() > 0, "");
return wasm::Branch{wasm::Label{m_breakContinueLabelNames.top().second}};
}
wasm::Expression WasmCodeTransform::operator()(Leave const&)
wasm::Expression WasmCodeTransform::operator()(yul::Leave const&)
{
yulAssert(!m_functionBodyLabel.empty(), "");
return wasm::Branch{wasm::Label{m_functionBodyLabel}};
}
wasm::Expression WasmCodeTransform::operator()(Block const& _block)
wasm::Expression WasmCodeTransform::operator()(yul::Block const& _block)
{
return wasm::Block{{}, visit(_block.statements)};
}

View File

@ -86,27 +86,34 @@ WasmDialect::WasmDialect()
addFunction("i64.extend_i32_u", {i32}, {i64});
addFunction("i32.store", {i32, i32}, {}, false);
m_functions["i32.store"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["i32.store"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["i32.store"_yulstring].sideEffects.otherState = SideEffects::None;
addFunction("i64.store", {i32, i64}, {}, false);
m_functions["i64.store"_yulstring].sideEffects.invalidatesStorage = false;
// TODO: add i32.store16, i64.store8, i64.store16, i64.store32
m_functions["i64.store"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["i64.store"_yulstring].sideEffects.otherState = SideEffects::None;
addFunction("i32.store8", {i32, i32}, {}, false);
m_functions["i32.store8"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["i32.store8"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["i32.store8"_yulstring].sideEffects.otherState = SideEffects::None;
addFunction("i64.store8", {i32, i64}, {}, false);
m_functions["i64.store8"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["i64.store8"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["i64.store8"_yulstring].sideEffects.otherState = SideEffects::None;
addFunction("i32.load", {i32}, {i32}, false);
m_functions["i32.load"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["i32.load"_yulstring].sideEffects.invalidatesMemory = false;
m_functions["i32.load"_yulstring].sideEffects.sideEffectFree = true;
m_functions["i32.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
m_functions["i32.load"_yulstring].sideEffects.canBeRemoved = true;
m_functions["i32.load"_yulstring].sideEffects.canBeRemovedIfNoMSize = true;
m_functions["i32.load"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["i32.load"_yulstring].sideEffects.memory = SideEffects::Read;
m_functions["i32.load"_yulstring].sideEffects.otherState = SideEffects::None;
addFunction("i64.load", {i32}, {i64}, false);
m_functions["i64.load"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["i64.load"_yulstring].sideEffects.invalidatesMemory = false;
m_functions["i64.load"_yulstring].sideEffects.sideEffectFree = true;
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
// TODO: add i32.load8, i32.load16, i64.load8, i64.load16, i64.load32
m_functions["i64.load"_yulstring].sideEffects.canBeRemoved = true;
m_functions["i64.load"_yulstring].sideEffects.canBeRemovedIfNoMSize = true;
m_functions["i64.load"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["i64.load"_yulstring].sideEffects.memory = SideEffects::Read;
m_functions["i64.load"_yulstring].sideEffects.otherState = SideEffects::None;
// Drop is actually overloaded for all types, but Yul does not support that.
// Because of that, we introduce "i32.drop" and "i64.drop".
@ -115,8 +122,9 @@ WasmDialect::WasmDialect()
addFunction("nop", {}, {});
addFunction("unreachable", {}, {}, false);
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
m_functions["unreachable"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None;
m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None;
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
@ -219,10 +227,24 @@ void WasmDialect::addEthereumExternals()
f.returns.emplace_back(YulString(p));
// TODO some of them are side effect free.
f.sideEffects = SideEffects::worst();
f.sideEffects.cannotLoop = true;
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminates;
f.controlFlowSideEffects = ext.controlFlowSideEffects;
f.isMSize = false;
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
f.literalArguments.clear();
static set<string> const writesToStorage{
"storageStore",
"call",
"callcode",
"callDelegate",
"create"
};
static set<string> const readsStorage{"storageLoad", "callStatic"};
if (readsStorage.count(ext.name))
f.sideEffects.storage = SideEffects::Read;
else if (!writesToStorage.count(ext.name))
f.sideEffects.storage = SideEffects::None;
}
}
@ -241,6 +263,9 @@ void WasmDialect::addFunction(
yulAssert(_returns.size() <= 1, "The Wasm 1.0 specification only allows up to 1 return value.");
f.returns = std::move(_returns);
f.sideEffects = _movable ? SideEffects{} : SideEffects::worst();
f.sideEffects.cannotLoop = true;
// TODO This should be improved when LoopInvariantCodeMotion gets specialized for WASM
f.sideEffects.movableApartFromEffects = _movable;
f.isMSize = false;
f.literalArguments = std::move(_literalArguments);
}

View File

@ -80,10 +80,20 @@ void FullInliner::run()
// TODO it might be good to determine a visiting order:
// first handle functions that are called from many places.
for (auto const& fun: m_functions)
// Note that the order of inlining can result in very different code.
// Since AST IDs and thus function names depend on whether or not a contract
// is compiled together with other source files, a change in AST IDs
// should have as little an impact as possible. This is the case
// if we handle inlining in source (and thus, for the IR generator,
// function name) order.
for (auto& statement: m_ast.statements)
{
handleBlock(fun.second->name, fun.second->body);
updateCodeSize(*fun.second);
if (!holds_alternative<FunctionDefinition>(statement))
continue;
FunctionDefinition& fun = std::get<FunctionDefinition>(statement);
handleBlock(fun.name, fun.body);
updateCodeSize(fun);
}
}

View File

@ -35,9 +35,9 @@ void LoopInvariantCodeMotion::run(OptimiserStepContext& _context, Block& _ast)
{
map<YulString, SideEffects> functionSideEffects =
SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast));
bool containsMSize = MSizeFinder::containsMSize(_context.dialect, _ast);
set<YulString> ssaVars = SSAValueTracker::ssaVariables(_ast);
LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects}(_ast);
LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects, containsMSize}(_ast);
}
void LoopInvariantCodeMotion::operator()(Block& _block)
@ -57,7 +57,8 @@ void LoopInvariantCodeMotion::operator()(Block& _block)
bool LoopInvariantCodeMotion::canBePromoted(
VariableDeclaration const& _varDecl,
set<YulString> const& _varsDefinedInCurrentScope
set<YulString> const& _varsDefinedInCurrentScope,
SideEffects const& _forLoopSideEffects
) const
{
// A declaration can be promoted iff
@ -73,7 +74,8 @@ bool LoopInvariantCodeMotion::canBePromoted(
for (auto const& ref: ReferencesCounter::countReferences(*_varDecl.value, ReferencesCounter::OnlyVariables))
if (_varsDefinedInCurrentScope.count(ref.first) || !m_ssaVariables.count(ref.first))
return false;
if (!SideEffectsCollector{m_dialect, *_varDecl.value, &m_functionSideEffects}.movable())
SideEffectsCollector sideEffects{m_dialect, *_varDecl.value, &m_functionSideEffects};
if (!sideEffects.movableRelativeTo(_forLoopSideEffects, m_containsMSize))
return false;
}
return true;
@ -82,6 +84,10 @@ bool LoopInvariantCodeMotion::canBePromoted(
optional<vector<Statement>> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for)
{
assertThrow(_for.pre.statements.empty(), OptimizerException, "");
auto forLoopSideEffects =
SideEffectsCollector{m_dialect, _for, &m_functionSideEffects}.sideEffects();
vector<Statement> replacement;
for (Block* block: {&_for.post, &_for.body})
{
@ -93,7 +99,7 @@ optional<vector<Statement>> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for)
if (holds_alternative<VariableDeclaration>(_s))
{
VariableDeclaration const& varDecl = std::get<VariableDeclaration>(_s);
if (canBePromoted(varDecl, varsDefinedInScope))
if (canBePromoted(varDecl, varsDefinedInScope, forLoopSideEffects))
{
replacement.emplace_back(std::move(_s));
// Do not add the variables declared here to varsDefinedInScope because we are moving them.

View File

@ -49,17 +49,24 @@ private:
explicit LoopInvariantCodeMotion(
Dialect const& _dialect,
std::set<YulString> const& _ssaVariables,
std::map<YulString, SideEffects> const& _functionSideEffects
std::map<YulString, SideEffects> const& _functionSideEffects,
bool _containsMSize
):
m_containsMSize(_containsMSize),
m_dialect(_dialect),
m_ssaVariables(_ssaVariables),
m_functionSideEffects(_functionSideEffects)
{ }
/// @returns true if the given variable declaration can be moved to in front of the loop.
bool canBePromoted(VariableDeclaration const& _varDecl, std::set<YulString> const& _varsDefinedInCurrentScope) const;
bool canBePromoted(
VariableDeclaration const& _varDecl,
std::set<YulString> const& _varsDefinedInCurrentScope,
SideEffects const& _forLoopSideEffects
) const;
std::optional<std::vector<Statement>> rewriteLoop(ForLoop& _for);
bool m_containsMSize = true;
Dialect const& m_dialect;
std::set<YulString> const& m_ssaVariables;
std::map<YulString, SideEffects> const& m_functionSideEffects;

View File

@ -22,8 +22,10 @@
#include <libyul/optimiser/NameDispenser.h>
#include <libyul/optimiser/NameCollector.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/AsmData.h>
#include <libyul/Dialect.h>
#include <libyul/YulString.h>
#include <libsolutil/CommonData.h>
@ -57,7 +59,5 @@ YulString NameDispenser::newName(YulString _nameHint)
bool NameDispenser::illegalName(YulString _name)
{
if (_name.empty() || m_usedNames.count(_name) || m_dialect.builtin(_name))
return true;
return false;
return isRestrictedIdentifier(m_dialect, _name) || m_usedNames.count(_name);
}

View File

@ -21,15 +21,19 @@
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/Dialect.h>
#include <libyul/AsmData.h>
#include <liblangutil/Token.h>
#include <libsolutil/CommonData.h>
#include <boost/range/algorithm_ext/erase.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::langutil;
using namespace solidity::util;
using namespace solidity::yul;
void yul::removeEmptyBlocks(Block& _block)
{
@ -38,3 +42,8 @@ void yul::removeEmptyBlocks(Block& _block)
};
boost::range::remove_erase_if(_block.statements, isEmptyBlock);
}
bool yul::isRestrictedIdentifier(Dialect const& _dialect, YulString const& _identifier)
{
return _identifier.empty() || TokenTraits::isYulKeyword(_identifier.str()) || _dialect.builtin(_identifier);
}

View File

@ -23,6 +23,8 @@
#include <libsolutil/Common.h>
#include <libyul/AsmDataForward.h>
#include <libyul/Dialect.h>
#include <libyul/YulString.h>
namespace solidity::yul
{
@ -30,4 +32,8 @@ namespace solidity::yul
/// Removes statements that are just empty blocks (non-recursive).
void removeEmptyBlocks(Block& _block);
/// Returns true if a given literal can not be used as an identifier.
/// This includes Yul keywords and builtins of the given dialect.
bool isRestrictedIdentifier(Dialect const& _dialect, YulString const& _identifier);
}

View File

@ -62,6 +62,16 @@ SideEffectsCollector::SideEffectsCollector(
operator()(_ast);
}
SideEffectsCollector::SideEffectsCollector(
Dialect const& _dialect,
ForLoop const& _ast,
map<YulString, SideEffects> const* _functionSideEffects
):
SideEffectsCollector(_dialect, _functionSideEffects)
{
operator()(_ast);
}
void SideEffectsCollector::operator()(FunctionCall const& _functionCall)
{
ASTWalker::operator()(_functionCall);
@ -106,8 +116,9 @@ map<YulString, SideEffects> SideEffectsPropagator::sideEffects(
for (auto const& function: _directCallGraph.functionsWithLoops + _directCallGraph.recursiveFunctions())
{
ret[function].movable = false;
ret[function].sideEffectFree = false;
ret[function].sideEffectFreeIfNoMSize = false;
ret[function].canBeRemoved = false;
ret[function].canBeRemovedIfNoMSize = false;
ret[function].cannotLoop = false;
}
for (auto const& call: _directCallGraph.functionCalls)

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