Merge pull request #8616 from ethereum/develop

Merge develop into release for 0.6.5
This commit is contained in:
chriseth 2020-04-06 15:03:00 +02:00 committed by GitHub
commit f956cc8990
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1144 changed files with 20199 additions and 10287 deletions

View File

@ -122,6 +122,10 @@ defaults:
name: command line tests
command: ./test/cmdlineTests.sh
- run_docs_pragma_min_version: &run_docs_pragma_min_version
name: docs pragma version check
command: ./scripts/docs_version_pragma_check.sh
- test_ubuntu1604_clang: &test_ubuntu1604_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >>
@ -303,6 +307,18 @@ jobs:
name: Linting Python Scripts
command: ./scripts/pylint_all.py
chk_antlr_grammar:
docker:
- image: buildpack-deps:eoan
steps:
- checkout
- run:
name: Install Java
command: apt -q update && apt install -y openjdk-14-jdk
- run:
name: Run tests
command: ./scripts/test_antlr_grammar.sh
chk_buglist:
docker:
- image: circleci/node
@ -335,6 +351,15 @@ jobs:
pip install --user z3-solver
- run: *run_proofs
chk_docs_pragma_min_version:
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >>
environment:
TERM: xterm
steps:
- checkout
- run: *run_docs_pragma_min_version
b_ubu_clang: &build_ubuntu1904_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.ubuntu-1904-clang-docker-image-rev >>
@ -768,6 +793,8 @@ workflows:
- chk_buglist: *workflow_trigger_on_tags
- chk_proofs: *workflow_trigger_on_tags
- chk_pylint: *workflow_trigger_on_tags
- chk_antlr_grammar: *workflow_trigger_on_tags
- chk_docs_pragma_min_version: *workflow_trigger_on_tags
# build-only
- b_docs: *workflow_trigger_on_tags

View File

@ -6,26 +6,35 @@
# Note that clang-format cannot express the style that closing parentheses
# behave similar to closing curly braces in a multi-line setting in that
# they have to be on a line of their own at the same indentation level
# as the opening part.
# as the opening part (aka "dangling parenthesis", see https://reviews.llvm.org/D33029).
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignEscapedNewlinesLeft: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Allman
ColumnLimit: 120
ContinuationIndentWidth: 4
FixNamespaceComments: false
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 2
PenaltyBreakBeforeFirstCallParameter: 2000
PointerAlignment: Left
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
TabWidth: 4
UseTab: ForIndentation
UseTab: Always
# Local Variables:
# mode: yaml

View File

@ -1,21 +0,0 @@
---
name: General Feedback
about: Any general feedback (neither feature request nor bug reports)
---
<!--## Prerequisites
- First, many thanks for taking part in the community. We really appreciate that.
- Read the [contributing guidelines](http://solidity.readthedocs.io/en/latest/contributing.html).
- Support questions are better asked in one of the following locations:
- [Solidity chat](https://gitter.im/ethereum/solidity)
- [Stack Overflow](https://ethereum.stackexchange.com/)
- Ensure the issue isn't already reported.
*Delete the above section and the instructions in the sections below before submitting*
-->
## Description
<!--
Please describe the purpose of your ticket.
-->

View File

@ -1,22 +0,0 @@
<!--### Your checklist for this pull request
Please review the [guidelines for contributing](http://solidity.readthedocs.io/en/latest/contributing.html) to this repository.
Please also note that this project is released with a [Contributor Code of Conduct](CONDUCT.md). By participating in this project you agree to abide by its terms.
-->
### Description
<!--
Please explain the changes you made here.
Thank you for your help!
-->
### Checklist
- [ ] Code compiles correctly
- [ ] All tests are passing
- [ ] New tests have been created which fail without the change (if possible)
- [ ] README / documentation was extended, if necessary
- [ ] Changelog entry (if change is visible to the user)
- [ ] Used meaningful commit messages

View File

@ -10,7 +10,9 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.6.4")
set(PROJECT_VERSION "0.6.5")
# OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
include(TestBigEndian)

View File

@ -141,7 +141,7 @@ struct MeanSigma
double const d = 0;
int i = 0;
int j = 0;
char* s;
char* s = nullptr;
MeanAndSigma ms meanAndSigma(std::vector<float> const& _v, Accuracy _a);
Derived* x = dynamic_cast<Derived*>(base);
for (auto i = x->begin(); i != x->end(); ++i) {}

View File

@ -1,3 +1,28 @@
### 0.6.5 (2020-04-06)
Important Bugfixes:
* Code Generator: Restrict the length of dynamic memory arrays to 64 bits during creation at runtime fixing a possible overflow.
Language Features:
* Allow local storage variables to be declared without initialization, as long as they are assigned before they are accessed.
* State variables can be marked ``immutable`` which causes them to be read-only, but assignable in the constructor. The value will be stored directly in the code.
Compiler Features:
* Commandline Interface: Enable output of storage layout with `--storage-layout`.
* Metadata: Added support for IPFS hashes of large files that need to be split in multiple chunks.
Bugfixes:
* Inheritance: Allow public state variables to override functions with dynamic memory types in their return values.
* Inline Assembly: Fix internal error when accessing invalid constant variables.
* Inline Assembly: Fix internal error when accessing functions.
* JSON AST: Always add pointer suffix for memory reference types.
* Reference Resolver: Fix internal error when accessing invalid struct members.
* Type Checker: Fix internal errors when assigning nested tuples.
### 0.6.4 (2020-03-10)
Language Features:
@ -18,7 +43,6 @@ Bugfixes:
* SMTChecker: Fix internal errors when analysing tuples.
* Yul AST Import: correctly import blocks as statements, switch statements and string literals.
### 0.6.3 (2020-02-18)
Language Features:
@ -32,6 +56,7 @@ Compiler Features:
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
* SMTChecker: CHC support to internal function calls.
Bugfixes:
@ -120,6 +145,12 @@ Compiler Features:
* ABIEncoderV2: Do not warn about enabled ABIEncoderV2 anymore (the pragma is still needed, though).
### 0.5.17 (2020-03-17)
Bugfixes:
* Type Checker: Disallow overriding of private functions.
### 0.5.16 (2020-01-02)
Backported Bugfixes:

View File

@ -12,6 +12,7 @@ Solidity is a statically typed, contract-oriented, high-level language for imple
- [Development](#development)
- [Maintainers](#maintainers)
- [License](#license)
- [Security](#security)
## Background
@ -75,3 +76,7 @@ releases [in the projects section](https://github.com/ethereum/solidity/projects
Solidity is licensed under [GNU General Public License v3.0](LICENSE.txt).
Some third-party code has its [own licensing terms](cmake/templates/license.h.in).
## Security
The security policy may be [found here](SECURITY.md).

52
SECURITY.md Normal file
View File

@ -0,0 +1,52 @@
# Security Policy
The Solidity team and community take all security bugs in Solidity seriously.
We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
## Scope
Bugs in the Solidity repository are in scope.
Bugs in third-party dependencies e.g., jsoncpp, boost etc. are not in scope unless they result in a Solidity specific bug.
Only bugs that have a demonstrable security impact on smart contracts are in scope.
For example, a Solidity program whose optimization is incorrect (e.g., leads to an incorrect output) qualifies as a security bug.
Please note that the [rules][2] of the [Ethereum bounty program][1] have precedence over this security policy.
## Supported Versions
As a general rule, only the latest release gets security updates.
Exceptions may be made when the current breaking release is relatively new, e.g. less than three months old.
If you are reporting a bug, please state clearly the Solidity version(s) it affects.
Example 1: Assuming the current release is `0.6.3` and a security bug has been found in it that affects both `0.5.x` and `0.6.x` trees, we may not only patch `0.6.3` (the bug-fix release numbered `0.6.4`) but `0.5.x` as well (the bug-fix release numbered `0.5.(x+1)`).
Example 2: Assuming the current release is `0.6.25` and a security bug has been found in it, we may only patch `0.6.25` (in the bug-fix release numbered `0.6.26`) even if the bug affects a previous tree such as `0.5.x`.
## Reporting a Vulnerability
To report a vulnerability, please follow the instructions stated in the [Ethereum bounty program][1].
In the bug report, please include all details necessary to reproduce the vulnerability such as:
- Input program that triggers the bug
- Compiler version affected
- Target EVM version
- Framework/IDE if applicable
- EVM execution environment/client if applicable
- Operating system
Please include steps to reproduce the bug you have found in as much detail as possible.
Once we have received your bug report, we will try to reproduce it and provide a more detailed response.
Once the reported bug has been successfully reproduced, the Solidity team will work on a fix.
The Solidity team maintains the following JSON-formatted lists of patched security vulnerabilities:
- [Summary of known security vulnerabilities][3]
- [List of security vulnerabilities affecting a specific version of the compiler][4].
[1]: https://bounty.ethereum.org/
[2]: https://bounty.ethereum.org/#rules
[3]: https://solidity.readthedocs.io/en/develop/bugs.html
[4]: https://github.com/ethereum/solidity/blob/develop/docs/bugs_by_version.json

View File

@ -26,6 +26,17 @@ eth_add_cxx_compiler_flag_if_supported(-Wimplicit-fallthrough)
# Prevent the path of the source directory from ending up in the binary via __FILE__ macros.
eth_add_cxx_compiler_flag_if_supported("-fmacro-prefix-map=${CMAKE_SOURCE_DIR}=/solidity")
# -Wpessimizing-move warns when a call to std::move would prevent copy elision
# if the argument was not wrapped in a call. This happens when moving a local
# variable in a return statement when the variable is the same type as the
# return type or using a move to create a new object from a temporary object.
eth_add_cxx_compiler_flag_if_supported(-Wpessimizing-move)
# -Wredundant-move warns when an implicit move would already be made, so the
# std::move call is not needed, such as when moving a local variable in a return
# that is different from the return type.
eth_add_cxx_compiler_flag_if_supported(-Wredundant-move)
if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))
# Enables all the warnings about constructions that some users consider questionable,
# and that are easy to avoid. Also enable some extra warning flags that are not

View File

@ -292,8 +292,9 @@ Consider you have the following pre-0.5.0 contract already deployed:
::
// This will not compile with the current version of the compiler
pragma solidity ^0.4.25;
// This will report a warning until version 0.4.25 of the compiler
// This will not compile after 0.5.0
contract OldContract {
function someOldFunction(uint8 a) {
//...
@ -369,8 +370,8 @@ Old version:
::
// This will not compile
pragma solidity ^0.4.25;
// This will not compile after 0.5.0
contract OtherContract {
uint x;
@ -396,7 +397,7 @@ Old version:
// Throw is fine in this version.
if (x > 100)
throw;
bytes b = new bytes(x);
bytes memory b = new bytes(x);
y = -3 >> 1;
// y == -1 (wrong, should be -2)
do {
@ -431,14 +432,15 @@ New version:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.5.99;
// This will not compile after 0.6.0
contract OtherContract {
uint x;
function f(uint y) external {
x = y;
}
receive() payable external {}
function() payable external {}
}
contract New {

482
docs/Solidity.g4 Normal file
View File

@ -0,0 +1,482 @@
// 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 pragmaValue ';' ;
pragmaName
: identifier ;
pragmaValue
: version | expression ;
version
: versionConstraint versionConstraint? ;
versionConstraint
: versionOperator? VersionLiteral ;
versionOperator
: '^' | '~' | '>=' | '>' | '<' | '<=' | '=' ;
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' '(' (elementaryTypeName | userDefinedTypeName) '=>' typeName ')' ;
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
| 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 ;
assemblyCall
: ( 'return' | 'address' | 'byte' | identifier ) ( '(' assemblyExpression? ( ',' assemblyExpression )* ')' )? ;
assemblyLocalDefinition
: 'let' assemblyIdentifierList ( ':=' assemblyExpression )? ;
assemblyAssignment
: assemblyIdentifierList ':=' assemblyExpression ;
assemblyIdentifierList
: 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 ;
numberLiteral
: (DecimalNumber | HexNumber) NumberUnit? ;
identifier
: ('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' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years' ;
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* '\'' ;
fragment
DoubleQuotedStringCharacter
: ~["\r\n\\] | ('\\' .) ;
fragment
SingleQuotedStringCharacter
: ~['\r\n\\] | ('\\' .) ;
VersionLiteral
: [0-9]+ '.' [0-9]+ ('.' [0-9]+)? ;
WS
: [ \t\r\n\u000C]+ -> skip ;
COMMENT
: '/*' .*? '*/' -> channel(HIDDEN) ;
LINE_COMMENT
: '//' ~[\r\n]* -> channel(HIDDEN) ;

View File

@ -234,7 +234,6 @@ Given the contract:
pragma solidity >=0.4.16 <0.7.0;
contract Foo {
function bar(bytes3[2] memory) public pure {}
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
@ -583,12 +582,11 @@ As an example, the code
pragma solidity >=0.4.19 <0.7.0;
pragma experimental ABIEncoderV2;
contract Test {
struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public {}
function g() public returns (S memory s, T memory t, uint a) {}
function f(S memory, T memory, uint) public pure {}
function g() public pure returns (S memory, T memory, uint) {}
}
would result in the JSON:

View File

@ -41,7 +41,7 @@ without a compiler change.
.. code::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.16 <0.7.0;
library GetCode {
function at(address _addr) public view returns (bytes memory o_code) {
@ -136,7 +136,7 @@ Local Solidity variables are available for assignments, for example:
.. code::
pragma solidity >=0.4.11 <0.7.0;
pragma solidity >=0.4.16 <0.7.0;
contract C {
uint b;

View File

@ -1,4 +1,12 @@
[
{
"name": "MemoryArrayCreationOverflow",
"summary": "The creation of very large memory arrays can result in overlapping memory regions and thus memory corruption.",
"description": "No runtime overflow checks were performed for the length of memory arrays during creation. In cases for which the memory size of an array in bytes, i.e. the array length times 32, is larger than 2^256-1, the memory allocation will overflow, potentially resulting in overlapping memory areas. The length of the array is still stored correctly, so copying or iterating over such an array will result in out-of-gas.",
"introduced": "0.2.0",
"fixed": "0.6.5",
"severity": "low"
},
{
"name": "YulOptimizerRedundantAssignmentBreakContinue",
"summary": "The Yul optimizer can remove essential assignments to variables declared inside for loops when Yul's continue or break statement is used. You are unlikely to be affected if you do not use inline assembly with for loops and continue and break statements.",
@ -10,6 +18,14 @@
"yulOptimizer": true
}
},
{
"name": "privateCanBeOverridden",
"summary": "Private methods can be overridden by inheriting contracts.",
"description": "While private methods of base contracts are not visible and cannot be called directly from the derived contract, it is still possible to declare a function of the same name and type and thus change the behaviour of the base contract's function.",
"introduced": "0.3.0",
"fixed": "0.5.17",
"severity": "low"
},
{
"name": "YulOptimizerRedundantAssignmentBreakContinue0.5",
"summary": "The Yul optimizer can remove essential assignments to variables declared inside for loops when Yul's continue or break statement is used. You are unlikely to be affected if you do not use inline assembly with for loops and continue and break statements.",

View File

@ -151,6 +151,7 @@
},
"0.2.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector",
@ -171,6 +172,7 @@
},
"0.2.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector",
@ -191,6 +193,7 @@
},
"0.2.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector",
@ -211,6 +214,8 @@
},
"0.3.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -232,6 +237,8 @@
},
"0.3.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -252,6 +259,8 @@
},
"0.3.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -272,6 +281,8 @@
},
"0.3.3": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -291,6 +302,8 @@
},
"0.3.4": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -310,6 +323,8 @@
},
"0.3.5": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -329,6 +344,8 @@
},
"0.3.6": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -346,6 +363,8 @@
},
"0.4.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -363,6 +382,8 @@
},
"0.4.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -380,6 +401,8 @@
},
"0.4.10": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -395,6 +418,8 @@
},
"0.4.11": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -409,6 +434,8 @@
},
"0.4.12": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -422,6 +449,8 @@
},
"0.4.13": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -435,6 +464,8 @@
},
"0.4.14": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -447,6 +478,8 @@
},
"0.4.15": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -458,6 +491,8 @@
},
"0.4.16": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -471,6 +506,8 @@
},
"0.4.17": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -485,6 +522,8 @@
},
"0.4.18": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -498,6 +537,8 @@
},
"0.4.19": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -512,6 +553,8 @@
},
"0.4.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -528,6 +571,8 @@
},
"0.4.20": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -542,6 +587,8 @@
},
"0.4.21": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -556,6 +603,8 @@
},
"0.4.22": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -570,6 +619,8 @@
},
"0.4.23": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -583,6 +634,8 @@
},
"0.4.24": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -596,6 +649,8 @@
},
"0.4.25": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -607,6 +662,8 @@
},
"0.4.26": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2"
@ -615,6 +672,8 @@
},
"0.4.3": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -630,6 +689,8 @@
},
"0.4.4": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
@ -644,6 +705,8 @@
},
"0.4.5": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -660,6 +723,8 @@
},
"0.4.6": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -675,6 +740,8 @@
},
"0.4.7": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -690,6 +757,8 @@
},
"0.4.8": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -705,6 +774,8 @@
},
"0.4.9": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -720,6 +791,8 @@
},
"0.5.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -731,6 +804,8 @@
},
"0.5.1": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -742,6 +817,8 @@
},
"0.5.10": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers"
],
@ -749,24 +826,32 @@
},
"0.5.11": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
"released": "2019-08-12"
},
"0.5.12": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
"released": "2019-10-01"
},
"0.5.13": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
"released": "2019-11-14"
},
"0.5.14": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2LoopYulOptimizer"
],
@ -774,16 +859,29 @@
},
"0.5.15": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5"
],
"released": "2019-12-17"
},
"0.5.16": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden"
],
"released": "2020-01-02"
},
"0.5.17": {
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-03-17"
},
"0.5.2": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -795,6 +893,8 @@
},
"0.5.3": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -806,6 +906,8 @@
},
"0.5.4": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -817,6 +919,8 @@
},
"0.5.5": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
@ -830,6 +934,8 @@
},
"0.5.6": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -843,6 +949,8 @@
},
"0.5.7": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
@ -854,6 +962,8 @@
},
"0.5.8": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
"SignedArrayStorageCopy",
@ -864,6 +974,8 @@
},
"0.5.9": {
"bugs": [
"MemoryArrayCreationOverflow",
"privateCanBeOverridden",
"YulOptimizerRedundantAssignmentBreakContinue0.5",
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
"SignedArrayStorageCopy",
@ -873,24 +985,37 @@
},
"0.6.0": {
"bugs": [
"MemoryArrayCreationOverflow",
"YulOptimizerRedundantAssignmentBreakContinue"
],
"released": "2019-12-17"
},
"0.6.1": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-01-02"
},
"0.6.2": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-01-27"
},
"0.6.3": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-02-18"
},
"0.6.4": {
"bugs": [],
"bugs": [
"MemoryArrayCreationOverflow"
],
"released": "2020-03-10"
},
"0.6.5": {
"bugs": [],
"released": "2020-04-06"
}
}

