mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8825 from ethereum/develop
Merge develop into release.
This commit is contained in:
commit
b8d736ae0c
@ -284,12 +284,18 @@ jobs:
|
|||||||
|
|
||||||
chk_coding_style:
|
chk_coding_style:
|
||||||
docker:
|
docker:
|
||||||
- image: buildpack-deps:disco
|
- image: buildpack-deps:focal
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Install shellcheck
|
||||||
|
command: apt -q update && apt install -y shellcheck
|
||||||
- run:
|
- run:
|
||||||
name: Check for C++ coding style
|
name: Check for C++ coding style
|
||||||
command: ./scripts/check_style.sh
|
command: ./scripts/check_style.sh
|
||||||
|
- run:
|
||||||
|
name: checking shell scripts
|
||||||
|
command: ./scripts/chk_shellscripts/chk_shellscripts.sh
|
||||||
|
|
||||||
chk_pylint:
|
chk_pylint:
|
||||||
docker:
|
docker:
|
||||||
@ -338,7 +344,7 @@ jobs:
|
|||||||
|
|
||||||
chk_proofs:
|
chk_proofs:
|
||||||
docker:
|
docker:
|
||||||
- image: buildpack-deps:disco
|
- image: buildpack-deps:latest
|
||||||
environment:
|
environment:
|
||||||
TERM: xterm
|
TERM: xterm
|
||||||
steps:
|
steps:
|
||||||
@ -347,8 +353,8 @@ jobs:
|
|||||||
name: Z3 python deps
|
name: Z3 python deps
|
||||||
command: |
|
command: |
|
||||||
apt-get -qq update
|
apt-get -qq update
|
||||||
apt-get -qy install python-pip
|
apt-get -qy install python3-pip
|
||||||
pip install --user z3-solver
|
pip3 install --user z3-solver
|
||||||
- run: *run_proofs
|
- run: *run_proofs
|
||||||
|
|
||||||
chk_docs_pragma_min_version:
|
chk_docs_pragma_min_version:
|
||||||
@ -661,7 +667,7 @@ jobs:
|
|||||||
|
|
||||||
t_ems_solcjs:
|
t_ems_solcjs:
|
||||||
docker:
|
docker:
|
||||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904
|
- image: buildpack-deps:latest
|
||||||
environment:
|
environment:
|
||||||
TERM: xterm
|
TERM: xterm
|
||||||
steps:
|
steps:
|
||||||
|
@ -52,8 +52,8 @@ then
|
|||||||
rm -rf z3-4.8.7-x64-osx-10.14.6
|
rm -rf z3-4.8.7-x64-osx-10.14.6
|
||||||
|
|
||||||
# evmone
|
# evmone
|
||||||
wget https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-darwin-x86_64.tar.gz
|
wget https://github.com/ethereum/evmone/releases/download/v0.4.0/evmone-0.4.0-darwin-x86_64.tar.gz
|
||||||
tar xzpf evmone-0.3.0-darwin-x86_64.tar.gz -C /usr/local
|
tar xzpf evmone-0.4.0-darwin-x86_64.tar.gz -C /usr/local
|
||||||
rm -f evmone-0.3.0-darwin-x86_64.tar.gz
|
rm -f evmone-0.4.0-darwin-x86_64.tar.gz
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
|||||||
eth_policy()
|
eth_policy()
|
||||||
|
|
||||||
# project name and version should be set after cmake_policy CMP0048
|
# project name and version should be set after cmake_policy CMP0048
|
||||||
set(PROJECT_VERSION "0.6.6")
|
set(PROJECT_VERSION "0.6.7")
|
||||||
# OSX target needed in order to support std::visit
|
# OSX target needed in order to support std::visit
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
||||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||||
|
26
Changelog.md
26
Changelog.md
@ -1,3 +1,29 @@
|
|||||||
|
### 0.6.7 (2020-05-04)
|
||||||
|
|
||||||
|
Language Features:
|
||||||
|
* Add support for EIP 165 interface identifiers with `type(I).interfaceId`.
|
||||||
|
* Allow virtual modifiers inside abstract contracts to have empty body.
|
||||||
|
|
||||||
|
|
||||||
|
Compiler Features:
|
||||||
|
* Optimizer: Simplify repeated AND and OR operations.
|
||||||
|
* Standard Json Input: Support the prefix ``file://`` in the field ``urls``.
|
||||||
|
* Add option to specify optimization steps to be performed by Yul optimizer with `--yul-optimizations` in the commandline interface or `optimizer.details.yulDetails.optimizerSteps` in standard-json.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* SMTChecker: Fix internal error when fixed points are used.
|
||||||
|
* SMTChecker: Fix internal error when using array slices.
|
||||||
|
* Type Checker: Disallow ``virtual`` and ``override`` for constructors.
|
||||||
|
* Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking.
|
||||||
|
* Type Checker: Fix internal error when assigning to empty tuples.
|
||||||
|
* Type Checker: Fix internal error when applying unary operators to tuples with empty components.
|
||||||
|
* Type Checker: Perform recursiveness check on structs declared at the file level.
|
||||||
|
|
||||||
|
Build System:
|
||||||
|
* soltest.sh: ``SOLIDITY_BUILD_DIR`` is no longer relative to ``REPO_ROOT`` to allow for build directories outside of the source tree.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 0.6.6 (2020-04-09)
|
### 0.6.6 (2020-04-09)
|
||||||
|
|
||||||
Important Bugfixes:
|
Important Bugfixes:
|
||||||
|
@ -68,7 +68,7 @@ structDefinition
|
|||||||
'{' ( variableDeclaration ';' (variableDeclaration ';')* )? '}' ;
|
'{' ( variableDeclaration ';' (variableDeclaration ';')* )? '}' ;
|
||||||
|
|
||||||
modifierDefinition
|
modifierDefinition
|
||||||
: 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* block ;
|
: 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* ( ';' | block ) ;
|
||||||
|
|
||||||
functionDefinition
|
functionDefinition
|
||||||
: functionDescriptor parameterList modifierList returnParameters? ( ';' | block ) ;
|
: functionDescriptor parameterList modifierList returnParameters? ( ';' | block ) ;
|
||||||
|
622
docs/_static/css/dark.css
vendored
Normal file
622
docs/_static/css/dark.css
vendored
Normal file
@ -0,0 +1,622 @@
|
|||||||
|
/* links */
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:visited {
|
||||||
|
color: #aaddff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* code directives */
|
||||||
|
|
||||||
|
.method dt,
|
||||||
|
.class dt,
|
||||||
|
.data dt,
|
||||||
|
.attribute dt,
|
||||||
|
.function dt,
|
||||||
|
.classmethod dt,
|
||||||
|
.exception dt,
|
||||||
|
.descclassname,
|
||||||
|
.descname {
|
||||||
|
background-color: #2d2d2d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-content dl:not(.docutils) dt {
|
||||||
|
color: #aaddff;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-top: solid 3px #525252;
|
||||||
|
border-left: solid 3px #525252;
|
||||||
|
}
|
||||||
|
|
||||||
|
em.property {
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* tables */
|
||||||
|
|
||||||
|
.rst-content table.docutils thead {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-content table.docutils td {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td {
|
||||||
|
background-color: #5a5a5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* inlined code highlights */
|
||||||
|
|
||||||
|
.xref,
|
||||||
|
.py-meth,
|
||||||
|
.rst-content a code {
|
||||||
|
color: #aaddff !important;
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-content code {
|
||||||
|
color: #eee !important;
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.literal {
|
||||||
|
background-color: #2d2d2d !important;
|
||||||
|
border: 1px solid #6d6d6d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.docutils.literal.notranslate {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* notes, warnings, hints */
|
||||||
|
|
||||||
|
.hint .admonition-title {
|
||||||
|
background: #2aa87c !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning .admonition-title {
|
||||||
|
background: #cc4444 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition-title {
|
||||||
|
background: #3a7ca8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition,
|
||||||
|
.note {
|
||||||
|
background-color: #2d2d2d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* table of contents */
|
||||||
|
|
||||||
|
.wy-nav-content-wrap {
|
||||||
|
background-color: rgba(0, 0, 0, 0.6) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background-color: #191919 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-title {
|
||||||
|
background-color: #2b2b2b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-menu-vertical a {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-menu-vertical code.docutils.literal.notranslate {
|
||||||
|
color: #404040;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-nav-content {
|
||||||
|
background: #3c3c3c;
|
||||||
|
color: #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-menu-vertical li.on a,
|
||||||
|
.wy-menu-vertical li.current>a {
|
||||||
|
background: #a3a3a3;
|
||||||
|
border-bottom: 0px !important;
|
||||||
|
border-top: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wy-menu-vertical li.current {
|
||||||
|
background: #b3b3b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-backref {
|
||||||
|
color: grey !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .hll {
|
||||||
|
background-color: #49483e
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background: #222;
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight .c {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Comment */
|
||||||
|
|
||||||
|
.highlight .err {
|
||||||
|
color: #960050;
|
||||||
|
background-color: #1e0010
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Error */
|
||||||
|
|
||||||
|
.highlight .k {
|
||||||
|
color: #66d9ef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Keyword */
|
||||||
|
|
||||||
|
.highlight .l {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal */
|
||||||
|
|
||||||
|
.highlight .n {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name */
|
||||||
|
|
||||||
|
.highlight .o {
|
||||||
|
color: #f92672
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Operator */
|
||||||
|
|
||||||
|
.highlight .p {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Punctuation */
|
||||||
|
|
||||||
|
.highlight .ch {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Comment.Hashbang */
|
||||||
|
|
||||||
|
.highlight .cm {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Comment.Multiline */
|
||||||
|
|
||||||
|
.highlight .cp {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Comment.Preproc */
|
||||||
|
|
||||||
|
.highlight .cpf {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Comment.PreprocFile */
|
||||||
|
|
||||||
|
.highlight .c1 {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Comment.Single */
|
||||||
|
|
||||||
|
.highlight .cs {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Comment.Special */
|
||||||
|
|
||||||
|
.highlight .gd {
|
||||||
|
color: #f92672
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Generic.Deleted */
|
||||||
|
|
||||||
|
.highlight .ge {
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Generic.Emph */
|
||||||
|
|
||||||
|
.highlight .gi {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Generic.Inserted */
|
||||||
|
|
||||||
|
.highlight .gs {
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Generic.Strong */
|
||||||
|
|
||||||
|
.highlight .gu {
|
||||||
|
color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Generic.Subheading */
|
||||||
|
|
||||||
|
.highlight .kc {
|
||||||
|
color: #66d9ef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Keyword.Constant */
|
||||||
|
|
||||||
|
.highlight .kd {
|
||||||
|
color: #66d9ef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Keyword.Declaration */
|
||||||
|
|
||||||
|
.highlight .kn {
|
||||||
|
color: #f92672
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Keyword.Namespace */
|
||||||
|
|
||||||
|
.highlight .kp {
|
||||||
|
color: #66d9ef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Keyword.Pseudo */
|
||||||
|
|
||||||
|
.highlight .kr {
|
||||||
|
color: #66d9ef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Keyword.Reserved */
|
||||||
|
|
||||||
|
.highlight .kt {
|
||||||
|
color: #66d9ef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Keyword.Type */
|
||||||
|
|
||||||
|
.highlight .ld {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Date */
|
||||||
|
|
||||||
|
.highlight .m {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Number */
|
||||||
|
|
||||||
|
.highlight .s {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String */
|
||||||
|
|
||||||
|
.highlight .na {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Attribute */
|
||||||
|
|
||||||
|
.highlight .nb {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Builtin */
|
||||||
|
|
||||||
|
.highlight .nc {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Class */
|
||||||
|
|
||||||
|
.highlight .no {
|
||||||
|
color: #66d9ef
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Constant */
|
||||||
|
|
||||||
|
.highlight .nd {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Decorator */
|
||||||
|
|
||||||
|
.highlight .ni {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Entity */
|
||||||
|
|
||||||
|
.highlight .ne {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Exception */
|
||||||
|
|
||||||
|
.highlight .nf {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Function */
|
||||||
|
|
||||||
|
.highlight .nl {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Label */
|
||||||
|
|
||||||
|
.highlight .nn {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Namespace */
|
||||||
|
|
||||||
|
.highlight .nx {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Other */
|
||||||
|
|
||||||
|
.highlight .py {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Property */
|
||||||
|
|
||||||
|
.highlight .nt {
|
||||||
|
color: #f92672
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Tag */
|
||||||
|
|
||||||
|
.highlight .nv {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Variable */
|
||||||
|
|
||||||
|
.highlight .ow {
|
||||||
|
color: #f92672
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Operator.Word */
|
||||||
|
|
||||||
|
.highlight .w {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Text.Whitespace */
|
||||||
|
|
||||||
|
.highlight .mb {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Number.Bin */
|
||||||
|
|
||||||
|
.highlight .mf {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Number.Float */
|
||||||
|
|
||||||
|
.highlight .mh {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Number.Hex */
|
||||||
|
|
||||||
|
.highlight .mi {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Number.Integer */
|
||||||
|
|
||||||
|
.highlight .mo {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Number.Oct */
|
||||||
|
|
||||||
|
.highlight .sa {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Affix */
|
||||||
|
|
||||||
|
.highlight .sb {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Backtick */
|
||||||
|
|
||||||
|
.highlight .sc {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Char */
|
||||||
|
|
||||||
|
.highlight .dl {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Delimiter */
|
||||||
|
|
||||||
|
.highlight .sd {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Doc */
|
||||||
|
|
||||||
|
.highlight .s2 {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Double */
|
||||||
|
|
||||||
|
.highlight .se {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Escape */
|
||||||
|
|
||||||
|
.highlight .sh {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Heredoc */
|
||||||
|
|
||||||
|
.highlight .si {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Interpol */
|
||||||
|
|
||||||
|
.highlight .sx {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Other */
|
||||||
|
|
||||||
|
.highlight .sr {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Regex */
|
||||||
|
|
||||||
|
.highlight .s1 {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Single */
|
||||||
|
|
||||||
|
.highlight .ss {
|
||||||
|
color: #e6db74
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.String.Symbol */
|
||||||
|
|
||||||
|
.highlight .bp {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Builtin.Pseudo */
|
||||||
|
|
||||||
|
.highlight .fm {
|
||||||
|
color: #a6e22e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Function.Magic */
|
||||||
|
|
||||||
|
.highlight .vc {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Variable.Class */
|
||||||
|
|
||||||
|
.highlight .vg {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Variable.Global */
|
||||||
|
|
||||||
|
.highlight .vi {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Variable.Instance */
|
||||||
|
|
||||||
|
.highlight .vm {
|
||||||
|
color: #f8f8f2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Name.Variable.Magic */
|
||||||
|
|
||||||
|
.highlight .il {
|
||||||
|
color: #ae81ff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Literal.Number.Integer.Long */
|
77
docs/_static/css/toggle.css
vendored
Normal file
77
docs/_static/css/toggle.css
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
input[type=checkbox] {
|
||||||
|
visibility: hidden;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-versions .rst-current-version {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-versions .rst-current-version .fa-book,
|
||||||
|
.rst-versions .rst-current-version .fa-v,
|
||||||
|
.rst-versions .rst-current-version .fa-caret-down {
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-versions .rst-current-version .fa-element {
|
||||||
|
width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-versions .rst-current-version .fa-book {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rst-versions .rst-current-version .fa-v {
|
||||||
|
color: #27AE60;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: inline-block;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: right;
|
||||||
|
border-radius: 100px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
text-indent: -9999px;
|
||||||
|
width: 50px;
|
||||||
|
height: 21px;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:after {
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
background: #fff;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
transition: ease-in-out 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked+label {
|
||||||
|
background: #3a7ca8;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked+label:after {
|
||||||
|
left: calc(100% - 5px);
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.transition,
|
||||||
|
html.transition *,
|
||||||
|
html.transition *:before,
|
||||||
|
html.transition *:after {
|
||||||
|
transition: ease-in-out 200ms !important;
|
||||||
|
transition-delay: 0 !important;
|
||||||
|
}
|
38
docs/_static/js/toggle.js
vendored
Normal file
38
docs/_static/js/toggle.js
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
function toggleCssMode(isDay) {
|
||||||
|
var mode = (isDay ? "Day" : "Night");
|
||||||
|
localStorage.setItem("css-mode", mode);
|
||||||
|
|
||||||
|
var daysheet = $('link[href="_static/pygments.css"]')[0].sheet;
|
||||||
|
daysheet.disabled = !isDay;
|
||||||
|
|
||||||
|
var nightsheet = $('link[href="_static/css/dark.css"]')[0];
|
||||||
|
if (!isDay && nightsheet === undefined) {
|
||||||
|
var element = document.createElement("link");
|
||||||
|
element.setAttribute("rel", "stylesheet");
|
||||||
|
element.setAttribute("type", "text/css");
|
||||||
|
element.setAttribute("href", "_static/css/dark.css");
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nightsheet !== undefined) {
|
||||||
|
nightsheet.sheet.disabled = isDay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var initial = localStorage.getItem("css-mode") != "Night";
|
||||||
|
var checkbox = document.querySelector('input[name=mode]');
|
||||||
|
|
||||||
|
toggleCssMode(initial);
|
||||||
|
checkbox.checked = initial;
|
||||||
|
|
||||||
|
checkbox.addEventListener('change', function() {
|
||||||
|
document.documentElement.classList.add('transition');
|
||||||
|
window.setTimeout(() => {
|
||||||
|
document.documentElement.classList.remove('transition');
|
||||||
|
}, 1000)
|
||||||
|
toggleCssMode(this.checked);
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
36
docs/_templates/versions.html
vendored
Normal file
36
docs/_templates/versions.html
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{# Add rst-badge after rst-versions for small badge style. #}
|
||||||
|
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
|
||||||
|
<span class="rst-current-version" data-toggle="rst-current-version">
|
||||||
|
<span class="fa fa-book fa-element"> RTD </span>
|
||||||
|
|
||||||
|
<span class="fa fa-element">
|
||||||
|
<input class="container_toggle" type="checkbox" id="switch" name="mode">
|
||||||
|
<label for="switch"></label>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="fa fa-v fa-element"> v: {{ current_version }} <span class="fa fa-caret-down"></span></span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<div class="rst-other-versions">
|
||||||
|
<dl>
|
||||||
|
<dt>{{ _('Versions') }}</dt> {% for slug, url in versions %}
|
||||||
|
<dd><a href="{{ url }}">{{ slug }}</a></dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt>{{ _('Downloads') }}</dt> {% for type, url in downloads %}
|
||||||
|
<dd><a href="{{ url }}">{{ type }}</a></dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
{# Translators: The phrase "Read the Docs" is not translated #}
|
||||||
|
<dt>{{ _('On Read the Docs') }}</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">{{ _('Project Home') }}</a>
|
||||||
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">{{ _('Builds') }}</a>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -202,7 +202,7 @@ on the type of ``X`` being
|
|||||||
- ``uint<M>``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order
|
- ``uint<M>``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order
|
||||||
(left) side with zero-bytes such that the length is 32 bytes.
|
(left) side with zero-bytes such that the length is 32 bytes.
|
||||||
- ``address``: as in the ``uint160`` case
|
- ``address``: as in the ``uint160`` case
|
||||||
- ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is 32 bytes.
|
- ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` bytes for negative ``X`` and with zero-bytes for non-negative ``X`` such that the length is 32 bytes.
|
||||||
- ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false``
|
- ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false``
|
||||||
- ``fixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``.
|
- ``fixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``.
|
||||||
- ``fixed``: as in the ``fixed128x18`` case
|
- ``fixed``: as in the ``fixed128x18`` case
|
||||||
@ -507,13 +507,17 @@ A function description is a JSON object with the fields:
|
|||||||
- ``outputs``: an array of objects similar to ``inputs``.
|
- ``outputs``: an array of objects similar to ``inputs``.
|
||||||
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read
|
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read
|
||||||
blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain
|
blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain
|
||||||
state <view-functions>`), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether).
|
state <view-functions>`), ``nonpayable`` (function does not accept Ether - the default) and ``payable`` (function accepts Ether).
|
||||||
|
|
||||||
Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either.
|
Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Sending non-zero Ether to non-payable function will revert the transaction.
|
Sending non-zero Ether to non-payable function will revert the transaction.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The state mutability ``nonpayable`` is reflected in Solidity by not specifying
|
||||||
|
a state mutability modifier at all.
|
||||||
|
|
||||||
An event description is a JSON object with fairly similar fields:
|
An event description is a JSON object with fairly similar fields:
|
||||||
|
|
||||||
- ``type``: always ``"event"``
|
- ``type``: always ``"event"``
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"name": "MemoryArrayCreationOverflow",
|
"name": "MemoryArrayCreationOverflow",
|
||||||
"summary": "The creation of very large memory arrays can result in overlapping memory regions and thus memory corruption.",
|
"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.",
|
"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.",
|
||||||
|
"link": "https://solidity.ethereum.org/2020/04/06/memory-creation-overflow-bug/",
|
||||||
"introduced": "0.2.0",
|
"introduced": "0.2.0",
|
||||||
"fixed": "0.6.5",
|
"fixed": "0.6.5",
|
||||||
"severity": "low"
|
"severity": "low"
|
||||||
@ -73,6 +74,7 @@
|
|||||||
"name": "SignedArrayStorageCopy",
|
"name": "SignedArrayStorageCopy",
|
||||||
"summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.",
|
"summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.",
|
||||||
"description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.",
|
"description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.",
|
||||||
|
"link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/",
|
||||||
"introduced": "0.4.7",
|
"introduced": "0.4.7",
|
||||||
"fixed": "0.5.10",
|
"fixed": "0.5.10",
|
||||||
"severity": "low/medium"
|
"severity": "low/medium"
|
||||||
@ -81,6 +83,7 @@
|
|||||||
"name": "ABIEncoderV2StorageArrayWithMultiSlotElement",
|
"name": "ABIEncoderV2StorageArrayWithMultiSlotElement",
|
||||||
"summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.",
|
"summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.",
|
||||||
"description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.",
|
"description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.",
|
||||||
|
"link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/",
|
||||||
"introduced": "0.4.16",
|
"introduced": "0.4.16",
|
||||||
"fixed": "0.5.10",
|
"fixed": "0.5.10",
|
||||||
"severity": "low",
|
"severity": "low",
|
||||||
|
@ -1085,5 +1085,9 @@
|
|||||||
"0.6.6": {
|
"0.6.6": {
|
||||||
"bugs": [],
|
"bugs": [],
|
||||||
"released": "2020-04-09"
|
"released": "2020-04-09"
|
||||||
|
},
|
||||||
|
"0.6.7": {
|
||||||
|
"bugs": [],
|
||||||
|
"released": "2020-05-04"
|
||||||
}
|
}
|
||||||
}
|
}
|
191
docs/cheatsheet.rst
Normal file
191
docs/cheatsheet.rst
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
**********
|
||||||
|
Cheatsheet
|
||||||
|
**********
|
||||||
|
|
||||||
|
.. index:: precedence
|
||||||
|
|
||||||
|
.. _order:
|
||||||
|
|
||||||
|
Order of Precedence of Operators
|
||||||
|
================================
|
||||||
|
|
||||||
|
The following is the order of precedence for operators, listed in order of evaluation.
|
||||||
|
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| Precedence | Description | Operator |
|
||||||
|
+============+=====================================+============================================+
|
||||||
|
| *1* | Postfix increment and decrement | ``++``, ``--`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | New expression | ``new <typename>`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Array subscripting | ``<array>[<index>]`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Member access | ``<object>.<member>`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Function-like call | ``<func>(<args...>)`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Parentheses | ``(<statement>)`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *2* | Prefix increment and decrement | ``++``, ``--`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Unary minus | ``-`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Unary operations | ``delete`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Logical NOT | ``!`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Bitwise NOT | ``~`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *3* | Exponentiation | ``**`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *5* | Addition and subtraction | ``+``, ``-`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *6* | Bitwise shift operators | ``<<``, ``>>`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *7* | Bitwise AND | ``&`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *8* | Bitwise XOR | ``^`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *9* | Bitwise OR | ``|`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *11* | Equality operators | ``==``, ``!=`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *12* | Logical AND | ``&&`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *13* | Logical OR | ``||`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *14* | Ternary operator | ``<conditional> ? <if-true> : <if-false>`` |
|
||||||
|
+ +-------------------------------------+--------------------------------------------+
|
||||||
|
| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
|
||||||
|
| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, |
|
||||||
|
| | | ``%=`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
| *15* | Comma operator | ``,`` |
|
||||||
|
+------------+-------------------------------------+--------------------------------------------+
|
||||||
|
|
||||||
|
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
|
||||||
|
|
||||||
|
Global Variables
|
||||||
|
================
|
||||||
|
|
||||||
|
- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI <ABI>`-decodes
|
||||||
|
the provided data. The types are given in parentheses as second argument.
|
||||||
|
Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))``
|
||||||
|
- ``abi.encode(...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes the given arguments
|
||||||
|
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of
|
||||||
|
the given arguments. Note that this encoding can be ambiguous!
|
||||||
|
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes
|
||||||
|
the given arguments starting from the second and prepends the given four-byte selector
|
||||||
|
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent
|
||||||
|
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)```
|
||||||
|
- ``block.coinbase`` (``address payable``): current block miner's address
|
||||||
|
- ``block.difficulty`` (``uint``): current block difficulty
|
||||||
|
- ``block.gaslimit`` (``uint``): current block gaslimit
|
||||||
|
- ``block.number`` (``uint``): current block number
|
||||||
|
- ``block.timestamp`` (``uint``): current block timestamp
|
||||||
|
- ``gasleft() returns (uint256)``: remaining gas
|
||||||
|
- ``msg.data`` (``bytes``): complete calldata
|
||||||
|
- ``msg.sender`` (``address payable``): sender of the message (current call)
|
||||||
|
- ``msg.value`` (``uint``): number of wei sent with the message
|
||||||
|
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
|
||||||
|
- ``tx.gasprice`` (``uint``): gas price of the transaction
|
||||||
|
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
|
||||||
|
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
|
||||||
|
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use
|
||||||
|
for malformed input or error in external component)
|
||||||
|
- ``require(bool condition, string memory message)``: abort execution and revert state changes if
|
||||||
|
condition is ``false`` (use for malformed input or error in external component). Also provide error message.
|
||||||
|
- ``revert()``: abort execution and revert state changes
|
||||||
|
- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string
|
||||||
|
- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks
|
||||||
|
- ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input
|
||||||
|
- ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input
|
||||||
|
- ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input
|
||||||
|
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with
|
||||||
|
the public key from elliptic curve signature, return zero on error
|
||||||
|
- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with
|
||||||
|
arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
||||||
|
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed
|
||||||
|
with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
||||||
|
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable``
|
||||||
|
- ``super``: the contract one level higher in the inheritance hierarchy
|
||||||
|
- ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address
|
||||||
|
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
|
||||||
|
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`,
|
||||||
|
returns ``false`` on failure
|
||||||
|
- ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
|
||||||
|
- ``type(C).name`` (``string``): the name of the contract
|
||||||
|
- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`.
|
||||||
|
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
|
||||||
|
- ``type(I).interfaceId`` (``bytes4``): value containing the EIP-165 interface identifier of the given interface, see :ref:`Type Information<meta-type>`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
|
||||||
|
unless you know what you are doing.
|
||||||
|
|
||||||
|
Both the timestamp and the block hash can be influenced by miners to some degree.
|
||||||
|
Bad actors in the mining community can for example run a casino payout function on a chosen hash
|
||||||
|
and just retry a different hash if they did not receive any money.
|
||||||
|
|
||||||
|
The current block timestamp must be strictly larger than the timestamp of the last block,
|
||||||
|
but the only guarantee is that it will be somewhere between the timestamps of two
|
||||||
|
consecutive blocks in the canonical chain.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The block hashes are not available for all blocks for scalability reasons.
|
||||||
|
You can only access the hashes of the most recent 256 blocks, all other
|
||||||
|
values will be zero.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``,
|
||||||
|
``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and
|
||||||
|
``sha3`` as alias for ``keccak256``.
|
||||||
|
|
||||||
|
.. index:: visibility, public, private, external, internal
|
||||||
|
|
||||||
|
Function Visibility Specifiers
|
||||||
|
==============================
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
function myFunction() <visibility specifier> returns (bool) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
- ``public``: visible externally and internally (creates a :ref:`getter function<getter-functions>` for storage/state variables)
|
||||||
|
- ``private``: only visible in the current contract
|
||||||
|
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
|
||||||
|
- ``internal``: only visible internally
|
||||||
|
|
||||||
|
|
||||||
|
.. index:: modifiers, pure, view, payable, constant, anonymous, indexed
|
||||||
|
|
||||||
|
Modifiers
|
||||||
|
=========
|
||||||
|
|
||||||
|
- ``pure`` for functions: Disallows modification or access of state.
|
||||||
|
- ``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
|
||||||
|
behaviour to be changed in derived contracts.
|
||||||
|
- ``override``: States that this function, modifier or public state variable changes
|
||||||
|
the behaviour of a function or modifier in a base contract.
|
||||||
|
|
||||||
|
Reserved Keywords
|
||||||
|
=================
|
||||||
|
|
||||||
|
These keywords are reserved in Solidity. They might become part of the syntax in the future:
|
||||||
|
|
||||||
|
``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``,
|
||||||
|
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
|
||||||
|
``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``,
|
||||||
|
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``,
|
||||||
|
``unchecked``.
|
@ -17,7 +17,7 @@ import sys
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from pygments_lexer_solidity import SolidityLexer
|
from pygments_lexer_solidity import SolidityLexer, YulLexer
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
@ -27,6 +27,7 @@ def setup(sphinx):
|
|||||||
thisdir = os.path.dirname(os.path.realpath(__file__))
|
thisdir = os.path.dirname(os.path.realpath(__file__))
|
||||||
sys.path.insert(0, thisdir + '/utils')
|
sys.path.insert(0, thisdir + '/utils')
|
||||||
sphinx.add_lexer('Solidity', SolidityLexer())
|
sphinx.add_lexer('Solidity', SolidityLexer())
|
||||||
|
sphinx.add_lexer('Yul', YulLexer())
|
||||||
|
|
||||||
sphinx.add_stylesheet('css/custom.css')
|
sphinx.add_stylesheet('css/custom.css')
|
||||||
|
|
||||||
@ -146,10 +147,14 @@ html_theme = 'sphinx_rtd_theme'
|
|||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
html_css_files = ["css/toggle.css"]
|
||||||
|
|
||||||
|
html_js_files = ["js/toggle.js"]
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
# Add any extra paths that contain custom files (such as robots.txt or
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
# .htaccess) here, relative to this directory. These files are copied
|
||||||
# directly to the root of the documentation.
|
# directly to the root of the documentation.
|
||||||
#html_extra_path = []
|
html_extra_path = ["_static/css"]
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
|
@ -52,3 +52,8 @@ facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/T
|
|||||||
Abstract contracts are useful in the same way that defining methods
|
Abstract contracts are useful in the same way that defining methods
|
||||||
in an interface is useful. It is a way for the designer of the
|
in an interface is useful. It is a way for the designer of the
|
||||||
abstract contract to say "any child of mine must implement this method".
|
abstract contract to say "any child of mine must implement this method".
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Abstract contracts cannot override an implemented virtual function with an
|
||||||
|
unimplemented one.
|
||||||
|
@ -195,7 +195,7 @@ In addition to the list of state modifying statements explained above, the follo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Pure functions are able to use the `revert()` and `require()` functions to revert
|
Pure functions are able to use the ``revert()`` and ``require()`` functions to revert
|
||||||
potential state changes when an :ref:`error occurs <assert-and-require>`.
|
potential state changes when an :ref:`error occurs <assert-and-require>`.
|
||||||
|
|
||||||
Reverting a state change is not considered a "state modification", as only changes to the
|
Reverting a state change is not considered a "state modification", as only changes to the
|
||||||
@ -235,9 +235,9 @@ A contract can have at most one ``receive`` function, declared using
|
|||||||
``receive() external payable { ... }``
|
``receive() external payable { ... }``
|
||||||
(without the ``function`` keyword).
|
(without the ``function`` keyword).
|
||||||
This function cannot have arguments, cannot return anything and must have
|
This function cannot have arguments, cannot return anything and must have
|
||||||
``external`` visibility and ``payable`` state mutability. It is executed on a
|
``external`` visibility and ``payable`` state mutability. It is executed on a
|
||||||
call to the contract with empty calldata. This is the function that is executed
|
call to the contract with empty calldata. This is the function that is executed
|
||||||
on plain Ether transfers (e.g. via `.send()` or `.transfer()`). If no such
|
on plain Ether transfers (e.g. via ``.send()`` or ``.transfer()``). If no such
|
||||||
function exists, but a payable :ref:`fallback function <fallback-function>`
|
function exists, but a payable :ref:`fallback function <fallback-function>`
|
||||||
exists, the fallback function will be called on a plain Ether transfer. If
|
exists, the fallback function will be called on a plain Ether transfer. If
|
||||||
neither a receive Ether nor a payable fallback function is present, the
|
neither a receive Ether nor a payable fallback function is present, the
|
||||||
@ -245,7 +245,7 @@ contract cannot receive Ether through regular transactions and throws an
|
|||||||
exception.
|
exception.
|
||||||
|
|
||||||
In the worst case, the fallback function can only rely on 2300 gas being
|
In the worst case, the fallback function can only rely on 2300 gas being
|
||||||
available (for example when `send` or `transfer` is used), leaving little
|
available (for example when ``send`` or ``transfer`` is used), leaving little
|
||||||
room to perform other operations except basic logging. The following operations
|
room to perform other operations except basic logging. The following operations
|
||||||
will consume more gas than the 2300 gas stipend:
|
will consume more gas than the 2300 gas stipend:
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ will consume more gas than the 2300 gas stipend:
|
|||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
A contract without a receive Ether function can receive Ether as a
|
A contract without a receive Ether function can receive Ether as a
|
||||||
recipient of a `coinbase transaction` (aka `miner block reward`)
|
recipient of a *coinbase transaction* (aka *miner block reward*)
|
||||||
or as a destination of a ``selfdestruct``.
|
or as a destination of a ``selfdestruct``.
|
||||||
|
|
||||||
A contract cannot react to such Ether transfers and thus also
|
A contract cannot react to such Ether transfers and thus also
|
||||||
|
@ -16,7 +16,7 @@ Functions have to be specified as being ``external``,
|
|||||||
``public``, ``internal`` or ``private``.
|
``public``, ``internal`` or ``private``.
|
||||||
For state variables, ``external`` is not possible.
|
For state variables, ``external`` is not possible.
|
||||||
|
|
||||||
``external``:
|
``external``
|
||||||
External functions are part of the contract interface,
|
External functions are part of the contract interface,
|
||||||
which means they can be called from other contracts and
|
which means they can be called from other contracts and
|
||||||
via transactions. An external function ``f`` cannot be called
|
via transactions. An external function ``f`` cannot be called
|
||||||
@ -25,18 +25,18 @@ For state variables, ``external`` is not possible.
|
|||||||
they receive large arrays of data, because the data
|
they receive large arrays of data, because the data
|
||||||
is not copied from calldata to memory.
|
is not copied from calldata to memory.
|
||||||
|
|
||||||
``public``:
|
``public``
|
||||||
Public functions are part of the contract interface
|
Public functions are part of the contract interface
|
||||||
and can be either called internally or via
|
and can be either called internally or via
|
||||||
messages. For public state variables, an automatic getter
|
messages. For public state variables, an automatic getter
|
||||||
function (see below) is generated.
|
function (see below) is generated.
|
||||||
|
|
||||||
``internal``:
|
``internal``
|
||||||
Those functions and state variables can only be
|
Those functions and state variables can only be
|
||||||
accessed internally (i.e. from within the current contract
|
accessed internally (i.e. from within the current contract
|
||||||
or contracts deriving from it), without using ``this``.
|
or contracts deriving from it), without using ``this``.
|
||||||
|
|
||||||
``private``:
|
``private``
|
||||||
Private functions and state variables are only
|
Private functions and state variables are only
|
||||||
visible for the contract they are defined in and not in
|
visible for the contract they are defined in and not in
|
||||||
derived contracts.
|
derived contracts.
|
||||||
|
@ -81,26 +81,55 @@ Thank you for your help!
|
|||||||
Running the compiler tests
|
Running the compiler tests
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
|
Prerequisites
|
||||||
but for quicker feedback, you might want to run specific tests.
|
-------------
|
||||||
|
|
||||||
|
Some tests require the `evmone <https://github.com/ethereum/evmone/releases>`_
|
||||||
|
library, others require `libz3 <https://github.com/Z3Prover/z3>`_. The test script
|
||||||
|
tries to discover the location of the ``evmone`` library, which can be located
|
||||||
|
in the current directory, installed on the system level, or the ``deps`` folder
|
||||||
|
in the project top level. The required file is called ``libevmone.so`` on Linux
|
||||||
|
systems, ``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS.
|
||||||
|
|
||||||
|
Running the tests
|
||||||
|
-----------------
|
||||||
|
|
||||||
Solidity includes different types of tests, most of them bundled into the
|
Solidity includes different types of tests, most of them bundled into the
|
||||||
`Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest``.
|
`Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest``.
|
||||||
Running ``build/test/soltest`` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes.
|
Running ``build/test/soltest`` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes.
|
||||||
|
|
||||||
Some tests require the ``evmone`` library, others require ``libz3``.
|
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
|
||||||
|
including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest`` (or its wrapper ``scripts/soltest.sh``),
|
||||||
|
as well as command line tests and compilation tests.
|
||||||
|
|
||||||
The test system will automatically try to discover the location of the ``evmone`` library
|
The test system automatically tries try to discover the location of the ``evmone`` library
|
||||||
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
|
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
|
||||||
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on MacOS. If it is not found, the relevant tests
|
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS. If it is not found, tests that
|
||||||
are skipped. To run all tests, download the library from
|
use it are skipped. These tests are ``libsolididty/semanticTests``, ``libsolidity/GasCosts``,
|
||||||
`Github <https://github.com/ethereum/evmone/releases/tag/v0.3.0>`_
|
``libsolidity/SolidityEndToEndTest``, part of the soltest suite. To run all tests, download the library from
|
||||||
and either place it in the project root path or inside the ``deps`` folder.
|
`GitHub <https://github.com/ethereum/evmone/releases/tag/v0.4.1>`_
|
||||||
|
and place it in the project root path or inside the ``deps`` folder.
|
||||||
|
|
||||||
If you do not have libz3 installed on your system, you should disable the SMT tests:
|
If the ``libz3`` library is not installed on your system, you should disable the
|
||||||
``./scripts/soltest.sh --no-smt``.
|
SMT tests by exporting ``SMT_FLAGS=--no-smt`` before running ``./scripts/tests.sh`` or
|
||||||
|
running ``./scripts/soltest.sh --no-smt``.
|
||||||
|
These tests are ``libsolidity/smtCheckerTests`` and ``libsolidity/smtCheckerTestsJSON``.
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
|
||||||
|
To get a list of all unit tests run by Soltest, run ``./build/test/soltest --list_content=HRF``.
|
||||||
|
|
||||||
|
For quicker results you can run a subset of, or specific tests.
|
||||||
|
|
||||||
|
To run a subset of tests, you can use filters:
|
||||||
|
``./scripts/soltest.sh -t TestSuite/TestName``,
|
||||||
|
where ``TestName`` can be a wildcard ``*``.
|
||||||
|
|
||||||
|
Or, for example, to run all the tests for the yul disambiguator:
|
||||||
|
``./scripts/soltest.sh -t "yulOptimizerTests/disambiguator/*" --no-smt``.
|
||||||
|
|
||||||
``./build/test/soltest --help`` has extensive help on all of the options available.
|
``./build/test/soltest --help`` has extensive help on all of the options available.
|
||||||
|
|
||||||
See especially:
|
See especially:
|
||||||
|
|
||||||
- `show_progress (-p) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
|
- `show_progress (-p) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
|
||||||
@ -109,20 +138,10 @@ See especially:
|
|||||||
|
|
||||||
.. note ::
|
.. note ::
|
||||||
|
|
||||||
Those working in a Windows environment wanting to run the above basic sets without libz3 in Git Bash, you would have to do: ``./build/test/Release/soltest.exe -- --no-smt``.
|
Those working in a Windows environment wanting to run the above basic sets
|
||||||
|
without libz3. Using Git Bash, you use: ``./build/test/Release/soltest.exe -- --no-smt``.
|
||||||
If you are running this in plain Command Prompt, use ``.\build\test\Release\soltest.exe -- --no-smt``.
|
If you are running this in plain Command Prompt, use ``.\build\test\Release\soltest.exe -- --no-smt``.
|
||||||
|
|
||||||
To run a subset of tests, you can use filters:
|
|
||||||
``./scripts/soltest.sh -t TestSuite/TestName``,
|
|
||||||
where ``TestName`` can be a wildcard ``*``.
|
|
||||||
|
|
||||||
For example, here is an example test you might run;
|
|
||||||
``./scripts/soltest.sh -t "yulOptimizerTests/disambiguator/*" --no-smt``.
|
|
||||||
This will test all the tests for the disambiguator.
|
|
||||||
|
|
||||||
To get a list of all tests, use
|
|
||||||
``./build/test/soltest --list_content=HRF``.
|
|
||||||
|
|
||||||
If you want to debug using GDB, make sure you build differently than the "usual".
|
If you want to debug using GDB, make sure you build differently than the "usual".
|
||||||
For example, you could run the following command in your ``build`` folder:
|
For example, you could run the following command in your ``build`` folder:
|
||||||
::
|
::
|
||||||
@ -130,14 +149,11 @@ For example, you could run the following command in your ``build`` folder:
|
|||||||
cmake -DCMAKE_BUILD_TYPE=Debug ..
|
cmake -DCMAKE_BUILD_TYPE=Debug ..
|
||||||
make
|
make
|
||||||
|
|
||||||
This will create symbols such that when you debug a test using the ``--debug`` flag, you will have access to functions and variables in which you can break or print with.
|
This creates symbols so that when you debug a test using the ``--debug`` flag,
|
||||||
|
you have access to functions and variables in which you can break or print with.
|
||||||
|
|
||||||
The script ``./scripts/tests.sh`` also runs commandline tests and compilation tests
|
|
||||||
in addition to those found in ``soltest``.
|
|
||||||
|
|
||||||
The CI runs additional tests (including ``solc-js`` and testing third party Solidity frameworks) that require compiling the Emscripten target.
|
|
||||||
|
|
||||||
|
The CI runs additional tests (including ``solc-js`` and testing third party Solidity
|
||||||
|
frameworks) that require compiling the Emscripten target.
|
||||||
|
|
||||||
Writing and running syntax tests
|
Writing and running syntax tests
|
||||||
--------------------------------
|
--------------------------------
|
||||||
@ -340,6 +356,11 @@ by as many concatenations of its contents as there were sets of variables suppli
|
|||||||
each time replacing any ``<inner>`` items by their respective value. Top-level variables can also be used
|
each time replacing any ``<inner>`` items by their respective value. Top-level variables can also be used
|
||||||
inside such areas.
|
inside such areas.
|
||||||
|
|
||||||
|
There are also conditionals of the form ``<?name>...<!name>...</name>``, where template replacements
|
||||||
|
continue recursively either in the first or the second segment depending on the value of the boolean
|
||||||
|
parameter ``name``. If ``<?+name>...<!+name>...</+name>`` is used, then the check is whether
|
||||||
|
the string parameter ``name`` is non-empty.
|
||||||
|
|
||||||
.. _documentation-style:
|
.. _documentation-style:
|
||||||
|
|
||||||
Documentation Style Guide
|
Documentation Style Guide
|
||||||
|
@ -454,7 +454,7 @@ The recipient should verify each message using the following process:
|
|||||||
|
|
||||||
We'll use the `ethereumjs-util <https://github.com/ethereumjs/ethereumjs-util>`_
|
We'll use the `ethereumjs-util <https://github.com/ethereumjs/ethereumjs-util>`_
|
||||||
library to write this verification. The final step can be done a number of ways,
|
library to write this verification. The final step can be done a number of ways,
|
||||||
and we use JavaScript. The following code borrows the `constructMessage` function from the signing **JavaScript code** above:
|
and we use JavaScript. The following code borrows the ``constructMessage`` function from the signing **JavaScript code** above:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
6
docs/grammar.rst
Normal file
6
docs/grammar.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
****************
|
||||||
|
Language Grammar
|
||||||
|
****************
|
||||||
|
|
||||||
|
.. literalinclude:: Solidity.g4
|
||||||
|
:language: antlr
|
@ -36,7 +36,7 @@ If you are new to the concept of smart contracts we recommend you start with
|
|||||||
:ref:`an example smart contract <simple-smart-contract>` written
|
:ref:`an example smart contract <simple-smart-contract>` written
|
||||||
in Solidity. When you are ready for more detail, we recommend you read the
|
in Solidity. When you are ready for more detail, we recommend you read the
|
||||||
:doc:`"Solidity by Example" <solidity-by-example>` and
|
:doc:`"Solidity by Example" <solidity-by-example>` and
|
||||||
:doc:`"Solidity in Depth" <solidity-in-depth>` sections to learn the core concepts of the language.
|
"Language Description" sections to learn the core concepts of the language.
|
||||||
|
|
||||||
For further reading, try :ref:`the basics of blockchains <blockchain-basics>`
|
For further reading, try :ref:`the basics of blockchains <blockchain-basics>`
|
||||||
and details of the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`.
|
and details of the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`.
|
||||||
@ -88,17 +88,49 @@ Contents
|
|||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
:caption: Basics
|
||||||
|
|
||||||
introduction-to-smart-contracts.rst
|
introduction-to-smart-contracts.rst
|
||||||
installing-solidity.rst
|
installing-solidity.rst
|
||||||
solidity-by-example.rst
|
solidity-by-example.rst
|
||||||
solidity-in-depth.rst
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Language Description
|
||||||
|
|
||||||
|
layout-of-source-files.rst
|
||||||
|
structure-of-a-contract.rst
|
||||||
|
types.rst
|
||||||
|
units-and-global-variables.rst
|
||||||
|
control-structures.rst
|
||||||
|
contracts.rst
|
||||||
|
assembly.rst
|
||||||
|
cheatsheet.rst
|
||||||
|
grammar.rst
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Internals
|
||||||
|
|
||||||
|
internals/layout_in_storage.rst
|
||||||
|
internals/layout_in_memory.rst
|
||||||
|
internals/layout_in_calldata.rst
|
||||||
|
internals/variable_cleanup.rst
|
||||||
|
internals/source_mappings.rst
|
||||||
|
internals/optimiser.rst
|
||||||
|
metadata.rst
|
||||||
|
abi-spec.rst
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Additional Material
|
||||||
|
|
||||||
|
050-breaking-changes.rst
|
||||||
|
060-breaking-changes.rst
|
||||||
natspec-format.rst
|
natspec-format.rst
|
||||||
security-considerations.rst
|
security-considerations.rst
|
||||||
resources.rst
|
resources.rst
|
||||||
using-the-compiler.rst
|
using-the-compiler.rst
|
||||||
metadata.rst
|
|
||||||
abi-spec.rst
|
|
||||||
yul.rst
|
yul.rst
|
||||||
style-guide.rst
|
style-guide.rst
|
||||||
common-patterns.rst
|
common-patterns.rst
|
||||||
|
@ -35,11 +35,11 @@ or if you require more compilation options.
|
|||||||
npm / Node.js
|
npm / Node.js
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Use `npm` for a convenient and portable way to install `solcjs`, a Solidity compiler. The
|
Use ``npm`` for a convenient and portable way to install ``solcjs``, a Solidity compiler. The
|
||||||
`solcjs` program has fewer features than the ways to access the compiler described
|
`solcjs` program has fewer features than the ways to access the compiler described
|
||||||
further down this page. The
|
further down this page. The
|
||||||
:ref:`commandline-compiler` documentation assumes you are using
|
:ref:`commandline-compiler` documentation assumes you are using
|
||||||
the full-featured compiler, `solc`. The usage of `solcjs` is documented inside its own
|
the full-featured compiler, ``solc``. The usage of ``solcjs`` is documented inside its own
|
||||||
`repository <https://github.com/ethereum/solc-js>`_.
|
`repository <https://github.com/ethereum/solc-js>`_.
|
||||||
|
|
||||||
Note: The solc-js project is derived from the C++
|
Note: The solc-js project is derived from the C++
|
||||||
@ -53,10 +53,10 @@ Please refer to the solc-js repository for instructions.
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The commandline executable is named `solcjs`.
|
The commandline executable is named ``solcjs``.
|
||||||
|
|
||||||
The comandline options of `solcjs` are not compatible with `solc` and tools (such as `geth`)
|
The comandline options of ``solcjs`` are not compatible with ``solc`` and tools (such as ``geth``)
|
||||||
expecting the behaviour of `solc` will not work with `solcjs`.
|
expecting the behaviour of ``solc`` will not work with ``solcjs``.
|
||||||
|
|
||||||
Docker
|
Docker
|
||||||
======
|
======
|
||||||
|
13
docs/internals/layout_in_calldata.rst
Normal file
13
docs/internals/layout_in_calldata.rst
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
*******************
|
||||||
|
Layout of Call Data
|
||||||
|
*******************
|
||||||
|
|
||||||
|
The input data for a function call is assumed to be in the format defined by the :ref:`ABI
|
||||||
|
specification <ABI>`. Among others, the ABI specification requires arguments to be padded to multiples of 32
|
||||||
|
bytes. The internal function calls use a different convention.
|
||||||
|
|
||||||
|
Arguments for the constructor of a contract are directly appended at the end of the
|
||||||
|
contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and
|
||||||
|
not by using the ``codesize`` opcode, since this of course changes when appending
|
||||||
|
data to the code.
|
||||||
|
|
39
docs/internals/layout_in_memory.rst
Normal file
39
docs/internals/layout_in_memory.rst
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
.. index: memory layout
|
||||||
|
|
||||||
|
****************
|
||||||
|
Layout in Memory
|
||||||
|
****************
|
||||||
|
|
||||||
|
Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:
|
||||||
|
|
||||||
|
- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods
|
||||||
|
- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer)
|
||||||
|
- ``0x60`` - ``0x7f`` (32 bytes): zero slot
|
||||||
|
|
||||||
|
Scratch space can be used between statements (i.e. within inline assembly). The zero slot
|
||||||
|
is used as initial value for dynamic memory arrays and should never be written to
|
||||||
|
(the free memory pointer points to ``0x80`` initially).
|
||||||
|
|
||||||
|
Solidity always places new objects at the free memory pointer and
|
||||||
|
memory is never freed (this might change in the future).
|
||||||
|
|
||||||
|
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this
|
||||||
|
is even true for ``byte[]``, but not for ``bytes`` and ``string``).
|
||||||
|
Multi-dimensional memory arrays are pointers to memory arrays. The length of a
|
||||||
|
dynamic array is stored at the first slot of the array and followed by the array
|
||||||
|
elements.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
There are some operations in Solidity that need a temporary memory area
|
||||||
|
larger than 64 bytes and therefore will not fit into the scratch space.
|
||||||
|
They will be placed where the free memory points to, but given their
|
||||||
|
short lifetime, the pointer is not updated. The memory may or may not
|
||||||
|
be zeroed out. Because of this, one should not expect the free memory
|
||||||
|
to point to zeroed out memory.
|
||||||
|
|
||||||
|
While it may seem like a good idea to use ``msize`` to arrive at a
|
||||||
|
definitely zeroed out memory area, using such a pointer non-temporarily
|
||||||
|
without updating the free memory pointer can have unexpected results.
|
||||||
|
|
||||||
|
.. index: calldata layout
|
359
docs/internals/layout_in_storage.rst
Normal file
359
docs/internals/layout_in_storage.rst
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
.. index:: storage, state variable, mapping
|
||||||
|
|
||||||
|
************************************
|
||||||
|
Layout of State Variables in Storage
|
||||||
|
************************************
|
||||||
|
|
||||||
|
.. _storage-inplace-encoding:
|
||||||
|
|
||||||
|
Statically-sized variables (everything except mapping and dynamically-sized
|
||||||
|
array types) are laid out contiguously in storage starting from position ``0``.
|
||||||
|
Multiple, contiguous items that need less than 32 bytes are packed into a single
|
||||||
|
storage slot if possible, according to the following rules:
|
||||||
|
|
||||||
|
- The first item in a storage slot is stored lower-order aligned.
|
||||||
|
- Elementary types use only as many bytes as are necessary to store them.
|
||||||
|
- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
|
||||||
|
- Structs and array data always start a new slot and occupy whole slots
|
||||||
|
(but items inside a struct or array are packed tightly according to these rules).
|
||||||
|
|
||||||
|
For contracts that use inheritance, the ordering of state variables is determined by the
|
||||||
|
C3-linearized order of contracts starting with the most base-ward contract. If allowed
|
||||||
|
by the above rules, state variables from different contracts do share the same storage slot.
|
||||||
|
|
||||||
|
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher.
|
||||||
|
This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller
|
||||||
|
than that, the EVM must use more operations in order to reduce the size of the element from 32
|
||||||
|
bytes to the desired size.
|
||||||
|
|
||||||
|
It is only beneficial to use reduced-size arguments if you are dealing with storage values
|
||||||
|
because the compiler will pack multiple elements into one storage slot, and thus, combine
|
||||||
|
multiple reads or writes into a single operation. When dealing with function arguments or memory
|
||||||
|
values, there is no inherent benefit because the compiler does not pack these values.
|
||||||
|
|
||||||
|
Finally, in order to allow the EVM to optimize for this, ensure that you try to order your
|
||||||
|
storage variables and ``struct`` members such that they can be packed tightly. For example,
|
||||||
|
declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of
|
||||||
|
``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the
|
||||||
|
latter will take up three.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The layout of state variables in storage is considered to be part of the external interface
|
||||||
|
of Solidity due to the fact that storage pointers can be passed to libraries. This means that
|
||||||
|
any change to the rules outlined in this section is considered a breaking change
|
||||||
|
of the language and due to its critical nature should be considered very carefully before
|
||||||
|
being executed.
|
||||||
|
|
||||||
|
|
||||||
|
Mappings and Dynamic Arrays
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. _storage-hashed-encoding:
|
||||||
|
|
||||||
|
Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash
|
||||||
|
computation to find the starting position of the value or the array data.
|
||||||
|
These starting positions are always full stack slots.
|
||||||
|
|
||||||
|
The mapping or the dynamic array itself occupies a slot in storage at some position ``p``
|
||||||
|
according to the above rule (or by recursively applying this rule for
|
||||||
|
mappings of mappings or arrays of arrays). For dynamic arrays,
|
||||||
|
this slot stores the number of elements in the array (byte arrays and
|
||||||
|
strings are an exception, see :ref:`below <bytes-and-string>`).
|
||||||
|
For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different
|
||||||
|
hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key
|
||||||
|
``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a
|
||||||
|
non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``.
|
||||||
|
|
||||||
|
So for the following contract snippet
|
||||||
|
the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``::
|
||||||
|
|
||||||
|
|
||||||
|
pragma solidity >=0.4.0 <0.7.0;
|
||||||
|
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
struct S { uint a; uint b; }
|
||||||
|
uint x;
|
||||||
|
mapping(uint => mapping(uint => S)) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. _bytes-and-string:
|
||||||
|
|
||||||
|
``bytes`` and ``string``
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same
|
||||||
|
slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored
|
||||||
|
in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``.
|
||||||
|
For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is
|
||||||
|
stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array
|
||||||
|
by checking if the lowest bit is set: short (not set) and long (set).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Handling invalidly encoded slots is currently not supported but may be added in the future.
|
||||||
|
|
||||||
|
JSON Output
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. _storage-layout-top-level:
|
||||||
|
|
||||||
|
The storage layout of a contract can be requested via
|
||||||
|
the :ref:`standard JSON interface <compiler-api>`. The output is a JSON object containing two keys,
|
||||||
|
``storage`` and ``types``. The ``storage`` object is an array where each
|
||||||
|
element has the following form:
|
||||||
|
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"astId": 2,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "x",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "0",
|
||||||
|
"type": "t_uint256"
|
||||||
|
}
|
||||||
|
|
||||||
|
The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA``
|
||||||
|
and
|
||||||
|
|
||||||
|
- ``astId`` is the id of the AST node of the state variable's declaration
|
||||||
|
- ``contract`` is the name of the contract including its path as prefix
|
||||||
|
- ``label`` is the name of the state variable
|
||||||
|
- ``offset`` is the offset in bytes within the storage slot according to the encoding
|
||||||
|
- ``slot`` is the storage slot where the state variable resides or starts. This
|
||||||
|
number may be very large and therefore its JSON value is represented as a
|
||||||
|
string.
|
||||||
|
- ``type`` is an identifier used as key to the variable's type information (described in the following)
|
||||||
|
|
||||||
|
The given ``type``, in this case ``t_uint256`` represents an element in
|
||||||
|
``types``, which has the form:
|
||||||
|
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
{
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "uint256",
|
||||||
|
"numberOfBytes": "32",
|
||||||
|
}
|
||||||
|
|
||||||
|
where
|
||||||
|
|
||||||
|
- ``encoding`` how the data is encoded in storage, where the possible values are:
|
||||||
|
|
||||||
|
- ``inplace``: data is laid out contiguously in storage (see :ref:`above <storage-inplace-encoding>`).
|
||||||
|
- ``mapping``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
|
||||||
|
- ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
|
||||||
|
- ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above <bytes-and-string>`).
|
||||||
|
|
||||||
|
- ``label`` is the canonical type name.
|
||||||
|
- ``numberOfBytes`` is the number of used bytes (as a decimal string).
|
||||||
|
Note that if ``numberOfBytes > 32`` this means that more than one slot is used.
|
||||||
|
|
||||||
|
Some types have extra information besides the four above. Mappings contain
|
||||||
|
its ``key`` and ``value`` types (again referencing an entry in this mapping
|
||||||
|
of types), arrays have its ``base`` type, and structs list their ``members`` in
|
||||||
|
the same format as the top-level ``storage`` (see :ref:`above
|
||||||
|
<storage-layout-top-level>`).
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
The JSON output format of a contract's storage layout is still considered experimental
|
||||||
|
and is subject to change in non-breaking releases of Solidity.
|
||||||
|
|
||||||
|
The following example shows a contract and its storage layout, containing
|
||||||
|
value and reference types, types that are encoded packed, and nested types.
|
||||||
|
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
pragma solidity >=0.4.0 <0.7.0;
|
||||||
|
contract A {
|
||||||
|
struct S {
|
||||||
|
uint128 a;
|
||||||
|
uint128 b;
|
||||||
|
uint[2] staticArray;
|
||||||
|
uint[] dynArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint x;
|
||||||
|
uint y;
|
||||||
|
S s;
|
||||||
|
address addr;
|
||||||
|
mapping (uint => mapping (address => bool)) map;
|
||||||
|
uint[] array;
|
||||||
|
string s1;
|
||||||
|
bytes b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
"storageLayout": {
|
||||||
|
"storage": [
|
||||||
|
{
|
||||||
|
"astId": 14,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "x",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "0",
|
||||||
|
"type": "t_uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 16,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "y",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "1",
|
||||||
|
"type": "t_uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 18,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "s",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "2",
|
||||||
|
"type": "t_struct(S)12_storage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 20,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "addr",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "6",
|
||||||
|
"type": "t_address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 26,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "map",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "7",
|
||||||
|
"type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 29,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "array",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "8",
|
||||||
|
"type": "t_array(t_uint256)dyn_storage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 31,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "s1",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "9",
|
||||||
|
"type": "t_string_storage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 33,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "b1",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "10",
|
||||||
|
"type": "t_bytes_storage"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": {
|
||||||
|
"t_address": {
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "address",
|
||||||
|
"numberOfBytes": "20"
|
||||||
|
},
|
||||||
|
"t_array(t_uint256)2_storage": {
|
||||||
|
"base": "t_uint256",
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "uint256[2]",
|
||||||
|
"numberOfBytes": "64"
|
||||||
|
},
|
||||||
|
"t_array(t_uint256)dyn_storage": {
|
||||||
|
"base": "t_uint256",
|
||||||
|
"encoding": "dynamic_array",
|
||||||
|
"label": "uint256[]",
|
||||||
|
"numberOfBytes": "32"
|
||||||
|
},
|
||||||
|
"t_bool": {
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "bool",
|
||||||
|
"numberOfBytes": "1"
|
||||||
|
},
|
||||||
|
"t_bytes_storage": {
|
||||||
|
"encoding": "bytes",
|
||||||
|
"label": "bytes",
|
||||||
|
"numberOfBytes": "32"
|
||||||
|
},
|
||||||
|
"t_mapping(t_address,t_bool)": {
|
||||||
|
"encoding": "mapping",
|
||||||
|
"key": "t_address",
|
||||||
|
"label": "mapping(address => bool)",
|
||||||
|
"numberOfBytes": "32",
|
||||||
|
"value": "t_bool"
|
||||||
|
},
|
||||||
|
"t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
|
||||||
|
"encoding": "mapping",
|
||||||
|
"key": "t_uint256",
|
||||||
|
"label": "mapping(uint256 => mapping(address => bool))",
|
||||||
|
"numberOfBytes": "32",
|
||||||
|
"value": "t_mapping(t_address,t_bool)"
|
||||||
|
},
|
||||||
|
"t_string_storage": {
|
||||||
|
"encoding": "bytes",
|
||||||
|
"label": "string",
|
||||||
|
"numberOfBytes": "32"
|
||||||
|
},
|
||||||
|
"t_struct(S)12_storage": {
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "struct A.S",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"astId": 2,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "a",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "0",
|
||||||
|
"type": "t_uint128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 4,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "b",
|
||||||
|
"offset": 16,
|
||||||
|
"slot": "0",
|
||||||
|
"type": "t_uint128"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 8,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "staticArray",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "1",
|
||||||
|
"type": "t_array(t_uint256)2_storage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"astId": 11,
|
||||||
|
"contract": "fileA:A",
|
||||||
|
"label": "dynArray",
|
||||||
|
"offset": 0,
|
||||||
|
"slot": "3",
|
||||||
|
"type": "t_array(t_uint256)dyn_storage"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"numberOfBytes": "128"
|
||||||
|
},
|
||||||
|
"t_uint128": {
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "uint128",
|
||||||
|
"numberOfBytes": "16"
|
||||||
|
},
|
||||||
|
"t_uint256": {
|
||||||
|
"encoding": "inplace",
|
||||||
|
"label": "uint256",
|
||||||
|
"numberOfBytes": "32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
docs/internals/optimiser.rst
Normal file
71
docs/internals/optimiser.rst
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.. index:: optimizer, common subexpression elimination, constant propagation
|
||||||
|
|
||||||
|
*************
|
||||||
|
The Optimiser
|
||||||
|
*************
|
||||||
|
|
||||||
|
This section discusses the optimiser that was first added to Solidity,
|
||||||
|
which operates on opcode streams. For information on the new Yul-based optimiser,
|
||||||
|
please see the `readme on github <https://github.com/ethereum/solidity/blob/develop/libyul/optimiser/README.md>`_.
|
||||||
|
|
||||||
|
The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks
|
||||||
|
at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser
|
||||||
|
analyses the instructions and records every modification to the stack,
|
||||||
|
memory, or storage as an expression which consists of an instruction and
|
||||||
|
a list of arguments which are pointers to other expressions. The optimiser
|
||||||
|
uses a component called "CommonSubexpressionEliminator" that amongst other
|
||||||
|
tasks, finds expressions that are always equal (on every input) and combines
|
||||||
|
them into an expression class. The optimiser first tries to find each new
|
||||||
|
expression in a list of already known expressions. If this does not work,
|
||||||
|
it simplifies the expression according to rules like
|
||||||
|
``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is
|
||||||
|
a recursive process, we can also apply the latter rule if the second factor
|
||||||
|
is a more complex expression where we know that it always evaluates to one.
|
||||||
|
Modifications to storage and memory locations have to erase knowledge about
|
||||||
|
storage and memory locations which are not known to be different. If we first
|
||||||
|
write to location x and then to location y and both are input variables, the
|
||||||
|
second could overwrite the first, so we do not know what is stored at x after
|
||||||
|
we wrote to y. If simplification of the expression x - y evaluates to a
|
||||||
|
non-zero constant, we know that we can keep our knowledge about what is stored at x.
|
||||||
|
|
||||||
|
After this process, we know which expressions have to be on the stack at
|
||||||
|
the end, and have a list of modifications to memory and storage. This information
|
||||||
|
is stored together with the basic blocks and is used to link them. Furthermore,
|
||||||
|
knowledge about the stack, storage and memory configuration is forwarded to
|
||||||
|
the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions,
|
||||||
|
we can build a complete control flow graph of the program. If there is only
|
||||||
|
one target we do not know (this can happen as in principle, jump targets can
|
||||||
|
be computed from inputs), we have to erase all knowledge about the input state
|
||||||
|
of a block as it can be the target of the unknown ``JUMP``. If the optimiser
|
||||||
|
finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it
|
||||||
|
to an unconditional jump.
|
||||||
|
|
||||||
|
As the last step, the code in each block is re-generated. The optimiser creates
|
||||||
|
a dependency graph from the expressions on the stack at the end of the block,
|
||||||
|
and it drops every operation that is not part of this graph. It generates code
|
||||||
|
that applies the modifications to memory and storage in the order they were
|
||||||
|
made in the original code (dropping modifications which were found not to be
|
||||||
|
needed). Finally, it generates all values that are required to be on the
|
||||||
|
stack in the correct place.
|
||||||
|
|
||||||
|
These steps are applied to each basic block and the newly generated code
|
||||||
|
is used as replacement if it is smaller. If a basic block is split at a
|
||||||
|
``JUMPI`` and during the analysis, the condition evaluates to a constant,
|
||||||
|
the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
uint x = 7;
|
||||||
|
data[7] = 9;
|
||||||
|
if (data[x] != x + 2)
|
||||||
|
return 2;
|
||||||
|
else
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
still simplifies to code which you can compile even though the instructions contained
|
||||||
|
a jump in the beginning of the process:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
data[7] = 9;
|
||||||
|
return 1;
|
62
docs/internals/source_mappings.rst
Normal file
62
docs/internals/source_mappings.rst
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
.. index:: source mappings
|
||||||
|
|
||||||
|
***************
|
||||||
|
Source Mappings
|
||||||
|
***************
|
||||||
|
|
||||||
|
As part of the AST output, the compiler provides the range of the source
|
||||||
|
code that is represented by the respective node in the AST. This can be
|
||||||
|
used for various purposes ranging from static analysis tools that report
|
||||||
|
errors based on the AST and debugging tools that highlight local variables
|
||||||
|
and their uses.
|
||||||
|
|
||||||
|
Furthermore, the compiler can also generate a mapping from the bytecode
|
||||||
|
to the range in the source code that generated the instruction. This is again
|
||||||
|
important for static analysis tools that operate on bytecode level and
|
||||||
|
for displaying the current position in the source code inside a debugger
|
||||||
|
or for breakpoint handling. This mapping also contains other information,
|
||||||
|
like the jump type and the modifier depth (see below).
|
||||||
|
|
||||||
|
Both kinds of source mappings use integer identifiers to refer to source files.
|
||||||
|
The identifier of a source file is stored in
|
||||||
|
``output['sources'][sourceName]['id']`` where ``output`` is the output of the
|
||||||
|
standard-json compiler interface parsed as JSON.
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
In the case of instructions that are not associated with any particular source file,
|
||||||
|
the source mapping assigns an integer identifier of ``-1``. This may happen for
|
||||||
|
bytecode sections stemming from compiler-generated inline assembly statements.
|
||||||
|
|
||||||
|
The source mappings inside the AST use the following
|
||||||
|
notation:
|
||||||
|
|
||||||
|
``s:l:f``
|
||||||
|
|
||||||
|
Where ``s`` is the byte-offset to the start of the range in the source file,
|
||||||
|
``l`` is the length of the source range in bytes and ``f`` is the source
|
||||||
|
index mentioned above.
|
||||||
|
|
||||||
|
The encoding in the source mapping for the bytecode is more complicated:
|
||||||
|
It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these
|
||||||
|
elements corresponds to an instruction, i.e. you cannot use the byte offset
|
||||||
|
but have to use the instruction offset (push instructions are longer than a single byte).
|
||||||
|
The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either
|
||||||
|
``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a
|
||||||
|
function, returns from a function or is a regular jump as part of e.g. a loop.
|
||||||
|
The last field, ``m``, is an integer that denotes the "modifier depth". This depth
|
||||||
|
is increased whenever the placeholder statement (``_``) is entered in a modifier
|
||||||
|
and decreased when it is left again. This allows debuggers to track tricky cases
|
||||||
|
like the same modifier being used twice or multiple placeholder statements being
|
||||||
|
used in a single modifier.
|
||||||
|
|
||||||
|
In order to compress these source mappings especially for bytecode, the
|
||||||
|
following rules are used:
|
||||||
|
|
||||||
|
- If a field is empty, the value of the preceding element is used.
|
||||||
|
- If a ``:`` is missing, all following fields are considered empty.
|
||||||
|
|
||||||
|
This means the following source mappings represent the same information:
|
||||||
|
|
||||||
|
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
|
||||||
|
|
||||||
|
``1:2:1;:9;2:1:2;;``
|
47
docs/internals/variable_cleanup.rst
Normal file
47
docs/internals/variable_cleanup.rst
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
.. index: variable cleanup
|
||||||
|
|
||||||
|
*********************
|
||||||
|
Cleaning Up Variables
|
||||||
|
*********************
|
||||||
|
|
||||||
|
When a value is shorter than 256 bit, in some cases the remaining bits
|
||||||
|
must be cleaned.
|
||||||
|
The Solidity compiler is designed to clean such remaining bits before any operations
|
||||||
|
that might be adversely affected by the potential garbage in the remaining bits.
|
||||||
|
For example, before writing a value to memory, the remaining bits need
|
||||||
|
to be cleared because the memory contents can be used for computing
|
||||||
|
hashes or sent as the data of a message call. Similarly, before
|
||||||
|
storing a value in the storage, the remaining bits need to be cleaned
|
||||||
|
because otherwise the garbled value can be observed.
|
||||||
|
|
||||||
|
On the other hand, we do not clean the bits if the immediately
|
||||||
|
following operation is not affected. For instance, since any non-zero
|
||||||
|
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
|
||||||
|
the boolean values before they are used as the condition for
|
||||||
|
``JUMPI``.
|
||||||
|
|
||||||
|
In addition to the design principle above, the Solidity compiler
|
||||||
|
cleans input data when it is loaded onto the stack.
|
||||||
|
|
||||||
|
Different types have different rules for cleaning up invalid values:
|
||||||
|
|
||||||
|
+---------------+---------------+-------------------+
|
||||||
|
|Type |Valid Values |Invalid Values Mean|
|
||||||
|
+===============+===============+===================+
|
||||||
|
|enum of n |0 until n - 1 |exception |
|
||||||
|
|members | | |
|
||||||
|
+---------------+---------------+-------------------+
|
||||||
|
|bool |0 or 1 |1 |
|
||||||
|
+---------------+---------------+-------------------+
|
||||||
|
|signed integers|sign-extended |currently silently |
|
||||||
|
| |word |wraps; in the |
|
||||||
|
| | |future exceptions |
|
||||||
|
| | |will be thrown |
|
||||||
|
| | | |
|
||||||
|
| | | |
|
||||||
|
+---------------+---------------+-------------------+
|
||||||
|
|unsigned |higher bits |currently silently |
|
||||||
|
|integers |zeroed |wraps; in the |
|
||||||
|
| | |future exceptions |
|
||||||
|
| | |will be thrown |
|
||||||
|
+---------------+---------------+-------------------+
|
@ -323,7 +323,7 @@ Every account has a persistent key-value store mapping 256-bit words to 256-bit
|
|||||||
words called **storage**.
|
words called **storage**.
|
||||||
|
|
||||||
Furthermore, every account has a **balance** in
|
Furthermore, every account has a **balance** in
|
||||||
Ether (in "Wei" to be exact, `1 ether` is `10**18 wei`) which can be modified by sending transactions that
|
Ether (in "Wei" to be exact, ``1 ether`` is ``10**18 wei``) which can be modified by sending transactions that
|
||||||
include Ether.
|
include Ether.
|
||||||
|
|
||||||
.. index:: ! transaction
|
.. index:: ! transaction
|
||||||
@ -520,9 +520,9 @@ idea, but it is potentially dangerous, as if someone sends Ether to removed
|
|||||||
contracts, the Ether is forever lost.
|
contracts, the Ether is forever lost.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
Even if a contract is removed by "selfdestruct", it is still part of the
|
Even if a contract is removed by ``selfdestruct``, it is still part of the
|
||||||
history of the blockchain and probably retained by most Ethereum nodes.
|
history of the blockchain and probably retained by most Ethereum nodes.
|
||||||
So using "selfdestruct" is not the same as deleting data from a hard disk.
|
So using ``selfdestruct`` is not the same as deleting data from a hard disk.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Even if a contract's code does not contain a call to ``selfdestruct``,
|
Even if a contract's code does not contain a call to ``selfdestruct``,
|
||||||
|
@ -1,814 +0,0 @@
|
|||||||
#############
|
|
||||||
Miscellaneous
|
|
||||||
#############
|
|
||||||
|
|
||||||
.. index:: storage, state variable, mapping
|
|
||||||
|
|
||||||
************************************
|
|
||||||
Layout of State Variables in Storage
|
|
||||||
************************************
|
|
||||||
|
|
||||||
.. _storage-inplace-encoding:
|
|
||||||
|
|
||||||
Statically-sized variables (everything except mapping and dynamically-sized
|
|
||||||
array types) are laid out contiguously in storage starting from position ``0``.
|
|
||||||
Multiple, contiguous items that need less than 32 bytes are packed into a single
|
|
||||||
storage slot if possible, according to the following rules:
|
|
||||||
|
|
||||||
- The first item in a storage slot is stored lower-order aligned.
|
|
||||||
- Elementary types use only as many bytes as are necessary to store them.
|
|
||||||
- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
|
|
||||||
- Structs and array data always start a new slot and occupy whole slots
|
|
||||||
(but items inside a struct or array are packed tightly according to these rules).
|
|
||||||
|
|
||||||
For contracts that use inheritance, the ordering of state variables is determined by the
|
|
||||||
C3-linearized order of contracts starting with the most base-ward contract. If allowed
|
|
||||||
by the above rules, state variables from different contracts do share the same storage slot.
|
|
||||||
|
|
||||||
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher.
|
|
||||||
This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller
|
|
||||||
than that, the EVM must use more operations in order to reduce the size of the element from 32
|
|
||||||
bytes to the desired size.
|
|
||||||
|
|
||||||
It is only beneficial to use reduced-size arguments if you are dealing with storage values
|
|
||||||
because the compiler will pack multiple elements into one storage slot, and thus, combine
|
|
||||||
multiple reads or writes into a single operation. When dealing with function arguments or memory
|
|
||||||
values, there is no inherent benefit because the compiler does not pack these values.
|
|
||||||
|
|
||||||
Finally, in order to allow the EVM to optimize for this, ensure that you try to order your
|
|
||||||
storage variables and ``struct`` members such that they can be packed tightly. For example,
|
|
||||||
declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of
|
|
||||||
``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the
|
|
||||||
latter will take up three.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
The layout of state variables in storage is considered to be part of the external interface
|
|
||||||
of Solidity due to the fact that storage pointers can be passed to libraries. This means that
|
|
||||||
any change to the rules outlined in this section is considered a breaking change
|
|
||||||
of the language and due to its critical nature should be considered very carefully before
|
|
||||||
being executed.
|
|
||||||
|
|
||||||
|
|
||||||
Mappings and Dynamic Arrays
|
|
||||||
===========================
|
|
||||||
|
|
||||||
.. _storage-hashed-encoding:
|
|
||||||
|
|
||||||
Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash
|
|
||||||
computation to find the starting position of the value or the array data.
|
|
||||||
These starting positions are always full stack slots.
|
|
||||||
|
|
||||||
The mapping or the dynamic array itself occupies a slot in storage at some position ``p``
|
|
||||||
according to the above rule (or by recursively applying this rule for
|
|
||||||
mappings of mappings or arrays of arrays). For dynamic arrays,
|
|
||||||
this slot stores the number of elements in the array (byte arrays and
|
|
||||||
strings are an exception, see :ref:`below <bytes-and-string>`).
|
|
||||||
For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different
|
|
||||||
hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key
|
|
||||||
``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a
|
|
||||||
non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``.
|
|
||||||
|
|
||||||
So for the following contract snippet
|
|
||||||
the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``::
|
|
||||||
|
|
||||||
|
|
||||||
pragma solidity >=0.4.0 <0.7.0;
|
|
||||||
|
|
||||||
|
|
||||||
contract C {
|
|
||||||
struct S { uint a; uint b; }
|
|
||||||
uint x;
|
|
||||||
mapping(uint => mapping(uint => S)) data;
|
|
||||||
}
|
|
||||||
|
|
||||||
.. _bytes-and-string:
|
|
||||||
|
|
||||||
``bytes`` and ``string``
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same
|
|
||||||
slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored
|
|
||||||
in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``.
|
|
||||||
For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is
|
|
||||||
stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array
|
|
||||||
by checking if the lowest bit is set: short (not set) and long (set).
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Handling invalidly encoded slots is currently not supported but may be added in the future.
|
|
||||||
|
|
||||||
JSON Output
|
|
||||||
===========
|
|
||||||
|
|
||||||
.. _storage-layout-top-level:
|
|
||||||
|
|
||||||
The storage layout of a contract can be requested via
|
|
||||||
the :ref:`standard JSON interface <compiler-api>`. The output is a JSON object containing two keys,
|
|
||||||
``storage`` and ``types``. The ``storage`` object is an array where each
|
|
||||||
element has the following form:
|
|
||||||
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"astId": 2,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "x",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "0",
|
|
||||||
"type": "t_uint256"
|
|
||||||
}
|
|
||||||
|
|
||||||
The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA``
|
|
||||||
and
|
|
||||||
|
|
||||||
- ``astId`` is the id of the AST node of the state variable's declaration
|
|
||||||
- ``contract`` is the name of the contract including its path as prefix
|
|
||||||
- ``label`` is the name of the state variable
|
|
||||||
- ``offset`` is the offset in bytes within the storage slot according to the encoding
|
|
||||||
- ``slot`` is the storage slot where the state variable resides or starts. This
|
|
||||||
number may be very large and therefore its JSON value is represented as a
|
|
||||||
string.
|
|
||||||
- ``type`` is an identifier used as key to the variable's type information (described in the following)
|
|
||||||
|
|
||||||
The given ``type``, in this case ``t_uint256`` represents an element in
|
|
||||||
``types``, which has the form:
|
|
||||||
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
{
|
|
||||||
"encoding": "inplace",
|
|
||||||
"label": "uint256",
|
|
||||||
"numberOfBytes": "32",
|
|
||||||
}
|
|
||||||
|
|
||||||
where
|
|
||||||
|
|
||||||
- ``encoding`` how the data is encoded in storage, where the possible values are:
|
|
||||||
|
|
||||||
- ``inplace``: data is laid out contiguously in storage (see :ref:`above <storage-inplace-encoding>`).
|
|
||||||
- ``mapping``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
|
|
||||||
- ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
|
|
||||||
- ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above <bytes-and-string>`).
|
|
||||||
|
|
||||||
- ``label`` is the canonical type name.
|
|
||||||
- ``numberOfBytes`` is the number of used bytes (as a decimal string).
|
|
||||||
Note that if ``numberOfBytes > 32`` this means that more than one slot is used.
|
|
||||||
|
|
||||||
Some types have extra information besides the four above. Mappings contain
|
|
||||||
its ``key`` and ``value`` types (again referencing an entry in this mapping
|
|
||||||
of types), arrays have its ``base`` type, and structs list their ``members`` in
|
|
||||||
the same format as the top-level ``storage`` (see :ref:`above
|
|
||||||
<storage-layout-top-level>`).
|
|
||||||
|
|
||||||
.. note ::
|
|
||||||
The JSON output format of a contract's storage layout is still considered experimental
|
|
||||||
and is subject to change in non-breaking releases of Solidity.
|
|
||||||
|
|
||||||
The following example shows a contract and its storage layout, containing
|
|
||||||
value and reference types, types that are encoded packed, and nested types.
|
|
||||||
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
pragma solidity >=0.4.0 <0.7.0;
|
|
||||||
contract A {
|
|
||||||
struct S {
|
|
||||||
uint128 a;
|
|
||||||
uint128 b;
|
|
||||||
uint[2] staticArray;
|
|
||||||
uint[] dynArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint x;
|
|
||||||
uint y;
|
|
||||||
S s;
|
|
||||||
address addr;
|
|
||||||
mapping (uint => mapping (address => bool)) map;
|
|
||||||
uint[] array;
|
|
||||||
string s1;
|
|
||||||
bytes b1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.. code::
|
|
||||||
|
|
||||||
"storageLayout": {
|
|
||||||
"storage": [
|
|
||||||
{
|
|
||||||
"astId": 14,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "x",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "0",
|
|
||||||
"type": "t_uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 16,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "y",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "1",
|
|
||||||
"type": "t_uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 18,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "s",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "2",
|
|
||||||
"type": "t_struct(S)12_storage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 20,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "addr",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "6",
|
|
||||||
"type": "t_address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 26,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "map",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "7",
|
|
||||||
"type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 29,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "array",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "8",
|
|
||||||
"type": "t_array(t_uint256)dyn_storage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 31,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "s1",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "9",
|
|
||||||
"type": "t_string_storage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 33,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "b1",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "10",
|
|
||||||
"type": "t_bytes_storage"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"types": {
|
|
||||||
"t_address": {
|
|
||||||
"encoding": "inplace",
|
|
||||||
"label": "address",
|
|
||||||
"numberOfBytes": "20"
|
|
||||||
},
|
|
||||||
"t_array(t_uint256)2_storage": {
|
|
||||||
"base": "t_uint256",
|
|
||||||
"encoding": "inplace",
|
|
||||||
"label": "uint256[2]",
|
|
||||||
"numberOfBytes": "64"
|
|
||||||
},
|
|
||||||
"t_array(t_uint256)dyn_storage": {
|
|
||||||
"base": "t_uint256",
|
|
||||||
"encoding": "dynamic_array",
|
|
||||||
"label": "uint256[]",
|
|
||||||
"numberOfBytes": "32"
|
|
||||||
},
|
|
||||||
"t_bool": {
|
|
||||||
"encoding": "inplace",
|
|
||||||
"label": "bool",
|
|
||||||
"numberOfBytes": "1"
|
|
||||||
},
|
|
||||||
"t_bytes_storage": {
|
|
||||||
"encoding": "bytes",
|
|
||||||
"label": "bytes",
|
|
||||||
"numberOfBytes": "32"
|
|
||||||
},
|
|
||||||
"t_mapping(t_address,t_bool)": {
|
|
||||||
"encoding": "mapping",
|
|
||||||
"key": "t_address",
|
|
||||||
"label": "mapping(address => bool)",
|
|
||||||
"numberOfBytes": "32",
|
|
||||||
"value": "t_bool"
|
|
||||||
},
|
|
||||||
"t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
|
|
||||||
"encoding": "mapping",
|
|
||||||
"key": "t_uint256",
|
|
||||||
"label": "mapping(uint256 => mapping(address => bool))",
|
|
||||||
"numberOfBytes": "32",
|
|
||||||
"value": "t_mapping(t_address,t_bool)"
|
|
||||||
},
|
|
||||||
"t_string_storage": {
|
|
||||||
"encoding": "bytes",
|
|
||||||
"label": "string",
|
|
||||||
"numberOfBytes": "32"
|
|
||||||
},
|
|
||||||
"t_struct(S)12_storage": {
|
|
||||||
"encoding": "inplace",
|
|
||||||
"label": "struct A.S",
|
|
||||||
"members": [
|
|
||||||
{
|
|
||||||
"astId": 2,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "a",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "0",
|
|
||||||
"type": "t_uint128"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 4,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "b",
|
|
||||||
"offset": 16,
|
|
||||||
"slot": "0",
|
|
||||||
"type": "t_uint128"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 8,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "staticArray",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "1",
|
|
||||||
"type": "t_array(t_uint256)2_storage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"astId": 11,
|
|
||||||
"contract": "fileA:A",
|
|
||||||
"label": "dynArray",
|
|
||||||
"offset": 0,
|
|
||||||
"slot": "3",
|
|
||||||
"type": "t_array(t_uint256)dyn_storage"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"numberOfBytes": "128"
|
|
||||||
},
|
|
||||||
"t_uint128": {
|
|
||||||
"encoding": "inplace",
|
|
||||||
"label": "uint128",
|
|
||||||
"numberOfBytes": "16"
|
|
||||||
},
|
|
||||||
"t_uint256": {
|
|
||||||
"encoding": "inplace",
|
|
||||||
"label": "uint256",
|
|
||||||
"numberOfBytes": "32"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.. index: memory layout
|
|
||||||
|
|
||||||
****************
|
|
||||||
Layout in Memory
|
|
||||||
****************
|
|
||||||
|
|
||||||
Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:
|
|
||||||
|
|
||||||
- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods
|
|
||||||
- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer)
|
|
||||||
- ``0x60`` - ``0x7f`` (32 bytes): zero slot
|
|
||||||
|
|
||||||
Scratch space can be used between statements (i.e. within inline assembly). The zero slot
|
|
||||||
is used as initial value for dynamic memory arrays and should never be written to
|
|
||||||
(the free memory pointer points to ``0x80`` initially).
|
|
||||||
|
|
||||||
Solidity always places new objects at the free memory pointer and
|
|
||||||
memory is never freed (this might change in the future).
|
|
||||||
|
|
||||||
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this
|
|
||||||
is even true for ``byte[]``, but not for ``bytes`` and ``string``).
|
|
||||||
Multi-dimensional memory arrays are pointers to memory arrays. The length of a
|
|
||||||
dynamic array is stored at the first slot of the array and followed by the array
|
|
||||||
elements.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
There are some operations in Solidity that need a temporary memory area
|
|
||||||
larger than 64 bytes and therefore will not fit into the scratch space.
|
|
||||||
They will be placed where the free memory points to, but given their
|
|
||||||
short lifetime, the pointer is not updated. The memory may or may not
|
|
||||||
be zeroed out. Because of this, one should not expect the free memory
|
|
||||||
to point to zeroed out memory.
|
|
||||||
|
|
||||||
While it may seem like a good idea to use ``msize`` to arrive at a
|
|
||||||
definitely zeroed out memory area, using such a pointer non-temporarily
|
|
||||||
without updating the free memory pointer can have unexpected results.
|
|
||||||
|
|
||||||
.. index: calldata layout
|
|
||||||
|
|
||||||
*******************
|
|
||||||
Layout of Call Data
|
|
||||||
*******************
|
|
||||||
|
|
||||||
The input data for a function call is assumed to be in the format defined by the :ref:`ABI
|
|
||||||
specification <ABI>`. Among others, the ABI specification requires arguments to be padded to multiples of 32
|
|
||||||
bytes. The internal function calls use a different convention.
|
|
||||||
|
|
||||||
Arguments for the constructor of a contract are directly appended at the end of the
|
|
||||||
contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and
|
|
||||||
not by using the ``codesize`` opcode, since this of course changes when appending
|
|
||||||
data to the code.
|
|
||||||
|
|
||||||
|
|
||||||
.. index: variable cleanup
|
|
||||||
|
|
||||||
*********************************
|
|
||||||
Internals - Cleaning Up Variables
|
|
||||||
*********************************
|
|
||||||
|
|
||||||
When a value is shorter than 256 bit, in some cases the remaining bits
|
|
||||||
must be cleaned.
|
|
||||||
The Solidity compiler is designed to clean such remaining bits before any operations
|
|
||||||
that might be adversely affected by the potential garbage in the remaining bits.
|
|
||||||
For example, before writing a value to memory, the remaining bits need
|
|
||||||
to be cleared because the memory contents can be used for computing
|
|
||||||
hashes or sent as the data of a message call. Similarly, before
|
|
||||||
storing a value in the storage, the remaining bits need to be cleaned
|
|
||||||
because otherwise the garbled value can be observed.
|
|
||||||
|
|
||||||
On the other hand, we do not clean the bits if the immediately
|
|
||||||
following operation is not affected. For instance, since any non-zero
|
|
||||||
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
|
|
||||||
the boolean values before they are used as the condition for
|
|
||||||
``JUMPI``.
|
|
||||||
|
|
||||||
In addition to the design principle above, the Solidity compiler
|
|
||||||
cleans input data when it is loaded onto the stack.
|
|
||||||
|
|
||||||
Different types have different rules for cleaning up invalid values:
|
|
||||||
|
|
||||||
+---------------+---------------+-------------------+
|
|
||||||
|Type |Valid Values |Invalid Values Mean|
|
|
||||||
+===============+===============+===================+
|
|
||||||
|enum of n |0 until n - 1 |exception |
|
|
||||||
|members | | |
|
|
||||||
+---------------+---------------+-------------------+
|
|
||||||
|bool |0 or 1 |1 |
|
|
||||||
+---------------+---------------+-------------------+
|
|
||||||
|signed integers|sign-extended |currently silently |
|
|
||||||
| |word |wraps; in the |
|
|
||||||
| | |future exceptions |
|
|
||||||
| | |will be thrown |
|
|
||||||
| | | |
|
|
||||||
| | | |
|
|
||||||
+---------------+---------------+-------------------+
|
|
||||||
|unsigned |higher bits |currently silently |
|
|
||||||
|integers |zeroed |wraps; in the |
|
|
||||||
| | |future exceptions |
|
|
||||||
| | |will be thrown |
|
|
||||||
+---------------+---------------+-------------------+
|
|
||||||
|
|
||||||
.. index:: optimizer, common subexpression elimination, constant propagation
|
|
||||||
|
|
||||||
*************************
|
|
||||||
Internals - The Optimiser
|
|
||||||
*************************
|
|
||||||
|
|
||||||
This section discusses the optimiser that was first added to Solidity,
|
|
||||||
which operates on opcode streams. For information on the new Yul-based optimiser,
|
|
||||||
please see the `readme on github <https://github.com/ethereum/solidity/blob/develop/libyul/optimiser/README.md>`_.
|
|
||||||
|
|
||||||
The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks
|
|
||||||
at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser
|
|
||||||
analyses the instructions and records every modification to the stack,
|
|
||||||
memory, or storage as an expression which consists of an instruction and
|
|
||||||
a list of arguments which are pointers to other expressions. The optimiser
|
|
||||||
uses a component called "CommonSubexpressionEliminator" that amongst other
|
|
||||||
tasks, finds expressions that are always equal (on every input) and combines
|
|
||||||
them into an expression class. The optimiser first tries to find each new
|
|
||||||
expression in a list of already known expressions. If this does not work,
|
|
||||||
it simplifies the expression according to rules like
|
|
||||||
``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is
|
|
||||||
a recursive process, we can also apply the latter rule if the second factor
|
|
||||||
is a more complex expression where we know that it always evaluates to one.
|
|
||||||
Modifications to storage and memory locations have to erase knowledge about
|
|
||||||
storage and memory locations which are not known to be different. If we first
|
|
||||||
write to location x and then to location y and both are input variables, the
|
|
||||||
second could overwrite the first, so we do not know what is stored at x after
|
|
||||||
we wrote to y. If simplification of the expression x - y evaluates to a
|
|
||||||
non-zero constant, we know that we can keep our knowledge about what is stored at x.
|
|
||||||
|
|
||||||
After this process, we know which expressions have to be on the stack at
|
|
||||||
the end, and have a list of modifications to memory and storage. This information
|
|
||||||
is stored together with the basic blocks and is used to link them. Furthermore,
|
|
||||||
knowledge about the stack, storage and memory configuration is forwarded to
|
|
||||||
the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions,
|
|
||||||
we can build a complete control flow graph of the program. If there is only
|
|
||||||
one target we do not know (this can happen as in principle, jump targets can
|
|
||||||
be computed from inputs), we have to erase all knowledge about the input state
|
|
||||||
of a block as it can be the target of the unknown ``JUMP``. If the optimiser
|
|
||||||
finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it
|
|
||||||
to an unconditional jump.
|
|
||||||
|
|
||||||
As the last step, the code in each block is re-generated. The optimiser creates
|
|
||||||
a dependency graph from the expressions on the stack at the end of the block,
|
|
||||||
and it drops every operation that is not part of this graph. It generates code
|
|
||||||
that applies the modifications to memory and storage in the order they were
|
|
||||||
made in the original code (dropping modifications which were found not to be
|
|
||||||
needed). Finally, it generates all values that are required to be on the
|
|
||||||
stack in the correct place.
|
|
||||||
|
|
||||||
These steps are applied to each basic block and the newly generated code
|
|
||||||
is used as replacement if it is smaller. If a basic block is split at a
|
|
||||||
``JUMPI`` and during the analysis, the condition evaluates to a constant,
|
|
||||||
the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
uint x = 7;
|
|
||||||
data[7] = 9;
|
|
||||||
if (data[x] != x + 2)
|
|
||||||
return 2;
|
|
||||||
else
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
still simplifies to code which you can compile even though the instructions contained
|
|
||||||
a jump in the beginning of the process:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
data[7] = 9;
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
.. index:: source mappings
|
|
||||||
|
|
||||||
***************
|
|
||||||
Source Mappings
|
|
||||||
***************
|
|
||||||
|
|
||||||
As part of the AST output, the compiler provides the range of the source
|
|
||||||
code that is represented by the respective node in the AST. This can be
|
|
||||||
used for various purposes ranging from static analysis tools that report
|
|
||||||
errors based on the AST and debugging tools that highlight local variables
|
|
||||||
and their uses.
|
|
||||||
|
|
||||||
Furthermore, the compiler can also generate a mapping from the bytecode
|
|
||||||
to the range in the source code that generated the instruction. This is again
|
|
||||||
important for static analysis tools that operate on bytecode level and
|
|
||||||
for displaying the current position in the source code inside a debugger
|
|
||||||
or for breakpoint handling. This mapping also contains other information,
|
|
||||||
like the jump type and the modifier depth (see below).
|
|
||||||
|
|
||||||
Both kinds of source mappings use integer identifiers to refer to source files.
|
|
||||||
The identifier of a source file is stored in
|
|
||||||
``output['sources'][sourceName]['id']`` where ``output`` is the output of the
|
|
||||||
standard-json compiler interface parsed as JSON.
|
|
||||||
|
|
||||||
.. note ::
|
|
||||||
In the case of instructions that are not associated with any particular source file,
|
|
||||||
the source mapping assigns an integer identifier of ``-1``. This may happen for
|
|
||||||
bytecode sections stemming from compiler-generated inline assembly statements.
|
|
||||||
|
|
||||||
The source mappings inside the AST use the following
|
|
||||||
notation:
|
|
||||||
|
|
||||||
``s:l:f``
|
|
||||||
|
|
||||||
Where ``s`` is the byte-offset to the start of the range in the source file,
|
|
||||||
``l`` is the length of the source range in bytes and ``f`` is the source
|
|
||||||
index mentioned above.
|
|
||||||
|
|
||||||
The encoding in the source mapping for the bytecode is more complicated:
|
|
||||||
It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these
|
|
||||||
elements corresponds to an instruction, i.e. you cannot use the byte offset
|
|
||||||
but have to use the instruction offset (push instructions are longer than a single byte).
|
|
||||||
The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either
|
|
||||||
``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a
|
|
||||||
function, returns from a function or is a regular jump as part of e.g. a loop.
|
|
||||||
The last field, ``m``, is an integer that denotes the "modifier depth". This depth
|
|
||||||
is increased whenever the placeholder statement (``_``) is entered in a modifier
|
|
||||||
and decreased when it is left again. This allows debuggers to track tricky cases
|
|
||||||
like the same modifier being used twice or multiple placeholder statements being
|
|
||||||
used in a single modifier.
|
|
||||||
|
|
||||||
In order to compress these source mappings especially for bytecode, the
|
|
||||||
following rules are used:
|
|
||||||
|
|
||||||
- If a field is empty, the value of the preceding element is used.
|
|
||||||
- If a ``:`` is missing, all following fields are considered empty.
|
|
||||||
|
|
||||||
This means the following source mappings represent the same information:
|
|
||||||
|
|
||||||
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
|
|
||||||
|
|
||||||
``1:2:1;:9;2:1:2;;``
|
|
||||||
|
|
||||||
***************
|
|
||||||
Tips and Tricks
|
|
||||||
***************
|
|
||||||
|
|
||||||
* Use ``delete`` on arrays to delete all its elements.
|
|
||||||
* Use shorter types for struct elements and sort them such that short types are
|
|
||||||
grouped together. This can lower the gas costs as multiple ``SSTORE`` operations
|
|
||||||
might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is
|
|
||||||
what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
|
|
||||||
* Make your state variables public - the compiler creates :ref:`getters <visibility-and-getters>` for you automatically.
|
|
||||||
* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`.
|
|
||||||
* Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});``
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
If the storage struct has tightly packed properties, initialize it with separate
|
|
||||||
assignments: ``x.a = 1; x.b = 2;``. In this way it will be easier for the
|
|
||||||
optimizer to update storage in one go, thus making assignment cheaper.
|
|
||||||
|
|
||||||
**********
|
|
||||||
Cheatsheet
|
|
||||||
**********
|
|
||||||
|
|
||||||
.. index:: precedence
|
|
||||||
|
|
||||||
.. _order:
|
|
||||||
|
|
||||||
Order of Precedence of Operators
|
|
||||||
================================
|
|
||||||
|
|
||||||
The following is the order of precedence for operators, listed in order of evaluation.
|
|
||||||
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| Precedence | Description | Operator |
|
|
||||||
+============+=====================================+============================================+
|
|
||||||
| *1* | Postfix increment and decrement | ``++``, ``--`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | New expression | ``new <typename>`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Array subscripting | ``<array>[<index>]`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Member access | ``<object>.<member>`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Function-like call | ``<func>(<args...>)`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Parentheses | ``(<statement>)`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *2* | Prefix increment and decrement | ``++``, ``--`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Unary minus | ``-`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Unary operations | ``delete`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Logical NOT | ``!`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Bitwise NOT | ``~`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *3* | Exponentiation | ``**`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *5* | Addition and subtraction | ``+``, ``-`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *6* | Bitwise shift operators | ``<<``, ``>>`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *7* | Bitwise AND | ``&`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *8* | Bitwise XOR | ``^`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *9* | Bitwise OR | ``|`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *11* | Equality operators | ``==``, ``!=`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *12* | Logical AND | ``&&`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *13* | Logical OR | ``||`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *14* | Ternary operator | ``<conditional> ? <if-true> : <if-false>`` |
|
|
||||||
+ +-------------------------------------+--------------------------------------------+
|
|
||||||
| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
|
|
||||||
| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, |
|
|
||||||
| | | ``%=`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
| *15* | Comma operator | ``,`` |
|
|
||||||
+------------+-------------------------------------+--------------------------------------------+
|
|
||||||
|
|
||||||
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
|
|
||||||
|
|
||||||
Global Variables
|
|
||||||
================
|
|
||||||
|
|
||||||
- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI <ABI>`-decodes
|
|
||||||
the provided data. The types are given in parentheses as second argument.
|
|
||||||
Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))``
|
|
||||||
- ``abi.encode(...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes the given arguments
|
|
||||||
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of
|
|
||||||
the given arguments. Note that this encoding can be ambiguous!
|
|
||||||
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes
|
|
||||||
the given arguments starting from the second and prepends the given four-byte selector
|
|
||||||
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent
|
|
||||||
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)```
|
|
||||||
- ``block.coinbase`` (``address payable``): current block miner's address
|
|
||||||
- ``block.difficulty`` (``uint``): current block difficulty
|
|
||||||
- ``block.gaslimit`` (``uint``): current block gaslimit
|
|
||||||
- ``block.number`` (``uint``): current block number
|
|
||||||
- ``block.timestamp`` (``uint``): current block timestamp
|
|
||||||
- ``gasleft() returns (uint256)``: remaining gas
|
|
||||||
- ``msg.data`` (``bytes``): complete calldata
|
|
||||||
- ``msg.sender`` (``address payable``): sender of the message (current call)
|
|
||||||
- ``msg.value`` (``uint``): number of wei sent with the message
|
|
||||||
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
|
|
||||||
- ``tx.gasprice`` (``uint``): gas price of the transaction
|
|
||||||
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
|
|
||||||
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
|
|
||||||
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use
|
|
||||||
for malformed input or error in external component)
|
|
||||||
- ``require(bool condition, string memory message)``: abort execution and revert state changes if
|
|
||||||
condition is ``false`` (use for malformed input or error in external component). Also provide error message.
|
|
||||||
- ``revert()``: abort execution and revert state changes
|
|
||||||
- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string
|
|
||||||
- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks
|
|
||||||
- ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input
|
|
||||||
- ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input
|
|
||||||
- ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input
|
|
||||||
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with
|
|
||||||
the public key from elliptic curve signature, return zero on error
|
|
||||||
- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with
|
|
||||||
arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
|
||||||
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed
|
|
||||||
with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
|
||||||
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable``
|
|
||||||
- ``super``: the contract one level higher in the inheritance hierarchy
|
|
||||||
- ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address
|
|
||||||
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
|
|
||||||
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`,
|
|
||||||
returns ``false`` on failure
|
|
||||||
- ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
|
|
||||||
- ``type(C).name`` (``string``): the name of the contract
|
|
||||||
- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`.
|
|
||||||
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
|
|
||||||
unless you know what you are doing.
|
|
||||||
|
|
||||||
Both the timestamp and the block hash can be influenced by miners to some degree.
|
|
||||||
Bad actors in the mining community can for example run a casino payout function on a chosen hash
|
|
||||||
and just retry a different hash if they did not receive any money.
|
|
||||||
|
|
||||||
The current block timestamp must be strictly larger than the timestamp of the last block,
|
|
||||||
but the only guarantee is that it will be somewhere between the timestamps of two
|
|
||||||
consecutive blocks in the canonical chain.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
The block hashes are not available for all blocks for scalability reasons.
|
|
||||||
You can only access the hashes of the most recent 256 blocks, all other
|
|
||||||
values will be zero.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``,
|
|
||||||
``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and
|
|
||||||
``sha3`` as alias for ``keccak256``.
|
|
||||||
|
|
||||||
.. index:: visibility, public, private, external, internal
|
|
||||||
|
|
||||||
Function Visibility Specifiers
|
|
||||||
==============================
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
function myFunction() <visibility specifier> returns (bool) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
- ``public``: visible externally and internally (creates a :ref:`getter function<getter-functions>` for storage/state variables)
|
|
||||||
- ``private``: only visible in the current contract
|
|
||||||
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
|
|
||||||
- ``internal``: only visible internally
|
|
||||||
|
|
||||||
|
|
||||||
.. index:: modifiers, pure, view, payable, constant, anonymous, indexed
|
|
||||||
|
|
||||||
Modifiers
|
|
||||||
=========
|
|
||||||
|
|
||||||
- ``pure`` for functions: Disallows modification or access of state.
|
|
||||||
- ``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
|
|
||||||
behaviour to be changed in derived contracts.
|
|
||||||
- ``override``: States that this function, modifier or public state variable changes
|
|
||||||
the behaviour of a function or modifier in a base contract.
|
|
||||||
|
|
||||||
Reserved Keywords
|
|
||||||
=================
|
|
||||||
|
|
||||||
These keywords are reserved in Solidity. They might become part of the syntax in the future:
|
|
||||||
|
|
||||||
``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``,
|
|
||||||
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
|
|
||||||
``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``,
|
|
||||||
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``,
|
|
||||||
``unchecked``.
|
|
||||||
|
|
||||||
Language Grammar
|
|
||||||
================
|
|
||||||
|
|
||||||
.. literalinclude:: Solidity.g4
|
|
||||||
:language: antlr
|
|
@ -73,8 +73,8 @@ Tags
|
|||||||
|
|
||||||
All tags are optional. The following table explains the purpose of each
|
All tags are optional. The following table explains the purpose of each
|
||||||
NatSpec tag and where it may be used. As a special case, if no tags are
|
NatSpec tag and where it may be used. As a special case, if no tags are
|
||||||
used then the Solidity compiler will interpret a `///` or `/**` comment
|
used then the Solidity compiler will interpret a ``///`` or ``/**`` comment
|
||||||
in the same way as if it were tagged with `@notice`.
|
in the same way as if it were tagged with ``@notice``.
|
||||||
|
|
||||||
=========== =============================================================================== =============================
|
=========== =============================================================================== =============================
|
||||||
Tag Context
|
Tag Context
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
sphinx_rtd_theme>=0.3.1
|
sphinx_rtd_theme>=0.3.1
|
||||||
pygments-lexer-solidity>=0.3.1
|
pygments-lexer-solidity>=0.5.1
|
||||||
|
@ -491,7 +491,8 @@ Horn clauses, where the lifecycle of the contract is represented by a loop
|
|||||||
that can visit every public/external function non-deterministically. This way,
|
that can visit every public/external function non-deterministically. This way,
|
||||||
the behavior of the entire contract over an unbounded number of transactions
|
the behavior of the entire contract over an unbounded number of transactions
|
||||||
is taken into account when analyzing any function. Loops are fully supported
|
is taken into account when analyzing any function. Loops are fully supported
|
||||||
by this engine. Function calls are currently unsupported.
|
by this engine. Internal function calls are supported, but external function
|
||||||
|
calls are currently unsupported.
|
||||||
|
|
||||||
The CHC engine is much more powerful than BMC in terms of what it can prove,
|
The CHC engine is much more powerful than BMC in terms of what it can prove,
|
||||||
and might require more computing resources.
|
and might require more computing resources.
|
||||||
@ -505,10 +506,16 @@ erasing knowledge or using a non-precise type). If it determines that a
|
|||||||
verification target is safe, it is indeed safe, that is, there are no false
|
verification target is safe, it is indeed safe, that is, there are no false
|
||||||
negatives (unless there is a bug in the SMTChecker).
|
negatives (unless there is a bug in the SMTChecker).
|
||||||
|
|
||||||
Function calls to the same contract (or base contracts) are inlined when
|
In the BMC engine, function calls to the same contract (or base contracts) are
|
||||||
possible, that is, when their implementation is available.
|
inlined when possible, that is, when their implementation is available. Calls
|
||||||
Calls to functions in other contracts are not inlined even if their code is
|
to functions in other contracts are not inlined even if their code is
|
||||||
available, since we cannot guarantee that the actual deployed code is the same.
|
available, since we cannot guarantee that the actual deployed code is the same.
|
||||||
|
|
||||||
|
The CHC engine creates nonlinear Horn clauses that use summaries of the called
|
||||||
|
functions to support internal function calls. The same approach can and will be
|
||||||
|
used for external function calls, but the latter requires more work regarding
|
||||||
|
the entire state of the blockchain and is still unimplemented.
|
||||||
|
|
||||||
Complex pure functions are abstracted by an uninterpreted function (UF) over
|
Complex pure functions are abstracted by an uninterpreted function (UF) over
|
||||||
the arguments.
|
the arguments.
|
||||||
|
|
||||||
@ -519,11 +526,14 @@ the arguments.
|
|||||||
+-----------------------------------+--------------------------------------+
|
+-----------------------------------+--------------------------------------+
|
||||||
|``require`` |Assumption |
|
|``require`` |Assumption |
|
||||||
+-----------------------------------+--------------------------------------+
|
+-----------------------------------+--------------------------------------+
|
||||||
|internal |Inline function call |
|
|internal |BMC: Inline function call |
|
||||||
|
| |CHC: Function summaries |
|
||||||
+-----------------------------------+--------------------------------------+
|
+-----------------------------------+--------------------------------------+
|
||||||
|external |Inline function call |
|
|external |BMC: Inline function call or |
|
||||||
| |Erase knowledge about state variables |
|
| |erase knowledge about state variables |
|
||||||
| |and local storage references |
|
| |and local storage references. |
|
||||||
|
| |CHC: Function summaries and erase |
|
||||||
|
| |state knowledge. |
|
||||||
+-----------------------------------+--------------------------------------+
|
+-----------------------------------+--------------------------------------+
|
||||||
|``gasleft``, ``blockhash``, |Abstracted with UF |
|
|``gasleft``, ``blockhash``, |Abstracted with UF |
|
||||||
|``keccak256``, ``ecrecover`` | |
|
|``keccak256``, ``ecrecover`` | |
|
||||||
@ -534,8 +544,8 @@ the arguments.
|
|||||||
|implementation (external or | |
|
|implementation (external or | |
|
||||||
|complex) | |
|
|complex) | |
|
||||||
+-----------------------------------+--------------------------------------+
|
+-----------------------------------+--------------------------------------+
|
||||||
|external functions without |Unsupported |
|
|external functions without |BMC: Unsupported |
|
||||||
|implementation | |
|
|implementation |CHC: Nondeterministic summary |
|
||||||
+-----------------------------------+--------------------------------------+
|
+-----------------------------------+--------------------------------------+
|
||||||
|others |Currently unsupported |
|
|others |Currently unsupported |
|
||||||
+-----------------------------------+--------------------------------------+
|
+-----------------------------------+--------------------------------------+
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
#################
|
|
||||||
Solidity in Depth
|
|
||||||
#################
|
|
||||||
|
|
||||||
This section should provide you with all you need to know about Solidity.
|
|
||||||
If something is missing here, please contact us on
|
|
||||||
`Gitter <https://gitter.im/ethereum/solidity>`_ or create a pull request on
|
|
||||||
`Github <https://github.com/ethereum/solidity/pulls>`_.
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
layout-of-source-files.rst
|
|
||||||
structure-of-a-contract.rst
|
|
||||||
types.rst
|
|
||||||
units-and-global-variables.rst
|
|
||||||
control-structures.rst
|
|
||||||
contracts.rst
|
|
||||||
assembly.rst
|
|
||||||
miscellaneous.rst
|
|
||||||
050-breaking-changes.rst
|
|
||||||
060-breaking-changes.rst
|
|
@ -24,8 +24,11 @@ solidity code. The goal of this guide is *consistency*. A quote from python's
|
|||||||
`pep8 <https://www.python.org/dev/peps/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds>`_
|
`pep8 <https://www.python.org/dev/peps/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds>`_
|
||||||
captures this concept well.
|
captures this concept well.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important.
|
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important.
|
||||||
But most importantly: know when to be inconsistent -- sometimes the style guide just doesn't apply. When in doubt, use your best judgement. Look at other examples and decide what looks best. And don't hesitate to ask!
|
|
||||||
|
But most importantly: **know when to be inconsistent** -- sometimes the style guide just doesn't apply. When in doubt, use your best judgement. Look at other examples and decide what looks best. And don't hesitate to ask!
|
||||||
|
|
||||||
|
|
||||||
***********
|
***********
|
||||||
@ -383,8 +386,7 @@ No::
|
|||||||
|
|
||||||
function spam(uint i , Coin coin) public ;
|
function spam(uint i , Coin coin) public ;
|
||||||
|
|
||||||
More than one space around an assignment or other operator to align with
|
More than one space around an assignment or other operator to align with another:
|
||||||
another:
|
|
||||||
|
|
||||||
Yes::
|
Yes::
|
||||||
|
|
||||||
@ -996,7 +998,7 @@ Contract and Library Names
|
|||||||
* Contract and library names should also match their filenames.
|
* Contract and library names should also match their filenames.
|
||||||
* If a contract file includes multiple contracts and/or libraries, then the filename should match the *core contract*. This is not recommended however if it can be avoided.
|
* If a contract file includes multiple contracts and/or libraries, then the filename should match the *core contract*. This is not recommended however if it can be avoided.
|
||||||
|
|
||||||
As shown in the example below, if the contract name is `Congress` and the library name is `Owned`, then their associated filenames should be `Congress.sol` and `Owned.sol`.
|
As shown in the example below, if the contract name is ``Congress`` and the library name is ``Owned``, then their associated filenames should be ``Congress.sol`` and ``Owned.sol``.
|
||||||
|
|
||||||
Yes::
|
Yes::
|
||||||
|
|
||||||
@ -1132,8 +1134,8 @@ Solidity contracts can have a form of comments that are the basis of the
|
|||||||
Ethereum Natural Language Specification Format.
|
Ethereum Natural Language Specification Format.
|
||||||
|
|
||||||
Add comments above functions or contracts following `doxygen <http://www.doxygen.nl>`_ notation
|
Add comments above functions or contracts following `doxygen <http://www.doxygen.nl>`_ notation
|
||||||
of one or multiple lines starting with `///` or a
|
of one or multiple lines starting with ``///`` or a
|
||||||
multiline comment starting with `/**` and ending with `*/`.
|
multiline comment starting with ``/**`` and ending with ``*/``.
|
||||||
|
|
||||||
For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments
|
For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments
|
||||||
added looks like the one below::
|
added looks like the one below::
|
||||||
|
@ -16,7 +16,7 @@ operators. For a quick reference of the various operators, see :ref:`order`.
|
|||||||
The concept of "undefined" or "null" values does not exist in Solidity, but newly
|
The concept of "undefined" or "null" values does not exist in Solidity, but newly
|
||||||
declared variables always have a :ref:`default value<default-value>` dependent
|
declared variables always have a :ref:`default value<default-value>` dependent
|
||||||
on its type. To handle any unexpected values, you should use the :ref:`revert function<assert-and-require>` to revert the whole transaction, or return a
|
on its type. To handle any unexpected values, you should use the :ref:`revert function<assert-and-require>` to revert the whole transaction, or return a
|
||||||
tuple with a second `bool` value denoting success.
|
tuple with a second ``bool`` value denoting success.
|
||||||
|
|
||||||
.. include:: types/value-types.rst
|
.. include:: types/value-types.rst
|
||||||
|
|
||||||
@ -26,4 +26,4 @@ tuple with a second `bool` value denoting success.
|
|||||||
|
|
||||||
.. include:: types/operators.rst
|
.. include:: types/operators.rst
|
||||||
|
|
||||||
.. include:: types/conversion.rst
|
.. include:: types/conversion.rst
|
||||||
|
@ -138,8 +138,8 @@ the ``sum`` function iterates over to sum all the values.
|
|||||||
if (keyIndex > 0)
|
if (keyIndex > 0)
|
||||||
return true;
|
return true;
|
||||||
else {
|
else {
|
||||||
self.keys.push();
|
|
||||||
keyIndex = self.keys.length;
|
keyIndex = self.keys.length;
|
||||||
|
self.keys.push();
|
||||||
self.data[key].keyIndex = keyIndex + 1;
|
self.data[key].keyIndex = keyIndex + 1;
|
||||||
self.keys[keyIndex].key = key;
|
self.keys[keyIndex].key = key;
|
||||||
self.size++;
|
self.size++;
|
||||||
|
@ -332,7 +332,7 @@ the :ref:`address type<address>`.
|
|||||||
Before version 0.5.0, contracts directly derived from the address type
|
Before version 0.5.0, contracts directly derived from the address type
|
||||||
and there was no distinction between ``address`` and ``address payable``.
|
and there was no distinction between ``address`` and ``address payable``.
|
||||||
|
|
||||||
If you declare a local variable of contract type (`MyContract c`), you can call
|
If you declare a local variable of contract type (``MyContract c``), you can call
|
||||||
functions on that contract. Take care to assign it from somewhere that is the
|
functions on that contract. Take care to assign it from somewhere that is the
|
||||||
same contract type.
|
same contract type.
|
||||||
|
|
||||||
|
@ -140,15 +140,19 @@ Error Handling
|
|||||||
See the dedicated section on :ref:`assert and require<assert-and-require>` for
|
See the dedicated section on :ref:`assert and require<assert-and-require>` for
|
||||||
more details on error handling and when to use which function.
|
more details on error handling and when to use which function.
|
||||||
|
|
||||||
``assert(bool condition)``:
|
``assert(bool condition)``
|
||||||
causes an invalid opcode and thus state change reversion if the condition is not met - to be used for internal errors.
|
causes an invalid opcode and thus state change reversion if the condition is not met - to be used for internal errors.
|
||||||
``require(bool condition)``:
|
|
||||||
|
``require(bool condition)``
|
||||||
reverts if the condition is not met - to be used for errors in inputs or external components.
|
reverts if the condition is not met - to be used for errors in inputs or external components.
|
||||||
``require(bool condition, string memory message)``:
|
|
||||||
|
``require(bool condition, string memory message)``
|
||||||
reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.
|
reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.
|
||||||
``revert()``:
|
|
||||||
|
``revert()``
|
||||||
abort execution and revert state changes
|
abort execution and revert state changes
|
||||||
``revert(string memory reason)``:
|
|
||||||
|
``revert(string memory reason)``
|
||||||
abort execution and revert state changes, providing an explanatory string
|
abort execution and revert state changes, providing an explanatory string
|
||||||
|
|
||||||
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
|
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
|
||||||
@ -156,32 +160,32 @@ more details on error handling and when to use which function.
|
|||||||
Mathematical and Cryptographic Functions
|
Mathematical and Cryptographic Functions
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
``addmod(uint x, uint y, uint k) returns (uint)``:
|
``addmod(uint x, uint y, uint k) returns (uint)``
|
||||||
compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
||||||
|
|
||||||
``mulmod(uint x, uint y, uint k) returns (uint)``:
|
``mulmod(uint x, uint y, uint k) returns (uint)``
|
||||||
compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
|
||||||
|
|
||||||
``keccak256(bytes memory) returns (bytes32)``:
|
``keccak256(bytes memory) returns (bytes32)``
|
||||||
compute the Keccak-256 hash of the input
|
compute the Keccak-256 hash of the input
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0.
|
There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0.
|
||||||
|
|
||||||
``sha256(bytes memory) returns (bytes32)``:
|
``sha256(bytes memory) returns (bytes32)``
|
||||||
compute the SHA-256 hash of the input
|
compute the SHA-256 hash of the input
|
||||||
|
|
||||||
``ripemd160(bytes memory) returns (bytes20)``:
|
``ripemd160(bytes memory) returns (bytes20)``
|
||||||
compute RIPEMD-160 hash of the input
|
compute RIPEMD-160 hash of the input
|
||||||
|
|
||||||
``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``:
|
``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``
|
||||||
recover the address associated with the public key from elliptic curve signature or return zero on error.
|
recover the address associated with the public key from elliptic curve signature or return zero on error.
|
||||||
The function parameters correspond to ECDSA values of the signature:
|
The function parameters correspond to ECDSA values of the signature:
|
||||||
|
|
||||||
``r`` = first 32 bytes of signature
|
* ``r`` = first 32 bytes of signature
|
||||||
``s`` = second 32 bytes of signature
|
* ``s`` = second 32 bytes of signature
|
||||||
``v`` = final 1 byte of signature
|
* ``v`` = final 1 byte of signature
|
||||||
|
|
||||||
``ecrecover`` returns an ``address``, and not an ``address payable``. See :ref:`address payable<address>` for
|
``ecrecover`` returns an ``address``, and not an ``address payable``. See :ref:`address payable<address>` for
|
||||||
conversion, in case you need to transfer funds to the recovered address.
|
conversion, in case you need to transfer funds to the recovered address.
|
||||||
@ -209,17 +213,22 @@ Mathematical and Cryptographic Functions
|
|||||||
Members of Address Types
|
Members of Address Types
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
``<address>.balance`` (``uint256``):
|
``<address>.balance`` (``uint256``)
|
||||||
balance of the :ref:`address` in Wei
|
balance of the :ref:`address` in Wei
|
||||||
``<address payable>.transfer(uint256 amount)``:
|
|
||||||
|
``<address payable>.transfer(uint256 amount)``
|
||||||
send given amount of Wei to :ref:`address`, reverts on failure, forwards 2300 gas stipend, not adjustable
|
send given amount of Wei to :ref:`address`, reverts on failure, forwards 2300 gas stipend, not adjustable
|
||||||
``<address payable>.send(uint256 amount) returns (bool)``:
|
|
||||||
|
``<address payable>.send(uint256 amount) returns (bool)``
|
||||||
send given amount of Wei to :ref:`address`, returns ``false`` on failure, forwards 2300 gas stipend, not adjustable
|
send given amount of Wei to :ref:`address`, returns ``false`` on failure, forwards 2300 gas stipend, not adjustable
|
||||||
``<address>.call(bytes memory) returns (bool, bytes memory)``:
|
|
||||||
|
``<address>.call(bytes memory) returns (bool, bytes memory)``
|
||||||
issue low-level ``CALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
|
issue low-level ``CALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
|
||||||
``<address>.delegatecall(bytes memory) returns (bool, bytes memory)``:
|
|
||||||
|
``<address>.delegatecall(bytes memory) returns (bool, bytes memory)``
|
||||||
issue low-level ``DELEGATECALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
|
issue low-level ``DELEGATECALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
|
||||||
``<address>.staticcall(bytes memory) returns (bool, bytes memory)``:
|
|
||||||
|
``<address>.staticcall(bytes memory) returns (bool, bytes memory)``
|
||||||
issue low-level ``STATICCALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
|
issue low-level ``STATICCALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
|
||||||
|
|
||||||
For more information, see the section on :ref:`address`.
|
For more information, see the section on :ref:`address`.
|
||||||
@ -258,10 +267,10 @@ For more information, see the section on :ref:`address`.
|
|||||||
Contract Related
|
Contract Related
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
``this`` (current contract's type):
|
``this`` (current contract's type)
|
||||||
the current contract, explicitly convertible to :ref:`address`
|
the current contract, explicitly convertible to :ref:`address`
|
||||||
|
|
||||||
``selfdestruct(address payable recipient)``:
|
``selfdestruct(address payable recipient)``
|
||||||
Destroy the current contract, sending its funds to the given :ref:`address`
|
Destroy the current contract, sending its funds to the given :ref:`address`
|
||||||
and end execution.
|
and end execution.
|
||||||
Note that ``selfdestruct`` has some peculiarities inherited from the EVM:
|
Note that ``selfdestruct`` has some peculiarities inherited from the EVM:
|
||||||
@ -290,10 +299,10 @@ type ``X``. Currently, there is limited support for this feature, but
|
|||||||
it might be expanded in the future. The following properties are
|
it might be expanded in the future. The following properties are
|
||||||
available for a contract type ``C``:
|
available for a contract type ``C``:
|
||||||
|
|
||||||
``type(C).name``:
|
``type(C).name``
|
||||||
The name of the contract.
|
The name of the contract.
|
||||||
|
|
||||||
``type(C).creationCode``:
|
``type(C).creationCode``
|
||||||
Memory byte array that contains the creation bytecode of the contract.
|
Memory byte array that contains the creation bytecode of the contract.
|
||||||
This can be used in inline assembly to build custom creation routines,
|
This can be used in inline assembly to build custom creation routines,
|
||||||
especially by using the ``create2`` opcode.
|
especially by using the ``create2`` opcode.
|
||||||
@ -301,7 +310,7 @@ available for a contract type ``C``:
|
|||||||
derived contract. It causes the bytecode to be included in the bytecode
|
derived contract. It causes the bytecode to be included in the bytecode
|
||||||
of the call site and thus circular references like that are not possible.
|
of the call site and thus circular references like that are not possible.
|
||||||
|
|
||||||
``type(C).runtimeCode``:
|
``type(C).runtimeCode``
|
||||||
Memory byte array that contains the runtime bytecode of the contract.
|
Memory byte array that contains the runtime bytecode of the contract.
|
||||||
This is the code that is usually deployed by the constructor of ``C``.
|
This is the code that is usually deployed by the constructor of ``C``.
|
||||||
If ``C`` has a constructor that uses inline assembly, this might be
|
If ``C`` has a constructor that uses inline assembly, this might be
|
||||||
@ -310,3 +319,12 @@ available for a contract type ``C``:
|
|||||||
regular calls.
|
regular calls.
|
||||||
The same restrictions as with ``.creationCode`` also apply for this
|
The same restrictions as with ``.creationCode`` also apply for this
|
||||||
property.
|
property.
|
||||||
|
|
||||||
|
In addition to the properties above, the following properties are available
|
||||||
|
for an interface type ``I``:
|
||||||
|
|
||||||
|
``type(I).interfaceId``:
|
||||||
|
A ``bytes4`` value containing the `EIP-165 <https://eips.ethereum.org/EIPS/eip-165>`_
|
||||||
|
interface identifier of the given interface ``I``. This identifier is defined as the ``XOR`` of all
|
||||||
|
function selectors defined within the interface itself - excluding all inherited functions.
|
||||||
|
|
||||||
|
@ -106,7 +106,8 @@ Target options
|
|||||||
Below is a list of target EVM versions and the compiler-relevant changes introduced
|
Below is a list of target EVM versions and the compiler-relevant changes introduced
|
||||||
at each version. Backward compatibility is not guaranteed between each version.
|
at each version. Backward compatibility is not guaranteed between each version.
|
||||||
|
|
||||||
- ``homestead`` (oldest version)
|
- ``homestead``
|
||||||
|
- (oldest version)
|
||||||
- ``tangerineWhistle``
|
- ``tangerineWhistle``
|
||||||
- Gas cost for access to other accounts increased, relevant for gas estimation and the optimizer.
|
- Gas cost for access to other accounts increased, relevant for gas estimation and the optimizer.
|
||||||
- All gas sent by default for external calls, previously a certain amount had to be retained.
|
- All gas sent by default for external calls, previously a certain amount had to be retained.
|
||||||
@ -692,7 +693,7 @@ Review changes
|
|||||||
|
|
||||||
The command above applies all changes as shown below. Please review them carefully.
|
The command above applies all changes as shown below. Please review them carefully.
|
||||||
|
|
||||||
.. code-block:: none
|
.. code-block:: solidity
|
||||||
|
|
||||||
pragma solidity >=0.6.0 <0.7.0;
|
pragma solidity >=0.6.0 <0.7.0;
|
||||||
|
|
||||||
|
38
docs/yul.rst
38
docs/yul.rst
@ -70,7 +70,7 @@ The following example program is written in the EVM dialect and computes exponen
|
|||||||
It can be compiled using ``solc --strict-assembly``. The builtin functions
|
It can be compiled using ``solc --strict-assembly``. The builtin functions
|
||||||
``mul`` and ``div`` compute product and division, respectively.
|
``mul`` and ``div`` compute product and division, respectively.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
function power(base, exponent) -> result
|
function power(base, exponent) -> result
|
||||||
@ -91,7 +91,7 @@ It is also possible to implement the same function using a for-loop
|
|||||||
instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``.
|
instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``.
|
||||||
less-than comparison.
|
less-than comparison.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
function power(base, exponent) -> result
|
function power(base, exponent) -> result
|
||||||
@ -115,7 +115,7 @@ This will use the :ref:`Yul object notation <yul-object>` so that it is possible
|
|||||||
to code as data to deploy contracts. This Yul mode is available for the commandline compiler
|
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>`:
|
(use ``--strict-assembly``) and for the :ref:`standard-json interface <compiler-api>`:
|
||||||
|
|
||||||
::
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"language": "Yul",
|
"language": "Yul",
|
||||||
@ -180,14 +180,14 @@ bitwise ``and`` with the string "abc" is computed.
|
|||||||
The final value is assigned to a local variable called ``x``.
|
The final value is assigned to a local variable called ``x``.
|
||||||
Strings are stored left-aligned and cannot be longer than 32 bytes.
|
Strings are stored left-aligned and cannot be longer than 32 bytes.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
let x := and("abc", add(3, 2))
|
let x := and("abc", add(3, 2))
|
||||||
|
|
||||||
Unless it is the default type, the type of a literal
|
Unless it is the default type, the type of a literal
|
||||||
has to be specified after a colon:
|
has to be specified after a colon:
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
let x := and("abc":uint32, add(3:uint256, 2:uint256))
|
let x := and("abc":uint32, add(3:uint256, 2:uint256))
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ If the function returns a single value, it can be directly used
|
|||||||
inside an expression again. If it returns multiple values,
|
inside an expression again. If it returns multiple values,
|
||||||
they have to be assigned to local variables.
|
they have to be assigned to local variables.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
mstore(0x80, add(mload(0x80), 3))
|
mstore(0x80, add(mload(0x80), 3))
|
||||||
// Here, the user-defined function `f` returns
|
// Here, the user-defined function `f` returns
|
||||||
@ -242,7 +242,7 @@ Future dialects migh introduce specific types for such pointers.
|
|||||||
When a variable is referenced, its current value is copied.
|
When a variable is referenced, its current value is copied.
|
||||||
For the EVM, this translates to a ``DUP`` instruction.
|
For the EVM, this translates to a ``DUP`` instruction.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
let zero := 0
|
let zero := 0
|
||||||
@ -260,7 +260,7 @@ you denote that following a colon. You can also declare multiple
|
|||||||
variables in one statement when you assign from a function call
|
variables in one statement when you assign from a function call
|
||||||
that returns multiple values.
|
that returns multiple values.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
let zero:uint32 := 0:uint32
|
let zero:uint32 := 0:uint32
|
||||||
@ -283,7 +283,7 @@ values have to match.
|
|||||||
If you want to assign the values returned from a function that has
|
If you want to assign the values returned from a function that has
|
||||||
multiple return parameters, you have to provide multiple variables.
|
multiple return parameters, you have to provide multiple variables.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
let v := 0
|
let v := 0
|
||||||
// re-assign v
|
// re-assign v
|
||||||
@ -301,7 +301,7 @@ The if statement can be used for conditionally executing code.
|
|||||||
No "else" block can be defined. Consider using "switch" instead (see below) if
|
No "else" block can be defined. Consider using "switch" instead (see below) if
|
||||||
you need multiple alternatives.
|
you need multiple alternatives.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
if eq(value, 0) { revert(0, 0) }
|
if eq(value, 0) { revert(0, 0) }
|
||||||
|
|
||||||
@ -317,7 +317,7 @@ Contrary to other programming languages, for safety reasons, control flow does
|
|||||||
not continue from one case to the next. There can be a fallback or default
|
not continue from one case to the next. There can be a fallback or default
|
||||||
case called ``default`` which is taken if none of the literal constants matches.
|
case called ``default`` which is taken if none of the literal constants matches.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
let x := 0
|
let x := 0
|
||||||
@ -349,7 +349,7 @@ or skip to the post-part, respectively.
|
|||||||
|
|
||||||
The following example computes the sum of an area in memory.
|
The following example computes the sum of an area in memory.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
let x := 0
|
let x := 0
|
||||||
@ -361,7 +361,7 @@ The following example computes the sum of an area in memory.
|
|||||||
For loops can also be used as a replacement for while loops:
|
For loops can also be used as a replacement for while loops:
|
||||||
Simply leave the initialization and post-iteration parts empty.
|
Simply leave the initialization and post-iteration parts empty.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
let x := 0
|
let x := 0
|
||||||
@ -404,7 +404,7 @@ the current yul function.
|
|||||||
|
|
||||||
The following example implements the power function by square-and-multiply.
|
The following example implements the power function by square-and-multiply.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
{
|
{
|
||||||
function power(base, exponent) -> result {
|
function power(base, exponent) -> result {
|
||||||
@ -425,7 +425,7 @@ Specification of Yul
|
|||||||
This chapter describes Yul code formally. Yul code is usually placed inside Yul objects,
|
This chapter describes Yul code formally. Yul code is usually placed inside Yul objects,
|
||||||
which are explained in their own chapter.
|
which are explained in their own chapter.
|
||||||
|
|
||||||
Grammar::
|
.. code-block:: none
|
||||||
|
|
||||||
Block = '{' Statement* '}'
|
Block = '{' Statement* '}'
|
||||||
Statement =
|
Statement =
|
||||||
@ -588,7 +588,7 @@ For an identifier ``v``, let ``$v`` be the name of the identifier.
|
|||||||
|
|
||||||
We will use a destructuring notation for the AST nodes.
|
We will use a destructuring notation for the AST nodes.
|
||||||
|
|
||||||
.. code::
|
.. code-block:: none
|
||||||
|
|
||||||
E(G, L, <{St1, ..., Stn}>: Block) =
|
E(G, L, <{St1, ..., Stn}>: Block) =
|
||||||
let G1, L1, mode = E(G, L, St1, ..., Stn)
|
let G1, L1, mode = E(G, L, St1, ..., Stn)
|
||||||
@ -915,7 +915,7 @@ Hex strings can be used to specify data in hex encoding,
|
|||||||
regular strings in native encoding. For code,
|
regular strings in native encoding. For code,
|
||||||
``datacopy`` will access its assembled binary representation.
|
``datacopy`` will access its assembled binary representation.
|
||||||
|
|
||||||
Grammar::
|
.. code-block:: none
|
||||||
|
|
||||||
Object = 'object' StringLiteral '{' Code ( Object | Data )* '}'
|
Object = 'object' StringLiteral '{' Code ( Object | Data )* '}'
|
||||||
Code = 'code' Block
|
Code = 'code' Block
|
||||||
@ -927,7 +927,7 @@ Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the pr
|
|||||||
|
|
||||||
An example Yul Object is shown below:
|
An example Yul Object is shown below:
|
||||||
|
|
||||||
.. code::
|
.. code-block:: yul
|
||||||
|
|
||||||
// A contract consists of a single object with sub-objects representing
|
// A contract consists of a single object with sub-objects representing
|
||||||
// the code to be deployed or other contracts it can create.
|
// the code to be deployed or other contracts it can create.
|
||||||
@ -1010,7 +1010,7 @@ for more details about its internals.
|
|||||||
|
|
||||||
If you want to use Solidity in stand-alone Yul mode, you activate the optimizer using ``--optimize``:
|
If you want to use Solidity in stand-alone Yul mode, you activate the optimizer using ``--optimize``:
|
||||||
|
|
||||||
::
|
.. code-block:: sh
|
||||||
|
|
||||||
solc --strict-assembly --optimize
|
solc --strict-assembly --optimize
|
||||||
|
|
||||||
|
@ -22,15 +22,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libevmasm/ExpressionClasses.h>
|
#include <libevmasm/ExpressionClasses.h>
|
||||||
#include <utility>
|
|
||||||
#include <tuple>
|
|
||||||
#include <functional>
|
|
||||||
#include <boost/range/adaptor/reversed.hpp>
|
|
||||||
#include <boost/noncopyable.hpp>
|
|
||||||
#include <libevmasm/Assembly.h>
|
#include <libevmasm/Assembly.h>
|
||||||
#include <libevmasm/CommonSubexpressionEliminator.h>
|
#include <libevmasm/CommonSubexpressionEliminator.h>
|
||||||
#include <libevmasm/SimplificationRules.h>
|
#include <libevmasm/SimplificationRules.h>
|
||||||
|
|
||||||
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::evmasm;
|
using namespace solidity::evmasm;
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace solidity::evmasm
|
namespace solidity::evmasm
|
||||||
{
|
{
|
||||||
@ -119,7 +120,7 @@ public:
|
|||||||
struct GasConsumption
|
struct GasConsumption
|
||||||
{
|
{
|
||||||
GasConsumption(unsigned _value = 0, bool _infinite = false): value(_value), isInfinite(_infinite) {}
|
GasConsumption(unsigned _value = 0, bool _infinite = false): value(_value), isInfinite(_infinite) {}
|
||||||
GasConsumption(u256 _value, bool _infinite = false): value(_value), isInfinite(_infinite) {}
|
GasConsumption(u256 _value, bool _infinite = false): value(std::move(_value)), isInfinite(_infinite) {}
|
||||||
static GasConsumption infinite() { return GasConsumption(0, true); }
|
static GasConsumption infinite() { return GasConsumption(0, true); }
|
||||||
|
|
||||||
GasConsumption& operator+=(GasConsumption const& _other);
|
GasConsumption& operator+=(GasConsumption const& _other);
|
||||||
@ -133,8 +134,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Constructs a new gas meter given the current state.
|
/// Constructs a new gas meter given the current state.
|
||||||
GasMeter(std::shared_ptr<KnownState> const& _state, langutil::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0):
|
GasMeter(std::shared_ptr<KnownState> _state, langutil::EVMVersion _evmVersion, u256 _largestMemoryAccess = 0):
|
||||||
m_state(_state), m_evmVersion(_evmVersion), m_largestMemoryAccess(_largestMemoryAccess) {}
|
m_state(std::move(_state)), m_evmVersion(_evmVersion), m_largestMemoryAccess(std::move(_largestMemoryAccess)) {}
|
||||||
|
|
||||||
/// @returns an upper bound on the gas consumed by the given instruction and updates
|
/// @returns an upper bound on the gas consumed by the given instruction and updates
|
||||||
/// the state.
|
/// the state.
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
@ -83,7 +84,7 @@ public:
|
|||||||
|
|
||||||
explicit KnownState(
|
explicit KnownState(
|
||||||
std::shared_ptr<ExpressionClasses> _expressionClasses = std::make_shared<ExpressionClasses>()
|
std::shared_ptr<ExpressionClasses> _expressionClasses = std::make_shared<ExpressionClasses>()
|
||||||
): m_expressionClasses(_expressionClasses)
|
): m_expressionClasses(std::move(_expressionClasses))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,6 +232,28 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class Pattern>
|
||||||
|
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4_5(
|
||||||
|
Pattern,
|
||||||
|
Pattern,
|
||||||
|
Pattern,
|
||||||
|
Pattern X,
|
||||||
|
Pattern Y
|
||||||
|
)
|
||||||
|
{
|
||||||
|
using Builtins = typename Pattern::Builtins;
|
||||||
|
return std::vector<SimplificationRule<Pattern>>{
|
||||||
|
// idempotent operations
|
||||||
|
{Builtins::AND(Builtins::AND(X, Y), Y), [=]{ return Builtins::AND(X, Y); }, true},
|
||||||
|
{Builtins::AND(Y, Builtins::AND(X, Y)), [=]{ return Builtins::AND(X, Y); }, true},
|
||||||
|
{Builtins::AND(Builtins::AND(Y, X), Y), [=]{ return Builtins::AND(Y, X); }, true},
|
||||||
|
{Builtins::AND(Y, Builtins::AND(Y, X)), [=]{ return Builtins::AND(Y, X); }, true},
|
||||||
|
{Builtins::OR(Builtins::OR(X, Y), Y), [=]{ return Builtins::OR(X, Y); }, true},
|
||||||
|
{Builtins::OR(Y, Builtins::OR(X, Y)), [=]{ return Builtins::OR(X, Y); }, true},
|
||||||
|
{Builtins::OR(Builtins::OR(Y, X), Y), [=]{ return Builtins::OR(Y, X); }, true},
|
||||||
|
{Builtins::OR(Y, Builtins::OR(Y, X)), [=]{ return Builtins::OR(Y, X); }, true},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
template <class Pattern>
|
template <class Pattern>
|
||||||
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||||
@ -663,6 +685,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
|
|||||||
rules += simplificationRuleListPart2(A, B, C, W, X);
|
rules += simplificationRuleListPart2(A, B, C, W, X);
|
||||||
rules += simplificationRuleListPart3(A, B, C, W, X);
|
rules += simplificationRuleListPart3(A, B, C, W, X);
|
||||||
rules += simplificationRuleListPart4(A, B, C, W, X);
|
rules += simplificationRuleListPart4(A, B, C, W, X);
|
||||||
|
rules += simplificationRuleListPart4_5(A, B, C, W, X);
|
||||||
rules += simplificationRuleListPart5(A, B, C, W, X);
|
rules += simplificationRuleListPart5(A, B, C, W, X);
|
||||||
rules += simplificationRuleListPart6(A, B, C, W, X);
|
rules += simplificationRuleListPart6(A, B, C, W, X);
|
||||||
rules += simplificationRuleListPart7(A, B, C, W, X);
|
rules += simplificationRuleListPart7(A, B, C, W, X);
|
||||||
|
@ -55,6 +55,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace solidity::langutil
|
namespace solidity::langutil
|
||||||
{
|
{
|
||||||
@ -68,8 +69,8 @@ class CharStream
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CharStream() = default;
|
CharStream() = default;
|
||||||
explicit CharStream(std::string const& _source, std::string const& name):
|
explicit CharStream(std::string _source, std::string name):
|
||||||
m_source(_source), m_name(name) {}
|
m_source(std::move(_source)), m_name(std::move(name)) {}
|
||||||
|
|
||||||
int position() const { return m_position; }
|
int position() const { return m_position; }
|
||||||
bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); }
|
bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); }
|
||||||
|
@ -22,15 +22,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <libsolutil/Exceptions.h>
|
#include <libsolutil/Exceptions.h>
|
||||||
#include <libsolutil/Assertions.h>
|
#include <libsolutil/Assertions.h>
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
#include <liblangutil/SourceLocation.h>
|
#include <liblangutil/SourceLocation.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace solidity::langutil
|
namespace solidity::langutil
|
||||||
{
|
{
|
||||||
class Error;
|
class Error;
|
||||||
|
@ -23,7 +23,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <liblangutil/Token.h>
|
#include <liblangutil/Token.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace solidity::langutil
|
namespace solidity::langutil
|
||||||
@ -80,8 +82,8 @@ struct SemVerMatchExpression
|
|||||||
class SemVerMatchExpressionParser
|
class SemVerMatchExpressionParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SemVerMatchExpressionParser(std::vector<Token> const& _tokens, std::vector<std::string> const& _literals):
|
SemVerMatchExpressionParser(std::vector<Token> _tokens, std::vector<std::string> _literals):
|
||||||
m_tokens(_tokens), m_literals(_literals)
|
m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
|
||||||
{}
|
{}
|
||||||
SemVerMatchExpression parse();
|
SemVerMatchExpression parse();
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#include <liblangutil/SourceReferenceFormatterHuman.h>
|
#include <liblangutil/SourceReferenceFormatterHuman.h>
|
||||||
#include <liblangutil/Scanner.h>
|
#include <liblangutil/Scanner.h>
|
||||||
#include <liblangutil/Exceptions.h>
|
#include <liblangutil/Exceptions.h>
|
||||||
#include <cmath>
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -70,58 +69,66 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _
|
|||||||
if (_ref.sourceName.empty())
|
if (_ref.sourceName.empty())
|
||||||
return; // Nothing we can print here
|
return; // Nothing we can print here
|
||||||
|
|
||||||
int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1;
|
|
||||||
|
|
||||||
// line 0: source name
|
|
||||||
frameColored() << string(leftpad, ' ') << "--> ";
|
|
||||||
|
|
||||||
if (_ref.position.line < 0)
|
if (_ref.position.line < 0)
|
||||||
{
|
{
|
||||||
m_stream << _ref.sourceName << "\n";
|
frameColored() << "-->";
|
||||||
|
m_stream << ' ' << _ref.sourceName << '\n';
|
||||||
return; // No line available, nothing else to print
|
return; // No line available, nothing else to print
|
||||||
}
|
}
|
||||||
|
|
||||||
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n';
|
string line = std::to_string(_ref.position.line + 1); // one-based line number as string
|
||||||
|
string leftpad = string(line.size(), ' ');
|
||||||
|
|
||||||
|
// line 0: source name
|
||||||
|
m_stream << leftpad;
|
||||||
|
frameColored() << "-->";
|
||||||
|
m_stream << ' ' << _ref.sourceName << ':' << line << ':' << (_ref.position.column + 1) << ":\n";
|
||||||
|
|
||||||
if (!_ref.multiline)
|
if (!_ref.multiline)
|
||||||
{
|
{
|
||||||
int const locationLength = _ref.endColumn - _ref.startColumn;
|
int const locationLength = _ref.endColumn - _ref.startColumn;
|
||||||
|
|
||||||
// line 1:
|
// line 1:
|
||||||
m_stream << string(leftpad, ' ');
|
m_stream << leftpad << ' ';
|
||||||
frameColored() << " |" << '\n';
|
frameColored() << '|';
|
||||||
|
m_stream << '\n';
|
||||||
|
|
||||||
// line 2:
|
// line 2:
|
||||||
frameColored() << (_ref.position.line + 1) << " | ";
|
frameColored() << line << " |";
|
||||||
m_stream << _ref.text.substr(0, _ref.startColumn);
|
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
|
||||||
highlightColored() << _ref.text.substr(_ref.startColumn, locationLength);
|
highlightColored() << _ref.text.substr(_ref.startColumn, locationLength);
|
||||||
m_stream << _ref.text.substr(_ref.endColumn) << '\n';
|
m_stream << _ref.text.substr(_ref.endColumn) << '\n';
|
||||||
|
|
||||||
// line 3:
|
// line 3:
|
||||||
m_stream << string(leftpad, ' ');
|
m_stream << leftpad << ' ';
|
||||||
frameColored() << " | ";
|
frameColored() << '|';
|
||||||
|
m_stream << ' ';
|
||||||
for_each(
|
for_each(
|
||||||
_ref.text.cbegin(),
|
_ref.text.cbegin(),
|
||||||
_ref.text.cbegin() + _ref.startColumn,
|
_ref.text.cbegin() + _ref.startColumn,
|
||||||
[this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); }
|
[this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); }
|
||||||
);
|
);
|
||||||
diagColored() << string(locationLength, '^') << '\n';
|
diagColored() << string(locationLength, '^');
|
||||||
|
m_stream << '\n';
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// line 1:
|
// line 1:
|
||||||
m_stream << string(leftpad, ' ');
|
m_stream << leftpad << ' ';
|
||||||
frameColored() << " |" << '\n';
|
frameColored() << '|';
|
||||||
|
m_stream << '\n';
|
||||||
|
|
||||||
// line 2:
|
// line 2:
|
||||||
frameColored() << (_ref.position.line + 1) << " | ";
|
frameColored() << line << " |";
|
||||||
m_stream << _ref.text.substr(0, _ref.startColumn);
|
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
|
||||||
highlightColored() << _ref.text.substr(_ref.startColumn) << '\n';
|
highlightColored() << _ref.text.substr(_ref.startColumn) << '\n';
|
||||||
|
|
||||||
// line 3:
|
// line 3:
|
||||||
frameColored() << string(leftpad, ' ') << " | ";
|
m_stream << leftpad << ' ';
|
||||||
m_stream << string(_ref.startColumn, ' ');
|
frameColored() << '|';
|
||||||
diagColored() << "^ (Relevant source part starts here and spans across multiple lines).\n";
|
m_stream << ' ' << string(_ref.startColumn, ' ');
|
||||||
|
diagColored() << "^ (Relevant source part starts here and spans across multiple lines).";
|
||||||
|
m_stream << '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@ set(sources
|
|||||||
analysis/ControlFlowGraph.h
|
analysis/ControlFlowGraph.h
|
||||||
analysis/DeclarationContainer.cpp
|
analysis/DeclarationContainer.cpp
|
||||||
analysis/DeclarationContainer.h
|
analysis/DeclarationContainer.h
|
||||||
|
analysis/DeclarationTypeChecker.cpp
|
||||||
|
analysis/DeclarationTypeChecker.h
|
||||||
analysis/DocStringAnalyser.cpp
|
analysis/DocStringAnalyser.cpp
|
||||||
analysis/DocStringAnalyser.h
|
analysis/DocStringAnalyser.h
|
||||||
analysis/ImmutableValidator.cpp
|
analysis/ImmutableValidator.cpp
|
||||||
@ -73,6 +75,8 @@ set(sources
|
|||||||
codegen/LValue.h
|
codegen/LValue.h
|
||||||
codegen/MultiUseYulFunctionCollector.h
|
codegen/MultiUseYulFunctionCollector.h
|
||||||
codegen/MultiUseYulFunctionCollector.cpp
|
codegen/MultiUseYulFunctionCollector.cpp
|
||||||
|
codegen/ReturnInfo.h
|
||||||
|
codegen/ReturnInfo.cpp
|
||||||
codegen/YulUtilFunctions.h
|
codegen/YulUtilFunctions.h
|
||||||
codegen/YulUtilFunctions.cpp
|
codegen/YulUtilFunctions.cpp
|
||||||
codegen/ir/IRGenerator.cpp
|
codegen/ir/IRGenerator.cpp
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
|
|
||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace solidity::langutil
|
namespace solidity::langutil
|
||||||
{
|
{
|
||||||
class ErrorReporter;
|
class ErrorReporter;
|
||||||
@ -47,7 +49,7 @@ public:
|
|||||||
):
|
):
|
||||||
m_errorReporter(_errorReporter),
|
m_errorReporter(_errorReporter),
|
||||||
m_depth(_newDepth),
|
m_depth(_newDepth),
|
||||||
m_types(_types)
|
m_types(std::move(_types))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
|
|||||||
checkDuplicateEvents(_contract);
|
checkDuplicateEvents(_contract);
|
||||||
m_overrideChecker.check(_contract);
|
m_overrideChecker.check(_contract);
|
||||||
checkBaseConstructorArguments(_contract);
|
checkBaseConstructorArguments(_contract);
|
||||||
checkAbstractFunctions(_contract);
|
checkAbstractDefinitions(_contract);
|
||||||
checkExternalTypeClashes(_contract);
|
checkExternalTypeClashes(_contract);
|
||||||
checkHashCollisions(_contract);
|
checkHashCollisions(_contract);
|
||||||
checkLibraryRequirements(_contract);
|
checkLibraryRequirements(_contract);
|
||||||
@ -156,55 +156,43 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract)
|
void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
// Mapping from name to function definition (exactly one per argument type equality class) and
|
// Collects functions, static variable getters and modifiers. If they
|
||||||
// flag to indicate whether it is fully implemented.
|
// override (unimplemented) base class ones, they are replaced.
|
||||||
using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>;
|
set<OverrideProxy, OverrideProxy::CompareBySignature> proxies;
|
||||||
map<string, vector<FunTypeAndFlag>> functions;
|
|
||||||
|
|
||||||
auto registerFunction = [&](Declaration const& _declaration, FunctionTypePointer const& _type, bool _implemented)
|
auto registerProxy = [&proxies](OverrideProxy const& _overrideProxy)
|
||||||
{
|
{
|
||||||
auto& overloads = functions[_declaration.name()];
|
// Overwrite an existing proxy, if it exists.
|
||||||
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
|
if (!_overrideProxy.unimplemented())
|
||||||
{
|
proxies.erase(_overrideProxy);
|
||||||
return _type->hasEqualParameterTypes(*_funAndFlag.first);
|
|
||||||
});
|
proxies.insert(_overrideProxy);
|
||||||
if (it == overloads.end())
|
|
||||||
overloads.emplace_back(_type, _implemented);
|
|
||||||
else if (_implemented)
|
|
||||||
it->second = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Search from base to derived, collect all functions and update
|
// Search from base to derived, collect all functions and modifiers and
|
||||||
// the 'implemented' flag.
|
// update proxies.
|
||||||
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
|
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
|
||||||
{
|
{
|
||||||
for (VariableDeclaration const* v: contract->stateVariables())
|
for (VariableDeclaration const* v: contract->stateVariables())
|
||||||
if (v->isPartOfExternalInterface())
|
if (v->isPartOfExternalInterface())
|
||||||
registerFunction(*v, TypeProvider::function(*v), true);
|
registerProxy(OverrideProxy(v));
|
||||||
|
|
||||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||||
if (!function->isConstructor())
|
if (!function->isConstructor())
|
||||||
registerFunction(
|
registerProxy(OverrideProxy(function));
|
||||||
*function,
|
|
||||||
TypeProvider::function(*function)->asCallableFunction(false),
|
for (ModifierDefinition const* modifier: contract->functionModifiers())
|
||||||
function->isImplemented()
|
registerProxy(OverrideProxy(modifier));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set to not fully implemented if at least one flag is false.
|
// Set to not fully implemented if at least one flag is false.
|
||||||
// Note that `_contract.annotation().unimplementedFunctions` has already been
|
// Note that `_contract.annotation().unimplementedDeclarations` has already been
|
||||||
// pre-filled by `checkBaseConstructorArguments`.
|
// pre-filled by `checkBaseConstructorArguments`.
|
||||||
for (auto const& it: functions)
|
for (auto const& proxy: proxies)
|
||||||
for (auto const& funAndFlag: it.second)
|
if (proxy.unimplemented())
|
||||||
if (!funAndFlag.second)
|
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
|
||||||
{
|
|
||||||
FunctionDefinition const* function = dynamic_cast<FunctionDefinition const*>(&funAndFlag.first->declaration());
|
|
||||||
solAssert(function, "");
|
|
||||||
_contract.annotation().unimplementedFunctions.push_back(function);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_contract.abstract())
|
if (_contract.abstract())
|
||||||
{
|
{
|
||||||
@ -221,15 +209,16 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
|
|||||||
if (
|
if (
|
||||||
_contract.contractKind() == ContractKind::Contract &&
|
_contract.contractKind() == ContractKind::Contract &&
|
||||||
!_contract.abstract() &&
|
!_contract.abstract() &&
|
||||||
!_contract.annotation().unimplementedFunctions.empty()
|
!_contract.annotation().unimplementedDeclarations.empty()
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
SecondarySourceLocation ssl;
|
SecondarySourceLocation ssl;
|
||||||
for (auto function: _contract.annotation().unimplementedFunctions)
|
for (auto declaration: _contract.annotation().unimplementedDeclarations)
|
||||||
ssl.append("Missing implementation:", function->location());
|
ssl.append("Missing implementation: ", declaration->location());
|
||||||
m_errorReporter.typeError(_contract.location(), ssl,
|
m_errorReporter.typeError(_contract.location(), ssl,
|
||||||
"Contract \"" + _contract.annotation().canonicalName
|
"Contract \"" + _contract.annotation().canonicalName
|
||||||
+ "\" should be marked as abstract.");
|
+ "\" should be marked as abstract.");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,7 +266,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
|
|||||||
if (FunctionDefinition const* constructor = contract->constructor())
|
if (FunctionDefinition const* constructor = contract->constructor())
|
||||||
if (contract != &_contract && !constructor->parameters().empty())
|
if (contract != &_contract && !constructor->parameters().empty())
|
||||||
if (!_contract.annotation().baseConstructorArguments.count(constructor))
|
if (!_contract.annotation().baseConstructorArguments.count(constructor))
|
||||||
_contract.annotation().unimplementedFunctions.push_back(constructor);
|
_contract.annotation().unimplementedDeclarations.push_back(constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContractLevelChecker::annotateBaseConstructorArguments(
|
void ContractLevelChecker::annotateBaseConstructorArguments(
|
||||||
|
@ -61,7 +61,8 @@ private:
|
|||||||
void checkDuplicateEvents(ContractDefinition const& _contract);
|
void checkDuplicateEvents(ContractDefinition const& _contract);
|
||||||
template <class T>
|
template <class T>
|
||||||
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
|
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
|
||||||
void checkAbstractFunctions(ContractDefinition const& _contract);
|
/// Checks for unimplemented functions and modifiers.
|
||||||
|
void checkAbstractDefinitions(ContractDefinition const& _contract);
|
||||||
/// Checks that the base constructor arguments are properly provided.
|
/// Checks that the base constructor arguments are properly provided.
|
||||||
/// Fills the list of unimplemented functions in _contract's annotations.
|
/// Fills the list of unimplemented functions in _contract's annotations.
|
||||||
void checkBaseConstructorArguments(ContractDefinition const& _contract);
|
void checkBaseConstructorArguments(ContractDefinition const& _contract);
|
||||||
|
@ -591,7 +591,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
|
|||||||
if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
|
if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*variableDeclaration,
|
*variableDeclaration,
|
||||||
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
static_cast<Expression const&>(_identifier).annotation().willBeWrittenTo ?
|
||||||
VariableOccurrence::Kind::Assignment :
|
VariableOccurrence::Kind::Assignment :
|
||||||
VariableOccurrence::Kind::Access,
|
VariableOccurrence::Kind::Access,
|
||||||
_identifier.location()
|
_identifier.location()
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
@ -48,8 +49,8 @@ public:
|
|||||||
Assignment,
|
Assignment,
|
||||||
InlineAssembly
|
InlineAssembly
|
||||||
};
|
};
|
||||||
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
|
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> _occurrence = {}):
|
||||||
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(std::move(_occurrence))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
382
libsolidity/analysis/DeclarationTypeChecker.cpp
Normal file
382
libsolidity/analysis/DeclarationTypeChecker.cpp
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
/*
|
||||||
|
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/DeclarationTypeChecker.h>
|
||||||
|
|
||||||
|
#include <libsolidity/analysis/ConstantEvaluator.h>
|
||||||
|
|
||||||
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
|
#include <liblangutil/ErrorReporter.h>
|
||||||
|
|
||||||
|
#include <libsolutil/Algorithms.h>
|
||||||
|
|
||||||
|
#include <boost/range/adaptor/transformed.hpp>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace solidity::langutil;
|
||||||
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
|
bool DeclarationTypeChecker::visit(ElementaryTypeName const& _typeName)
|
||||||
|
{
|
||||||
|
if (_typeName.annotation().type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName());
|
||||||
|
if (_typeName.stateMutability().has_value())
|
||||||
|
{
|
||||||
|
// for non-address types this was already caught by the parser
|
||||||
|
solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
|
||||||
|
switch (*_typeName.stateMutability())
|
||||||
|
{
|
||||||
|
case StateMutability::Payable:
|
||||||
|
_typeName.annotation().type = TypeProvider::payableAddress();
|
||||||
|
break;
|
||||||
|
case StateMutability::NonPayable:
|
||||||
|
_typeName.annotation().type = TypeProvider::address();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
typeError(
|
||||||
|
_typeName.location(),
|
||||||
|
"Address types can only be payable or non-payable."
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
|
||||||
|
{
|
||||||
|
if (_struct.annotation().recursive.has_value())
|
||||||
|
{
|
||||||
|
if (!m_currentStructsSeen.empty() && *_struct.annotation().recursive)
|
||||||
|
m_recursiveStructSeen = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_currentStructsSeen.count(&_struct))
|
||||||
|
{
|
||||||
|
_struct.annotation().recursive = true;
|
||||||
|
m_recursiveStructSeen = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool previousRecursiveStructSeen = m_recursiveStructSeen;
|
||||||
|
bool hasRecursiveChild = false;
|
||||||
|
|
||||||
|
m_currentStructsSeen.insert(&_struct);
|
||||||
|
|
||||||
|
for (auto const& member: _struct.members())
|
||||||
|
{
|
||||||
|
m_recursiveStructSeen = false;
|
||||||
|
member->accept(*this);
|
||||||
|
solAssert(member->annotation().type, "");
|
||||||
|
solAssert(member->annotation().type->canBeStored(), "Type cannot be used in struct.");
|
||||||
|
if (m_recursiveStructSeen)
|
||||||
|
hasRecursiveChild = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_struct.annotation().recursive.has_value())
|
||||||
|
_struct.annotation().recursive = hasRecursiveChild;
|
||||||
|
m_recursiveStructSeen = previousRecursiveStructSeen || *_struct.annotation().recursive;
|
||||||
|
m_currentStructsSeen.erase(&_struct);
|
||||||
|
if (m_currentStructsSeen.empty())
|
||||||
|
m_recursiveStructSeen = false;
|
||||||
|
|
||||||
|
// Check direct recursion, fatal error if detected.
|
||||||
|
auto visitor = [&](StructDefinition const& _struct, auto& _cycleDetector, size_t _depth)
|
||||||
|
{
|
||||||
|
if (_depth >= 256)
|
||||||
|
fatalDeclarationError(_struct.location(), "Struct definition exhausts cyclic dependency validator.");
|
||||||
|
|
||||||
|
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
||||||
|
{
|
||||||
|
Type const* memberType = member->annotation().type;
|
||||||
|
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
|
||||||
|
{
|
||||||
|
if (arrayType->isDynamicallySized())
|
||||||
|
break;
|
||||||
|
memberType = arrayType->baseType();
|
||||||
|
}
|
||||||
|
if (auto structType = dynamic_cast<StructType const*>(memberType))
|
||||||
|
if (_cycleDetector.run(structType->structDefinition()))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (util::CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
|
||||||
|
fatalTypeError(_struct.location(), "Recursive struct definition.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
|
||||||
|
{
|
||||||
|
if (_typeName.annotation().type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Declaration const* declaration = _typeName.annotation().referencedDeclaration;
|
||||||
|
solAssert(declaration, "");
|
||||||
|
|
||||||
|
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
|
||||||
|
{
|
||||||
|
if (!m_insideFunctionType && !m_currentStructsSeen.empty())
|
||||||
|
structDef->accept(*this);
|
||||||
|
_typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage);
|
||||||
|
}
|
||||||
|
else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
|
||||||
|
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
|
||||||
|
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||||
|
_typeName.annotation().type = TypeProvider::contract(*contract);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_typeName.annotation().type = TypeProvider::emptyTuple();
|
||||||
|
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool DeclarationTypeChecker::visit(FunctionTypeName const& _typeName)
|
||||||
|
{
|
||||||
|
if (_typeName.annotation().type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool previousInsideFunctionType = m_insideFunctionType;
|
||||||
|
m_insideFunctionType = true;
|
||||||
|
_typeName.parameterTypeList()->accept(*this);
|
||||||
|
_typeName.returnParameterTypeList()->accept(*this);
|
||||||
|
m_insideFunctionType = previousInsideFunctionType;
|
||||||
|
|
||||||
|
switch (_typeName.visibility())
|
||||||
|
{
|
||||||
|
case Visibility::Internal:
|
||||||
|
case Visibility::External:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
|
||||||
|
{
|
||||||
|
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_typeName.annotation().type = TypeProvider::function(_typeName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
|
||||||
|
{
|
||||||
|
if (_mapping.annotation().type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
|
||||||
|
{
|
||||||
|
if (auto const* contractType = dynamic_cast<ContractType const*>(typeName->annotation().type))
|
||||||
|
{
|
||||||
|
if (contractType->contractDefinition().isLibrary())
|
||||||
|
m_errorReporter.fatalTypeError(
|
||||||
|
typeName->location(),
|
||||||
|
"Library types cannot be used as mapping keys."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (typeName->annotation().type->category() != Type::Category::Enum)
|
||||||
|
m_errorReporter.fatalTypeError(
|
||||||
|
typeName->location(),
|
||||||
|
"Only elementary types, contract types or enums are allowed as mapping keys."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
|
||||||
|
|
||||||
|
TypePointer keyType = _mapping.keyType().annotation().type;
|
||||||
|
TypePointer valueType = _mapping.valueType().annotation().type;
|
||||||
|
|
||||||
|
// Convert key type to memory.
|
||||||
|
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
||||||
|
|
||||||
|
// Convert value type to storage reference.
|
||||||
|
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
|
||||||
|
_mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
|
||||||
|
{
|
||||||
|
if (_typeName.annotation().type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TypePointer baseType = _typeName.baseType().annotation().type;
|
||||||
|
if (!baseType)
|
||||||
|
{
|
||||||
|
solAssert(!m_errorReporter.errors().empty(), "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (baseType->storageBytes() == 0)
|
||||||
|
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
|
||||||
|
if (Expression const* length = _typeName.length())
|
||||||
|
{
|
||||||
|
TypePointer& lengthTypeGeneric = length->annotation().type;
|
||||||
|
if (!lengthTypeGeneric)
|
||||||
|
lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
|
||||||
|
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
|
||||||
|
u256 lengthValue = 0;
|
||||||
|
if (!lengthType || !lengthType->mobileType())
|
||||||
|
typeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
|
||||||
|
else if (lengthType->isZero())
|
||||||
|
typeError(length->location(), "Array with zero length specified.");
|
||||||
|
else if (lengthType->isFractional())
|
||||||
|
typeError(length->location(), "Array with fractional length specified.");
|
||||||
|
else if (lengthType->isNegative())
|
||||||
|
typeError(length->location(), "Array with negative length specified.");
|
||||||
|
else
|
||||||
|
lengthValue = lengthType->literalValue(nullptr);
|
||||||
|
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
|
||||||
|
}
|
||||||
|
void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
|
||||||
|
{
|
||||||
|
if (_variable.annotation().type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
// This can still happen in very unusual cases where a developer uses constructs, such as
|
||||||
|
// `var a;`, however, such code will have generated errors already.
|
||||||
|
// However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
|
||||||
|
// invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
|
||||||
|
// after this step.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
using Location = VariableDeclaration::Location;
|
||||||
|
Location varLoc = _variable.referenceLocation();
|
||||||
|
DataLocation typeLoc = DataLocation::Memory;
|
||||||
|
|
||||||
|
set<Location> allowedDataLocations = _variable.allowedDataLocations();
|
||||||
|
if (!allowedDataLocations.count(varLoc))
|
||||||
|
{
|
||||||
|
auto locationToString = [](VariableDeclaration::Location _location) -> string
|
||||||
|
{
|
||||||
|
switch (_location)
|
||||||
|
{
|
||||||
|
case Location::Memory: return "\"memory\"";
|
||||||
|
case Location::Storage: return "\"storage\"";
|
||||||
|
case Location::CallData: return "\"calldata\"";
|
||||||
|
case Location::Unspecified: return "none";
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
string errorString;
|
||||||
|
if (!_variable.hasReferenceOrMappingType())
|
||||||
|
errorString = "Data location can only be specified for array, struct or mapping types";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorString = "Data location must be " +
|
||||||
|
util::joinHumanReadable(
|
||||||
|
allowedDataLocations | boost::adaptors::transformed(locationToString),
|
||||||
|
", ",
|
||||||
|
" or "
|
||||||
|
);
|
||||||
|
if (_variable.isCallableOrCatchParameter())
|
||||||
|
errorString +=
|
||||||
|
" for " +
|
||||||
|
string(_variable.isReturnParameter() ? "return " : "") +
|
||||||
|
"parameter in" +
|
||||||
|
string(_variable.isExternalCallableParameter() ? " external" : "") +
|
||||||
|
" function";
|
||||||
|
else
|
||||||
|
errorString += " for variable";
|
||||||
|
}
|
||||||
|
errorString += ", but " + locationToString(varLoc) + " was given.";
|
||||||
|
typeError(_variable.location(), errorString);
|
||||||
|
|
||||||
|
solAssert(!allowedDataLocations.empty(), "");
|
||||||
|
varLoc = *allowedDataLocations.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find correct data location.
|
||||||
|
if (_variable.isEventParameter())
|
||||||
|
{
|
||||||
|
solAssert(varLoc == Location::Unspecified, "");
|
||||||
|
typeLoc = DataLocation::Memory;
|
||||||
|
}
|
||||||
|
else if (_variable.isStateVariable())
|
||||||
|
{
|
||||||
|
solAssert(varLoc == Location::Unspecified, "");
|
||||||
|
typeLoc = (_variable.isConstant() || _variable.immutable()) ? DataLocation::Memory : DataLocation::Storage;
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
dynamic_cast<StructDefinition const*>(_variable.scope()) ||
|
||||||
|
dynamic_cast<EnumDefinition const*>(_variable.scope())
|
||||||
|
)
|
||||||
|
// The actual location will later be changed depending on how the type is used.
|
||||||
|
typeLoc = DataLocation::Storage;
|
||||||
|
else
|
||||||
|
switch (varLoc)
|
||||||
|
{
|
||||||
|
case Location::Memory:
|
||||||
|
typeLoc = DataLocation::Memory;
|
||||||
|
break;
|
||||||
|
case Location::Storage:
|
||||||
|
typeLoc = DataLocation::Storage;
|
||||||
|
break;
|
||||||
|
case Location::CallData:
|
||||||
|
typeLoc = DataLocation::CallData;
|
||||||
|
break;
|
||||||
|
case Location::Unspecified:
|
||||||
|
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePointer type = _variable.typeName()->annotation().type;
|
||||||
|
if (auto ref = dynamic_cast<ReferenceType const*>(type))
|
||||||
|
{
|
||||||
|
bool isPointer = !_variable.isStateVariable();
|
||||||
|
type = TypeProvider::withLocation(ref, typeLoc, isPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_variable.annotation().type = type;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeclarationTypeChecker::typeError(SourceLocation const& _location, string const& _description)
|
||||||
|
{
|
||||||
|
m_errorOccurred = true;
|
||||||
|
m_errorReporter.typeError(_location, _description);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeclarationTypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description)
|
||||||
|
{
|
||||||
|
m_errorOccurred = true;
|
||||||
|
m_errorReporter.fatalTypeError(_location, _description);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeclarationTypeChecker::fatalDeclarationError(SourceLocation const& _location, string const& _description)
|
||||||
|
{
|
||||||
|
m_errorOccurred = true;
|
||||||
|
m_errorReporter.fatalDeclarationError(_location, _description);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeclarationTypeChecker::check(ASTNode const& _node)
|
||||||
|
{
|
||||||
|
_node.accept(*this);
|
||||||
|
return !m_errorOccurred;
|
||||||
|
}
|
79
libsolidity/analysis/DeclarationTypeChecker.h
Normal file
79
libsolidity/analysis/DeclarationTypeChecker.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
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 <libsolidity/ast/ASTAnnotations.h>
|
||||||
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace solidity::langutil
|
||||||
|
{
|
||||||
|
class ErrorReporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace solidity::frontend
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns types to declarations.
|
||||||
|
*/
|
||||||
|
class DeclarationTypeChecker: private ASTConstVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DeclarationTypeChecker(
|
||||||
|
langutil::ErrorReporter& _errorReporter,
|
||||||
|
langutil::EVMVersion _evmVersion
|
||||||
|
):
|
||||||
|
m_errorReporter(_errorReporter),
|
||||||
|
m_evmVersion(_evmVersion)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool check(ASTNode const& _contract);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool visit(ElementaryTypeName const& _typeName) override;
|
||||||
|
void endVisit(UserDefinedTypeName const& _typeName) override;
|
||||||
|
bool visit(FunctionTypeName const& _typeName) override;
|
||||||
|
void endVisit(Mapping const& _mapping) override;
|
||||||
|
void endVisit(ArrayTypeName const& _typeName) override;
|
||||||
|
void endVisit(VariableDeclaration const& _variable) override;
|
||||||
|
bool visit(StructDefinition const& _struct) override;
|
||||||
|
|
||||||
|
/// Adds a new error to the list of errors.
|
||||||
|
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||||
|
|
||||||
|
/// Adds a new error to the list of errors and throws to abort reference resolving.
|
||||||
|
void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||||
|
|
||||||
|
/// Adds a new error to the list of errors and throws to abort reference resolving.
|
||||||
|
void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||||
|
|
||||||
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
bool m_errorOccurred = false;
|
||||||
|
langutil::EVMVersion m_evmVersion;
|
||||||
|
bool m_insideFunctionType = false;
|
||||||
|
bool m_recursiveStructSeen = false;
|
||||||
|
std::set<StructDefinition const*> m_currentStructsSeen;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -148,7 +148,8 @@ bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDec
|
|||||||
funcDef->body().accept(*this);
|
funcDef->body().accept(*this);
|
||||||
}
|
}
|
||||||
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
|
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
|
||||||
modDef->body().accept(*this);
|
if (modDef->isImplemented())
|
||||||
|
modDef->body().accept(*this);
|
||||||
|
|
||||||
m_currentConstructor = prevConstructor;
|
m_currentConstructor = prevConstructor;
|
||||||
|
|
||||||
@ -160,7 +161,7 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
|
|||||||
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
|
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_expression.annotation().lValueRequested && _expression.annotation().lValueOfOrdinaryAssignment)
|
if (_expression.annotation().willBeWrittenTo && _expression.annotation().lValueOfOrdinaryAssignment)
|
||||||
{
|
{
|
||||||
if (!m_currentConstructor)
|
if (!m_currentConstructor)
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
|
@ -195,51 +195,6 @@ Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> c
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
|
|
||||||
Identifier const& _identifier,
|
|
||||||
vector<Declaration const*> const& _declarations
|
|
||||||
)
|
|
||||||
{
|
|
||||||
solAssert(_declarations.size() > 1, "");
|
|
||||||
vector<Declaration const*> uniqueFunctions;
|
|
||||||
|
|
||||||
for (Declaration const* declaration: _declarations)
|
|
||||||
{
|
|
||||||
solAssert(declaration, "");
|
|
||||||
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
|
|
||||||
solAssert(
|
|
||||||
dynamic_cast<FunctionDefinition const*>(declaration) ||
|
|
||||||
dynamic_cast<EventDefinition const*>(declaration) ||
|
|
||||||
dynamic_cast<VariableDeclaration const*>(declaration) ||
|
|
||||||
dynamic_cast<MagicVariableDeclaration const*>(declaration),
|
|
||||||
"Found overloading involving something not a function, event or a (magic) variable."
|
|
||||||
);
|
|
||||||
|
|
||||||
FunctionTypePointer functionType { declaration->functionType(false) };
|
|
||||||
if (!functionType)
|
|
||||||
functionType = declaration->functionType(true);
|
|
||||||
solAssert(functionType, "Failed to determine the function type of the overloaded.");
|
|
||||||
|
|
||||||
for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
|
|
||||||
if (!parameter)
|
|
||||||
m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
|
|
||||||
|
|
||||||
if (uniqueFunctions.end() == find_if(
|
|
||||||
uniqueFunctions.begin(),
|
|
||||||
uniqueFunctions.end(),
|
|
||||||
[&](Declaration const* d)
|
|
||||||
{
|
|
||||||
FunctionType const* newFunctionType = d->functionType(false);
|
|
||||||
if (!newFunctionType)
|
|
||||||
newFunctionType = d->functionType(true);
|
|
||||||
return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
|
|
||||||
}
|
|
||||||
))
|
|
||||||
uniqueFunctions.push_back(declaration);
|
|
||||||
}
|
|
||||||
return uniqueFunctions;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
|
void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
|
||||||
{
|
{
|
||||||
for (auto const& instruction: evmasm::c_instructions)
|
for (auto const& instruction: evmasm::c_instructions)
|
||||||
|
@ -95,12 +95,6 @@ public:
|
|||||||
/// @note Returns a null pointer if any component in the path was not unique or not found.
|
/// @note Returns a null pointer if any component in the path was not unique or not found.
|
||||||
Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path) const;
|
Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path) const;
|
||||||
|
|
||||||
/// returns the vector of declarations without repetitions
|
|
||||||
std::vector<Declaration const*> cleanedDeclarations(
|
|
||||||
Identifier const& _identifier,
|
|
||||||
std::vector<Declaration const*> const& _declarations
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Generate and store warnings about variables that are named like instructions.
|
/// Generate and store warnings about variables that are named like instructions.
|
||||||
void warnVariablesNamedLikeInstructions();
|
void warnVariablesNamedLikeInstructions();
|
||||||
|
|
||||||
|
@ -327,6 +327,16 @@ ModifierType const* OverrideProxy::modifierType() const
|
|||||||
}, m_item);
|
}, m_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Declaration const* OverrideProxy::declaration() const
|
||||||
|
{
|
||||||
|
return std::visit(GenericVisitor{
|
||||||
|
[&](FunctionDefinition const* _function) -> Declaration const* { return _function; },
|
||||||
|
[&](VariableDeclaration const* _variable) -> Declaration const* { return _variable; },
|
||||||
|
[&](ModifierDefinition const* _modifier) -> Declaration const* { return _modifier; }
|
||||||
|
}, m_item);
|
||||||
|
}
|
||||||
|
|
||||||
SourceLocation const& OverrideProxy::location() const
|
SourceLocation const& OverrideProxy::location() const
|
||||||
{
|
{
|
||||||
return std::visit(GenericVisitor{
|
return std::visit(GenericVisitor{
|
||||||
@ -365,7 +375,7 @@ bool OverrideProxy::unimplemented() const
|
|||||||
{
|
{
|
||||||
return std::visit(GenericVisitor{
|
return std::visit(GenericVisitor{
|
||||||
[&](FunctionDefinition const* _item) { return !_item->isImplemented(); },
|
[&](FunctionDefinition const* _item) { return !_item->isImplemented(); },
|
||||||
[&](ModifierDefinition const*) { return false; },
|
[&](ModifierDefinition const* _item) { return !_item->isImplemented(); },
|
||||||
[&](VariableDeclaration const*) { return false; }
|
[&](VariableDeclaration const*) { return false; }
|
||||||
}, m_item);
|
}, m_item);
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,8 @@ public:
|
|||||||
FunctionType const* functionType() const;
|
FunctionType const* functionType() const;
|
||||||
ModifierType const* modifierType() const;
|
ModifierType const* modifierType() const;
|
||||||
|
|
||||||
|
Declaration const* declaration() const;
|
||||||
|
|
||||||
langutil::SourceLocation const& location() const;
|
langutil::SourceLocation const& location() const;
|
||||||
|
|
||||||
std::string astNodeName() const;
|
std::string astNodeName() const;
|
||||||
|
@ -22,9 +22,7 @@
|
|||||||
|
|
||||||
#include <libsolidity/analysis/ReferencesResolver.h>
|
#include <libsolidity/analysis/ReferencesResolver.h>
|
||||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||||
#include <libsolidity/analysis/ConstantEvaluator.h>
|
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
|
||||||
|
|
||||||
#include <libyul/AsmAnalysis.h>
|
#include <libyul/AsmAnalysis.h>
|
||||||
#include <libyul/AsmAnalysisInfo.h>
|
#include <libyul/AsmAnalysisInfo.h>
|
||||||
@ -37,7 +35,6 @@
|
|||||||
#include <libsolutil/StringUtils.h>
|
#include <libsolutil/StringUtils.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/range/adaptor/transformed.hpp>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
@ -126,40 +123,10 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
|
|||||||
else if (declarations.size() == 1)
|
else if (declarations.size() == 1)
|
||||||
_identifier.annotation().referencedDeclaration = declarations.front();
|
_identifier.annotation().referencedDeclaration = declarations.front();
|
||||||
else
|
else
|
||||||
_identifier.annotation().overloadedDeclarations =
|
_identifier.annotation().candidateDeclarations = declarations;
|
||||||
m_resolver.cleanedDeclarations(_identifier, declarations);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
|
|
||||||
{
|
|
||||||
if (!_typeName.annotation().type)
|
|
||||||
{
|
|
||||||
_typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName());
|
|
||||||
if (_typeName.stateMutability().has_value())
|
|
||||||
{
|
|
||||||
// for non-address types this was already caught by the parser
|
|
||||||
solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
|
|
||||||
switch (*_typeName.stateMutability())
|
|
||||||
{
|
|
||||||
case StateMutability::Payable:
|
|
||||||
_typeName.annotation().type = TypeProvider::payableAddress();
|
|
||||||
break;
|
|
||||||
case StateMutability::NonPayable:
|
|
||||||
_typeName.annotation().type = TypeProvider::address();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_errorReporter.typeError(
|
|
||||||
_typeName.location(),
|
|
||||||
"Address types can only be payable or non-payable."
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
|
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
|
||||||
{
|
{
|
||||||
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
||||||
@ -194,113 +161,6 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_typeName.annotation().referencedDeclaration = declaration;
|
_typeName.annotation().referencedDeclaration = declaration;
|
||||||
|
|
||||||
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
|
|
||||||
_typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage);
|
|
||||||
else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
|
|
||||||
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
|
|
||||||
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
|
|
||||||
_typeName.annotation().type = TypeProvider::contract(*contract);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_typeName.annotation().type = TypeProvider::emptyTuple();
|
|
||||||
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
|
|
||||||
{
|
|
||||||
switch (_typeName.visibility())
|
|
||||||
{
|
|
||||||
case Visibility::Internal:
|
|
||||||
case Visibility::External:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
|
|
||||||
{
|
|
||||||
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_typeName.visibility() == Visibility::External)
|
|
||||||
for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes())
|
|
||||||
{
|
|
||||||
solAssert(t->annotation().type, "Type not set for parameter.");
|
|
||||||
if (!t->annotation().type->interfaceType(false).get())
|
|
||||||
{
|
|
||||||
fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_typeName.annotation().type = TypeProvider::function(_typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReferencesResolver::endVisit(Mapping const& _mapping)
|
|
||||||
{
|
|
||||||
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
|
|
||||||
{
|
|
||||||
if (auto const* contractType = dynamic_cast<ContractType const*>(typeName->annotation().type))
|
|
||||||
{
|
|
||||||
if (contractType->contractDefinition().isLibrary())
|
|
||||||
m_errorReporter.fatalTypeError(
|
|
||||||
typeName->location(),
|
|
||||||
"Library types cannot be used as mapping keys."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (typeName->annotation().type->category() != Type::Category::Enum)
|
|
||||||
m_errorReporter.fatalTypeError(
|
|
||||||
typeName->location(),
|
|
||||||
"Only elementary types, contract types or enums are allowed as mapping keys."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
|
|
||||||
|
|
||||||
TypePointer keyType = _mapping.keyType().annotation().type;
|
|
||||||
TypePointer valueType = _mapping.valueType().annotation().type;
|
|
||||||
|
|
||||||
// Convert key type to memory.
|
|
||||||
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
|
||||||
|
|
||||||
// Convert value type to storage reference.
|
|
||||||
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
|
|
||||||
_mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
|
|
||||||
{
|
|
||||||
TypePointer baseType = _typeName.baseType().annotation().type;
|
|
||||||
if (!baseType)
|
|
||||||
{
|
|
||||||
solAssert(!m_errorReporter.errors().empty(), "");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (baseType->storageBytes() == 0)
|
|
||||||
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
|
|
||||||
if (Expression const* length = _typeName.length())
|
|
||||||
{
|
|
||||||
TypePointer& lengthTypeGeneric = length->annotation().type;
|
|
||||||
if (!lengthTypeGeneric)
|
|
||||||
lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
|
|
||||||
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
|
|
||||||
if (!lengthType || !lengthType->mobileType())
|
|
||||||
fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
|
|
||||||
else if (lengthType->isZero())
|
|
||||||
fatalTypeError(length->location(), "Array with zero length specified.");
|
|
||||||
else if (lengthType->isFractional())
|
|
||||||
fatalTypeError(length->location(), "Array with fractional length specified.");
|
|
||||||
else if (lengthType->isNegative())
|
|
||||||
fatalTypeError(length->location(), "Array with negative length specified.");
|
|
||||||
else
|
|
||||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthType->literalValue(nullptr));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
|
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
|
||||||
@ -321,115 +181,6 @@ bool ReferencesResolver::visit(Return const& _return)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
|
|
||||||
{
|
|
||||||
if (_variable.annotation().type)
|
|
||||||
return;
|
|
||||||
|
|
||||||
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())
|
|
||||||
{
|
|
||||||
// This can still happen in very unusual cases where a developer uses constructs, such as
|
|
||||||
// `var a;`, however, such code will have generated errors already.
|
|
||||||
// However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
|
|
||||||
// invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
|
|
||||||
// after this step.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
using Location = VariableDeclaration::Location;
|
|
||||||
Location varLoc = _variable.referenceLocation();
|
|
||||||
DataLocation typeLoc = DataLocation::Memory;
|
|
||||||
|
|
||||||
set<Location> allowedDataLocations = _variable.allowedDataLocations();
|
|
||||||
if (!allowedDataLocations.count(varLoc))
|
|
||||||
{
|
|
||||||
auto locationToString = [](VariableDeclaration::Location _location) -> string
|
|
||||||
{
|
|
||||||
switch (_location)
|
|
||||||
{
|
|
||||||
case Location::Memory: return "\"memory\"";
|
|
||||||
case Location::Storage: return "\"storage\"";
|
|
||||||
case Location::CallData: return "\"calldata\"";
|
|
||||||
case Location::Unspecified: return "none";
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
string errorString;
|
|
||||||
if (!_variable.hasReferenceOrMappingType())
|
|
||||||
errorString = "Data location can only be specified for array, struct or mapping types";
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorString = "Data location must be " +
|
|
||||||
util::joinHumanReadable(
|
|
||||||
allowedDataLocations | boost::adaptors::transformed(locationToString),
|
|
||||||
", ",
|
|
||||||
" or "
|
|
||||||
);
|
|
||||||
if (_variable.isCallableOrCatchParameter())
|
|
||||||
errorString +=
|
|
||||||
" for " +
|
|
||||||
string(_variable.isReturnParameter() ? "return " : "") +
|
|
||||||
"parameter in" +
|
|
||||||
string(_variable.isExternalCallableParameter() ? " external" : "") +
|
|
||||||
" function";
|
|
||||||
else
|
|
||||||
errorString += " for variable";
|
|
||||||
}
|
|
||||||
errorString += ", but " + locationToString(varLoc) + " was given.";
|
|
||||||
typeError(_variable.location(), errorString);
|
|
||||||
|
|
||||||
solAssert(!allowedDataLocations.empty(), "");
|
|
||||||
varLoc = *allowedDataLocations.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find correct data location.
|
|
||||||
if (_variable.isEventParameter())
|
|
||||||
{
|
|
||||||
solAssert(varLoc == Location::Unspecified, "");
|
|
||||||
typeLoc = DataLocation::Memory;
|
|
||||||
}
|
|
||||||
else if (_variable.isStateVariable())
|
|
||||||
{
|
|
||||||
solAssert(varLoc == Location::Unspecified, "");
|
|
||||||
typeLoc = (_variable.isConstant() || _variable.immutable()) ? DataLocation::Memory : DataLocation::Storage;
|
|
||||||
}
|
|
||||||
else if (
|
|
||||||
dynamic_cast<StructDefinition const*>(_variable.scope()) ||
|
|
||||||
dynamic_cast<EnumDefinition const*>(_variable.scope())
|
|
||||||
)
|
|
||||||
// The actual location will later be changed depending on how the type is used.
|
|
||||||
typeLoc = DataLocation::Storage;
|
|
||||||
else
|
|
||||||
switch (varLoc)
|
|
||||||
{
|
|
||||||
case Location::Memory:
|
|
||||||
typeLoc = DataLocation::Memory;
|
|
||||||
break;
|
|
||||||
case Location::Storage:
|
|
||||||
typeLoc = DataLocation::Storage;
|
|
||||||
break;
|
|
||||||
case Location::CallData:
|
|
||||||
typeLoc = DataLocation::CallData;
|
|
||||||
break;
|
|
||||||
case Location::Unspecified:
|
|
||||||
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
TypePointer type = _variable.typeName()->annotation().type;
|
|
||||||
if (auto ref = dynamic_cast<ReferenceType const*>(type))
|
|
||||||
{
|
|
||||||
bool isPointer = !_variable.isStateVariable();
|
|
||||||
type = TypeProvider::withLocation(ref, typeLoc, isPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
_variable.annotation().type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
|
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
bool wasInsideFunction = m_yulInsideFunction;
|
bool wasInsideFunction = m_yulInsideFunction;
|
||||||
@ -514,18 +265,6 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
|
|||||||
visit(*_varDecl.value);
|
visit(*_varDecl.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
|
|
||||||
{
|
|
||||||
m_errorOccurred = true;
|
|
||||||
m_errorReporter.typeError(_location, _description);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReferencesResolver::fatalTypeError(SourceLocation const& _location, string const& _description)
|
|
||||||
{
|
|
||||||
m_errorOccurred = true;
|
|
||||||
m_errorReporter.fatalTypeError(_location, _description);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
|
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
|
||||||
{
|
{
|
||||||
m_errorOccurred = true;
|
m_errorOccurred = true;
|
||||||
|
@ -76,29 +76,18 @@ private:
|
|||||||
void endVisit(ForStatement const& _for) override;
|
void endVisit(ForStatement const& _for) override;
|
||||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||||
bool visit(Identifier const& _identifier) override;
|
bool visit(Identifier const& _identifier) override;
|
||||||
bool visit(ElementaryTypeName const& _typeName) override;
|
|
||||||
bool visit(FunctionDefinition const& _functionDefinition) override;
|
bool visit(FunctionDefinition const& _functionDefinition) override;
|
||||||
void endVisit(FunctionDefinition const& _functionDefinition) override;
|
void endVisit(FunctionDefinition const& _functionDefinition) override;
|
||||||
bool visit(ModifierDefinition const& _modifierDefinition) override;
|
bool visit(ModifierDefinition const& _modifierDefinition) override;
|
||||||
void endVisit(ModifierDefinition const& _modifierDefinition) override;
|
void endVisit(ModifierDefinition const& _modifierDefinition) override;
|
||||||
void endVisit(UserDefinedTypeName const& _typeName) override;
|
void endVisit(UserDefinedTypeName const& _typeName) override;
|
||||||
void endVisit(FunctionTypeName const& _typeName) override;
|
|
||||||
void endVisit(Mapping const& _mapping) override;
|
|
||||||
void endVisit(ArrayTypeName const& _typeName) override;
|
|
||||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||||
bool visit(Return const& _return) override;
|
bool visit(Return const& _return) override;
|
||||||
void endVisit(VariableDeclaration const& _variable) override;
|
|
||||||
|
|
||||||
void operator()(yul::FunctionDefinition const& _function) override;
|
void operator()(yul::FunctionDefinition const& _function) override;
|
||||||
void operator()(yul::Identifier const& _identifier) override;
|
void operator()(yul::Identifier const& _identifier) override;
|
||||||
void operator()(yul::VariableDeclaration const& _varDecl) override;
|
void operator()(yul::VariableDeclaration const& _varDecl) override;
|
||||||
|
|
||||||
/// Adds a new error to the list of errors.
|
|
||||||
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
|
|
||||||
|
|
||||||
/// Adds a new error to the list of errors and throws to abort reference resolving.
|
|
||||||
void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description);
|
|
||||||
|
|
||||||
/// Adds a new error to the list of errors.
|
/// Adds a new error to the list of errors.
|
||||||
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);
|
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&)
|
|||||||
|
|
||||||
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
|
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
|
||||||
{
|
{
|
||||||
if (!m_placeholderFound)
|
if (_modifier.isImplemented() && !m_placeholderFound)
|
||||||
m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
|
m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
|
||||||
m_placeholderFound = false;
|
m_placeholderFound = false;
|
||||||
}
|
}
|
||||||
|
@ -289,37 +289,10 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
|||||||
m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
|
m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TypeChecker::visit(StructDefinition const& _struct)
|
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
|
||||||
{
|
{
|
||||||
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
|
||||||
solAssert(type(*member)->canBeStored(), "Type cannot be used in struct.");
|
m_errorReporter.typeError(_modifier.location(), "Modifiers without implementation must be marked virtual.");
|
||||||
|
|
||||||
// Check recursion, fatal error if detected.
|
|
||||||
auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector, size_t _depth)
|
|
||||||
{
|
|
||||||
if (_depth >= 256)
|
|
||||||
m_errorReporter.fatalDeclarationError(_struct.location(), "Struct definition exhausting cyclic dependency validator.");
|
|
||||||
|
|
||||||
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
|
||||||
{
|
|
||||||
Type const* memberType = type(*member);
|
|
||||||
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
|
|
||||||
{
|
|
||||||
if (arrayType->isDynamicallySized())
|
|
||||||
break;
|
|
||||||
memberType = arrayType->baseType();
|
|
||||||
}
|
|
||||||
if (auto structType = dynamic_cast<StructType const*>(memberType))
|
|
||||||
if (_cycleDetector.run(structType->structDefinition()))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
|
|
||||||
m_errorReporter.fatalTypeError(_struct.location(), "Recursive struct definition.");
|
|
||||||
|
|
||||||
ASTNode::listAccept(_struct.members(), *this);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TypeChecker::visit(FunctionDefinition const& _function)
|
bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||||
@ -518,19 +491,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
|||||||
m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables.");
|
m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables.");
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (varType->category())
|
if (auto referenceType = dynamic_cast<ReferenceType const*>(varType))
|
||||||
{
|
{
|
||||||
case Type::Category::Array:
|
auto result = referenceType->validForLocation(referenceType->location());
|
||||||
if (auto arrayType = dynamic_cast<ArrayType const*>(varType))
|
if (result && _variable.isPublicCallableParameter())
|
||||||
if (
|
result = referenceType->validForLocation(DataLocation::CallData);
|
||||||
((arrayType->location() == DataLocation::Memory) ||
|
if (!result)
|
||||||
(arrayType->location() == DataLocation::CallData)) &&
|
{
|
||||||
!arrayType->validForCalldata()
|
solAssert(!result.message().empty(), "Expected detailed error message");
|
||||||
)
|
m_errorReporter.typeError(_variable.location(), result.message());
|
||||||
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded.");
|
}
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -631,7 +601,15 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
|||||||
{
|
{
|
||||||
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
|
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
|
||||||
if (fun.kind() == FunctionType::Kind::External)
|
if (fun.kind() == FunctionType::Kind::External)
|
||||||
|
{
|
||||||
|
for (auto const& t: _funType.parameterTypes() + _funType.returnParameterTypes())
|
||||||
|
{
|
||||||
|
solAssert(t->annotation().type, "Type not set for parameter.");
|
||||||
|
if (!t->annotation().type->interfaceType(false).get())
|
||||||
|
m_errorReporter.typeError(t->location(), "Internal type cannot be used for external function type.");
|
||||||
|
}
|
||||||
solAssert(fun.interfaceType(false), "External function type uses internal types.");
|
solAssert(fun.interfaceType(false), "External function type uses internal types.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||||
@ -1303,7 +1281,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
|
|||||||
_conditional.trueExpression().annotation().isPure &&
|
_conditional.trueExpression().annotation().isPure &&
|
||||||
_conditional.falseExpression().annotation().isPure;
|
_conditional.falseExpression().annotation().isPure;
|
||||||
|
|
||||||
if (_conditional.annotation().lValueRequested)
|
if (_conditional.annotation().willBeWrittenTo)
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
_conditional.location(),
|
_conditional.location(),
|
||||||
"Conditional expression as left value is not supported yet."
|
"Conditional expression as left value is not supported yet."
|
||||||
@ -1316,8 +1294,11 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
|
|||||||
{
|
{
|
||||||
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
|
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
|
||||||
{
|
{
|
||||||
|
if (tupleExpression->components().empty())
|
||||||
|
m_errorReporter.typeError(_expression.location(), "Empty tuple on the left hand side.");
|
||||||
|
|
||||||
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
|
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
|
||||||
auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector<TypePointer> { &_type };
|
auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : vector<TypePointer> { &_type };
|
||||||
|
|
||||||
solAssert(
|
solAssert(
|
||||||
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
|
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
|
||||||
@ -1399,7 +1380,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
|||||||
vector<ASTPointer<Expression>> const& components = _tuple.components();
|
vector<ASTPointer<Expression>> const& components = _tuple.components();
|
||||||
TypePointers types;
|
TypePointers types;
|
||||||
|
|
||||||
if (_tuple.annotation().lValueRequested)
|
if (_tuple.annotation().willBeWrittenTo)
|
||||||
{
|
{
|
||||||
if (_tuple.isInlineArray())
|
if (_tuple.isInlineArray())
|
||||||
m_errorReporter.fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue.");
|
m_errorReporter.fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue.");
|
||||||
@ -1497,13 +1478,13 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
|||||||
TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op);
|
TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op);
|
||||||
if (!t)
|
if (!t)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(
|
string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->toString();
|
||||||
_operation.location(),
|
if (modifying)
|
||||||
"Unary operator " +
|
// Cannot just report the error, ignore the unary operator, and continue,
|
||||||
string(TokenTraits::toString(op)) +
|
// because the sub-expression was already processed with requireLValue()
|
||||||
" cannot be applied to type " +
|
m_errorReporter.fatalTypeError(_operation.location(), description);
|
||||||
subExprType->toString()
|
else
|
||||||
);
|
m_errorReporter.typeError(_operation.location(), description);
|
||||||
t = subExprType;
|
t = subExprType;
|
||||||
}
|
}
|
||||||
_operation.annotation().type = t;
|
_operation.annotation().type = t;
|
||||||
@ -1796,6 +1777,10 @@ void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
|
|||||||
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
|
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
solAssert(_function.isConstructor(), "");
|
solAssert(_function.isConstructor(), "");
|
||||||
|
if (_function.markedVirtual())
|
||||||
|
m_errorReporter.typeError(_function.location(), "Constructors cannot be virtual.");
|
||||||
|
if (_function.overrides())
|
||||||
|
m_errorReporter.typeError(_function.location(), "Constructors cannot override.");
|
||||||
if (!_function.returnParameters().empty())
|
if (!_function.returnParameters().empty())
|
||||||
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
|
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
|
||||||
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
|
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
|
||||||
@ -2613,6 +2598,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
|||||||
}
|
}
|
||||||
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
|
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
|
||||||
annotation.isPure = true;
|
annotation.isPure = true;
|
||||||
|
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId")
|
||||||
|
annotation.isPure = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -2776,11 +2763,57 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector<Declaration const*> TypeChecker::cleanOverloadedDeclarations(
|
||||||
|
Identifier const& _identifier,
|
||||||
|
vector<Declaration const*> const& _candidates
|
||||||
|
)
|
||||||
|
{
|
||||||
|
solAssert(_candidates.size() > 1, "");
|
||||||
|
vector<Declaration const*> uniqueDeclarations;
|
||||||
|
|
||||||
|
for (Declaration const* declaration: _candidates)
|
||||||
|
{
|
||||||
|
solAssert(declaration, "");
|
||||||
|
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
|
||||||
|
solAssert(
|
||||||
|
dynamic_cast<FunctionDefinition const*>(declaration) ||
|
||||||
|
dynamic_cast<EventDefinition const*>(declaration) ||
|
||||||
|
dynamic_cast<VariableDeclaration const*>(declaration) ||
|
||||||
|
dynamic_cast<MagicVariableDeclaration const*>(declaration),
|
||||||
|
"Found overloading involving something not a function, event or a (magic) variable."
|
||||||
|
);
|
||||||
|
|
||||||
|
FunctionTypePointer functionType {declaration->functionType(false)};
|
||||||
|
if (!functionType)
|
||||||
|
functionType = declaration->functionType(true);
|
||||||
|
solAssert(functionType, "Failed to determine the function type of the overloaded.");
|
||||||
|
|
||||||
|
for (TypePointer parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
|
||||||
|
if (!parameter)
|
||||||
|
m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
|
||||||
|
|
||||||
|
if (uniqueDeclarations.end() == find_if(
|
||||||
|
uniqueDeclarations.begin(),
|
||||||
|
uniqueDeclarations.end(),
|
||||||
|
[&](Declaration const* d)
|
||||||
|
{
|
||||||
|
FunctionType const* newFunctionType = d->functionType(false);
|
||||||
|
if (!newFunctionType)
|
||||||
|
newFunctionType = d->functionType(true);
|
||||||
|
return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
|
||||||
|
}
|
||||||
|
))
|
||||||
|
uniqueDeclarations.push_back(declaration);
|
||||||
|
}
|
||||||
|
return uniqueDeclarations;
|
||||||
|
}
|
||||||
|
|
||||||
bool TypeChecker::visit(Identifier const& _identifier)
|
bool TypeChecker::visit(Identifier const& _identifier)
|
||||||
{
|
{
|
||||||
IdentifierAnnotation& annotation = _identifier.annotation();
|
IdentifierAnnotation& annotation = _identifier.annotation();
|
||||||
if (!annotation.referencedDeclaration)
|
if (!annotation.referencedDeclaration)
|
||||||
{
|
{
|
||||||
|
annotation.overloadedDeclarations = cleanOverloadedDeclarations(_identifier, annotation.candidateDeclarations);
|
||||||
if (!annotation.arguments)
|
if (!annotation.arguments)
|
||||||
{
|
{
|
||||||
// The identifier should be a public state variable shadowing other functions
|
// The identifier should be a public state variable shadowing other functions
|
||||||
@ -3000,7 +3033,7 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
|
|||||||
|
|
||||||
void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAssignment)
|
void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAssignment)
|
||||||
{
|
{
|
||||||
_expression.annotation().lValueRequested = true;
|
_expression.annotation().willBeWrittenTo = true;
|
||||||
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
|
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
|
||||||
_expression.accept(*this);
|
_expression.accept(*this);
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ private:
|
|||||||
|
|
||||||
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
||||||
void endVisit(UsingForDirective const& _usingFor) override;
|
void endVisit(UsingForDirective const& _usingFor) override;
|
||||||
bool visit(StructDefinition const& _struct) override;
|
void endVisit(ModifierDefinition const& _modifier) override;
|
||||||
bool visit(FunctionDefinition const& _function) override;
|
bool visit(FunctionDefinition const& _function) override;
|
||||||
bool visit(VariableDeclaration const& _variable) override;
|
bool visit(VariableDeclaration const& _variable) override;
|
||||||
/// We need to do this manually because we want to pass the bases of the current contract in
|
/// We need to do this manually because we want to pass the bases of the current contract in
|
||||||
@ -154,6 +154,11 @@ private:
|
|||||||
/// @returns the referenced declaration and throws on error.
|
/// @returns the referenced declaration and throws on error.
|
||||||
Declaration const& dereference(UserDefinedTypeName const& _typeName) const;
|
Declaration const& dereference(UserDefinedTypeName const& _typeName) const;
|
||||||
|
|
||||||
|
std::vector<Declaration const*> cleanOverloadedDeclarations(
|
||||||
|
Identifier const& _reference,
|
||||||
|
std::vector<Declaration const*> const& _candidates
|
||||||
|
);
|
||||||
|
|
||||||
/// Runs type checks on @a _expression to infer its type and then checks that it is implicitly
|
/// Runs type checks on @a _expression to infer its type and then checks that it is implicitly
|
||||||
/// convertible to @a _expectedType.
|
/// convertible to @a _expectedType.
|
||||||
bool expectType(Expression const& _expression, Type const& _expectedType);
|
bool expectType(Expression const& _expression, Type const& _expectedType);
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <libevmasm/SemanticInformation.h>
|
#include <libevmasm/SemanticInformation.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -41,7 +42,7 @@ public:
|
|||||||
std::function<void(StateMutability, SourceLocation const&)> _reportMutability
|
std::function<void(StateMutability, SourceLocation const&)> _reportMutability
|
||||||
):
|
):
|
||||||
m_dialect(_dialect),
|
m_dialect(_dialect),
|
||||||
m_reportMutability(_reportMutability) {}
|
m_reportMutability(std::move(_reportMutability)) {}
|
||||||
|
|
||||||
void operator()(yul::Literal const&) {}
|
void operator()(yul::Literal const&) {}
|
||||||
void operator()(yul::Identifier const&) {}
|
void operator()(yul::Identifier const&) {}
|
||||||
@ -196,7 +197,7 @@ void ViewPureChecker::endVisit(Identifier const& _identifier)
|
|||||||
|
|
||||||
StateMutability mutability = StateMutability::Pure;
|
StateMutability mutability = StateMutability::Pure;
|
||||||
|
|
||||||
bool writes = _identifier.annotation().lValueRequested;
|
bool writes = _identifier.annotation().willBeWrittenTo;
|
||||||
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||||
{
|
{
|
||||||
if (varDecl->isStateVariable() && !varDecl->isConstant())
|
if (varDecl->isStateVariable() && !varDecl->isConstant())
|
||||||
@ -331,7 +332,7 @@ bool ViewPureChecker::visit(MemberAccess const& _memberAccess)
|
|||||||
void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
||||||
{
|
{
|
||||||
StateMutability mutability = StateMutability::Pure;
|
StateMutability mutability = StateMutability::Pure;
|
||||||
bool writes = _memberAccess.annotation().lValueRequested;
|
bool writes = _memberAccess.annotation().willBeWrittenTo;
|
||||||
|
|
||||||
ASTString const& member = _memberAccess.memberName();
|
ASTString const& member = _memberAccess.memberName();
|
||||||
switch (_memberAccess.expression().annotation().type->category())
|
switch (_memberAccess.expression().annotation().type->category())
|
||||||
@ -355,6 +356,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
|||||||
{MagicType::Kind::MetaType, "creationCode"},
|
{MagicType::Kind::MetaType, "creationCode"},
|
||||||
{MagicType::Kind::MetaType, "runtimeCode"},
|
{MagicType::Kind::MetaType, "runtimeCode"},
|
||||||
{MagicType::Kind::MetaType, "name"},
|
{MagicType::Kind::MetaType, "name"},
|
||||||
|
{MagicType::Kind::MetaType, "interfaceId"},
|
||||||
};
|
};
|
||||||
set<MagicMember> static const payableMembers{
|
set<MagicMember> static const payableMembers{
|
||||||
{MagicType::Kind::Message, "value"}
|
{MagicType::Kind::Message, "value"}
|
||||||
@ -401,7 +403,7 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
|
|||||||
solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
|
solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool writes = _indexAccess.annotation().lValueRequested;
|
bool writes = _indexAccess.annotation().willBeWrittenTo;
|
||||||
if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
|
if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
|
||||||
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location());
|
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location());
|
||||||
}
|
}
|
||||||
@ -409,7 +411,7 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
|
|||||||
|
|
||||||
void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess)
|
void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess)
|
||||||
{
|
{
|
||||||
bool writes = _indexRangeAccess.annotation().lValueRequested;
|
bool writes = _indexRangeAccess.annotation().willBeWrittenTo;
|
||||||
if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
|
if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
|
||||||
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location());
|
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location());
|
||||||
}
|
}
|
||||||
|
@ -28,16 +28,18 @@
|
|||||||
#include <libsolutil/Keccak256.h>
|
#include <libsolutil/Keccak256.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
ASTNode::ASTNode(int64_t _id, SourceLocation const& _location):
|
ASTNode::ASTNode(int64_t _id, SourceLocation _location):
|
||||||
m_id(_id),
|
m_id(_id),
|
||||||
m_location(_location)
|
m_location(std::move(_location))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +98,9 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
|
|||||||
return util::contains(annotation().linearizedBaseContracts, &_base);
|
return util::contains(annotation().linearizedBaseContracts, &_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
|
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const
|
||||||
{
|
{
|
||||||
auto exportedFunctionList = interfaceFunctionList();
|
auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions);
|
||||||
|
|
||||||
map<util::FixedHash<4>, FunctionTypePointer> exportedFunctions;
|
map<util::FixedHash<4>, FunctionTypePointer> exportedFunctions;
|
||||||
for (auto const& it: exportedFunctionList)
|
for (auto const& it: exportedFunctionList)
|
||||||
@ -174,14 +176,16 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
|
|||||||
return *m_interfaceEvents;
|
return *m_interfaceEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList() const
|
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
|
||||||
{
|
{
|
||||||
if (!m_interfaceFunctionList)
|
if (!m_interfaceFunctionList[_includeInheritedFunctions])
|
||||||
{
|
{
|
||||||
set<string> signaturesSeen;
|
set<string> signaturesSeen;
|
||||||
m_interfaceFunctionList = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
|
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
|
||||||
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
||||||
{
|
{
|
||||||
|
if (_includeInheritedFunctions == false && contract != this)
|
||||||
|
continue;
|
||||||
vector<FunctionTypePointer> functions;
|
vector<FunctionTypePointer> functions;
|
||||||
for (FunctionDefinition const* f: contract->definedFunctions())
|
for (FunctionDefinition const* f: contract->definedFunctions())
|
||||||
if (f->isPartOfExternalInterface())
|
if (f->isPartOfExternalInterface())
|
||||||
@ -199,12 +203,12 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
|
|||||||
{
|
{
|
||||||
signaturesSeen.insert(functionSignature);
|
signaturesSeen.insert(functionSignature);
|
||||||
util::FixedHash<4> hash(util::keccak256(functionSignature));
|
util::FixedHash<4> hash(util::keccak256(functionSignature));
|
||||||
m_interfaceFunctionList->emplace_back(hash, fun);
|
m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return *m_interfaceFunctionList;
|
return *m_interfaceFunctionList[_includeInheritedFunctions];
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePointer ContractDefinition::type() const
|
TypePointer ContractDefinition::type() const
|
||||||
@ -255,12 +259,13 @@ TypeNameAnnotation& TypeName::annotation() const
|
|||||||
|
|
||||||
TypePointer StructDefinition::type() const
|
TypePointer StructDefinition::type() const
|
||||||
{
|
{
|
||||||
|
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");
|
||||||
return TypeProvider::typeType(TypeProvider::structType(*this, DataLocation::Storage));
|
return TypeProvider::typeType(TypeProvider::structType(*this, DataLocation::Storage));
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeDeclarationAnnotation& StructDefinition::annotation() const
|
StructDeclarationAnnotation& StructDefinition::annotation() const
|
||||||
{
|
{
|
||||||
return initAnnotation<TypeDeclarationAnnotation>();
|
return initAnnotation<StructDeclarationAnnotation>();
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePointer EnumValue::type() const
|
TypePointer EnumValue::type() const
|
||||||
@ -556,6 +561,18 @@ bool VariableDeclaration::isExternalCallableParameter() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VariableDeclaration::isPublicCallableParameter() const
|
||||||
|
{
|
||||||
|
if (!isCallableOrCatchParameter())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
|
||||||
|
if (callable->visibility() == Visibility::Public)
|
||||||
|
return !isReturnParameter();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool VariableDeclaration::isInternalCallableParameter() const
|
bool VariableDeclaration::isInternalCallableParameter() const
|
||||||
{
|
{
|
||||||
if (!isCallableOrCatchParameter())
|
if (!isCallableOrCatchParameter())
|
||||||
@ -614,12 +631,20 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
|
|||||||
else if (isLocalVariable())
|
else if (isLocalVariable())
|
||||||
{
|
{
|
||||||
solAssert(typeName(), "");
|
solAssert(typeName(), "");
|
||||||
solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
|
auto dataLocations = [](TypePointer _type, auto&& _recursion) -> set<Location> {
|
||||||
if (typeName()->annotation().type->category() == Type::Category::Mapping)
|
solAssert(_type, "Can only be called after reference resolution");
|
||||||
return set<Location>{ Location::Storage };
|
switch (_type->category())
|
||||||
else
|
{
|
||||||
// TODO: add Location::Calldata once implemented for local variables.
|
case Type::Category::Array:
|
||||||
return set<Location>{ Location::Memory, Location::Storage };
|
return _recursion(dynamic_cast<ArrayType const*>(_type)->baseType(), _recursion);
|
||||||
|
case Type::Category::Mapping:
|
||||||
|
return set<Location>{ Location::Storage };
|
||||||
|
default:
|
||||||
|
// TODO: add Location::Calldata once implemented for local variables.
|
||||||
|
return set<Location>{ Location::Memory, Location::Storage };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return dataLocations(typeName()->annotation().type, dataLocations);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
// Struct members etc.
|
// Struct members etc.
|
||||||
@ -756,3 +781,25 @@ string Literal::getChecksummedAddress() const
|
|||||||
address.insert(address.begin(), 40 - address.size(), '0');
|
address.insert(address.begin(), 40 - address.size(), '0');
|
||||||
return util::getChecksummedAddress(address);
|
return util::getChecksummedAddress(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TryCatchClause const* TryStatement::successClause() const
|
||||||
|
{
|
||||||
|
solAssert(m_clauses.size() > 0, "");
|
||||||
|
return m_clauses[0].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCatchClause const* TryStatement::structuredClause() const
|
||||||
|
{
|
||||||
|
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||||
|
if (m_clauses[i]->errorName() == "Error")
|
||||||
|
return m_clauses[i].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TryCatchClause const* TryStatement::fallbackClause() const
|
||||||
|
{
|
||||||
|
for (size_t i = 1; i < m_clauses.size(); ++i)
|
||||||
|
if (m_clauses[i]->errorName().empty())
|
||||||
|
return m_clauses[i].get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace solidity::yul
|
namespace solidity::yul
|
||||||
@ -82,7 +83,7 @@ public:
|
|||||||
|
|
||||||
using SourceLocation = langutil::SourceLocation;
|
using SourceLocation = langutil::SourceLocation;
|
||||||
|
|
||||||
explicit ASTNode(int64_t _id, SourceLocation const& _location);
|
explicit ASTNode(int64_t _id, SourceLocation _location);
|
||||||
virtual ~ASTNode() {}
|
virtual ~ASTNode() {}
|
||||||
|
|
||||||
/// @returns an identifier of this AST node that is unique for a single compilation run.
|
/// @returns an identifier of this AST node that is unique for a single compilation run.
|
||||||
@ -155,8 +156,8 @@ std::vector<T const*> ASTNode::filteredNodes(std::vector<ASTPointer<ASTNode>> co
|
|||||||
class SourceUnit: public ASTNode
|
class SourceUnit: public ASTNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SourceUnit(int64_t _id, SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> const& _nodes):
|
SourceUnit(int64_t _id, SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> _nodes):
|
||||||
ASTNode(_id, _location), m_nodes(_nodes) {}
|
ASTNode(_id, _location), m_nodes(std::move(_nodes)) {}
|
||||||
|
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -224,10 +225,10 @@ public:
|
|||||||
Declaration(
|
Declaration(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _name,
|
ASTPointer<ASTString> _name,
|
||||||
Visibility _visibility = Visibility::Default
|
Visibility _visibility = Visibility::Default
|
||||||
):
|
):
|
||||||
ASTNode(_id, _location), m_name(_name), m_visibility(_visibility) {}
|
ASTNode(_id, _location), m_name(std::move(_name)), m_visibility(_visibility) {}
|
||||||
|
|
||||||
/// @returns the declared name.
|
/// @returns the declared name.
|
||||||
ASTString const& name() const { return *m_name; }
|
ASTString const& name() const { return *m_name; }
|
||||||
@ -275,9 +276,9 @@ public:
|
|||||||
PragmaDirective(
|
PragmaDirective(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
std::vector<Token> const& _tokens,
|
std::vector<Token> _tokens,
|
||||||
std::vector<ASTString> const& _literals
|
std::vector<ASTString> _literals
|
||||||
): ASTNode(_id, _location), m_tokens(_tokens), m_literals(_literals)
|
): ASTNode(_id, _location), m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
@ -318,12 +319,12 @@ public:
|
|||||||
ImportDirective(
|
ImportDirective(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _path,
|
ASTPointer<ASTString> _path,
|
||||||
ASTPointer<ASTString> const& _unitAlias,
|
ASTPointer<ASTString> const& _unitAlias,
|
||||||
SymbolAliasList _symbolAliases
|
SymbolAliasList _symbolAliases
|
||||||
):
|
):
|
||||||
Declaration(_id, _location, _unitAlias),
|
Declaration(_id, _location, _unitAlias),
|
||||||
m_path(_path),
|
m_path(std::move(_path)),
|
||||||
m_symbolAliases(move(_symbolAliases))
|
m_symbolAliases(move(_symbolAliases))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
@ -372,8 +373,8 @@ public:
|
|||||||
StructuredDocumentation(
|
StructuredDocumentation(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _text
|
ASTPointer<ASTString> _text
|
||||||
): ASTNode(_id, _location), m_text(_text)
|
): ASTNode(_id, _location), m_text(std::move(_text))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
@ -394,7 +395,7 @@ class Documented
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~Documented() = default;
|
virtual ~Documented() = default;
|
||||||
explicit Documented(ASTPointer<ASTString> const& _documentation): m_documentation(_documentation) {}
|
explicit Documented(ASTPointer<ASTString> _documentation): m_documentation(std::move(_documentation)) {}
|
||||||
|
|
||||||
/// @return A shared pointer of an ASTString.
|
/// @return A shared pointer of an ASTString.
|
||||||
/// Can contain a nullptr in which case indicates absence of documentation
|
/// Can contain a nullptr in which case indicates absence of documentation
|
||||||
@ -411,7 +412,7 @@ class StructurallyDocumented
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~StructurallyDocumented() = default;
|
virtual ~StructurallyDocumented() = default;
|
||||||
explicit StructurallyDocumented(ASTPointer<StructuredDocumentation> const& _documentation): m_documentation(_documentation) {}
|
explicit StructurallyDocumented(ASTPointer<StructuredDocumentation> _documentation): m_documentation(std::move(_documentation)) {}
|
||||||
|
|
||||||
/// @return A shared pointer of a FormalDocumentation.
|
/// @return A shared pointer of a FormalDocumentation.
|
||||||
/// Can contain a nullptr in which case indicates absence of documentation
|
/// Can contain a nullptr in which case indicates absence of documentation
|
||||||
@ -453,15 +454,15 @@ public:
|
|||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _name,
|
ASTPointer<ASTString> const& _name,
|
||||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||||
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
|
std::vector<ASTPointer<InheritanceSpecifier>> _baseContracts,
|
||||||
std::vector<ASTPointer<ASTNode>> const& _subNodes,
|
std::vector<ASTPointer<ASTNode>> _subNodes,
|
||||||
ContractKind _contractKind = ContractKind::Contract,
|
ContractKind _contractKind = ContractKind::Contract,
|
||||||
bool _abstract = false
|
bool _abstract = false
|
||||||
):
|
):
|
||||||
Declaration(_id, _location, _name),
|
Declaration(_id, _location, _name),
|
||||||
StructurallyDocumented(_documentation),
|
StructurallyDocumented(_documentation),
|
||||||
m_baseContracts(_baseContracts),
|
m_baseContracts(std::move(_baseContracts)),
|
||||||
m_subNodes(_subNodes),
|
m_subNodes(std::move(_subNodes)),
|
||||||
m_contractKind(_contractKind),
|
m_contractKind(_contractKind),
|
||||||
m_abstract(_abstract)
|
m_abstract(_abstract)
|
||||||
{}
|
{}
|
||||||
@ -488,8 +489,8 @@ public:
|
|||||||
|
|
||||||
/// @returns a map of canonical function signatures to FunctionDefinitions
|
/// @returns a map of canonical function signatures to FunctionDefinitions
|
||||||
/// as intended for use by the ABI.
|
/// as intended for use by the ABI.
|
||||||
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions() const;
|
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const;
|
||||||
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList() const;
|
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const;
|
||||||
|
|
||||||
/// @returns a list of all declarations in this contract
|
/// @returns a list of all declarations in this contract
|
||||||
std::vector<Declaration const*> declarations() const { return filteredNodes<Declaration>(m_subNodes); }
|
std::vector<Declaration const*> declarations() const { return filteredNodes<Declaration>(m_subNodes); }
|
||||||
@ -528,7 +529,7 @@ private:
|
|||||||
ContractKind m_contractKind;
|
ContractKind m_contractKind;
|
||||||
bool m_abstract{false};
|
bool m_abstract{false};
|
||||||
|
|
||||||
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
|
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
|
||||||
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
|
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -538,10 +539,10 @@ public:
|
|||||||
InheritanceSpecifier(
|
InheritanceSpecifier(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<UserDefinedTypeName> const& _baseName,
|
ASTPointer<UserDefinedTypeName> _baseName,
|
||||||
std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
|
std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
|
||||||
):
|
):
|
||||||
ASTNode(_id, _location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {}
|
ASTNode(_id, _location), m_baseName(std::move(_baseName)), m_arguments(std::move(_arguments)) {}
|
||||||
|
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -568,10 +569,10 @@ public:
|
|||||||
UsingForDirective(
|
UsingForDirective(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<UserDefinedTypeName> const& _libraryName,
|
ASTPointer<UserDefinedTypeName> _libraryName,
|
||||||
ASTPointer<TypeName> const& _typeName
|
ASTPointer<TypeName> _typeName
|
||||||
):
|
):
|
||||||
ASTNode(_id, _location), m_libraryName(_libraryName), m_typeName(_typeName) {}
|
ASTNode(_id, _location), m_libraryName(std::move(_libraryName)), m_typeName(std::move(_typeName)) {}
|
||||||
|
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -592,9 +593,9 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _name,
|
ASTPointer<ASTString> const& _name,
|
||||||
std::vector<ASTPointer<VariableDeclaration>> const& _members
|
std::vector<ASTPointer<VariableDeclaration>> _members
|
||||||
):
|
):
|
||||||
Declaration(_id, _location, _name), m_members(_members) {}
|
Declaration(_id, _location, _name), m_members(std::move(_members)) {}
|
||||||
|
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -606,7 +607,7 @@ public:
|
|||||||
bool isVisibleInDerivedContracts() const override { return true; }
|
bool isVisibleInDerivedContracts() const override { return true; }
|
||||||
bool isVisibleViaContractTypeAccess() const override { return true; }
|
bool isVisibleViaContractTypeAccess() const override { return true; }
|
||||||
|
|
||||||
TypeDeclarationAnnotation& annotation() const override;
|
StructDeclarationAnnotation& annotation() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<ASTPointer<VariableDeclaration>> m_members;
|
std::vector<ASTPointer<VariableDeclaration>> m_members;
|
||||||
@ -619,9 +620,9 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _name,
|
ASTPointer<ASTString> const& _name,
|
||||||
std::vector<ASTPointer<EnumValue>> const& _members
|
std::vector<ASTPointer<EnumValue>> _members
|
||||||
):
|
):
|
||||||
Declaration(_id, _location, _name), m_members(_members) {}
|
Declaration(_id, _location, _name), m_members(std::move(_members)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -664,9 +665,9 @@ public:
|
|||||||
ParameterList(
|
ParameterList(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
std::vector<ASTPointer<VariableDeclaration>> const& _parameters
|
std::vector<ASTPointer<VariableDeclaration>> _parameters
|
||||||
):
|
):
|
||||||
ASTNode(_id, _location), m_parameters(_parameters) {}
|
ASTNode(_id, _location), m_parameters(std::move(_parameters)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -688,15 +689,15 @@ public:
|
|||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _name,
|
ASTPointer<ASTString> const& _name,
|
||||||
Visibility _visibility,
|
Visibility _visibility,
|
||||||
ASTPointer<ParameterList> const& _parameters,
|
ASTPointer<ParameterList> _parameters,
|
||||||
bool _isVirtual = false,
|
bool _isVirtual = false,
|
||||||
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
ASTPointer<OverrideSpecifier> _overrides = nullptr,
|
||||||
ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>()
|
ASTPointer<ParameterList> _returnParameters = ASTPointer<ParameterList>()
|
||||||
):
|
):
|
||||||
Declaration(_id, _location, _name, _visibility),
|
Declaration(_id, _location, _name, _visibility),
|
||||||
m_parameters(_parameters),
|
m_parameters(std::move(_parameters)),
|
||||||
m_overrides(_overrides),
|
m_overrides(std::move(_overrides)),
|
||||||
m_returnParameters(_returnParameters),
|
m_returnParameters(std::move(_returnParameters)),
|
||||||
m_isVirtual(_isVirtual)
|
m_isVirtual(_isVirtual)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -740,10 +741,10 @@ public:
|
|||||||
OverrideSpecifier(
|
OverrideSpecifier(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
std::vector<ASTPointer<UserDefinedTypeName>> const& _overrides
|
std::vector<ASTPointer<UserDefinedTypeName>> _overrides
|
||||||
):
|
):
|
||||||
ASTNode(_id, _location),
|
ASTNode(_id, _location),
|
||||||
m_overrides(_overrides)
|
m_overrides(std::move(_overrides))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -771,7 +772,7 @@ public:
|
|||||||
ASTPointer<OverrideSpecifier> const& _overrides,
|
ASTPointer<OverrideSpecifier> const& _overrides,
|
||||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||||
ASTPointer<ParameterList> const& _parameters,
|
ASTPointer<ParameterList> const& _parameters,
|
||||||
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
|
std::vector<ASTPointer<ModifierInvocation>> _modifiers,
|
||||||
ASTPointer<ParameterList> const& _returnParameters,
|
ASTPointer<ParameterList> const& _returnParameters,
|
||||||
ASTPointer<Block> const& _body
|
ASTPointer<Block> const& _body
|
||||||
):
|
):
|
||||||
@ -780,7 +781,7 @@ public:
|
|||||||
ImplementationOptional(_body != nullptr),
|
ImplementationOptional(_body != nullptr),
|
||||||
m_stateMutability(_stateMutability),
|
m_stateMutability(_stateMutability),
|
||||||
m_kind(_kind),
|
m_kind(_kind),
|
||||||
m_functionModifiers(_modifiers),
|
m_functionModifiers(std::move(_modifiers)),
|
||||||
m_body(_body)
|
m_body(_body)
|
||||||
{
|
{
|
||||||
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
|
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
|
||||||
@ -869,23 +870,23 @@ public:
|
|||||||
VariableDeclaration(
|
VariableDeclaration(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<TypeName> const& _type,
|
ASTPointer<TypeName> _type,
|
||||||
ASTPointer<ASTString> const& _name,
|
ASTPointer<ASTString> const& _name,
|
||||||
ASTPointer<Expression> _value,
|
ASTPointer<Expression> _value,
|
||||||
Visibility _visibility,
|
Visibility _visibility,
|
||||||
bool _isStateVar = false,
|
bool _isStateVar = false,
|
||||||
bool _isIndexed = false,
|
bool _isIndexed = false,
|
||||||
Mutability _mutability = Mutability::Mutable,
|
Mutability _mutability = Mutability::Mutable,
|
||||||
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
ASTPointer<OverrideSpecifier> _overrides = nullptr,
|
||||||
Location _referenceLocation = Location::Unspecified
|
Location _referenceLocation = Location::Unspecified
|
||||||
):
|
):
|
||||||
Declaration(_id, _location, _name, _visibility),
|
Declaration(_id, _location, _name, _visibility),
|
||||||
m_typeName(_type),
|
m_typeName(std::move(_type)),
|
||||||
m_value(_value),
|
m_value(std::move(_value)),
|
||||||
m_isStateVariable(_isStateVar),
|
m_isStateVariable(_isStateVar),
|
||||||
m_isIndexed(_isIndexed),
|
m_isIndexed(_isIndexed),
|
||||||
m_mutability(_mutability),
|
m_mutability(_mutability),
|
||||||
m_overrides(_overrides),
|
m_overrides(std::move(_overrides)),
|
||||||
m_location(_referenceLocation) {}
|
m_location(_referenceLocation) {}
|
||||||
|
|
||||||
|
|
||||||
@ -913,6 +914,8 @@ public:
|
|||||||
/// @returns true if this variable is a parameter (not return parameter) of an external function.
|
/// @returns true if this variable is a parameter (not return parameter) of an external function.
|
||||||
/// This excludes parameters of external function type names.
|
/// This excludes parameters of external function type names.
|
||||||
bool isExternalCallableParameter() const;
|
bool isExternalCallableParameter() const;
|
||||||
|
/// @returns true if this variable is a parameter (not return parameter) of a public function.
|
||||||
|
bool isPublicCallableParameter() const;
|
||||||
/// @returns true if this variable is a parameter or return parameter of an internal function
|
/// @returns true if this variable is a parameter or return parameter of an internal function
|
||||||
/// or a function type of internal visibility.
|
/// or a function type of internal visibility.
|
||||||
bool isInternalCallableParameter() const;
|
bool isInternalCallableParameter() const;
|
||||||
@ -966,7 +969,7 @@ private:
|
|||||||
/**
|
/**
|
||||||
* Definition of a function modifier.
|
* Definition of a function modifier.
|
||||||
*/
|
*/
|
||||||
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented
|
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ModifierDefinition(
|
ModifierDefinition(
|
||||||
@ -981,6 +984,7 @@ public:
|
|||||||
):
|
):
|
||||||
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
|
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
|
||||||
StructurallyDocumented(_documentation),
|
StructurallyDocumented(_documentation),
|
||||||
|
ImplementationOptional(_body != nullptr),
|
||||||
m_body(_body)
|
m_body(_body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -988,7 +992,7 @@ public:
|
|||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
Block const& body() const { return *m_body; }
|
Block const& body() const { solAssert(m_body, ""); return *m_body; }
|
||||||
|
|
||||||
TypePointer type() const override;
|
TypePointer type() const override;
|
||||||
|
|
||||||
@ -1015,10 +1019,10 @@ public:
|
|||||||
ModifierInvocation(
|
ModifierInvocation(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Identifier> const& _name,
|
ASTPointer<Identifier> _name,
|
||||||
std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
|
std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
|
||||||
):
|
):
|
||||||
ASTNode(_id, _location), m_modifierName(_name), m_arguments(std::move(_arguments)) {}
|
ASTNode(_id, _location), m_modifierName(std::move(_name)), m_arguments(std::move(_arguments)) {}
|
||||||
|
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1159,8 +1163,8 @@ private:
|
|||||||
class UserDefinedTypeName: public TypeName
|
class UserDefinedTypeName: public TypeName
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector<ASTString> const& _namePath):
|
UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector<ASTString> _namePath):
|
||||||
TypeName(_id, _location), m_namePath(_namePath) {}
|
TypeName(_id, _location), m_namePath(std::move(_namePath)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1181,12 +1185,12 @@ public:
|
|||||||
FunctionTypeName(
|
FunctionTypeName(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ParameterList> const& _parameterTypes,
|
ASTPointer<ParameterList> _parameterTypes,
|
||||||
ASTPointer<ParameterList> const& _returnTypes,
|
ASTPointer<ParameterList> _returnTypes,
|
||||||
Visibility _visibility,
|
Visibility _visibility,
|
||||||
StateMutability _stateMutability
|
StateMutability _stateMutability
|
||||||
):
|
):
|
||||||
TypeName(_id, _location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes),
|
TypeName(_id, _location), m_parameterTypes(std::move(_parameterTypes)), m_returnTypes(std::move(_returnTypes)),
|
||||||
m_visibility(_visibility), m_stateMutability(_stateMutability)
|
m_visibility(_visibility), m_stateMutability(_stateMutability)
|
||||||
{}
|
{}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
@ -1220,10 +1224,10 @@ public:
|
|||||||
Mapping(
|
Mapping(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<TypeName> const& _keyType,
|
ASTPointer<TypeName> _keyType,
|
||||||
ASTPointer<TypeName> const& _valueType
|
ASTPointer<TypeName> _valueType
|
||||||
):
|
):
|
||||||
TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {}
|
TypeName(_id, _location), m_keyType(std::move(_keyType)), m_valueType(std::move(_valueType)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1244,10 +1248,10 @@ public:
|
|||||||
ArrayTypeName(
|
ArrayTypeName(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<TypeName> const& _baseType,
|
ASTPointer<TypeName> _baseType,
|
||||||
ASTPointer<Expression> const& _length
|
ASTPointer<Expression> _length
|
||||||
):
|
):
|
||||||
TypeName(_id, _location), m_baseType(_baseType), m_length(_length) {}
|
TypeName(_id, _location), m_baseType(std::move(_baseType)), m_length(std::move(_length)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1291,9 +1295,9 @@ public:
|
|||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
yul::Dialect const& _dialect,
|
yul::Dialect const& _dialect,
|
||||||
std::shared_ptr<yul::Block> const& _operations
|
std::shared_ptr<yul::Block> _operations
|
||||||
):
|
):
|
||||||
Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(_operations) {}
|
Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(std::move(_operations)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1317,9 +1321,9 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
std::vector<ASTPointer<Statement>> const& _statements
|
std::vector<ASTPointer<Statement>> _statements
|
||||||
):
|
):
|
||||||
Statement(_id, _location, _docString), m_statements(_statements) {}
|
Statement(_id, _location, _docString), m_statements(std::move(_statements)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1359,14 +1363,14 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
ASTPointer<Expression> const& _condition,
|
ASTPointer<Expression> _condition,
|
||||||
ASTPointer<Statement> const& _trueBody,
|
ASTPointer<Statement> _trueBody,
|
||||||
ASTPointer<Statement> const& _falseBody
|
ASTPointer<Statement> _falseBody
|
||||||
):
|
):
|
||||||
Statement(_id, _location, _docString),
|
Statement(_id, _location, _docString),
|
||||||
m_condition(_condition),
|
m_condition(std::move(_condition)),
|
||||||
m_trueBody(_trueBody),
|
m_trueBody(std::move(_trueBody)),
|
||||||
m_falseBody(_falseBody)
|
m_falseBody(std::move(_falseBody))
|
||||||
{}
|
{}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1393,14 +1397,14 @@ public:
|
|||||||
TryCatchClause(
|
TryCatchClause(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _errorName,
|
ASTPointer<ASTString> _errorName,
|
||||||
ASTPointer<ParameterList> const& _parameters,
|
ASTPointer<ParameterList> _parameters,
|
||||||
ASTPointer<Block> const& _block
|
ASTPointer<Block> _block
|
||||||
):
|
):
|
||||||
ASTNode(_id, _location),
|
ASTNode(_id, _location),
|
||||||
m_errorName(_errorName),
|
m_errorName(std::move(_errorName)),
|
||||||
m_parameters(_parameters),
|
m_parameters(std::move(_parameters)),
|
||||||
m_block(_block)
|
m_block(std::move(_block))
|
||||||
{}
|
{}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1438,12 +1442,12 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
ASTPointer<Expression> const& _externalCall,
|
ASTPointer<Expression> _externalCall,
|
||||||
std::vector<ASTPointer<TryCatchClause>> const& _clauses
|
std::vector<ASTPointer<TryCatchClause>> _clauses
|
||||||
):
|
):
|
||||||
Statement(_id, _location, _docString),
|
Statement(_id, _location, _docString),
|
||||||
m_externalCall(_externalCall),
|
m_externalCall(std::move(_externalCall)),
|
||||||
m_clauses(_clauses)
|
m_clauses(std::move(_clauses))
|
||||||
{}
|
{}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1451,6 +1455,10 @@ public:
|
|||||||
Expression const& externalCall() const { return *m_externalCall; }
|
Expression const& externalCall() const { return *m_externalCall; }
|
||||||
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
|
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
|
||||||
|
|
||||||
|
TryCatchClause const* successClause() const;
|
||||||
|
TryCatchClause const* structuredClause() const;
|
||||||
|
TryCatchClause const* fallbackClause() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ASTPointer<Expression> m_externalCall;
|
ASTPointer<Expression> m_externalCall;
|
||||||
std::vector<ASTPointer<TryCatchClause>> m_clauses;
|
std::vector<ASTPointer<TryCatchClause>> m_clauses;
|
||||||
@ -1476,11 +1484,11 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
ASTPointer<Expression> const& _condition,
|
ASTPointer<Expression> _condition,
|
||||||
ASTPointer<Statement> const& _body,
|
ASTPointer<Statement> _body,
|
||||||
bool _isDoWhile
|
bool _isDoWhile
|
||||||
):
|
):
|
||||||
BreakableStatement(_id, _location, _docString), m_condition(_condition), m_body(_body),
|
BreakableStatement(_id, _location, _docString), m_condition(std::move(_condition)), m_body(std::move(_body)),
|
||||||
m_isDoWhile(_isDoWhile) {}
|
m_isDoWhile(_isDoWhile) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1505,16 +1513,16 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
ASTPointer<Statement> const& _initExpression,
|
ASTPointer<Statement> _initExpression,
|
||||||
ASTPointer<Expression> const& _conditionExpression,
|
ASTPointer<Expression> _conditionExpression,
|
||||||
ASTPointer<ExpressionStatement> const& _loopExpression,
|
ASTPointer<ExpressionStatement> _loopExpression,
|
||||||
ASTPointer<Statement> const& _body
|
ASTPointer<Statement> _body
|
||||||
):
|
):
|
||||||
BreakableStatement(_id, _location, _docString),
|
BreakableStatement(_id, _location, _docString),
|
||||||
m_initExpression(_initExpression),
|
m_initExpression(std::move(_initExpression)),
|
||||||
m_condExpression(_conditionExpression),
|
m_condExpression(std::move(_conditionExpression)),
|
||||||
m_loopExpression(_loopExpression),
|
m_loopExpression(std::move(_loopExpression)),
|
||||||
m_body(_body)
|
m_body(std::move(_body))
|
||||||
{}
|
{}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1563,7 +1571,7 @@ public:
|
|||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
ASTPointer<Expression> _expression
|
ASTPointer<Expression> _expression
|
||||||
): Statement(_id, _location, _docString), m_expression(_expression) {}
|
): Statement(_id, _location, _docString), m_expression(std::move(_expression)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1597,9 +1605,9 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
ASTPointer<FunctionCall> const& _functionCall
|
ASTPointer<FunctionCall> _functionCall
|
||||||
):
|
):
|
||||||
Statement(_id, _location, _docString), m_eventCall(_functionCall) {}
|
Statement(_id, _location, _docString), m_eventCall(std::move(_functionCall)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1624,10 +1632,10 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
std::vector<ASTPointer<VariableDeclaration>> const& _variables,
|
std::vector<ASTPointer<VariableDeclaration>> _variables,
|
||||||
ASTPointer<Expression> const& _initialValue
|
ASTPointer<Expression> _initialValue
|
||||||
):
|
):
|
||||||
Statement(_id, _location, _docString), m_variables(_variables), m_initialValue(_initialValue) {}
|
Statement(_id, _location, _docString), m_variables(std::move(_variables)), m_initialValue(std::move(_initialValue)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1656,7 +1664,7 @@ public:
|
|||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
ASTPointer<Expression> _expression
|
ASTPointer<Expression> _expression
|
||||||
):
|
):
|
||||||
Statement(_id, _location, _docString), m_expression(_expression) {}
|
Statement(_id, _location, _docString), m_expression(std::move(_expression)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1690,14 +1698,14 @@ public:
|
|||||||
Conditional(
|
Conditional(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> const& _condition,
|
ASTPointer<Expression> _condition,
|
||||||
ASTPointer<Expression> const& _trueExpression,
|
ASTPointer<Expression> _trueExpression,
|
||||||
ASTPointer<Expression> const& _falseExpression
|
ASTPointer<Expression> _falseExpression
|
||||||
):
|
):
|
||||||
Expression(_id, _location),
|
Expression(_id, _location),
|
||||||
m_condition(_condition),
|
m_condition(std::move(_condition)),
|
||||||
m_trueExpression(_trueExpression),
|
m_trueExpression(std::move(_trueExpression)),
|
||||||
m_falseExpression(_falseExpression)
|
m_falseExpression(std::move(_falseExpression))
|
||||||
{}
|
{}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1720,14 +1728,14 @@ public:
|
|||||||
Assignment(
|
Assignment(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> const& _leftHandSide,
|
ASTPointer<Expression> _leftHandSide,
|
||||||
Token _assignmentOperator,
|
Token _assignmentOperator,
|
||||||
ASTPointer<Expression> const& _rightHandSide
|
ASTPointer<Expression> _rightHandSide
|
||||||
):
|
):
|
||||||
Expression(_id, _location),
|
Expression(_id, _location),
|
||||||
m_leftHandSide(_leftHandSide),
|
m_leftHandSide(std::move(_leftHandSide)),
|
||||||
m_assigmentOperator(_assignmentOperator),
|
m_assigmentOperator(_assignmentOperator),
|
||||||
m_rightHandSide(_rightHandSide)
|
m_rightHandSide(std::move(_rightHandSide))
|
||||||
{
|
{
|
||||||
solAssert(TokenTraits::isAssignmentOp(_assignmentOperator), "");
|
solAssert(TokenTraits::isAssignmentOp(_assignmentOperator), "");
|
||||||
}
|
}
|
||||||
@ -1758,11 +1766,11 @@ public:
|
|||||||
TupleExpression(
|
TupleExpression(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
std::vector<ASTPointer<Expression>> const& _components,
|
std::vector<ASTPointer<Expression>> _components,
|
||||||
bool _isArray
|
bool _isArray
|
||||||
):
|
):
|
||||||
Expression(_id, _location),
|
Expression(_id, _location),
|
||||||
m_components(_components),
|
m_components(std::move(_components)),
|
||||||
m_isArray(_isArray) {}
|
m_isArray(_isArray) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
@ -1786,12 +1794,12 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
Token _operator,
|
Token _operator,
|
||||||
ASTPointer<Expression> const& _subExpression,
|
ASTPointer<Expression> _subExpression,
|
||||||
bool _isPrefix
|
bool _isPrefix
|
||||||
):
|
):
|
||||||
Expression(_id, _location),
|
Expression(_id, _location),
|
||||||
m_operator(_operator),
|
m_operator(_operator),
|
||||||
m_subExpression(_subExpression),
|
m_subExpression(std::move(_subExpression)),
|
||||||
m_isPrefix(_isPrefix)
|
m_isPrefix(_isPrefix)
|
||||||
{
|
{
|
||||||
solAssert(TokenTraits::isUnaryOp(_operator), "");
|
solAssert(TokenTraits::isUnaryOp(_operator), "");
|
||||||
@ -1819,11 +1827,11 @@ public:
|
|||||||
BinaryOperation(
|
BinaryOperation(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> const& _left,
|
ASTPointer<Expression> _left,
|
||||||
Token _operator,
|
Token _operator,
|
||||||
ASTPointer<Expression> const& _right
|
ASTPointer<Expression> _right
|
||||||
):
|
):
|
||||||
Expression(_id, _location), m_left(_left), m_operator(_operator), m_right(_right)
|
Expression(_id, _location), m_left(std::move(_left)), m_operator(_operator), m_right(std::move(_right))
|
||||||
{
|
{
|
||||||
solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), "");
|
solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), "");
|
||||||
}
|
}
|
||||||
@ -1851,11 +1859,11 @@ public:
|
|||||||
FunctionCall(
|
FunctionCall(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> const& _expression,
|
ASTPointer<Expression> _expression,
|
||||||
std::vector<ASTPointer<Expression>> const& _arguments,
|
std::vector<ASTPointer<Expression>> _arguments,
|
||||||
std::vector<ASTPointer<ASTString>> const& _names
|
std::vector<ASTPointer<ASTString>> _names
|
||||||
):
|
):
|
||||||
Expression(_id, _location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {}
|
Expression(_id, _location), m_expression(std::move(_expression)), m_arguments(std::move(_arguments)), m_names(std::move(_names)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1881,11 +1889,11 @@ public:
|
|||||||
FunctionCallOptions(
|
FunctionCallOptions(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> const& _expression,
|
ASTPointer<Expression> _expression,
|
||||||
std::vector<ASTPointer<Expression>> const& _options,
|
std::vector<ASTPointer<Expression>> _options,
|
||||||
std::vector<ASTPointer<ASTString>> const& _names
|
std::vector<ASTPointer<ASTString>> _names
|
||||||
):
|
):
|
||||||
Expression(_id, _location), m_expression(_expression), m_options(_options), m_names(_names) {}
|
Expression(_id, _location), m_expression(std::move(_expression)), m_options(std::move(_options)), m_names(std::move(_names)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1910,9 +1918,9 @@ public:
|
|||||||
NewExpression(
|
NewExpression(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<TypeName> const& _typeName
|
ASTPointer<TypeName> _typeName
|
||||||
):
|
):
|
||||||
Expression(_id, _location), m_typeName(_typeName) {}
|
Expression(_id, _location), m_typeName(std::move(_typeName)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1932,9 +1940,9 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> _expression,
|
ASTPointer<Expression> _expression,
|
||||||
ASTPointer<ASTString> const& _memberName
|
ASTPointer<ASTString> _memberName
|
||||||
):
|
):
|
||||||
Expression(_id, _location), m_expression(_expression), m_memberName(_memberName) {}
|
Expression(_id, _location), m_expression(std::move(_expression)), m_memberName(std::move(_memberName)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
Expression const& expression() const { return *m_expression; }
|
Expression const& expression() const { return *m_expression; }
|
||||||
@ -1956,10 +1964,10 @@ public:
|
|||||||
IndexAccess(
|
IndexAccess(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> const& _base,
|
ASTPointer<Expression> _base,
|
||||||
ASTPointer<Expression> const& _index
|
ASTPointer<Expression> _index
|
||||||
):
|
):
|
||||||
Expression(_id, _location), m_base(_base), m_index(_index) {}
|
Expression(_id, _location), m_base(std::move(_base)), m_index(std::move(_index)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -1980,11 +1988,11 @@ public:
|
|||||||
IndexRangeAccess(
|
IndexRangeAccess(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<Expression> const& _base,
|
ASTPointer<Expression> _base,
|
||||||
ASTPointer<Expression> const& _start,
|
ASTPointer<Expression> _start,
|
||||||
ASTPointer<Expression> const& _end
|
ASTPointer<Expression> _end
|
||||||
):
|
):
|
||||||
Expression(_id, _location), m_base(_base), m_start(_start), m_end(_end) {}
|
Expression(_id, _location), m_base(std::move(_base)), m_start(std::move(_start)), m_end(std::move(_end)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -2017,9 +2025,9 @@ public:
|
|||||||
Identifier(
|
Identifier(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _name
|
ASTPointer<ASTString> _name
|
||||||
):
|
):
|
||||||
PrimaryExpression(_id, _location), m_name(_name) {}
|
PrimaryExpression(_id, _location), m_name(std::move(_name)) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
@ -2042,10 +2050,10 @@ public:
|
|||||||
ElementaryTypeNameExpression(
|
ElementaryTypeNameExpression(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ElementaryTypeName> const& _type
|
ASTPointer<ElementaryTypeName> _type
|
||||||
):
|
):
|
||||||
PrimaryExpression(_id, _location),
|
PrimaryExpression(_id, _location),
|
||||||
m_type(_type)
|
m_type(std::move(_type))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
@ -2081,10 +2089,10 @@ public:
|
|||||||
int64_t _id,
|
int64_t _id,
|
||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
Token _token,
|
Token _token,
|
||||||
ASTPointer<ASTString> const& _value,
|
ASTPointer<ASTString> _value,
|
||||||
SubDenomination _sub = SubDenomination::None
|
SubDenomination _sub = SubDenomination::None
|
||||||
):
|
):
|
||||||
PrimaryExpression(_id, _location), m_token(_token), m_value(_value), m_subDenomination(_sub) {}
|
PrimaryExpression(_id, _location), m_token(_token), m_value(std::move(_value)), m_subDenomination(_sub) {}
|
||||||
void accept(ASTVisitor& _visitor) override;
|
void accept(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
|
@ -128,10 +128,20 @@ struct TypeDeclarationAnnotation: DeclarationAnnotation
|
|||||||
std::string canonicalName;
|
std::string canonicalName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StructDeclarationAnnotation: TypeDeclarationAnnotation
|
||||||
|
{
|
||||||
|
/// Whether the struct is recursive, i.e. if the struct (recursively) contains a member that involves a struct of the same
|
||||||
|
/// type, either in a dynamic array, as member of another struct or inside a mapping.
|
||||||
|
/// Only cases in which the recursive occurrence is within a dynamic array or a mapping are valid, while direct
|
||||||
|
/// recursion immediately raises an error.
|
||||||
|
/// Will be filled in by the DeclarationTypeChecker.
|
||||||
|
std::optional<bool> recursive;
|
||||||
|
};
|
||||||
|
|
||||||
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
|
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||||
{
|
{
|
||||||
/// List of functions without a body. Can also contain functions from base classes.
|
/// List of functions and modifiers without a body. Can also contain functions from base classes.
|
||||||
std::vector<FunctionDefinition const*> unimplementedFunctions;
|
std::vector<Declaration const*> unimplementedDeclarations;
|
||||||
/// List of all (direct and indirect) base contracts in order from derived to
|
/// List of all (direct and indirect) base contracts in order from derived to
|
||||||
/// base, including the contract itself.
|
/// base, including the contract itself.
|
||||||
std::vector<ContractDefinition const*> linearizedBaseContracts;
|
std::vector<ContractDefinition const*> linearizedBaseContracts;
|
||||||
@ -234,7 +244,7 @@ struct ExpressionAnnotation: ASTAnnotation
|
|||||||
/// Whether it is an LValue (i.e. something that can be assigned to).
|
/// Whether it is an LValue (i.e. something that can be assigned to).
|
||||||
bool isLValue = false;
|
bool isLValue = false;
|
||||||
/// Whether the expression is used in a context where the LValue is actually required.
|
/// Whether the expression is used in a context where the LValue is actually required.
|
||||||
bool lValueRequested = false;
|
bool willBeWrittenTo = false;
|
||||||
/// Whether the expression is an lvalue that is only assigned.
|
/// Whether the expression is an lvalue that is only assigned.
|
||||||
/// Would be false for --, ++, delete, +=, -=, ....
|
/// Would be false for --, ++, delete, +=, -=, ....
|
||||||
bool lValueOfOrdinaryAssignment = false;
|
bool lValueOfOrdinaryAssignment = false;
|
||||||
@ -248,6 +258,8 @@ struct IdentifierAnnotation: ExpressionAnnotation
|
|||||||
{
|
{
|
||||||
/// Referenced declaration, set at latest during overload resolution stage.
|
/// Referenced declaration, set at latest during overload resolution stage.
|
||||||
Declaration const* referencedDeclaration = nullptr;
|
Declaration const* referencedDeclaration = nullptr;
|
||||||
|
/// List of possible declarations it could refer to (can contain duplicates).
|
||||||
|
std::vector<Declaration const*> candidateDeclarations;
|
||||||
/// List of possible declarations it could refer to.
|
/// List of possible declarations it could refer to.
|
||||||
std::vector<Declaration const*> overloadedDeclarations;
|
std::vector<Declaration const*> overloadedDeclarations;
|
||||||
};
|
};
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include <boost/algorithm/string/join.hpp>
|
#include <boost/algorithm/string/join.hpp>
|
||||||
#include <boost/range/algorithm/sort.hpp>
|
#include <boost/range/algorithm/sort.hpp>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ namespace solidity::frontend
|
|||||||
|
|
||||||
ASTJsonConverter::ASTJsonConverter(bool _legacy, map<string, unsigned> _sourceIndices):
|
ASTJsonConverter::ASTJsonConverter(bool _legacy, map<string, unsigned> _sourceIndices):
|
||||||
m_legacy(_legacy),
|
m_legacy(_legacy),
|
||||||
m_sourceIndices(_sourceIndices)
|
m_sourceIndices(std::move(_sourceIndices))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +182,7 @@ void ASTJsonConverter::appendExpressionAttributes(
|
|||||||
make_pair("isConstant", _annotation.isConstant),
|
make_pair("isConstant", _annotation.isConstant),
|
||||||
make_pair("isPure", _annotation.isPure),
|
make_pair("isPure", _annotation.isPure),
|
||||||
make_pair("isLValue", _annotation.isLValue),
|
make_pair("isLValue", _annotation.isLValue),
|
||||||
make_pair("lValueRequested", _annotation.lValueRequested),
|
make_pair("lValueRequested", _annotation.willBeWrittenTo),
|
||||||
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
|
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
|
||||||
};
|
};
|
||||||
_attributes += exprAttributes;
|
_attributes += exprAttributes;
|
||||||
@ -271,7 +272,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
|||||||
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
|
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
|
||||||
make_pair("contractKind", contractKind(_node.contractKind())),
|
make_pair("contractKind", contractKind(_node.contractKind())),
|
||||||
make_pair("abstract", _node.abstract()),
|
make_pair("abstract", _node.abstract()),
|
||||||
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
|
make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()),
|
||||||
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
|
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
|
||||||
make_pair("baseContracts", toJson(_node.baseContracts())),
|
make_pair("baseContracts", toJson(_node.baseContracts())),
|
||||||
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
|
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
|
||||||
@ -406,7 +407,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
|
|||||||
make_pair("parameters", toJson(_node.parameterList())),
|
make_pair("parameters", toJson(_node.parameterList())),
|
||||||
make_pair("virtual", _node.markedVirtual()),
|
make_pair("virtual", _node.markedVirtual()),
|
||||||
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
|
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
|
||||||
make_pair("body", toJson(_node.body()))
|
make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue)
|
||||||
};
|
};
|
||||||
if (!_node.annotation().baseFunctions.empty())
|
if (!_node.annotation().baseFunctions.empty())
|
||||||
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));
|
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));
|
||||||
|
@ -453,7 +453,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
|
|||||||
createParameterList(member(_node, "parameters")),
|
createParameterList(member(_node, "parameters")),
|
||||||
memberAsBool(_node, "virtual"),
|
memberAsBool(_node, "virtual"),
|
||||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||||
createBlock(member(_node, "body"))
|
_node["body"].isNull() ? nullptr: createBlock(member(_node, "body"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,9 +23,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
{
|
{
|
||||||
@ -299,7 +301,7 @@ public:
|
|||||||
SimpleASTVisitor(
|
SimpleASTVisitor(
|
||||||
std::function<bool(ASTNode const&)> _onVisit,
|
std::function<bool(ASTNode const&)> _onVisit,
|
||||||
std::function<void(ASTNode const&)> _onEndVisit
|
std::function<void(ASTNode const&)> _onEndVisit
|
||||||
): m_onVisit(_onVisit), m_onEndVisit(_onEndVisit) {}
|
): m_onVisit(std::move(_onVisit)), m_onEndVisit(std::move(_onEndVisit)) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; }
|
bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; }
|
||||||
@ -329,7 +331,7 @@ public:
|
|||||||
ASTReduce(
|
ASTReduce(
|
||||||
std::function<bool(ASTNode const&)> _onNode,
|
std::function<bool(ASTNode const&)> _onNode,
|
||||||
std::function<void(ASTNode const&, ASTNode const&)> _onEdge
|
std::function<void(ASTNode const&, ASTNode const&)> _onEdge
|
||||||
): m_onNode(_onNode), m_onEdge(_onEdge)
|
): m_onNode(std::move(_onNode)), m_onEdge(std::move(_onEdge))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +288,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
|
|||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
if (m_overrides)
|
if (m_overrides)
|
||||||
m_overrides->accept(_visitor);
|
m_overrides->accept(_visitor);
|
||||||
m_body->accept(_visitor);
|
if (m_body)
|
||||||
|
m_body->accept(_visitor);
|
||||||
}
|
}
|
||||||
_visitor.endVisit(*this);
|
_visitor.endVisit(*this);
|
||||||
}
|
}
|
||||||
@ -302,7 +303,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
|
|||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
if (m_overrides)
|
if (m_overrides)
|
||||||
m_overrides->accept(_visitor);
|
m_overrides->accept(_visitor);
|
||||||
m_body->accept(_visitor);
|
if (m_body)
|
||||||
|
m_body->accept(_visitor);
|
||||||
}
|
}
|
||||||
_visitor.endVisit(*this);
|
_visitor.endVisit(*this);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
#include <liblangutil/SourceLocation.h>
|
#include <liblangutil/SourceLocation.h>
|
||||||
#include <libyul/AsmDataForward.h>
|
#include <libyul/AsmDataForward.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -35,7 +37,7 @@ namespace solidity::frontend
|
|||||||
class AsmJsonImporter
|
class AsmJsonImporter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit AsmJsonImporter(std::string _sourceName) : m_sourceName(_sourceName) {}
|
explicit AsmJsonImporter(std::string _sourceName) : m_sourceName(std::move(_sourceName)) {}
|
||||||
yul::Block createBlock(Json::Value const& _node);
|
yul::Block createBlock(Json::Value const& _node);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
#include <boost/range/algorithm/copy.hpp>
|
#include <boost/range/algorithm/copy.hpp>
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
@ -1253,8 +1254,8 @@ StringLiteralType::StringLiteralType(Literal const& _literal):
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
StringLiteralType::StringLiteralType(string const& _value):
|
StringLiteralType::StringLiteralType(string _value):
|
||||||
m_value{_value}
|
m_value{std::move(_value)}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1648,12 +1649,50 @@ bool ArrayType::operator==(Type const& _other) const
|
|||||||
return isDynamicallySized() || length() == other.length();
|
return isDynamicallySized() || length() == other.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ArrayType::validForCalldata() const
|
BoolResult ArrayType::validForLocation(DataLocation _loc) const
|
||||||
{
|
{
|
||||||
if (auto arrayBaseType = dynamic_cast<ArrayType const*>(baseType()))
|
if (auto arrayBaseType = dynamic_cast<ArrayType const*>(baseType()))
|
||||||
if (!arrayBaseType->validForCalldata())
|
{
|
||||||
return false;
|
BoolResult result = arrayBaseType->validForLocation(_loc);
|
||||||
return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits<unsigned>::max();
|
if (!result)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (isDynamicallySized())
|
||||||
|
return true;
|
||||||
|
switch (_loc)
|
||||||
|
{
|
||||||
|
case DataLocation::Memory:
|
||||||
|
{
|
||||||
|
bigint size = bigint(length());
|
||||||
|
auto type = m_baseType;
|
||||||
|
while (auto arrayType = dynamic_cast<ArrayType const*>(type))
|
||||||
|
{
|
||||||
|
if (arrayType->isDynamicallySized())
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size *= arrayType->length();
|
||||||
|
type = arrayType->baseType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type->isDynamicallySized())
|
||||||
|
size *= type->memoryHeadSize();
|
||||||
|
else
|
||||||
|
size *= type->memoryDataSize();
|
||||||
|
if (size >= numeric_limits<unsigned>::max())
|
||||||
|
return BoolResult::err("Type too large for memory.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DataLocation::CallData:
|
||||||
|
{
|
||||||
|
if (unlimitedStaticCalldataSize(true) >= numeric_limits<unsigned>::max())
|
||||||
|
return BoolResult::err("Type too large for calldata.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DataLocation::Storage:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bigint ArrayType::unlimitedStaticCalldataSize(bool _padded) const
|
bigint ArrayType::unlimitedStaticCalldataSize(bool _padded) const
|
||||||
@ -2174,93 +2213,119 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
|||||||
|
|
||||||
TypeResult StructType::interfaceType(bool _inLibrary) const
|
TypeResult StructType::interfaceType(bool _inLibrary) const
|
||||||
{
|
{
|
||||||
if (_inLibrary && m_interfaceType_library.has_value())
|
if (!_inLibrary)
|
||||||
return *m_interfaceType_library;
|
{
|
||||||
|
if (!m_interfaceType.has_value())
|
||||||
if (!_inLibrary && m_interfaceType.has_value())
|
{
|
||||||
|
if (recursive())
|
||||||
|
m_interfaceType = TypeResult::err("Recursive type not allowed for public or external contract functions.");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TypeResult result{TypePointer{}};
|
||||||
|
for (ASTPointer<VariableDeclaration> const& member: m_struct.members())
|
||||||
|
{
|
||||||
|
if (!member->annotation().type)
|
||||||
|
{
|
||||||
|
result = TypeResult::err("Invalid type!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto interfaceType = member->annotation().type->interfaceType(false);
|
||||||
|
if (!interfaceType.get())
|
||||||
|
{
|
||||||
|
solAssert(!interfaceType.message().empty(), "Expected detailed error message!");
|
||||||
|
result = interfaceType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.message().empty())
|
||||||
|
m_interfaceType = TypeProvider::withLocation(this, DataLocation::Memory, true);
|
||||||
|
else
|
||||||
|
m_interfaceType = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
return *m_interfaceType;
|
return *m_interfaceType;
|
||||||
|
}
|
||||||
|
else if (m_interfaceType_library.has_value())
|
||||||
|
return *m_interfaceType_library;
|
||||||
|
|
||||||
TypeResult result{TypePointer{}};
|
TypeResult result{TypePointer{}};
|
||||||
|
|
||||||
m_recursive = false;
|
util::BreadthFirstSearch<StructDefinition const*> breadthFirstSearch{{&m_struct}};
|
||||||
|
breadthFirstSearch.run(
|
||||||
auto visitor = [&](
|
[&](StructDefinition const* _struct, auto&& _addChild) {
|
||||||
StructDefinition const& _struct,
|
// Check that all members have interface types.
|
||||||
util::CycleDetector<StructDefinition>& _cycleDetector,
|
// Return an error if at least one struct member does not have a type.
|
||||||
size_t /*_depth*/
|
// This might happen, for example, if the type of the member does not exist.
|
||||||
)
|
for (ASTPointer<VariableDeclaration> const& variable: _struct->members())
|
||||||
{
|
|
||||||
// Check that all members have interface types.
|
|
||||||
// Return an error if at least one struct member does not have a type.
|
|
||||||
// This might happen, for example, if the type of the member does not exist.
|
|
||||||
for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
|
|
||||||
{
|
|
||||||
// If the struct member does not have a type return false.
|
|
||||||
// A TypeError is expected in this case.
|
|
||||||
if (!variable->annotation().type)
|
|
||||||
{
|
|
||||||
result = TypeResult::err("Invalid type!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type const* memberType = variable->annotation().type;
|
|
||||||
|
|
||||||
while (dynamic_cast<ArrayType const*>(memberType))
|
|
||||||
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType();
|
|
||||||
|
|
||||||
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
|
|
||||||
if (
|
|
||||||
innerStruct->m_recursive == true ||
|
|
||||||
_cycleDetector.run(innerStruct->structDefinition())
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
m_recursive = true;
|
// If the struct member does not have a type return false.
|
||||||
if (_inLibrary && location() == DataLocation::Storage)
|
// A TypeError is expected in this case.
|
||||||
continue;
|
if (!variable->annotation().type)
|
||||||
else
|
|
||||||
{
|
{
|
||||||
result = TypeResult::err("Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions.");
|
result = TypeResult::err("Invalid type!");
|
||||||
|
breadthFirstSearch.abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto iType = memberType->interfaceType(_inLibrary);
|
Type const* memberType = variable->annotation().type;
|
||||||
if (!iType.get())
|
|
||||||
{
|
while (dynamic_cast<ArrayType const*>(memberType))
|
||||||
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType();
|
||||||
result = iType;
|
|
||||||
return;
|
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
|
||||||
|
{
|
||||||
|
if (innerStruct->recursive() && !(_inLibrary && location() == DataLocation::Storage))
|
||||||
|
{
|
||||||
|
result = TypeResult::err(
|
||||||
|
"Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions."
|
||||||
|
);
|
||||||
|
breadthFirstSearch.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_addChild(&innerStruct->structDefinition());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto iType = memberType->interfaceType(_inLibrary);
|
||||||
|
if (!iType.get())
|
||||||
|
{
|
||||||
|
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
||||||
|
result = iType;
|
||||||
|
breadthFirstSearch.abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
);
|
||||||
|
|
||||||
m_recursive = m_recursive.value() || (util::CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr);
|
if (!result.message().empty())
|
||||||
|
return result;
|
||||||
|
|
||||||
std::string const recursiveErrMsg = "Recursive type not allowed for public or external contract functions.";
|
if (location() == DataLocation::Storage)
|
||||||
|
m_interfaceType_library = this;
|
||||||
if (_inLibrary)
|
|
||||||
{
|
|
||||||
if (!result.message().empty())
|
|
||||||
m_interfaceType_library = result;
|
|
||||||
else if (location() == DataLocation::Storage)
|
|
||||||
m_interfaceType_library = this;
|
|
||||||
else
|
|
||||||
m_interfaceType_library = TypeProvider::withLocation(this, DataLocation::Memory, true);
|
|
||||||
|
|
||||||
if (m_recursive.value())
|
|
||||||
m_interfaceType = TypeResult::err(recursiveErrMsg);
|
|
||||||
|
|
||||||
return *m_interfaceType_library;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_recursive.value())
|
|
||||||
m_interfaceType = TypeResult::err(recursiveErrMsg);
|
|
||||||
else if (!result.message().empty())
|
|
||||||
m_interfaceType = result;
|
|
||||||
else
|
else
|
||||||
m_interfaceType = TypeProvider::withLocation(this, DataLocation::Memory, true);
|
m_interfaceType_library = TypeProvider::withLocation(this, DataLocation::Memory, true);
|
||||||
|
return *m_interfaceType_library;
|
||||||
|
}
|
||||||
|
|
||||||
return *m_interfaceType;
|
BoolResult StructType::validForLocation(DataLocation _loc) const
|
||||||
|
{
|
||||||
|
for (auto const& member: m_struct.members())
|
||||||
|
if (auto referenceType = dynamic_cast<ReferenceType const*>(member->annotation().type))
|
||||||
|
{
|
||||||
|
BoolResult result = referenceType->validForLocation(_loc);
|
||||||
|
if (!result)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StructType::recursive() const
|
||||||
|
{
|
||||||
|
solAssert(m_struct.annotation().recursive.has_value(), "Called StructType::recursive() before DeclarationTypeChecker.");
|
||||||
|
return *m_struct.annotation().recursive;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ReferenceType> StructType::copyForLocation(DataLocation _location, bool _isPointer) const
|
std::unique_ptr<ReferenceType> StructType::copyForLocation(DataLocation _location, bool _isPointer) const
|
||||||
@ -2643,21 +2708,11 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName):
|
|||||||
for (auto const& t: _typeName.parameterTypes())
|
for (auto const& t: _typeName.parameterTypes())
|
||||||
{
|
{
|
||||||
solAssert(t->annotation().type, "Type not set for parameter.");
|
solAssert(t->annotation().type, "Type not set for parameter.");
|
||||||
if (m_kind == Kind::External)
|
|
||||||
solAssert(
|
|
||||||
t->annotation().type->interfaceType(false).get(),
|
|
||||||
"Internal type used as parameter for external function."
|
|
||||||
);
|
|
||||||
m_parameterTypes.push_back(t->annotation().type);
|
m_parameterTypes.push_back(t->annotation().type);
|
||||||
}
|
}
|
||||||
for (auto const& t: _typeName.returnParameterTypes())
|
for (auto const& t: _typeName.returnParameterTypes())
|
||||||
{
|
{
|
||||||
solAssert(t->annotation().type, "Type not set for return parameter.");
|
solAssert(t->annotation().type, "Type not set for return parameter.");
|
||||||
if (m_kind == Kind::External)
|
|
||||||
solAssert(
|
|
||||||
t->annotation().type->interfaceType(false).get(),
|
|
||||||
"Internal type used as return parameter for external function."
|
|
||||||
);
|
|
||||||
m_returnParameterTypes.push_back(t->annotation().type);
|
m_returnParameterTypes.push_back(t->annotation().type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3719,7 +3774,9 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
|||||||
{"name", TypeProvider::stringMemory()},
|
{"name", TypeProvider::stringMemory()},
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
return {};
|
return MemberList::MemberMap({
|
||||||
|
{"interfaceId", TypeProvider::fixedBytes(4)},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
solAssert(false, "Unknown kind of magic.");
|
solAssert(false, "Unknown kind of magic.");
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
{
|
{
|
||||||
@ -92,8 +93,8 @@ class MemberList
|
|||||||
public:
|
public:
|
||||||
struct Member
|
struct Member
|
||||||
{
|
{
|
||||||
Member(std::string const& _name, Type const* _type, Declaration const* _declaration = nullptr):
|
Member(std::string _name, Type const* _type, Declaration const* _declaration = nullptr):
|
||||||
name(_name),
|
name(std::move(_name)),
|
||||||
type(_type),
|
type(_type),
|
||||||
declaration(_declaration)
|
declaration(_declaration)
|
||||||
{
|
{
|
||||||
@ -106,7 +107,7 @@ public:
|
|||||||
|
|
||||||
using MemberMap = std::vector<Member>;
|
using MemberMap = std::vector<Member>;
|
||||||
|
|
||||||
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {}
|
explicit MemberList(MemberMap _members): m_memberTypes(std::move(_members)) {}
|
||||||
|
|
||||||
void combine(MemberList const& _other);
|
void combine(MemberList const& _other);
|
||||||
TypePointer memberType(std::string const& _name) const
|
TypePointer memberType(std::string const& _name) const
|
||||||
@ -520,8 +521,8 @@ private:
|
|||||||
class RationalNumberType: public Type
|
class RationalNumberType: public Type
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit RationalNumberType(rational const& _value, Type const* _compatibleBytesType = nullptr):
|
explicit RationalNumberType(rational _value, Type const* _compatibleBytesType = nullptr):
|
||||||
m_value(_value), m_compatibleBytesType(_compatibleBytesType)
|
m_value(std::move(_value)), m_compatibleBytesType(_compatibleBytesType)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Category category() const override { return Category::RationalNumber; }
|
Category category() const override { return Category::RationalNumber; }
|
||||||
@ -543,7 +544,7 @@ public:
|
|||||||
|
|
||||||
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
|
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
|
||||||
IntegerType const* integerType() const;
|
IntegerType const* integerType() const;
|
||||||
/// @returns the smallest fixed type that can hold the value or incurs the least precision loss,
|
/// @returns the smallest fixed type that can hold the value or incurs the least precision loss,
|
||||||
/// unless the value was truncated, then a suitable type will be chosen to indicate such event.
|
/// unless the value was truncated, then a suitable type will be chosen to indicate such event.
|
||||||
/// If the integer part does not fit, returns an empty pointer.
|
/// If the integer part does not fit, returns an empty pointer.
|
||||||
FixedPointType const* fixedPointType() const;
|
FixedPointType const* fixedPointType() const;
|
||||||
@ -582,7 +583,7 @@ class StringLiteralType: public Type
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit StringLiteralType(Literal const& _literal);
|
explicit StringLiteralType(Literal const& _literal);
|
||||||
explicit StringLiteralType(std::string const& _value);
|
explicit StringLiteralType(std::string _value);
|
||||||
|
|
||||||
Category category() const override { return Category::StringLiteral; }
|
Category category() const override { return Category::StringLiteral; }
|
||||||
|
|
||||||
@ -705,6 +706,9 @@ public:
|
|||||||
/// never change the contents of the original value.
|
/// never change the contents of the original value.
|
||||||
bool isPointer() const;
|
bool isPointer() const;
|
||||||
|
|
||||||
|
/// @returns true if this is valid to be stored in data location _loc
|
||||||
|
virtual BoolResult validForLocation(DataLocation _loc) const = 0;
|
||||||
|
|
||||||
bool operator==(ReferenceType const& _other) const
|
bool operator==(ReferenceType const& _other) const
|
||||||
{
|
{
|
||||||
return location() == _other.location() && isPointer() == _other.isPointer();
|
return location() == _other.location() && isPointer() == _other.isPointer();
|
||||||
@ -744,11 +748,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Constructor for a fixed-size array type ("type[20]")
|
/// Constructor for a fixed-size array type ("type[20]")
|
||||||
ArrayType(DataLocation _location, Type const* _baseType, u256 const& _length):
|
ArrayType(DataLocation _location, Type const* _baseType, u256 _length):
|
||||||
ReferenceType(_location),
|
ReferenceType(_location),
|
||||||
m_baseType(copyForLocationIfReference(_baseType)),
|
m_baseType(copyForLocationIfReference(_baseType)),
|
||||||
m_hasDynamicLength(false),
|
m_hasDynamicLength(false),
|
||||||
m_length(_length)
|
m_length(std::move(_length))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Category category() const override { return Category::Array; }
|
Category category() const override { return Category::Array; }
|
||||||
@ -771,8 +775,7 @@ public:
|
|||||||
TypePointer decodingType() const override;
|
TypePointer decodingType() const override;
|
||||||
TypeResult interfaceType(bool _inLibrary) const override;
|
TypeResult interfaceType(bool _inLibrary) const override;
|
||||||
|
|
||||||
/// @returns true if this is valid to be stored in calldata
|
BoolResult validForLocation(DataLocation _loc) const override;
|
||||||
bool validForCalldata() const;
|
|
||||||
|
|
||||||
/// @returns true if this is a byte array or a string
|
/// @returns true if this is a byte array or a string
|
||||||
bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; }
|
bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; }
|
||||||
@ -826,8 +829,7 @@ public:
|
|||||||
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
|
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
/// @returns true if this is valid to be stored in calldata
|
BoolResult validForLocation(DataLocation _loc) const override { return m_arrayType.validForLocation(_loc); }
|
||||||
bool validForCalldata() const { return m_arrayType.validForCalldata(); }
|
|
||||||
|
|
||||||
ArrayType const& arrayType() const { return m_arrayType; }
|
ArrayType const& arrayType() const { return m_arrayType; }
|
||||||
u256 memoryDataSize() const override { solAssert(false, ""); }
|
u256 memoryDataSize() const override { solAssert(false, ""); }
|
||||||
@ -933,15 +935,9 @@ public:
|
|||||||
Type const* encodingType() const override;
|
Type const* encodingType() const override;
|
||||||
TypeResult interfaceType(bool _inLibrary) const override;
|
TypeResult interfaceType(bool _inLibrary) const override;
|
||||||
|
|
||||||
bool recursive() const
|
BoolResult validForLocation(DataLocation _loc) const override;
|
||||||
{
|
|
||||||
if (m_recursive.has_value())
|
|
||||||
return m_recursive.value();
|
|
||||||
|
|
||||||
interfaceType(false);
|
bool recursive() const;
|
||||||
|
|
||||||
return m_recursive.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
|
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||||
|
|
||||||
@ -970,7 +966,6 @@ private:
|
|||||||
// Caches for interfaceType(bool)
|
// Caches for interfaceType(bool)
|
||||||
mutable std::optional<TypeResult> m_interfaceType;
|
mutable std::optional<TypeResult> m_interfaceType;
|
||||||
mutable std::optional<TypeResult> m_interfaceType_library;
|
mutable std::optional<TypeResult> m_interfaceType_library;
|
||||||
mutable std::optional<bool> m_recursive;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1131,8 +1126,8 @@ public:
|
|||||||
|
|
||||||
/// Detailed constructor, use with care.
|
/// Detailed constructor, use with care.
|
||||||
FunctionType(
|
FunctionType(
|
||||||
TypePointers const& _parameterTypes,
|
TypePointers _parameterTypes,
|
||||||
TypePointers const& _returnParameterTypes,
|
TypePointers _returnParameterTypes,
|
||||||
strings _parameterNames = strings(),
|
strings _parameterNames = strings(),
|
||||||
strings _returnParameterNames = strings(),
|
strings _returnParameterNames = strings(),
|
||||||
Kind _kind = Kind::Internal,
|
Kind _kind = Kind::Internal,
|
||||||
@ -1144,10 +1139,10 @@ public:
|
|||||||
bool _saltSet = false,
|
bool _saltSet = false,
|
||||||
bool _bound = false
|
bool _bound = false
|
||||||
):
|
):
|
||||||
m_parameterTypes(_parameterTypes),
|
m_parameterTypes(std::move(_parameterTypes)),
|
||||||
m_returnParameterTypes(_returnParameterTypes),
|
m_returnParameterTypes(std::move(_returnParameterTypes)),
|
||||||
m_parameterNames(_parameterNames),
|
m_parameterNames(std::move(_parameterNames)),
|
||||||
m_returnParameterNames(_returnParameterNames),
|
m_returnParameterNames(std::move(_returnParameterNames)),
|
||||||
m_kind(_kind),
|
m_kind(_kind),
|
||||||
m_stateMutability(_stateMutability),
|
m_stateMutability(_stateMutability),
|
||||||
m_arbitraryParameters(_arbitraryParameters),
|
m_arbitraryParameters(_arbitraryParameters),
|
||||||
@ -1395,7 +1390,6 @@ public:
|
|||||||
std::string richIdentifier() const override;
|
std::string richIdentifier() const override;
|
||||||
bool operator==(Type const& _other) const override;
|
bool operator==(Type const& _other) const override;
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||||
private:
|
private:
|
||||||
|
@ -502,6 +502,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
|
|||||||
&meter,
|
&meter,
|
||||||
_object,
|
_object,
|
||||||
_optimiserSettings.optimizeStackAllocation,
|
_optimiserSettings.optimizeStackAllocation,
|
||||||
|
_optimiserSettings.yulOptimiserSteps,
|
||||||
_externalIdentifiers
|
_externalIdentifiers
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -733,7 +733,10 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
{
|
{
|
||||||
case Type::Category::Bool:
|
case Type::Category::Bool:
|
||||||
case Type::Category::Address:
|
case Type::Category::Address:
|
||||||
solAssert(*type == *variable->annotation().type, "");
|
// Either both the literal and the variable are bools, or they are both addresses.
|
||||||
|
// If they are both bools, comparing category is the same as comparing the types.
|
||||||
|
// If they are both addresses, compare category so that payable/nonpayable is not compared.
|
||||||
|
solAssert(type->category() == variable->annotation().type->category(), "");
|
||||||
value = type->literalValue(literal);
|
value = type->literalValue(literal);
|
||||||
break;
|
break;
|
||||||
case Type::Category::StringLiteral:
|
case Type::Category::StringLiteral:
|
||||||
@ -949,43 +952,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
|
|||||||
|
|
||||||
// Try to decode the error message.
|
// Try to decode the error message.
|
||||||
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
||||||
m_context << u256(0);
|
m_context.callYulFunction(m_context.utilFunctions().tryDecodeErrorMessageFunction(), 0, 1);
|
||||||
m_context.appendInlineAssembly(
|
|
||||||
util::Whiskers(R"({
|
|
||||||
data := mload(0x40)
|
|
||||||
mstore(data, 0)
|
|
||||||
for {} 1 {} {
|
|
||||||
if lt(returndatasize(), 0x44) { data := 0 break }
|
|
||||||
returndatacopy(0, 0, 4)
|
|
||||||
let sig := <getSig>
|
|
||||||
if iszero(eq(sig, 0x<ErrorSignature>)) { data := 0 break }
|
|
||||||
returndatacopy(data, 4, sub(returndatasize(), 4))
|
|
||||||
let offset := mload(data)
|
|
||||||
if or(
|
|
||||||
gt(offset, 0xffffffffffffffff),
|
|
||||||
gt(add(offset, 0x24), returndatasize())
|
|
||||||
) {
|
|
||||||
data := 0
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let msg := add(data, offset)
|
|
||||||
let length := mload(msg)
|
|
||||||
if gt(length, 0xffffffffffffffff) { data := 0 break }
|
|
||||||
let end := add(add(msg, 0x20), length)
|
|
||||||
if gt(end, add(data, returndatasize())) { data := 0 break }
|
|
||||||
mstore(0x40, and(add(end, 0x1f), not(0x1f)))
|
|
||||||
data := msg
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})")
|
|
||||||
("ErrorSignature", errorHash)
|
|
||||||
("getSig",
|
|
||||||
m_context.evmVersion().hasBitwiseShifting() ?
|
|
||||||
"shr(224, mload(0))" :
|
|
||||||
"div(mload(0), " + (u256(1) << 224).str() + ")"
|
|
||||||
).render(),
|
|
||||||
{"data"}
|
|
||||||
);
|
|
||||||
m_context << Instruction::DUP1;
|
m_context << Instruction::DUP1;
|
||||||
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
||||||
m_context << Instruction::POP;
|
m_context << Instruction::POP;
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
#include <libsolidity/codegen/ExpressionCompiler.h>
|
#include <libsolidity/codegen/ExpressionCompiler.h>
|
||||||
|
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/codegen/ReturnInfo.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
|
||||||
#include <libsolidity/codegen/CompilerContext.h>
|
#include <libsolidity/codegen/CompilerContext.h>
|
||||||
#include <libsolidity/codegen/CompilerUtils.h>
|
#include <libsolidity/codegen/CompilerUtils.h>
|
||||||
#include <libsolidity/codegen/LValue.h>
|
#include <libsolidity/codegen/LValue.h>
|
||||||
|
|
||||||
|
#include <libsolidity/ast/AST.h>
|
||||||
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
#include <libevmasm/GasMeter.h>
|
#include <libevmasm/GasMeter.h>
|
||||||
#include <libsolutil/Common.h>
|
#include <libsolutil/Common.h>
|
||||||
#include <libsolutil/Keccak256.h>
|
#include <libsolutil/Keccak256.h>
|
||||||
@ -346,15 +348,15 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
|||||||
if (component)
|
if (component)
|
||||||
{
|
{
|
||||||
component->accept(*this);
|
component->accept(*this);
|
||||||
if (_tuple.annotation().lValueRequested)
|
if (_tuple.annotation().willBeWrittenTo)
|
||||||
{
|
{
|
||||||
solAssert(!!m_currentLValue, "");
|
solAssert(!!m_currentLValue, "");
|
||||||
lvalues.push_back(move(m_currentLValue));
|
lvalues.push_back(move(m_currentLValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_tuple.annotation().lValueRequested)
|
else if (_tuple.annotation().willBeWrittenTo)
|
||||||
lvalues.push_back(unique_ptr<LValue>());
|
lvalues.push_back(unique_ptr<LValue>());
|
||||||
if (_tuple.annotation().lValueRequested)
|
if (_tuple.annotation().willBeWrittenTo)
|
||||||
{
|
{
|
||||||
if (_tuple.components().size() == 1)
|
if (_tuple.components().size() == 1)
|
||||||
m_currentLValue = move(lvalues[0]);
|
m_currentLValue = move(lvalues[0]);
|
||||||
@ -1580,6 +1582,15 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
|||||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||||
utils().storeStringData(contract.name());
|
utils().storeStringData(contract.name());
|
||||||
}
|
}
|
||||||
|
else if (member == "interfaceId")
|
||||||
|
{
|
||||||
|
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
|
||||||
|
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
|
||||||
|
uint64_t result{0};
|
||||||
|
for (auto const& function: contract.interfaceFunctionList(false))
|
||||||
|
result ^= fromBigEndian<uint64_t>(function.first.ref());
|
||||||
|
m_context << (u256{result} << (256 - 32));
|
||||||
|
}
|
||||||
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
|
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
|
||||||
{
|
{
|
||||||
// no-op
|
// no-op
|
||||||
@ -2185,30 +2196,11 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
|||||||
solAssert(!_functionType.isBareCall(), "");
|
solAssert(!_functionType.isBareCall(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
ReturnInfo const returnInfo{m_context.evmVersion(), _functionType};
|
||||||
unsigned retSize = 0;
|
bool const haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||||
bool dynamicReturnSize = false;
|
unsigned const retSize = returnInfo.estimatedReturnSize;
|
||||||
TypePointers returnTypes;
|
bool const dynamicReturnSize = returnInfo.dynamicReturnSize;
|
||||||
if (!returnSuccessConditionAndReturndata)
|
TypePointers const& returnTypes = returnInfo.returnTypes;
|
||||||
{
|
|
||||||
if (haveReturndatacopy)
|
|
||||||
returnTypes = _functionType.returnParameterTypes();
|
|
||||||
else
|
|
||||||
returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
|
|
||||||
|
|
||||||
for (auto const& retType: returnTypes)
|
|
||||||
if (retType->isDynamicallyEncoded())
|
|
||||||
{
|
|
||||||
solAssert(haveReturndatacopy, "");
|
|
||||||
dynamicReturnSize = true;
|
|
||||||
retSize = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (retType->decodingType())
|
|
||||||
retSize += retType->decodingType()->calldataEncodedSize();
|
|
||||||
else
|
|
||||||
retSize += retType->calldataEncodedSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate arguments.
|
// Evaluate arguments.
|
||||||
TypePointers argumentTypes;
|
TypePointers argumentTypes;
|
||||||
|
@ -148,7 +148,7 @@ void ExpressionCompiler::setLValue(Expression const& _expression, Arguments cons
|
|||||||
{
|
{
|
||||||
solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one.");
|
solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one.");
|
||||||
std::unique_ptr<LValueType> lvalue = std::make_unique<LValueType>(m_context, _arguments...);
|
std::unique_ptr<LValueType> lvalue = std::make_unique<LValueType>(m_context, _arguments...);
|
||||||
if (_expression.annotation().lValueRequested)
|
if (_expression.annotation().willBeWrittenTo)
|
||||||
m_currentLValue = move(lvalue);
|
m_currentLValue = move(lvalue);
|
||||||
else
|
else
|
||||||
lvalue->retrieveValue(_expression.location(), true);
|
lvalue->retrieveValue(_expression.location(), true);
|
||||||
|
@ -34,6 +34,7 @@ string MultiUseYulFunctionCollector::requestedFunctions()
|
|||||||
{
|
{
|
||||||
string result;
|
string result;
|
||||||
for (auto const& f: m_requestedFunctions)
|
for (auto const& f: m_requestedFunctions)
|
||||||
|
// std::map guarantees ascending order when iterating through its keys.
|
||||||
result += f.second;
|
result += f.second;
|
||||||
m_requestedFunctions.clear();
|
m_requestedFunctions.clear();
|
||||||
return result;
|
return result;
|
||||||
|
@ -41,10 +41,15 @@ public:
|
|||||||
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
||||||
|
|
||||||
/// @returns concatenation of all generated functions.
|
/// @returns concatenation of all generated functions.
|
||||||
|
/// Guarantees that the order of functions in the generated code is deterministic and
|
||||||
|
/// platform-independent.
|
||||||
/// Clears the internal list, i.e. calling it again will result in an
|
/// Clears the internal list, i.e. calling it again will result in an
|
||||||
/// empty return value.
|
/// empty return value.
|
||||||
std::string requestedFunctions();
|
std::string requestedFunctions();
|
||||||
|
|
||||||
|
/// @returns true IFF a function with the specified name has already been collected.
|
||||||
|
bool contains(std::string const& _name) const { return m_requestedFunctions.count(_name) > 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Map from function name to code for a multi-use function.
|
/// Map from function name to code for a multi-use function.
|
||||||
std::map<std::string, std::string> m_requestedFunctions;
|
std::map<std::string, std::string> m_requestedFunctions;
|
||||||
|
55
libsolidity/codegen/ReturnInfo.cpp
Normal file
55
libsolidity/codegen/ReturnInfo.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
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/codegen/ReturnInfo.h>
|
||||||
|
|
||||||
|
#include <libsolidity/ast/Types.h>
|
||||||
|
#include <libsolidity/ast/AST.h>
|
||||||
|
|
||||||
|
using namespace solidity::frontend;
|
||||||
|
using namespace solidity::langutil;
|
||||||
|
|
||||||
|
ReturnInfo::ReturnInfo(EVMVersion const& _evmVersion, FunctionType const& _functionType)
|
||||||
|
{
|
||||||
|
FunctionType::Kind const funKind = _functionType.kind();
|
||||||
|
bool const haveReturndatacopy = _evmVersion.supportsReturndata();
|
||||||
|
bool const returnSuccessConditionAndReturndata =
|
||||||
|
funKind == FunctionType::Kind::BareCall ||
|
||||||
|
funKind == FunctionType::Kind::BareDelegateCall ||
|
||||||
|
funKind == FunctionType::Kind::BareStaticCall;
|
||||||
|
|
||||||
|
if (!returnSuccessConditionAndReturndata)
|
||||||
|
{
|
||||||
|
if (haveReturndatacopy)
|
||||||
|
returnTypes = _functionType.returnParameterTypes();
|
||||||
|
else
|
||||||
|
returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
|
||||||
|
|
||||||
|
for (auto const& retType: returnTypes)
|
||||||
|
if (retType->isDynamicallyEncoded())
|
||||||
|
{
|
||||||
|
solAssert(haveReturndatacopy, "");
|
||||||
|
dynamicReturnSize = true;
|
||||||
|
estimatedReturnSize = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (retType->decodingType())
|
||||||
|
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
|
||||||
|
else
|
||||||
|
estimatedReturnSize += retType->calldataEncodedSize();
|
||||||
|
}
|
||||||
|
}
|
47
libsolidity/codegen/ReturnInfo.h
Normal file
47
libsolidity/codegen/ReturnInfo.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Component that computes information relevant during decoding an external function
|
||||||
|
* call's return values.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
#include <libsolidity/ast/Types.h>
|
||||||
|
|
||||||
|
namespace solidity::frontend
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes and holds information relevant during decoding an external function
|
||||||
|
* call's return values.
|
||||||
|
*/
|
||||||
|
struct ReturnInfo
|
||||||
|
{
|
||||||
|
ReturnInfo(langutil::EVMVersion const& _evmVersion, FunctionType const& _functionType);
|
||||||
|
|
||||||
|
/// Vector of TypePointer, for each return variable. Dynamic types are already replaced if required.
|
||||||
|
TypePointers returnTypes = {};
|
||||||
|
|
||||||
|
/// Boolean, indicating whether or not return size is only known at runtime.
|
||||||
|
bool dynamicReturnSize = false;
|
||||||
|
|
||||||
|
/// Contains the at compile time estimated return size.
|
||||||
|
unsigned estimatedReturnSize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -1337,6 +1337,34 @@ string YulUtilFunctions::allocationFunction()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string YulUtilFunctions::allocationTemporaryMemoryFunction()
|
||||||
|
{
|
||||||
|
string functionName = "allocateTemporaryMemory";
|
||||||
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
|
return Whiskers(R"(
|
||||||
|
function <functionName>() -> memPtr {
|
||||||
|
memPtr := mload(<freeMemoryPointer>)
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||||
|
("functionName", functionName)
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
string YulUtilFunctions::releaseTemporaryMemoryFunction()
|
||||||
|
{
|
||||||
|
string functionName = "releaseTemporaryMemory";
|
||||||
|
return m_functionCollector.createFunction(functionName, [&](){
|
||||||
|
return Whiskers(R"(
|
||||||
|
function <functionName>() {
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
|
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
if (_type.baseType()->hasSimpleZeroValueInMemory())
|
if (_type.baseType()->hasSimpleZeroValueInMemory())
|
||||||
@ -2079,7 +2107,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
|||||||
{
|
{
|
||||||
conversions +=
|
conversions +=
|
||||||
suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) +
|
suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) +
|
||||||
" := " +
|
(toComponent->sizeOnStack() > 0 ? " := " : "") +
|
||||||
conversionFunction(*fromComponent, *toComponent) +
|
conversionFunction(*fromComponent, *toComponent) +
|
||||||
"(" +
|
"(" +
|
||||||
suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) +
|
suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) +
|
||||||
@ -2089,12 +2117,13 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
|||||||
sourceStackSize += fromComponent->sizeOnStack();
|
sourceStackSize += fromComponent->sizeOnStack();
|
||||||
}
|
}
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(<values>) -> <converted> {
|
function <functionName>(<values>) <arrow> <converted> {
|
||||||
<conversions>
|
<conversions>
|
||||||
}
|
}
|
||||||
)")
|
)")
|
||||||
("functionName", functionName)
|
("functionName", functionName)
|
||||||
("values", suffixedVariableNameList("value", 0, sourceStackSize))
|
("values", suffixedVariableNameList("value", 0, sourceStackSize))
|
||||||
|
("arrow", destStackSize > 0 ? "->" : "")
|
||||||
("converted", suffixedVariableNameList("converted", 0, destStackSize))
|
("converted", suffixedVariableNameList("converted", 0, destStackSize))
|
||||||
("conversions", conversions)
|
("conversions", conversions)
|
||||||
.render();
|
.render();
|
||||||
@ -2252,3 +2281,120 @@ string YulUtilFunctions::revertReasonIfDebug(string const& _message)
|
|||||||
{
|
{
|
||||||
return revertReasonIfDebug(m_revertStrings, _message);
|
return revertReasonIfDebug(m_revertStrings, _message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string YulUtilFunctions::tryDecodeErrorMessageFunction()
|
||||||
|
{
|
||||||
|
string const functionName = "try_decode_error_message";
|
||||||
|
|
||||||
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
|
return util::Whiskers(R"(
|
||||||
|
function <functionName>() -> ret {
|
||||||
|
if lt(returndatasize(), 0x44) { leave }
|
||||||
|
|
||||||
|
returndatacopy(0, 0, 4)
|
||||||
|
let sig := <shr224>(mload(0))
|
||||||
|
if iszero(eq(sig, 0x<ErrorSignature>)) { leave }
|
||||||
|
|
||||||
|
let data := mload(<freeMemoryPointer>)
|
||||||
|
returndatacopy(data, 4, sub(returndatasize(), 4))
|
||||||
|
|
||||||
|
let offset := mload(data)
|
||||||
|
if or(
|
||||||
|
gt(offset, 0xffffffffffffffff),
|
||||||
|
gt(add(offset, 0x24), returndatasize())
|
||||||
|
) {
|
||||||
|
leave
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg := add(data, offset)
|
||||||
|
let length := mload(msg)
|
||||||
|
if gt(length, 0xffffffffffffffff) { leave }
|
||||||
|
|
||||||
|
let end := add(add(msg, 0x20), length)
|
||||||
|
if gt(end, add(data, returndatasize())) { leave }
|
||||||
|
|
||||||
|
mstore(<freeMemoryPointer>, add(add(msg, 0x20), <roundUp>(length)))
|
||||||
|
ret := msg
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
("shr224", shiftRightFunction(224))
|
||||||
|
("ErrorSignature", FixedHash<4>(util::keccak256("Error(string)")).hex())
|
||||||
|
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||||
|
("roundUp", roundUpFunction())
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
string YulUtilFunctions::extractReturndataFunction()
|
||||||
|
{
|
||||||
|
string const functionName = "extract_returndata";
|
||||||
|
|
||||||
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
|
return util::Whiskers(R"(
|
||||||
|
function <functionName>() -> data {
|
||||||
|
<?supportsReturndata>
|
||||||
|
switch returndatasize()
|
||||||
|
case 0 {
|
||||||
|
data := <emptyArray>()
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
// allocate some memory into data of size returndatasize() + PADDING
|
||||||
|
data := <allocate>(<roundUp>(add(returndatasize(), 0x20)))
|
||||||
|
|
||||||
|
// store array length into the front
|
||||||
|
mstore(data, returndatasize())
|
||||||
|
|
||||||
|
// append to data
|
||||||
|
returndatacopy(add(data, 0x20), 0, returndatasize())
|
||||||
|
}
|
||||||
|
<!supportsReturndata>
|
||||||
|
data := <emptyArray>()
|
||||||
|
</supportsReturndata>
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
("supportsReturndata", m_evmVersion.supportsReturndata())
|
||||||
|
("allocate", allocationFunction())
|
||||||
|
("roundUp", roundUpFunction())
|
||||||
|
("emptyArray", zeroValueFunction(*TypeProvider::bytesMemory()))
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction(
|
||||||
|
ContractDefinition const& _contract,
|
||||||
|
string const& _creationObjectName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string functionName = "copy_arguments_for_constructor_" +
|
||||||
|
toString(_contract.constructor()->id()) +
|
||||||
|
"_object_" +
|
||||||
|
_contract.name() +
|
||||||
|
"_" +
|
||||||
|
toString(_contract.id());
|
||||||
|
|
||||||
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
|
string returnParams = suffixedVariableNameList("ret_param_",0, _contract.constructor()->parameters().size());
|
||||||
|
ABIFunctions abiFunctions(m_evmVersion, m_revertStrings, m_functionCollector);
|
||||||
|
|
||||||
|
return util::Whiskers(R"(
|
||||||
|
function <functionName>() -> <retParams> {
|
||||||
|
let programSize := datasize("<object>")
|
||||||
|
let argSize := sub(codesize(), programSize)
|
||||||
|
|
||||||
|
let memoryDataOffset := <allocate>(argSize)
|
||||||
|
codecopy(memoryDataOffset, programSize, argSize)
|
||||||
|
|
||||||
|
<retParams> := <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
("retParams", returnParams)
|
||||||
|
("object", _creationObjectName)
|
||||||
|
("allocate", allocationFunction())
|
||||||
|
("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true))
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <liblangutil/EVMVersion.h>
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
|
||||||
|
#include <libsolidity/ast/Types.h>
|
||||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||||
|
|
||||||
#include <libsolidity/interface/DebugSettings.h>
|
#include <libsolidity/interface/DebugSettings.h>
|
||||||
@ -252,6 +253,13 @@ public:
|
|||||||
/// Return value: pointer
|
/// Return value: pointer
|
||||||
std::string allocationFunction();
|
std::string allocationFunction();
|
||||||
|
|
||||||
|
/// @returns the name of the function that allocates temporary memory with predefined size
|
||||||
|
/// Return value: pointer
|
||||||
|
std::string allocationTemporaryMemoryFunction();
|
||||||
|
|
||||||
|
/// @returns the name of the function that releases previously allocated temporary memory
|
||||||
|
std::string releaseTemporaryMemoryFunction();
|
||||||
|
|
||||||
/// @returns the name of a function that zeroes an array.
|
/// @returns the name of a function that zeroes an array.
|
||||||
/// signature: (dataStart, dataSizeInBytes) ->
|
/// signature: (dataStart, dataSizeInBytes) ->
|
||||||
std::string zeroMemoryArrayFunction(ArrayType const& _type);
|
std::string zeroMemoryArrayFunction(ArrayType const& _type);
|
||||||
@ -322,6 +330,27 @@ public:
|
|||||||
static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = "");
|
static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = "");
|
||||||
|
|
||||||
std::string revertReasonIfDebug(std::string const& _message = "");
|
std::string revertReasonIfDebug(std::string const& _message = "");
|
||||||
|
|
||||||
|
/// Returns the name of a function that decodes an error message.
|
||||||
|
/// signature: () -> arrayPtr
|
||||||
|
///
|
||||||
|
/// Returns a newly allocated `bytes memory` array containing the decoded error message
|
||||||
|
/// or 0 on failure.
|
||||||
|
std::string tryDecodeErrorMessageFunction();
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns a function name that returns a newly allocated `bytes` array that contains the return data.
|
||||||
|
///
|
||||||
|
/// If returndatacopy() is not supported by the underlying target, a empty array will be returned instead.
|
||||||
|
std::string extractReturndataFunction();
|
||||||
|
|
||||||
|
/// @returns function name that returns constructor arguments copied to memory
|
||||||
|
/// signature: () -> arguments
|
||||||
|
std::string copyConstructorArgumentsToMemoryFunction(
|
||||||
|
ContractDefinition const& _contract,
|
||||||
|
std::string const& _creationObjectName
|
||||||
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Special case of conversionFunction - handles everything that does not
|
/// Special case of conversionFunction - handles everything that does not
|
||||||
/// use exactly one variable to hold the value.
|
/// use exactly one variable to hold the value.
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <libsolidity/codegen/ir/IRGenerationContext.h>
|
#include <libsolidity/codegen/ir/IRGenerationContext.h>
|
||||||
|
|
||||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||||
|
#include <libsolidity/codegen/ABIFunctions.h>
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
@ -32,6 +33,25 @@ using namespace solidity;
|
|||||||
using namespace solidity::util;
|
using namespace solidity::util;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
|
string IRGenerationContext::enqueueFunctionForCodeGeneration(FunctionDefinition const& _function)
|
||||||
|
{
|
||||||
|
string name = functionName(_function);
|
||||||
|
|
||||||
|
if (!m_functions.contains(name))
|
||||||
|
m_functionGenerationQueue.insert(&_function);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionDefinition const* IRGenerationContext::dequeueFunctionForCodeGeneration()
|
||||||
|
{
|
||||||
|
solAssert(!m_functionGenerationQueue.empty(), "");
|
||||||
|
|
||||||
|
FunctionDefinition const* result = *m_functionGenerationQueue.begin();
|
||||||
|
m_functionGenerationQueue.erase(m_functionGenerationQueue.begin());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
ContractDefinition const& IRGenerationContext::mostDerivedContract() const
|
ContractDefinition const& IRGenerationContext::mostDerivedContract() const
|
||||||
{
|
{
|
||||||
solAssert(m_mostDerivedContract, "Most derived contract requested but not set.");
|
solAssert(m_mostDerivedContract, "Most derived contract requested but not set.");
|
||||||
@ -77,9 +97,13 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
|
|||||||
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
|
string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const
|
||||||
{
|
{
|
||||||
return functionName(_functionDeclaration.resolveVirtual(mostDerivedContract()));
|
return _contract.name() + "_" + toString(_contract.id());
|
||||||
|
}
|
||||||
|
string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const
|
||||||
|
{
|
||||||
|
return _contract.name() + "_" + toString(_contract.id()) + "_deployed";
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGenerationContext::newYulVariable()
|
string IRGenerationContext::newYulVariable()
|
||||||
@ -87,6 +111,17 @@ string IRGenerationContext::newYulVariable()
|
|||||||
return "_" + to_string(++m_varCounter);
|
return "_" + to_string(++m_varCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string IRGenerationContext::trySuccessConditionVariable(Expression const& _expression) const
|
||||||
|
{
|
||||||
|
// NB: The TypeChecker already ensured that the Expression is of type FunctionCall.
|
||||||
|
solAssert(
|
||||||
|
static_cast<FunctionCallAnnotation const&>(_expression.annotation()).tryCall,
|
||||||
|
"Parameter must be a FunctionCall with tryCall-annotation set."
|
||||||
|
);
|
||||||
|
|
||||||
|
return "trySuccessCondition_" + to_string(_expression.id());
|
||||||
|
}
|
||||||
|
|
||||||
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||||
{
|
{
|
||||||
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
||||||
@ -97,7 +132,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
|||||||
<#cases>
|
<#cases>
|
||||||
case <funID>
|
case <funID>
|
||||||
{
|
{
|
||||||
<out> := <name>(<in>)
|
<out> <assignment_op> <name>(<in>)
|
||||||
}
|
}
|
||||||
</cases>
|
</cases>
|
||||||
default { invalid() }
|
default { invalid() }
|
||||||
@ -108,7 +143,13 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
|||||||
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
|
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
|
||||||
templ("in", suffixedVariableNameList("in_", 0, _in));
|
templ("in", suffixedVariableNameList("in_", 0, _in));
|
||||||
templ("arrow", _out > 0 ? "->" : "");
|
templ("arrow", _out > 0 ? "->" : "");
|
||||||
|
templ("assignment_op", _out > 0 ? ":=" : "");
|
||||||
templ("out", suffixedVariableNameList("out_", 0, _out));
|
templ("out", suffixedVariableNameList("out_", 0, _out));
|
||||||
|
|
||||||
|
// UNIMPLEMENTED: Internal library calls via pointers are not implemented yet.
|
||||||
|
// We're not generating code for internal library functions here even though it's possible
|
||||||
|
// to call them via pointers. Right now such calls end up triggering the `default` case in
|
||||||
|
// the switch above.
|
||||||
vector<map<string, string>> functions;
|
vector<map<string, string>> functions;
|
||||||
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
|
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
|
||||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||||
@ -126,6 +167,8 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
|||||||
{ "funID", to_string(function->id()) },
|
{ "funID", to_string(function->id()) },
|
||||||
{ "name", functionName(*function)}
|
{ "name", functionName(*function)}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
enqueueFunctionForCodeGeneration(*function);
|
||||||
}
|
}
|
||||||
templ("cases", move(functions));
|
templ("cases", move(functions));
|
||||||
return templ.render();
|
return templ.render();
|
||||||
@ -137,7 +180,13 @@ YulUtilFunctions IRGenerationContext::utils()
|
|||||||
return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
|
return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ABIFunctions IRGenerationContext::abiFunctions()
|
||||||
|
{
|
||||||
|
return ABIFunctions(m_evmVersion, m_revertStrings, m_functions);
|
||||||
|
}
|
||||||
|
|
||||||
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
|
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
|
||||||
{
|
{
|
||||||
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
|
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||||
#include <libsolidity/interface/OptimiserSettings.h>
|
#include <libsolidity/interface/OptimiserSettings.h>
|
||||||
#include <libsolidity/interface/DebugSettings.h>
|
#include <libsolidity/interface/DebugSettings.h>
|
||||||
@ -30,6 +31,7 @@
|
|||||||
|
|
||||||
#include <libsolutil/Common.h>
|
#include <libsolutil/Common.h>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -37,11 +39,8 @@
|
|||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
{
|
{
|
||||||
|
|
||||||
class ContractDefinition;
|
|
||||||
class VariableDeclaration;
|
|
||||||
class FunctionDefinition;
|
|
||||||
class Expression;
|
|
||||||
class YulUtilFunctions;
|
class YulUtilFunctions;
|
||||||
|
class ABIFunctions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that contains contextual information during IR generation.
|
* Class that contains contextual information during IR generation.
|
||||||
@ -61,6 +60,15 @@ public:
|
|||||||
|
|
||||||
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
|
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
|
||||||
|
|
||||||
|
/// Adds a Solidity function to the function generation queue and returns the name of the
|
||||||
|
/// corresponding Yul function.
|
||||||
|
std::string enqueueFunctionForCodeGeneration(FunctionDefinition const& _function);
|
||||||
|
|
||||||
|
/// Pops one item from the function generation queue. Must not be called if the queue is empty.
|
||||||
|
FunctionDefinition const* dequeueFunctionForCodeGeneration();
|
||||||
|
|
||||||
|
bool functionGenerationQueueEmpty() { return m_functionGenerationQueue.empty(); }
|
||||||
|
|
||||||
/// Sets the most derived contract (the one currently being compiled)>
|
/// Sets the most derived contract (the one currently being compiled)>
|
||||||
void setMostDerivedContract(ContractDefinition const& _mostDerivedContract)
|
void setMostDerivedContract(ContractDefinition const& _mostDerivedContract)
|
||||||
{
|
{
|
||||||
@ -82,7 +90,9 @@ public:
|
|||||||
|
|
||||||
std::string functionName(FunctionDefinition const& _function);
|
std::string functionName(FunctionDefinition const& _function);
|
||||||
std::string functionName(VariableDeclaration const& _varDecl);
|
std::string functionName(VariableDeclaration const& _varDecl);
|
||||||
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
|
|
||||||
|
std::string creationObjectName(ContractDefinition const& _contract) const;
|
||||||
|
std::string runtimeObjectName(ContractDefinition const& _contract) const;
|
||||||
|
|
||||||
std::string newYulVariable();
|
std::string newYulVariable();
|
||||||
|
|
||||||
@ -93,12 +103,20 @@ public:
|
|||||||
|
|
||||||
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
|
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
|
||||||
|
|
||||||
|
ABIFunctions abiFunctions();
|
||||||
|
|
||||||
/// @returns code that stores @param _message for revert reason
|
/// @returns code that stores @param _message for revert reason
|
||||||
/// if m_revertStrings is debug.
|
/// if m_revertStrings is debug.
|
||||||
std::string revertReasonIfDebug(std::string const& _message = "");
|
std::string revertReasonIfDebug(std::string const& _message = "");
|
||||||
|
|
||||||
RevertStrings revertStrings() const { return m_revertStrings; }
|
RevertStrings revertStrings() const { return m_revertStrings; }
|
||||||
|
|
||||||
|
/// @returns the variable name that can be used to inspect the success or failure of an external
|
||||||
|
/// function call that was invoked as part of the try statement.
|
||||||
|
std::string trySuccessConditionVariable(Expression const& _expression) const;
|
||||||
|
|
||||||
|
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
RevertStrings m_revertStrings;
|
RevertStrings m_revertStrings;
|
||||||
@ -109,6 +127,17 @@ private:
|
|||||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||||
MultiUseYulFunctionCollector m_functions;
|
MultiUseYulFunctionCollector m_functions;
|
||||||
size_t m_varCounter = 0;
|
size_t m_varCounter = 0;
|
||||||
|
|
||||||
|
/// Function definitions queued for code generation. They're the Solidity functions whose calls
|
||||||
|
/// were discovered by the IR generator during AST traversal.
|
||||||
|
/// Note that the queue gets filled in a lazy way - new definitions can be added while the
|
||||||
|
/// collected ones get removed and traversed.
|
||||||
|
/// The order and duplicates are irrelevant here (hence std::set rather than std::queue) as
|
||||||
|
/// long as the order of Yul functions in the generated code is deterministic and the same on
|
||||||
|
/// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector.
|
||||||
|
std::set<FunctionDefinition const*> m_functionGenerationQueue;
|
||||||
|
|
||||||
|
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -48,9 +48,12 @@ using namespace solidity;
|
|||||||
using namespace solidity::util;
|
using namespace solidity::util;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
|
pair<string, string> IRGenerator::run(
|
||||||
|
ContractDefinition const& _contract,
|
||||||
|
map<ContractDefinition const*, string const> const& _otherYulSources
|
||||||
|
)
|
||||||
{
|
{
|
||||||
string const ir = yul::reindent(generate(_contract));
|
string const ir = yul::reindent(generate(_contract, _otherYulSources));
|
||||||
|
|
||||||
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||||
if (!asmStack.parseAndAnalyze("", ir))
|
if (!asmStack.parseAndAnalyze("", ir))
|
||||||
@ -73,15 +76,28 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
|
|||||||
return {warning + ir, warning + asmStack.print()};
|
return {warning + ir, warning + asmStack.print()};
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGenerator::generate(ContractDefinition const& _contract)
|
string IRGenerator::generate(
|
||||||
|
ContractDefinition const& _contract,
|
||||||
|
map<ContractDefinition const*, string const> const& _otherYulSources
|
||||||
|
)
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
|
auto subObjectSources = [&_otherYulSources](std::set<ContractDefinition const*, ASTNode::CompareByID> const& subObjects) -> string
|
||||||
|
{
|
||||||
|
std::string subObjectsSources;
|
||||||
|
for (ContractDefinition const* subObject: subObjects)
|
||||||
|
subObjectsSources += _otherYulSources.at(subObject);
|
||||||
|
return subObjectsSources;
|
||||||
|
};
|
||||||
|
|
||||||
Whiskers t(R"(
|
Whiskers t(R"(
|
||||||
object "<CreationObject>" {
|
object "<CreationObject>" {
|
||||||
code {
|
code {
|
||||||
<memoryInit>
|
<memoryInit>
|
||||||
<constructor>
|
<callValueCheck>
|
||||||
|
<?notLibrary>
|
||||||
|
<?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams>
|
||||||
|
<implicitConstructor>(<constructorParams>)
|
||||||
|
</notLibrary>
|
||||||
<deploy>
|
<deploy>
|
||||||
<functions>
|
<functions>
|
||||||
}
|
}
|
||||||
@ -91,32 +107,46 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
|||||||
<dispatch>
|
<dispatch>
|
||||||
<runtimeFunctions>
|
<runtimeFunctions>
|
||||||
}
|
}
|
||||||
|
<runtimeSubObjects>
|
||||||
}
|
}
|
||||||
|
<subObjects>
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
|
|
||||||
resetContext(_contract);
|
resetContext(_contract);
|
||||||
|
|
||||||
t("CreationObject", creationObjectName(_contract));
|
t("CreationObject", m_context.creationObjectName(_contract));
|
||||||
t("memoryInit", memoryInit());
|
t("memoryInit", memoryInit());
|
||||||
t("constructor", constructorCode(_contract));
|
t("notLibrary", !_contract.isLibrary());
|
||||||
|
|
||||||
|
FunctionDefinition const* constructor = _contract.constructor();
|
||||||
|
t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : "");
|
||||||
|
vector<string> constructorParams;
|
||||||
|
if (constructor && !constructor->parameters().empty())
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < constructor->parameters().size(); ++i)
|
||||||
|
constructorParams.emplace_back(m_context.newYulVariable());
|
||||||
|
t(
|
||||||
|
"copyConstructorArguments",
|
||||||
|
m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
t("constructorParams", joinHumanReadable(constructorParams));
|
||||||
|
t("constructorHasParams", !constructorParams.empty());
|
||||||
|
t("implicitConstructor", implicitConstructorName(_contract));
|
||||||
|
|
||||||
t("deploy", deployCode(_contract));
|
t("deploy", deployCode(_contract));
|
||||||
// We generate code for all functions and rely on the optimizer to remove them again
|
generateImplicitConstructors(_contract);
|
||||||
// TODO it would probably be better to only generate functions when internalDispatch or
|
generateQueuedFunctions();
|
||||||
// virtualFunctionName is called - same below.
|
|
||||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
|
||||||
for (auto const* fun: contract->definedFunctions())
|
|
||||||
generateFunction(*fun);
|
|
||||||
t("functions", m_context.functionCollector().requestedFunctions());
|
t("functions", m_context.functionCollector().requestedFunctions());
|
||||||
|
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||||
|
|
||||||
resetContext(_contract);
|
resetContext(_contract);
|
||||||
m_context.setMostDerivedContract(_contract);
|
t("RuntimeObject", m_context.runtimeObjectName(_contract));
|
||||||
t("RuntimeObject", runtimeObjectName(_contract));
|
|
||||||
t("dispatch", dispatchRoutine(_contract));
|
t("dispatch", dispatchRoutine(_contract));
|
||||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
generateQueuedFunctions();
|
||||||
for (auto const* fun: contract->definedFunctions())
|
|
||||||
generateFunction(*fun);
|
|
||||||
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
||||||
|
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||||
return t.render();
|
return t.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +157,13 @@ string IRGenerator::generate(Block const& _block)
|
|||||||
return generator.code();
|
return generator.code();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IRGenerator::generateQueuedFunctions()
|
||||||
|
{
|
||||||
|
while (!m_context.functionGenerationQueueEmpty())
|
||||||
|
// NOTE: generateFunction() may modify function generation queue
|
||||||
|
generateFunction(*m_context.dequeueFunctionForCodeGeneration());
|
||||||
|
}
|
||||||
|
|
||||||
string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
string functionName = m_context.functionName(_function);
|
string functionName = m_context.functionName(_function);
|
||||||
@ -239,64 +276,116 @@ string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDec
|
|||||||
return generator.code();
|
return generator.code();
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGenerator::constructorCode(ContractDefinition const& _contract)
|
pair<string, map<ContractDefinition const*, string>> IRGenerator::evaluateConstructorArguments(
|
||||||
|
ContractDefinition const& _contract
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// Initialization of state variables in base-to-derived order.
|
map<ContractDefinition const*, string> constructorParams;
|
||||||
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
|
vector<pair<ContractDefinition const*, std::vector<ASTPointer<Expression>>const *>> baseConstructorArguments;
|
||||||
|
|
||||||
using boost::adaptors::reverse;
|
for (ASTPointer<InheritanceSpecifier> const& base: _contract.baseContracts())
|
||||||
|
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
|
||||||
|
base->name().annotation().referencedDeclaration
|
||||||
|
)->constructor(); baseConstructor && base->arguments())
|
||||||
|
baseConstructorArguments.emplace_back(
|
||||||
|
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
|
||||||
|
base->arguments()
|
||||||
|
);
|
||||||
|
|
||||||
ostringstream out;
|
if (FunctionDefinition const* constructor = _contract.constructor())
|
||||||
|
for (auto const& modifier: constructor->modifiers())
|
||||||
|
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
|
||||||
|
modifier->name()->annotation().referencedDeclaration
|
||||||
|
)->constructor(); baseConstructor && modifier->arguments())
|
||||||
|
baseConstructorArguments.emplace_back(
|
||||||
|
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
|
||||||
|
modifier->arguments()
|
||||||
|
);
|
||||||
|
|
||||||
FunctionDefinition const* constructor = _contract.constructor();
|
IRGeneratorForStatements generator{m_context, m_utils};
|
||||||
if (constructor && !constructor->isPayable())
|
for (auto&& [baseContract, arguments]: baseConstructorArguments)
|
||||||
out << callValueCheck();
|
|
||||||
|
|
||||||
for (ContractDefinition const* contract: reverse(_contract.annotation().linearizedBaseContracts))
|
|
||||||
{
|
{
|
||||||
out <<
|
solAssert(baseContract && arguments, "");
|
||||||
"\n// Begin state variable initialization for contract \"" <<
|
if (baseContract->constructor() && !arguments->empty())
|
||||||
contract->name() <<
|
{
|
||||||
"\" (" <<
|
vector<string> params;
|
||||||
contract->stateVariables().size() <<
|
for (size_t i = 0; i < arguments->size(); ++i)
|
||||||
" variables)\n";
|
params.emplace_back(generator.evaluateExpression(
|
||||||
|
*(arguments->at(i)),
|
||||||
IRGeneratorForStatements generator{m_context, m_utils};
|
*(baseContract->constructor()->parameters()[i]->type())
|
||||||
for (VariableDeclaration const* variable: contract->stateVariables())
|
).commaSeparatedList());
|
||||||
if (!variable->isConstant() && !variable->immutable())
|
constructorParams[baseContract] = joinHumanReadable(params);
|
||||||
generator.initializeStateVar(*variable);
|
}
|
||||||
out << generator.code();
|
|
||||||
|
|
||||||
out << "// End state variable initialization for contract \"" << contract->name() << "\".\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constructor)
|
return {generator.code(), constructorParams};
|
||||||
|
}
|
||||||
|
|
||||||
|
string IRGenerator::initStateVariables(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
IRGeneratorForStatements generator{m_context, m_utils};
|
||||||
|
for (VariableDeclaration const* variable: _contract.stateVariables())
|
||||||
|
if (!variable->isConstant() && !variable->immutable())
|
||||||
|
generator.initializeStateVar(*variable);
|
||||||
|
|
||||||
|
return generator.code();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
auto listAllParams = [&](
|
||||||
|
map<ContractDefinition const*, string> const& baseParams) -> string
|
||||||
{
|
{
|
||||||
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
|
vector<string> params;
|
||||||
unsigned paramVars = make_shared<TupleType>(constructor->functionType(false)->parameterTypes())->sizeOnStack();
|
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
|
||||||
|
if (baseParams.count(contract))
|
||||||
|
params.emplace_back(baseParams.at(contract));
|
||||||
|
return joinHumanReadable(params);
|
||||||
|
};
|
||||||
|
|
||||||
Whiskers t(R"X(
|
map<ContractDefinition const*, string> baseConstructorParams;
|
||||||
let programSize := datasize("<object>")
|
for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i)
|
||||||
let argSize := sub(codesize(), programSize)
|
{
|
||||||
|
ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i];
|
||||||
|
baseConstructorParams.erase(contract);
|
||||||
|
|
||||||
let memoryDataOffset := <allocate>(argSize)
|
m_context.functionCollector().createFunction(implicitConstructorName(*contract), [&]() {
|
||||||
codecopy(memoryDataOffset, programSize, argSize)
|
Whiskers t(R"(
|
||||||
|
function <functionName>(<params><comma><baseParams>) {
|
||||||
|
<evalBaseArguments>
|
||||||
|
<?hasNextConstructor> <nextConstructor>(<nextParams>) </hasNextConstructor>
|
||||||
|
<initStateVariables>
|
||||||
|
<userDefinedConstructorBody>
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
string params;
|
||||||
|
if (contract->constructor())
|
||||||
|
for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters())
|
||||||
|
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
|
||||||
|
t("params", params);
|
||||||
|
string baseParamsString = listAllParams(baseConstructorParams);
|
||||||
|
t("baseParams", baseParamsString);
|
||||||
|
t("comma", !params.empty() && !baseParamsString.empty() ? ", " : "");
|
||||||
|
t("functionName", implicitConstructorName(*contract));
|
||||||
|
pair<string, map<ContractDefinition const*, string>> evaluatedArgs = evaluateConstructorArguments(*contract);
|
||||||
|
baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end());
|
||||||
|
t("evalBaseArguments", evaluatedArgs.first);
|
||||||
|
if (i < _contract.annotation().linearizedBaseContracts.size() - 1)
|
||||||
|
{
|
||||||
|
t("hasNextConstructor", true);
|
||||||
|
ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1];
|
||||||
|
t("nextConstructor", implicitConstructorName(*nextContract));
|
||||||
|
t("nextParams", listAllParams(baseConstructorParams));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
t("hasNextConstructor", false);
|
||||||
|
t("initStateVariables", initStateVariables(*contract));
|
||||||
|
t("userDefinedConstructorBody", contract->constructor() ? generate(contract->constructor()->body()) : "");
|
||||||
|
|
||||||
<assignToParams> <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
|
return t.render();
|
||||||
|
});
|
||||||
<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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGenerator::deployCode(ContractDefinition const& _contract)
|
string IRGenerator::deployCode(ContractDefinition const& _contract)
|
||||||
@ -305,7 +394,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract)
|
|||||||
codecopy(0, dataoffset("<object>"), datasize("<object>"))
|
codecopy(0, dataoffset("<object>"), datasize("<object>"))
|
||||||
return(0, datasize("<object>"))
|
return(0, datasize("<object>"))
|
||||||
)X");
|
)X");
|
||||||
t("object", runtimeObjectName(_contract));
|
t("object", m_context.runtimeObjectName(_contract));
|
||||||
return t.render();
|
return t.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,14 +403,9 @@ string IRGenerator::callValueCheck()
|
|||||||
return "if callvalue() { revert(0, 0) }";
|
return "if callvalue() { revert(0, 0) }";
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGenerator::creationObjectName(ContractDefinition const& _contract)
|
string IRGenerator::implicitConstructorName(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
return _contract.name() + "_" + to_string(_contract.id());
|
return "constructor_" + _contract.name() + "_" + to_string(_contract.id());
|
||||||
}
|
|
||||||
|
|
||||||
string IRGenerator::runtimeObjectName(ContractDefinition const& _contract)
|
|
||||||
{
|
|
||||||
return _contract.name() + "_" + to_string(_contract.id()) + "_deployed";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||||
@ -370,7 +454,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
|||||||
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
|
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
|
||||||
|
|
||||||
if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&type->declaration()))
|
if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&type->declaration()))
|
||||||
templ["function"] = generateFunction(*funDef);
|
templ["function"] = m_context.enqueueFunctionForCodeGeneration(*funDef);
|
||||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(&type->declaration()))
|
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(&type->declaration()))
|
||||||
templ["function"] = generateGetter(*varDecl);
|
templ["function"] = generateGetter(*varDecl);
|
||||||
else
|
else
|
||||||
@ -386,14 +470,14 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
|||||||
string fallbackCode;
|
string fallbackCode;
|
||||||
if (!fallback->isPayable())
|
if (!fallback->isPayable())
|
||||||
fallbackCode += callValueCheck();
|
fallbackCode += callValueCheck();
|
||||||
fallbackCode += generateFunction(*fallback) + "() stop()";
|
fallbackCode += m_context.enqueueFunctionForCodeGeneration(*fallback) + "() stop()";
|
||||||
|
|
||||||
t("fallback", fallbackCode);
|
t("fallback", fallbackCode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
t("fallback", "revert(0, 0)");
|
t("fallback", "revert(0, 0)");
|
||||||
if (FunctionDefinition const* etherReceiver = _contract.receiveFunction())
|
if (FunctionDefinition const* etherReceiver = _contract.receiveFunction())
|
||||||
t("receiveEther", generateFunction(*etherReceiver) + "() stop()");
|
t("receiveEther", m_context.enqueueFunctionForCodeGeneration(*etherReceiver) + "() stop()");
|
||||||
else
|
else
|
||||||
t("receiveEther", "");
|
t("receiveEther", "");
|
||||||
return t.render();
|
return t.render();
|
||||||
@ -413,6 +497,10 @@ string IRGenerator::memoryInit()
|
|||||||
|
|
||||||
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
|
solAssert(
|
||||||
|
m_context.functionGenerationQueueEmpty(),
|
||||||
|
"Reset function generation queue while it still had functions."
|
||||||
|
);
|
||||||
solAssert(
|
solAssert(
|
||||||
m_context.functionCollector().requestedFunctions().empty(),
|
m_context.functionCollector().requestedFunctions().empty(),
|
||||||
"Reset context while it still had functions."
|
"Reset context while it still had functions."
|
||||||
|
@ -50,12 +50,21 @@ public:
|
|||||||
|
|
||||||
/// Generates and returns the IR code, in unoptimized and optimized form
|
/// Generates and returns the IR code, in unoptimized and optimized form
|
||||||
/// (or just pretty-printed, depending on the optimizer settings).
|
/// (or just pretty-printed, depending on the optimizer settings).
|
||||||
std::pair<std::string, std::string> run(ContractDefinition const& _contract);
|
std::pair<std::string, std::string> run(
|
||||||
|
ContractDefinition const& _contract,
|
||||||
|
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
|
||||||
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string generate(ContractDefinition const& _contract);
|
std::string generate(
|
||||||
|
ContractDefinition const& _contract,
|
||||||
|
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
|
||||||
|
);
|
||||||
std::string generate(Block const& _block);
|
std::string generate(Block const& _block);
|
||||||
|
|
||||||
|
/// Generates code for all the functions from the function generation queue.
|
||||||
|
/// The resulting code is stored in the function collector in IRGenerationContext.
|
||||||
|
void generateQueuedFunctions();
|
||||||
/// Generates code for and returns the name of the function.
|
/// Generates code for and returns the name of the function.
|
||||||
std::string generateFunction(FunctionDefinition const& _function);
|
std::string generateFunction(FunctionDefinition const& _function);
|
||||||
/// Generates a getter for the given declaration and returns its name
|
/// Generates a getter for the given declaration and returns its name
|
||||||
@ -64,12 +73,27 @@ private:
|
|||||||
/// Generates code that assigns the initial value of the respective type.
|
/// Generates code that assigns the initial value of the respective type.
|
||||||
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
|
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
|
||||||
|
|
||||||
std::string constructorCode(ContractDefinition const& _contract);
|
/// Generates implicit constructors for all contracts in the inheritance hierarchy of
|
||||||
|
/// @a _contract
|
||||||
|
/// If there are user defined constructors, their body will be included in implicit constructors body.
|
||||||
|
void generateImplicitConstructors(ContractDefinition const& _contract);
|
||||||
|
|
||||||
|
/// Evaluates constructor's arguments for all base contracts (listed in inheritance specifiers) of
|
||||||
|
/// @a _contract
|
||||||
|
/// @returns Pair of expressions needed to evaluate params and list of parameters in a map contract -> params
|
||||||
|
std::pair<std::string, std::map<ContractDefinition const*, std::string>> evaluateConstructorArguments(
|
||||||
|
ContractDefinition const& _contract
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Initializes state variables of
|
||||||
|
/// @a _contract
|
||||||
|
/// @returns Source code to initialize state variables
|
||||||
|
std::string initStateVariables(ContractDefinition const& _contract);
|
||||||
|
|
||||||
std::string deployCode(ContractDefinition const& _contract);
|
std::string deployCode(ContractDefinition const& _contract);
|
||||||
std::string callValueCheck();
|
std::string callValueCheck();
|
||||||
|
|
||||||
std::string creationObjectName(ContractDefinition const& _contract);
|
std::string implicitConstructorName(ContractDefinition const& _contract);
|
||||||
std::string runtimeObjectName(ContractDefinition const& _contract);
|
|
||||||
|
|
||||||
std::string dispatchRoutine(ContractDefinition const& _contract);
|
std::string dispatchRoutine(ContractDefinition const& _contract);
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||||
#include <libsolidity/codegen/ABIFunctions.h>
|
#include <libsolidity/codegen/ABIFunctions.h>
|
||||||
#include <libsolidity/codegen/CompilerUtils.h>
|
#include <libsolidity/codegen/CompilerUtils.h>
|
||||||
|
#include <libsolidity/codegen/ReturnInfo.h>
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
#include <libevmasm/GasMeter.h>
|
#include <libevmasm/GasMeter.h>
|
||||||
@ -41,6 +42,7 @@
|
|||||||
#include <libsolutil/Keccak256.h>
|
#include <libsolutil/Keccak256.h>
|
||||||
#include <libsolutil/Visitor.h>
|
#include <libsolutil/Visitor.h>
|
||||||
|
|
||||||
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
#include <boost/range/adaptor/transformed.hpp>
|
#include <boost/range/adaptor/transformed.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -167,6 +169,14 @@ void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _va
|
|||||||
assign(m_context.localVariable(_varDecl), zero);
|
assign(m_context.localVariable(_varDecl), zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
|
||||||
|
{
|
||||||
|
_expression.accept(*this);
|
||||||
|
IRVariable variable{m_context.newYulVariable(), _targetType};
|
||||||
|
define(variable, _expression);
|
||||||
|
return variable;
|
||||||
|
}
|
||||||
|
|
||||||
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
|
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
|
||||||
{
|
{
|
||||||
if (Expression const* expression = _varDeclStatement.initialValue())
|
if (Expression const* expression = _varDeclStatement.initialValue())
|
||||||
@ -255,14 +265,14 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
|||||||
solUnimplementedAssert(false, "");
|
solUnimplementedAssert(false, "");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool lValueRequested = _tuple.annotation().lValueRequested;
|
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
|
||||||
if (lValueRequested)
|
if (willBeWrittenTo)
|
||||||
solAssert(!m_currentLValue, "");
|
solAssert(!m_currentLValue, "");
|
||||||
if (_tuple.components().size() == 1)
|
if (_tuple.components().size() == 1)
|
||||||
{
|
{
|
||||||
solAssert(_tuple.components().front(), "");
|
solAssert(_tuple.components().front(), "");
|
||||||
_tuple.components().front()->accept(*this);
|
_tuple.components().front()->accept(*this);
|
||||||
if (lValueRequested)
|
if (willBeWrittenTo)
|
||||||
solAssert(!!m_currentLValue, "");
|
solAssert(!!m_currentLValue, "");
|
||||||
else
|
else
|
||||||
define(_tuple, *_tuple.components().front());
|
define(_tuple, *_tuple.components().front());
|
||||||
@ -274,7 +284,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
|||||||
if (auto const& component = _tuple.components()[i])
|
if (auto const& component = _tuple.components()[i])
|
||||||
{
|
{
|
||||||
component->accept(*this);
|
component->accept(*this);
|
||||||
if (lValueRequested)
|
if (willBeWrittenTo)
|
||||||
{
|
{
|
||||||
solAssert(!!m_currentLValue, "");
|
solAssert(!!m_currentLValue, "");
|
||||||
lvalues.emplace_back(std::move(m_currentLValue));
|
lvalues.emplace_back(std::move(m_currentLValue));
|
||||||
@ -283,10 +293,10 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
|||||||
else
|
else
|
||||||
define(IRVariable(_tuple).tupleComponent(i), *component);
|
define(IRVariable(_tuple).tupleComponent(i), *component);
|
||||||
}
|
}
|
||||||
else if (lValueRequested)
|
else if (willBeWrittenTo)
|
||||||
lvalues.emplace_back();
|
lvalues.emplace_back();
|
||||||
|
|
||||||
if (_tuple.annotation().lValueRequested)
|
if (_tuple.annotation().willBeWrittenTo)
|
||||||
m_currentLValue.emplace(IRLValue{
|
m_currentLValue.emplace(IRLValue{
|
||||||
*_tuple.annotation().type,
|
*_tuple.annotation().type,
|
||||||
IRLValue::Tuple{std::move(lvalues)}
|
IRLValue::Tuple{std::move(lvalues)}
|
||||||
@ -557,9 +567,20 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
|
arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
|
||||||
|
if (auto expressionType = dynamic_cast<TypeType const*>(memberAccess->expression().annotation().type))
|
||||||
|
if (auto contractType = dynamic_cast<ContractType const*>(expressionType->actualType()))
|
||||||
|
solUnimplementedAssert(
|
||||||
|
!contractType->contractDefinition().isLibrary() || functionType->kind() == FunctionType::Kind::Internal,
|
||||||
|
"Only internal function calls implemented for libraries"
|
||||||
|
);
|
||||||
|
|
||||||
solUnimplementedAssert(!functionType->bound(), "");
|
solUnimplementedAssert(!functionType->bound(), "");
|
||||||
switch (functionType->kind())
|
switch (functionType->kind())
|
||||||
{
|
{
|
||||||
|
case FunctionType::Kind::Declaration:
|
||||||
|
solAssert(false, "Attempted to generate code for calling a function definition.");
|
||||||
|
break;
|
||||||
case FunctionType::Kind::Internal:
|
case FunctionType::Kind::Internal:
|
||||||
{
|
{
|
||||||
vector<string> args;
|
vector<string> args;
|
||||||
@ -569,29 +590,60 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
else
|
else
|
||||||
args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList());
|
args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList());
|
||||||
|
|
||||||
if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
|
optional<FunctionDefinition const*> functionDef;
|
||||||
|
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
|
||||||
{
|
{
|
||||||
solAssert(!functionType->bound(), "");
|
solUnimplementedAssert(!functionType->bound(), "Internal calls to bound functions are not yet implemented for libraries and not allowed for contracts");
|
||||||
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
|
|
||||||
|
functionDef = dynamic_cast<FunctionDefinition const*>(memberAccess->annotation().referencedDeclaration);
|
||||||
|
if (functionDef.value() != nullptr)
|
||||||
|
solAssert(functionType->declaration() == *memberAccess->annotation().referencedDeclaration, "");
|
||||||
|
else
|
||||||
{
|
{
|
||||||
define(_functionCall) <<
|
solAssert(dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration), "");
|
||||||
m_context.virtualFunctionName(*functionDef) <<
|
solAssert(!functionType->hasDeclaration(), "");
|
||||||
"(" <<
|
|
||||||
joinHumanReadable(args) <<
|
|
||||||
")\n";
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
|
||||||
|
{
|
||||||
|
solAssert(!functionType->bound(), "");
|
||||||
|
|
||||||
define(_functionCall) <<
|
if (auto unresolvedFunctionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
|
||||||
m_context.internalDispatch(
|
{
|
||||||
TupleType(functionType->parameterTypes()).sizeOnStack(),
|
functionDef = &unresolvedFunctionDef->resolveVirtual(m_context.mostDerivedContract());
|
||||||
TupleType(functionType->returnParameterTypes()).sizeOnStack()
|
solAssert(functionType->declaration() == *identifier->annotation().referencedDeclaration, "");
|
||||||
) <<
|
}
|
||||||
"(" <<
|
else
|
||||||
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
|
{
|
||||||
joinHumanReadablePrefixed(args) <<
|
functionDef = nullptr;
|
||||||
")\n";
|
solAssert(dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration), "");
|
||||||
|
solAssert(!functionType->hasDeclaration(), "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Not a simple expression like x or A.x
|
||||||
|
functionDef = nullptr;
|
||||||
|
|
||||||
|
solAssert(functionDef.has_value(), "");
|
||||||
|
solAssert(functionDef.value() == nullptr || functionDef.value()->isImplemented(), "");
|
||||||
|
|
||||||
|
if (functionDef.value() != nullptr)
|
||||||
|
define(_functionCall) <<
|
||||||
|
m_context.enqueueFunctionForCodeGeneration(*functionDef.value()) <<
|
||||||
|
"(" <<
|
||||||
|
joinHumanReadable(args) <<
|
||||||
|
")\n";
|
||||||
|
else
|
||||||
|
define(_functionCall) <<
|
||||||
|
// NOTE: internalDispatch() takes care of adding the function to function generation queue
|
||||||
|
m_context.internalDispatch(
|
||||||
|
TupleType(functionType->parameterTypes()).sizeOnStack(),
|
||||||
|
TupleType(functionType->returnParameterTypes()).sizeOnStack()
|
||||||
|
) <<
|
||||||
|
"(" <<
|
||||||
|
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
|
||||||
|
joinHumanReadablePrefixed(args) <<
|
||||||
|
")\n";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FunctionType::Kind::External:
|
case FunctionType::Kind::External:
|
||||||
@ -678,6 +730,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case FunctionType::Kind::Revert:
|
||||||
|
{
|
||||||
|
solAssert(arguments.size() == parameterTypes.size(), "");
|
||||||
|
if (arguments.empty())
|
||||||
|
m_code << "revert(0, 0)\n";
|
||||||
|
else
|
||||||
|
solUnimplementedAssert(false, "");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Array creation using new
|
// Array creation using new
|
||||||
case FunctionType::Kind::ObjectCreation:
|
case FunctionType::Kind::ObjectCreation:
|
||||||
{
|
{
|
||||||
@ -711,6 +773,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
"))\n";
|
"))\n";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case FunctionType::Kind::ECRecover:
|
||||||
|
case FunctionType::Kind::SHA256:
|
||||||
|
case FunctionType::Kind::RIPEMD160:
|
||||||
|
{
|
||||||
|
solAssert(!_functionCall.annotation().tryCall, "");
|
||||||
|
appendExternalFunctionCall(_functionCall, arguments);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case FunctionType::Kind::ArrayPop:
|
case FunctionType::Kind::ArrayPop:
|
||||||
{
|
{
|
||||||
auto const& memberAccessExpression = dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression();
|
auto const& memberAccessExpression = dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression();
|
||||||
@ -754,6 +824,128 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case FunctionType::Kind::MetaType:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FunctionType::Kind::GasLeft:
|
||||||
|
{
|
||||||
|
define(_functionCall) << "gas()\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FunctionType::Kind::Selfdestruct:
|
||||||
|
{
|
||||||
|
solAssert(arguments.size() == 1, "");
|
||||||
|
define(_functionCall) << "selfdestruct(" << expressionAsType(*arguments.front(), *parameterTypes.front()) << ")\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FunctionType::Kind::Log0:
|
||||||
|
case FunctionType::Kind::Log1:
|
||||||
|
case FunctionType::Kind::Log2:
|
||||||
|
case FunctionType::Kind::Log3:
|
||||||
|
case FunctionType::Kind::Log4:
|
||||||
|
{
|
||||||
|
unsigned logNumber = int(functionType->kind()) - int(FunctionType::Kind::Log0);
|
||||||
|
solAssert(arguments.size() == logNumber + 1, "");
|
||||||
|
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
||||||
|
string indexedArgs;
|
||||||
|
for (unsigned arg = 0; arg < logNumber; ++arg)
|
||||||
|
indexedArgs += ", " + expressionAsType(*arguments[arg + 1], *(parameterTypes[arg + 1]));
|
||||||
|
Whiskers templ(R"({
|
||||||
|
let <pos> := <freeMemory>
|
||||||
|
let <end> := <encode>(<pos>, <nonIndexedArgs>)
|
||||||
|
<log>(<pos>, sub(<end>, <pos>) <indexedArgs>)
|
||||||
|
})");
|
||||||
|
templ("pos", m_context.newYulVariable());
|
||||||
|
templ("end", m_context.newYulVariable());
|
||||||
|
templ("freeMemory", freeMemory());
|
||||||
|
templ("encode", abi.tupleEncoder({arguments.front()->annotation().type}, {parameterTypes.front()}));
|
||||||
|
templ("nonIndexedArgs", IRVariable(*arguments.front()).commaSeparatedList());
|
||||||
|
templ("log", "log" + to_string(logNumber));
|
||||||
|
templ("indexedArgs", indexedArgs);
|
||||||
|
m_code << templ.render();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FunctionType::Kind::Creation:
|
||||||
|
{
|
||||||
|
solAssert(!functionType->gasSet(), "Gas limit set for contract creation.");
|
||||||
|
solAssert(
|
||||||
|
functionType->returnParameterTypes().size() == 1,
|
||||||
|
"Constructor should return only one type"
|
||||||
|
);
|
||||||
|
|
||||||
|
TypePointers argumentTypes;
|
||||||
|
string constructorParams;
|
||||||
|
for (ASTPointer<Expression const> const& arg: arguments)
|
||||||
|
{
|
||||||
|
argumentTypes.push_back(arg->annotation().type);
|
||||||
|
constructorParams += ", " + IRVariable{*arg}.commaSeparatedList();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContractDefinition const* contract =
|
||||||
|
&dynamic_cast<ContractType const&>(*functionType->returnParameterTypes().front()).contractDefinition();
|
||||||
|
m_context.subObjectsCreated().insert(contract);
|
||||||
|
|
||||||
|
Whiskers t(R"(
|
||||||
|
let <memPos> := <allocateTemporaryMemory>()
|
||||||
|
let <memEnd> := add(<memPos>, datasize("<object>"))
|
||||||
|
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { revert(0, 0) }
|
||||||
|
datacopy(<memPos>, dataoffset("<object>"), datasize("<object>"))
|
||||||
|
<memEnd> := <abiEncode>(<memEnd><constructorParams>)
|
||||||
|
<?saltSet>
|
||||||
|
let <retVars> := create2(<value>, <memPos>, sub(<memEnd>, <memPos>), <salt>)
|
||||||
|
<!saltSet>
|
||||||
|
let <retVars> := create(<value>, <memPos>, sub(<memEnd>, <memPos>))
|
||||||
|
</saltSet>
|
||||||
|
<releaseTemporaryMemory>()
|
||||||
|
)");
|
||||||
|
t("memPos", m_context.newYulVariable());
|
||||||
|
t("memEnd", m_context.newYulVariable());
|
||||||
|
t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction());
|
||||||
|
t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction());
|
||||||
|
t("object", m_context.creationObjectName(*contract));
|
||||||
|
t("abiEncode",
|
||||||
|
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(),false)
|
||||||
|
);
|
||||||
|
t("constructorParams", constructorParams);
|
||||||
|
t("value", functionType->valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
|
||||||
|
t("saltSet", functionType->saltSet());
|
||||||
|
if (functionType->saltSet())
|
||||||
|
t("salt", IRVariable(_functionCall.expression()).part("salt").name());
|
||||||
|
t("retVars", IRVariable(_functionCall).commaSeparatedList());
|
||||||
|
m_code << t.render();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FunctionType::Kind::Send:
|
||||||
|
case FunctionType::Kind::Transfer:
|
||||||
|
{
|
||||||
|
solAssert(arguments.size() == 1 && parameterTypes.size() == 1, "");
|
||||||
|
string address{IRVariable(_functionCall.expression()).part("address").name()};
|
||||||
|
string value{expressionAsType(*arguments[0], *(parameterTypes[0]))};
|
||||||
|
Whiskers templ(R"(
|
||||||
|
let <gas> := 0
|
||||||
|
if iszero(<value>) { <gas> := <callStipend> }
|
||||||
|
let <success> := call(<gas>, <address>, <value>, 0, 0, 0, 0)
|
||||||
|
<?isTransfer>
|
||||||
|
if iszero(<success>) { <forwardingRevert>() }
|
||||||
|
</isTransfer>
|
||||||
|
)");
|
||||||
|
templ("gas", m_context.newYulVariable());
|
||||||
|
templ("callStipend", toString(evmasm::GasCosts::callStipend));
|
||||||
|
templ("address", address);
|
||||||
|
templ("value", value);
|
||||||
|
if (functionType->kind() == FunctionType::Kind::Transfer)
|
||||||
|
templ("success", m_context.newYulVariable());
|
||||||
|
else
|
||||||
|
templ("success", IRVariable(_functionCall).commaSeparatedList());
|
||||||
|
templ("isTransfer", functionType->kind() == FunctionType::Kind::Transfer);
|
||||||
|
templ("forwardingRevert", m_utils.forwardingRevertFunction());
|
||||||
|
m_code << templ.render();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
|
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
|
||||||
}
|
}
|
||||||
@ -840,11 +1032,18 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
|||||||
case Type::Category::Function:
|
case Type::Category::Function:
|
||||||
if (member == "selector")
|
if (member == "selector")
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(
|
FunctionType const& functionType = dynamic_cast<FunctionType const&>(
|
||||||
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
|
*_memberAccess.expression().annotation().type
|
||||||
FunctionType::Kind::External, ""
|
|
||||||
);
|
);
|
||||||
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
|
if (functionType.kind() == FunctionType::Kind::External)
|
||||||
|
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
|
||||||
|
else if (functionType.kind() == FunctionType::Kind::Declaration)
|
||||||
|
{
|
||||||
|
solAssert(functionType.hasDeclaration(), "");
|
||||||
|
define(IRVariable{_memberAccess}) << formatNumber(functionType.externalIdentifier() << 224) << "\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(false, "Invalid use of .selector");
|
||||||
}
|
}
|
||||||
else if (member == "address")
|
else if (member == "address")
|
||||||
{
|
{
|
||||||
@ -904,6 +1103,15 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
|||||||
{
|
{
|
||||||
solUnimplementedAssert(false, "");
|
solUnimplementedAssert(false, "");
|
||||||
}
|
}
|
||||||
|
else if (member == "interfaceId")
|
||||||
|
{
|
||||||
|
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
|
||||||
|
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
|
||||||
|
uint64_t result{0};
|
||||||
|
for (auto const& function: contract.interfaceFunctionList(false))
|
||||||
|
result ^= fromBigEndian<uint64_t>(function.first.ref());
|
||||||
|
define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n";
|
||||||
|
}
|
||||||
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
|
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
|
||||||
{
|
{
|
||||||
// no-op
|
// no-op
|
||||||
@ -971,6 +1179,78 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
|||||||
solAssert(false, "Illegal fixed bytes member.");
|
solAssert(false, "Illegal fixed bytes member.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Type::Category::TypeType:
|
||||||
|
{
|
||||||
|
Type const& actualType = *dynamic_cast<TypeType const&>(
|
||||||
|
*_memberAccess.expression().annotation().type
|
||||||
|
).actualType();
|
||||||
|
|
||||||
|
if (actualType.category() == Type::Category::Contract)
|
||||||
|
{
|
||||||
|
if (auto const* variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
|
||||||
|
handleVariableReference(*variable, _memberAccess);
|
||||||
|
else if (auto const* funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
|
||||||
|
{
|
||||||
|
switch (funType->kind())
|
||||||
|
{
|
||||||
|
case FunctionType::Kind::Declaration:
|
||||||
|
break;
|
||||||
|
case FunctionType::Kind::Internal:
|
||||||
|
if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
|
||||||
|
define(_memberAccess) << to_string(function->id()) << "\n";
|
||||||
|
else
|
||||||
|
solAssert(false, "Function not found in member access");
|
||||||
|
break;
|
||||||
|
case FunctionType::Kind::Event:
|
||||||
|
solAssert(
|
||||||
|
dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration),
|
||||||
|
"Event not found"
|
||||||
|
);
|
||||||
|
// the call will do the resolving
|
||||||
|
break;
|
||||||
|
case FunctionType::Kind::DelegateCall:
|
||||||
|
define(IRVariable(_memberAccess).part("address"), _memberAccess.expression());
|
||||||
|
define(IRVariable(_memberAccess).part("functionIdentifier")) << formatNumber(funType->externalIdentifier()) << "\n";
|
||||||
|
break;
|
||||||
|
case FunctionType::Kind::External:
|
||||||
|
case FunctionType::Kind::Creation:
|
||||||
|
case FunctionType::Kind::Send:
|
||||||
|
case FunctionType::Kind::BareCall:
|
||||||
|
case FunctionType::Kind::BareCallCode:
|
||||||
|
case FunctionType::Kind::BareDelegateCall:
|
||||||
|
case FunctionType::Kind::BareStaticCall:
|
||||||
|
case FunctionType::Kind::Transfer:
|
||||||
|
case FunctionType::Kind::Log0:
|
||||||
|
case FunctionType::Kind::Log1:
|
||||||
|
case FunctionType::Kind::Log2:
|
||||||
|
case FunctionType::Kind::Log3:
|
||||||
|
case FunctionType::Kind::Log4:
|
||||||
|
case FunctionType::Kind::ECRecover:
|
||||||
|
case FunctionType::Kind::SHA256:
|
||||||
|
case FunctionType::Kind::RIPEMD160:
|
||||||
|
default:
|
||||||
|
solAssert(false, "unsupported member function");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type))
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// The old code generator had a generic "else" case here
|
||||||
|
// without any specific code being generated,
|
||||||
|
// but it would still be better to have an exhaustive list.
|
||||||
|
solAssert(false, "");
|
||||||
|
}
|
||||||
|
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(&actualType))
|
||||||
|
define(_memberAccess) << to_string(enumType->memberValue(_memberAccess.memberName())) << "\n";
|
||||||
|
else
|
||||||
|
// The old code generator had a generic "else" case here
|
||||||
|
// without any specific code being generated,
|
||||||
|
// but it would still be better to have an exhaustive list.
|
||||||
|
solAssert(false, "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
solAssert(false, "Member access to unknown type.");
|
solAssert(false, "Member access to unknown type.");
|
||||||
}
|
}
|
||||||
@ -985,7 +1265,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
|||||||
solAssert(holds_alternative<yul::Block>(modified), "");
|
solAssert(holds_alternative<yul::Block>(modified), "");
|
||||||
|
|
||||||
// Do not provide dialect so that we get the full type information.
|
// Do not provide dialect so that we get the full type information.
|
||||||
m_code << yul::AsmPrinter()(std::get<yul::Block>(std::move(modified))) << "\n";
|
m_code << yul::AsmPrinter()(std::get<yul::Block>(modified)) << "\n";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1186,31 +1466,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
|||||||
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
|
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
|
||||||
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
|
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
|
||||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||||
|
handleVariableReference(*varDecl, _identifier);
|
||||||
|
else if (dynamic_cast<ContractDefinition const*>(declaration))
|
||||||
{
|
{
|
||||||
// TODO for the constant case, we have to be careful:
|
// no-op
|
||||||
// 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,
|
|
||||||
IRLValue::Stack{m_context.localVariable(*varDecl)}
|
|
||||||
});
|
|
||||||
else if (m_context.isStateVariable(*varDecl))
|
|
||||||
setLValue(_identifier, IRLValue{
|
|
||||||
*varDecl->annotation().type,
|
|
||||||
IRLValue::Storage{
|
|
||||||
toCompactHexWithPrefix(m_context.storageLocationOfVariable(*varDecl).first),
|
|
||||||
m_context.storageLocationOfVariable(*varDecl).second
|
|
||||||
}
|
|
||||||
});
|
|
||||||
else
|
|
||||||
solAssert(false, "Invalid variable kind.");
|
|
||||||
}
|
|
||||||
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
|
|
||||||
{
|
|
||||||
solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported.");
|
|
||||||
}
|
}
|
||||||
else if (dynamic_cast<EventDefinition const*>(declaration))
|
else if (dynamic_cast<EventDefinition const*>(declaration))
|
||||||
{
|
{
|
||||||
@ -1249,6 +1508,33 @@ bool IRGeneratorForStatements::visit(Literal const& _literal)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IRGeneratorForStatements::handleVariableReference(
|
||||||
|
VariableDeclaration const& _variable,
|
||||||
|
Expression const& _referencingExpression
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// 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(!_variable.isConstant(), "");
|
||||||
|
solUnimplementedAssert(!_variable.immutable(), "");
|
||||||
|
if (m_context.isLocalVariable(_variable))
|
||||||
|
setLValue(_referencingExpression, IRLValue{
|
||||||
|
*_variable.annotation().type,
|
||||||
|
IRLValue::Stack{m_context.localVariable(_variable)}
|
||||||
|
});
|
||||||
|
else if (m_context.isStateVariable(_variable))
|
||||||
|
setLValue(_referencingExpression, IRLValue{
|
||||||
|
*_variable.annotation().type,
|
||||||
|
IRLValue::Storage{
|
||||||
|
toCompactHexWithPrefix(m_context.storageLocationOfVariable(_variable).first),
|
||||||
|
m_context.storageLocationOfVariable(_variable).second
|
||||||
|
}
|
||||||
|
});
|
||||||
|
else
|
||||||
|
solAssert(false, "Invalid variable kind.");
|
||||||
|
}
|
||||||
|
|
||||||
void IRGeneratorForStatements::appendExternalFunctionCall(
|
void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||||
FunctionCall const& _functionCall,
|
FunctionCall const& _functionCall,
|
||||||
vector<ASTPointer<Expression const>> const& _arguments
|
vector<ASTPointer<Expression const>> const& _arguments
|
||||||
@ -1260,46 +1546,23 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
_arguments.size() == funType.parameterTypes().size(), ""
|
_arguments.size() == funType.parameterTypes().size(), ""
|
||||||
);
|
);
|
||||||
solUnimplementedAssert(!funType.bound(), "");
|
solUnimplementedAssert(!funType.bound(), "");
|
||||||
FunctionType::Kind funKind = funType.kind();
|
FunctionType::Kind const funKind = funType.kind();
|
||||||
|
|
||||||
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
|
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
|
||||||
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
|
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
|
||||||
|
|
||||||
bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
|
bool const isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
|
||||||
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
|
bool const useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
||||||
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
|
||||||
|
|
||||||
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
ReturnInfo const returnInfo{m_context.evmVersion(), funType};
|
||||||
unsigned estimatedReturnSize = 0;
|
|
||||||
bool dynamicReturnSize = false;
|
|
||||||
TypePointers returnTypes;
|
|
||||||
if (!returnSuccessConditionAndReturndata)
|
|
||||||
{
|
|
||||||
if (haveReturndatacopy)
|
|
||||||
returnTypes = funType.returnParameterTypes();
|
|
||||||
else
|
|
||||||
returnTypes = funType.returnParameterTypesWithoutDynamicTypes();
|
|
||||||
|
|
||||||
for (auto const& retType: returnTypes)
|
|
||||||
if (retType->isDynamicallyEncoded())
|
|
||||||
{
|
|
||||||
solAssert(haveReturndatacopy, "");
|
|
||||||
dynamicReturnSize = true;
|
|
||||||
estimatedReturnSize = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (retType->decodingType())
|
|
||||||
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
|
|
||||||
else
|
|
||||||
estimatedReturnSize += retType->calldataEncodedSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
TypePointers argumentTypes;
|
TypePointers argumentTypes;
|
||||||
vector<string> argumentStrings;
|
vector<string> argumentStrings;
|
||||||
for (auto const& arg: _arguments)
|
for (auto const& arg: _arguments)
|
||||||
{
|
{
|
||||||
argumentTypes.emplace_back(&type(*arg));
|
argumentTypes.emplace_back(&type(*arg));
|
||||||
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
if (IRVariable(*arg).type().sizeOnStack() > 0)
|
||||||
|
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
||||||
}
|
}
|
||||||
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
|
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
|
||||||
|
|
||||||
@ -1311,41 +1574,92 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
// (which we would have to subtract from the gas left)
|
// (which we would have to subtract from the gas left)
|
||||||
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
|
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
|
||||||
// would remove that, so we use MSTORE here.
|
// would remove that, so we use MSTORE here.
|
||||||
if (!funType.gasSet() && estimatedReturnSize > 0)
|
if (!funType.gasSet() && returnInfo.estimatedReturnSize > 0)
|
||||||
m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n";
|
m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
||||||
|
|
||||||
solUnimplementedAssert(!funType.isBareCall(), "");
|
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
<?checkExistence>
|
<?checkExistence>
|
||||||
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
||||||
</checkExistence>
|
</checkExistence>
|
||||||
|
|
||||||
|
// storage for arguments and returned data
|
||||||
let <pos> := <freeMemory>
|
let <pos> := <freeMemory>
|
||||||
|
<?bareCall>
|
||||||
|
<!bareCall>
|
||||||
|
mstore(<pos>, <shl28>(<funId>))
|
||||||
|
</bareCall>
|
||||||
|
let <end> := <encodeArgs>(
|
||||||
|
<?bareCall>
|
||||||
|
<pos>
|
||||||
|
<!bareCall>
|
||||||
|
add(<pos>, 4)
|
||||||
|
</bareCall>
|
||||||
|
<argumentString>
|
||||||
|
)
|
||||||
|
|
||||||
mstore(<pos>, <shl28>(<funId>))
|
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||||
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
<?noTryCall>
|
||||||
|
if iszero(<success>) { <forwardingRevert>() }
|
||||||
|
</noTryCall>
|
||||||
|
<?hasRetVars> let <retVars> </hasRetVars>
|
||||||
|
if <success> {
|
||||||
|
<?dynamicReturnSize>
|
||||||
|
// copy dynamic return data out
|
||||||
|
returndatacopy(<pos>, 0, returndatasize())
|
||||||
|
</dynamicReturnSize>
|
||||||
|
|
||||||
let <result> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
// update freeMemoryPointer according to dynamic return size
|
||||||
if iszero(<result>) { <forwardingRevert>() }
|
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
|
||||||
|
|
||||||
<?dynamicReturnSize>
|
// decode return parameters from external try-call into retVars
|
||||||
returndatacopy(<pos>, 0, returndatasize())
|
<?hasRetVars> <retVars> := </hasRetVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||||
</dynamicReturnSize>
|
}
|
||||||
|
|
||||||
mstore(<freeMemoryPointer>, add(<pos>, and(add(<returnSize>, 0x1f), not(0x1f))))
|
|
||||||
<?returns> let <retVars> := </returns> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
|
||||||
)");
|
)");
|
||||||
templ("pos", m_context.newYulVariable());
|
templ("pos", m_context.newYulVariable());
|
||||||
templ("end", m_context.newYulVariable());
|
templ("end", m_context.newYulVariable());
|
||||||
templ("result", m_context.newYulVariable());
|
templ("bareCall", funType.isBareCall());
|
||||||
|
if (_functionCall.annotation().tryCall)
|
||||||
|
templ("success", m_context.trySuccessConditionVariable(_functionCall));
|
||||||
|
else
|
||||||
|
templ("success", m_context.newYulVariable());
|
||||||
templ("freeMemory", freeMemory());
|
templ("freeMemory", freeMemory());
|
||||||
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
|
|
||||||
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
||||||
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
|
||||||
templ("address", IRVariable(_functionCall.expression()).part("address").name());
|
if (!funType.isBareCall())
|
||||||
|
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
||||||
|
|
||||||
|
if (funKind == FunctionType::Kind::ECRecover)
|
||||||
|
templ("address", "1");
|
||||||
|
else if (funKind == FunctionType::Kind::SHA256)
|
||||||
|
templ("address", "2");
|
||||||
|
else if (funKind == FunctionType::Kind::RIPEMD160)
|
||||||
|
templ("address", "3");
|
||||||
|
else
|
||||||
|
templ("address", IRVariable(_functionCall.expression()).part("address").name());
|
||||||
|
|
||||||
|
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
|
||||||
|
// This ensures it can catch badly formatted input from external calls.
|
||||||
|
if (m_context.evmVersion().supportsReturndata())
|
||||||
|
templ("returnSize", "returndatasize()");
|
||||||
|
else
|
||||||
|
templ("returnSize", to_string(returnInfo.estimatedReturnSize));
|
||||||
|
|
||||||
|
templ("reservedReturnSize", returnInfo.dynamicReturnSize ? "0" : to_string(returnInfo.estimatedReturnSize));
|
||||||
|
|
||||||
|
string const retVars = IRVariable(_functionCall).commaSeparatedList();
|
||||||
|
templ("retVars", retVars);
|
||||||
|
templ("hasRetVars", !retVars.empty());
|
||||||
|
solAssert(retVars.empty() == returnInfo.returnTypes.empty(), "");
|
||||||
|
|
||||||
|
templ("roundUp", m_utils.roundUpFunction());
|
||||||
|
templ("abiDecode", abi.tupleDecoder(returnInfo.returnTypes, true));
|
||||||
|
templ("dynamicReturnSize", returnInfo.dynamicReturnSize);
|
||||||
|
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
|
||||||
|
|
||||||
|
templ("noTryCall", !_functionCall.annotation().tryCall);
|
||||||
|
|
||||||
// If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
|
// If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
|
||||||
// Move arguments to memory, will not update the free memory pointer (but will update the memory
|
// Move arguments to memory, will not update the free memory pointer (but will update the memory
|
||||||
@ -1356,9 +1670,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
// but all parameters of ecrecover are value types anyway.
|
// but all parameters of ecrecover are value types anyway.
|
||||||
encodeInPlace = false;
|
encodeInPlace = false;
|
||||||
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
|
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
|
||||||
solUnimplementedAssert(!encodeInPlace, "");
|
|
||||||
solUnimplementedAssert(funType.padArguments(), "");
|
solUnimplementedAssert(encodeInPlace == !funType.padArguments(), "");
|
||||||
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
|
if (encodeInPlace)
|
||||||
|
{
|
||||||
|
solUnimplementedAssert(!encodeForLibraryCall, "");
|
||||||
|
templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
|
||||||
templ("argumentString", argumentString);
|
templ("argumentString", argumentString);
|
||||||
|
|
||||||
// Output data will replace input data, unless we have ECRecover (then, output
|
// Output data will replace input data, unless we have ECRecover (then, output
|
||||||
@ -1401,24 +1721,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
|
|
||||||
templ("forwardingRevert", m_utils.forwardingRevertFunction());
|
templ("forwardingRevert", m_utils.forwardingRevertFunction());
|
||||||
|
|
||||||
solUnimplementedAssert(!returnSuccessConditionAndReturndata, "");
|
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
|
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
||||||
|
|
||||||
templ("dynamicReturnSize", dynamicReturnSize);
|
|
||||||
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
|
|
||||||
// This ensures it can catch badly formatted input from external calls.
|
|
||||||
if (haveReturndatacopy)
|
|
||||||
templ("returnSize", "returndatasize()");
|
|
||||||
else
|
|
||||||
templ("returnSize", to_string(estimatedReturnSize));
|
|
||||||
|
|
||||||
templ("reservedReturnSize", dynamicReturnSize ? "0" : to_string(estimatedReturnSize));
|
|
||||||
|
|
||||||
templ("abiDecode", abi.tupleDecoder(returnTypes, true));
|
|
||||||
templ("returns", !returnTypes.empty());
|
|
||||||
templ("retVars", IRVariable(_functionCall).commaSeparatedList());
|
|
||||||
|
|
||||||
m_code << templ.render();
|
m_code << templ.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1471,14 +1776,17 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable
|
|||||||
else
|
else
|
||||||
m_code << (_declare ? "let ": "") << _lhs.part(stackItemName).name() << " := " << _rhs.part(stackItemName).name() << "\n";
|
m_code << (_declare ? "let ": "") << _lhs.part(stackItemName).name() << " := " << _rhs.part(stackItemName).name() << "\n";
|
||||||
else
|
else
|
||||||
m_code <<
|
{
|
||||||
(_declare ? "let ": "") <<
|
if (_lhs.type().sizeOnStack() > 0)
|
||||||
_lhs.commaSeparatedList() <<
|
m_code <<
|
||||||
" := " <<
|
(_declare ? "let ": "") <<
|
||||||
m_context.utils().conversionFunction(_rhs.type(), _lhs.type()) <<
|
_lhs.commaSeparatedList() <<
|
||||||
|
" := ";
|
||||||
|
m_code << m_context.utils().conversionFunction(_rhs.type(), _lhs.type()) <<
|
||||||
"(" <<
|
"(" <<
|
||||||
_rhs.commaSeparatedList() <<
|
_rhs.commaSeparatedList() <<
|
||||||
")\n";
|
")\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
|
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
|
||||||
@ -1540,6 +1848,15 @@ string IRGeneratorForStatements::binaryOperation(
|
|||||||
case Token::Mod:
|
case Token::Mod:
|
||||||
fun = m_utils.checkedIntModFunction(*type);
|
fun = m_utils.checkedIntModFunction(*type);
|
||||||
break;
|
break;
|
||||||
|
case Token::BitOr:
|
||||||
|
fun = "or";
|
||||||
|
break;
|
||||||
|
case Token::BitXor:
|
||||||
|
fun = "xor";
|
||||||
|
break;
|
||||||
|
case Token::BitAnd:
|
||||||
|
fun = "and";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1688,7 +2005,7 @@ void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue
|
|||||||
{
|
{
|
||||||
solAssert(!m_currentLValue, "");
|
solAssert(!m_currentLValue, "");
|
||||||
|
|
||||||
if (_expression.annotation().lValueRequested)
|
if (_expression.annotation().willBeWrittenTo)
|
||||||
{
|
{
|
||||||
m_currentLValue.emplace(std::move(_lvalue));
|
m_currentLValue.emplace(std::move(_lvalue));
|
||||||
solAssert(!_lvalue.type.dataStoredIn(DataLocation::CallData), "");
|
solAssert(!_lvalue.type.dataStoredIn(DataLocation::CallData), "");
|
||||||
@ -1749,3 +2066,118 @@ Type const& IRGeneratorForStatements::type(Expression const& _expression)
|
|||||||
solAssert(_expression.annotation().type, "Type of expression not set.");
|
solAssert(_expression.annotation().type, "Type of expression not set.");
|
||||||
return *_expression.annotation().type;
|
return *_expression.annotation().type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
|
||||||
|
{
|
||||||
|
Expression const& externalCall = _tryStatement.externalCall();
|
||||||
|
externalCall.accept(*this);
|
||||||
|
|
||||||
|
m_code << "switch iszero(" << m_context.trySuccessConditionVariable(externalCall) << ")\n";
|
||||||
|
|
||||||
|
m_code << "case 0 { // success case\n";
|
||||||
|
TryCatchClause const& successClause = *_tryStatement.clauses().front();
|
||||||
|
if (successClause.parameters())
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
for (ASTPointer<VariableDeclaration> const& varDecl: successClause.parameters()->parameters())
|
||||||
|
{
|
||||||
|
solAssert(varDecl, "");
|
||||||
|
define(m_context.addLocalVariable(*varDecl),
|
||||||
|
successClause.parameters()->parameters().size() == 1 ?
|
||||||
|
IRVariable(externalCall) :
|
||||||
|
IRVariable(externalCall).tupleComponent(i++)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
successClause.block().accept(*this);
|
||||||
|
m_code << "}\n";
|
||||||
|
|
||||||
|
m_code << "default { // failure case\n";
|
||||||
|
handleCatch(_tryStatement);
|
||||||
|
m_code << "}\n";
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement)
|
||||||
|
{
|
||||||
|
if (_tryStatement.structuredClause())
|
||||||
|
handleCatchStructuredAndFallback(*_tryStatement.structuredClause(), _tryStatement.fallbackClause());
|
||||||
|
else if (_tryStatement.fallbackClause())
|
||||||
|
handleCatchFallback(*_tryStatement.fallbackClause());
|
||||||
|
else
|
||||||
|
rethrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRGeneratorForStatements::handleCatchStructuredAndFallback(
|
||||||
|
TryCatchClause const& _structured,
|
||||||
|
TryCatchClause const* _fallback
|
||||||
|
)
|
||||||
|
{
|
||||||
|
solAssert(
|
||||||
|
_structured.parameters() &&
|
||||||
|
_structured.parameters()->parameters().size() == 1 &&
|
||||||
|
_structured.parameters()->parameters().front() &&
|
||||||
|
*_structured.parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
solAssert(m_context.evmVersion().supportsReturndata(), "");
|
||||||
|
|
||||||
|
// Try to decode the error message.
|
||||||
|
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
||||||
|
string const dataVariable = m_context.newYulVariable();
|
||||||
|
|
||||||
|
m_code << "let " << dataVariable << " := " << m_utils.tryDecodeErrorMessageFunction() << "()\n";
|
||||||
|
m_code << "switch iszero(" << dataVariable << ") \n";
|
||||||
|
m_code << "case 0 { // decoding success\n";
|
||||||
|
if (_structured.parameters())
|
||||||
|
{
|
||||||
|
solAssert(_structured.parameters()->parameters().size() == 1, "");
|
||||||
|
IRVariable const& var = m_context.addLocalVariable(*_structured.parameters()->parameters().front());
|
||||||
|
define(var) << dataVariable << "\n";
|
||||||
|
}
|
||||||
|
_structured.accept(*this);
|
||||||
|
m_code << "}\n";
|
||||||
|
m_code << "default { // decoding failure\n";
|
||||||
|
if (_fallback)
|
||||||
|
handleCatchFallback(*_fallback);
|
||||||
|
else
|
||||||
|
rethrow();
|
||||||
|
m_code << "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRGeneratorForStatements::handleCatchFallback(TryCatchClause const& _fallback)
|
||||||
|
{
|
||||||
|
if (_fallback.parameters())
|
||||||
|
{
|
||||||
|
solAssert(m_context.evmVersion().supportsReturndata(), "");
|
||||||
|
solAssert(
|
||||||
|
_fallback.parameters()->parameters().size() == 1 &&
|
||||||
|
_fallback.parameters()->parameters().front() &&
|
||||||
|
*_fallback.parameters()->parameters().front()->annotation().type == *TypeProvider::bytesMemory(),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
VariableDeclaration const& paramDecl = *_fallback.parameters()->parameters().front();
|
||||||
|
define(m_context.addLocalVariable(paramDecl)) << m_utils.extractReturndataFunction() << "()\n";
|
||||||
|
}
|
||||||
|
_fallback.accept(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRGeneratorForStatements::rethrow()
|
||||||
|
{
|
||||||
|
if (m_context.evmVersion().supportsReturndata())
|
||||||
|
m_code << R"(
|
||||||
|
returndatacopy(0, 0, returndatasize())
|
||||||
|
revert(0, returndatasize())
|
||||||
|
)"s;
|
||||||
|
else
|
||||||
|
m_code << "revert(0, 0) // rethrow\n"s;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IRGeneratorForStatements::visit(TryCatchClause const& _clause)
|
||||||
|
{
|
||||||
|
_clause.block().accept(*this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
#include <libsolidity/codegen/ir/IRLValue.h>
|
#include <libsolidity/codegen/ir/IRLValue.h>
|
||||||
#include <libsolidity/codegen/ir/IRVariable.h>
|
#include <libsolidity/codegen/ir/IRVariable.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -49,6 +51,9 @@ public:
|
|||||||
/// Generates code to initialize the given local variable.
|
/// Generates code to initialize the given local variable.
|
||||||
void initializeLocalVar(VariableDeclaration const& _varDecl);
|
void initializeLocalVar(VariableDeclaration const& _varDecl);
|
||||||
|
|
||||||
|
/// Calculates expression's value and returns variable where it was stored
|
||||||
|
IRVariable evaluateExpression(Expression const& _expression, Type const& _to);
|
||||||
|
|
||||||
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
|
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
|
||||||
bool visit(Conditional const& _conditional) override;
|
bool visit(Conditional const& _conditional) override;
|
||||||
bool visit(Assignment const& _assignment) override;
|
bool visit(Assignment const& _assignment) override;
|
||||||
@ -70,7 +75,26 @@ public:
|
|||||||
void endVisit(Identifier const& _identifier) override;
|
void endVisit(Identifier const& _identifier) override;
|
||||||
bool visit(Literal const& _literal) override;
|
bool visit(Literal const& _literal) override;
|
||||||
|
|
||||||
|
bool visit(TryStatement const& _tryStatement) override;
|
||||||
|
bool visit(TryCatchClause const& _tryCatchClause) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Handles all catch cases of a try statement, except the success-case.
|
||||||
|
void handleCatch(TryStatement const& _tryStatement);
|
||||||
|
void handleCatchStructuredAndFallback(
|
||||||
|
TryCatchClause const& _structured,
|
||||||
|
TryCatchClause const* _fallback
|
||||||
|
);
|
||||||
|
void handleCatchFallback(TryCatchClause const& _fallback);
|
||||||
|
|
||||||
|
/// Generates code to rethrow an exception.
|
||||||
|
void rethrow();
|
||||||
|
|
||||||
|
void handleVariableReference(
|
||||||
|
VariableDeclaration const& _variable,
|
||||||
|
Expression const& _referencingExpression
|
||||||
|
);
|
||||||
|
|
||||||
/// Appends code to call an external function with the given arguments.
|
/// Appends code to call an external function with the given arguments.
|
||||||
/// All involved expressions have already been visited.
|
/// All involved expressions have already been visited.
|
||||||
void appendExternalFunctionCall(
|
void appendExternalFunctionCall(
|
||||||
@ -123,7 +147,7 @@ private:
|
|||||||
/// @returns a fresh IR variable containing the value of the lvalue @a _lvalue.
|
/// @returns a fresh IR variable containing the value of the lvalue @a _lvalue.
|
||||||
IRVariable readFromLValue(IRLValue const& _lvalue);
|
IRVariable readFromLValue(IRLValue const& _lvalue);
|
||||||
|
|
||||||
/// Stores the given @a _lvalue in m_currentLValue, if it will be written to (lValueRequested). Otherwise
|
/// Stores the given @a _lvalue in m_currentLValue, if it will be written to (willBeWrittenTo). Otherwise
|
||||||
/// defines the expression @a _expression by reading the value from @a _lvalue.
|
/// defines the expression @a _expression by reading the value from @a _lvalue.
|
||||||
void setLValue(Expression const& _expression, IRLValue _lvalue);
|
void setLValue(Expression const& _expression, IRLValue _lvalue);
|
||||||
void generateLoop(
|
void generateLoop(
|
||||||
|
@ -29,8 +29,7 @@ class Expression;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
|
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
|
||||||
* of a specific S
|
* of a specific Solidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
|
||||||
* olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
|
|
||||||
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
|
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
|
||||||
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
|
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
|
||||||
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference
|
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference
|
||||||
|
@ -304,7 +304,10 @@ void BMC::endVisit(UnaryOperation const& _op)
|
|||||||
{
|
{
|
||||||
SMTEncoder::endVisit(_op);
|
SMTEncoder::endVisit(_op);
|
||||||
|
|
||||||
if (_op.annotation().type->category() == Type::Category::RationalNumber)
|
if (
|
||||||
|
_op.annotation().type->category() == Type::Category::RationalNumber ||
|
||||||
|
_op.annotation().type->category() == Type::Category::FixedPoint
|
||||||
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (_op.getOperator())
|
switch (_op.getOperator())
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
|
using namespace solidity::util;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
@ -130,15 +131,10 @@ bool CHC::visit(ContractDefinition const& _contract)
|
|||||||
|
|
||||||
clearIndices(&_contract);
|
clearIndices(&_contract);
|
||||||
|
|
||||||
auto errorFunctionSort = make_shared<smt::FunctionSort>(
|
|
||||||
vector<smt::SortPointer>(),
|
|
||||||
smt::SortProvider::boolSort
|
|
||||||
);
|
|
||||||
|
|
||||||
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
||||||
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
|
m_errorPredicate = createSymbolicBlock(arity0FunctionSort(), "error_" + suffix);
|
||||||
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
|
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
|
||||||
m_implicitConstructorPredicate = createSymbolicBlock(interfaceSort(), "implicit_constructor_" + suffix);
|
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix);
|
||||||
auto stateExprs = currentStateVariables();
|
auto stateExprs = currentStateVariables();
|
||||||
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||||
|
|
||||||
@ -148,15 +144,7 @@ bool CHC::visit(ContractDefinition const& _contract)
|
|||||||
|
|
||||||
void CHC::endVisit(ContractDefinition const& _contract)
|
void CHC::endVisit(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
for (auto const& var: m_stateVariables)
|
auto implicitConstructor = (*m_implicitConstructorPredicate)({});
|
||||||
{
|
|
||||||
solAssert(m_context.knownVariable(*var), "");
|
|
||||||
auto const& symbVar = m_context.variable(*var);
|
|
||||||
symbVar->resetIndex();
|
|
||||||
m_context.setZeroValue(*var);
|
|
||||||
symbVar->increaseIndex();
|
|
||||||
}
|
|
||||||
auto implicitConstructor = (*m_implicitConstructorPredicate)(initialStateVariables());
|
|
||||||
connectBlocks(genesis(), implicitConstructor);
|
connectBlocks(genesis(), implicitConstructor);
|
||||||
m_currentBlock = implicitConstructor;
|
m_currentBlock = implicitConstructor;
|
||||||
m_context.addAssertion(m_error.currentValue() == 0);
|
m_context.addAssertion(m_error.currentValue() == 0);
|
||||||
@ -643,19 +631,19 @@ set<Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTNode const*
|
|||||||
|
|
||||||
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
|
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
vector<VariableDeclaration const*> stateVars;
|
return fold(
|
||||||
for (auto const& contract: _contract.annotation().linearizedBaseContracts)
|
_contract.annotation().linearizedBaseContracts,
|
||||||
for (auto var: contract->stateVariables())
|
vector<VariableDeclaration const*>{},
|
||||||
stateVars.push_back(var);
|
[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
|
||||||
return stateVars;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
vector<smt::SortPointer> stateSorts;
|
return applyMap(
|
||||||
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||||
stateSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
|
||||||
return stateSorts;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
smt::SortPointer CHC::constructorSort()
|
smt::SortPointer CHC::constructorSort()
|
||||||
@ -682,6 +670,14 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::SortPointer CHC::arity0FunctionSort()
|
||||||
|
{
|
||||||
|
return make_shared<smt::FunctionSort>(
|
||||||
|
vector<smt::SortPointer>(),
|
||||||
|
smt::SortProvider::boolSort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// A function in the symbolic CFG requires:
|
/// A function in the symbolic CFG requires:
|
||||||
/// - Index of failed assertion. 0 means no assertion failed.
|
/// - Index of failed assertion. 0 means no assertion failed.
|
||||||
/// - 2 sets of state variables:
|
/// - 2 sets of state variables:
|
||||||
@ -695,12 +691,9 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
|||||||
/// - 1 set of output variables
|
/// - 1 set of output variables
|
||||||
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
vector<smt::SortPointer> inputSorts;
|
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||||
for (auto const& var: _function.parameters())
|
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||||
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||||
vector<smt::SortPointer> outputSorts;
|
|
||||||
for (auto const& var: _function.returnParameters())
|
|
||||||
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
|
||||||
return make_shared<smt::FunctionSort>(
|
return make_shared<smt::FunctionSort>(
|
||||||
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
||||||
smt::SortProvider::boolSort
|
smt::SortProvider::boolSort
|
||||||
@ -715,11 +708,9 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
|
|||||||
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
|
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
|
||||||
solAssert(fSort, "");
|
solAssert(fSort, "");
|
||||||
|
|
||||||
vector<smt::SortPointer> varSorts;
|
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||||
for (auto const& var: m_currentFunction->localVariables())
|
|
||||||
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
|
||||||
return make_shared<smt::FunctionSort>(
|
return make_shared<smt::FunctionSort>(
|
||||||
fSort->domain + varSorts,
|
fSort->domain + applyMap(m_currentFunction->localVariables(), smtSort),
|
||||||
smt::SortProvider::boolSort
|
smt::SortProvider::boolSort
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -729,11 +720,9 @@ smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractD
|
|||||||
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||||
auto sorts = stateSorts(_contract);
|
auto sorts = stateSorts(_contract);
|
||||||
|
|
||||||
vector<smt::SortPointer> inputSorts, outputSorts;
|
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||||
for (auto const& var: _function.parameters())
|
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||||
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||||
for (auto const& var: _function.returnParameters())
|
|
||||||
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
|
||||||
return make_shared<smt::FunctionSort>(
|
return make_shared<smt::FunctionSort>(
|
||||||
vector<smt::SortPointer>{smt::SortProvider::intSort} + sorts + inputSorts + sorts + outputSorts,
|
vector<smt::SortPointer>{smt::SortProvider::intSort} + sorts + inputSorts + sorts + outputSorts,
|
||||||
smt::SortProvider::boolSort
|
smt::SortProvider::boolSort
|
||||||
@ -769,9 +758,10 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
|||||||
|
|
||||||
smt::Expression CHC::interface()
|
smt::Expression CHC::interface()
|
||||||
{
|
{
|
||||||
vector<smt::Expression> paramExprs;
|
auto paramExprs = applyMap(
|
||||||
for (auto const& var: m_stateVariables)
|
m_stateVariables,
|
||||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
[this](auto _var) { return m_context.variable(*_var)->currentValue(); }
|
||||||
|
);
|
||||||
return (*m_interfaces.at(m_currentContract))(paramExprs);
|
return (*m_interfaces.at(m_currentContract))(paramExprs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -803,11 +793,9 @@ smt::Expression CHC::summary(FunctionDefinition const& _function)
|
|||||||
vector<smt::Expression> args{m_error.currentValue()};
|
vector<smt::Expression> args{m_error.currentValue()};
|
||||||
auto contract = _function.annotation().contract;
|
auto contract = _function.annotation().contract;
|
||||||
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
|
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
|
||||||
for (auto const& var: _function.parameters())
|
args += applyMap(_function.parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); });
|
||||||
args.push_back(m_context.variable(*var)->valueAtIndex(0));
|
|
||||||
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||||
for (auto const& var: _function.returnParameters())
|
args += applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||||
args.push_back(m_context.variable(*var)->currentValue());
|
|
||||||
return (*m_summaries.at(m_currentContract).at(&_function))(args);
|
return (*m_summaries.at(m_currentContract).at(&_function))(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,27 +842,21 @@ vector<smt::Expression> CHC::initialStateVariables()
|
|||||||
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index)
|
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index)
|
||||||
{
|
{
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
vector<smt::Expression> exprs;
|
return applyMap(m_stateVariables, [&](auto _var) { return valueAtIndex(*_var, _index); });
|
||||||
for (auto const& var: m_stateVariables)
|
|
||||||
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
|
||||||
return exprs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
|
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
vector<smt::Expression> exprs;
|
return applyMap(
|
||||||
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||||
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
[&](auto _var) { return valueAtIndex(*_var, _index); }
|
||||||
return exprs;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smt::Expression> CHC::currentStateVariables()
|
vector<smt::Expression> CHC::currentStateVariables()
|
||||||
{
|
{
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
vector<smt::Expression> exprs;
|
return applyMap(m_stateVariables, [this](auto _var) { return currentValue(*_var); });
|
||||||
for (auto const& var: m_stateVariables)
|
|
||||||
exprs.push_back(m_context.variable(*var)->currentValue());
|
|
||||||
return exprs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smt::Expression> CHC::currentFunctionVariables()
|
vector<smt::Expression> CHC::currentFunctionVariables()
|
||||||
@ -886,9 +868,7 @@ vector<smt::Expression> CHC::currentFunctionVariables()
|
|||||||
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
|
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
|
||||||
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
|
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
|
||||||
}
|
}
|
||||||
vector<smt::Expression> returnExprs;
|
auto returnExprs = applyMap(m_currentFunction->returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||||
for (auto const& var: m_currentFunction->returnParameters())
|
|
||||||
returnExprs.push_back(m_context.variable(*var)->currentValue());
|
|
||||||
return vector<smt::Expression>{m_error.currentValue()} +
|
return vector<smt::Expression>{m_error.currentValue()} +
|
||||||
initialStateVariables() +
|
initialStateVariables() +
|
||||||
initInputExprs +
|
initInputExprs +
|
||||||
@ -899,11 +879,10 @@ vector<smt::Expression> CHC::currentFunctionVariables()
|
|||||||
|
|
||||||
vector<smt::Expression> CHC::currentBlockVariables()
|
vector<smt::Expression> CHC::currentBlockVariables()
|
||||||
{
|
{
|
||||||
vector<smt::Expression> paramExprs;
|
|
||||||
if (m_currentFunction)
|
if (m_currentFunction)
|
||||||
for (auto const& var: m_currentFunction->localVariables())
|
return currentFunctionVariables() + applyMap(m_currentFunction->localVariables(), [this](auto _var) { return currentValue(*_var); });
|
||||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
|
||||||
return currentFunctionVariables() + paramExprs;
|
return currentFunctionVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
|
string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
|
||||||
@ -958,8 +937,7 @@ smt::Expression CHC::predicate(FunctionCall const& _funCall)
|
|||||||
m_context.variable(*param)->increaseIndex();
|
m_context.variable(*param)->increaseIndex();
|
||||||
else
|
else
|
||||||
createVariable(*param);
|
createVariable(*param);
|
||||||
for (auto const& var: function->returnParameters())
|
args += applyMap(function->returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||||
args.push_back(m_context.variable(*var)->currentValue());
|
|
||||||
|
|
||||||
if (contract->isLibrary())
|
if (contract->isLibrary())
|
||||||
return (*m_summaries.at(contract).at(function))(args);
|
return (*m_summaries.at(contract).at(function))(args);
|
||||||
|
@ -106,6 +106,7 @@ private:
|
|||||||
smt::SortPointer constructorSort();
|
smt::SortPointer constructorSort();
|
||||||
smt::SortPointer interfaceSort();
|
smt::SortPointer interfaceSort();
|
||||||
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
|
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||||
|
smt::SortPointer arity0FunctionSort();
|
||||||
smt::SortPointer sort(FunctionDefinition const& _function);
|
smt::SortPointer sort(FunctionDefinition const& _function);
|
||||||
smt::SortPointer sort(ASTNode const* _block);
|
smt::SortPointer sort(ASTNode const* _block);
|
||||||
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
|
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
|
||||||
|
@ -196,6 +196,16 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
|
|||||||
solAssert(sortSort, "");
|
solAssert(sortSort, "");
|
||||||
return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1]));
|
return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1]));
|
||||||
}
|
}
|
||||||
|
else if (n == "tuple_get")
|
||||||
|
{
|
||||||
|
shared_ptr<TupleSort> tupleSort = std::dynamic_pointer_cast<TupleSort>(_expr.arguments[0].sort);
|
||||||
|
solAssert(tupleSort, "");
|
||||||
|
CVC4::DatatypeType tt = m_context.mkTupleType(cvc4Sort(tupleSort->components));
|
||||||
|
CVC4::Datatype const& dt = tt.getDatatype();
|
||||||
|
size_t index = std::stoi(_expr.arguments[1].name);
|
||||||
|
CVC4::Expr s = dt[0][index].getSelector();
|
||||||
|
return m_context.mkExpr(CVC4::kind::APPLY_SELECTOR, s, arguments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
solAssert(false, "");
|
solAssert(false, "");
|
||||||
}
|
}
|
||||||
@ -229,6 +239,11 @@ CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)
|
|||||||
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
|
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
|
||||||
return m_context.mkArrayType(cvc4Sort(*arraySort.domain), cvc4Sort(*arraySort.range));
|
return m_context.mkArrayType(cvc4Sort(*arraySort.domain), cvc4Sort(*arraySort.range));
|
||||||
}
|
}
|
||||||
|
case Kind::Tuple:
|
||||||
|
{
|
||||||
|
auto const& tupleSort = dynamic_cast<TupleSort const&>(_sort);
|
||||||
|
return m_context.mkTupleType(cvc4Sort(tupleSort.components));
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,9 @@ void SMTEncoder::endVisit(ContractDefinition const& _contract)
|
|||||||
|
|
||||||
solAssert(m_currentContract == &_contract, "");
|
solAssert(m_currentContract == &_contract, "");
|
||||||
m_currentContract = nullptr;
|
m_currentContract = nullptr;
|
||||||
|
|
||||||
|
if (m_callStack.empty())
|
||||||
|
m_context.popSolver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SMTEncoder::endVisit(VariableDeclaration const& _varDecl)
|
void SMTEncoder::endVisit(VariableDeclaration const& _varDecl)
|
||||||
@ -191,7 +194,8 @@ void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation,
|
|||||||
pushCallStack({_definition, _invocation});
|
pushCallStack({_definition, _invocation});
|
||||||
if (auto modifier = dynamic_cast<ModifierDefinition const*>(_definition))
|
if (auto modifier = dynamic_cast<ModifierDefinition const*>(_definition))
|
||||||
{
|
{
|
||||||
modifier->body().accept(*this);
|
if (modifier->isImplemented())
|
||||||
|
modifier->body().accept(*this);
|
||||||
popCallStack();
|
popCallStack();
|
||||||
}
|
}
|
||||||
else if (auto function = dynamic_cast<FunctionDefinition const*>(_definition))
|
else if (auto function = dynamic_cast<FunctionDefinition const*>(_definition))
|
||||||
@ -296,16 +300,22 @@ void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
|
|||||||
{
|
{
|
||||||
auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*init));
|
auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*init));
|
||||||
solAssert(symbTuple, "");
|
solAssert(symbTuple, "");
|
||||||
auto const& components = symbTuple->components();
|
auto const& symbComponents = symbTuple->components();
|
||||||
|
|
||||||
|
auto tupleType = dynamic_cast<TupleType const*>(init->annotation().type);
|
||||||
|
solAssert(tupleType, "");
|
||||||
|
solAssert(tupleType->components().size() == symbTuple->components().size(), "");
|
||||||
|
auto const& components = tupleType->components();
|
||||||
|
|
||||||
auto const& declarations = _varDecl.declarations();
|
auto const& declarations = _varDecl.declarations();
|
||||||
solAssert(components.size() == declarations.size(), "");
|
solAssert(symbComponents.size() == declarations.size(), "");
|
||||||
for (unsigned i = 0; i < declarations.size(); ++i)
|
for (unsigned i = 0; i < declarations.size(); ++i)
|
||||||
if (
|
if (
|
||||||
components.at(i) &&
|
components.at(i) &&
|
||||||
declarations.at(i) &&
|
declarations.at(i) &&
|
||||||
m_context.knownVariable(*declarations.at(i))
|
m_context.knownVariable(*declarations.at(i))
|
||||||
)
|
)
|
||||||
assignment(*declarations.at(i), components.at(i)->currentValue(declarations.at(i)->type()));
|
assignment(*declarations.at(i), symbTuple->component(i, components.at(i), declarations.at(i)->type()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_context.knownVariable(*_varDecl.declarations().front()))
|
else if (m_context.knownVariable(*_varDecl.declarations().front()))
|
||||||
@ -354,7 +364,7 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
|
|||||||
{
|
{
|
||||||
auto const& type = _assignment.annotation().type;
|
auto const& type = _assignment.annotation().type;
|
||||||
vector<smt::Expression> rightArguments;
|
vector<smt::Expression> rightArguments;
|
||||||
if (_assignment.rightHandSide().annotation().type->category() == Type::Category::Tuple)
|
if (auto const* tupleTypeRight = dynamic_cast<TupleType const*>(_assignment.rightHandSide().annotation().type))
|
||||||
{
|
{
|
||||||
auto symbTupleLeft = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_assignment.leftHandSide()));
|
auto symbTupleLeft = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_assignment.leftHandSide()));
|
||||||
solAssert(symbTupleLeft, "");
|
solAssert(symbTupleLeft, "");
|
||||||
@ -365,17 +375,16 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
|
|||||||
auto const& rightComponents = symbTupleRight->components();
|
auto const& rightComponents = symbTupleRight->components();
|
||||||
solAssert(leftComponents.size() == rightComponents.size(), "");
|
solAssert(leftComponents.size() == rightComponents.size(), "");
|
||||||
|
|
||||||
for (unsigned i = 0; i < leftComponents.size(); ++i)
|
auto tupleTypeLeft = dynamic_cast<TupleType const*>(_assignment.leftHandSide().annotation().type);
|
||||||
{
|
solAssert(tupleTypeLeft, "");
|
||||||
auto const& left = leftComponents.at(i);
|
solAssert(tupleTypeLeft->components().size() == leftComponents.size(), "");
|
||||||
auto const& right = rightComponents.at(i);
|
auto const& typesLeft = tupleTypeLeft->components();
|
||||||
/// Right hand side tuple component cannot be empty.
|
|
||||||
solAssert(right, "");
|
solAssert(tupleTypeRight->components().size() == rightComponents.size(), "");
|
||||||
if (left)
|
auto const& typesRight = tupleTypeRight->components();
|
||||||
rightArguments.push_back(right->currentValue(left->originalType()));
|
|
||||||
else
|
for (unsigned i = 0; i < rightComponents.size(); ++i)
|
||||||
rightArguments.push_back(right->currentValue());
|
rightArguments.push_back(symbTupleRight->component(i, typesRight.at(i), typesLeft.at(i)));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -418,17 +427,16 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
|
|||||||
solAssert(symbComponents.size() == tupleComponents->size(), "");
|
solAssert(symbComponents.size() == tupleComponents->size(), "");
|
||||||
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
||||||
{
|
{
|
||||||
auto sComponent = symbComponents.at(i);
|
|
||||||
auto tComponent = tupleComponents->at(i);
|
auto tComponent = tupleComponents->at(i);
|
||||||
if (sComponent && tComponent)
|
if (tComponent)
|
||||||
{
|
{
|
||||||
if (auto varDecl = identifierToVariable(*tComponent))
|
if (auto varDecl = identifierToVariable(*tComponent))
|
||||||
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
|
m_context.addAssertion(symbTuple->component(i) == currentValue(*varDecl));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!m_context.knownExpression(*tComponent))
|
if (!m_context.knownExpression(*tComponent))
|
||||||
createExpr(*tComponent);
|
createExpr(*tComponent);
|
||||||
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
|
m_context.addAssertion(symbTuple->component(i) == expr(*tComponent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,6 +458,9 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
|
|||||||
|
|
||||||
createExpr(_op);
|
createExpr(_op);
|
||||||
|
|
||||||
|
if (_op.annotation().type->category() == Type::Category::FixedPoint)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (_op.getOperator())
|
switch (_op.getOperator())
|
||||||
{
|
{
|
||||||
case Token::Not: // !
|
case Token::Not: // !
|
||||||
@ -463,7 +474,7 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
|
|||||||
{
|
{
|
||||||
|
|
||||||
solAssert(smt::isInteger(_op.annotation().type->category()), "");
|
solAssert(smt::isInteger(_op.annotation().type->category()), "");
|
||||||
solAssert(_op.subExpression().annotation().lValueRequested, "");
|
solAssert(_op.subExpression().annotation().willBeWrittenTo, "");
|
||||||
if (auto identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
|
if (auto identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
|
||||||
{
|
{
|
||||||
auto decl = identifierToVariable(*identifier);
|
auto decl = identifierToVariable(*identifier);
|
||||||
@ -658,7 +669,6 @@ void SMTEncoder::initFunction(FunctionDefinition const& _function)
|
|||||||
{
|
{
|
||||||
solAssert(m_callStack.empty(), "");
|
solAssert(m_callStack.empty(), "");
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
m_context.reset();
|
|
||||||
m_context.pushSolver();
|
m_context.pushSolver();
|
||||||
m_pathConditions.clear();
|
m_pathConditions.clear();
|
||||||
pushCallStack({&_function, nullptr});
|
pushCallStack({&_function, nullptr});
|
||||||
@ -700,7 +710,7 @@ void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
|
|||||||
|
|
||||||
void SMTEncoder::endVisit(Identifier const& _identifier)
|
void SMTEncoder::endVisit(Identifier const& _identifier)
|
||||||
{
|
{
|
||||||
if (_identifier.annotation().lValueRequested)
|
if (_identifier.annotation().willBeWrittenTo)
|
||||||
{
|
{
|
||||||
// Will be translated as part of the node that requested the lvalue.
|
// Will be translated as part of the node that requested the lvalue.
|
||||||
}
|
}
|
||||||
@ -807,13 +817,15 @@ void SMTEncoder::endVisit(Return const& _return)
|
|||||||
{
|
{
|
||||||
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*_return.expression()));
|
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*_return.expression()));
|
||||||
solAssert(symbTuple, "");
|
solAssert(symbTuple, "");
|
||||||
auto const& components = symbTuple->components();
|
solAssert(symbTuple->components().size() == returnParams.size(), "");
|
||||||
solAssert(components.size() == returnParams.size(), "");
|
|
||||||
|
auto const* tupleType = dynamic_cast<TupleType const*>(_return.expression()->annotation().type);
|
||||||
|
solAssert(tupleType, "");
|
||||||
|
auto const& types = tupleType->components();
|
||||||
|
solAssert(types.size() == returnParams.size(), "");
|
||||||
|
|
||||||
for (unsigned i = 0; i < returnParams.size(); ++i)
|
for (unsigned i = 0; i < returnParams.size(); ++i)
|
||||||
{
|
m_context.addAssertion(symbTuple->component(i, types.at(i), returnParams.at(i)->type()) == m_context.newValue(*returnParams.at(i)));
|
||||||
solAssert(components.at(i), "");
|
|
||||||
m_context.addAssertion(components.at(i)->currentValue(returnParams.at(i)->type()) == m_context.newValue(*returnParams.at(i)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (returnParams.size() == 1)
|
else if (returnParams.size() == 1)
|
||||||
m_context.addAssertion(expr(*_return.expression(), returnParams.front()->type()) == m_context.newValue(*returnParams.front()));
|
m_context.addAssertion(expr(*_return.expression(), returnParams.front()->type()) == m_context.newValue(*returnParams.front()));
|
||||||
@ -1676,14 +1688,10 @@ void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
|
|||||||
solAssert(symbComponents.size() == returnParams.size(), "");
|
solAssert(symbComponents.size() == returnParams.size(), "");
|
||||||
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
||||||
{
|
{
|
||||||
auto sComponent = symbComponents.at(i);
|
|
||||||
auto param = returnParams.at(i);
|
auto param = returnParams.at(i);
|
||||||
solAssert(param, "");
|
solAssert(param, "");
|
||||||
if (sComponent)
|
solAssert(m_context.knownVariable(*param), "");
|
||||||
{
|
m_context.addAssertion(symbTuple->component(i) == currentValue(*param));
|
||||||
solAssert(m_context.knownVariable(*param), "");
|
|
||||||
m_context.addAssertion(sComponent->currentValue() == currentValue(*param));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (returnParams.size() == 1)
|
else if (returnParams.size() == 1)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user