mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
commit
ff0ec61e1e
@ -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
|
||||
|
20
Changelog.md
20
Changelog.md
@ -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)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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(
|
||||
|
@ -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)
|
@ -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)
|
487
docs/Solidity.g4
487
docs/Solidity.g4
@ -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) ;
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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>`.
|
||||
|
||||
::
|
||||
|
||||
|
@ -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>`_
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
511
docs/grammar/Solidity.g4
Normal 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;
|
334
docs/grammar/SolidityLexer.g4
Normal file
334
docs/grammar/SolidityLexer.g4
Normal 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) ;
|
@ -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>`_
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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``.
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -1,2 +1,3 @@
|
||||
sphinx_rtd_theme>=0.3.1
|
||||
pygments-lexer-solidity>=0.5.1
|
||||
sphinx-a4doc>=1.2.1
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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::
|
||||
|
||||
|
@ -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": {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
|
@ -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 + ".");
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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, "");
|
||||
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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")),
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -22,6 +22,7 @@ set(sources
|
||||
LazyInit.h
|
||||
picosha2.h
|
||||
Result.h
|
||||
SetOnce.h
|
||||
StringUtils.cpp
|
||||
StringUtils.h
|
||||
SwarmHash.cpp
|
||||
|
@ -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
86
libsolutil/SetOnce.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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)};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user