View File

@ -13,7 +13,7 @@ This can be done by using the ``abstract`` keyword as shown in the following exa
defined as abstract, because the function ``utterance()`` was defined, but no implementation was
provided (no implementation body ``{ }`` was given).::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
abstract contract Feline {
function utterance() public virtual returns (bytes32);

View File

@ -1,11 +1,49 @@
.. index:: ! constant
************************
Constant State Variables
************************
**************************************
Constant and Immutable State Variables
**************************************
State variables can be declared as ``constant``. In this case, they have to be
assigned from an expression which is a constant at compile time. Any expression
State variables can be declared as ``constant`` or ``immutable``.
In both cases, the variables cannot be modified after the contract has been constructed.
For ``constant`` variables, the value has to be fixed at compile-time, while
for ``immutable``, it can still be assigned at construction time.
The compiler does not reserve a storage slot for these variables, and every occurrence is
replaced by the respective value.
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>`_.
::
pragma solidity >0.6.4 <0.7.0;
contract C {
uint constant X = 32**22 + 8;
string constant TEXT = "abc";
bytes32 constant MY_HASH = keccak256("abc");
uint immutable decimals;
uint immutable maxBalance;
address immutable owner = msg.sender;
constructor(uint _decimals, address _reference) public {
decimals = _decimals;
// Assignments to immutables can even access the environment.
maxBalance = _reference.balance;
}
function isBalanceTooHigh(address _other) public view returns (bool) {
return _other.balance > maxBalance;
}
}
Constant
========
For ``constant`` variables, the value has to be a constant at compile time and it has to be
assigned where the variable is declared. Any expression
that accesses storage, blockchain data (e.g. ``now``, ``address(this).balance`` or
``block.number``) or
execution data (``msg.value`` or ``gasleft()``) or makes calls to external contracts is disallowed. Expressions
@ -18,18 +56,17 @@ The reason behind allowing side-effects on the memory allocator is that it
should be possible to construct complex objects like e.g. lookup-tables.
This feature is not yet fully usable.
The compiler does not reserve a storage slot for these variables, and every occurrence is
replaced by the respective constant expression (which might be computed to a single value by the optimizer).
Immutable
=========
Not all types for constants are implemented at this time. The only supported types are
value types and strings.
Variables declared as ``immutable`` are a bit less restricted than those
declared as ``constant``: Immutable variables can be assigned an arbitrary
value in the constructor of the contract or at the point of their declaration.
They cannot be read during construction time and can only be assigned once.
::
pragma solidity >=0.4.0 <0.7.0;
contract C {
uint constant X = 32**22 + 8;
string constant TEXT = "abc";
bytes32 constant MY_HASH = keccak256("abc");
}
The contract creation code generated by the compiler will modify the
contract's runtime code before it is returned by replacing all references
to immutables by the values assigned to the them. This is important if
you are comparing the
runtime code generated by the compiler with the one actually stored in the
blockchain.

View File

@ -335,7 +335,7 @@ operations as long as there is enough gas passed on to it.
::
pragma solidity >0.6.1 <0.7.0;
pragma solidity >=0.6.2 <0.7.0;
contract Test {
// This function is called for all messages sent to

View File

@ -154,7 +154,7 @@ A call to ``Final.destroy()`` will call ``Base2.destroy`` because we specify it
explicitly in the final override, but this function will bypass
``Base1.destroy``. The way around this is to use ``super``::
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract owned {
constructor() public { owner = msg.sender; }
@ -204,7 +204,7 @@ use the ``override`` keyword in the function header as shown in this example:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract Base
{
@ -227,7 +227,7 @@ bases, it has to explicitly override it:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract Base1
{
@ -253,7 +253,7 @@ that already overrides all other functions.
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract A { function f() public pure{} }
contract B is A {}
@ -293,7 +293,7 @@ of the variable:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract A
{
@ -324,7 +324,7 @@ and the ``override`` keyword must be used in the overriding modifier:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract Base
{
@ -342,7 +342,7 @@ explicitly:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract Base1
{
@ -498,7 +498,7 @@ One area where inheritance linearization is especially important and perhaps not
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
contract Base1 {
constructor() public {}

View File

@ -22,7 +22,7 @@ Interfaces are denoted by their own keyword:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.2 <0.7.0;
interface Token {
enum TokenType { Fungible, NonFungible }
@ -42,7 +42,7 @@ inheritance.
::
pragma solidity >0.6.1 <0.7.0;
pragma solidity >=0.6.2 <0.7.0;
interface ParentA {
function test() external returns (uint256);

View File

@ -47,12 +47,14 @@ more advanced example to implement a set).
::
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }
struct Data {
mapping(uint => bool) flags;
}
library Set {
// Note that the first parameter is of type "storage
@ -123,7 +125,7 @@ custom types without the overhead of external function calls:
::
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
struct bigint {
uint[] limbs;
@ -237,7 +239,7 @@ Its value can be obtained from Solidity using the ``.selector`` member as follow
::
pragma solidity >0.5.13 <0.7.0;
pragma solidity >=0.5.14 <0.7.0;
library L {
function f(uint256) external {}

View File

@ -29,7 +29,7 @@ may only be used inside a contract, not inside any of its functions.
Let us rewrite the set example from the
:ref:`libraries` in this way::
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
// This is the same code as before, just without comments

View File

@ -68,7 +68,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.16 <0.7.0;
contract C {
uint private data;
@ -112,7 +112,7 @@ when they are declared.
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.16 <0.7.0;
contract C {
uint public data = 42;
@ -151,7 +151,7 @@ to write a function, for example:
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.16 <0.7.0;
contract arrayExample {
// public state variable

View File

@ -41,7 +41,7 @@ Internal Function Calls
Functions of the current contract can be called directly ("internally"), also recursively, as seen in
this nonsensical example::
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
contract C {
function g(uint a) public pure returns (uint ret) { return a + f(); }
@ -82,7 +82,7 @@ to the total balance of that contract:
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.6.2 <0.7.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
@ -160,7 +160,7 @@ Those parameters will still be present on the stack, but they are inaccessible.
::
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
contract C {
// omitted name for parameter
@ -183,7 +183,7 @@ is compiled so recursive creation-dependencies are not possible.
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.2 <0.7.0;
contract D {
uint public x;
@ -238,7 +238,7 @@ which only need to be created if there is a dispute.
::
pragma solidity >0.6.1 <0.7.0;
pragma solidity >=0.6.2 <0.7.0;
contract D {
uint public x;
@ -307,7 +307,7 @@ groupings of expressions.
::
pragma solidity >0.4.23 <0.7.0;
pragma solidity >=0.5.0 <0.7.0;
contract C {
uint index;
@ -352,7 +352,7 @@ because only a reference and not a copy is passed.
::
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
contract C {
uint[20] x;

View File

@ -24,7 +24,7 @@ to receive their money - contracts cannot activate themselves.
::
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.5.0 <0.7.0;
contract SimpleAuction {
// Parameters of the auction. Times are either
@ -184,7 +184,7 @@ invalid bids.
::
pragma solidity >0.4.23 <0.7.0;
pragma solidity >=0.5.0 <0.7.0;
contract BlindAuction {
struct Bid {

View File

@ -338,7 +338,7 @@ The full contract
::
pragma solidity >=0.4.24 <0.7.0;
pragma solidity >=0.5.0 <0.7.0;
contract SimplePaymentChannel {
address payable public sender; // The account sending payments.

View File

@ -19,7 +19,7 @@ and the sum of all balances is an invariant across the lifetime of the contract.
::
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.5.0 <0.7.0;
library Balances {
function move(mapping(address => uint256) storage balances, address from, address to, uint amount) internal {

View File

@ -25,7 +25,7 @@ you can use state machine-like constructs inside a contract.
::
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.5.0 <0.7.0;
contract Purchase {
uint public value;

View File

@ -1,193 +0,0 @@
SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)*
// Pragma actually parses anything up to the trailing ';' to be fully forward-compatible.
PragmaDirective = 'pragma' Identifier ([^;]+) ';'
ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = 'abstract'? ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
ContractPart = StateVariableDeclaration | UsingForDeclaration
| StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' | OverrideSpecifier )* Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
ModifierDefinition = 'modifier' Identifier ParameterList? ( 'virtual' | OverrideSpecifier )* Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = FunctionDescriptor ParameterList
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' | 'virtual' | OverrideSpecifier )*
( 'returns' ParameterList )? ( ';' | Block )
FunctionDescriptor = 'function' Identifier | 'constructor' | 'fallback' | 'receive'
OverrideSpecifier = 'override' ( '(' UserDefinedTypeName (',' UserDefinedTypeName)* ')' )?
EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';'
EnumValue = Identifier
EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}'
ParameterList = '(' ( Parameter (',' Parameter)* )? ')'
Parameter = TypeName StorageLocation? Identifier?
EventParameterList = '(' ( EventParameter (',' EventParameter )* )? ')'
EventParameter = TypeName 'indexed'? Identifier?
FunctionTypeParameterList = '(' ( FunctionTypeParameter (',' FunctionTypeParameter )* )? ')'
FunctionTypeParameter = TypeName StorageLocation?
// semantic restriction: mappings and structs (recursively) containing mappings
// are not allowed in argument lists
VariableDeclaration = TypeName StorageLocation? Identifier
TypeName = ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
| ( 'address' 'payable' )
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ( ElementaryTypeName | UserDefinedTypeName ) '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
( 'returns' FunctionTypeParameterList )?
StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+
CatchClause = 'catch' ( Identifier? ParameterList )? Block
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
InlineAssemblyStatement = 'assembly' StringLiteral? AssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
EmitStatement = 'emit' FunctionCall
VariableDefinition = (VariableDeclaration | '(' VariableDeclaration? (',' VariableDeclaration? )* ')' ) ( '=' Expression )?
// Precedence by order (see github.com/ethereum/solidity/pull/732)
Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| IndexRangeAccess
| MemberAccess
| FunctionCall
| Expression '{' NameValueList '}'
| '(' Expression ')'
| ('!' | '~' | '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
| PrimaryExpression
PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
| TupleExpression
| Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
IndexRangeAccess = Expression '[' Expression? ':' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+ ( '.' [0-9]* )? ( [eE] [0-9]+ )?
TupleExpression = '(' ( Expression? ( ',' Expression? )* )? ')'
| '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | Int | Uint | 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 = '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]+ )
AssemblyBlock = '{' AssemblyStatement* '}'
AssemblyStatement = AssemblyBlock
| AssemblyFunctionDefinition
| AssemblyVariableDeclaration
| AssemblyAssignment
| AssemblyIf
| AssemblyExpression
| AssemblySwitch
| AssemblyForLoop
| AssemblyBreakContinue
| AssemblyLeave
AssemblyFunctionDefinition =
'function' Identifier '(' AssemblyIdentifierList? ')'
( '->' AssemblyIdentifierList )? AssemblyBlock
AssemblyVariableDeclaration = 'let' AssemblyIdentifierList ( ':=' AssemblyExpression )?
AssemblyAssignment = AssemblyIdentifierList ':=' AssemblyExpression
AssemblyExpression = AssemblyFunctionCall | Identifier | Literal
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression ( AssemblyCase+ AssemblyDefault? | AssemblyDefault )
AssemblyCase = 'case' Literal AssemblyBlock
AssemblyDefault = 'default' AssemblyBlock
AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock
AssemblyBreakContinue = 'break' | 'continue'
AssemblyLeave = 'leave'
AssemblyFunctionCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
AssemblyIdentifierList = Identifier ( ',' Identifier )*

View File

@ -17,7 +17,7 @@ Storage Example
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.16 <0.7.0;
contract SimpleStorage {
uint storedData;

View File

@ -284,7 +284,7 @@ for the two function parameters and two return variables.
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.21 <0.7.0;
/** @title Shape calculator. */
contract ShapeCalculator {

View File

@ -788,6 +788,7 @@ Modifiers
- ``view`` for functions: Disallows modification of state.
- ``payable`` for functions: Allows them to receive Ether together with a call.
- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code.
- ``anonymous`` for events: Does not store event signature as topic.
- ``indexed`` for event parameters: Stores the parameter as topic.
- ``virtual`` for functions and modifiers: Allows the function's or modifier's
@ -809,5 +810,5 @@ These keywords are reserved in Solidity. They might become part of the syntax in
Language Grammar
================
.. literalinclude:: grammar.txt
:language: none
.. literalinclude:: Solidity.g4
:language: antlr

View File

@ -81,7 +81,7 @@ as it uses ``call`` which forwards all remaining gas by default:
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.6.2 <0.7.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
@ -277,7 +277,7 @@ field of a ``struct`` that is the base type of a dynamic storage array. The
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract Map {
mapping (uint => uint)[] array;
@ -547,6 +547,7 @@ not mean loss of proving power.
pragma solidity >=0.5.0;
pragma experimental SMTChecker;
// This may report a warning if no SMT solver available.
contract Recover
{
@ -601,6 +602,7 @@ types.
pragma solidity >=0.5.0;
pragma experimental SMTChecker;
// This will report a warning
contract Aliasing
{
uint[] array;

View File

@ -109,7 +109,7 @@ Yes::
No::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
abstract contract A {
function spam() virtual pure public;
@ -326,7 +326,7 @@ Yes::
No::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity ^0.6.0;
contract A {
@ -745,7 +745,7 @@ manner as modifiers if the function declaration is long or hard to read.
Yes::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
// Base contracts just to make this compile
contract B {
@ -777,7 +777,7 @@ Yes::
No::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
// Base contracts just to make this compile
@ -1000,7 +1000,7 @@ As shown in the example below, if the contract name is `Congress` and the librar
Yes::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
// Owned.sol
@ -1034,7 +1034,7 @@ and in ``Congress.sol``::
No::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
// owned.sol
@ -1138,7 +1138,7 @@ multiline comment starting with `/**` and ending with `*/`.
For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments
added looks like the one below::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.16 <0.7.0;
/// @author The Solidity Team

View File

@ -66,7 +66,7 @@ The example below uses ``_allowances`` to record the amount someone else is allo
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.22 <0.7.0;
contract MappingExample {
@ -120,7 +120,7 @@ the ``sum`` function iterates over to sum all the values.
::
pragma solidity >=0.5.99 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }

View File

@ -57,7 +57,7 @@ Data locations are not only relevant for persistency of data, but also for the s
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.5.0 <0.7.0;
contract C {
// The data location of x is storage.
@ -124,6 +124,12 @@ Accessing an array past its end causes a failing assertion. Methods ``.push()``
to append a new element at the end of the array, where ``.push()`` appends a zero-initialized element and returns
a reference to it.
.. index:: ! string, ! bytes
.. _strings:
.. _bytes:
``bytes`` and ``strings`` as Arrays
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -268,7 +274,7 @@ Array Members
::
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
@ -381,7 +387,7 @@ If ``start`` is greater than ``end`` or if ``end`` is greater
than the length of the array, an exception is thrown.
Both ``start`` and ``end`` are optional: ``start`` defaults
to ``0`` and ``end`` defaults to the length of the array.
to ``0`` and ``end`` defaults to the length of the array.
Array slices do not have any members. They are implicitly
convertible to arrays of their underlying type
@ -400,7 +406,7 @@ Array slices are useful to ABI-decode secondary data passed in function paramete
::
pragma solidity >=0.4.99 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
contract Proxy {
/// Address of the client contract managed by proxy i.e., this contract
@ -437,7 +443,7 @@ shown in the following example:
::
pragma solidity >=0.4.11 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
// Defines a new type with two fields.
// Declaring a struct outside of a contract allows
@ -494,7 +500,7 @@ shown in the following example:
The contract does not provide the full functionality of a crowdfunding
contract, but it contains the basic concepts necessary to understand structs.
Struct types can be used inside mappings and arrays and they can itself
Struct types can be used inside mappings and arrays and they can themselves
contain mappings and arrays.
It is not possible for a struct to contain a member of its own type,

View File

@ -300,6 +300,11 @@ All three functions ``call``, ``delegatecall`` and ``staticcall`` are very low-l
The ``gas`` option is available on all three methods, while the ``value`` option is not
supported for ``delegatecall``.
.. note::
It is best to avoid relying on hardcoded gas values in your smart contract code,
regardless of whether state is read from or written to, as this can have many pitfalls.
Also, access to gas might change in the future.
.. note::
All contracts can be converted to ``address`` type, so it is possible to query the balance of the
current contract using ``address(this).balance``.
@ -645,7 +650,7 @@ External (or public) functions have the following members:
Example that shows how to use the members::
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.6.0 <0.7.0;
// This will report a warning
contract Example {
@ -665,7 +670,6 @@ Example that shows how to use internal function types::
pragma solidity >=0.4.16 <0.7.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context

View File

@ -302,7 +302,8 @@ Input Description
// evm.bytecode.opcodes - Opcodes list
// evm.bytecode.sourceMap - Source mapping (useful for debugging)
// evm.bytecode.linkReferences - Link references (if unlinked object)
// evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode)
// evm.deployedBytecode* - Deployed bytecode (has all the options that evm.bytecode has)
// evm.deployedBytecode.immutableReferences - Map from AST ids to bytecode ranges that reference immutables
// evm.methodIdentifiers - The list of function hashes
// evm.gasEstimates - Function gas estimates
// ewasm.wast - eWASM S-expressions format (not supported at the moment)
@ -424,8 +425,14 @@ Output Description
}
}
},
// The same layout as above.
"deployedBytecode": { },
"deployedBytecode": {
..., // The same layout as above.
"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": {
"delegate(address)": "5c19a95c"
@ -605,8 +612,8 @@ Assume you have the following contracts you want to update declared in ``Source.
.. code-block:: none
// This will not compile
pragma solidity >0.4.23;
// This will not compile after 0.5.0
pragma solidity >0.4.23 <0.5.0;
contract Updateable {
function run() public view returns (bool);
@ -687,7 +694,7 @@ The command above applies all changes as shown below. Please review them careful
.. code-block:: none
pragma solidity >0.4.23;
pragma solidity >=0.6.0 <0.7.0;
abstract contract Updateable {
function run() public view virtual returns (bool);

View File

@ -111,7 +111,7 @@ Stand-Alone Usage
=================
You can use Yul in its stand-alone form in the EVM dialect using the Solidity compiler.
This will use the `Yul object notation <yul-objects>`_ so that it is possible to refer
This will use the :ref:`Yul object notation <yul-object>` so that it is possible to refer
to code as data to deploy contracts. This Yul mode is available for the commandline compiler
(use ``--strict-assembly``) and for the :ref:`standard-json interface <compiler-api>`:
@ -146,7 +146,7 @@ so you can e.g. use ``//`` and ``/* */`` to denote comments.
There is one exception: Identifiers in Yul can contain dots: ``.``.
Yul can specify "objects" that consist of code, data and sub-objects.
Please see `Yul Objects <yul-objects>`_ below for details on that.
Please see :ref:`Yul Objects <yul-object>` below for details on that.
In this section, we are only concerned with the code part of such an object.
This code part always consists of a curly-braces
delimited block. Most tools support specifying just a code block

View File

@ -283,6 +283,24 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end)
);
break;
case PushImmutable:
collection.append(createJsonValue(
"PUSHIMMUTABLE",
sourceIndex,
i.location().start,
i.location().end,
m_immutables.at(h256(i.data()))
));
break;
case AssignImmutable:
collection.append(createJsonValue(
"ASSIGNIMMUTABLE",
sourceIndex,
i.location().start,
i.location().end,
m_immutables.at(h256(i.data()))
));
break;
case Tag:
collection.append(
createJsonValue("tag", sourceIndex, i.location().start, i.location().end, toString(i.data())));
@ -333,6 +351,20 @@ AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier)
return AssemblyItem{PushLibraryAddress, h};
}
AssemblyItem Assembly::newPushImmutable(string const& _identifier)
{
h256 h(util::keccak256(_identifier));
m_immutables[h] = _identifier;
return AssemblyItem{PushImmutable, h};
}
AssemblyItem Assembly::newImmutableAssignment(string const& _identifier)
{
h256 h(util::keccak256(_identifier));
m_immutables[h] = _identifier;
return AssemblyItem{AssignImmutable, h};
}
Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs)
{
OptimiserSettings settings;
@ -495,16 +527,44 @@ LinkerObject const& Assembly::assemble() const
// Otherwise ensure the object is actually clear.
assertThrow(m_assembledObject.linkReferences.empty(), AssemblyException, "Unexpected link references.");
LinkerObject& ret = m_assembledObject;
size_t subTagSize = 1;
map<u256, pair<string, vector<size_t>>> immutableReferencesBySub;
for (auto const& sub: m_subs)
{
sub->assemble();
auto const& linkerObject = sub->assemble();
if (!linkerObject.immutableReferences.empty())
{
assertThrow(
immutableReferencesBySub.empty(),
AssemblyException,
"More than one sub-assembly references immutables."
);
immutableReferencesBySub = linkerObject.immutableReferences;
}
for (size_t tagPos: sub->m_tagPositionsInBytecode)
if (tagPos != size_t(-1) && tagPos > subTagSize)
subTagSize = tagPos;
}
LinkerObject& ret = m_assembledObject;
bool setsImmutables = false;
bool pushesImmutables = false;
for (auto const& i: m_items)
if (i.type() == AssignImmutable)
{
i.setImmutableOccurrences(immutableReferencesBySub[i.data()].second.size());
setsImmutables = true;
}
else if (i.type() == PushImmutable)
pushesImmutables = true;
if (setsImmutables || pushesImmutables)
assertThrow(
setsImmutables != pushesImmutables,
AssemblyException,
"Cannot push and assign immutables in the same assembly subroutine."
);
size_t bytesRequiredForCode = bytesRequired(subTagSize);
m_tagPositionsInBytecode = vector<size_t>(m_usedTags, -1);
@ -598,6 +658,25 @@ LinkerObject const& Assembly::assemble() const
ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data());
ret.bytecode.resize(ret.bytecode.size() + 20);
break;
case PushImmutable:
ret.bytecode.push_back(uint8_t(Instruction::PUSH32));
ret.immutableReferences[i.data()].first = m_immutables.at(i.data());
ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
ret.bytecode.resize(ret.bytecode.size() + 32);
break;
case AssignImmutable:
for (auto const& offset: immutableReferencesBySub[i.data()].second)
{
ret.bytecode.push_back(uint8_t(Instruction::DUP1));
// TODO: should we make use of the constant optimizer methods for pushing the offsets?
bytes offsetBytes = toCompactBigEndian(u256(offset));
ret.bytecode.push_back(uint8_t(Instruction::PUSH1) - 1 + offsetBytes.size());
ret.bytecode += offsetBytes;
ret.bytecode.push_back(uint8_t(Instruction::MSTORE));
}
immutableReferencesBySub.erase(i.data());
ret.bytecode.push_back(uint8_t(Instruction::POP));
break;
case PushDeployTimeAddress:
ret.bytecode.push_back(uint8_t(Instruction::PUSH20));
ret.bytecode.resize(ret.bytecode.size() + 20);
@ -615,6 +694,13 @@ LinkerObject const& Assembly::assemble() const
}
}
assertThrow(
immutableReferencesBySub.empty(),
AssemblyException,
"Some immutables were read from but never assigned."
);
if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty())
// Append an INVALID here to help tests find miscompilation.
ret.bytecode.push_back(uint8_t(Instruction::INVALID));

View File

@ -54,6 +54,8 @@ public:
Assembly& sub(size_t _sub) { return *m_subs.at(_sub); }
AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); }
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(std::string const& _identifier);
AssemblyItem const& append(AssemblyItem const& _i);
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
@ -64,6 +66,8 @@ public:
/// after compilation and CODESIZE is not an option.
void appendProgramSize() { append(AssemblyItem(PushProgramSize)); }
void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); }
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
@ -166,6 +170,7 @@ protected:
std::vector<std::shared_ptr<Assembly>> m_subs;
std::map<util::h256, std::string> m_strings;
std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables.
mutable LinkerObject m_assembledObject;
mutable std::vector<size_t> m_tagPositionsInBytecode;

View File

@ -80,6 +80,13 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
case PushLibraryAddress:
case PushDeployTimeAddress:
return 1 + 20;
case PushImmutable:
return 1 + 32;
case AssignImmutable:
if (m_immutableOccurrences)
return 1 + (3 + 32) * *m_immutableOccurrences;
else
return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
default:
break;
}
@ -90,6 +97,8 @@ int AssemblyItem::arguments() const
{
if (type() == Operation)
return instructionInfo(instruction()).args;
else if (type() == AssignImmutable)
return 1;
else
return 0;
}
@ -108,6 +117,7 @@ int AssemblyItem::returnValues() const
case PushSubSize:
case PushProgramSize:
case PushLibraryAddress:
case PushImmutable:
case PushDeployTimeAddress:
return 1;
case Tag:
@ -135,6 +145,7 @@ bool AssemblyItem::canBeFunctional() const
case PushProgramSize:
case PushLibraryAddress:
case PushDeployTimeAddress:
case PushImmutable:
return true;
case Tag:
return false;
@ -210,6 +221,12 @@ string AssemblyItem::toAssemblyText() const
case PushDeployTimeAddress:
text = string("deployTimeAddress()");
break;
case PushImmutable:
text = string("immutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
break;
case AssignImmutable:
text = string("assignImmutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
break;
case UndefinedItem:
assertThrow(false, AssemblyException, "Invalid assembly item.");
break;
@ -275,6 +292,12 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
case PushDeployTimeAddress:
_out << " PushDeployTimeAddress";
break;
case PushImmutable:
_out << " PushImmutable";
break;
case AssignImmutable:
_out << " AssignImmutable";
break;
case UndefinedItem:
_out << " ???";
break;

View File

@ -44,7 +44,9 @@ enum AssemblyItemType {
Tag,
PushData,
PushLibraryAddress, ///< Push a currently unknown address of another (library) contract.
PushDeployTimeAddress ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
AssignImmutable ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
};
class Assembly;
@ -153,6 +155,8 @@ public:
size_t m_modifierDepth = 0;
void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = std::make_shared<size_t>(_n); }
private:
AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation
@ -162,6 +166,8 @@ private:
/// Pushed value for operations with data to be determined during assembly stage,
/// e.g. PushSubSize, PushTag, PushSub, etc.
mutable std::shared_ptr<u256> m_pushedValue;
/// Number of PushImmutable's with the same hash. Only used for AssignImmutable.
mutable std::shared_ptr<size_t> m_immutableOccurrences;
};
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)

View File

@ -91,6 +91,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
{
// can be ignored
}
else if (_item.type() == AssignImmutable)
// Since AssignImmutable breaks blocks, it should be fine to only consider its changes to the stack, which
// is the same as POP.
return feedItem(AssemblyItem(Instruction::POP), _copyItem);
else if (_item.type() != Operation)
{
assertThrow(_item.deposit() == 1, InvalidDeposit, "");

View File

@ -40,6 +40,10 @@ struct LinkerObject
/// need to be replaced by the actual addresses by the linker.
std::map<size_t, std::string> linkReferences;
/// Map from hashes of the identifiers of immutable variables to the full identifier of the immutable and
/// to a list of offsets into the bytecode that refer to their values.
std::map<u256, std::pair<std::string, std::vector<size_t>>> immutableReferences;
/// Appends the bytecode of @a _other and incorporates its link references.
void append(LinkerObject const& _other);

View File

@ -36,6 +36,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
case UndefinedItem:
case Tag:
case PushDeployTimeAddress:
case AssignImmutable:
return true;
case Push:
case PushString:
@ -45,6 +46,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
case PushProgramSize:
case PushData:
case PushLibraryAddress:
case PushImmutable:
return false;
case Operation:
{

View File

@ -14,6 +14,8 @@ set(sources
analysis/DeclarationContainer.h
analysis/DocStringAnalyser.cpp
analysis/DocStringAnalyser.h
analysis/ImmutableValidator.cpp
analysis/ImmutableValidator.h
analysis/GlobalContext.cpp
analysis/GlobalContext.h
analysis/NameAndTypeResolver.cpp
@ -100,6 +102,8 @@ set(sources
formal/SMTPortfolio.cpp
formal/SMTPortfolio.h
formal/SolverInterface.h
formal/Sorts.cpp
formal/Sorts.h
formal/SSAVariable.cpp
formal/SSAVariable.h
formal/SymbolicTypes.cpp

View File

@ -153,7 +153,7 @@ void DocStringAnalyser::parseDocStrings(
appendError(
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
" exceedes the number of return parameters."
" exceeds the number of return parameters."
);
else
{

View File

@ -0,0 +1,220 @@
/*
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/>.
*/
#include <libsolidity/analysis/ImmutableValidator.h>
#include <libsolutil/CommonData.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace solidity::frontend;
void ImmutableValidator::analyze()
{
m_inConstructionContext = true;
auto linearizedContracts = m_currentContract.annotation().linearizedBaseContracts | boost::adaptors::reversed;
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())
visitCallableIfNew(*contract->constructor());
for (ContractDefinition const* contract: linearizedContracts)
for (std::shared_ptr<InheritanceSpecifier> const inheritSpec: contract->baseContracts())
if (auto args = inheritSpec->arguments())
ASTNode::listAccept(*args, *this);
m_inConstructionContext = false;
for (ContractDefinition const* contract: linearizedContracts)
{
for (auto funcDef: contract->definedFunctions())
visitCallableIfNew(*funcDef);
for (auto modDef: contract->functionModifiers())
visitCallableIfNew(*modDef);
}
checkAllVariablesInitialized(m_currentContract.location());
}
bool ImmutableValidator::visit(FunctionDefinition const& _functionDefinition)
{
return analyseCallable(_functionDefinition);
}
bool ImmutableValidator::visit(ModifierDefinition const& _modifierDefinition)
{
return analyseCallable(_modifierDefinition);
}
bool ImmutableValidator::visit(MemberAccess const& _memberAccess)
{
_memberAccess.expression().accept(*this);
if (auto contractType = dynamic_cast<ContractType const*>(_memberAccess.expression().annotation().type))
if (!contractType->isSuper())
// external access, no analysis needed.
return false;
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
analyseVariableReference(*varDecl, _memberAccess);
else if (auto funcType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
if (funcType->kind() == FunctionType::Kind::Internal && funcType->hasDeclaration())
visitCallableIfNew(funcType->declaration());
return false;
}
bool ImmutableValidator::visit(IfStatement const& _ifStatement)
{
bool prevInBranch = m_inBranch;
_ifStatement.condition().accept(*this);
m_inBranch = true;
_ifStatement.trueStatement().accept(*this);
if (auto falseStatement = _ifStatement.falseStatement())
falseStatement->accept(*this);
m_inBranch = prevInBranch;
return false;
}
bool ImmutableValidator::visit(WhileStatement const& _whileStatement)
{
bool prevInLoop = m_inLoop;
m_inLoop = true;
_whileStatement.condition().accept(*this);
_whileStatement.body().accept(*this);
m_inLoop = prevInLoop;
return false;
}
void ImmutableValidator::endVisit(Identifier const& _identifier)
{
if (auto const callableDef = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration))
visitCallableIfNew(callableDef->resolveVirtual(m_currentContract));
if (auto const varDecl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
analyseVariableReference(*varDecl, _identifier);
}
void ImmutableValidator::endVisit(Return const& _return)
{
if (m_currentConstructor != nullptr)
checkAllVariablesInitialized(_return.location());
}
bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDeclaration)
{
FunctionDefinition const* prevConstructor = m_currentConstructor;
m_currentConstructor = nullptr;
if (FunctionDefinition const* funcDef = dynamic_cast<decltype(funcDef)>(&_callableDeclaration))
{
ASTNode::listAccept(funcDef->modifiers(), *this);
if (funcDef->isConstructor())
m_currentConstructor = funcDef;
if (funcDef->isImplemented())
funcDef->body().accept(*this);
}
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
modDef->body().accept(*this);
m_currentConstructor = prevConstructor;
return false;
}
void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression)
{
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
return;
if (_expression.annotation().lValueRequested && _expression.annotation().lValueOfOrdinaryAssignment)
{
if (!m_currentConstructor)
m_errorReporter.typeError(
_expression.location(),
"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(
_expression.location(),
"Immutable variables must be initialized in the constructor of the contract they are defined in."
);
else if (m_inLoop)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables can only be initialized once, not in a while statement."
);
else if (m_inBranch)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables must be initialized unconditionally, not in an if statement."
);
if (!m_initializedStateVariables.emplace(&_variableReference).second)
m_errorReporter.typeError(
_expression.location(),
"Immutable state variable already initialized."
);
}
else if (m_inConstructionContext)
m_errorReporter.typeError(
_expression.location(),
"Immutable variables cannot be read during contract creation time, which means "
"they cannot be read in the constructor or any function or modifier called from it."
);
}
void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::SourceLocation const& _location)
{
for (ContractDefinition const* contract: m_currentContract.annotation().linearizedBaseContracts)
for (VariableDeclaration const* varDecl: contract->stateVariables())
if (varDecl->immutable())
if (!util::contains(m_initializedStateVariables, varDecl))
m_errorReporter.typeError(
_location,
solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()),
"Construction control flow ends without initializing all immutable state variables."
);
}
void ImmutableValidator::visitCallableIfNew(Declaration const& _declaration)
{
CallableDeclaration const* _callable = dynamic_cast<CallableDeclaration const*>(&_declaration);
solAssert(_callable != nullptr, "");
if (m_visitedCallables.emplace(_callable).second)
_declaration.accept(*this);
}

View File

@ -0,0 +1,78 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <liblangutil/ErrorReporter.h>
#include <memory>
namespace solidity::frontend
{
/**
* Validates access and initialization of immutable variables:
* must be directly initialized in their respective c'tor
* can not be read by any function/modifier called by the c'tor (or the c'tor itself)
* must be initialized outside loops (only one initialization)
* must be initialized outside ifs (must be initialized unconditionally)
* must be initialized exactly once (no multiple statements)
* must be initialized exactly once (no early return to skip initialization)
*/
class ImmutableValidator: private ASTConstVisitor
{
using CallableDeclarationSet = std::set<CallableDeclaration const*, ASTNode::CompareByID>;
public:
ImmutableValidator(langutil::ErrorReporter& _errorReporter, ContractDefinition const& _contractDefinition):
m_currentContract(_contractDefinition),
m_errorReporter(_errorReporter)
{ }
void analyze();
private:
bool visit(FunctionDefinition const& _functionDefinition);
bool visit(ModifierDefinition const& _modifierDefinition);
bool visit(MemberAccess const& _memberAccess);
bool visit(IfStatement const& _ifStatement);
bool visit(WhileStatement const& _whileStatement);
void endVisit(Identifier const& _identifier);
void endVisit(Return const& _return);
bool analyseCallable(CallableDeclaration const& _callableDeclaration);
void analyseVariableReference(VariableDeclaration const& _variableReference, Expression const& _expression);
void checkAllVariablesInitialized(langutil::SourceLocation const& _location);
void visitCallableIfNew(Declaration const& _declaration);
ContractDefinition const& m_currentContract;
CallableDeclarationSet m_visitedCallables;
std::set<VariableDeclaration const*, ASTNode::CompareByID> m_initializedStateVariables;
langutil::ErrorReporter& m_errorReporter;
FunctionDefinition const* m_currentConstructor = nullptr;
bool m_inLoop = false;
bool m_inBranch = false;
bool m_inConstructionContext = false;
};
}

View File

@ -204,7 +204,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
typeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
}
}
@ -328,6 +328,8 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
if (_variable.isConstant() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
if (_variable.immutable() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"immutable\" keyword can only be used for state variables.");
if (!_variable.typeName())
{
@ -394,7 +396,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
else if (_variable.isStateVariable())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = _variable.isConstant() ? DataLocation::Memory : DataLocation::Storage;
typeLoc = (_variable.isConstant() || _variable.immutable()) ? DataLocation::Memory : DataLocation::Storage;
}
else if (
dynamic_cast<StructDefinition const*>(_variable.scope()) ||

View File

@ -303,7 +303,9 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
);
}
if (!_function.isImplemented() && !_function.modifiers().empty())
if (m_isInterface && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions in interfaces cannot have modifiers.");
else if (!_function.isImplemented() && !_function.modifiers().empty())
m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
return true;

View File

@ -480,6 +480,18 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
"Initial value for constant variable has to be compile-time constant."
);
}
else if (_variable.immutable())
{
if (!_variable.type()->isValueType())
m_errorReporter.typeError(_variable.location(), "Immutable variables cannot have a non-value type.");
if (
auto const* functionType = dynamic_cast<FunctionType const*>(_variable.type());
functionType && functionType->kind() == FunctionType::Kind::External
)
m_errorReporter.typeError(_variable.location(), "Immutable variables of external function type are not yet supported.");
solAssert(_variable.type()->sizeOnStack() == 1 || m_errorReporter.hasErrors(), "");
}
if (!_variable.isStateVariable())
{
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
@ -641,16 +653,21 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
{
solAssert(var->type(), "Expected variable type!");
if (var->immutable())
{
m_errorReporter.typeError(_identifier.location, "Assembly access to immutable variables is not supported.");
return size_t(-1);
}
if (var->isConstant())
{
var = rootVariableDeclaration(*var);
var = rootConstVariableDeclaration(*var);
if (!var->value())
if (var && !var->value())
{
m_errorReporter.typeError(_identifier.location, "Constant has no value.");
return size_t(-1);
}
else if (!type(*var)->isValueType() || (
else if (!var || !type(*var)->isValueType() || (
dynamic_cast<Literal const*>(var->value().get()) == nullptr &&
type(*var->value())->category() != Type::Category::RationalNumber
))
@ -731,6 +748,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
if (dynamic_cast<FunctionDefinition const*>(declaration))
{
m_errorReporter.declarationError(_identifier.location, "Access to functions is not allowed in inline assembly.");
return size_t(-1);
}
else if (dynamic_cast<VariableDeclaration const*>(declaration))
{
@ -1052,17 +1071,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
if (!varDecl.annotation().type)
m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed.");
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl)))
{
if (ref->dataStoredIn(DataLocation::Storage))
{
string errorText{"Uninitialized storage pointer."};
solAssert(varDecl.referenceLocation() != VariableDeclaration::Location::Unspecified, "Expected a specified location at this point");
solAssert(m_scope, "");
m_errorReporter.declarationError(varDecl.location(), errorText);
}
}
else if (dynamic_cast<MappingType const*>(type(varDecl)))
if (dynamic_cast<MappingType const*>(type(varDecl)))
m_errorReporter.typeError(
varDecl.location(),
"Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
@ -1308,11 +1317,11 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
{
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
auto const& types = tupleType ? tupleType->components() : vector<TypePointer> { &_type };
auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector<TypePointer> { &_type };
solAssert(
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
"Array sizes don't match or no errors generated."
"Array sizes don't match and no errors generated."
);
for (size_t i = 0; i < min(tupleExpression->components().size(), types.size()); i++)
@ -1336,7 +1345,10 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
bool TypeChecker::visit(Assignment const& _assignment)
{
requireLValue(_assignment.leftHandSide());
requireLValue(
_assignment.leftHandSide(),
_assignment.assignmentOperator() == Token::Assign
);
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
@ -1394,7 +1406,10 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
for (auto const& component: components)
if (component)
{
requireLValue(*component);
requireLValue(
*component,
_tuple.annotation().lValueOfOrdinaryAssignment
);
types.push_back(type(*component));
}
else
@ -1479,7 +1494,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
Token op = _operation.getOperator();
bool const modifying = (op == Token::Inc || op == Token::Dec || op == Token::Delete);
if (modifying)
requireLValue(_operation.subExpression());
requireLValue(_operation.subExpression(), false);
else
_operation.subExpression().accept(*this);
TypePointer const& subExprType = type(_operation.subExpression());
@ -2987,9 +3002,10 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
return true;
}
void TypeChecker::requireLValue(Expression const& _expression)
void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAssignment)
{
_expression.annotation().lValueRequested = true;
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
_expression.accept(*this);
if (_expression.annotation().isLValue)

View File

@ -158,7 +158,7 @@ private:
/// convertible to @a _expectedType.
bool expectType(Expression const& _expression, Type const& _expectedType);
/// Runs type checks on @a _expression to infer its type and then checks that it is an LValue.
void requireLValue(Expression const& _expression);
void requireLValue(Expression const& _expression, bool _ordinaryAssignment);
ContractDefinition const* m_scope = nullptr;

View File

@ -319,6 +319,37 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
return initAnnotation<FunctionDefinitionAnnotation>();
}
FunctionDefinition const& FunctionDefinition::resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart
) const
{
solAssert(!isConstructor(), "");
// If we are not doing super-lookup and the function is not virtual, we can stop here.
if (_searchStart == nullptr && !virtualSemantics())
return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
FunctionType const* functionType = TypeProvider::function(*this)->asCallableFunction(false);
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
if (_searchStart != nullptr && c != _searchStart)
continue;
_searchStart = nullptr;
for (FunctionDefinition const* function: c->definedFunctions())
if (
function->name() == name() &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(*functionType)
)
return *function;
}
solAssert(false, "Virtual function " + name() + " not found.");
return *this; // not reached
}
TypePointer ModifierDefinition::type() const
{
return TypeProvider::modifier(*this);
@ -329,6 +360,33 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const
return initAnnotation<ModifierDefinitionAnnotation>();
}
ModifierDefinition const& ModifierDefinition::resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart
) const
{
solAssert(_searchStart == nullptr, "Used super in connection with modifiers.");
// If we are not doing super-lookup and the modifier is not virtual, we can stop here.
if (_searchStart == nullptr && !virtualSemantics())
return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
if (_searchStart != nullptr && c != _searchStart)
continue;
_searchStart = nullptr;
for (ModifierDefinition const* modifier: c->functionModifiers())
if (modifier->name() == name())
return *modifier;
}
solAssert(false, "Virtual modifier " + name() + " not found.");
return *this; // not reached
}
TypePointer EventDefinition::type() const
{
return TypeProvider::function(*this);
@ -508,8 +566,6 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter())
return set<Location>{ Location::Unspecified };
else if (isStateVariable() && isConstant())
return set<Location>{ Location::Memory };
else if (isExternalCallableParameter())
{
set<Location> locations{ Location::CallData };

View File

@ -62,6 +62,24 @@ class ASTConstVisitor;
class ASTNode: private boost::noncopyable
{
public:
struct CompareByID
{
using is_transparent = void;
bool operator()(ASTNode const* _lhs, ASTNode const* _rhs) const
{
return _lhs->id() < _rhs->id();
}
bool operator()(ASTNode const* _lhs, int64_t _rhs) const
{
return _lhs->id() < _rhs;
}
bool operator()(int64_t _lhs, ASTNode const* _rhs) const
{
return _lhs < _rhs->id();
}
};
using SourceLocation = langutil::SourceLocation;
explicit ASTNode(int64_t _id, SourceLocation const& _location);
@ -689,6 +707,18 @@ public:
CallableDeclarationAnnotation& annotation() const override = 0;
/// Performs virtual or super function/modifier lookup:
/// If @a _searchStart is nullptr, performs virtual function lookup, i.e.
/// searches the inheritance hierarchy of @a _mostDerivedContract towards the base
/// and returns the first function/modifier definition that
/// is overwritten by this callable.
/// If @a _searchStart is non-null, starts searching only from that contract, but
/// still in the hierarchy of @a _mostDerivedContract.
virtual CallableDeclaration const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const = 0;
protected:
ASTPointer<ParameterList> m_parameters;
ASTPointer<OverrideSpecifier> m_overrides;
@ -799,6 +829,12 @@ public:
CallableDeclaration::virtualSemantics() ||
(annotation().contract && annotation().contract->isInterface());
}
FunctionDefinition const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const override;
private:
StateMutability m_stateMutability;
Token const m_kind;
@ -879,6 +915,7 @@ public:
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_constantness == Constantness::Constant; }
bool immutable() const { return m_constantness == Constantness::Immutable; }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
Location referenceLocation() const { return m_location; }
/// @returns a set of allowed storage locations for the variable.
@ -944,6 +981,12 @@ public:
ModifierDefinitionAnnotation& annotation() const override;
ModifierDefinition const& resolveVirtual(
ContractDefinition const& _mostDerivedContract,
ContractDefinition const* _searchStart = nullptr
) const override;
private:
ASTPointer<Block> m_body;
};
@ -1009,6 +1052,14 @@ public:
EventDefinitionAnnotation& annotation() const override;
CallableDeclaration const& resolveVirtual(
ContractDefinition const&,
ContractDefinition const*
) const override
{
return *this;
}
private:
bool m_anonymous = false;
};

View File

@ -47,6 +47,14 @@ using TypePointer = Type const*;
struct ASTAnnotation
{
ASTAnnotation() = default;
ASTAnnotation(ASTAnnotation const&) = delete;
ASTAnnotation(ASTAnnotation&&) = delete;
ASTAnnotation& operator=(ASTAnnotation const&) = delete;
ASTAnnotation& operator=(ASTAnnotation&&) = delete;
virtual ~ASTAnnotation() = default;
};
@ -58,7 +66,16 @@ struct DocTag
struct StructurallyDocumentedAnnotation
{
StructurallyDocumentedAnnotation() = default;
StructurallyDocumentedAnnotation(StructurallyDocumentedAnnotation const&) = delete;
StructurallyDocumentedAnnotation(StructurallyDocumentedAnnotation&&) = delete;
StructurallyDocumentedAnnotation& operator=(StructurallyDocumentedAnnotation const&) = delete;
StructurallyDocumentedAnnotation& operator=(StructurallyDocumentedAnnotation&&) = delete;
virtual ~StructurallyDocumentedAnnotation() = default;
/// Mapping docstring tag name -> content.
std::multimap<std::string, DocTag> docTags;
};
@ -75,6 +92,16 @@ struct SourceUnitAnnotation: ASTAnnotation
struct ScopableAnnotation
{
ScopableAnnotation() = default;
ScopableAnnotation(ScopableAnnotation const&) = delete;
ScopableAnnotation(ScopableAnnotation&&) = delete;
ScopableAnnotation& operator=(ScopableAnnotation const&) = delete;
ScopableAnnotation& operator=(ScopableAnnotation&&) = delete;
virtual ~ScopableAnnotation() = default;
/// The scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step.
ASTNode const* scope = nullptr;
@ -208,6 +235,9 @@ struct ExpressionAnnotation: ASTAnnotation
bool isLValue = false;
/// Whether the expression is used in a context where the LValue is actually required.
bool lValueRequested = false;
/// Whether the expression is an lvalue that is only assigned.
/// Would be false for --, ++, delete, +=, -=, ....
bool lValueOfOrdinaryAssignment = false;
/// Types and - if given - names of arguments if the expr. is a function
/// that is called, used for overload resoultion

View File

@ -21,7 +21,7 @@
namespace solidity::frontend
{
VariableDeclaration const* rootVariableDeclaration(VariableDeclaration const& _varDecl)
VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration const& _varDecl)
{
solAssert(_varDecl.isConstant(), "Constant variable expected");
@ -30,7 +30,8 @@ VariableDeclaration const* rootVariableDeclaration(VariableDeclaration const& _v
while ((identifier = dynamic_cast<Identifier const*>(rootDecl->value().get())))
{
auto referencedVarDecl = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration);
solAssert(referencedVarDecl && referencedVarDecl->isConstant(), "Identifier is not referencing a variable declaration");
if (!referencedVarDecl || !referencedVarDecl->isConstant())
return nullptr;
rootDecl = referencedVarDecl;
}
return rootDecl;

View File

@ -22,8 +22,9 @@ namespace solidity::frontend
class VariableDeclaration;
/// Find the topmost referenced variable declaration when the given variable
/// Find the topmost referenced constant variable declaration when the given variable
/// declaration value is an identifier. Works only for constant variable declarations.
VariableDeclaration const* rootVariableDeclaration(VariableDeclaration const& _varDecl);
/// Returns nullptr if an identifier in the chain is not referencing a constant variable declaration.
VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration const& _varDecl);
}

View File

@ -41,7 +41,16 @@ namespace solidity::frontend
class ASTVisitor
{
public:
ASTVisitor() = default;
ASTVisitor(ASTVisitor const&) = delete;
ASTVisitor(ASTVisitor&&) = delete;
ASTVisitor& operator=(ASTVisitor const&) = delete;
ASTVisitor& operator=(ASTVisitor&&) = delete;
virtual ~ASTVisitor() = default;
virtual bool visit(SourceUnit& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective& _node) { return visitNode(_node); }
@ -158,7 +167,16 @@ protected:
class ASTConstVisitor
{
public:
ASTConstVisitor() = default;
ASTConstVisitor(ASTConstVisitor const&) = delete;
ASTConstVisitor(ASTConstVisitor&&) = delete;
ASTConstVisitor& operator=(ASTConstVisitor const&) = delete;
ASTConstVisitor& operator=(ASTConstVisitor&&) = delete;
virtual ~ASTConstVisitor() = default;
virtual bool visit(SourceUnit const& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective const& _node) { return visitNode(_node); }

View File

@ -1487,11 +1487,19 @@ TypeResult ReferenceType::unaryOperatorResult(Token _operator) const
case DataLocation::Memory:
return TypeProvider::emptyTuple();
case DataLocation::Storage:
return m_isPointer ? nullptr : TypeProvider::emptyTuple();
return isPointer() ? nullptr : TypeProvider::emptyTuple();
}
return nullptr;
}
bool ReferenceType::isPointer() const
{
if (m_location == DataLocation::Storage)
return m_isPointer;
else
return true;
}
TypePointer ReferenceType::copyForLocationIfReference(Type const* _type) const
{
return TypeProvider::withLocationIfReference(m_location, _type);
@ -1502,7 +1510,7 @@ string ReferenceType::stringForReferencePart() const
switch (m_location)
{
case DataLocation::Storage:
return string("storage ") + (m_isPointer ? "pointer" : "ref");
return string("storage ") + (isPointer() ? "pointer" : "ref");
case DataLocation::CallData:
return "calldata";
case DataLocation::Memory:
@ -1868,7 +1876,8 @@ u256 ArrayType::memoryDataSize() const
std::unique_ptr<ReferenceType> ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const
{
auto copy = make_unique<ArrayType>(_location);
copy->m_isPointer = _isPointer;
if (_location == DataLocation::Storage)
copy->m_isPointer = _isPointer;
copy->m_arrayKind = m_arrayKind;
copy->m_baseType = copy->copyForLocationIfReference(m_baseType);
copy->m_hasDynamicLength = m_hasDynamicLength;
@ -1988,7 +1997,7 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (!variable->isConstant())
if (!(variable->isConstant() || variable->immutable()))
variables.push_back(variable);
TypePointers types;
for (auto variable: variables)
@ -2003,6 +2012,16 @@ vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
return variablesAndOffsets;
}
vector<VariableDeclaration const*> ContractType::immutableVariables() const
{
vector<VariableDeclaration const*> variables;
for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.annotation().linearizedBaseContracts))
for (VariableDeclaration const* variable: contract->stateVariables())
if (variable->immutable())
variables.push_back(variable);
return variables;
}
vector<tuple<string, TypePointer>> ContractType::makeStackItems() const
{
if (m_super)
@ -2247,7 +2266,8 @@ TypeResult StructType::interfaceType(bool _inLibrary) const
std::unique_ptr<ReferenceType> StructType::copyForLocation(DataLocation _location, bool _isPointer) const
{
auto copy = make_unique<StructType>(m_struct, _location);
copy->m_isPointer = _isPointer;
if (_location == DataLocation::Storage)
copy->m_isPointer = _isPointer;
return copy;
}
@ -2318,7 +2338,7 @@ TypePointers StructType::memoryMemberTypes() const
TypePointers types;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (variable->annotation().type->canLiveOutsideStorage())
types.push_back(variable->annotation().type);
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
return types;
}
@ -2954,7 +2974,7 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
if (m_valueSet)
slots.emplace_back("value", TypeProvider::uint256());
if (m_saltSet)
slots.emplace_back("salt", TypeProvider::uint256());
slots.emplace_back("salt", TypeProvider::fixedBytes(32));
if (bound())
for (auto const& [boundName, boundType]: m_parameterTypes.front()->stackItems())
slots.emplace_back("self_" + boundName, boundType);

View File

@ -701,7 +701,9 @@ public:
/// pointer type, state variables are bound references. Assignments to pointers or deleting
/// them will not modify storage (that will only change the pointer). Assignment from
/// non-storage objects to a variable of storage pointer type is not possible.
bool isPointer() const { return m_isPointer; }
/// For anything other than storage, this always returns true because assignments
/// never change the contents of the original value.
bool isPointer() const;
bool operator==(ReferenceType const& _other) const
{
@ -893,6 +895,8 @@ public:
/// @returns a list of all state variables (including inherited) of the contract and their
/// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
/// @returns a list of all immutable variables (including inherited) of the contract.
std::vector<VariableDeclaration const*> immutableVariables() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:

View File

@ -71,6 +71,49 @@ void CompilerContext::addStateVariable(
m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset);
}
void CompilerContext::addImmutable(VariableDeclaration const& _variable)
{
solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable.");
solUnimplementedAssert(_variable.annotation().type->isValueType(), "Only immutable variables of value type are supported.");
solAssert(m_runtimeContext, "Attempted to register an immutable variable for runtime code generation.");
m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory;
solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap.");
*m_reservedMemory += _variable.annotation().type->memoryHeadSize();
}
size_t CompilerContext::immutableMemoryOffset(VariableDeclaration const& _variable) const
{
solAssert(m_immutableVariables.count(&_variable), "Memory offset of unknown immutable queried.");
solAssert(m_runtimeContext, "Attempted to fetch the memory offset of an immutable variable during runtime code generation.");
return m_immutableVariables.at(&_variable);
}
vector<string> CompilerContext::immutableVariableSlotNames(VariableDeclaration const& _variable)
{
string baseName = to_string(_variable.id());
solAssert(_variable.annotation().type->sizeOnStack() > 0, "");
if (_variable.annotation().type->sizeOnStack() == 1)
return {baseName};
vector<string> names;
auto collectSlotNames = [&](string const& _baseName, TypePointer type, auto const& _recurse) -> void {
for (auto const& [slot, type]: type->stackItems())
if (type)
_recurse(_baseName + " " + slot, type, _recurse);
else
names.emplace_back(_baseName);
};
collectSlotNames(baseName, _variable.annotation().type, collectSlotNames);
return names;
}
size_t CompilerContext::reservedMemory()
{
solAssert(m_reservedMemory.has_value(), "Reserved memory was used before ");
size_t reservedMemory = *m_reservedMemory;
m_reservedMemory = std::nullopt;
return reservedMemory;
}
void CompilerContext::startFunction(Declaration const& _function)
{
m_functionCompilationQueue.startFunction(_function);
@ -223,57 +266,43 @@ evmasm::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration con
return m_functionCompilationQueue.entryLabelIfExists(_declaration);
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function)
{
// Libraries do not allow inheritance and their functions can be inlined, so we should not
// search the inheritance hierarchy (which will be the wrong one in case the function
// is inlined).
if (auto scope = dynamic_cast<ContractDefinition const*>(_function.scope()))
if (scope->isLibrary())
return _function;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin());
}
FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base)
{
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
return resolveVirtualFunction(_function, superContract(_base));
solAssert(m_mostDerivedContract, "No most derived contract set.");
ContractDefinition const* super = superContract(_base);
solAssert(super, "Super contract not available.");
return _function.resolveVirtual(mostDerivedContract(), super);
}
FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const
{
vector<ContractDefinition const*>::const_iterator it = superContract(_contract);
for (; it != m_inheritanceHierarchy.end(); ++it)
if ((*it)->constructor())
return (*it)->constructor();
ContractDefinition const* next = superContract(_contract);
if (next == nullptr)
return nullptr;
for (ContractDefinition const* c: m_mostDerivedContract->annotation().linearizedBaseContracts)
if (next != nullptr && next != c)
continue;
else
{
next = nullptr;
if (c->constructor())
return c->constructor();
}
return nullptr;
}
ContractDefinition const& CompilerContext::mostDerivedContract() const
{
solAssert(m_mostDerivedContract, "Most derived contract not set.");
return *m_mostDerivedContract;
}
Declaration const* CompilerContext::nextFunctionToCompile() const
{
return m_functionCompilationQueue.nextFunctionToCompile();
}
ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier(
ModifierDefinition const& _modifier
) const
{
// Libraries do not allow inheritance and their functions can be inlined, so we should not
// search the inheritance hierarchy (which will be the wrong one in case the function
// is inlined).
if (auto scope = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
if (scope->isLibrary())
return _modifier;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
for (ContractDefinition const* contract: m_inheritanceHierarchy)
for (ModifierDefinition const* modifier: contract->functionModifiers())
if (modifier->name() == _modifier.name())
return *modifier;
solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy.");
}
unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const
{
auto res = m_localVariables.find(&_declaration);
@ -500,32 +529,26 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
#endif
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
FunctionDefinition const& _function,
vector<ContractDefinition const*>::const_iterator _searchStart
)
LinkerObject const& CompilerContext::assembledObject() const
{
string name = _function.name();
FunctionType functionType(_function);
auto it = _searchStart;
for (; it != m_inheritanceHierarchy.end(); ++it)
for (FunctionDefinition const* function: (*it)->definedFunctions())
if (
function->name() == name &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
return _function; // not reached
LinkerObject const& object = m_asm->assemble();
solAssert(object.immutableReferences.empty(), "Leftover immutables.");
return object;
}
vector<ContractDefinition const*>::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const
ContractDefinition const* CompilerContext::superContract(ContractDefinition const& _contract) const
{
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract);
solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy.");
return ++it;
auto const& hierarchy = mostDerivedContract().annotation().linearizedBaseContracts;
auto it = find(hierarchy.begin(), hierarchy.end(), &_contract);
solAssert(it != hierarchy.end(), "Base not found in inheritance hierarchy.");
++it;
if (it == hierarchy.end())
return nullptr;
else
{
solAssert(*it != &_contract, "");
return *it;
}
}
string CompilerContext::revertReasonIfDebug(string const& _message)

View File

@ -64,6 +64,7 @@ public:
m_asm(std::make_shared<evmasm::Assembly>()),
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_reservedMemory{0},
m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
@ -80,6 +81,16 @@ public:
bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addImmutable(VariableDeclaration const& _declaration);
/// @returns the reserved memory for storing the value of the immutable @a _variable during contract creation.
size_t immutableMemoryOffset(VariableDeclaration const& _variable) const;
/// @returns a list of slot names referring to the stack slots of an immutable variable.
static std::vector<std::string> immutableVariableSlotNames(VariableDeclaration const& _variable);
/// @returns the reserved memory and resets it to mark it as used.
size_t reservedMemory();
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(Declaration const& _declaration);
/// Removes all local variables currently allocated above _stackHeight.
@ -103,15 +114,14 @@ public:
/// @returns the entry label of the given function. Might return an AssemblyItem of type
/// UndefinedItem if it does not exist yet.
evmasm::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const;
/// @returns the entry label of the given function and takes overrides into account.
FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function);
/// @returns the function that overrides the given declaration from the most derived class just
/// above _base in the current inheritance hierarchy.
FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base);
/// @returns the next constructor in the inheritance hierarchy.
FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const;
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; }
/// Sets the contract currently being compiled - the most derived one.
void setMostDerivedContract(ContractDefinition const& _contract) { m_mostDerivedContract = &_contract; }
ContractDefinition const& mostDerivedContract() const;
/// @returns the next function in the queue of functions that are still to be compiled
/// (i.e. that were referenced during compilation but where we did not yet generate code for).
@ -160,7 +170,6 @@ public:
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
/// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns
@ -217,6 +226,10 @@ public:
evmasm::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); }
/// Appends the address (virtual, will be filled in by linker) of a library.
void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); }
/// Appends an immutable variable. The value will be filled in by the constructor.
void appendImmutable(std::string const& _identifier) { m_asm->appendImmutable(_identifier); }
/// Appends an assignment to an immutable variable. Only valid in creation code.
void appendImmutableAssignment(std::string const& _identifier) { m_asm->appendImmutableAssignment(_identifier); }
/// Appends a zero-address that can be replaced by something else at deploy time (if the
/// position in bytecode is known).
void appendDeployTimeAddress() { m_asm->append(evmasm::PushDeployTimeAddress); }
@ -282,7 +295,7 @@ public:
return m_asm->assemblyJSON(_indicies);
}
evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
evmasm::LinkerObject const& assembledObject() const;
evmasm::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); }
/**
@ -300,14 +313,8 @@ public:
RevertStrings revertStrings() const { return m_revertStrings; }
private:
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
/// the first function definition that is overwritten by _function.
FunctionDefinition const& resolveVirtualFunction(
FunctionDefinition const& _function,
std::vector<ContractDefinition const*>::const_iterator _searchStart
);
/// @returns an iterator to the contract directly above the given contract.
std::vector<ContractDefinition const*>::const_iterator superContract(ContractDefinition const& _contract) const;
/// @returns a pointer to the contract directly above the given contract.
ContractDefinition const* superContract(ContractDefinition const& _contract) const;
/// Updates source location set in the assembly.
void updateSourceLocation();
@ -355,13 +362,19 @@ private:
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers;
/// Storage offsets of state variables
std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Memory offsets reserved for the values of immutable variables during contract creation.
std::map<VariableDeclaration const*, size_t> m_immutableVariables;
/// Total amount of reserved memory. Reserved memory is used to store immutable variables during contract creation.
/// This has to be finalized before initialiseFreeMemoryPointer() is called. That function
/// will reset the optional to verify that.
std::optional<size_t> m_reservedMemory = {0};
/// Offsets of local variables on the stack (relative to stack base).
/// This needs to be a stack because if a modifier contains a local variable and this
/// modifier is applied twice, the position of the variable needs to be restored
/// after the nested modifier is left.
std::map<Declaration const*, std::vector<unsigned>> m_localVariables;
/// List of current inheritance hierarchy from derived to base.
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
/// The contract currently being compiled. Virtual function lookup starts from this contarct.
ContractDefinition const* m_mostDerivedContract = nullptr;
/// Stack of current visited AST nodes, used for location attachment
std::stack<ASTNode const*> m_visitedNodes;
/// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime.

View File

@ -51,7 +51,9 @@ static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPoi
void CompilerUtils::initialiseFreeMemoryPointer()
{
m_context << u256(generalPurposeMemoryStart);
size_t reservedMemory = m_context.reservedMemory();
solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, "");
m_context << (u256(generalPurposeMemoryStart) + reservedMemory);
storeFreeMemoryPointer();
}

View File

@ -129,7 +129,9 @@ void ContractCompiler::initializeContext(
{
m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setOtherCompilers(_otherCompilers);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
if (m_runtimeCompiler)
registerImmutableVariables(_contract);
CompilerUtils(m_context).initialiseFreeMemoryPointer();
registerStateVariables(_contract);
m_context.resetVisitedNodes(&_contract);
@ -183,10 +185,26 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
m_context << deployRoutine;
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
ContractType contractType(_contract);
auto const& immutables = contractType.immutableVariables();
// Push all immutable values on the stack.
for (auto const& immutable: immutables)
CompilerUtils(m_context).loadFromMemory(m_context.immutableMemoryOffset(*immutable), *immutable->annotation().type);
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << Instruction::DUP1;
if (immutables.empty())
m_context << Instruction::DUP1;
m_context.pushSubroutineOffset(m_context.runtimeSub());
m_context << u256(0) << Instruction::CODECOPY;
// Assign immutable values from stack in reversed order.
for (auto const& immutable: immutables | boost::adaptors::reversed)
{
auto slotNames = m_context.immutableVariableSlotNames(*immutable);
for (auto&& slotName: slotNames | boost::adaptors::reversed)
m_context.appendImmutableAssignment(slotName);
}
if (!immutables.empty())
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context << u256(0) << Instruction::RETURN;
return m_context.runtimeSub();
@ -521,6 +539,13 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}
void ContractCompiler::registerImmutableVariables(ContractDefinition const& _contract)
{
solAssert(m_runtimeCompiler, "Attempted to register immutables for runtime code generation.");
for (auto const& var: ContractType(_contract).immutableVariables())
m_context.addImmutable(*var);
}
void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract)
{
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
@ -662,7 +687,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
functionDef = &functionDef->resolveVirtual(m_context.mostDerivedContract());
auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag();
solAssert(functionEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendLabelReference(size_t(functionEntryLabel.data()));
@ -680,9 +705,15 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
}
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
{
solAssert(!variable->immutable(), "");
if (variable->isConstant())
{
variable = rootVariableDeclaration(*variable);
variable = rootConstVariableDeclaration(*variable);
// If rootConstVariableDeclaration fails and returns nullptr,
// it should have failed in TypeChecker already, causing a compilation error.
// In such case we should not get here.
solAssert(variable, "");
u256 value;
if (variable->value()->annotation().type->category() == Type::Category::RationalNumber)
{
@ -1302,10 +1333,9 @@ void ContractCompiler::appendModifierOrFunctionCode()
appendModifierOrFunctionCode();
else
{
ModifierDefinition const& nonVirtualModifier = dynamic_cast<ModifierDefinition const&>(
ModifierDefinition const& modifier = dynamic_cast<ModifierDefinition const&>(
*modifierInvocation->name()->annotation().referencedDeclaration
);
ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier);
).resolveVirtual(m_context.mostDerivedContract());
CompilerContext::LocationSetter locationSetter(m_context, modifier);
std::vector<ASTPointer<Expression>> const& modifierArguments =
modifierInvocation->arguments() ? *modifierInvocation->arguments() : std::vector<ASTPointer<Expression>>();

View File

@ -99,6 +99,7 @@ private:
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
void registerStateVariables(ContractDefinition const& _contract);
void registerImmutableVariables(ContractDefinition const& _contract);
void initializeStateVariables(ContractDefinition const& _contract);
bool visit(VariableDeclaration const& _variableDeclaration) override;

View File

@ -73,7 +73,10 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
utils().convertType(*type, *_varDecl.annotation().type);
type = _varDecl.annotation().type;
}
StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
if (_varDecl.immutable())
ImmutableItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
else
StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
}
void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl)
@ -93,11 +96,17 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
FunctionType accessorType(_varDecl);
TypePointers paramTypes = accessorType.parameterTypes();
if (_varDecl.immutable())
solAssert(paramTypes.empty(), "");
m_context.adjustStackOffset(1 + CompilerUtils::sizeOnStack(paramTypes));
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
m_context << location.first << u256(location.second);
if (!_varDecl.immutable())
{
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
m_context << location.first << u256(location.second);
}
TypePointer returnType = _varDecl.annotation().type;
@ -179,6 +188,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
solAssert(returnTypes.size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(returnType))
{
solAssert(!_varDecl.immutable(), "");
// remove offset
m_context << Instruction::POP;
auto const& names = accessorType.returnParameterNames();
@ -205,7 +215,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{
// simple value or array
solAssert(returnTypes.size() == 1, "");
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
if (_varDecl.immutable())
ImmutableItem(m_context, _varDecl).retrieveValue(SourceLocation());
else
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
utils().convertType(*returnType, *returnTypes.front());
retSizeOnStack = returnTypes.front()->sizeOnStack();
}
@ -572,7 +585,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Do not directly visit the identifier, because this way, we can avoid
// the runtime entry label to be created at the creation time context.
CompilerContext::LocationSetter locationSetter2(m_context, *identifier);
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false);
utils().pushCombinedFunctionEntryLabel(
functionDef->resolveVirtual(m_context.mostDerivedContract()),
false
);
shortcutTaken = true;
}
}
@ -992,6 +1008,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Fetch requested length.
acceptAndConvert(*arguments[0], *TypeProvider::uint256());
// Make sure we can allocate memory without overflow
m_context << u256(0xffffffffffffffff);
m_context << Instruction::DUP2;
m_context << Instruction::GT;
m_context.appendConditionalRevert();
// Stack: requested_length
utils().fetchFreeMemoryPointer();
@ -1800,6 +1822,7 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
{
CompilerContext::LocationSetter locationSetter(m_context, _indexAccess);
_indexAccess.baseExpression().accept(*this);
// stack: offset length
Type const& baseType = *_indexAccess.baseExpression().annotation().type;
@ -1815,27 +1838,21 @@ bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess)
acceptAndConvert(*_indexAccess.startExpression(), *TypeProvider::uint256());
else
m_context << u256(0);
// stack: offset length sliceStart
m_context << Instruction::SWAP1;
// stack: offset sliceStart length
if (_indexAccess.endExpression())
acceptAndConvert(*_indexAccess.endExpression(), *TypeProvider::uint256());
else
m_context << Instruction::DUP2;
m_context << Instruction::DUP1;
// stack: offset sliceStart length sliceEnd
m_context.appendInlineAssembly(
Whiskers(R"({
if gt(sliceStart, sliceEnd) { <revertStringStartEnd> }
if gt(sliceEnd, length) { <revertStringEndLength> }
m_context << Instruction::SWAP3;
// stack: sliceEnd sliceStart length offset
offset := add(offset, mul(sliceStart, <stride>))
length := sub(sliceEnd, sliceStart)
})")
("stride", toString(arrayType->calldataStride()))
("revertStringStartEnd", m_context.revertReasonIfDebug("Slice starts after end"))
("revertStringEndLength", m_context.revertReasonIfDebug("Slice is greater than length"))
.render(),
{"offset", "length", "sliceStart", "sliceEnd"}
);
m_context << Instruction::POP << Instruction::POP;
m_context.callYulFunction(m_context.utilFunctions().calldataArrayIndexRangeAccess(*arrayType), 4, 2);
return false;
}
@ -1866,7 +1883,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
// we want to avoid having a reference to the runtime function entry point in the
// constructor context, since this would force the compiler to include unreferenced
// internal functions in the runtime contex.
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef));
utils().pushCombinedFunctionEntryLabel(functionDef->resolveVirtual(m_context.mostDerivedContract()));
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
appendVariable(*variable, static_cast<Expression const&>(_identifier));
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
@ -2438,10 +2455,12 @@ void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType,
void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Expression const& _expression)
{
if (!_variable.isConstant())
setLValueFromDeclaration(_variable, _expression);
else
if (_variable.isConstant())
acceptAndConvert(*_variable.value(), *_variable.annotation().type);
else if (_variable.immutable())
setLValue<ImmutableItem>(_expression, _variable);
else
setLValueFromDeclaration(_variable, _expression);
}
void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression)

View File

@ -144,9 +144,46 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const
m_context << Instruction::POP;
}
ImmutableItem::ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable):
LValue(_compilerContext, _variable.annotation().type), m_variable(_variable)
{
solAssert(_variable.immutable(), "");
}
void ImmutableItem::retrieveValue(SourceLocation const&, bool) const
{
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(!m_context.runtimeContext(), "Tried to read immutable at construction time.");
for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable))
m_context.appendImmutable(slotName);
}
void ImmutableItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
{
CompilerUtils utils(m_context);
solUnimplementedAssert(m_dataType->isValueType(), "");
solAssert(_sourceType.isValueType(), "");
utils.convertType(_sourceType, *m_dataType, true);
m_context << m_context.immutableMemoryOffset(m_variable);
if (_move)
utils.moveIntoStack(m_dataType->sizeOnStack());
else
utils.copyToStackTop(m_dataType->sizeOnStack() + 1, m_dataType->sizeOnStack());
utils.storeInMemoryDynamic(*m_dataType, false);
m_context << Instruction::POP;
}
void ImmutableItem::setToZero(SourceLocation const&, bool) const
{
solAssert(false, "Attempted to set immutable variable to zero.");
}
StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
StorageItem(_compilerContext, *_declaration.annotation().type)
{
solAssert(!_declaration.immutable(), "");
auto const& location = m_context.storageLocationOfVariable(_declaration);
m_context << location.first << u256(location.second);
}

View File

@ -23,6 +23,7 @@
#pragma once
#include <libsolidity/codegen/ArrayUtils.h>
#include <libsolutil/Common.h>
#include <liblangutil/SourceLocation.h>
#include <memory>
#include <vector>
@ -82,12 +83,12 @@ public:
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -108,12 +109,12 @@ public:
MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true);
unsigned sizeOnStack() const override { return 1; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -122,6 +123,30 @@ private:
bool m_padded = false;
};
/**
* Reference to an immutable variable. During contract creation this refers to a location in memory. At the
* end of contract creation the values from these memory locations are copied into all occurrences of the immutable
* variable in the runtime code.
*/
class ImmutableItem: public LValue
{
public:
ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable);
unsigned sizeOnStack() const override { return 0; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
private:
VariableDeclaration const& m_variable;
};
/**
* Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>,
* where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied
@ -136,12 +161,12 @@ public:
StorageItem(CompilerContext& _compilerContext, Type const& _type);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -158,12 +183,12 @@ public:
StorageByteArrayElement(CompilerContext& _compilerContext);
unsigned sizeOnStack() const override { return 2; }
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
@ -180,12 +205,12 @@ public:
TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues);
unsigned sizeOnStack() const override;
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;

View File

@ -658,6 +658,8 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
solAssert(_type.baseType()->isValueType(), "");
string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
@ -794,6 +796,7 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
});
}
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
@ -933,6 +936,28 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
});
}
string YulUtilFunctions::calldataArrayIndexRangeAccess(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
solAssert(_type.isDynamicallySized(), "");
string functionName = "calldata_array_index_range_access_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(offset, length, startIndex, endIndex) -> offsetOut, lengthOut {
if gt(startIndex, endIndex) { <revertSliceStartAfterEnd> }
if gt(endIndex, length) { <revertSliceGreaterThanLength> }
offsetOut := add(offset, mul(startIndex, <stride>))
lengthOut := sub(endIndex, startIndex)
}
)")
("functionName", functionName)
("stride", to_string(_type.calldataStride()))
("revertSliceStartAfterEnd", revertReasonIfDebug("Slice starts after end"))
("revertSliceGreaterThanLength", revertReasonIfDebug("Slice is greater than length"))
.render();
});
}
string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
{
solAssert(_type.isDynamicallyEncoded(), "");
@ -1156,7 +1181,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, value)
}
}
)")
("functionName", functionName)
.render();
@ -1180,7 +1205,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, <cleanup>(value))
}
}
)")
("functionName", functionName)
("cleanup", cleanupFunction(_type))
@ -1312,28 +1337,112 @@ string YulUtilFunctions::allocationFunction()
});
}
string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
if (_type.baseType()->hasSimpleZeroValueInMemory())
return zeroMemoryFunction(*_type.baseType());
return zeroComplexMemoryArrayFunction(_type);
}
string functionName = "allocate_memory_array_" + _type.identifier();
string YulUtilFunctions::zeroMemoryFunction(Type const& _type)
{
solAssert(_type.hasSimpleZeroValueInMemory(), "");
string functionName = "zero_memory_chunk_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
memPtr := <alloc>(<allocSize>(length))
<?dynamic>
mstore(memPtr, length)
</dynamic>
function <functionName>(dataStart, dataSizeInBytes) {
calldatacopy(dataStart, calldatasize(), dataSizeInBytes)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::zeroComplexMemoryArrayFunction(ArrayType const& _type)
{
solAssert(!_type.baseType()->hasSimpleZeroValueInMemory(), "");
string functionName = "zero_complex_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
solAssert(_type.memoryStride() == 32, "");
return Whiskers(R"(
function <functionName>(dataStart, dataSizeInBytes) {
for {let i := 0} lt(i, dataSizeInBytes) { i := add(i, <stride>) } {
mstore(add(dataStart, i), <zeroValue>())
}
}
)")
("functionName", functionName)
("stride", to_string(_type.memoryStride()))
("zeroValue", zeroValueFunction(*_type.baseType(), false))
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
string functionName = "allocate_and_zero_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
let allocSize := <allocSize>(length)
memPtr := <alloc>(allocSize)
let dataStart := memPtr
let dataSize := allocSize
<?dynamic>
dataStart := add(dataStart, 32)
dataSize := sub(dataSize, 32)
mstore(memPtr, length)
</dynamic>
<zeroArrayFunction>(dataStart, dataSize)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("zeroArrayFunction", zeroMemoryArrayFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
{
string functionName = "allocate_and_initialize_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
let allocSize := <allocSize>()
memPtr := <alloc>(allocSize)
let offset := memPtr
<#member>
mstore(offset, <zeroValue>())
offset := add(offset, 32)
</member>
}
)");
templ("functionName", functionName);
templ("alloc", allocationFunction());
TypePointers const& members = _type.memoryMemberTypes();
templ("allocSize", _type.memoryDataSize().str());
vector<map<string, string>> memberParams(members.size());
for (size_t i = 0; i < members.size(); ++i)
{
solAssert(members[i]->memoryHeadSize() == 32, "");
solAssert(members[i]->dataStoredIn(DataLocation::Memory), "");
memberParams[i]["zeroValue"] = zeroValueFunction(*members[i], false);
}
templ("member", memberParams);
return templ.render();
});
}
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::Function)
@ -1365,6 +1474,37 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
});
}
if (_from.category() == Type::Category::ArraySlice)
{
solAssert(_from.isDynamicallySized(), "");
solAssert(_from.dataStoredIn(DataLocation::CallData), "");
solAssert(_to.category() == Type::Category::Array, "");
ArraySliceType const& fromType = dynamic_cast<ArraySliceType const&>(_from);
ArrayType const& targetType = dynamic_cast<ArrayType const&>(_to);
solAssert(
*fromType.arrayType().baseType() == *targetType.baseType(),
"Converting arrays of different type is not possible"
);
string const functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(offset, length) -> outOffset, outLength {
outOffset := offset
outLength := length
}
)")
("functionName", functionName)
.render();
});
}
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to);
@ -1831,23 +1971,58 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type)
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!");
solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented");
solAssert(_type.category() != Type::Category::Mapping, "");
string const functionName = "zero_value_for_" + _type.identifier();
string const functionName = "zero_value_for_" + string(_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> ret {
<body>
}
)")
FunctionType const* fType = dynamic_cast<FunctionType const*>(&_type);
if (fType && fType->kind() == FunctionType::Kind::External && _splitFunctionTypes)
return Whiskers(R"(
function <functionName>() -> retAddress, retFunction {
retAddress := 0
retFunction := 0
}
)")
("functionName", functionName)
("body", "ret := 0x0")
.render();
});
Whiskers templ(R"(
function <functionName>() -> ret {
ret := <zeroValue>
}
)");
templ("functionName", functionName);
if (_type.isValueType())
{
solAssert((
_type.hasSimpleZeroValueInMemory() ||
(fType && (fType->kind() == FunctionType::Kind::Internal || fType->kind() == FunctionType::Kind::External))
), "");
templ("zeroValue", "0");
}
else
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type))
{
if (_type.isDynamicallySized())
templ("zeroValue", to_string(CompilerUtils::zeroPointer));
else
templ("zeroValue", allocateAndInitializeMemoryArrayFunction(*arrayType) + "(" + to_string(unsigned(arrayType->length())) + ")");
}
else if (auto const* structType = dynamic_cast<StructType const*>(&_type))
templ("zeroValue", allocateAndInitializeMemoryStructFunction(*structType) + "()");
else
solUnimplementedAssert(false, "");
}
return templ.render();
});
}
string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)

View File

@ -37,6 +37,7 @@ class Type;
class ArrayType;
class MappingType;
class IntegerType;
class StructType;
/**
* Component that can generate various useful Yul functions.
@ -154,6 +155,7 @@ public:
/// to store an array in memory given its length (internally encoded, not ABI encoded).
/// The function reverts for too large lengths.
std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
/// for the data position of an array which is stored in that slot / memory area / calldata area.
@ -175,6 +177,11 @@ public:
/// signature: (baseRef, index) -> offset[, length]
std::string calldataArrayIndexAccessFunction(ArrayType const& _type);
/// @returns the name of a function that returns offset and length for array slice
/// for the given array offset, length and start and end indices for slice
/// signature: (arrayOffset, arrayLength, sliceStart, sliceEnd) -> offset, length
std::string calldataArrayIndexRangeAccess(ArrayType const& _type);
/// @returns the name of a function that follows a calldata tail while performing
/// bounds checks.
/// signature: (baseRef, tailPointer) -> offset[, length]
@ -245,10 +252,27 @@ public:
/// Return value: pointer
std::string allocationFunction();
/// @returns the name of a function that allocates a memory array.
/// @returns the name of a function that zeroes an array.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that zeroes a chunk of memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryFunction(Type const& _type);
/// @returns the name of a function that zeroes an array
/// where the base does not have simple zero value in memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroComplexMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory array.
/// For dynamic arrays it adds space for length and stores it.
/// signature: (length) -> memPtr
std::string allocateMemoryArrayFunction(ArrayType const& _type);
std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory struct.
/// signature: (members) -> memPtr
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
/// @returns the name of the function that converts a value of type @a _from
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
@ -283,8 +307,9 @@ public:
std::string negateNumberCheckedFunction(Type const& _type);
/// @returns the name of a function that returns the zero value for the
/// provided type
std::string zeroValueFunction(Type const& _type);
/// provided type.
/// @param _splitFunctionTypes if false, returns two zeroes
std::string zeroValueFunction(Type const& _type, bool _splitFunctionTypes = true);
/// @returns the name of a function that will set the given storage item to
/// zero

View File

@ -22,6 +22,7 @@
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
@ -31,6 +32,12 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
ContractDefinition const& IRGenerationContext::mostDerivedContract() const
{
solAssert(m_mostDerivedContract, "Most derived contract requested but not set.");
return *m_mostDerivedContract;
}
IRVariable const& IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl)
{
auto const& [it, didInsert] = m_localVariables.emplace(
@ -70,26 +77,9 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
// but since we do not work with jump positions anymore, this should not be a problem, right?
string name = _function.name();
FunctionType functionType(_function);
for (auto const& contract: m_inheritanceHierarchy)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
function->name() == name &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
}
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
{
return functionName(virtualFunction(_functionDeclaration));
return functionName(_functionDeclaration.resolveVirtual(mostDerivedContract()));
}
string IRGenerationContext::newYulVariable()
@ -120,12 +110,13 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
templ("arrow", _out > 0 ? "->" : "");
templ("out", suffixedVariableNameList("out_", 0, _out));
vector<map<string, string>> functions;
for (auto const& contract: m_inheritanceHierarchy)
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
FunctionType const* functionType = TypeProvider::function(*function)->asCallableFunction(false);
!function->isConstructor() &&
function->parameters().size() == _in &&
function->returnParameters().size() == _out
TupleType(functionType->parameterTypes()).sizeOnStack() == _in &&
TupleType(functionType->returnParameterTypes()).sizeOnStack() == _out
)
{
// 0 is reserved for uninitialized function pointers

View File

@ -61,11 +61,12 @@ public:
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
/// Sets the most derived contract (the one currently being compiled)>
void setMostDerivedContract(ContractDefinition const& _mostDerivedContract)
{
m_inheritanceHierarchy = std::move(_hierarchy);
m_mostDerivedContract = &_mostDerivedContract;
}
ContractDefinition const& mostDerivedContract() const;
IRVariable const& addLocalVariable(VariableDeclaration const& _varDecl);
@ -81,7 +82,6 @@ public:
std::string functionName(FunctionDefinition const& _function);
std::string functionName(VariableDeclaration const& _varDecl);
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
std::string newYulVariable();
@ -103,7 +103,7 @@ private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
ContractDefinition const* m_mostDerivedContract = nullptr;
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;

View File

@ -110,7 +110,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
t("functions", m_context.functionCollector().requestedFunctions());
resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
@ -133,6 +133,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
return m_context.functionCollector().createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
<initReturnVariables>
<body>
}
)");
@ -142,9 +143,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string retParams;
string retInit;
for (auto const& varDecl: _function.returnParameters())
{
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
retInit += generateInitialAssignment(*varDecl);
}
t("returns", retParams.empty() ? "" : " -> " + retParams);
t("initReturnVariables", retInit);
t("body", generate(_function.body()));
return t.render();
});
@ -157,6 +163,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
Type const* type = _varDecl.annotation().type;
solAssert(!_varDecl.isConstant(), "");
solAssert(!_varDecl.immutable(), "");
solAssert(_varDecl.isStateVariable(), "");
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
@ -225,6 +232,13 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
}
}
string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl)
{
IRGeneratorForStatements generator(m_context, m_utils);
generator.initializeLocalVar(_varDecl);
return generator.code();
}
string IRGenerator::constructorCode(ContractDefinition const& _contract)
{
// Initialization of state variables in base-to-derived order.
@ -249,7 +263,7 @@ string IRGenerator::constructorCode(ContractDefinition const& _contract)
IRGeneratorForStatements generator{m_context, m_utils};
for (VariableDeclaration const* variable: contract->stateVariables())
if (!variable->isConstant())
if (!variable->isConstant() && !variable->immutable())
generator.initializeStateVar(*variable);
out << generator.code();
@ -258,11 +272,28 @@ string IRGenerator::constructorCode(ContractDefinition const& _contract)
if (constructor)
{
solUnimplementedAssert(constructor->parameters().empty(), "");
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
unsigned paramVars = make_shared<TupleType>(constructor->functionType(false)->parameterTypes())->sizeOnStack();
// TODO base constructors
Whiskers t(R"X(
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
out << m_context.functionName(*constructor) + "()\n";
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
<assignToParams> <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
<constructorName>(<params>)
)X");
t("object", creationObjectName(_contract));
t("allocate", m_utils.allocationFunction());
t("assignToParams", paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ");
t("params", suffixedVariableNameList("param_", 0, paramVars));
t("abiDecode", abiFunctions.tupleDecoder(constructor->functionType(false)->parameterTypes(), true));
t("constructorName", m_context.functionName(*constructor));
out << t.render();
}
return out.str();
@ -388,7 +419,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
);
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
m_context.setMostDerivedContract(_contract);
for (auto const& var: ContractType(_contract).stateVariables())
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}

View File

@ -61,6 +61,9 @@ private:
/// Generates a getter for the given declaration and returns its name
std::string generateGetter(VariableDeclaration const& _varDecl);
/// Generates code that assigns the initial value of the respective type.
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
std::string constructorCode(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();

View File

@ -140,6 +140,7 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
{
solAssert(m_context.isStateVariable(_varDecl), "Must be a state variable.");
solAssert(!_varDecl.isConstant(), "");
solAssert(!_varDecl.immutable(), "");
if (_varDecl.value())
{
_varDecl.value()->accept(*this);
@ -153,6 +154,19 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
}
}
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
{
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
auto const* type = _varDecl.type();
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
return;
IRVariable zero = zeroValue(*type);
assign(m_context.localVariable(_varDecl), zero);
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (Expression const* expression = _varDeclStatement.initialValue())
@ -178,7 +192,10 @@ void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _var
else
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
{
declare(m_context.addLocalVariable(*decl));
initializeLocalVar(*decl);
}
}
bool IRGeneratorForStatements::visit(Conditional const& _conditional)
@ -567,7 +584,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
define(_functionCall) <<
m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) <<
m_context.internalDispatch(
TupleType(functionType->parameterTypes()).sizeOnStack(),
TupleType(functionType->returnParameterTypes()).sizeOnStack()
) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
@ -666,7 +686,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
define(_functionCall) <<
m_utils.allocateMemoryArrayFunction(arrayType) <<
m_utils.allocateAndInitializeMemoryArrayFunction(arrayType) <<
"(" <<
value.commaSeparatedList() <<
")\n";
@ -739,6 +759,25 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
}
void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options)
{
FunctionType const& previousType = dynamic_cast<FunctionType const&>(*_options.expression().annotation().type);
solUnimplementedAssert(!previousType.bound(), "");
// Copy over existing values.
for (auto const& item: previousType.stackItems())
define(IRVariable(_options).part(get<0>(item)), IRVariable(_options.expression()).part(get<0>(item)));
for (size_t i = 0; i < _options.names().size(); ++i)
{
string const& name = *_options.names()[i];
solAssert(name == "salt" || name == "gas" || name == "value", "");
define(IRVariable(_options).part(name), *_options.options()[i]);
}
}
void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{
ASTString const& member = _memberAccess.memberName();
@ -981,9 +1020,16 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
}
});
}
else if (baseType.category() == Type::Category::Array)
else if (baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice)
{
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
ArrayType const& arrayType =
baseType.category() == Type::Category::Array ?
dynamic_cast<ArrayType const&>(baseType) :
dynamic_cast<ArraySliceType const&>(baseType).arrayType();
if (baseType.category() == Type::Category::ArraySlice)
solAssert(arrayType.dataStoredIn(DataLocation::CallData) && arrayType.isDynamicallySized(), "");
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
switch (arrayType.location())
@ -1066,9 +1112,50 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
solAssert(false, "Index access only allowed for mappings or arrays.");
}
void IRGeneratorForStatements::endVisit(IndexRangeAccess const&)
void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
solUnimplementedAssert(false, "Index range accesses not yet implemented.");
Type const& baseType = *_indexRangeAccess.baseExpression().annotation().type;
solAssert(
baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice,
"Index range accesses is available only on arrays and array slices."
);
ArrayType const& arrayType =
baseType.category() == Type::Category::Array ?
dynamic_cast<ArrayType const &>(baseType) :
dynamic_cast<ArraySliceType const &>(baseType).arrayType();
switch (arrayType.location())
{
case DataLocation::CallData:
{
solAssert(baseType.isDynamicallySized(), "");
IRVariable sliceStart{m_context.newYulVariable(), *TypeProvider::uint256()};
if (_indexRangeAccess.startExpression())
define(sliceStart, IRVariable{*_indexRangeAccess.startExpression()});
else
define(sliceStart) << u256(0) << "\n";
IRVariable sliceEnd{
m_context.newYulVariable(),
*TypeProvider::uint256()
};
if (_indexRangeAccess.endExpression())
define(sliceEnd, IRVariable{*_indexRangeAccess.endExpression()});
else
define(sliceEnd, IRVariable{_indexRangeAccess.baseExpression()}.part("length"));
IRVariable range{_indexRangeAccess};
define(range) <<
m_utils.calldataArrayIndexRangeAccess(arrayType) << "(" <<
IRVariable{_indexRangeAccess.baseExpression()}.commaSeparatedList() << ", " <<
sliceStart.name() << ", " <<
sliceEnd.name() << ")\n";
break;
}
default:
solUnimplementedAssert(false, "Index range accesses is implemented only on calldata arrays.");
}
}
void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
@ -1097,13 +1184,14 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
return;
}
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
define(_identifier) << to_string(m_context.virtualFunction(*functionDef).id()) << "\n";
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
{
// TODO for the constant case, we have to be careful:
// If the value is visited twice, `defineExpression` is called twice on
// the same expression.
solUnimplementedAssert(!varDecl->isConstant(), "");
solUnimplementedAssert(!varDecl->immutable(), "");
if (m_context.isLocalVariable(*varDecl))
setLValue(_identifier, IRLValue{
*varDecl->annotation().type,
@ -1367,6 +1455,12 @@ std::ostream& IRGeneratorForStatements::define(IRVariable const& _var)
return m_code;
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
}
void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable const& _rhs, bool _declare)
{
string output;
@ -1386,10 +1480,15 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable
_rhs.commaSeparatedList() <<
")\n";
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
IRVariable irVar{
"zero_value_for_type_" + _type.identifier() + m_context.newYulVariable(),
_type
};
define(irVar) << m_utils.zeroValueFunction(_type, _splitFunctionTypes) << "()\n";
return irVar;
}
void IRGeneratorForStatements::appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr)
@ -1519,7 +1618,7 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
solAssert(dynamic_cast<ReferenceType const*>(&_lvalue.type), "");
auto const* valueReferenceType = dynamic_cast<ReferenceType const*>(&_value.type());
solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory), "");
m_code << "mstore(" + _memory.address + ", " + _value.name() + ")\n";
m_code << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n";
}
},
[&](IRLValue::Stack const& _stack) { assign(_stack.variable, _value); },

View File

@ -46,6 +46,8 @@ public:
/// Generates code to initialize the given state variable.
void initializeStateVar(VariableDeclaration const& _varDecl);
/// Generates code to initialize the given local variable.
void initializeLocalVar(VariableDeclaration const& _varDecl);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
@ -60,6 +62,7 @@ public:
void endVisit(UnaryOperation const& _unaryOperation) override;
bool visit(BinaryOperation const& _binOp) override;
void endVisit(FunctionCall const& _funCall) override;
void endVisit(FunctionCallOptions const& _funCallOptions) override;
void endVisit(MemberAccess const& _memberAccess) override;
bool visit(InlineAssembly const& _inlineAsm) override;
void endVisit(IndexAccess const& _indexAccess) override;
@ -99,6 +102,11 @@ private:
void declareAssign(IRVariable const& _var, IRVariable const& _value, bool _define);
/// @returns an IRVariable with the zero
/// value of @a _type.
/// @param _splitFunctionTypes if false, returns two zeroes
IRVariable zeroValue(Type const& _type, bool _splitFunctionTypes = true);
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);

View File

@ -432,12 +432,6 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall)
m_context.newValue(*param);
m_context.setUnknownValue(*param);
}
m_errorReporter.warning(
_funCall.location(),
"Assertion checker does not support recursive function calls.",
SecondarySourceLocation().append("Starting from function:", funDef->location())
);
}
else
{

View File

@ -79,10 +79,9 @@ void CHC::analyze(SourceUnit const& _source)
resetSourceAnalysis();
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto genesisSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
boolSort
smt::SortProvider::boolSort
);
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
addRule(genesis(), "genesis");
@ -131,12 +130,9 @@ bool CHC::visit(ContractDefinition const& _contract)
clearIndices(&_contract);
// TODO create static instances for Bool/Int sorts in SolverInterface.
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto errorFunctionSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
boolSort
smt::SortProvider::boolSort
);
string suffix = _contract.name() + "_" + to_string(_contract.id());
@ -459,6 +455,8 @@ void CHC::endVisit(FunctionCall const& _funCall)
SMTEncoder::endVisit(_funCall);
break;
case FunctionType::Kind::Internal:
internalFunctionCall(_funCall);
break;
case FunctionType::Kind::External:
case FunctionType::Kind::DelegateCall:
case FunctionType::Kind::BareCall:
@ -525,6 +523,39 @@ void CHC::visitAssert(FunctionCall const& _funCall)
m_context.addAssertion(m_error.currentValue() == previousError);
}
void CHC::internalFunctionCall(FunctionCall const& _funCall)
{
solAssert(m_currentContract, "");
auto const* function = functionCallToDefinition(_funCall);
if (function)
{
if (m_currentFunction && !m_currentFunction->isConstructor())
m_callGraph[m_currentFunction].insert(function);
else
m_callGraph[m_currentContract].insert(function);
auto const* contract = function->annotation().contract;
// Libraries can have constants as their "state" variables,
// so we need to ensure they were constructed correctly.
if (contract->isLibrary())
m_context.addAssertion(interface(*contract));
}
auto previousError = m_error.currentValue();
m_context.addAssertion(predicate(_funCall));
connectBlocks(
m_currentBlock,
(m_currentFunction && !m_currentFunction->isConstructor()) ? summary(*m_currentFunction) : summary(*m_currentContract),
(m_error.currentValue() > 0)
);
m_context.addAssertion(m_error.currentValue() == 0);
m_error.increaseIndex();
m_context.addAssertion(m_error.currentValue() == previousError);
}
void CHC::unknownFunctionCall(FunctionCall const&)
{
/// Function calls are not handled at the moment,
@ -580,12 +611,7 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
bool CHC::shouldVisit(FunctionDefinition const& _function) const
{
if (
_function.isPublic() &&
_function.isImplemented()
)
return true;
return false;
return _function.isImplemented();
}
void CHC::setCurrentBlock(
@ -634,29 +660,25 @@ vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
smt::SortPointer CHC::constructorSort()
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts,
smt::SortProvider::boolSort
);
}
smt::SortPointer CHC::interfaceSort()
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
m_stateSorts,
boolSort
smt::SortProvider::boolSort
);
}
smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
stateSorts(_contract),
boolSort
smt::SortProvider::boolSort
);
}
@ -673,8 +695,6 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
/// - 1 set of output variables
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
@ -682,8 +702,8 @@ smt::SortPointer CHC::sort(FunctionDefinition const& _function)
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
smt::SortProvider::boolSort
);
}
@ -695,13 +715,12 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
solAssert(fSort, "");
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
vector<smt::SortPointer> varSorts;
for (auto const& var: m_currentFunction->localVariables())
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
fSort->domain + varSorts,
boolSort
smt::SortProvider::boolSort
);
}
@ -710,16 +729,14 @@ smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractD
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
auto sorts = stateSorts(_contract);
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts, outputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + sorts + inputSorts + sorts + outputSorts,
boolSort
vector<smt::SortPointer>{smt::SortProvider::intSort} + sorts + inputSorts + sorts + outputSorts,
smt::SortProvider::boolSort
);
}
@ -919,6 +936,34 @@ smt::Expression CHC::predicate(
return _block(_arguments);
}
smt::Expression CHC::predicate(FunctionCall const& _funCall)
{
auto const* function = functionCallToDefinition(_funCall);
if (!function)
return smt::Expression(true);
m_error.increaseIndex();
vector<smt::Expression> args{m_error.currentValue()};
auto const* contract = function->annotation().contract;
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : currentStateVariables();
args += symbolicArguments(_funCall);
for (auto const& var: m_stateVariables)
m_context.variable(*var)->increaseIndex();
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
auto const& returnParams = function->returnParameters();
for (auto param: returnParams)
if (m_context.knownVariable(*param))
m_context.variable(*param)->increaseIndex();
else
createVariable(*param);
for (auto const& var: function->returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
return (*m_summaries.at(contract).at(function))(args);
}
void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
{
m_interface->addRule(_rule, _ruleName);

View File

@ -76,6 +76,7 @@ private:
void endVisit(Continue const& _node) override;
void visitAssert(FunctionCall const& _funCall);
void internalFunctionCall(FunctionCall const& _funCall);
void unknownFunctionCall(FunctionCall const& _funCall);
//@}
@ -164,6 +165,8 @@ private:
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
/// @returns a predicate application over @param _arguments.
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments);
/// @returns the summary predicate for the called function.
smt::Expression predicate(FunctionCall const& _funCall);
/// @returns a predicate that defines a constructor summary.
smt::Expression summary(ContractDefinition const& _contract);
/// @returns a predicate that defines a function summary.

View File

@ -28,8 +28,8 @@ EncodingContext::EncodingContext():
m_thisAddress(make_unique<SymbolicAddressVariable>("this", *this))
{
auto sort = make_shared<ArraySort>(
make_shared<Sort>(Kind::Int),
make_shared<Sort>(Kind::Int)
SortProvider::intSort,
SortProvider::intSort
);
m_balances = make_unique<SymbolicVariable>(sort, "balances", *this);
}

View File

@ -673,7 +673,6 @@ void SMTEncoder::visitAssert(FunctionCall const& _funCall)
auto const& args = _funCall.arguments();
solAssert(args.size() == 1, "");
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
addPathImpliedExpression(expr(*args.front()));
}
void SMTEncoder::visitRequire(FunctionCall const& _funCall)

View File

@ -17,6 +17,8 @@
#pragma once
#include <libsolidity/formal/Sorts.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/interface/ReadFile.h>
#include <liblangutil/Exceptions.h>
@ -52,94 +54,6 @@ enum class CheckResult
SATISFIABLE, UNSATISFIABLE, UNKNOWN, CONFLICTING, ERROR
};
enum class Kind
{
Int,
Bool,
Function,
Array,
Sort
};
struct Sort
{
Sort(Kind _kind):
kind(_kind) {}
virtual ~Sort() = default;
virtual bool operator==(Sort const& _other) const { return kind == _other.kind; }
Kind const kind;
};
using SortPointer = std::shared_ptr<Sort>;
struct FunctionSort: public Sort
{
FunctionSort(std::vector<SortPointer> _domain, SortPointer _codomain):
Sort(Kind::Function), domain(std::move(_domain)), codomain(std::move(_codomain)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherFunction = dynamic_cast<FunctionSort const*>(&_other);
solAssert(_otherFunction, "");
if (domain.size() != _otherFunction->domain.size())
return false;
if (!std::equal(
domain.begin(),
domain.end(),
_otherFunction->domain.begin(),
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
))
return false;
solAssert(codomain, "");
solAssert(_otherFunction->codomain, "");
return *codomain == *_otherFunction->codomain;
}
std::vector<SortPointer> domain;
SortPointer codomain;
};
struct ArraySort: public Sort
{
/// _domain is the sort of the indices
/// _range is the sort of the values
ArraySort(SortPointer _domain, SortPointer _range):
Sort(Kind::Array), domain(std::move(_domain)), range(std::move(_range)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherArray = dynamic_cast<ArraySort const*>(&_other);
solAssert(_otherArray, "");
solAssert(_otherArray->domain, "");
solAssert(_otherArray->range, "");
solAssert(domain, "");
solAssert(range, "");
return *domain == *_otherArray->domain && *range == *_otherArray->range;
}
SortPointer domain;
SortPointer range;
};
struct SortSort: public Sort
{
SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
solAssert(_otherSort, "");
solAssert(_otherSort->inner, "");
solAssert(inner, "");
return *inner == *_otherSort->inner;
}
SortPointer inner;
};
// Forward declaration.
SortPointer smtSort(Type const& _type);

View File

@ -0,0 +1,29 @@
/*
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/>.
*/
#include <libsolidity/formal/Sorts.h>
using namespace std;
namespace solidity::frontend::smt
{
shared_ptr<Sort> const SortProvider::boolSort{make_shared<Sort>(Kind::Bool)};
shared_ptr<Sort> const SortProvider::intSort{make_shared<Sort>(Kind::Int)};
}

125
libsolidity/formal/Sorts.h Normal file
View File

@ -0,0 +1,125 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <liblangutil/Exceptions.h>
#include <libsolutil/Common.h>
#include <libsolutil/Exceptions.h>
#include <memory>
#include <vector>
namespace solidity::frontend::smt
{
enum class Kind
{
Int,
Bool,
Function,
Array,
Sort
};
struct Sort
{
Sort(Kind _kind):
kind(_kind) {}
virtual ~Sort() = default;
virtual bool operator==(Sort const& _other) const { return kind == _other.kind; }
Kind const kind;
};
using SortPointer = std::shared_ptr<Sort>;
struct FunctionSort: public Sort
{
FunctionSort(std::vector<SortPointer> _domain, SortPointer _codomain):
Sort(Kind::Function), domain(std::move(_domain)), codomain(std::move(_codomain)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherFunction = dynamic_cast<FunctionSort const*>(&_other);
solAssert(_otherFunction, "");
if (domain.size() != _otherFunction->domain.size())
return false;
if (!std::equal(
domain.begin(),
domain.end(),
_otherFunction->domain.begin(),
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
))
return false;
solAssert(codomain, "");
solAssert(_otherFunction->codomain, "");
return *codomain == *_otherFunction->codomain;
}
std::vector<SortPointer> domain;
SortPointer codomain;
};
struct ArraySort: public Sort
{
/// _domain is the sort of the indices
/// _range is the sort of the values
ArraySort(SortPointer _domain, SortPointer _range):
Sort(Kind::Array), domain(std::move(_domain)), range(std::move(_range)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherArray = dynamic_cast<ArraySort const*>(&_other);
solAssert(_otherArray, "");
solAssert(_otherArray->domain, "");
solAssert(_otherArray->range, "");
solAssert(domain, "");
solAssert(range, "");
return *domain == *_otherArray->domain && *range == *_otherArray->range;
}
SortPointer domain;
SortPointer range;
};
struct SortSort: public Sort
{
SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {}
bool operator==(Sort const& _other) const override
{
if (!Sort::operator==(_other))
return false;
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
solAssert(_otherSort, "");
solAssert(_otherSort->inner, "");
solAssert(inner, "");
return *inner == *_otherSort->inner;
}
SortPointer inner;
};
/** Frequently used sorts.*/
struct SortProvider
{
static std::shared_ptr<Sort> const boolSort;
static std::shared_ptr<Sort> const intSort;
};
}

View File

@ -32,9 +32,9 @@ SortPointer smtSort(frontend::Type const& _type)
switch (smtKind(_type.category()))
{
case Kind::Int:
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
case Kind::Bool:
return make_shared<Sort>(Kind::Bool);
return SortProvider::boolSort;
case Kind::Function:
{
auto fType = dynamic_cast<frontend::FunctionType const*>(&_type);
@ -45,10 +45,10 @@ SortPointer smtSort(frontend::Type const& _type)
// TODO change this when we support tuples.
if (returnTypes.size() == 0)
// We cannot declare functions without a return sort, so we use the smallest.
returnSort = make_shared<Sort>(Kind::Bool);
returnSort = SortProvider::boolSort;
else if (returnTypes.size() > 1)
// Abstract sort.
returnSort = make_shared<Sort>(Kind::Int);
returnSort = SortProvider::intSort;
else
returnSort = smtSort(*returnTypes.front());
return make_shared<FunctionSort>(parameterSorts, returnSort);
@ -65,20 +65,19 @@ SortPointer smtSort(frontend::Type const& _type)
{
auto stringLitType = dynamic_cast<frontend::StringLiteralType const*>(&_type);
solAssert(stringLitType, "");
auto intSort = make_shared<Sort>(Kind::Int);
return make_shared<ArraySort>(intSort, intSort);
return make_shared<ArraySort>(SortProvider::intSort, SortProvider::intSort);
}
else
{
solAssert(isArray(_type.category()), "");
auto arrayType = dynamic_cast<frontend::ArrayType const*>(&_type);
solAssert(arrayType, "");
return make_shared<ArraySort>(make_shared<Sort>(Kind::Int), smtSortAbstractFunction(*arrayType->baseType()));
return make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
}
}
default:
// Abstract case.
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
}
}
@ -93,7 +92,7 @@ vector<SortPointer> smtSort(vector<frontend::TypePointer> const& _types)
SortPointer smtSortAbstractFunction(frontend::Type const& _type)
{
if (isFunction(_type.category()))
return make_shared<Sort>(Kind::Int);
return SortProvider::intSort;
return smtSort(_type);
}

View File

@ -218,15 +218,28 @@ SymbolicArrayVariable::SymbolicArrayVariable(
solAssert(isArray(m_type->category()), "");
}
SymbolicArrayVariable::SymbolicArrayVariable(
SortPointer _sort,
string _uniqueName,
EncodingContext& _context
):
SymbolicVariable(move(_sort), move(_uniqueName), _context)
{
solAssert(m_sort->kind == Kind::Array, "");
}
smt::Expression SymbolicArrayVariable::currentValue(frontend::TypePointer const& _targetType) const
{
if (_targetType)
{
solAssert(m_originalType, "");
// StringLiterals are encoded as SMT arrays in the generic case,
// but they can also be compared/assigned to fixed bytes, in which
// case they'd need to be encoded as numbers.
if (auto strType = dynamic_cast<StringLiteralType const*>(m_originalType))
if (_targetType->category() == frontend::Type::Category::FixedBytes)
return smt::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add)));
}
return SymbolicVariable::currentValue(_targetType);
}

View File

@ -47,6 +47,8 @@ public:
EncodingContext& _context
);
SymbolicVariable(SymbolicVariable&&) = default;
virtual ~SymbolicVariable() = default;
virtual Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const;
@ -212,6 +214,13 @@ public:
std::string _uniqueName,
EncodingContext& _context
);
SymbolicArrayVariable(
SortPointer _sort,
std::string _uniqueName,
EncodingContext& _context
);
SymbolicArrayVariable(SymbolicArrayVariable&&) = default;
Expression currentValue(frontend::TypePointer const& _targetType = TypePointer{}) const override;
};

View File

@ -35,6 +35,7 @@
#include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/analysis/ViewPureChecker.h>
#include <libsolidity/analysis/ImmutableValidator.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
@ -383,6 +384,15 @@ bool CompilerStack::analyze()
noErrors = false;
}
// Check that immutable variables are never read in c'tors and assigned
// exactly once
if (noErrors)
for (Source const* source: m_sourceOrder)
if (source->ast)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
ImmutableValidator(m_errorReporter, *contract).analyze();
if (noErrors)
{
// Control flow graph generator and analyzer. It can check for issues such as
@ -899,8 +909,7 @@ h256 const& CompilerStack::Source::swarmHash() const
string const& CompilerStack::Source::ipfsUrl() const
{
if (ipfsUrlCached.empty())
if (scanner->source().size() < 1024 * 256)
ipfsUrlCached = "dweb:/ipfs/" + util::ipfsHashBase58(scanner->source());
ipfsUrlCached = "dweb:/ipfs/" + util::ipfsHashBase58(scanner->source());
return ipfsUrlCached;
}
@ -1373,10 +1382,7 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
MetadataCBOREncoder encoder;
if (m_metadataHash == MetadataHash::IPFS)
{
solAssert(_metadata.length() < 1024 * 256, "Metadata too large.");
encoder.pushBytes("ipfs", util::ipfsHash(_metadata));
}
else if (m_metadataHash == MetadataHash::Bzzr1)
encoder.pushBytes("bzzr1", util::bzzr1Hash(_metadata).asBytes());
else

View File

@ -54,7 +54,7 @@ inline std::optional<RevertStrings> revertStringsFromString(std::string const& _
for (auto i: {RevertStrings::Default, RevertStrings::Strip, RevertStrings::Debug, RevertStrings::VerboseDebug})
if (revertStringsToString(i) == _str)
return i;
return {};
return std::nullopt;
}
}

View File

@ -234,6 +234,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
"wast", "wasm", "ewasm.wast", "ewasm.wasm",
"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"
@ -309,13 +310,36 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere
return ret;
}
Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const* _sourceMap)
Json::Value formatImmutableReferences(map<u256, pair<string, vector<size_t>>> const& _immutableReferences)
{
Json::Value ret(Json::objectValue);
for (auto const& immutableReference: _immutableReferences)
{
auto const& [identifier, byteOffsets] = immutableReference.second;
Json::Value array(Json::arrayValue);
for (size_t byteOffset: byteOffsets)
{
Json::Value byteRange(Json::objectValue);
byteRange["start"] = Json::UInt(byteOffset);
byteRange["length"] = Json::UInt(32); // immutable references are currently always 32 bytes wide
array.append(byteRange);
}
ret[identifier] = array;
}
return ret;
}
Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const* _sourceMap, bool _runtimeObject)
{
Json::Value output = Json::objectValue;
output["object"] = _object.toHex();
output["opcodes"] = evmasm::disassemble(_object.bytecode);
output["sourceMap"] = _sourceMap ? *_sourceMap : "";
output["linkReferences"] = formatLinkReferences(_object.linkReferences);
if (_runtimeObject)
output["immutableReferences"] = formatImmutableReferences(_object.immutableReferences);
return output;
}
@ -982,19 +1006,21 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
))
evmData["bytecode"] = collectEVMObject(
compilerStack.object(contractName),
compilerStack.sourceMapping(contractName)
compilerStack.sourceMapping(contractName),
false
);
if (compilationSuccess && isArtifactRequested(
_inputsAndSettings.outputSelection,
file,
name,
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" },
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", "evm.deployedBytecode.immutableReferences" },
wildcardMatchesExperimental
))
evmData["deployedBytecode"] = collectEVMObject(
compilerStack.runtimeObject(contractName),
compilerStack.runtimeSourceMapping(contractName)
compilerStack.runtimeSourceMapping(contractName),
true
);
if (!evmData.empty())
@ -1081,7 +1107,7 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
wildcardMatchesExperimental
))
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get());
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get(), false);
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();

View File

@ -730,8 +730,18 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
{
if (_options.allowIndexed && token == Token::Indexed)
isIndexed = true;
else if (token == Token::Constant)
constantness = VariableDeclaration::Constantness::Constant;
else if (token == Token::Constant || token == Token::Immutable)
{
if (constantness != VariableDeclaration::Constantness::Mutable)
parserError(
string("Constantness already set to ") +
(constantness == VariableDeclaration::Constantness::Constant ? "\"constant\"" : "\"immutable\"")
);
else if (token == Token::Constant)
constantness = VariableDeclaration::Constantness::Constant;
else if (token == Token::Immutable)
constantness = VariableDeclaration::Constantness::Immutable;
}
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
{
if (location != VariableDeclaration::Location::Unspecified)

View File

@ -40,6 +40,21 @@ bytes varintEncoding(size_t _n)
return encoded;
}
bytes encodeByteArray(bytes const& _data)
{
return bytes{0x0a} + varintEncoding(_data.size()) + _data;
}
bytes encodeHash(bytes const& _data)
{
return bytes{0x12, 0x20} + picosha2::hash256(_data);
}
bytes encodeLinkData(bytes const& _data)
{
return bytes{0x12} + varintEncoding(_data.size()) + _data;
}
string base58Encode(bytes const& _data)
{
static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"};
@ -53,36 +68,132 @@ string base58Encode(bytes const& _data)
reverse(output.begin(), output.end());
return output;
}
struct Chunk
{
Chunk() = default;
Chunk(bytes _hash, size_t _size, size_t _blockSize):
hash(std::move(_hash)),
size(_size),
blockSize(_blockSize)
{}
bytes hash = {};
size_t size = 0;
size_t blockSize = 0;
};
using Chunks = vector<Chunk>;
Chunk combineLinks(Chunks& _links)
{
bytes data = {};
bytes lengths = {};
Chunk chunk = {};
for (Chunk& link: _links)
{
chunk.size += link.size;
chunk.blockSize += link.blockSize;
data += encodeLinkData(
bytes {0x0a} +
varintEncoding(link.hash.size()) +
std::move(link.hash) +
bytes{0x12, 0x00, 0x18} +
varintEncoding(link.blockSize)
);
lengths += bytes{0x20} + varintEncoding(link.size);
}
bytes blockData = data + encodeByteArray(bytes{0x08, 0x02, 0x18} + varintEncoding(chunk.size) + lengths);
chunk.blockSize += blockData.size();
chunk.hash = encodeHash(blockData);
return chunk;
}
Chunks buildNextLevel(Chunks& _currentLevel)
{
size_t const maxChildNum = 174;
Chunks nextLevel;
Chunks links;
for (Chunk& chunk: _currentLevel)
{
links.emplace_back(std::move(chunk.hash), chunk.size, chunk.blockSize);
if (links.size() == maxChildNum)
{
nextLevel.emplace_back(combineLinks(links));
links = {};
}
}
if (!links.empty())
nextLevel.emplace_back(combineLinks(links));
return nextLevel;
}
/// Builds a tree starting from the bottom level where nodes are data nodes.
/// Data nodes should be calculated and passed as the only level in chunk levels
/// Each next level is calculated as following:
/// - Pick up to maxChildNum (174) nodes until a whole level is added, group them and pass to the node in the next level
/// - Do this until the current level has only one node, return the hash in that node
bytes groupChunksBottomUp(Chunks _currentLevel)
{
// when we reach root it will be the only node in that level
while (_currentLevel.size() != 1)
_currentLevel = buildNextLevel(_currentLevel);
// top level's only node stores the hash for file
return _currentLevel.front().hash;
}
}
bytes solidity::util::ipfsHash(string _data)
{
assertThrow(_data.length() < 1024 * 256, DataTooLong, "IPFS hash for large (chunked) files not yet implemented.");
size_t const maxChunkSize = 1024 * 256;
size_t chunkCount = _data.length() / maxChunkSize + (_data.length() % maxChunkSize > 0 ? 1 : 0);
chunkCount = chunkCount == 0 ? 1 : chunkCount;
bytes lengthAsVarint = varintEncoding(_data.size());
Chunks allChunks;
bytes protobufEncodedData;
// Type: File
protobufEncodedData += bytes{0x08, 0x02};
if (!_data.empty())
for (unsigned long chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++)
{
// Data (length delimited bytes)
protobufEncodedData += bytes{0x12};
protobufEncodedData += lengthAsVarint;
protobufEncodedData += asBytes(std::move(_data));
bytes chunkBytes = asBytes(
_data.substr(chunkIndex * maxChunkSize, min(maxChunkSize, _data.length() - chunkIndex * maxChunkSize))
);
bytes lengthAsVarint = varintEncoding(chunkBytes.size());
bytes protobufEncodedData;
// Type: File
protobufEncodedData += bytes{0x08, 0x02};
if (!chunkBytes.empty())
{
// Data (length delimited bytes)
protobufEncodedData += bytes{0x12};
protobufEncodedData += lengthAsVarint;
protobufEncodedData += chunkBytes;
}
// filesize: length as varint
protobufEncodedData += bytes{0x18} + lengthAsVarint;
// PBDag:
// Data: (length delimited bytes)
bytes blockData = encodeByteArray(protobufEncodedData);
// Multihash: sha2-256, 256 bits
allChunks.emplace_back(
encodeHash(blockData),
chunkBytes.size(),
blockData.size()
);
}
// filesize: length as varint
protobufEncodedData += bytes{0x18} + lengthAsVarint;
// PBDag:
// Data: (length delimited bytes)
size_t protobufLength = protobufEncodedData.size();
bytes blockData = bytes{0x0a} + varintEncoding(protobufLength) + std::move(protobufEncodedData);
// TODO Handle "large" files with multiple blocks
// Multihash: sha2-256, 256 bits
bytes hash = bytes{0x12, 0x20} + picosha2::hash256(std::move(blockData));
return hash;
return groupChunksBottomUp(std::move(allChunks));
}
string solidity::util::ipfsHashBase58(string _data)

View File

@ -44,7 +44,6 @@ public:
std::vector<unsigned char> toBytes() const { return std::vector<unsigned char>(reinterpret_cast<unsigned char const*>(m_data), reinterpret_cast<unsigned char const*>(m_data) + m_count * sizeof(T)); }
std::string toString() const { return std::string((char const*)m_data, ((char const*)m_data) + m_count * sizeof(T)); }
template <class T2> explicit operator vector_ref<T2>() const { assert(m_count * sizeof(T) / sizeof(T2) * sizeof(T2) / sizeof(T) == m_count); return vector_ref<T2>(reinterpret_cast<T2*>(m_data), m_count * sizeof(T) / sizeof(T2)); }
operator vector_ref<T const>() const { return vector_ref<T const>(m_data, m_count); }
T* data() const { return m_data; }

View File

@ -324,6 +324,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
case Token::Byte:
case Token::Bool:
case Token::Address:
case Token::Var:
{
YulString literal{currentLiteral()};
if (m_dialect.builtin(literal))
@ -513,6 +514,7 @@ YulString Parser::expectAsmIdentifier()
case Token::Address:
case Token::Bool:
case Token::Identifier:
case Token::Var:
break;
default:
expectToken(Token::Identifier);

View File

@ -203,6 +203,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
EthAssemblyAdapter adapter(assembly);
compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation);
object.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble());
yulAssert(object.bytecode->immutableReferences.empty(), "Leftover immutables.");
object.assembly = assembly.assemblyString();
object.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(

79
scripts/common_cmdline.sh Normal file
View File

@ -0,0 +1,79 @@
# ------------------------------------------------------------------------------
# vim:ts=4:et
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016-2019 solidity contributors.
# ------------------------------------------------------------------------------
FULLARGS="--optimize --ignore-missing --combined-json abi,asm,ast,bin,bin-runtime,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc"
OLDARGS="--optimize --combined-json abi,asm,ast,bin,bin-runtime,devdoc,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc"
function compileFull()
{
local expected_exit_code=0
local expect_output=0
if [[ $1 = '-e' ]]; then
expected_exit_code=1
expect_output=1
shift;
fi
if [[ $1 = '-w' ]]; then
expect_output=1
shift;
fi
if [[ $1 = '-o' ]]; then
expect_output=2
shift;
fi
local args=$FULLARGS
if [[ $1 = '-v' ]]; then
if (echo $2 | grep -Po '(?<=0.4.)\d+' >/dev/null); then
patch=$(echo $2 | grep -Po '(?<=0.4.)\d+')
if (( patch < 22 )); then
args=$OLDARGS
fi
fi
shift 2
fi
local files="$*"
local output
local stderr_path=$(mktemp)
set +e
"$SOLC" ${args} ${files} >/dev/null 2>"$stderr_path"
local exit_code=$?
local errors=$(grep -v -E 'Warning: This is a pre-release compiler version|Warning: Experimental features are turned on|pragma experimental ABIEncoderV2|^ +--> |^ +\||^[0-9]+ +\|' < "$stderr_path")
set -e
rm "$stderr_path"
if [[ \
("$exit_code" -ne "$expected_exit_code" || \
( $expect_output -eq 0 && -n "$errors" ) || \
( $expect_output -ne 0 && $expected_exit_code -eq 0 && $expect_output -ne 2 && -z "$errors" ))
]]
then
printError "Unexpected compilation result:"
printError "Expected failure: $expected_exit_code - Expected warning / error output: $expect_output"
printError "Was failure: $exit_code"
echo "$errors"
printError "While calling:"
echo "\"$SOLC\" $ARGS $files"
printError "Inside directory:"
pwd
false
fi
}

View File

@ -0,0 +1,187 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016 solidity contributors.
#------------------------------------------------------------------------------
# This script verifies that the examples compile with the oldest version mentioned in the pragma.
# It does not verify that it cannot be compiled with an older version
# and it also does not verify that it can be compiled with the newest version compatible with the pragma.
set -e
## GLOBAL VARIABLES
REPO_ROOT=$(cd $(dirname "$0")/.. && pwd)
SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build}
source "${REPO_ROOT}/scripts/common.sh"
source "${REPO_ROOT}/scripts/common_cmdline.sh"
function versionGreater()
{
v1=$1
v2=$2
ver1=( ${v1//./ } )
ver2=( ${v2//./ } )
if (( ${ver1[0]} > ${ver2[0]} ))
then
return 0
elif (( ${ver1[0]} == ${ver2[0]} )) && (( ${ver1[1]} > ${ver2[1]} ))
then
return 0
elif (( ${ver1[0]} == ${ver2[0]} )) && (( ${ver1[1]} == ${ver2[1]} )) && (( ${ver1[2]} > ${ver2[2]} ))
then
return 0
fi
return 1
}
function versionEqual()
{
if [ "$1" == "$2" ]
then
return 0
fi
return 1
}
function getAllAvailableVersions()
{
allVersions=()
local allListedVersions=( $(
wget -q -O- https://ethereum.github.io/solc-bin/bin/list.txt |
grep -Po '(?<=soljson-v)\d+.\d+.\d+(?=\+commit)' |
sort -V
) )
for listed in "${allListedVersions[@]}"
do
if versionGreater "$listed" "0.4.10"
then
allVersions+=( $listed )
fi
done
}
function findMinimalVersion()
{
local f=$1
local greater=false
local pragmaVersion
# Get minimum compiler version defined by pragma
if (grep -Po '(?<=pragma solidity >=)\d+.\d+.\d+' "$f" >/dev/null)
then
pragmaVersion="$(grep -Po '(?<=pragma solidity >=)\d+.\d+.\d+' "$f")"
sign=">="
elif (grep -Po '(?<=pragma solidity \^)\d+.\d+.\d+' "$f" >/dev/null)
then
pragmaVersion="$(grep -Po '(?<=pragma solidity \^)\d+.\d+.\d+' "$f")"
sign="^"
elif (grep -Po '(?<=pragma solidity >)\d+.\d+.\d+' "$f" >/dev/null)
then
pragmaVersion="$(grep -Po '(?<=pragma solidity >)\d+.\d+.\d+' "$f")"
sign=">"
greater=true;
else
printError "No valid pragma statement in file. Skipping..."
return
fi
version=""
for ver in "${allVersions[@]}"
do
if versionGreater "$ver" "$pragmaVersion"
then
minVersion="$ver"
break
elif ([ $greater == false ]) && versionEqual "$ver" "$pragmaVersion"
then
version="$ver"
break
fi
done
if [ -z version ]
then
printError "No release $sign$pragmaVersion was listed in available releases!"
fi
}
printTask "Verifying that all examples from the documentation have the correct version range..."
SOLTMPDIR=$(mktemp -d)
(
set -e
cd "$SOLTMPDIR"
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/ docs
getAllAvailableVersions
for f in *.sol
do
# The contributors guide uses syntax tests, but we cannot
# really handle them here.
if grep -E 'DeclarationError:|// ----' "$f" >/dev/null
then
continue
fi
echo "$f"
opts=''
# We expect errors if explicitly stated, or if imports
# are used (in the style guide)
if ( ! grep -E "This will not compile after" "$f" >/dev/null && \
grep -E "This will not compile|import \"" "$f" >/dev/null )
then
opts="-e"
fi
# ignore warnings in this case
opts="$opts -o"
findMinimalVersion $f
if [ -z "$version" ]
then
continue
fi
opts="$opts -v $version"
solc_bin="solc-$version"
echo "$solc_bin"
if [[ ! -f "$solc_bin" ]]
then
echo "Downloading release from github..."
if wget -q https://github.com/ethereum/solidity/releases/download/v$version/solc-static-linux >/dev/null
then
mv solc-static-linux $solc_bin
else
printError "No release $version was found on github!"
continue
fi
fi
ln -sf "$solc_bin" "solc"
chmod a+x solc
SOLC="$SOLTMPDIR/solc"
compileFull $opts "$SOLTMPDIR/$f"
done
)
rm -rf "$SOLTMPDIR"
echo "Done."

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