mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9729 from ethereum/develop
Merge develop into release for 0.7.1
This commit is contained in:
commit
f4a555bedc
@ -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"
|
||||
@ -410,6 +410,7 @@ jobs:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_OPTIONS: -DSANITIZE=address
|
||||
MAKEFLAGS: -j 3
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
@ -433,6 +434,7 @@ jobs:
|
||||
<<: *build_ubuntu2004
|
||||
environment:
|
||||
FORCE_RELEASE: ON
|
||||
MAKEFLAGS: -j 10
|
||||
|
||||
b_ubu18: &build_ubuntu1804
|
||||
docker:
|
||||
@ -440,6 +442,7 @@ jobs:
|
||||
environment:
|
||||
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2
|
||||
CMAKE_BUILD_TYPE: RelWithDebugInfo
|
||||
MAKEFLAGS: -j 3
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
@ -451,6 +454,7 @@ jobs:
|
||||
environment:
|
||||
COVERAGE: ON
|
||||
CMAKE_BUILD_TYPE: Debug
|
||||
MAKEFLAGS: -j 10
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
@ -483,7 +487,8 @@ 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
|
||||
- run: *run_build
|
||||
@ -495,7 +500,7 @@ jobs:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
TERM: xterm
|
||||
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
|
||||
MAKEFLAGS: -j 3
|
||||
steps:
|
||||
- checkout
|
||||
- run: *setup_prerelease_commit_hash
|
||||
@ -524,6 +529,7 @@ jobs:
|
||||
- image: archlinux/base
|
||||
environment:
|
||||
TERM: xterm
|
||||
MAKEFLAGS: -j 3
|
||||
steps:
|
||||
- run:
|
||||
name: Install build dependencies
|
||||
@ -540,6 +546,7 @@ jobs:
|
||||
environment:
|
||||
TERM: xterm
|
||||
CMAKE_BUILD_TYPE: Release
|
||||
MAKEFLAGS: -j 5
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
@ -625,6 +632,7 @@ jobs:
|
||||
<<: *build_ubuntu2004
|
||||
environment:
|
||||
CMAKE_OPTIONS: -DSANITIZE=address
|
||||
MAKEFLAGS: -j 10
|
||||
CMAKE_BUILD_TYPE: Release
|
||||
steps:
|
||||
- checkout
|
||||
@ -884,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
|
||||
@ -917,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
|
||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.7.0")
|
||||
set(PROJECT_VERSION "0.7.1")
|
||||
# OSX target needed in order to support std::visit
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||
|
36
Changelog.md
36
Changelog.md
@ -1,3 +1,39 @@
|
||||
### 0.7.1 (2020-09-02)
|
||||
|
||||
Language Features:
|
||||
* Allow function definitions outside of contracts, behaving much like internal library functions.
|
||||
* Code generator: Implementing copying structs from calldata to storage.
|
||||
|
||||
Compiler Features:
|
||||
* SMTChecker: Add underflow and overflow as verification conditions in the CHC engine.
|
||||
* SMTChecker: Support bitwise or, xor and not operators.
|
||||
* SMTChecker: Support conditional operator.
|
||||
* Standard JSON Interface: Do not run EVM bytecode code generation, if only Yul IR or EWasm output is requested.
|
||||
* Yul Optimizer: LoopInvariantCodeMotion can move reading operations outside for-loops as long as the affected area is not modified inside the loop.
|
||||
* Yul: Report error when using non-string literals for ``datasize()``, ``dataoffset()``, ``linkersymbol()``, ``loadimmutable()``, ``setimmutable()``.
|
||||
|
||||
Bugfixes:
|
||||
* AST: Remove ``null`` member values also when the compiler is used in standard-json-mode.
|
||||
* General: Allow `type(Contract).name` for abstract contracts and interfaces.
|
||||
* Immutables: Disallow assigning immutables more than once during their declaration.
|
||||
* 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.
|
||||
* References Resolver: Fix internal bug when using constructor for library.
|
||||
* 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``.
|
||||
* Type Checker: Disallow ``using for`` directive inside interfaces.
|
||||
* Type Checker: Disallow signed literals as exponent in exponentiation operator.
|
||||
* Type Checker: Disallow structs containing nested mapping in memory as parameters for library functions.
|
||||
* Yul Optimizer: Ensure that Yul keywords are not mistakenly used by the NameDispenser and VarNameCleaners. The bug would manifest as uncompilable code.
|
||||
* Yul Optimizer: Make function inlining order more resilient to whether or not unrelated source files are present.
|
||||
|
||||
|
||||
### 0.7.0 (2020-07-28)
|
||||
|
||||
Breaking changes:
|
||||
|
@ -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,10 @@
|
||||
# Require C++17.
|
||||
if (NOT DEFINED CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 17) # This requires at least CMake 3.8 to accept this C++17 flag.
|
||||
endif ()
|
||||
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.
|
||||
|
@ -1185,5 +1185,9 @@
|
||||
"0.7.0": {
|
||||
"bugs": [],
|
||||
"released": "2020-07-28"
|
||||
},
|
||||
"0.7.1": {
|
||||
"bugs": [],
|
||||
"released": "2020-09-02"
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
||||
|
@ -229,7 +229,9 @@ The following identifiers are used for the types in the signatures:
|
||||
- Non-storage array types follow the same convention as in the contract ABI, i.e. ``<type>[]`` for dynamic arrays and
|
||||
``<type>[M]`` for fixed-size arrays of ``M`` elements.
|
||||
- Non-storage structs are referred to by their fully qualified name, i.e. ``C.S`` for ``contract C { struct S { ... } }``.
|
||||
- Storage pointer types use the type identifier of their corresponding non-storage type, but append a single space
|
||||
- Storage pointer mappings use ``mapping(<keyType> => <valueType>) storage`` where ``<keyType>`` and ``<valueType>`` are
|
||||
the identifiers for the key and value types of the mapping, respectively.
|
||||
- Other storage pointer types use the type identifier of their corresponding non-storage type, but append a single space
|
||||
followed by ``storage`` to it.
|
||||
|
||||
The argument encoding is the same as for the regular contract ABI, except for storage pointers, which are encoded as a
|
||||
|
@ -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>`_
|
||||
|
@ -55,7 +55,7 @@ Please refer to the solc-js repository for instructions.
|
||||
|
||||
The commandline executable is named ``solcjs``.
|
||||
|
||||
The comandline options of ``solcjs`` are not compatible with ``solc`` and tools (such as ``geth``)
|
||||
The commandline options of ``solcjs`` are not compatible with ``solc`` and tools (such as ``geth``)
|
||||
expecting the behaviour of ``solc`` will not work with ``solcjs``.
|
||||
|
||||
Docker
|
||||
@ -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
|
||||
|
@ -37,7 +37,7 @@ GPL version 3.0. Machine-readable license specifiers are important
|
||||
in a setting where publishing the source code is the default.
|
||||
|
||||
The next line specifies that the source code is written for
|
||||
Solidity version 0.4.16, or a newer version of the language up to, but not including version 0.7.0.
|
||||
Solidity version 0.4.16, or a newer version of the language up to, but not including version 0.8.0.
|
||||
This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently.
|
||||
:ref:`Pragmas<pragma>` are common instructions for compilers about how to treat the
|
||||
source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_).
|
||||
@ -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
|
||||
|
||||
@ -14,7 +14,7 @@ SPDX License Identifier
|
||||
|
||||
Trust in smart contract can be better established if their source code
|
||||
is available. Since making source code available always touches on legal problems
|
||||
with regards to copyright, the Solidity compiler encouranges the use
|
||||
with regards to copyright, the Solidity compiler encourages the use
|
||||
of machine-readable `SPDX license identifiers <https://spdx.org>`_.
|
||||
Every source file should start with a comment indicating its license:
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -46,7 +46,7 @@ The following example shows a contract and a function using all available tags.
|
||||
public. You are welcome to use similar comments for your internal and
|
||||
private functions, but those will not be parsed.
|
||||
|
||||
.. code:: solidity
|
||||
.. code:: Solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.10 <0.8.0;
|
||||
@ -126,7 +126,7 @@ JSON output, for example the end-user client software, may present this to the e
|
||||
|
||||
For example, some client software will render:
|
||||
|
||||
.. code:: solidity
|
||||
.. code:: Solidity
|
||||
|
||||
/// @notice This function will multiply `a` by 7
|
||||
|
||||
@ -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": {
|
||||
@ -637,7 +637,7 @@ Example
|
||||
|
||||
Assume that you have the following contract in ``Source.sol``:
|
||||
|
||||
.. code-block:: solidity
|
||||
.. code-block:: Solidity
|
||||
|
||||
pragma solidity >=0.6.0 <0.6.4;
|
||||
// This will not compile after 0.7.0
|
||||
@ -691,7 +691,7 @@ It is recommended to explicitly specify the upgrade modules by using ``--modules
|
||||
The command above applies all changes as shown below. Please review them carefully (the pragmas will
|
||||
have to be updated manually.)
|
||||
|
||||
.. code-block:: solidity
|
||||
.. code-block:: Solidity
|
||||
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -581,9 +581,18 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
|
||||
rules.push_back({
|
||||
Builtins::BYTE(A, Builtins::SHR(B, X)),
|
||||
[=]() -> Pattern { return A.d() < B.d() / 8 ? Word(0) : Builtins::BYTE(A.d() - B.d() / 8, X); },
|
||||
[=]() -> Pattern { return Word(0); },
|
||||
true,
|
||||
[=] { return A.d() < B.d() / 8; }
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
Builtins::BYTE(A, Builtins::SHR(B, X)),
|
||||
[=]() -> Pattern { return Builtins::BYTE(A.d() - B.d() / 8, X); },
|
||||
false,
|
||||
[=] { return B.d() % 8 == 0 && A.d() < Pattern::WordSize / 8 && B.d() <= Pattern::WordSize; }
|
||||
[=] {
|
||||
return B.d() % 8 == 0 && A.d() < Pattern::WordSize / 8 && B.d() <= Pattern::WordSize && A.d() >= B.d() / 8;
|
||||
}
|
||||
});
|
||||
|
||||
return rules;
|
||||
|
@ -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 + ".");
|
||||
|
@ -147,7 +147,7 @@ void Scanner::reset(shared_ptr<CharStream> _source)
|
||||
void Scanner::reset()
|
||||
{
|
||||
m_source->reset();
|
||||
m_supportPeriodInIdentifier = false;
|
||||
m_kind = ScannerKind::Solidity;
|
||||
m_char = m_source->get();
|
||||
skipWhitespace();
|
||||
next();
|
||||
@ -163,12 +163,6 @@ void Scanner::setPosition(size_t _offset)
|
||||
next();
|
||||
}
|
||||
|
||||
void Scanner::supportPeriodInIdentifier(bool _value)
|
||||
{
|
||||
m_supportPeriodInIdentifier = _value;
|
||||
rescan();
|
||||
}
|
||||
|
||||
bool Scanner::scanHexByte(char& o_scannedByte)
|
||||
{
|
||||
char x = 0;
|
||||
@ -546,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;
|
||||
@ -569,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;
|
||||
@ -684,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;
|
||||
@ -970,10 +966,20 @@ tuple<Token, unsigned, unsigned> Scanner::scanIdentifierOrKeyword()
|
||||
LiteralScope literal(this, LITERAL_TYPE_STRING);
|
||||
addLiteralCharAndAdvance();
|
||||
// Scan the rest of the identifier characters.
|
||||
while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier))
|
||||
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
|
||||
|
@ -68,6 +68,12 @@ class AstRawString;
|
||||
class AstValueFactory;
|
||||
class ParserRecorder;
|
||||
|
||||
enum class ScannerKind
|
||||
{
|
||||
Solidity,
|
||||
Yul
|
||||
};
|
||||
|
||||
enum class ScannerError
|
||||
{
|
||||
NoError,
|
||||
@ -107,9 +113,14 @@ public:
|
||||
/// Resets scanner to the start of input.
|
||||
void reset();
|
||||
|
||||
/// Enables or disables support for period in identifier.
|
||||
/// This re-scans the current token and comment literal and thus invalidates it.
|
||||
void supportPeriodInIdentifier(bool _value);
|
||||
/// Changes the scanner mode.
|
||||
void setScannerMode(ScannerKind _kind)
|
||||
{
|
||||
m_kind = _kind;
|
||||
|
||||
// Invalidate lookahead buffer.
|
||||
rescan();
|
||||
}
|
||||
|
||||
/// @returns the next token and advances input
|
||||
Token next();
|
||||
@ -249,8 +260,6 @@ private:
|
||||
size_t sourcePos() const { return m_source->position(); }
|
||||
bool isSourcePastEndOfInput() const { return m_source->isPastEndOfInput(); }
|
||||
|
||||
bool m_supportPeriodInIdentifier = false;
|
||||
|
||||
enum TokenIndex { Current, Next, NextNext };
|
||||
|
||||
TokenDesc m_skippedComments[3] = {}; // desc for the current, next and nextnext skipped comment
|
||||
@ -258,6 +267,8 @@ private:
|
||||
|
||||
std::shared_ptr<CharStream> m_source;
|
||||
|
||||
ScannerKind m_kind = ScannerKind::Solidity;
|
||||
|
||||
/// one character look-ahead, equals 0 at end of input
|
||||
char m_char;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -100,6 +100,8 @@ set(sources
|
||||
formal/EncodingContext.h
|
||||
formal/ModelChecker.cpp
|
||||
formal/ModelChecker.h
|
||||
formal/Predicate.cpp
|
||||
formal/Predicate.h
|
||||
formal/SMTEncoder.cpp
|
||||
formal/SMTEncoder.h
|
||||
formal/SSAVariable.cpp
|
||||
|
@ -243,12 +243,7 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
|
||||
solAssert(!m_errorReporter.errors().empty(), "");
|
||||
return;
|
||||
}
|
||||
if (baseType->storageBytes() == 0)
|
||||
m_errorReporter.fatalTypeError(
|
||||
6493_error,
|
||||
_typeName.baseType().location(),
|
||||
"Illegal base type of storage size zero for array."
|
||||
);
|
||||
solAssert(baseType->storageBytes() != 0, "Illegal base type of storage size zero for array.");
|
||||
if (Expression const* length = _typeName.length())
|
||||
{
|
||||
TypePointer& lengthTypeGeneric = length->annotation().type;
|
||||
|
@ -165,12 +165,7 @@ void DocStringTagParser::parseDocStrings(
|
||||
returnTagsVisited++;
|
||||
if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(&_node))
|
||||
{
|
||||
if (!varDecl->isPublic())
|
||||
m_errorReporter.docstringParsingError(
|
||||
9440_error,
|
||||
_node.documentation()->location(),
|
||||
"Documentation tag \"@" + docTag.first + "\" is only allowed on public state-variables."
|
||||
);
|
||||
solAssert(varDecl->isPublic(), "@return is only allowed on public state-variables.");
|
||||
if (returnTagsVisited > 1)
|
||||
m_errorReporter.docstringParsingError(
|
||||
5256_error,
|
||||
|
@ -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))
|
||||
|
@ -231,15 +231,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
||||
string(".slot").size() :
|
||||
string(".offset").size()
|
||||
));
|
||||
if (realName.empty())
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
4794_error,
|
||||
_identifier.location,
|
||||
"In variable names .slot and .offset can only be used as a suffix."
|
||||
);
|
||||
return;
|
||||
}
|
||||
solAssert(!realName.empty(), "Empty name.");
|
||||
declarations = m_resolver.nameFromCurrentScope(realName);
|
||||
if (!declarations.empty())
|
||||
// To support proper path resolution, we have to use pathFromCurrentScope.
|
||||
@ -321,8 +313,29 @@ void ReferencesResolver::resolveInheritDoc(StructuredDocumentation const& _docum
|
||||
case 1:
|
||||
{
|
||||
string const& name = _annotation.docTags.find("inheritdoc")->second.content;
|
||||
if (name.empty())
|
||||
{
|
||||
m_errorReporter.docstringParsingError(
|
||||
1933_error,
|
||||
_documentation.location(),
|
||||
"Expected contract name following documentation tag @inheritdoc."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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,11 +788,13 @@ public:
|
||||
StructurallyDocumented(_documentation),
|
||||
ImplementationOptional(_body != nullptr),
|
||||
m_stateMutability(_stateMutability),
|
||||
m_free(_free),
|
||||
m_kind(_kind),
|
||||
m_functionModifiers(std::move(_modifiers)),
|
||||
m_body(_body)
|
||||
{
|
||||
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
|
||||
solAssert(isOrdinary() == !name().empty(), "");
|
||||
}
|
||||
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
@ -803,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; }
|
||||
@ -814,6 +818,7 @@ public:
|
||||
}
|
||||
bool isVisibleViaContractTypeAccess() const override
|
||||
{
|
||||
solAssert(!isFree(), "");
|
||||
return isOrdinary() && visibility() >= Visibility::Public;
|
||||
}
|
||||
bool isPartOfExternalInterface() const override { return isOrdinary() && isPublic(); }
|
||||
@ -849,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")),
|
||||
|
@ -451,7 +451,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc
|
||||
);
|
||||
for (FunctionDefinition const* function: library.definedFunctions())
|
||||
{
|
||||
if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function))
|
||||
if (!function->isOrdinary() || !function->isVisibleAsLibraryMember() || seenFunctions.count(function))
|
||||
continue;
|
||||
seenFunctions.insert(function);
|
||||
if (function->parameters().empty())
|
||||
@ -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);
|
||||
|
||||
@ -3838,6 +3848,8 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
|
||||
{
|
||||
if (dynamic_cast<ModifierDefinition const*>(declaration))
|
||||
continue;
|
||||
if (declaration->name().empty())
|
||||
continue;
|
||||
|
||||
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
|
||||
{
|
||||
@ -4071,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:
|
||||
|
@ -357,38 +357,47 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
||||
"Struct assignment with conversion."
|
||||
);
|
||||
solAssert(!structType.containsNestedMapping(), "");
|
||||
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
|
||||
for (auto const& member: structType.members(nullptr))
|
||||
if (sourceType.location() == DataLocation::CallData)
|
||||
{
|
||||
// assign each member that can live outside of storage
|
||||
TypePointer const& memberType = member.type;
|
||||
solAssert(memberType->nameable(), "");
|
||||
TypePointer sourceMemberType = sourceType.memberType(member.name);
|
||||
if (sourceType.location() == DataLocation::Storage)
|
||||
solAssert(sourceType.sizeOnStack() == 1, "");
|
||||
solAssert(structType.sizeOnStack() == 1, "");
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2;
|
||||
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& member: structType.members(nullptr))
|
||||
{
|
||||
// stack layout: source_ref target_ref
|
||||
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
|
||||
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
|
||||
// assign each member that can live outside of storage
|
||||
TypePointer const& memberType = member.type;
|
||||
solAssert(memberType->nameable(), "");
|
||||
TypePointer sourceMemberType = sourceType.memberType(member.name);
|
||||
if (sourceType.location() == DataLocation::Storage)
|
||||
{
|
||||
// stack layout: source_ref target_ref
|
||||
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
|
||||
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
// stack: source_ref target_ref source_member_ref source_member_off
|
||||
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack: source_ref target_ref source_value...
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(sourceType.location() == DataLocation::Memory, "");
|
||||
// stack layout: source_ref target_ref
|
||||
m_context << sourceType.memoryOffsetOfMember(member.name);
|
||||
m_context << Instruction::DUP3 << Instruction::ADD;
|
||||
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack layout: source_ref target_ref source_value...
|
||||
}
|
||||
unsigned stackSize = sourceMemberType->sizeOnStack();
|
||||
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
|
||||
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
// stack: source_ref target_ref source_member_ref source_member_off
|
||||
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack: source_ref target_ref source_value...
|
||||
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
|
||||
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(sourceType.location() == DataLocation::Memory, "");
|
||||
// stack layout: source_ref target_ref
|
||||
m_context << sourceType.memoryOffsetOfMember(member.name);
|
||||
m_context << Instruction::DUP3 << Instruction::ADD;
|
||||
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack layout: source_ref target_ref source_value...
|
||||
}
|
||||
unsigned stackSize = sourceMemberType->sizeOnStack();
|
||||
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
|
||||
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
|
||||
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
|
||||
}
|
||||
// stack layout: source_ref target_ref
|
||||
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
|
||||
|
@ -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";
|
||||
@ -831,7 +964,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
|
||||
("dataAreaFunction", arrayDataAreaFunction(_type))
|
||||
("isByteArray", _type.isByteArray())
|
||||
("indexAccess", storageArrayIndexAccessFunction(_type))
|
||||
("storeValue", updateStorageValueFunction(*_type.baseType()))
|
||||
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
|
||||
("maxArrayLength", (u256(1) << 64).str())
|
||||
("shl", shiftLeftFunctionDynamic())
|
||||
("shr", shiftRightFunction(248))
|
||||
@ -861,7 +994,7 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
|
||||
("functionName", functionName)
|
||||
("fetchLength", arrayLengthFunction(_type))
|
||||
("indexAccess", storageArrayIndexAccessFunction(_type))
|
||||
("storeValue", updateStorageValueFunction(*_type.baseType()))
|
||||
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
|
||||
("maxArrayLength", (u256(1) << 64).str())
|
||||
("zeroValueFunction", zeroValueFunction(*_type.baseType()))
|
||||
.render();
|
||||
@ -1271,15 +1404,35 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
|
||||
|
||||
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
if (_type.isValueType())
|
||||
return readFromStorageValueType(_type, _offset, _splitFunctionTypes);
|
||||
else
|
||||
{
|
||||
solAssert(_offset == 0, "");
|
||||
return readFromStorageReferenceType(_type);
|
||||
}
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
return readFromStorageValueTypeDynamic(_type, _splitFunctionTypes);
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromStorageValueType(Type const& _type, size_t _offset, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
|
||||
if (_type.category() == Type::Category::Function)
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
string functionName =
|
||||
"read_from_storage_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
"_" +
|
||||
_type.identifier();
|
||||
"read_from_storage_" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"offset_" +
|
||||
to_string(_offset) +
|
||||
"_" +
|
||||
_type.identifier();
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
solAssert(_type.sizeOnStack() == 1, "");
|
||||
return Whiskers(R"(
|
||||
@ -1287,18 +1440,19 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
|
||||
value := <extract>(sload(slot))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("extract", extractFromStorageValue(_type, _offset, false))
|
||||
.render();
|
||||
("functionName", functionName)
|
||||
("extract", extractFromStorageValue(_type, _offset, false))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
|
||||
string YulUtilFunctions::readFromStorageValueTypeDynamic(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
if (_type.category() == Type::Category::Function)
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
|
||||
string functionName =
|
||||
"read_from_storage_dynamic" +
|
||||
"read_from_storage_value_type_dynamic" +
|
||||
string(_splitFunctionTypes ? "split_" : "") +
|
||||
"_" +
|
||||
_type.identifier();
|
||||
@ -1314,6 +1468,55 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
|
||||
.render();
|
||||
});
|
||||
}
|
||||
string YulUtilFunctions::readFromStorageReferenceType(Type const& _type)
|
||||
{
|
||||
solUnimplementedAssert(_type.category() == Type::Category::Struct, "");
|
||||
|
||||
string functionName = "read_from_storage_reference_type_" + _type.identifier();
|
||||
|
||||
auto const& structType = dynamic_cast<StructType const&>(_type);
|
||||
solAssert(structType.location() == DataLocation::Memory, "");
|
||||
MemberList::MemberMap structMembers = structType.nativeMembers(nullptr);
|
||||
vector<map<string, string>> memberSetValues(structMembers.size());
|
||||
for (size_t i = 0; i < structMembers.size(); ++i)
|
||||
{
|
||||
auto const& [memberSlotDiff, memberStorageOffset] = structType.storageOffsetsOfMember(structMembers[i].name);
|
||||
|
||||
memberSetValues[i]["setMember"] = Whiskers(R"(
|
||||
{
|
||||
let <memberValues> := <readFromStorage>(add(slot, <memberSlotDiff>)<?hasOffset>, <memberStorageOffset></hasOffset>)
|
||||
<writeToMemory>(add(value, <memberMemoryOffset>), <memberValues>)
|
||||
}
|
||||
)")
|
||||
("memberValues", suffixedVariableNameList("memberValue_", 0, structMembers[i].type->stackItems().size()))
|
||||
("memberMemoryOffset", structType.memoryOffsetOfMember(structMembers[i].name).str())
|
||||
("memberSlotDiff", memberSlotDiff.str())
|
||||
("memberStorageOffset", to_string(memberStorageOffset))
|
||||
("readFromStorage",
|
||||
structMembers[i].type->isValueType() ?
|
||||
readFromStorageDynamic(*structMembers[i].type, true) :
|
||||
readFromStorage(*structMembers[i].type, memberStorageOffset, true)
|
||||
)
|
||||
("writeToMemory", writeToMemoryFunction(*structMembers[i].type))
|
||||
("hasOffset", structMembers[i].type->isValueType())
|
||||
.render();
|
||||
}
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> value {
|
||||
value := <allocStruct>()
|
||||
<#member>
|
||||
<setMember>
|
||||
</member>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("allocStruct", allocateMemoryStructFunction(structType))
|
||||
("member", memberSetValues)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromMemory(Type const& _type)
|
||||
{
|
||||
@ -1325,18 +1528,25 @@ string YulUtilFunctions::readFromCalldata(Type const& _type)
|
||||
return readFromMemoryOrCalldata(_type, true);
|
||||
}
|
||||
|
||||
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::optional<unsigned> const& _offset)
|
||||
string YulUtilFunctions::updateStorageValueFunction(
|
||||
Type const& _fromType,
|
||||
Type const& _toType,
|
||||
std::optional<unsigned> const& _offset
|
||||
)
|
||||
{
|
||||
string const functionName =
|
||||
"update_storage_value_" +
|
||||
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
|
||||
_type.identifier();
|
||||
_fromType.identifier() +
|
||||
"_to_" +
|
||||
_toType.identifier();
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&] {
|
||||
if (_type.isValueType())
|
||||
if (_toType.isValueType())
|
||||
{
|
||||
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
|
||||
solAssert(_type.storageBytes() > 0, "Invalid storage bytes size.");
|
||||
solAssert(_fromType.isImplicitlyConvertibleTo(_toType), "");
|
||||
solAssert(_toType.storageBytes() <= 32, "Invalid storage bytes size.");
|
||||
solAssert(_toType.storageBytes() > 0, "Invalid storage bytes size.");
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot, <offset>value) {
|
||||
@ -1347,19 +1557,83 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
|
||||
("functionName", functionName)
|
||||
("update",
|
||||
_offset.has_value() ?
|
||||
updateByteSliceFunction(_type.storageBytes(), *_offset) :
|
||||
updateByteSliceFunctionDynamic(_type.storageBytes())
|
||||
updateByteSliceFunction(_toType.storageBytes(), *_offset) :
|
||||
updateByteSliceFunctionDynamic(_toType.storageBytes())
|
||||
)
|
||||
("offset", _offset.has_value() ? "" : "offset, ")
|
||||
("prepare", prepareStoreFunction(_type))
|
||||
("prepare", prepareStoreFunction(_toType))
|
||||
.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_type.category() == Type::Category::Array)
|
||||
solUnimplementedAssert(false, "");
|
||||
else if (_type.category() == Type::Category::Struct)
|
||||
auto const* toReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
|
||||
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_toType);
|
||||
solAssert(fromReferenceType && toReferenceType, "");
|
||||
solAssert(*toReferenceType->copyForLocation(
|
||||
fromReferenceType->location(),
|
||||
fromReferenceType->isPointer()
|
||||
).get() == *fromReferenceType, "");
|
||||
|
||||
if (_toType.category() == Type::Category::Array)
|
||||
solUnimplementedAssert(false, "");
|
||||
else if (_toType.category() == Type::Category::Struct)
|
||||
{
|
||||
solAssert(_fromType.category() == Type::Category::Struct, "");
|
||||
auto const& fromStructType = dynamic_cast<StructType const&>(_fromType);
|
||||
auto const& toStructType = dynamic_cast<StructType const&>(_toType);
|
||||
solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), "");
|
||||
solAssert(fromStructType.location() != DataLocation::Storage, "");
|
||||
solUnimplementedAssert(_offset.has_value() && _offset.value() == 0, "");
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot, value) {
|
||||
<#member>
|
||||
{
|
||||
<updateMemberCall>
|
||||
}
|
||||
</member>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
|
||||
MemberList::MemberMap structMembers = fromStructType.nativeMembers(nullptr);
|
||||
|
||||
vector<map<string, string>> memberParams(structMembers.size());
|
||||
for (size_t i = 0; i < structMembers.size(); ++i)
|
||||
{
|
||||
solAssert(structMembers[i].type->memoryHeadSize() == 32, "");
|
||||
bool fromCalldata = fromStructType.location() == DataLocation::CallData;
|
||||
auto const& [slotDiff, offset] = toStructType.storageOffsetsOfMember(structMembers[i].name);
|
||||
memberParams[i]["updateMemberCall"] = Whiskers(R"(
|
||||
let <memberValues> := <loadFromMemoryOrCalldata>(add(value, <memberOffset>))
|
||||
<updateMember>(add(slot, <memberStorageSlotDiff>), <?hasOffset><memberStorageOffset>,</hasOffset> <memberValues>)
|
||||
)")
|
||||
("memberValues", suffixedVariableNameList(
|
||||
"memberValue_",
|
||||
0,
|
||||
structMembers[i].type->stackItems().size()
|
||||
))
|
||||
("hasOffset", structMembers[i].type->isValueType())
|
||||
(
|
||||
"updateMember",
|
||||
structMembers[i].type->isValueType() ?
|
||||
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type) :
|
||||
updateStorageValueFunction(*structMembers[i].type, *structMembers[i].type, offset)
|
||||
)
|
||||
("memberStorageSlotDiff", slotDiff.str())
|
||||
("memberStorageOffset", to_string(offset))
|
||||
("memberOffset",
|
||||
fromCalldata ?
|
||||
to_string(fromStructType.calldataOffsetOfMember(structMembers[i].name)) :
|
||||
fromStructType.memoryOffsetOfMember(structMembers[i].name).str()
|
||||
)
|
||||
("loadFromMemoryOrCalldata", readFromMemoryOrCalldata(*structMembers[i].type, fromCalldata))
|
||||
.render();
|
||||
}
|
||||
templ("member", memberParams);
|
||||
|
||||
return templ.render();
|
||||
}
|
||||
else
|
||||
solAssert(false, "Invalid non-value type for assignment.");
|
||||
}
|
||||
@ -1911,8 +2185,37 @@ 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::Memory, "");
|
||||
|
||||
if (fromStructType.location() == DataLocation::CallData)
|
||||
{
|
||||
body = Whiskers(R"(
|
||||
converted := <abiDecode>(value, calldatasize())
|
||||
)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder(
|
||||
{&toStructType}
|
||||
)).render();
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(fromStructType.location() == DataLocation::Storage, "");
|
||||
|
||||
body = Whiskers(R"(
|
||||
converted := <readFromStorage>(value)
|
||||
)")
|
||||
("readFromStorage", readFromStorage(toStructType, 0, true))
|
||||
.render();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from);
|
||||
@ -2342,7 +2645,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("store", updateStorageValueFunction(_type))
|
||||
("store", updateStorageValueFunction(_type, _type))
|
||||
("zeroValue", zeroValueFunction(_type))
|
||||
.render();
|
||||
else if (_type.category() == Type::Category::Array)
|
||||
|
@ -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
|
||||
@ -206,8 +221,7 @@ public:
|
||||
/// @param _keyType the type of the value provided
|
||||
std::string mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType);
|
||||
|
||||
/// @returns a function that reads a value type from storage.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @returns a function that reads a type from storage.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
@ -233,7 +247,11 @@ public:
|
||||
/// the specified slot and offset. If offset is not given, it is expected as
|
||||
/// runtime parameter.
|
||||
/// signature: (slot, [offset,] value)
|
||||
std::string updateStorageValueFunction(Type const& _type, std::optional<unsigned> const& _offset = std::optional<unsigned>());
|
||||
std::string updateStorageValueFunction(
|
||||
Type const& _fromType,
|
||||
Type const& _toType,
|
||||
std::optional<unsigned> const& _offset = std::optional<unsigned>()
|
||||
);
|
||||
|
||||
/// Returns the name of a function that will write the given value to
|
||||
/// the specified address.
|
||||
@ -386,6 +404,16 @@ private:
|
||||
|
||||
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
|
||||
|
||||
/// @returns a function that reads a value type from storage.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
/// @param _splitFunctionTypes if false, returns the address and function signature in a
|
||||
/// single variable.
|
||||
std::string readFromStorageValueType(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
std::string readFromStorageValueTypeDynamic(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns a function that reads a reference type from storage to memory (performing a deep copy).
|
||||
std::string readFromStorageReferenceType(Type const& _type);
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings m_revertStrings;
|
||||
MultiUseYulFunctionCollector& m_functionCollector;
|
||||
|
@ -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(), "");
|
||||
@ -301,9 +314,9 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
|
||||
writeToLValue(*m_currentLValue, value);
|
||||
|
||||
m_currentLValue.reset();
|
||||
if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
|
||||
if (m_currentLValue->type.category() != Type::Category::Struct && *_assignment.annotation().type != *TypeProvider::emptyTuple())
|
||||
define(_assignment, value);
|
||||
m_currentLValue.reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -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());
|
||||
@ -733,8 +749,18 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
|
||||
if (identifier)
|
||||
functionDef = &functionDef->resolveVirtual(m_context.mostDerivedContract());
|
||||
else
|
||||
{
|
||||
ContractType const* type = dynamic_cast<ContractType const*>(memberAccess->expression().annotation().type);
|
||||
if (type && type->isSuper())
|
||||
{
|
||||
ContractDefinition const* super = type->contractDefinition().superContract(m_context.mostDerivedContract());
|
||||
solAssert(super, "Super contract not available.");
|
||||
functionDef = &functionDef->resolveVirtual(m_context.mostDerivedContract(), super);
|
||||
}
|
||||
}
|
||||
|
||||
solAssert(functionDef->isImplemented(), "");
|
||||
solAssert(functionDef && functionDef->isImplemented(), "");
|
||||
}
|
||||
|
||||
solAssert(!functionType->takesArbitraryParameters(), "");
|
||||
@ -1381,7 +1407,17 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type);
|
||||
if (type.isSuper())
|
||||
{
|
||||
solUnimplementedAssert(false, "");
|
||||
solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved.");
|
||||
ContractDefinition const* super = type.contractDefinition().superContract(m_context.mostDerivedContract());
|
||||
solAssert(super, "Super contract not available.");
|
||||
FunctionDefinition const& resolvedFunctionDef = dynamic_cast<FunctionDefinition const&>(
|
||||
*_memberAccess.annotation().referencedDeclaration
|
||||
).resolveVirtual(m_context.mostDerivedContract(), super);
|
||||
|
||||
define(_memberAccess) << to_string(resolvedFunctionDef.id()) << "\n";
|
||||
solAssert(resolvedFunctionDef.functionType(true), "");
|
||||
solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal, "");
|
||||
m_context.internalFunctionAccessed(_memberAccess, resolvedFunctionDef);
|
||||
}
|
||||
// ordinary contract type
|
||||
else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
|
||||
@ -2022,8 +2058,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
|
||||
@ -2470,7 +2506,7 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
|
||||
offset = std::get<unsigned>(_storage.offset);
|
||||
|
||||
m_code <<
|
||||
m_utils.updateStorageValueFunction(_lvalue.type, offset) <<
|
||||
m_utils.updateStorageValueFunction(_value.type(), _lvalue.type, offset) <<
|
||||
"(" <<
|
||||
_storage.slot <<
|
||||
(
|
||||
|
@ -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;
|
||||
|
||||
|
@ -61,7 +61,7 @@ void BMC::analyze(SourceUnit const& _source, map<ASTNode const*, set<Verificatio
|
||||
m_context.setSolver(m_interface.get());
|
||||
m_context.clear();
|
||||
m_context.setAssertionAccumulation(true);
|
||||
m_variableUsage.setFunctionInlining(true);
|
||||
m_variableUsage.setFunctionInlining(shouldInlineFunctionCall);
|
||||
|
||||
_source.accept(*this);
|
||||
|
||||
@ -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;
|
||||
|
@ -29,7 +29,6 @@
|
||||
#include <libsmtutil/CHCSmtLib2Interface.h>
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
#include <queue>
|
||||
@ -82,15 +81,10 @@ void CHC::analyze(SourceUnit const& _source)
|
||||
}
|
||||
m_context.clear();
|
||||
m_context.setAssertionAccumulation(false);
|
||||
m_variableUsage.setFunctionInlining(false);
|
||||
|
||||
resetSourceAnalysis();
|
||||
|
||||
auto genesisSort = make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>(),
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
|
||||
m_genesisPredicate = createSymbolicBlock(arity0FunctionSort(), "genesis");
|
||||
addRule(genesis(), "genesis");
|
||||
|
||||
set<SourceUnit const*, IdCompare> sources;
|
||||
@ -119,16 +113,14 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
initContract(_contract);
|
||||
|
||||
m_stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
m_stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
m_stateSorts = stateSorts(_contract);
|
||||
|
||||
clearIndices(&_contract);
|
||||
|
||||
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
||||
m_errorPredicate = createSymbolicBlock(arity0FunctionSort(), "error_" + suffix);
|
||||
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
|
||||
m_symbolFunction[m_constructorSummaryPredicate->currentFunctionValue().name] = &_contract;
|
||||
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix);
|
||||
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix, &_contract);
|
||||
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix, &_contract);
|
||||
auto stateExprs = currentStateVariables();
|
||||
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||
|
||||
@ -148,7 +140,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);
|
||||
@ -234,7 +226,7 @@ void CHC::endVisit(FunctionDefinition const& _function)
|
||||
if (_function.isConstructor())
|
||||
{
|
||||
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
|
||||
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix);
|
||||
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix, m_currentContract);
|
||||
connectBlocks(m_currentBlock, predicate(*constructorExit, currentFunctionVariables(*m_currentContract)));
|
||||
|
||||
clearIndices(m_currentContract, m_currentFunction);
|
||||
@ -327,8 +319,8 @@ bool CHC::visit(WhileStatement const& _while)
|
||||
|
||||
auto outerBreakDest = m_breakDest;
|
||||
auto outerContinueDest = m_continueDest;
|
||||
m_breakDest = afterLoopBlock.get();
|
||||
m_continueDest = loopHeaderBlock.get();
|
||||
m_breakDest = afterLoopBlock;
|
||||
m_continueDest = loopHeaderBlock;
|
||||
|
||||
if (_while.isDoWhile())
|
||||
_while.body().accept(*this);
|
||||
@ -378,8 +370,8 @@ bool CHC::visit(ForStatement const& _for)
|
||||
|
||||
auto outerBreakDest = m_breakDest;
|
||||
auto outerContinueDest = m_continueDest;
|
||||
m_breakDest = afterLoopBlock.get();
|
||||
m_continueDest = postLoop ? postLoopBlock.get() : loopHeaderBlock.get();
|
||||
m_breakDest = afterLoopBlock;
|
||||
m_continueDest = postLoop ? postLoopBlock : loopHeaderBlock;
|
||||
|
||||
if (auto init = _for.initializationExpression())
|
||||
init->accept(*this);
|
||||
@ -425,9 +417,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;
|
||||
@ -453,6 +445,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:
|
||||
@ -460,9 +455,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;
|
||||
@ -571,7 +564,6 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
|
||||
m_context.variable(*var)->increaseIndex();
|
||||
|
||||
auto nondet = (*m_nondetInterfaces.at(m_currentContract))(preCallState + currentStateVariables());
|
||||
m_symbolFunction[nondet.name] = &_funCall;
|
||||
m_context.addAssertion(nondet);
|
||||
|
||||
m_context.addAssertion(m_error.currentValue() == 0);
|
||||
@ -602,14 +594,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()
|
||||
@ -621,7 +673,9 @@ void CHC::resetSourceAnalysis()
|
||||
m_errorIds.clear();
|
||||
m_callGraph.clear();
|
||||
m_summaries.clear();
|
||||
m_symbolFunction.clear();
|
||||
m_interfaces.clear();
|
||||
m_nondetInterfaces.clear();
|
||||
Predicate::reset();
|
||||
}
|
||||
|
||||
void CHC::resetContractAnalysis()
|
||||
@ -657,7 +711,7 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
|
||||
}
|
||||
|
||||
void CHC::setCurrentBlock(
|
||||
smt::SymbolicFunctionVariable const& _block,
|
||||
Predicate const& _block,
|
||||
vector<smtutil::Expression> const* _arguments
|
||||
)
|
||||
{
|
||||
@ -683,24 +737,10 @@ set<frontend::Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTN
|
||||
return assertions;
|
||||
}
|
||||
|
||||
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
|
||||
{
|
||||
return fold(
|
||||
_contract.annotation().linearizedBaseContracts,
|
||||
vector<VariableDeclaration const*>{},
|
||||
[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
|
||||
);
|
||||
}
|
||||
|
||||
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function)
|
||||
{
|
||||
return stateVariablesIncludingInheritedAndPrivate(dynamic_cast<ContractDefinition const&>(*_function.scope()));
|
||||
}
|
||||
|
||||
vector<smtutil::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
||||
{
|
||||
return applyMap(
|
||||
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
|
||||
);
|
||||
}
|
||||
@ -746,7 +786,7 @@ smtutil::SortPointer CHC::nondetInterfaceSort(ContractDefinition const& _contrac
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::arity0FunctionSort()
|
||||
smtutil::SortPointer CHC::arity0FunctionSort() const
|
||||
{
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>(),
|
||||
@ -793,7 +833,7 @@ smtutil::SortPointer CHC::sort(ASTNode const* _node)
|
||||
|
||||
smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||
{
|
||||
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
auto stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
auto sorts = stateSorts(_contract);
|
||||
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
@ -810,14 +850,10 @@ smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, Contr
|
||||
);
|
||||
}
|
||||
|
||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smtutil::SortPointer _sort, string const& _name)
|
||||
Predicate const* CHC::createSymbolicBlock(SortPointer _sort, string const& _name, ASTNode const* _node)
|
||||
{
|
||||
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
||||
_sort,
|
||||
_name,
|
||||
m_context
|
||||
);
|
||||
m_interface->registerRelation(block->currentFunctionValue());
|
||||
auto const* block = Predicate::create(_sort, _name, m_context, _node);
|
||||
m_interface->registerRelation(block->functor());
|
||||
return block;
|
||||
}
|
||||
|
||||
@ -827,20 +863,24 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||
if (auto const* contract = dynamic_cast<ContractDefinition const*>(node.get()))
|
||||
for (auto const* base: contract->annotation().linearizedBaseContracts)
|
||||
{
|
||||
string suffix = base->name() + "_" + to_string(base->id());
|
||||
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
|
||||
m_nondetInterfaces[base] = createSymbolicBlock(nondetInterfaceSort(*base), "nondet_interface_" + suffix);
|
||||
|
||||
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
|
||||
for (auto const* var: SMTEncoder::stateVariablesIncludingInheritedAndPrivate(*base))
|
||||
if (!m_context.knownVariable(*var))
|
||||
createVariable(*var);
|
||||
|
||||
/// Base nondeterministic interface that allows
|
||||
/// 0 steps to be taken, used as base for the inductive
|
||||
/// rule for each function.
|
||||
auto const& iface = *m_nondetInterfaces.at(base);
|
||||
auto state0 = stateVariablesAtIndex(0, *base);
|
||||
addRule(iface(state0 + state0), "base_nondet");
|
||||
if (!m_interfaces.count(base))
|
||||
{
|
||||
solAssert(!m_nondetInterfaces.count(base), "");
|
||||
string suffix = base->name() + "_" + to_string(base->id());
|
||||
m_interfaces.emplace(base, createSymbolicBlock(interfaceSort(*base), "interface_" + suffix, base));
|
||||
m_nondetInterfaces.emplace(base, createSymbolicBlock(nondetInterfaceSort(*base), "nondet_interface_" + suffix, base));
|
||||
|
||||
/// Base nondeterministic interface that allows
|
||||
/// 0 steps to be taken, used as base for the inductive
|
||||
/// rule for each function.
|
||||
auto const* iface = m_nondetInterfaces.at(base);
|
||||
auto state0 = stateVariablesAtIndex(0, *base);
|
||||
addRule((*iface)(state0 + state0), "base_nondet");
|
||||
}
|
||||
|
||||
for (auto const* function: base->definedFunctions())
|
||||
{
|
||||
@ -863,8 +903,10 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||
auto state1 = stateVariablesAtIndex(1, *base);
|
||||
auto state2 = stateVariablesAtIndex(2, *base);
|
||||
|
||||
auto nondetPre = iface(state0 + state1);
|
||||
auto nondetPost = iface(state0 + state2);
|
||||
auto const* iface = m_nondetInterfaces.at(base);
|
||||
auto state0 = stateVariablesAtIndex(0, *base);
|
||||
auto nondetPre = (*iface)(state0 + state1);
|
||||
auto nondetPost = (*iface)(state0 + state2);
|
||||
|
||||
vector<smtutil::Expression> args{m_error.currentValue()};
|
||||
args += state1 +
|
||||
@ -900,7 +942,7 @@ smtutil::Expression CHC::error()
|
||||
|
||||
smtutil::Expression CHC::error(unsigned _idx)
|
||||
{
|
||||
return m_errorPredicate->functionValueAtIndex(_idx)({});
|
||||
return m_errorPredicate->functor(_idx)({});
|
||||
}
|
||||
|
||||
smtutil::Expression CHC::summary(ContractDefinition const& _contract)
|
||||
@ -934,37 +976,33 @@ smtutil::Expression CHC::summary(FunctionDefinition const& _function)
|
||||
return summary(_function, *m_currentContract);
|
||||
}
|
||||
|
||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||
Predicate const* CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||
{
|
||||
auto block = createSymbolicBlock(sort(_node),
|
||||
"block_" +
|
||||
uniquePrefix() +
|
||||
"_" +
|
||||
_prefix +
|
||||
predicateName(_node));
|
||||
auto block = createSymbolicBlock(
|
||||
sort(_node),
|
||||
"block_" + uniquePrefix() + "_" + _prefix + predicateName(_node),
|
||||
_node
|
||||
);
|
||||
|
||||
solAssert(m_currentFunction, "");
|
||||
m_symbolFunction[block->currentFunctionValue().name] = m_currentFunction;
|
||||
return block;
|
||||
}
|
||||
|
||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||
Predicate const* CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||
{
|
||||
auto block = createSymbolicBlock(summarySort(_function, _contract),
|
||||
"summary_" +
|
||||
uniquePrefix() +
|
||||
"_" +
|
||||
predicateName(&_function, &_contract));
|
||||
auto block = createSymbolicBlock(
|
||||
summarySort(_function, _contract),
|
||||
"summary_" + uniquePrefix() + "_" + predicateName(&_function, &_contract),
|
||||
&_function
|
||||
);
|
||||
|
||||
m_symbolFunction[block->currentFunctionValue().name] = &_function;
|
||||
return block;
|
||||
}
|
||||
|
||||
void CHC::createErrorBlock()
|
||||
{
|
||||
solAssert(m_errorPredicate, "");
|
||||
m_errorPredicate->increaseIndex();
|
||||
m_interface->registerRelation(m_errorPredicate->currentFunctionValue());
|
||||
m_errorPredicate = createSymbolicBlock(arity0FunctionSort(), "error_target_" + to_string(m_context.newUniqueId()));
|
||||
m_interface->registerRelation(m_errorPredicate->functor());
|
||||
}
|
||||
|
||||
void CHC::connectBlocks(smtutil::Expression const& _from, smtutil::Expression const& _to, smtutil::Expression const& _constraints)
|
||||
@ -995,7 +1033,7 @@ vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index)
|
||||
vector<smtutil::Expression> CHC::stateVariablesAtIndex(unsigned _index, ContractDefinition const& _contract)
|
||||
{
|
||||
return applyMap(
|
||||
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[&](auto _var) { return valueAtIndex(*_var, _index); }
|
||||
);
|
||||
}
|
||||
@ -1008,7 +1046,7 @@ vector<smtutil::Expression> CHC::currentStateVariables()
|
||||
|
||||
vector<smtutil::Expression> CHC::currentStateVariables(ContractDefinition const& _contract)
|
||||
{
|
||||
return applyMap(stateVariablesIncludingInheritedAndPrivate(_contract), [this](auto _var) { return currentValue(*_var); });
|
||||
return applyMap(SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract), [this](auto _var) { return currentValue(*_var); });
|
||||
}
|
||||
|
||||
vector<smtutil::Expression> CHC::currentFunctionVariables()
|
||||
@ -1068,13 +1106,13 @@ string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contr
|
||||
return prefix + "_" + to_string(_node->id()) + "_" + to_string(contract->id());
|
||||
}
|
||||
|
||||
smtutil::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block)
|
||||
smtutil::Expression CHC::predicate(Predicate const& _block)
|
||||
{
|
||||
return _block(currentBlockVariables());
|
||||
}
|
||||
|
||||
smtutil::Expression CHC::predicate(
|
||||
smt::SymbolicFunctionVariable const& _block,
|
||||
Predicate const& _block,
|
||||
vector<smtutil::Expression> const& _arguments
|
||||
)
|
||||
{
|
||||
@ -1175,26 +1213,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)
|
||||
@ -1204,8 +1241,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)
|
||||
{
|
||||
@ -1214,12 +1255,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1323,42 +1403,31 @@ optional<string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const&
|
||||
solAssert(edges.size() <= 2, "");
|
||||
|
||||
unsigned summaryId = edges.at(0);
|
||||
|
||||
optional<unsigned> interfaceId;
|
||||
if (edges.size() == 2)
|
||||
{
|
||||
interfaceId = edges.at(1);
|
||||
if (_graph.nodes.at(summaryId).first.rfind("summary", 0) != 0)
|
||||
if (!Predicate::predicate(_graph.nodes.at(summaryId).first)->isSummary())
|
||||
swap(summaryId, *interfaceId);
|
||||
solAssert(_graph.nodes.at(*interfaceId).first.rfind("interface", 0) == 0, "");
|
||||
auto interfacePredicate = Predicate::predicate(_graph.nodes.at(*interfaceId).first);
|
||||
solAssert(interfacePredicate && interfacePredicate->isInterface(), "");
|
||||
}
|
||||
/// The children are unordered, so we need to check which is the summary and
|
||||
/// which is the interface.
|
||||
|
||||
solAssert(_graph.nodes.at(summaryId).first.rfind("summary", 0) == 0, "");
|
||||
Predicate const* summaryPredicate = Predicate::predicate(_graph.nodes.at(summaryId).first);
|
||||
solAssert(summaryPredicate && summaryPredicate->isSummary(), "");
|
||||
/// At this point property 2 from the function description is verified for this node.
|
||||
auto summaryArgs = _graph.nodes.at(summaryId).second;
|
||||
|
||||
auto const& summaryNode = _graph.nodes.at(summaryId);
|
||||
solAssert(m_symbolFunction.count(summaryNode.first), "");
|
||||
|
||||
FunctionDefinition const* calledFun = nullptr;
|
||||
ContractDefinition const* calledContract = nullptr;
|
||||
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_symbolFunction.at(summaryNode.first)))
|
||||
{
|
||||
if (auto const* constructor = contract->constructor())
|
||||
calledFun = constructor;
|
||||
else
|
||||
calledContract = contract;
|
||||
}
|
||||
else if (auto const* fun = dynamic_cast<FunctionDefinition const*>(m_symbolFunction.at(summaryNode.first)))
|
||||
calledFun = fun;
|
||||
else
|
||||
solAssert(false, "");
|
||||
FunctionDefinition const* calledFun = summaryPredicate->programFunction();
|
||||
ContractDefinition const* calledContract = summaryPredicate->programContract();
|
||||
|
||||
solAssert((calledFun && !calledContract) || (!calledFun && calledContract), "");
|
||||
auto const& stateVars = calledFun ? stateVariablesIncludingInheritedAndPrivate(*calledFun) : stateVariablesIncludingInheritedAndPrivate(*calledContract);
|
||||
/// calledContract != nullptr implies that the constructor of the analyzed contract is implicit and
|
||||
/// therefore takes no parameters.
|
||||
auto stateVars = summaryPredicate->stateVariables();
|
||||
solAssert(stateVars.has_value(), "");
|
||||
auto stateValues = summaryPredicate->summaryStateValues(summaryArgs);
|
||||
solAssert(stateValues.size() == stateVars->size(), "");
|
||||
|
||||
/// This summary node is the end of a tx.
|
||||
/// If it is the first summary node seen in this loop, it is the summary
|
||||
@ -1368,36 +1437,23 @@ optional<string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const&
|
||||
{
|
||||
lastTxSeen = true;
|
||||
/// Generate counterexample message local to the failed target.
|
||||
localState = formatStateCounterexample(stateVars, calledFun, summaryNode.second) + "\n";
|
||||
localState = formatVariableModel(*stateVars, stateValues, ", ") + "\n";
|
||||
if (calledFun)
|
||||
{
|
||||
/// The signature of a summary predicate is: summary(error, preStateVars, preInputVars, postInputVars, outputVars).
|
||||
auto inValues = summaryPredicate->summaryPostInputValues(summaryArgs);
|
||||
auto const& inParams = calledFun->parameters();
|
||||
unsigned initLocals = stateVars.size() * 2 + 1 + inParams.size();
|
||||
/// In this loop we are interested in postInputVars.
|
||||
for (unsigned i = initLocals; i < initLocals + inParams.size(); ++i)
|
||||
{
|
||||
auto param = inParams.at(i - initLocals);
|
||||
if (param->type()->isValueType())
|
||||
localState += param->name() + " = " + summaryNode.second.at(i) + "\n";
|
||||
}
|
||||
localState += formatVariableModel(inParams, inValues, "\n") + "\n";
|
||||
auto outValues = summaryPredicate->summaryPostOutputValues(summaryArgs);
|
||||
auto const& outParams = calledFun->returnParameters();
|
||||
initLocals += inParams.size();
|
||||
/// In this loop we are interested in outputVars.
|
||||
for (unsigned i = initLocals; i < initLocals + outParams.size(); ++i)
|
||||
{
|
||||
auto param = outParams.at(i - initLocals);
|
||||
if (param->type()->isValueType())
|
||||
localState += param->name() + " = " + summaryNode.second.at(i) + "\n";
|
||||
}
|
||||
localState += formatVariableModel(outParams, outValues, "\n") + "\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
/// We report the state after every tx in the trace except for the last, which is reported
|
||||
/// first in the code above.
|
||||
path.emplace_back("State: " + formatStateCounterexample(stateVars, calledFun, summaryNode.second));
|
||||
path.emplace_back("State: " + formatVariableModel(*stateVars, stateValues, ", "));
|
||||
|
||||
string txCex = calledContract ? "constructor()" : formatFunctionCallCounterexample(stateVars, *calledFun, summaryNode.second);
|
||||
string txCex = summaryPredicate->formatSummaryCall(summaryArgs);
|
||||
path.emplace_back(txCex);
|
||||
|
||||
/// Recurse on the next interface node which represents the previous transaction
|
||||
@ -1411,66 +1467,6 @@ optional<string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const&
|
||||
return localState + "\nTransaction trace:\n" + boost::algorithm::join(boost::adaptors::reverse(path), "\n");
|
||||
}
|
||||
|
||||
string CHC::formatStateCounterexample(vector<VariableDeclaration const*> const& _stateVars, FunctionDefinition const* _function, vector<string> const& _summaryValues)
|
||||
{
|
||||
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postInputVars, outputVars).
|
||||
/// The signature of an implicit constructor summary predicate is: summary(error, postStateVars).
|
||||
/// Here we are interested in postStateVars.
|
||||
vector<string>::const_iterator stateFirst;
|
||||
vector<string>::const_iterator stateLast;
|
||||
if (_function)
|
||||
{
|
||||
stateFirst = _summaryValues.begin() + 1 + static_cast<int>(_stateVars.size()) + static_cast<int>(_function->parameters().size());
|
||||
stateLast = stateFirst + static_cast<int>(_stateVars.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
stateFirst = _summaryValues.begin() + 1;
|
||||
stateLast = stateFirst + static_cast<int>(_stateVars.size());
|
||||
}
|
||||
|
||||
solAssert(stateFirst >= _summaryValues.begin() && stateFirst <= _summaryValues.end(), "");
|
||||
solAssert(stateLast >= _summaryValues.begin() && stateLast <= _summaryValues.end(), "");
|
||||
vector<string> stateArgs(stateFirst, stateLast);
|
||||
solAssert(stateArgs.size() == _stateVars.size(), "");
|
||||
|
||||
vector<string> stateCex;
|
||||
for (unsigned i = 0; i < stateArgs.size(); ++i)
|
||||
{
|
||||
auto var = _stateVars.at(i);
|
||||
if (var->type()->isValueType())
|
||||
stateCex.emplace_back(var->name() + " = " + stateArgs.at(i));
|
||||
}
|
||||
|
||||
return boost::algorithm::join(stateCex, ", ");
|
||||
}
|
||||
|
||||
string CHC::formatFunctionCallCounterexample(vector<VariableDeclaration const*> const& _stateVars, FunctionDefinition const& _function, vector<string> const& _summaryValues)
|
||||
{
|
||||
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postInputVars, outputVars).
|
||||
/// Here we are interested in preInputVars.
|
||||
vector<string>::const_iterator first = _summaryValues.begin() + static_cast<int>(_stateVars.size()) + 1;
|
||||
vector<string>::const_iterator last = first + static_cast<int>(_function.parameters().size());
|
||||
solAssert(first >= _summaryValues.begin() && first <= _summaryValues.end(), "");
|
||||
solAssert(last >= _summaryValues.begin() && last <= _summaryValues.end(), "");
|
||||
vector<string> functionArgsCex(first, last);
|
||||
vector<string> functionArgs;
|
||||
|
||||
auto const& params = _function.parameters();
|
||||
solAssert(params.size() == functionArgsCex.size(), "");
|
||||
for (unsigned i = 0; i < params.size(); ++i)
|
||||
if (params[i]->type()->isValueType())
|
||||
functionArgs.emplace_back(functionArgsCex[i]);
|
||||
else
|
||||
functionArgs.emplace_back(params[i]->name());
|
||||
|
||||
string fName = _function.isConstructor() ? "constructor" :
|
||||
_function.isFallback() ? "fallback" :
|
||||
_function.isReceive() ? "receive" :
|
||||
_function.name();
|
||||
return fName + "(" + boost::algorithm::join(functionArgs, ", ") + ")";
|
||||
}
|
||||
|
||||
string CHC::cex2dot(smtutil::CHCSolverInterface::CexGraph const& _cex)
|
||||
{
|
||||
string dot = "digraph {\n";
|
||||
|
@ -31,12 +31,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/Predicate.h>
|
||||
#include <libsolidity/formal/SMTEncoder.h>
|
||||
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
|
||||
#include <libsmtutil/CHCSolverInterface.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
@ -84,6 +87,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
|
||||
@ -100,10 +111,8 @@ private:
|
||||
void resetContractAnalysis();
|
||||
void eraseKnowledge();
|
||||
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
||||
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smtutil::Expression> const* _arguments = nullptr);
|
||||
void setCurrentBlock(Predicate const& _block, std::vector<smtutil::Expression> const* _arguments = nullptr);
|
||||
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
|
||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function);
|
||||
//@}
|
||||
|
||||
/// Sort helpers.
|
||||
@ -114,7 +123,7 @@ private:
|
||||
smtutil::SortPointer nondetInterfaceSort();
|
||||
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||
static smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _const);
|
||||
smtutil::SortPointer arity0FunctionSort();
|
||||
smtutil::SortPointer arity0FunctionSort() const;
|
||||
smtutil::SortPointer sort(FunctionDefinition const& _function);
|
||||
smtutil::SortPointer sort(ASTNode const* _block);
|
||||
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
|
||||
@ -126,7 +135,7 @@ private:
|
||||
/// Predicate helpers.
|
||||
//@{
|
||||
/// @returns a new block of given _sort and _name.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smtutil::SortPointer _sort, std::string const& _name);
|
||||
Predicate const* createSymbolicBlock(smtutil::SortPointer _sort, std::string const& _name, ASTNode const* _node = nullptr);
|
||||
|
||||
/// Creates summary predicates for all functions of all contracts
|
||||
/// in a given _source.
|
||||
@ -142,10 +151,10 @@ private:
|
||||
smtutil::Expression error(unsigned _idx);
|
||||
|
||||
/// Creates a block for the given _node.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = "");
|
||||
Predicate const* createBlock(ASTNode const* _node, std::string const& _prefix = "");
|
||||
/// Creates a call block for the given function _function from contract _contract.
|
||||
/// The contract is needed here because of inheritance.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||
Predicate const* createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||
|
||||
/// Creates a new error block to be used by an assertion.
|
||||
/// Also registers the predicate.
|
||||
@ -176,9 +185,9 @@ private:
|
||||
/// @returns the predicate name for a given node.
|
||||
std::string predicateName(ASTNode const* _node, ContractDefinition const* _contract = nullptr);
|
||||
/// @returns a predicate application over the current scoped variables.
|
||||
smtutil::Expression predicate(smt::SymbolicFunctionVariable const& _block);
|
||||
smtutil::Expression predicate(Predicate const& _block);
|
||||
/// @returns a predicate application over @param _arguments.
|
||||
smtutil::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smtutil::Expression> const& _arguments);
|
||||
smtutil::Expression predicate(Predicate const& _block, std::vector<smtutil::Expression> const& _arguments);
|
||||
/// @returns the summary predicate for the called function.
|
||||
smtutil::Expression predicate(FunctionCall const& _funCall);
|
||||
/// @returns a predicate that defines a constructor summary.
|
||||
@ -197,8 +206,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.
|
||||
@ -214,15 +223,23 @@ private:
|
||||
);
|
||||
|
||||
std::optional<std::string> generateCounterexample(smtutil::CHCSolverInterface::CexGraph const& _graph, std::string const& _root);
|
||||
/// @returns values for the _stateVariables after a transaction calling
|
||||
/// _function was executed.
|
||||
/// _function = nullptr means the transaction was the deployment of a
|
||||
/// contract without an explicit constructor.
|
||||
std::string formatStateCounterexample(std::vector<VariableDeclaration const*> const& _stateVariables, FunctionDefinition const* _function, std::vector<std::string> const& _summaryValues);
|
||||
/// @returns a formatted text representing a call to _function
|
||||
/// with the concrete values for value type parameters and
|
||||
/// the parameter name for reference types.
|
||||
std::string formatFunctionCallCounterexample(std::vector<VariableDeclaration const*> const& _stateVariables, FunctionDefinition const& _function, std::vector<std::string> const& _summaryValues);
|
||||
|
||||
/// @returns a set of pairs _var = _value separated by _separator.
|
||||
template <typename T>
|
||||
std::string formatVariableModel(std::vector<T> const& _variables, std::vector<std::string> const& _values, std::string const& _separator) const
|
||||
{
|
||||
solAssert(_variables.size() == _values.size(), "");
|
||||
|
||||
std::vector<std::string> assignments;
|
||||
for (unsigned i = 0; i < _values.size(); ++i)
|
||||
{
|
||||
auto var = _variables.at(i);
|
||||
if (var && var->type()->isValueType())
|
||||
assignments.emplace_back(var->name() + " = " + _values.at(i));
|
||||
}
|
||||
|
||||
return boost::algorithm::join(assignments, _separator);
|
||||
}
|
||||
|
||||
/// @returns a DAG in the dot format.
|
||||
/// Used for debugging purposes.
|
||||
@ -243,32 +260,32 @@ private:
|
||||
/// Predicates.
|
||||
//@{
|
||||
/// Genesis predicate.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_genesisPredicate;
|
||||
Predicate const* m_genesisPredicate = nullptr;
|
||||
|
||||
/// Implicit constructor predicate.
|
||||
/// Explicit constructors are handled as functions.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_implicitConstructorPredicate;
|
||||
Predicate const* m_implicitConstructorPredicate = nullptr;
|
||||
|
||||
/// Constructor summary predicate, exists after the constructor
|
||||
/// (implicit or explicit) and before the interface.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorSummaryPredicate;
|
||||
Predicate const* m_constructorSummaryPredicate = nullptr;
|
||||
|
||||
/// Artificial Interface predicate.
|
||||
/// Single entry block for all functions.
|
||||
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
|
||||
std::map<ContractDefinition const*, Predicate const*> m_interfaces;
|
||||
|
||||
/// Nondeterministic interfaces.
|
||||
/// These are used when the analyzed contract makes external calls to unknown code,
|
||||
/// which means that the analyzed contract can potentially be called
|
||||
/// nondeterministically.
|
||||
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_nondetInterfaces;
|
||||
std::map<ContractDefinition const*, Predicate const*> m_nondetInterfaces;
|
||||
|
||||
/// Artificial Error predicate.
|
||||
/// Single error block for all assertions.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
||||
Predicate const* m_errorPredicate = nullptr;
|
||||
|
||||
/// Function predicates.
|
||||
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>>> m_summaries;
|
||||
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, Predicate const*>> m_summaries;
|
||||
|
||||
smt::SymbolicIntVariable m_error{
|
||||
TypeProvider::uint256(),
|
||||
@ -329,9 +346,9 @@ private:
|
||||
bool m_unknownFunctionCallSeen = false;
|
||||
|
||||
/// Block where a loop break should go to.
|
||||
smt::SymbolicFunctionVariable const* m_breakDest = nullptr;
|
||||
Predicate const* m_breakDest;
|
||||
/// Block where a loop continue should go to.
|
||||
smt::SymbolicFunctionVariable const* m_continueDest = nullptr;
|
||||
Predicate const* m_continueDest;
|
||||
//@}
|
||||
|
||||
/// CHC solver.
|
||||
|
260
libsolidity/formal/Predicate.cpp
Normal file
260
libsolidity/formal/Predicate.cpp
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/formal/Predicate.h>
|
||||
|
||||
#include <libsolidity/formal/SMTEncoder.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <utility>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::smtutil;
|
||||
using namespace solidity::frontend;
|
||||
using namespace solidity::frontend::smt;
|
||||
|
||||
map<string, Predicate> Predicate::m_predicates;
|
||||
|
||||
Predicate const* Predicate::create(
|
||||
SortPointer _sort,
|
||||
string _name,
|
||||
EncodingContext& _context,
|
||||
ASTNode const* _node
|
||||
)
|
||||
{
|
||||
smt::SymbolicFunctionVariable predicate{_sort, move(_name), _context};
|
||||
string functorName = predicate.currentName();
|
||||
solAssert(!m_predicates.count(functorName), "");
|
||||
return &m_predicates.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(functorName),
|
||||
std::forward_as_tuple(move(predicate), _node)
|
||||
).first->second;
|
||||
}
|
||||
|
||||
Predicate::Predicate(
|
||||
smt::SymbolicFunctionVariable&& _predicate,
|
||||
ASTNode const* _node
|
||||
):
|
||||
m_predicate(move(_predicate)),
|
||||
m_node(_node)
|
||||
{
|
||||
}
|
||||
|
||||
Predicate const* Predicate::predicate(string const& _name)
|
||||
{
|
||||
return &m_predicates.at(_name);
|
||||
}
|
||||
|
||||
void Predicate::reset()
|
||||
{
|
||||
m_predicates.clear();
|
||||
}
|
||||
|
||||
smtutil::Expression Predicate::operator()(vector<smtutil::Expression> const& _args) const
|
||||
{
|
||||
return m_predicate(_args);
|
||||
}
|
||||
|
||||
smtutil::Expression Predicate::functor() const
|
||||
{
|
||||
return m_predicate.currentFunctionValue();
|
||||
}
|
||||
|
||||
smtutil::Expression Predicate::functor(unsigned _idx) const
|
||||
{
|
||||
return m_predicate.functionValueAtIndex(_idx);
|
||||
}
|
||||
|
||||
void Predicate::newFunctor()
|
||||
{
|
||||
m_predicate.increaseIndex();
|
||||
}
|
||||
|
||||
ASTNode const* Predicate::programNode() const
|
||||
{
|
||||
return m_node;
|
||||
}
|
||||
|
||||
ContractDefinition const* Predicate::programContract() const
|
||||
{
|
||||
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_node))
|
||||
if (!contract->constructor())
|
||||
return contract;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FunctionDefinition const* Predicate::programFunction() const
|
||||
{
|
||||
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_node))
|
||||
{
|
||||
if (contract->constructor())
|
||||
return contract->constructor();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (auto const* fun = dynamic_cast<FunctionDefinition const*>(m_node))
|
||||
return fun;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
optional<vector<VariableDeclaration const*>> Predicate::stateVariables() const
|
||||
{
|
||||
if (auto const* fun = programFunction())
|
||||
return SMTEncoder::stateVariablesIncludingInheritedAndPrivate(*fun);
|
||||
if (auto const* contract = programContract())
|
||||
return SMTEncoder::stateVariablesIncludingInheritedAndPrivate(*contract);
|
||||
|
||||
auto const* node = m_node;
|
||||
while (auto const* scopable = dynamic_cast<Scopable const*>(node))
|
||||
{
|
||||
node = scopable->scope();
|
||||
if (auto const* fun = dynamic_cast<FunctionDefinition const*>(node))
|
||||
return SMTEncoder::stateVariablesIncludingInheritedAndPrivate(*fun);
|
||||
}
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool Predicate::isSummary() const
|
||||
{
|
||||
return functor().name.rfind("summary", 0) == 0;
|
||||
}
|
||||
|
||||
bool Predicate::isInterface() const
|
||||
{
|
||||
return functor().name.rfind("interface", 0) == 0;
|
||||
}
|
||||
|
||||
string Predicate::formatSummaryCall(vector<string> const& _args) const
|
||||
{
|
||||
if (programContract())
|
||||
return "constructor()";
|
||||
|
||||
solAssert(isSummary(), "");
|
||||
|
||||
auto stateVars = stateVariables();
|
||||
solAssert(stateVars.has_value(), "");
|
||||
auto const* fun = programFunction();
|
||||
solAssert(fun, "");
|
||||
|
||||
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postStateVars, postInputVars, outputVars).
|
||||
/// Here we are interested in preInputVars.
|
||||
vector<string>::const_iterator first = _args.begin() + static_cast<int>(stateVars->size()) + 1;
|
||||
vector<string>::const_iterator last = first + static_cast<int>(fun->parameters().size());
|
||||
solAssert(first >= _args.begin() && first <= _args.end(), "");
|
||||
solAssert(last >= _args.begin() && last <= _args.end(), "");
|
||||
vector<string> functionArgsCex(first, last);
|
||||
vector<string> functionArgs;
|
||||
|
||||
auto const& params = fun->parameters();
|
||||
solAssert(params.size() == functionArgsCex.size(), "");
|
||||
for (unsigned i = 0; i < params.size(); ++i)
|
||||
if (params[i]->type()->isValueType())
|
||||
functionArgs.emplace_back(functionArgsCex[i]);
|
||||
else
|
||||
functionArgs.emplace_back(params[i]->name());
|
||||
|
||||
string fName = fun->isConstructor() ? "constructor" :
|
||||
fun->isFallback() ? "fallback" :
|
||||
fun->isReceive() ? "receive" :
|
||||
fun->name();
|
||||
return fName + "(" + boost::algorithm::join(functionArgs, ", ") + ")";
|
||||
|
||||
}
|
||||
|
||||
vector<string> Predicate::summaryStateValues(vector<string> const& _args) const
|
||||
{
|
||||
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postStateVars, postInputVars, outputVars).
|
||||
/// The signature of an implicit constructor summary predicate is: summary(error, postStateVars).
|
||||
/// Here we are interested in postStateVars.
|
||||
|
||||
auto stateVars = stateVariables();
|
||||
solAssert(stateVars.has_value(), "");
|
||||
|
||||
vector<string>::const_iterator stateFirst;
|
||||
vector<string>::const_iterator stateLast;
|
||||
if (auto const* function = programFunction())
|
||||
{
|
||||
stateFirst = _args.begin() + 1 + static_cast<int>(stateVars->size()) + static_cast<int>(function->parameters().size());
|
||||
stateLast = stateFirst + static_cast<int>(stateVars->size());
|
||||
}
|
||||
else if (programContract())
|
||||
{
|
||||
stateFirst = _args.begin() + 1;
|
||||
stateLast = stateFirst + static_cast<int>(stateVars->size());
|
||||
}
|
||||
else
|
||||
solAssert(false, "");
|
||||
|
||||
solAssert(stateFirst >= _args.begin() && stateFirst <= _args.end(), "");
|
||||
solAssert(stateLast >= _args.begin() && stateLast <= _args.end(), "");
|
||||
|
||||
vector<string> stateArgs(stateFirst, stateLast);
|
||||
solAssert(stateArgs.size() == stateVars->size(), "");
|
||||
return stateArgs;
|
||||
}
|
||||
|
||||
vector<string> Predicate::summaryPostInputValues(vector<string> const& _args) const
|
||||
{
|
||||
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postStateVars, postInputVars, outputVars).
|
||||
/// Here we are interested in postInputVars.
|
||||
auto const* function = programFunction();
|
||||
solAssert(function, "");
|
||||
|
||||
auto stateVars = stateVariables();
|
||||
solAssert(stateVars.has_value(), "");
|
||||
|
||||
auto const& inParams = function->parameters();
|
||||
|
||||
vector<string>::const_iterator first = _args.begin() + 1 + static_cast<int>(stateVars->size()) * 2 + static_cast<int>(inParams.size());
|
||||
vector<string>::const_iterator last = first + static_cast<int>(inParams.size());
|
||||
|
||||
solAssert(first >= _args.begin() && first <= _args.end(), "");
|
||||
solAssert(last >= _args.begin() && last <= _args.end(), "");
|
||||
|
||||
vector<string> inValues(first, last);
|
||||
solAssert(inValues.size() == inParams.size(), "");
|
||||
return inValues;
|
||||
}
|
||||
|
||||
vector<string> Predicate::summaryPostOutputValues(vector<string> const& _args) const
|
||||
{
|
||||
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postStateVars, postInputVars, outputVars).
|
||||
/// Here we are interested in outputVars.
|
||||
auto const* function = programFunction();
|
||||
solAssert(function, "");
|
||||
|
||||
auto stateVars = stateVariables();
|
||||
solAssert(stateVars.has_value(), "");
|
||||
|
||||
auto const& inParams = function->parameters();
|
||||
|
||||
vector<string>::const_iterator first = _args.begin() + 1 + static_cast<int>(stateVars->size()) * 2 + static_cast<int>(inParams.size()) * 2;
|
||||
|
||||
solAssert(first >= _args.begin() && first <= _args.end(), "");
|
||||
|
||||
vector<string> outValues(first, _args.end());
|
||||
solAssert(outValues.size() == function->returnParameters().size(), "");
|
||||
return outValues;
|
||||
}
|
120
libsolidity/formal/Predicate.h
Normal file
120
libsolidity/formal/Predicate.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/SymbolicVariables.h>
|
||||
#include <libsolidity/formal/SymbolicVariables.h>
|
||||
|
||||
#include <libsmtutil/Sorts.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
/**
|
||||
* Represents a predicate used by the CHC engine.
|
||||
*/
|
||||
class Predicate
|
||||
{
|
||||
public:
|
||||
static Predicate const* create(
|
||||
smtutil::SortPointer _sort,
|
||||
std::string _name,
|
||||
smt::EncodingContext& _context,
|
||||
ASTNode const* _node = nullptr
|
||||
);
|
||||
|
||||
Predicate(
|
||||
smt::SymbolicFunctionVariable&& _predicate,
|
||||
ASTNode const* _node = nullptr
|
||||
);
|
||||
|
||||
/// Predicate should not be copiable.
|
||||
Predicate(Predicate const&) = delete;
|
||||
Predicate& operator=(Predicate const&) = delete;
|
||||
|
||||
/// @returns the Predicate associated with _name.
|
||||
static Predicate const* predicate(std::string const& _name);
|
||||
|
||||
/// Resets all the allocated predicates.
|
||||
static void reset();
|
||||
|
||||
/// @returns a function application of the predicate over _args.
|
||||
smtutil::Expression operator()(std::vector<smtutil::Expression> const& _args) const;
|
||||
|
||||
/// @returns the function declaration of the predicate.
|
||||
smtutil::Expression functor() const;
|
||||
/// @returns the function declaration of the predicate with index _idx.
|
||||
smtutil::Expression functor(unsigned _idx) const;
|
||||
/// Increases the index of the function declaration of the predicate.
|
||||
void newFunctor();
|
||||
|
||||
/// @returns the program node this predicate represents.
|
||||
ASTNode const* programNode() const;
|
||||
|
||||
/// @returns the ContractDefinition that this predicate represents
|
||||
/// or nullptr otherwise.
|
||||
ContractDefinition const* programContract() const;
|
||||
|
||||
/// @returns the FunctionDefinition that this predicate represents
|
||||
/// or nullptr otherwise.
|
||||
FunctionDefinition const* programFunction() const;
|
||||
|
||||
/// @returns the program state variables in the scope of this predicate.
|
||||
std::optional<std::vector<VariableDeclaration const*>> stateVariables() const;
|
||||
|
||||
/// @returns true if this predicate represents a summary.
|
||||
bool isSummary() const;
|
||||
|
||||
/// @returns true if this predicate represents an interface.
|
||||
bool isInterface() const;
|
||||
|
||||
/// @returns a formatted string representing a call to this predicate
|
||||
/// with _args.
|
||||
std::string formatSummaryCall(std::vector<std::string> const& _args) const;
|
||||
|
||||
/// @returns the values of the state variables from _args at the point
|
||||
/// where this summary was reached.
|
||||
std::vector<std::string> summaryStateValues(std::vector<std::string> const& _args) const;
|
||||
|
||||
/// @returns the values of the function input variables from _args at the point
|
||||
/// where this summary was reached.
|
||||
std::vector<std::string> summaryPostInputValues(std::vector<std::string> const& _args) const;
|
||||
|
||||
/// @returns the values of the function output variables from _args at the point
|
||||
/// where this summary was reached.
|
||||
std::vector<std::string> summaryPostOutputValues(std::vector<std::string> const& _args) const;
|
||||
|
||||
private:
|
||||
/// The actual SMT expression.
|
||||
smt::SymbolicFunctionVariable m_predicate;
|
||||
|
||||
/// The ASTNode that this predicate represents.
|
||||
/// nullptr if this predicate is not associated with a specific program AST node.
|
||||
ASTNode const* m_node = nullptr;
|
||||
|
||||
/// Maps the name of the predicate to the actual Predicate.
|
||||
/// Used in counterexample generation.
|
||||
static std::map<std::string, Predicate> m_predicates;
|
||||
};
|
||||
|
||||
}
|
@ -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,28 @@ 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)
|
||||
{
|
||||
/// We need to shortcut here due to potentially unknown
|
||||
/// rational number sizes.
|
||||
if (_op.annotation().type->category() == Type::Category::RationalNumber)
|
||||
return;
|
||||
|
||||
if (TokenTraits::isBitOp(_op.getOperator()))
|
||||
return bitwiseNotOperation(_op);
|
||||
|
||||
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 +477,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 +487,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 +505,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 +581,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 +617,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_funCall.annotation().kind == FunctionCallKind::TypeConversion)
|
||||
if (functionCallKind == FunctionCallKind::TypeConversion)
|
||||
{
|
||||
visitTypeConversion(_funCall);
|
||||
return;
|
||||
@ -753,7 +777,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();
|
||||
@ -923,6 +947,15 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
|
||||
|
||||
if (_indexAccess.annotation().type->category() == Type::Category::TypeType)
|
||||
return;
|
||||
if (_indexAccess.baseExpression().annotation().type->category() == Type::Category::FixedBytes)
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
7989_error,
|
||||
_indexAccess.location(),
|
||||
"Assertion checker does not yet support index accessing fixed bytes."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<smt::SymbolicVariable> array;
|
||||
if (auto const* id = dynamic_cast<Identifier const*>(&_indexAccess.baseExpression()))
|
||||
@ -930,16 +963,6 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
|
||||
auto varDecl = identifierToVariable(*id);
|
||||
solAssert(varDecl, "");
|
||||
array = m_context.variable(*varDecl);
|
||||
|
||||
if (varDecl->type()->category() == Type::Category::FixedBytes)
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
7989_error,
|
||||
_indexAccess.location(),
|
||||
"Assertion checker does not yet support index accessing fixed bytes."
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (auto const* innerAccess = dynamic_cast<IndexAccess const*>(&_indexAccess.baseExpression()))
|
||||
{
|
||||
@ -1084,25 +1107,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))
|
||||
{
|
||||
@ -1386,20 +1406,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);
|
||||
@ -1407,18 +1414,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.
|
||||
@ -1444,9 +1459,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()))
|
||||
{
|
||||
@ -1474,15 +1487,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(), "");
|
||||
@ -1503,13 +1517,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)
|
||||
@ -1900,10 +1914,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)
|
||||
{
|
||||
@ -1949,7 +1965,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;
|
||||
@ -1969,6 +1985,20 @@ FunctionDefinition const* SMTEncoder::functionCallToDefinition(FunctionCall cons
|
||||
return funDef;
|
||||
}
|
||||
|
||||
vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
|
||||
{
|
||||
return fold(
|
||||
_contract.annotation().linearizedBaseContracts,
|
||||
vector<VariableDeclaration const*>{},
|
||||
[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
|
||||
);
|
||||
}
|
||||
|
||||
vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function)
|
||||
{
|
||||
return stateVariablesIncludingInheritedAndPrivate(dynamic_cast<ContractDefinition const&>(*_function.scope()));
|
||||
}
|
||||
|
||||
void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
|
||||
{
|
||||
FunctionDefinition const* funDef = functionCallToDefinition(_funCall);
|
||||
|
@ -54,13 +54,17 @@ 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.
|
||||
static FunctionDefinition const* functionCallToDefinition(FunctionCall const& _funCall);
|
||||
|
||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function);
|
||||
|
||||
protected:
|
||||
// TODO: Check that we do not have concurrent reads and writes to a variable,
|
||||
// because the order of expression evaluation is undefined
|
||||
@ -83,6 +87,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 +119,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);
|
||||
|
@ -60,8 +60,8 @@ void VariableUsage::endVisit(IndexAccess const& _indexAccess)
|
||||
|
||||
void VariableUsage::endVisit(FunctionCall const& _funCall)
|
||||
{
|
||||
if (m_inlineFunctionCalls)
|
||||
if (auto const& funDef = SMTEncoder::functionCallToDefinition(_funCall))
|
||||
if (m_inlineFunctionCalls(_funCall))
|
||||
if (auto funDef = SMTEncoder::functionCallToDefinition(_funCall))
|
||||
{
|
||||
solAssert(funDef, "");
|
||||
if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end())
|
||||
|
@ -36,7 +36,7 @@ public:
|
||||
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<CallableDeclaration const*> const& _outerCallstack);
|
||||
|
||||
/// Sets whether to inline function calls.
|
||||
void setFunctionInlining(bool _inlineFunction) { m_inlineFunctionCalls = _inlineFunction; }
|
||||
void setFunctionInlining(std::function<bool(FunctionCall const&)> _inlineFunctionCalls) { m_inlineFunctionCalls = _inlineFunctionCalls; }
|
||||
|
||||
private:
|
||||
void endVisit(Identifier const& _node) override;
|
||||
@ -54,7 +54,7 @@ private:
|
||||
std::vector<CallableDeclaration const*> m_callStack;
|
||||
CallableDeclaration const* m_lastCall = nullptr;
|
||||
|
||||
bool m_inlineFunctionCalls = false;
|
||||
std::function<bool(FunctionCall const&)> m_inlineFunctionCalls = [](FunctionCall const&) { return false; };
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -85,9 +85,6 @@ static int g_compilerStackCounts = 0;
|
||||
CompilerStack::CompilerStack(ReadCallback::Callback _readFile):
|
||||
m_readFile{std::move(_readFile)},
|
||||
m_enabledSMTSolvers{smtutil::SMTSolverChoice::All()},
|
||||
m_generateIR{false},
|
||||
m_generateEwasm{false},
|
||||
m_errorList{},
|
||||
m_errorReporter{m_errorList}
|
||||
{
|
||||
// Because TypeProvider is currently a singleton API, we must ensure that
|
||||
@ -518,7 +515,8 @@ bool CompilerStack::compile()
|
||||
{
|
||||
try
|
||||
{
|
||||
compileContract(*contract, otherCompilers);
|
||||
if (m_generateEvmBytecode)
|
||||
compileContract(*contract, otherCompilers);
|
||||
}
|
||||
catch (Error const& _error)
|
||||
{
|
||||
@ -824,6 +822,14 @@ string const& CompilerStack::metadata(string const& _contractName) const
|
||||
return metadata(contract(_contractName));
|
||||
}
|
||||
|
||||
bytes CompilerStack::cborMetadata(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
return createCBORMetadata(contract(_contractName));
|
||||
}
|
||||
|
||||
string const& CompilerStack::metadata(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
@ -1063,10 +1069,7 @@ void CompilerStack::compileContract(
|
||||
shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_revertStrings, m_optimiserSettings);
|
||||
compiledContract.compiler = compiler;
|
||||
|
||||
bytes cborEncodedMetadata = createCBORMetadata(
|
||||
metadata(compiledContract),
|
||||
!onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures)
|
||||
);
|
||||
bytes cborEncodedMetadata = createCBORMetadata(compiledContract);
|
||||
|
||||
try
|
||||
{
|
||||
@ -1390,18 +1393,24 @@ private:
|
||||
bytes m_data;
|
||||
};
|
||||
|
||||
bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimentalMode)
|
||||
bytes CompilerStack::createCBORMetadata(Contract const& _contract) const
|
||||
{
|
||||
bool const experimentalMode = !onlySafeExperimentalFeaturesActivated(
|
||||
_contract.contract->sourceUnit().annotation().experimentalFeatures
|
||||
);
|
||||
|
||||
string meta = metadata(_contract);
|
||||
|
||||
MetadataCBOREncoder encoder;
|
||||
|
||||
if (m_metadataHash == MetadataHash::IPFS)
|
||||
encoder.pushBytes("ipfs", util::ipfsHash(_metadata));
|
||||
encoder.pushBytes("ipfs", util::ipfsHash(meta));
|
||||
else if (m_metadataHash == MetadataHash::Bzzr1)
|
||||
encoder.pushBytes("bzzr1", util::bzzr1Hash(_metadata).asBytes());
|
||||
encoder.pushBytes("bzzr1", util::bzzr1Hash(meta).asBytes());
|
||||
else
|
||||
solAssert(m_metadataHash == MetadataHash::None, "Invalid metadata hash");
|
||||
|
||||
if (_experimentalMode)
|
||||
if (experimentalMode)
|
||||
encoder.pushBool("experimental", true);
|
||||
if (m_release)
|
||||
encoder.pushBytes("solc", VersionCompactBytes);
|
||||
|
@ -176,6 +176,9 @@ public:
|
||||
m_requestedContractNames = _contractNames;
|
||||
}
|
||||
|
||||
/// Enable EVM Bytecode generation. This is enabled by default.
|
||||
void enableEvmBytecodeGeneration(bool _enable = true) { m_generateEvmBytecode = _enable; }
|
||||
|
||||
/// Enable experimental generation of Yul IR code.
|
||||
void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; }
|
||||
|
||||
@ -314,6 +317,9 @@ public:
|
||||
/// @returns the Contract Metadata
|
||||
std::string const& metadata(std::string const& _contractName) const;
|
||||
|
||||
/// @returns the cbor-encoded metadata.
|
||||
bytes cborMetadata(std::string const& _contractName) const;
|
||||
|
||||
/// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
|
||||
Json::Value gasEstimates(std::string const& _contractName) const;
|
||||
|
||||
@ -402,7 +408,7 @@ private:
|
||||
std::string createMetadata(Contract const& _contract) const;
|
||||
|
||||
/// @returns the metadata CBOR for the given serialised metadata JSON.
|
||||
bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
|
||||
bytes createCBORMetadata(Contract const& _contract) const;
|
||||
|
||||
/// @returns the contract ABI as a JSON object.
|
||||
/// This will generate the JSON object and store it in the Contract object if it is not present yet.
|
||||
@ -437,8 +443,9 @@ private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
smtutil::SMTSolverChoice m_enabledSMTSolvers;
|
||||
std::map<std::string, std::set<std::string>> m_requestedContractNames;
|
||||
bool m_generateIR;
|
||||
bool m_generateEwasm;
|
||||
bool m_generateEvmBytecode = true;
|
||||
bool m_generateIR = false;
|
||||
bool m_generateEwasm = false;
|
||||
std::map<std::string, util::h160> m_libraries;
|
||||
/// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum
|
||||
/// "context:prefix=target"
|
||||
|
@ -257,6 +257,30 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns true if EVM bytecode was requested, i.e. we have to run the old code generator.
|
||||
bool isEvmBytecodeRequested(Json::Value const& _outputSelection)
|
||||
{
|
||||
if (!_outputSelection.isObject())
|
||||
return false;
|
||||
|
||||
static vector<string> const outputsThatRequireEvmBinaries{
|
||||
"*",
|
||||
"evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes",
|
||||
"evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences",
|
||||
"evm.deployedBytecode.immutableReferences",
|
||||
"evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap",
|
||||
"evm.bytecode.linkReferences",
|
||||
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
|
||||
};
|
||||
|
||||
for (auto const& fileRequests: _outputSelection)
|
||||
for (auto const& requests: fileRequests)
|
||||
for (auto const& output: outputsThatRequireEvmBinaries)
|
||||
if (isArtifactRequested(requests, output, false))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns true if any Ewasm code was requested. Note that as an exception, '*' does not
|
||||
/// yet match "ewasm.wast" or "ewasm"
|
||||
bool isEwasmRequested(Json::Value const& _outputSelection)
|
||||
@ -840,8 +864,8 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
compilerStack.setMetadataHash(_inputsAndSettings.metadataHash);
|
||||
compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection));
|
||||
|
||||
compilerStack.enableEvmBytecodeGeneration(isEvmBytecodeRequested(_inputsAndSettings.outputSelection));
|
||||
compilerStack.enableIRGeneration(isIRRequested(_inputsAndSettings.outputSelection));
|
||||
|
||||
compilerStack.enableEwasmGeneration(isEwasmRequested(_inputsAndSettings.outputSelection));
|
||||
|
||||
Json::Value errors = std::move(_inputsAndSettings.errors);
|
||||
|
@ -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);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user