Merge pull request #9729 from ethereum/develop

Merge develop into release for 0.7.1
This commit is contained in:
chriseth 2020-09-02 14:43:57 +02:00 committed by GitHub
commit f4a555bedc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
529 changed files with 8362 additions and 2269 deletions

View File

@ -21,8 +21,8 @@ parameters:
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:7a4d5271b5552139d9f2caefc50d42f401bf74132cf8f253e199e11c80ab42de"
ubuntu-1604-clang-ossfuzz-docker-image:
type: string
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-2
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:efaabb3c143f64171be596932c62013bcfd7f73b1fbcb832025a34dd2b6e6922"
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-3
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:6fa6914bd81abcac4b162c738e6ff05d87cefe7655e3859c7a827e5a8ec20dc7"
emscripten-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc"
@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,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(

View File

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

View File

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

View File

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

View File

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

View File

@ -1185,5 +1185,9 @@
"0.7.0": {
"bugs": [],
"released": "2020-07-28"
},
"0.7.1": {
"bugs": [],
"released": "2020-09-02"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ Layout of a Solidity Source File
Source files can contain an arbitrary number of
:ref:`contract definitions<contract_structure>`, import_ directives,
:ref:`pragma directives<pragma>` and
:ref:`struct<structs>` and :ref:`enum<enums>` definitions.
:ref:`struct<structs>`, :ref:`enum<enums>` and :ref:`function<functions>` definitions.
.. index:: ! license, spdx
@ -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``.

View File

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

View File

@ -36,7 +36,7 @@ for the purposes of NatSpec.
- For Vyper, use ``"""`` indented to the inner contents with bare
comments. See `Vyper
documentation <https://vyper.readthedocs.io/en/latest/structure-of-a-contract.html#natspec-metadata>`__.
documentation <https://vyper.readthedocs.io/en/latest/natspec.html>`__.
The following example shows a contract and a function using all available tags.
@ -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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,9 +49,7 @@ public:
// Z3 "basic resources" limit.
// This is used to make the runs more deterministic and platform/machine independent.
// The tests start failing for Z3 with less than 10000000,
// so using double that.
static int const resourceLimit = 20000000;
static int const resourceLimit = 12500000;
private:
void declareFunction(std::string const& _name, Sort const& _sort);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -774,6 +774,7 @@ public:
ASTPointer<ASTString> const& _name,
Visibility _visibility,
StateMutability _stateMutability,
bool _free,
Token _kind,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
@ -787,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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -597,6 +597,139 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
});
}
string YulUtilFunctions::overflowCheckedIntExpFunction(
IntegerType const& _type,
IntegerType const& _exponentType
)
{
solAssert(!_exponentType.isSigned(), "");
string functionName = "checked_exp_" + _type.identifier() + "_" + _exponentType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent) -> power {
base := <baseCleanupFunction>(base)
exponent := <exponentCleanupFunction>(exponent)
<?signed>
power := <exp>(base, exponent, <minValue>, <maxValue>)
<!signed>
power := <exp>(base, exponent, <maxValue>)
</signed>
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("exp", _type.isSigned() ? overflowCheckedSignedExpFunction() : overflowCheckedUnsignedExpFunction())
("maxValue", toCompactHexWithPrefix(_type.max()))
("minValue", toCompactHexWithPrefix(_type.min()))
("baseCleanupFunction", cleanupFunction(_type))
("exponentCleanupFunction", cleanupFunction(_exponentType))
.render();
});
}
string YulUtilFunctions::overflowCheckedUnsignedExpFunction()
{
string functionName = "checked_exp_unsigned";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent, max) -> power {
// This function currently cannot be inlined because of the
// "leave" statements. We have to improve the optimizer.
// Note that 0**0 == 1
if iszero(exponent) { power := 1 leave }
if iszero(base) { power := 0 leave }
power := 1
for { } gt(exponent, 1) {}
{
// overflow check for base * base
if gt(base, div(max, base)) { revert(0, 0) }
if and(exponent, 1)
{
// no check needed here because base >= power
power := mul(power, base)
}
base := mul(base, base)
exponent := <shr_1>(exponent)
}
if gt(power, div(max, base)) { revert(0, 0) }
power := mul(power, base)
}
)")
("functionName", functionName)
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::overflowCheckedSignedExpFunction()
{
string functionName = "checked_exp_signed";
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(base, exponent, min, max) -> power {
// Currently, `leave` avoids this function being inlined.
// We have to improve the optimizer.
// Note that 0**0 == 1
switch exponent
case 0 { power := 1 leave }
case 1 { power := base leave }
if iszero(base) { power := 0 leave }
power := 1
// We pull out the first iteration because it is the only one in which
// base can be negative.
// Exponent is at least 2 here.
// overflow check for base * base
switch sgt(base, 0)
case 1 { if gt(base, div(max, base)) { revert(0, 0) } }
case 0 { if slt(base, sdiv(max, base)) { revert(0, 0) } }
if and(exponent, 1)
{
power := base
}
base := mul(base, base)
exponent := <shr_1>(exponent)
// Below this point, base is always positive.
for { } gt(exponent, 1) {}
{
// overflow check for base * base
if gt(base, div(max, base)) { revert(0, 0) }
if and(exponent, 1)
{
// No checks for power := mul(power, base) needed, because the check
// for base * base above is sufficient, since:
// |power| <= base (proof by induction) and thus:
// |power * base| <= base * base <= max <= |min|
power := mul(power, base)
}
base := mul(base, base)
exponent := <shr_1>(exponent)
}
if and(sgt(power, 0), gt(power, div(max, base))) { revert(0, 0) }
if and(slt(power, 0), slt(power, sdiv(min, base))) { revert(0, 0) }
power := mul(power, base)
}
)")
("functionName", functionName)
("shr_1", shiftRightFunction(1))
.render();
});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
string functionName = "extract_byte_array_length";
@ -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)

View File

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

View File

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

View File

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

View File

@ -75,18 +75,30 @@ struct CopyTranslate: public yul::ASTCopier
{
solAssert(reference.isOffset != reference.isSlot, "");
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(*varDecl);
string value;
if (varDecl->isStateVariable())
value =
reference.isSlot ?
m_context.storageLocationOfStateVariable(*varDecl).first.str() :
to_string(m_context.storageLocationOfStateVariable(*varDecl).second);
else
{
solAssert(varDecl->isLocalVariable(), "");
if (reference.isSlot)
value = IRVariable{*varDecl}.part("slot").name();
else if (varDecl->type()->isValueType())
value = IRVariable{*varDecl}.part("offset").name();
else
{
solAssert(!IRVariable{*varDecl}.hasPart("offset"), "");
value = "0";
}
}
string const value = reference.isSlot ?
slot_offset.first.str() :
to_string(slot_offset.second);
return yul::Literal{
_identifier.location,
yul::LiteralKind::Number,
yul::YulString{value},
{}
};
if (isdigit(value.front()))
return yul::Literal{_identifier.location, yul::LiteralKind::Number, yul::YulString{value}, {}};
else
return yul::Identifier{_identifier.location, yul::YulString{value}};
}
}
return ASTCopier::operator()(_identifier);
@ -152,8 +164,8 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
_varDecl.immutable() ?
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first),
m_context.storageLocationOfVariable(_varDecl).second
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
m_context.storageLocationOfStateVariable(_varDecl).second
}},
*_varDecl.value()
);
@ -277,6 +289,7 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
solAssert(rightIntermediateType->isValueType(), "Compound operators only available for value types.");
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
solAssert(binaryOperator != Token::Exp, "");
if (TokenTraits::isShiftOp(binaryOperator))
{
solAssert(type(_assignment) == leftIntermediate.type(), "");
@ -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 <<
(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -420,8 +420,11 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
_tuple.location(),
"Assertion checker does not yet implement inline arrays."
);
else if (_tuple.annotation().type->category() == Type::Category::Tuple)
else if (_tuple.components().size() == 1)
defineExpr(_tuple, expr(*_tuple.components().front()));
else
{
solAssert(_tuple.annotation().type->category() == Type::Category::Tuple, "");
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
@ -445,28 +448,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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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