mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
commit
6728e3ef94
@ -338,7 +338,7 @@ jobs:
|
||||
|
||||
chk_proofs:
|
||||
docker:
|
||||
- image: buildpack-deps:disco
|
||||
- image: buildpack-deps:latest
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
@ -347,8 +347,8 @@ jobs:
|
||||
name: Z3 python deps
|
||||
command: |
|
||||
apt-get -qq update
|
||||
apt-get -qy install python-pip
|
||||
pip install --user z3-solver
|
||||
apt-get -qy install python3-pip
|
||||
pip3 install --user z3-solver
|
||||
- run: *run_proofs
|
||||
|
||||
chk_docs_pragma_min_version:
|
||||
@ -661,7 +661,7 @@ jobs:
|
||||
|
||||
t_ems_solcjs:
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904
|
||||
- image: buildpack-deps:latest
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
|
@ -52,8 +52,8 @@ then
|
||||
rm -rf z3-4.8.7-x64-osx-10.14.6
|
||||
|
||||
# evmone
|
||||
wget https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-darwin-x86_64.tar.gz
|
||||
tar xzpf evmone-0.3.0-darwin-x86_64.tar.gz -C /usr/local
|
||||
rm -f 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.4.0-darwin-x86_64.tar.gz -C /usr/local
|
||||
rm -f evmone-0.4.0-darwin-x86_64.tar.gz
|
||||
fi
|
||||
|
||||
|
20
Changelog.md
20
Changelog.md
@ -12,7 +12,7 @@ Compiler Features:
|
||||
Bugfixes:
|
||||
|
||||
|
||||
### 0.6.6 (unreleased)
|
||||
### 0.6.7 (unreleased)
|
||||
|
||||
Language Features:
|
||||
|
||||
@ -21,7 +21,25 @@ Compiler Features:
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* 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: 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)
|
||||
|
||||
Important Bugfixes:
|
||||
* Fix tuple assignments with components occupying multiple stack slots and different stack size on left- and right-hand-side.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* AST export: Export `immutable` property in the field `mutability`.
|
||||
* SMTChecker: Fix internal error in the CHC engine when calling inherited functions internally.
|
||||
* Type Checker: Error when trying to encode functions with call options gas and value set.
|
||||
|
||||
|
||||
|
||||
|
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
|
||||
(left) side with zero-bytes such that the length is 32 bytes.
|
||||
- ``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``
|
||||
- ``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
|
||||
|
@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"name": "TupleAssignmentMultiStackSlotComponents",
|
||||
"summary": "Tuple assignments with components that occupy several stack slots, i.e. nested tuples, pointers to external functions or references to dynamically sized calldata arrays, can result in invalid values.",
|
||||
"description": "Tuple assignments did not correctly account for tuple components that occupy multiple stack slots in case the number of stack slots differs between left-hand-side and right-hand-side. This can either happen in the presence of nested tuples or if the right-hand-side contains external function pointers or references to dynamic calldata arrays, while the left-hand-side contains an omission.",
|
||||
"introduced": "0.1.6",
|
||||
"fixed": "0.6.6",
|
||||
"severity": "very low"
|
||||
},
|
||||
{
|
||||
"name": "MemoryArrayCreationOverflow",
|
||||
"summary": "The creation of very large memory arrays can result in overlapping memory regions and thus memory corruption.",
|
||||
|
@ -111,6 +111,7 @@
|
||||
},
|
||||
"0.1.6": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -131,6 +132,7 @@
|
||||
},
|
||||
"0.1.7": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
"ZeroFunctionSelector",
|
||||
@ -151,6 +153,7 @@
|
||||
},
|
||||
"0.2.0": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
@ -172,6 +175,7 @@
|
||||
},
|
||||
"0.2.1": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
@ -193,6 +197,7 @@
|
||||
},
|
||||
"0.2.2": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"ExpExponentCleanup",
|
||||
"NestedArrayFunctionCallDecoder",
|
||||
@ -214,6 +219,7 @@
|
||||
},
|
||||
"0.3.0": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -237,6 +243,7 @@
|
||||
},
|
||||
"0.3.1": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -259,6 +266,7 @@
|
||||
},
|
||||
"0.3.2": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -281,6 +289,7 @@
|
||||
},
|
||||
"0.3.3": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -302,6 +311,7 @@
|
||||
},
|
||||
"0.3.4": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -323,6 +333,7 @@
|
||||
},
|
||||
"0.3.5": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -344,6 +355,7 @@
|
||||
},
|
||||
"0.3.6": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -363,6 +375,7 @@
|
||||
},
|
||||
"0.4.0": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -382,6 +395,7 @@
|
||||
},
|
||||
"0.4.1": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -401,6 +415,7 @@
|
||||
},
|
||||
"0.4.10": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -418,6 +433,7 @@
|
||||
},
|
||||
"0.4.11": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -434,6 +450,7 @@
|
||||
},
|
||||
"0.4.12": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -449,6 +466,7 @@
|
||||
},
|
||||
"0.4.13": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -464,6 +482,7 @@
|
||||
},
|
||||
"0.4.14": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -478,6 +497,7 @@
|
||||
},
|
||||
"0.4.15": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -491,6 +511,7 @@
|
||||
},
|
||||
"0.4.16": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -506,6 +527,7 @@
|
||||
},
|
||||
"0.4.17": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -522,6 +544,7 @@
|
||||
},
|
||||
"0.4.18": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -537,6 +560,7 @@
|
||||
},
|
||||
"0.4.19": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -553,6 +577,7 @@
|
||||
},
|
||||
"0.4.2": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -571,6 +596,7 @@
|
||||
},
|
||||
"0.4.20": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -587,6 +613,7 @@
|
||||
},
|
||||
"0.4.21": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -603,6 +630,7 @@
|
||||
},
|
||||
"0.4.22": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -619,6 +647,7 @@
|
||||
},
|
||||
"0.4.23": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -634,6 +663,7 @@
|
||||
},
|
||||
"0.4.24": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -649,6 +679,7 @@
|
||||
},
|
||||
"0.4.25": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -662,6 +693,7 @@
|
||||
},
|
||||
"0.4.26": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -672,6 +704,7 @@
|
||||
},
|
||||
"0.4.3": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -689,6 +722,7 @@
|
||||
},
|
||||
"0.4.4": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"IncorrectEventSignatureInLibraries_0.4.x",
|
||||
@ -705,6 +739,7 @@
|
||||
},
|
||||
"0.4.5": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
@ -723,6 +758,7 @@
|
||||
},
|
||||
"0.4.6": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"UninitializedFunctionPointerInConstructor_0.4.x",
|
||||
@ -740,6 +776,7 @@
|
||||
},
|
||||
"0.4.7": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -757,6 +794,7 @@
|
||||
},
|
||||
"0.4.8": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -774,6 +812,7 @@
|
||||
},
|
||||
"0.4.9": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -791,6 +830,7 @@
|
||||
},
|
||||
"0.5.0": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -804,6 +844,7 @@
|
||||
},
|
||||
"0.5.1": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -817,6 +858,7 @@
|
||||
},
|
||||
"0.5.10": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5",
|
||||
@ -826,6 +868,7 @@
|
||||
},
|
||||
"0.5.11": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5"
|
||||
@ -834,6 +877,7 @@
|
||||
},
|
||||
"0.5.12": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5"
|
||||
@ -842,6 +886,7 @@
|
||||
},
|
||||
"0.5.13": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5"
|
||||
@ -850,6 +895,7 @@
|
||||
},
|
||||
"0.5.14": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5",
|
||||
@ -859,6 +905,7 @@
|
||||
},
|
||||
"0.5.15": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5"
|
||||
@ -867,6 +914,7 @@
|
||||
},
|
||||
"0.5.16": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden"
|
||||
],
|
||||
@ -874,12 +922,14 @@
|
||||
},
|
||||
"0.5.17": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow"
|
||||
],
|
||||
"released": "2020-03-17"
|
||||
},
|
||||
"0.5.2": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -893,6 +943,7 @@
|
||||
},
|
||||
"0.5.3": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -906,6 +957,7 @@
|
||||
},
|
||||
"0.5.4": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -919,6 +971,7 @@
|
||||
},
|
||||
"0.5.5": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"SignedArrayStorageCopy",
|
||||
@ -934,6 +987,7 @@
|
||||
},
|
||||
"0.5.6": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
|
||||
@ -949,6 +1003,7 @@
|
||||
},
|
||||
"0.5.7": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
|
||||
@ -962,6 +1017,7 @@
|
||||
},
|
||||
"0.5.8": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5",
|
||||
@ -974,6 +1030,7 @@
|
||||
},
|
||||
"0.5.9": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"privateCanBeOverridden",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue0.5",
|
||||
@ -985,6 +1042,7 @@
|
||||
},
|
||||
"0.6.0": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow",
|
||||
"YulOptimizerRedundantAssignmentBreakContinue"
|
||||
],
|
||||
@ -992,30 +1050,40 @@
|
||||
},
|
||||
"0.6.1": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow"
|
||||
],
|
||||
"released": "2020-01-02"
|
||||
},
|
||||
"0.6.2": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow"
|
||||
],
|
||||
"released": "2020-01-27"
|
||||
},
|
||||
"0.6.3": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow"
|
||||
],
|
||||
"released": "2020-02-18"
|
||||
},
|
||||
"0.6.4": {
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents",
|
||||
"MemoryArrayCreationOverflow"
|
||||
],
|
||||
"released": "2020-03-10"
|
||||
},
|
||||
"0.6.5": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"TupleAssignmentMultiStackSlotComponents"
|
||||
],
|
||||
"released": "2020-04-06"
|
||||
},
|
||||
"0.6.6": {
|
||||
"bugs": [],
|
||||
"released": "2020-04-09"
|
||||
}
|
||||
}
|
@ -146,10 +146,14 @@ html_theme = 'sphinx_rtd_theme'
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
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
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# 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,
|
||||
# using the given strftime format.
|
||||
|
@ -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>`.
|
||||
|
||||
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 { ... }``
|
||||
(without the ``function`` keyword).
|
||||
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
|
||||
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>`
|
||||
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
|
||||
@ -245,7 +245,7 @@ contract cannot receive Ether through regular transactions and throws an
|
||||
exception.
|
||||
|
||||
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
|
||||
will consume more gas than the 2300 gas stipend:
|
||||
|
||||
@ -265,7 +265,7 @@ will consume more gas than the 2300 gas stipend:
|
||||
|
||||
.. warning::
|
||||
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``.
|
||||
|
||||
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``.
|
||||
For state variables, ``external`` is not possible.
|
||||
|
||||
``external``:
|
||||
``external``
|
||||
External functions are part of the contract interface,
|
||||
which means they can be called from other contracts and
|
||||
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
|
||||
is not copied from calldata to memory.
|
||||
|
||||
``public``:
|
||||
``public``
|
||||
Public functions are part of the contract interface
|
||||
and can be either called internally or via
|
||||
messages. For public state variables, an automatic getter
|
||||
function (see below) is generated.
|
||||
|
||||
``internal``:
|
||||
``internal``
|
||||
Those functions and state variables can only be
|
||||
accessed internally (i.e. from within the current contract
|
||||
or contracts deriving from it), without using ``this``.
|
||||
|
||||
``private``:
|
||||
``private``
|
||||
Private functions and state variables are only
|
||||
visible for the contract they are defined in and not in
|
||||
derived contracts.
|
||||
|
@ -81,26 +81,55 @@ Thank you for your help!
|
||||
Running the compiler tests
|
||||
==========================
|
||||
|
||||
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
|
||||
but for quicker feedback, you might want to run specific tests.
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
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
|
||||
`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.
|
||||
|
||||
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,
|
||||
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on MacOS. If it is not found, the relevant tests
|
||||
are skipped. To run all tests, download the library from
|
||||
`Github <https://github.com/ethereum/evmone/releases/tag/v0.3.0>`_
|
||||
and either place it in the project root path or inside the ``deps`` folder.
|
||||
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS. If it is not found, tests that
|
||||
use it are skipped. These tests are ``libsolididty/semanticTests``, ``libsolidity/GasCosts``,
|
||||
``libsolidity/SolidityEndToEndTest``, part of the soltest suite. To run all tests, download the library from
|
||||
`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:
|
||||
``./scripts/soltest.sh --no-smt``.
|
||||
If the ``libz3`` library is not installed on your system, you should disable the
|
||||
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.
|
||||
|
||||
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,
|
||||
@ -109,20 +138,10 @@ See especially:
|
||||
|
||||
.. 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``.
|
||||
|
||||
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".
|
||||
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 ..
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
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 CI runs additional tests (including ``solc-js`` and testing third party Solidity
|
||||
frameworks) that require compiling the Emscripten target.
|
||||
|
||||
Writing and running syntax tests
|
||||
--------------------------------
|
||||
|
@ -433,7 +433,7 @@ The full contract
|
||||
.. note::
|
||||
The function ``splitSignature`` does not use all security
|
||||
checks. A real implementation should use a more rigorously tested library,
|
||||
such as openzepplin's `version <https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ECRecovery.sol>`_ of this code.
|
||||
such as openzepplin's `version <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol>`_ of this code.
|
||||
|
||||
Verifying Payments
|
||||
------------------
|
||||
@ -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>`_
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
|
@ -35,11 +35,11 @@ or if you require more compilation options.
|
||||
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
|
||||
further down this page. The
|
||||
: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>`_.
|
||||
|
||||
Note: The solc-js project is derived from the C++
|
||||
@ -53,10 +53,10 @@ Please refer to the solc-js repository for instructions.
|
||||
|
||||
.. 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`)
|
||||
expecting the behaviour of `solc` will not work with `solcjs`.
|
||||
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``.
|
||||
|
||||
Docker
|
||||
======
|
||||
|
@ -323,7 +323,7 @@ Every account has a persistent key-value store mapping 256-bit words to 256-bit
|
||||
words called **storage**.
|
||||
|
||||
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.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. 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.
|
||||
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::
|
||||
Even if a contract's code does not contain a call to ``selfdestruct``,
|
||||
|
@ -73,8 +73,8 @@ Tags
|
||||
|
||||
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
|
||||
used then the Solidity compiler will interpret a `///` or `/**` comment
|
||||
in the same way as if it were tagged with `@notice`.
|
||||
used then the Solidity compiler will interpret a ``///`` or ``/**`` comment
|
||||
in the same way as if it were tagged with ``@notice``.
|
||||
|
||||
=========== =============================================================================== =============================
|
||||
Tag Context
|
||||
|
@ -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>`_
|
||||
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.
|
||||
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 ;
|
||||
|
||||
More than one space around an assignment or other operator to align with
|
||||
another:
|
||||
More than one space around an assignment or other operator to align with another:
|
||||
|
||||
Yes::
|
||||
|
||||
@ -996,7 +998,7 @@ Contract and Library Names
|
||||
* 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.
|
||||
|
||||
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::
|
||||
|
||||
@ -1132,8 +1134,8 @@ Solidity contracts can have a form of comments that are the basis of the
|
||||
Ethereum Natural Language Specification Format.
|
||||
|
||||
Add comments above functions or contracts following `doxygen <http://www.doxygen.nl>`_ notation
|
||||
of one or multiple lines starting with `///` or a
|
||||
multiline comment starting with `/**` and ending with `*/`.
|
||||
of one or multiple lines starting with ``///`` or a
|
||||
multiline comment starting with ``/**`` and ending with ``*/``.
|
||||
|
||||
For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments
|
||||
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
|
||||
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
|
||||
tuple with a second `bool` value denoting success.
|
||||
tuple with a second ``bool`` value denoting success.
|
||||
|
||||
.. include:: types/value-types.rst
|
||||
|
||||
@ -26,4 +26,4 @@ tuple with a second `bool` value denoting success.
|
||||
|
||||
.. include:: types/operators.rst
|
||||
|
||||
.. include:: types/conversion.rst
|
||||
.. include:: types/conversion.rst
|
||||
|
@ -332,7 +332,7 @@ the :ref:`address type<address>`.
|
||||
Before version 0.5.0, contracts directly derived from the address type
|
||||
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
|
||||
same contract type.
|
||||
|
||||
|
@ -140,15 +140,19 @@ Error Handling
|
||||
See the dedicated section on :ref:`assert and require<assert-and-require>` for
|
||||
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.
|
||||
``require(bool condition)``:
|
||||
|
||||
``require(bool condition)``
|
||||
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.
|
||||
``revert()``:
|
||||
|
||||
``revert()``
|
||||
abort execution and revert state changes
|
||||
``revert(string memory reason)``:
|
||||
|
||||
``revert(string memory reason)``
|
||||
abort execution and revert state changes, providing an explanatory string
|
||||
|
||||
.. 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
|
||||
----------------------------------------
|
||||
|
||||
``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.
|
||||
|
||||
``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.
|
||||
|
||||
``keccak256(bytes memory) returns (bytes32)``:
|
||||
``keccak256(bytes memory) returns (bytes32)``
|
||||
compute the Keccak-256 hash of the input
|
||||
|
||||
.. note::
|
||||
|
||||
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
|
||||
|
||||
``ripemd160(bytes memory) returns (bytes20)``:
|
||||
``ripemd160(bytes memory) returns (bytes20)``
|
||||
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.
|
||||
The function parameters correspond to ECDSA values of the signature:
|
||||
|
||||
``r`` = first 32 bytes of signature
|
||||
``s`` = second 32 bytes of signature
|
||||
``v`` = final 1 byte of signature
|
||||
* ``r`` = first 32 bytes of signature
|
||||
* ``s`` = second 32 bytes of signature
|
||||
* ``v`` = final 1 byte of signature
|
||||
|
||||
``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.
|
||||
@ -209,17 +213,22 @@ Mathematical and Cryptographic Functions
|
||||
Members of Address Types
|
||||
------------------------
|
||||
|
||||
``<address>.balance`` (``uint256``):
|
||||
``<address>.balance`` (``uint256``)
|
||||
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
|
||||
``<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
|
||||
``<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
|
||||
``<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
|
||||
``<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
|
||||
|
||||
For more information, see the section on :ref:`address`.
|
||||
@ -258,10 +267,10 @@ For more information, see the section on :ref:`address`.
|
||||
Contract Related
|
||||
----------------
|
||||
|
||||
``this`` (current contract's type):
|
||||
``this`` (current contract's type)
|
||||
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`
|
||||
and end execution.
|
||||
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
|
||||
available for a contract type ``C``:
|
||||
|
||||
``type(C).name``:
|
||||
``type(C).name``
|
||||
The name of the contract.
|
||||
|
||||
``type(C).creationCode``:
|
||||
``type(C).creationCode``
|
||||
Memory byte array that contains the creation bytecode of the contract.
|
||||
This can be used in inline assembly to build custom creation routines,
|
||||
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
|
||||
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.
|
||||
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
|
||||
|
@ -106,7 +106,8 @@ Target options
|
||||
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.
|
||||
|
||||
- ``homestead`` (oldest version)
|
||||
- ``homestead``
|
||||
- (oldest version)
|
||||
- ``tangerineWhistle``
|
||||
- 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.
|
||||
@ -692,7 +693,7 @@ Review changes
|
||||
|
||||
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.8.0;
|
||||
|
||||
|
@ -22,15 +22,17 @@
|
||||
*/
|
||||
|
||||
#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/CommonSubexpressionEliminator.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 solidity;
|
||||
using namespace solidity::evmasm;
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#include <ostream>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::evmasm
|
||||
{
|
||||
@ -119,7 +120,7 @@ public:
|
||||
struct GasConsumption
|
||||
{
|
||||
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); }
|
||||
|
||||
GasConsumption& operator+=(GasConsumption const& _other);
|
||||
@ -133,8 +134,8 @@ public:
|
||||
};
|
||||
|
||||
/// Constructs a new gas meter given the current state.
|
||||
GasMeter(std::shared_ptr<KnownState> const& _state, langutil::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0):
|
||||
m_state(_state), m_evmVersion(_evmVersion), m_largestMemoryAccess(_largestMemoryAccess) {}
|
||||
GasMeter(std::shared_ptr<KnownState> _state, langutil::EVMVersion _evmVersion, u256 _largestMemoryAccess = 0):
|
||||
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
|
||||
/// the state.
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@ -83,7 +84,7 @@ public:
|
||||
|
||||
explicit KnownState(
|
||||
std::shared_ptr<ExpressionClasses> _expressionClasses = std::make_shared<ExpressionClasses>()
|
||||
): m_expressionClasses(_expressionClasses)
|
||||
): m_expressionClasses(std::move(_expressionClasses))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
@ -68,8 +69,8 @@ class CharStream
|
||||
{
|
||||
public:
|
||||
CharStream() = default;
|
||||
explicit CharStream(std::string const& _source, std::string const& name):
|
||||
m_source(_source), m_name(name) {}
|
||||
explicit CharStream(std::string _source, std::string name):
|
||||
m_source(std::move(_source)), m_name(std::move(name)) {}
|
||||
|
||||
int position() const { return m_position; }
|
||||
bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); }
|
||||
|
@ -22,15 +22,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <libsolutil/Exceptions.h>
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
class Error;
|
||||
|
@ -23,7 +23,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <liblangutil/Token.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::langutil
|
||||
@ -80,8 +82,8 @@ struct SemVerMatchExpression
|
||||
class SemVerMatchExpressionParser
|
||||
{
|
||||
public:
|
||||
SemVerMatchExpressionParser(std::vector<Token> const& _tokens, std::vector<std::string> const& _literals):
|
||||
m_tokens(_tokens), m_literals(_literals)
|
||||
SemVerMatchExpressionParser(std::vector<Token> _tokens, std::vector<std::string> _literals):
|
||||
m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
|
||||
{}
|
||||
SemVerMatchExpression parse();
|
||||
|
||||
|
@ -12,6 +12,8 @@ set(sources
|
||||
analysis/ControlFlowGraph.h
|
||||
analysis/DeclarationContainer.cpp
|
||||
analysis/DeclarationContainer.h
|
||||
analysis/DeclarationTypeChecker.cpp
|
||||
analysis/DeclarationTypeChecker.h
|
||||
analysis/DocStringAnalyser.cpp
|
||||
analysis/DocStringAnalyser.h
|
||||
analysis/ImmutableValidator.cpp
|
||||
@ -73,6 +75,8 @@ set(sources
|
||||
codegen/LValue.h
|
||||
codegen/MultiUseYulFunctionCollector.h
|
||||
codegen/MultiUseYulFunctionCollector.cpp
|
||||
codegen/ReturnInfo.h
|
||||
codegen/ReturnInfo.cpp
|
||||
codegen/YulUtilFunctions.h
|
||||
codegen/YulUtilFunctions.cpp
|
||||
codegen/ir/IRGenerator.cpp
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
class ErrorReporter;
|
||||
@ -47,7 +49,7 @@ public:
|
||||
):
|
||||
m_errorReporter(_errorReporter),
|
||||
m_depth(_newDepth),
|
||||
m_types(_types)
|
||||
m_types(std::move(_types))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -591,7 +591,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
|
||||
if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
*variableDeclaration,
|
||||
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
||||
static_cast<Expression const&>(_identifier).annotation().willBeWrittenTo ?
|
||||
VariableOccurrence::Kind::Assignment :
|
||||
VariableOccurrence::Kind::Access,
|
||||
_identifier.location()
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::frontend
|
||||
@ -48,8 +49,8 @@ public:
|
||||
Assignment,
|
||||
InlineAssembly
|
||||
};
|
||||
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
|
||||
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
||||
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> _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;
|
||||
};
|
||||
|
||||
}
|
@ -160,7 +160,7 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
|
||||
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
|
||||
return;
|
||||
|
||||
if (_expression.annotation().lValueRequested && _expression.annotation().lValueOfOrdinaryAssignment)
|
||||
if (_expression.annotation().willBeWrittenTo && _expression.annotation().lValueOfOrdinaryAssignment)
|
||||
{
|
||||
if (!m_currentConstructor)
|
||||
m_errorReporter.typeError(
|
||||
|
@ -195,51 +195,6 @@ Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> c
|
||||
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()
|
||||
{
|
||||
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.
|
||||
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.
|
||||
void warnVariablesNamedLikeInstructions();
|
||||
|
||||
|
@ -22,9 +22,7 @@
|
||||
|
||||
#include <libsolidity/analysis/ReferencesResolver.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/analysis/ConstantEvaluator.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
@ -37,7 +35,6 @@
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::langutil;
|
||||
@ -126,40 +123,10 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
|
||||
else if (declarations.size() == 1)
|
||||
_identifier.annotation().referencedDeclaration = declarations.front();
|
||||
else
|
||||
_identifier.annotation().overloadedDeclarations =
|
||||
m_resolver.cleanedDeclarations(_identifier, declarations);
|
||||
_identifier.annotation().candidateDeclarations = declarations;
|
||||
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)
|
||||
{
|
||||
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
||||
@ -194,113 +161,6 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
|
||||
}
|
||||
|
||||
_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)
|
||||
@ -321,115 +181,6 @@ bool ReferencesResolver::visit(Return const& _return)
|
||||
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)
|
||||
{
|
||||
bool wasInsideFunction = m_yulInsideFunction;
|
||||
@ -514,18 +265,6 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
|
||||
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)
|
||||
{
|
||||
m_errorOccurred = true;
|
||||
|
@ -76,29 +76,18 @@ private:
|
||||
void endVisit(ForStatement const& _for) override;
|
||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
bool visit(ElementaryTypeName const& _typeName) override;
|
||||
bool visit(FunctionDefinition const& _functionDefinition) override;
|
||||
void endVisit(FunctionDefinition const& _functionDefinition) override;
|
||||
bool visit(ModifierDefinition const& _modifierDefinition) override;
|
||||
void endVisit(ModifierDefinition const& _modifierDefinition) 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(Return const& _return) override;
|
||||
void endVisit(VariableDeclaration const& _variable) override;
|
||||
|
||||
void operator()(yul::FunctionDefinition const& _function) override;
|
||||
void operator()(yul::Identifier const& _identifier) 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.
|
||||
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
|
@ -289,39 +289,6 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||
m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(StructDefinition const& _struct)
|
||||
{
|
||||
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
|
||||
solAssert(type(*member)->canBeStored(), "Type cannot be used in struct.");
|
||||
|
||||
// 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 isLibraryFunction = _function.inContractKind() == ContractKind::Library;
|
||||
@ -520,19 +487,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
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:
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(varType))
|
||||
if (
|
||||
((arrayType->location() == DataLocation::Memory) ||
|
||||
(arrayType->location() == DataLocation::CallData)) &&
|
||||
!arrayType->validForCalldata()
|
||||
)
|
||||
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded.");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
auto result = referenceType->validForLocation(referenceType->location());
|
||||
if (result && _variable.isPublicCallableParameter())
|
||||
result = referenceType->validForLocation(DataLocation::CallData);
|
||||
if (!result)
|
||||
{
|
||||
solAssert(!result.message().empty(), "Expected detailed error message");
|
||||
m_errorReporter.typeError(_variable.location(), result.message());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -633,7 +597,15 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
||||
{
|
||||
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
@ -1305,7 +1277,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
|
||||
_conditional.trueExpression().annotation().isPure &&
|
||||
_conditional.falseExpression().annotation().isPure;
|
||||
|
||||
if (_conditional.annotation().lValueRequested)
|
||||
if (_conditional.annotation().willBeWrittenTo)
|
||||
m_errorReporter.typeError(
|
||||
_conditional.location(),
|
||||
"Conditional expression as left value is not supported yet."
|
||||
@ -1401,7 +1373,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
vector<ASTPointer<Expression>> const& components = _tuple.components();
|
||||
TypePointers types;
|
||||
|
||||
if (_tuple.annotation().lValueRequested)
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
if (_tuple.isInlineArray())
|
||||
m_errorReporter.fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue.");
|
||||
@ -1432,41 +1404,37 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
if (!components[i])
|
||||
m_errorReporter.fatalTypeError(_tuple.location(), "Tuple component cannot be empty.");
|
||||
else if (components[i])
|
||||
{
|
||||
components[i]->accept(*this);
|
||||
types.push_back(type(*components[i]));
|
||||
|
||||
if (types[i]->category() == Type::Category::Tuple)
|
||||
if (dynamic_cast<TupleType const&>(*types[i]).components().empty())
|
||||
{
|
||||
if (_tuple.isInlineArray())
|
||||
m_errorReporter.fatalTypeError(components[i]->location(), "Array component cannot be empty.");
|
||||
m_errorReporter.typeError(components[i]->location(), "Tuple component cannot be empty.");
|
||||
}
|
||||
components[i]->accept(*this);
|
||||
types.push_back(type(*components[i]));
|
||||
|
||||
// Note: code generation will visit each of the expression even if they are not assigned from.
|
||||
if (types[i]->category() == Type::Category::RationalNumber && components.size() > 1)
|
||||
if (!dynamic_cast<RationalNumberType const&>(*types[i]).mobileType())
|
||||
m_errorReporter.fatalTypeError(components[i]->location(), "Invalid rational number.");
|
||||
|
||||
if (_tuple.isInlineArray())
|
||||
if (types[i]->category() == Type::Category::Tuple)
|
||||
if (dynamic_cast<TupleType const&>(*types[i]).components().empty())
|
||||
{
|
||||
solAssert(!!types[i], "Inline array cannot have empty components");
|
||||
|
||||
if ((i == 0 || inlineArrayType) && !types[i]->mobileType())
|
||||
m_errorReporter.fatalTypeError(components[i]->location(), "Invalid mobile type.");
|
||||
|
||||
if (i == 0)
|
||||
inlineArrayType = types[i]->mobileType();
|
||||
else if (inlineArrayType)
|
||||
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
|
||||
if (_tuple.isInlineArray())
|
||||
m_errorReporter.fatalTypeError(components[i]->location(), "Array component cannot be empty.");
|
||||
m_errorReporter.typeError(components[i]->location(), "Tuple component cannot be empty.");
|
||||
}
|
||||
if (!components[i]->annotation().isPure)
|
||||
isPure = false;
|
||||
|
||||
// Note: code generation will visit each of the expression even if they are not assigned from.
|
||||
if (types[i]->category() == Type::Category::RationalNumber && components.size() > 1)
|
||||
if (!dynamic_cast<RationalNumberType const&>(*types[i]).mobileType())
|
||||
m_errorReporter.fatalTypeError(components[i]->location(), "Invalid rational number.");
|
||||
|
||||
if (_tuple.isInlineArray())
|
||||
{
|
||||
solAssert(!!types[i], "Inline array cannot have empty components");
|
||||
|
||||
if ((i == 0 || inlineArrayType) && !types[i]->mobileType())
|
||||
m_errorReporter.fatalTypeError(components[i]->location(), "Invalid mobile type.");
|
||||
|
||||
if (i == 0)
|
||||
inlineArrayType = types[i]->mobileType();
|
||||
else if (inlineArrayType)
|
||||
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
|
||||
}
|
||||
else
|
||||
types.push_back(TypePointer());
|
||||
if (!components[i]->annotation().isPure)
|
||||
isPure = false;
|
||||
}
|
||||
_tuple.annotation().isPure = isPure;
|
||||
if (_tuple.isInlineArray())
|
||||
@ -1802,6 +1770,10 @@ void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
|
||||
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
|
||||
{
|
||||
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())
|
||||
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
|
||||
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
|
||||
@ -2782,11 +2754,57 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
|
||||
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)
|
||||
{
|
||||
IdentifierAnnotation& annotation = _identifier.annotation();
|
||||
if (!annotation.referencedDeclaration)
|
||||
{
|
||||
annotation.overloadedDeclarations = cleanOverloadedDeclarations(_identifier, annotation.candidateDeclarations);
|
||||
if (!annotation.arguments)
|
||||
{
|
||||
// The identifier should be a public state variable shadowing other functions
|
||||
@ -3006,7 +3024,7 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
|
||||
|
||||
void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAssignment)
|
||||
{
|
||||
_expression.annotation().lValueRequested = true;
|
||||
_expression.annotation().willBeWrittenTo = true;
|
||||
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
|
||||
_expression.accept(*this);
|
||||
|
||||
|
@ -112,7 +112,6 @@ private:
|
||||
|
||||
void endVisit(InheritanceSpecifier const& _inheritance) override;
|
||||
void endVisit(UsingForDirective const& _usingFor) override;
|
||||
bool visit(StructDefinition const& _struct) override;
|
||||
bool visit(FunctionDefinition const& _function) 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
|
||||
@ -154,6 +153,11 @@ private:
|
||||
/// @returns the referenced declaration and throws on error.
|
||||
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
|
||||
/// convertible to @a _expectedType.
|
||||
bool expectType(Expression const& _expression, Type const& _expectedType);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
using namespace std;
|
||||
@ -41,7 +42,7 @@ public:
|
||||
std::function<void(StateMutability, SourceLocation const&)> _reportMutability
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_reportMutability(_reportMutability) {}
|
||||
m_reportMutability(std::move(_reportMutability)) {}
|
||||
|
||||
void operator()(yul::Literal const&) {}
|
||||
void operator()(yul::Identifier const&) {}
|
||||
@ -196,7 +197,7 @@ void ViewPureChecker::endVisit(Identifier const& _identifier)
|
||||
|
||||
StateMutability mutability = StateMutability::Pure;
|
||||
|
||||
bool writes = _identifier.annotation().lValueRequested;
|
||||
bool writes = _identifier.annotation().willBeWrittenTo;
|
||||
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
{
|
||||
if (varDecl->isStateVariable() && !varDecl->isConstant())
|
||||
@ -331,7 +332,7 @@ bool ViewPureChecker::visit(MemberAccess const& _memberAccess)
|
||||
void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
StateMutability mutability = StateMutability::Pure;
|
||||
bool writes = _memberAccess.annotation().lValueRequested;
|
||||
bool writes = _memberAccess.annotation().willBeWrittenTo;
|
||||
|
||||
ASTString const& member = _memberAccess.memberName();
|
||||
switch (_memberAccess.expression().annotation().type->category())
|
||||
@ -401,7 +402,7 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
|
||||
solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
|
||||
else
|
||||
{
|
||||
bool writes = _indexAccess.annotation().lValueRequested;
|
||||
bool writes = _indexAccess.annotation().willBeWrittenTo;
|
||||
if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
|
||||
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location());
|
||||
}
|
||||
@ -409,7 +410,7 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
|
||||
|
||||
void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess)
|
||||
{
|
||||
bool writes = _indexRangeAccess.annotation().lValueRequested;
|
||||
bool writes = _indexRangeAccess.annotation().willBeWrittenTo;
|
||||
if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
|
||||
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location());
|
||||
}
|
||||
|
@ -28,16 +28,18 @@
|
||||
#include <libsolutil/Keccak256.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
ASTNode::ASTNode(int64_t _id, SourceLocation const& _location):
|
||||
ASTNode::ASTNode(int64_t _id, SourceLocation _location):
|
||||
m_id(_id),
|
||||
m_location(_location)
|
||||
m_location(std::move(_location))
|
||||
{
|
||||
}
|
||||
|
||||
@ -255,12 +257,13 @@ TypeNameAnnotation& TypeName::annotation() const
|
||||
|
||||
TypePointer StructDefinition::type() const
|
||||
{
|
||||
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");
|
||||
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
|
||||
@ -556,6 +559,18 @@ bool VariableDeclaration::isExternalCallableParameter() const
|
||||
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
|
||||
{
|
||||
if (!isCallableOrCatchParameter())
|
||||
@ -614,12 +629,20 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
|
||||
else if (isLocalVariable())
|
||||
{
|
||||
solAssert(typeName(), "");
|
||||
solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
|
||||
if (typeName()->annotation().type->category() == Type::Category::Mapping)
|
||||
return set<Location>{ Location::Storage };
|
||||
else
|
||||
// TODO: add Location::Calldata once implemented for local variables.
|
||||
return set<Location>{ Location::Memory, Location::Storage };
|
||||
auto dataLocations = [](TypePointer _type, auto&& _recursion) -> set<Location> {
|
||||
solAssert(_type, "Can only be called after reference resolution");
|
||||
switch (_type->category())
|
||||
{
|
||||
case Type::Category::Array:
|
||||
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
|
||||
// Struct members etc.
|
||||
@ -756,3 +779,25 @@ string Literal::getChecksummedAddress() const
|
||||
address.insert(address.begin(), 40 - address.size(), '0');
|
||||
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 <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::yul
|
||||
@ -82,7 +83,7 @@ public:
|
||||
|
||||
using SourceLocation = langutil::SourceLocation;
|
||||
|
||||
explicit ASTNode(int64_t _id, SourceLocation const& _location);
|
||||
explicit ASTNode(int64_t _id, SourceLocation _location);
|
||||
virtual ~ASTNode() {}
|
||||
|
||||
/// @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
|
||||
{
|
||||
public:
|
||||
SourceUnit(int64_t _id, SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> const& _nodes):
|
||||
ASTNode(_id, _location), m_nodes(_nodes) {}
|
||||
SourceUnit(int64_t _id, SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> _nodes):
|
||||
ASTNode(_id, _location), m_nodes(std::move(_nodes)) {}
|
||||
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -224,10 +225,10 @@ public:
|
||||
Declaration(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
ASTPointer<ASTString> _name,
|
||||
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.
|
||||
ASTString const& name() const { return *m_name; }
|
||||
@ -275,9 +276,9 @@ public:
|
||||
PragmaDirective(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
std::vector<Token> const& _tokens,
|
||||
std::vector<ASTString> const& _literals
|
||||
): ASTNode(_id, _location), m_tokens(_tokens), m_literals(_literals)
|
||||
std::vector<Token> _tokens,
|
||||
std::vector<ASTString> _literals
|
||||
): ASTNode(_id, _location), m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
|
||||
{}
|
||||
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
@ -318,12 +319,12 @@ public:
|
||||
ImportDirective(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _path,
|
||||
ASTPointer<ASTString> _path,
|
||||
ASTPointer<ASTString> const& _unitAlias,
|
||||
SymbolAliasList _symbolAliases
|
||||
):
|
||||
Declaration(_id, _location, _unitAlias),
|
||||
m_path(_path),
|
||||
m_path(std::move(_path)),
|
||||
m_symbolAliases(move(_symbolAliases))
|
||||
{ }
|
||||
|
||||
@ -372,8 +373,8 @@ public:
|
||||
StructuredDocumentation(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _text
|
||||
): ASTNode(_id, _location), m_text(_text)
|
||||
ASTPointer<ASTString> _text
|
||||
): ASTNode(_id, _location), m_text(std::move(_text))
|
||||
{}
|
||||
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
@ -394,7 +395,7 @@ class Documented
|
||||
{
|
||||
public:
|
||||
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.
|
||||
/// Can contain a nullptr in which case indicates absence of documentation
|
||||
@ -411,7 +412,7 @@ class StructurallyDocumented
|
||||
{
|
||||
public:
|
||||
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.
|
||||
/// Can contain a nullptr in which case indicates absence of documentation
|
||||
@ -453,15 +454,15 @@ public:
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
|
||||
std::vector<ASTPointer<ASTNode>> const& _subNodes,
|
||||
std::vector<ASTPointer<InheritanceSpecifier>> _baseContracts,
|
||||
std::vector<ASTPointer<ASTNode>> _subNodes,
|
||||
ContractKind _contractKind = ContractKind::Contract,
|
||||
bool _abstract = false
|
||||
):
|
||||
Declaration(_id, _location, _name),
|
||||
StructurallyDocumented(_documentation),
|
||||
m_baseContracts(_baseContracts),
|
||||
m_subNodes(_subNodes),
|
||||
m_baseContracts(std::move(_baseContracts)),
|
||||
m_subNodes(std::move(_subNodes)),
|
||||
m_contractKind(_contractKind),
|
||||
m_abstract(_abstract)
|
||||
{}
|
||||
@ -538,10 +539,10 @@ public:
|
||||
InheritanceSpecifier(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<UserDefinedTypeName> const& _baseName,
|
||||
ASTPointer<UserDefinedTypeName> _baseName,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
@ -568,10 +569,10 @@ public:
|
||||
UsingForDirective(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<UserDefinedTypeName> const& _libraryName,
|
||||
ASTPointer<TypeName> const& _typeName
|
||||
ASTPointer<UserDefinedTypeName> _libraryName,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
@ -592,9 +593,9 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
@ -606,7 +607,7 @@ public:
|
||||
bool isVisibleInDerivedContracts() const override { return true; }
|
||||
bool isVisibleViaContractTypeAccess() const override { return true; }
|
||||
|
||||
TypeDeclarationAnnotation& annotation() const override;
|
||||
StructDeclarationAnnotation& annotation() const override;
|
||||
|
||||
private:
|
||||
std::vector<ASTPointer<VariableDeclaration>> m_members;
|
||||
@ -619,9 +620,9 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -664,9 +665,9 @@ public:
|
||||
ParameterList(
|
||||
int64_t _id,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -688,15 +689,15 @@ public:
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
Visibility _visibility,
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
ASTPointer<ParameterList> _parameters,
|
||||
bool _isVirtual = false,
|
||||
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
||||
ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>()
|
||||
ASTPointer<OverrideSpecifier> _overrides = nullptr,
|
||||
ASTPointer<ParameterList> _returnParameters = ASTPointer<ParameterList>()
|
||||
):
|
||||
Declaration(_id, _location, _name, _visibility),
|
||||
m_parameters(_parameters),
|
||||
m_overrides(_overrides),
|
||||
m_returnParameters(_returnParameters),
|
||||
m_parameters(std::move(_parameters)),
|
||||
m_overrides(std::move(_overrides)),
|
||||
m_returnParameters(std::move(_returnParameters)),
|
||||
m_isVirtual(_isVirtual)
|
||||
{
|
||||
}
|
||||
@ -740,10 +741,10 @@ public:
|
||||
OverrideSpecifier(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
std::vector<ASTPointer<UserDefinedTypeName>> const& _overrides
|
||||
std::vector<ASTPointer<UserDefinedTypeName>> _overrides
|
||||
):
|
||||
ASTNode(_id, _location),
|
||||
m_overrides(_overrides)
|
||||
m_overrides(std::move(_overrides))
|
||||
{
|
||||
}
|
||||
|
||||
@ -771,7 +772,7 @@ public:
|
||||
ASTPointer<OverrideSpecifier> const& _overrides,
|
||||
ASTPointer<StructuredDocumentation> const& _documentation,
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
|
||||
std::vector<ASTPointer<ModifierInvocation>> _modifiers,
|
||||
ASTPointer<ParameterList> const& _returnParameters,
|
||||
ASTPointer<Block> const& _body
|
||||
):
|
||||
@ -780,7 +781,7 @@ public:
|
||||
ImplementationOptional(_body != nullptr),
|
||||
m_stateMutability(_stateMutability),
|
||||
m_kind(_kind),
|
||||
m_functionModifiers(_modifiers),
|
||||
m_functionModifiers(std::move(_modifiers)),
|
||||
m_body(_body)
|
||||
{
|
||||
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
|
||||
@ -854,28 +855,38 @@ class VariableDeclaration: public Declaration
|
||||
{
|
||||
public:
|
||||
enum Location { Unspecified, Storage, Memory, CallData };
|
||||
enum class Constantness { Mutable, Immutable, Constant };
|
||||
enum class Mutability { Mutable, Immutable, Constant };
|
||||
static std::string mutabilityToString(Mutability _mutability)
|
||||
{
|
||||
switch (_mutability)
|
||||
{
|
||||
case Mutability::Mutable: return "mutable";
|
||||
case Mutability::Immutable: return "immutable";
|
||||
case Mutability::Constant: return "constant";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
VariableDeclaration(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<TypeName> const& _type,
|
||||
ASTPointer<TypeName> _type,
|
||||
ASTPointer<ASTString> const& _name,
|
||||
ASTPointer<Expression> _value,
|
||||
Visibility _visibility,
|
||||
bool _isStateVar = false,
|
||||
bool _isIndexed = false,
|
||||
Constantness _constantness = Constantness::Mutable,
|
||||
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
||||
Mutability _mutability = Mutability::Mutable,
|
||||
ASTPointer<OverrideSpecifier> _overrides = nullptr,
|
||||
Location _referenceLocation = Location::Unspecified
|
||||
):
|
||||
Declaration(_id, _location, _name, _visibility),
|
||||
m_typeName(_type),
|
||||
m_value(_value),
|
||||
m_typeName(std::move(_type)),
|
||||
m_value(std::move(_value)),
|
||||
m_isStateVariable(_isStateVar),
|
||||
m_isIndexed(_isIndexed),
|
||||
m_constantness(_constantness),
|
||||
m_overrides(_overrides),
|
||||
m_mutability(_mutability),
|
||||
m_overrides(std::move(_overrides)),
|
||||
m_location(_referenceLocation) {}
|
||||
|
||||
|
||||
@ -903,6 +914,8 @@ public:
|
||||
/// @returns true if this variable is a parameter (not return parameter) of an external function.
|
||||
/// This excludes parameters of external function type names.
|
||||
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
|
||||
/// or a function type of internal visibility.
|
||||
bool isInternalCallableParameter() const;
|
||||
@ -918,8 +931,9 @@ public:
|
||||
bool hasReferenceOrMappingType() const;
|
||||
bool isStateVariable() const { return m_isStateVariable; }
|
||||
bool isIndexed() const { return m_isIndexed; }
|
||||
bool isConstant() const { return m_constantness == Constantness::Constant; }
|
||||
bool immutable() const { return m_constantness == Constantness::Immutable; }
|
||||
Mutability mutability() const { return m_mutability; }
|
||||
bool isConstant() const { return m_mutability == Mutability::Constant; }
|
||||
bool immutable() const { return m_mutability == Mutability::Immutable; }
|
||||
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
|
||||
Location referenceLocation() const { return m_location; }
|
||||
/// @returns a set of allowed storage locations for the variable.
|
||||
@ -947,7 +961,7 @@ private:
|
||||
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
|
||||
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
|
||||
/// Whether the variable is "constant", "immutable" or non-marked (mutable).
|
||||
Constantness m_constantness = Constantness::Mutable;
|
||||
Mutability m_mutability = Mutability::Mutable;
|
||||
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
|
||||
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
|
||||
};
|
||||
@ -966,11 +980,11 @@ public:
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
bool _isVirtual,
|
||||
ASTPointer<OverrideSpecifier> const& _overrides,
|
||||
ASTPointer<Block> const& _body
|
||||
ASTPointer<Block> _body
|
||||
):
|
||||
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
|
||||
StructurallyDocumented(_documentation),
|
||||
m_body(_body)
|
||||
m_body(std::move(_body))
|
||||
{
|
||||
}
|
||||
|
||||
@ -1004,10 +1018,10 @@ public:
|
||||
ModifierInvocation(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Identifier> const& _name,
|
||||
ASTPointer<Identifier> _name,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
@ -1148,8 +1162,8 @@ private:
|
||||
class UserDefinedTypeName: public TypeName
|
||||
{
|
||||
public:
|
||||
UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector<ASTString> const& _namePath):
|
||||
TypeName(_id, _location), m_namePath(_namePath) {}
|
||||
UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector<ASTString> _namePath):
|
||||
TypeName(_id, _location), m_namePath(std::move(_namePath)) {}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1170,12 +1184,12 @@ public:
|
||||
FunctionTypeName(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ParameterList> const& _parameterTypes,
|
||||
ASTPointer<ParameterList> const& _returnTypes,
|
||||
ASTPointer<ParameterList> _parameterTypes,
|
||||
ASTPointer<ParameterList> _returnTypes,
|
||||
Visibility _visibility,
|
||||
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)
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
@ -1209,10 +1223,10 @@ public:
|
||||
Mapping(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<TypeName> const& _keyType,
|
||||
ASTPointer<TypeName> const& _valueType
|
||||
ASTPointer<TypeName> _keyType,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1233,10 +1247,10 @@ public:
|
||||
ArrayTypeName(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<TypeName> const& _baseType,
|
||||
ASTPointer<Expression> const& _length
|
||||
ASTPointer<TypeName> _baseType,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1280,9 +1294,9 @@ public:
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1306,9 +1320,9 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1348,14 +1362,14 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
ASTPointer<Expression> const& _condition,
|
||||
ASTPointer<Statement> const& _trueBody,
|
||||
ASTPointer<Statement> const& _falseBody
|
||||
ASTPointer<Expression> _condition,
|
||||
ASTPointer<Statement> _trueBody,
|
||||
ASTPointer<Statement> _falseBody
|
||||
):
|
||||
Statement(_id, _location, _docString),
|
||||
m_condition(_condition),
|
||||
m_trueBody(_trueBody),
|
||||
m_falseBody(_falseBody)
|
||||
m_condition(std::move(_condition)),
|
||||
m_trueBody(std::move(_trueBody)),
|
||||
m_falseBody(std::move(_falseBody))
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -1382,14 +1396,14 @@ public:
|
||||
TryCatchClause(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _errorName,
|
||||
ASTPointer<ParameterList> const& _parameters,
|
||||
ASTPointer<Block> const& _block
|
||||
ASTPointer<ASTString> _errorName,
|
||||
ASTPointer<ParameterList> _parameters,
|
||||
ASTPointer<Block> _block
|
||||
):
|
||||
ASTNode(_id, _location),
|
||||
m_errorName(_errorName),
|
||||
m_parameters(_parameters),
|
||||
m_block(_block)
|
||||
m_errorName(std::move(_errorName)),
|
||||
m_parameters(std::move(_parameters)),
|
||||
m_block(std::move(_block))
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -1427,12 +1441,12 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
ASTPointer<Expression> const& _externalCall,
|
||||
std::vector<ASTPointer<TryCatchClause>> const& _clauses
|
||||
ASTPointer<Expression> _externalCall,
|
||||
std::vector<ASTPointer<TryCatchClause>> _clauses
|
||||
):
|
||||
Statement(_id, _location, _docString),
|
||||
m_externalCall(_externalCall),
|
||||
m_clauses(_clauses)
|
||||
m_externalCall(std::move(_externalCall)),
|
||||
m_clauses(std::move(_clauses))
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -1440,6 +1454,10 @@ public:
|
||||
Expression const& externalCall() const { return *m_externalCall; }
|
||||
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
|
||||
|
||||
TryCatchClause const* successClause() const;
|
||||
TryCatchClause const* structuredClause() const;
|
||||
TryCatchClause const* fallbackClause() const;
|
||||
|
||||
private:
|
||||
ASTPointer<Expression> m_externalCall;
|
||||
std::vector<ASTPointer<TryCatchClause>> m_clauses;
|
||||
@ -1465,11 +1483,11 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
ASTPointer<Expression> const& _condition,
|
||||
ASTPointer<Statement> const& _body,
|
||||
ASTPointer<Expression> _condition,
|
||||
ASTPointer<Statement> _body,
|
||||
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) {}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -1494,16 +1512,16 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
ASTPointer<Statement> const& _initExpression,
|
||||
ASTPointer<Expression> const& _conditionExpression,
|
||||
ASTPointer<ExpressionStatement> const& _loopExpression,
|
||||
ASTPointer<Statement> const& _body
|
||||
ASTPointer<Statement> _initExpression,
|
||||
ASTPointer<Expression> _conditionExpression,
|
||||
ASTPointer<ExpressionStatement> _loopExpression,
|
||||
ASTPointer<Statement> _body
|
||||
):
|
||||
BreakableStatement(_id, _location, _docString),
|
||||
m_initExpression(_initExpression),
|
||||
m_condExpression(_conditionExpression),
|
||||
m_loopExpression(_loopExpression),
|
||||
m_body(_body)
|
||||
m_initExpression(std::move(_initExpression)),
|
||||
m_condExpression(std::move(_conditionExpression)),
|
||||
m_loopExpression(std::move(_loopExpression)),
|
||||
m_body(std::move(_body))
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -1552,7 +1570,7 @@ public:
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1586,9 +1604,9 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1613,10 +1631,10 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& _variables,
|
||||
ASTPointer<Expression> const& _initialValue
|
||||
std::vector<ASTPointer<VariableDeclaration>> _variables,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1645,7 +1663,7 @@ public:
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1679,14 +1697,14 @@ public:
|
||||
Conditional(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Expression> const& _condition,
|
||||
ASTPointer<Expression> const& _trueExpression,
|
||||
ASTPointer<Expression> const& _falseExpression
|
||||
ASTPointer<Expression> _condition,
|
||||
ASTPointer<Expression> _trueExpression,
|
||||
ASTPointer<Expression> _falseExpression
|
||||
):
|
||||
Expression(_id, _location),
|
||||
m_condition(_condition),
|
||||
m_trueExpression(_trueExpression),
|
||||
m_falseExpression(_falseExpression)
|
||||
m_condition(std::move(_condition)),
|
||||
m_trueExpression(std::move(_trueExpression)),
|
||||
m_falseExpression(std::move(_falseExpression))
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -1709,14 +1727,14 @@ public:
|
||||
Assignment(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Expression> const& _leftHandSide,
|
||||
ASTPointer<Expression> _leftHandSide,
|
||||
Token _assignmentOperator,
|
||||
ASTPointer<Expression> const& _rightHandSide
|
||||
ASTPointer<Expression> _rightHandSide
|
||||
):
|
||||
Expression(_id, _location),
|
||||
m_leftHandSide(_leftHandSide),
|
||||
m_leftHandSide(std::move(_leftHandSide)),
|
||||
m_assigmentOperator(_assignmentOperator),
|
||||
m_rightHandSide(_rightHandSide)
|
||||
m_rightHandSide(std::move(_rightHandSide))
|
||||
{
|
||||
solAssert(TokenTraits::isAssignmentOp(_assignmentOperator), "");
|
||||
}
|
||||
@ -1747,11 +1765,11 @@ public:
|
||||
TupleExpression(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
std::vector<ASTPointer<Expression>> const& _components,
|
||||
std::vector<ASTPointer<Expression>> _components,
|
||||
bool _isArray
|
||||
):
|
||||
Expression(_id, _location),
|
||||
m_components(_components),
|
||||
m_components(std::move(_components)),
|
||||
m_isArray(_isArray) {}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
@ -1775,12 +1793,12 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
Token _operator,
|
||||
ASTPointer<Expression> const& _subExpression,
|
||||
ASTPointer<Expression> _subExpression,
|
||||
bool _isPrefix
|
||||
):
|
||||
Expression(_id, _location),
|
||||
m_operator(_operator),
|
||||
m_subExpression(_subExpression),
|
||||
m_subExpression(std::move(_subExpression)),
|
||||
m_isPrefix(_isPrefix)
|
||||
{
|
||||
solAssert(TokenTraits::isUnaryOp(_operator), "");
|
||||
@ -1808,11 +1826,11 @@ public:
|
||||
BinaryOperation(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Expression> const& _left,
|
||||
ASTPointer<Expression> _left,
|
||||
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), "");
|
||||
}
|
||||
@ -1840,11 +1858,11 @@ public:
|
||||
FunctionCall(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Expression> const& _expression,
|
||||
std::vector<ASTPointer<Expression>> const& _arguments,
|
||||
std::vector<ASTPointer<ASTString>> const& _names
|
||||
ASTPointer<Expression> _expression,
|
||||
std::vector<ASTPointer<Expression>> _arguments,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1870,11 +1888,11 @@ public:
|
||||
FunctionCallOptions(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Expression> const& _expression,
|
||||
std::vector<ASTPointer<Expression>> const& _options,
|
||||
std::vector<ASTPointer<ASTString>> const& _names
|
||||
ASTPointer<Expression> _expression,
|
||||
std::vector<ASTPointer<Expression>> _options,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1899,9 +1917,9 @@ public:
|
||||
NewExpression(
|
||||
int64_t _id,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1921,9 +1939,9 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
Expression const& expression() const { return *m_expression; }
|
||||
@ -1945,10 +1963,10 @@ public:
|
||||
IndexAccess(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Expression> const& _base,
|
||||
ASTPointer<Expression> const& _index
|
||||
ASTPointer<Expression> _base,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -1969,11 +1987,11 @@ public:
|
||||
IndexRangeAccess(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<Expression> const& _base,
|
||||
ASTPointer<Expression> const& _start,
|
||||
ASTPointer<Expression> const& _end
|
||||
ASTPointer<Expression> _base,
|
||||
ASTPointer<Expression> _start,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -2006,9 +2024,9 @@ public:
|
||||
Identifier(
|
||||
int64_t _id,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
@ -2031,10 +2049,10 @@ public:
|
||||
ElementaryTypeNameExpression(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ElementaryTypeName> const& _type
|
||||
ASTPointer<ElementaryTypeName> _type
|
||||
):
|
||||
PrimaryExpression(_id, _location),
|
||||
m_type(_type)
|
||||
m_type(std::move(_type))
|
||||
{
|
||||
}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
@ -2070,10 +2088,10 @@ public:
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
Token _token,
|
||||
ASTPointer<ASTString> const& _value,
|
||||
ASTPointer<ASTString> _value,
|
||||
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(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
|
@ -128,6 +128,16 @@ struct TypeDeclarationAnnotation: DeclarationAnnotation
|
||||
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
|
||||
{
|
||||
/// List of functions without a body. Can also contain functions from base classes.
|
||||
@ -234,7 +244,7 @@ struct ExpressionAnnotation: ASTAnnotation
|
||||
/// Whether it is an LValue (i.e. something that can be assigned to).
|
||||
bool isLValue = false;
|
||||
/// 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.
|
||||
/// Would be false for --, ++, delete, +=, -=, ....
|
||||
bool lValueOfOrdinaryAssignment = false;
|
||||
@ -248,6 +258,8 @@ struct IdentifierAnnotation: ExpressionAnnotation
|
||||
{
|
||||
/// Referenced declaration, set at latest during overload resolution stage.
|
||||
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.
|
||||
std::vector<Declaration const*> overloadedDeclarations;
|
||||
};
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/algorithm/sort.hpp>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
@ -45,7 +46,7 @@ namespace solidity::frontend
|
||||
|
||||
ASTJsonConverter::ASTJsonConverter(bool _legacy, map<string, unsigned> _sourceIndices):
|
||||
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("isPure", _annotation.isPure),
|
||||
make_pair("isLValue", _annotation.isLValue),
|
||||
make_pair("lValueRequested", _annotation.lValueRequested),
|
||||
make_pair("lValueRequested", _annotation.willBeWrittenTo),
|
||||
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
|
||||
};
|
||||
_attributes += exprAttributes;
|
||||
@ -378,6 +379,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("typeName", toJsonOrNull(_node.typeName())),
|
||||
make_pair("constant", _node.isConstant()),
|
||||
make_pair("mutability", VariableDeclaration::mutabilityToString(_node.mutability())),
|
||||
make_pair("stateVariable", _node.isStateVariable()),
|
||||
make_pair("storageLocation", location(_node.referenceLocation())),
|
||||
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
|
||||
|
@ -411,11 +411,24 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
||||
{
|
||||
astAssert(_node["name"].isString(), "Expected 'name' to be a string!");
|
||||
|
||||
VariableDeclaration::Constantness constantness{};
|
||||
if (memberAsBool(_node, "constant"))
|
||||
constantness = VariableDeclaration::Constantness::Constant;
|
||||
VariableDeclaration::Mutability mutability{};
|
||||
astAssert(member(_node, "mutability").isString(), "'mutability' expected to be string.");
|
||||
string const mutabilityStr = member(_node, "mutability").asString();
|
||||
if (mutabilityStr == "constant")
|
||||
{
|
||||
mutability = VariableDeclaration::Mutability::Constant;
|
||||
astAssert(memberAsBool(_node, "constant"), "");
|
||||
}
|
||||
else
|
||||
constantness = VariableDeclaration::Constantness::Mutable;
|
||||
{
|
||||
astAssert(!memberAsBool(_node, "constant"), "");
|
||||
if (mutabilityStr == "mutable")
|
||||
mutability = VariableDeclaration::Mutability::Mutable;
|
||||
else if (mutabilityStr == "immutable")
|
||||
mutability = VariableDeclaration::Mutability::Immutable;
|
||||
else
|
||||
astAssert(false, "");
|
||||
}
|
||||
|
||||
return createASTNode<VariableDeclaration>(
|
||||
_node,
|
||||
@ -425,7 +438,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
||||
visibility(_node),
|
||||
memberAsBool(_node, "stateVariable"),
|
||||
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
|
||||
constantness,
|
||||
mutability,
|
||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||
location(_node)
|
||||
);
|
||||
@ -495,7 +508,7 @@ ASTPointer<UserDefinedTypeName> ASTJsonImporter::createUserDefinedTypeName(Json:
|
||||
string nameString = member(_node, "name").asString();
|
||||
boost::algorithm::split(strs, nameString, boost::is_any_of("."));
|
||||
for (string s: strs)
|
||||
namePath.push_back(ASTString(s));
|
||||
namePath.emplace_back(s);
|
||||
return createASTNode<UserDefinedTypeName>(
|
||||
_node,
|
||||
namePath
|
||||
|
@ -23,9 +23,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
@ -299,7 +301,7 @@ public:
|
||||
SimpleASTVisitor(
|
||||
std::function<bool(ASTNode const&)> _onVisit,
|
||||
std::function<void(ASTNode const&)> _onEndVisit
|
||||
): m_onVisit(_onVisit), m_onEndVisit(_onEndVisit) {}
|
||||
): m_onVisit(std::move(_onVisit)), m_onEndVisit(std::move(_onEndVisit)) {}
|
||||
|
||||
protected:
|
||||
bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; }
|
||||
@ -329,7 +331,7 @@ public:
|
||||
ASTReduce(
|
||||
std::function<bool(ASTNode const&)> _onNode,
|
||||
std::function<void(ASTNode const&, ASTNode const&)> _onEdge
|
||||
): m_onNode(_onNode), m_onEdge(_onEdge)
|
||||
): m_onNode(std::move(_onNode)), m_onEdge(std::move(_onEdge))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include <libyul/AsmDataForward.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
@ -35,7 +37,7 @@ namespace solidity::frontend
|
||||
class AsmJsonImporter
|
||||
{
|
||||
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);
|
||||
|
||||
private:
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include <boost/range/algorithm/copy.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -1253,8 +1254,8 @@ StringLiteralType::StringLiteralType(Literal const& _literal):
|
||||
{
|
||||
}
|
||||
|
||||
StringLiteralType::StringLiteralType(string const& _value):
|
||||
m_value{_value}
|
||||
StringLiteralType::StringLiteralType(string _value):
|
||||
m_value{std::move(_value)}
|
||||
{
|
||||
}
|
||||
|
||||
@ -1648,12 +1649,50 @@ bool ArrayType::operator==(Type const& _other) const
|
||||
return isDynamicallySized() || length() == other.length();
|
||||
}
|
||||
|
||||
bool ArrayType::validForCalldata() const
|
||||
BoolResult ArrayType::validForLocation(DataLocation _loc) const
|
||||
{
|
||||
if (auto arrayBaseType = dynamic_cast<ArrayType const*>(baseType()))
|
||||
if (!arrayBaseType->validForCalldata())
|
||||
return false;
|
||||
return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits<unsigned>::max();
|
||||
{
|
||||
BoolResult result = arrayBaseType->validForLocation(_loc);
|
||||
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
|
||||
@ -2174,93 +2213,119 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
||||
|
||||
TypeResult StructType::interfaceType(bool _inLibrary) const
|
||||
{
|
||||
if (_inLibrary && m_interfaceType_library.has_value())
|
||||
return *m_interfaceType_library;
|
||||
|
||||
if (!_inLibrary && m_interfaceType.has_value())
|
||||
if (!_inLibrary)
|
||||
{
|
||||
if (!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;
|
||||
}
|
||||
else if (m_interfaceType_library.has_value())
|
||||
return *m_interfaceType_library;
|
||||
|
||||
TypeResult result{TypePointer{}};
|
||||
|
||||
m_recursive = false;
|
||||
|
||||
auto visitor = [&](
|
||||
StructDefinition const& _struct,
|
||||
util::CycleDetector<StructDefinition>& _cycleDetector,
|
||||
size_t /*_depth*/
|
||||
)
|
||||
{
|
||||
// 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())
|
||||
)
|
||||
util::BreadthFirstSearch<StructDefinition const*> breadthFirstSearch{{&m_struct}};
|
||||
breadthFirstSearch.run(
|
||||
[&](StructDefinition const* _struct, auto&& _addChild) {
|
||||
// Check that all members have interface types.
|
||||
// Return an error if at least one struct member does not have a type.
|
||||
// This might happen, for example, if the type of the member does not exist.
|
||||
for (ASTPointer<VariableDeclaration> const& variable: _struct->members())
|
||||
{
|
||||
m_recursive = true;
|
||||
if (_inLibrary && location() == DataLocation::Storage)
|
||||
continue;
|
||||
else
|
||||
// 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("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;
|
||||
}
|
||||
}
|
||||
|
||||
auto iType = memberType->interfaceType(_inLibrary);
|
||||
if (!iType.get())
|
||||
{
|
||||
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
||||
result = iType;
|
||||
return;
|
||||
Type const* memberType = variable->annotation().type;
|
||||
|
||||
while (dynamic_cast<ArrayType const*>(memberType))
|
||||
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType();
|
||||
|
||||
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
|
||||
{
|
||||
if (innerStruct->recursive() && !(_inLibrary && location() == DataLocation::Storage))
|
||||
{
|
||||
result = TypeResult::err(
|
||||
"Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions."
|
||||
);
|
||||
breadthFirstSearch.abort();
|
||||
return;
|
||||
}
|
||||
else
|
||||
_addChild(&innerStruct->structDefinition());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto iType = memberType->interfaceType(_inLibrary);
|
||||
if (!iType.get())
|
||||
{
|
||||
solAssert(!iType.message().empty(), "Expected detailed error message!");
|
||||
result = iType;
|
||||
breadthFirstSearch.abort();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
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 (_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;
|
||||
if (location() == DataLocation::Storage)
|
||||
m_interfaceType_library = this;
|
||||
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
|
||||
@ -2643,21 +2708,11 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName):
|
||||
for (auto const& t: _typeName.parameterTypes())
|
||||
{
|
||||
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);
|
||||
}
|
||||
for (auto const& t: _typeName.returnParameterTypes())
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@ -3112,6 +3167,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
|
||||
|
||||
TypePointer FunctionType::encodingType() const
|
||||
{
|
||||
if (m_gasSet || m_valueSet)
|
||||
return nullptr;
|
||||
// Only external functions can be encoded, internal functions cannot leave code boundaries.
|
||||
if (m_kind == Kind::External)
|
||||
return this;
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
@ -92,8 +93,8 @@ class MemberList
|
||||
public:
|
||||
struct Member
|
||||
{
|
||||
Member(std::string const& _name, Type const* _type, Declaration const* _declaration = nullptr):
|
||||
name(_name),
|
||||
Member(std::string _name, Type const* _type, Declaration const* _declaration = nullptr):
|
||||
name(std::move(_name)),
|
||||
type(_type),
|
||||
declaration(_declaration)
|
||||
{
|
||||
@ -106,7 +107,7 @@ public:
|
||||
|
||||
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);
|
||||
TypePointer memberType(std::string const& _name) const
|
||||
@ -520,8 +521,8 @@ private:
|
||||
class RationalNumberType: public Type
|
||||
{
|
||||
public:
|
||||
explicit RationalNumberType(rational const& _value, Type const* _compatibleBytesType = nullptr):
|
||||
m_value(_value), m_compatibleBytesType(_compatibleBytesType)
|
||||
explicit RationalNumberType(rational _value, Type const* _compatibleBytesType = nullptr):
|
||||
m_value(std::move(_value)), m_compatibleBytesType(_compatibleBytesType)
|
||||
{}
|
||||
|
||||
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.
|
||||
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.
|
||||
/// If the integer part does not fit, returns an empty pointer.
|
||||
FixedPointType const* fixedPointType() const;
|
||||
@ -582,7 +583,7 @@ class StringLiteralType: public Type
|
||||
{
|
||||
public:
|
||||
explicit StringLiteralType(Literal const& _literal);
|
||||
explicit StringLiteralType(std::string const& _value);
|
||||
explicit StringLiteralType(std::string _value);
|
||||
|
||||
Category category() const override { return Category::StringLiteral; }
|
||||
|
||||
@ -705,6 +706,9 @@ public:
|
||||
/// never change the contents of the original value.
|
||||
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
|
||||
{
|
||||
return location() == _other.location() && isPointer() == _other.isPointer();
|
||||
@ -744,11 +748,11 @@ public:
|
||||
}
|
||||
|
||||
/// 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),
|
||||
m_baseType(copyForLocationIfReference(_baseType)),
|
||||
m_hasDynamicLength(false),
|
||||
m_length(_length)
|
||||
m_length(std::move(_length))
|
||||
{}
|
||||
|
||||
Category category() const override { return Category::Array; }
|
||||
@ -771,8 +775,7 @@ public:
|
||||
TypePointer decodingType() const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
|
||||
/// @returns true if this is valid to be stored in calldata
|
||||
bool validForCalldata() const;
|
||||
BoolResult validForLocation(DataLocation _loc) const override;
|
||||
|
||||
/// @returns true if this is a byte array or a string
|
||||
bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; }
|
||||
@ -826,8 +829,7 @@ public:
|
||||
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
|
||||
std::string toString(bool _short) const override;
|
||||
|
||||
/// @returns true if this is valid to be stored in calldata
|
||||
bool validForCalldata() const { return m_arrayType.validForCalldata(); }
|
||||
BoolResult validForLocation(DataLocation _loc) const override { return m_arrayType.validForLocation(_loc); }
|
||||
|
||||
ArrayType const& arrayType() const { return m_arrayType; }
|
||||
u256 memoryDataSize() const override { solAssert(false, ""); }
|
||||
@ -933,15 +935,9 @@ public:
|
||||
Type const* encodingType() const override;
|
||||
TypeResult interfaceType(bool _inLibrary) const override;
|
||||
|
||||
bool recursive() const
|
||||
{
|
||||
if (m_recursive.has_value())
|
||||
return m_recursive.value();
|
||||
BoolResult validForLocation(DataLocation _loc) const override;
|
||||
|
||||
interfaceType(false);
|
||||
|
||||
return m_recursive.value();
|
||||
}
|
||||
bool recursive() const;
|
||||
|
||||
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
|
||||
@ -970,7 +966,6 @@ private:
|
||||
// Caches for interfaceType(bool)
|
||||
mutable std::optional<TypeResult> m_interfaceType;
|
||||
mutable std::optional<TypeResult> m_interfaceType_library;
|
||||
mutable std::optional<bool> m_recursive;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1131,8 +1126,8 @@ public:
|
||||
|
||||
/// Detailed constructor, use with care.
|
||||
FunctionType(
|
||||
TypePointers const& _parameterTypes,
|
||||
TypePointers const& _returnParameterTypes,
|
||||
TypePointers _parameterTypes,
|
||||
TypePointers _returnParameterTypes,
|
||||
strings _parameterNames = strings(),
|
||||
strings _returnParameterNames = strings(),
|
||||
Kind _kind = Kind::Internal,
|
||||
@ -1144,10 +1139,10 @@ public:
|
||||
bool _saltSet = false,
|
||||
bool _bound = false
|
||||
):
|
||||
m_parameterTypes(_parameterTypes),
|
||||
m_returnParameterTypes(_returnParameterTypes),
|
||||
m_parameterNames(_parameterNames),
|
||||
m_returnParameterNames(_returnParameterNames),
|
||||
m_parameterTypes(std::move(_parameterTypes)),
|
||||
m_returnParameterTypes(std::move(_returnParameterTypes)),
|
||||
m_parameterNames(std::move(_parameterNames)),
|
||||
m_returnParameterNames(std::move(_returnParameterNames)),
|
||||
m_kind(_kind),
|
||||
m_stateMutability(_stateMutability),
|
||||
m_arbitraryParameters(_arbitraryParameters),
|
||||
|
@ -843,7 +843,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
if (dynamicMember)
|
||||
solAssert(dynamic, "");
|
||||
|
||||
members.push_back({});
|
||||
members.emplace_back();
|
||||
members.back()["preprocess"] = "";
|
||||
|
||||
switch (_from.location())
|
||||
@ -1336,7 +1336,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
|
||||
memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false));
|
||||
|
||||
members.push_back({});
|
||||
members.emplace_back();
|
||||
members.back()["decode"] = memberTempl.render();
|
||||
members.back()["memberName"] = member.name;
|
||||
headPos += decodingType->calldataHeadSize();
|
||||
|
@ -1108,12 +1108,12 @@ void CompilerUtils::convertType(
|
||||
// Value shrank
|
||||
for (unsigned j = targetSize; j < sourceSize; ++j)
|
||||
{
|
||||
moveToStackTop(depth - 1, 1);
|
||||
moveToStackTop(depth + targetSize - sourceSize, 1);
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
// Value grew
|
||||
if (targetSize > sourceSize)
|
||||
moveIntoStack(depth + targetSize - sourceSize - 1, targetSize - sourceSize);
|
||||
moveIntoStack(depth - sourceSize, targetSize - sourceSize);
|
||||
}
|
||||
}
|
||||
depth -= sourceSize;
|
||||
|
@ -949,43 +949,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
|
||||
|
||||
// Try to decode the error message.
|
||||
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
|
||||
m_context << u256(0);
|
||||
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.callYulFunction(m_context.utilFunctions().tryDecodeErrorMessageFunction(), 0, 1);
|
||||
m_context << Instruction::DUP1;
|
||||
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
|
||||
m_context << Instruction::POP;
|
||||
|
@ -22,12 +22,14 @@
|
||||
|
||||
#include <libsolidity/codegen/ExpressionCompiler.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/ReturnInfo.h>
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/codegen/LValue.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libevmasm/GasMeter.h>
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
@ -346,15 +348,15 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
||||
if (component)
|
||||
{
|
||||
component->accept(*this);
|
||||
if (_tuple.annotation().lValueRequested)
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
solAssert(!!m_currentLValue, "");
|
||||
lvalues.push_back(move(m_currentLValue));
|
||||
}
|
||||
}
|
||||
else if (_tuple.annotation().lValueRequested)
|
||||
else if (_tuple.annotation().willBeWrittenTo)
|
||||
lvalues.push_back(unique_ptr<LValue>());
|
||||
if (_tuple.annotation().lValueRequested)
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
if (_tuple.components().size() == 1)
|
||||
m_currentLValue = move(lvalues[0]);
|
||||
@ -2185,30 +2187,11 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
solAssert(!_functionType.isBareCall(), "");
|
||||
}
|
||||
|
||||
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||
unsigned retSize = 0;
|
||||
bool dynamicReturnSize = false;
|
||||
TypePointers returnTypes;
|
||||
if (!returnSuccessConditionAndReturndata)
|
||||
{
|
||||
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();
|
||||
}
|
||||
ReturnInfo const returnInfo{m_context.evmVersion(), _functionType};
|
||||
bool const haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||
unsigned const retSize = returnInfo.estimatedReturnSize;
|
||||
bool const dynamicReturnSize = returnInfo.dynamicReturnSize;
|
||||
TypePointers const& returnTypes = returnInfo.returnTypes;
|
||||
|
||||
// Evaluate arguments.
|
||||
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.");
|
||||
std::unique_ptr<LValueType> lvalue = std::make_unique<LValueType>(m_context, _arguments...);
|
||||
if (_expression.annotation().lValueRequested)
|
||||
if (_expression.annotation().willBeWrittenTo)
|
||||
m_currentLValue = move(lvalue);
|
||||
else
|
||||
lvalue->retrieveValue(_expression.location(), true);
|
||||
|
@ -34,6 +34,7 @@ string MultiUseYulFunctionCollector::requestedFunctions()
|
||||
{
|
||||
string result;
|
||||
for (auto const& f: m_requestedFunctions)
|
||||
// std::map guarantees ascending order when iterating through its keys.
|
||||
result += f.second;
|
||||
m_requestedFunctions.clear();
|
||||
return result;
|
||||
|
@ -41,10 +41,15 @@ public:
|
||||
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
||||
|
||||
/// @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
|
||||
/// empty return value.
|
||||
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:
|
||||
/// Map from function name to code for a multi-use function.
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
@ -2079,7 +2079,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
||||
{
|
||||
conversions +=
|
||||
suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) +
|
||||
" := " +
|
||||
(toComponent->sizeOnStack() > 0 ? " := " : "") +
|
||||
conversionFunction(*fromComponent, *toComponent) +
|
||||
"(" +
|
||||
suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) +
|
||||
@ -2089,12 +2089,13 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
||||
sourceStackSize += fromComponent->sizeOnStack();
|
||||
}
|
||||
return Whiskers(R"(
|
||||
function <functionName>(<values>) -> <converted> {
|
||||
function <functionName>(<values>) <arrow> <converted> {
|
||||
<conversions>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("values", suffixedVariableNameList("value", 0, sourceStackSize))
|
||||
("arrow", destStackSize > 0 ? "->" : "")
|
||||
("converted", suffixedVariableNameList("converted", 0, destStackSize))
|
||||
("conversions", conversions)
|
||||
.render();
|
||||
@ -2252,3 +2253,84 @@ string YulUtilFunctions::revertReasonIfDebug(string const& _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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
@ -322,6 +323,20 @@ public:
|
||||
static std::string revertReasonIfDebug(RevertStrings revertStrings, 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();
|
||||
|
||||
private:
|
||||
/// Special case of conversionFunction - handles everything that does not
|
||||
/// use exactly one variable to hold the value.
|
||||
|
@ -32,6 +32,25 @@ using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
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
|
||||
{
|
||||
solAssert(m_mostDerivedContract, "Most derived contract requested but not set.");
|
||||
@ -77,16 +96,22 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
|
||||
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
||||
}
|
||||
|
||||
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
|
||||
{
|
||||
return functionName(_functionDeclaration.resolveVirtual(mostDerivedContract()));
|
||||
}
|
||||
|
||||
string IRGenerationContext::newYulVariable()
|
||||
{
|
||||
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 funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
||||
@ -126,6 +151,8 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
{ "funID", to_string(function->id()) },
|
||||
{ "name", functionName(*function)}
|
||||
});
|
||||
|
||||
enqueueFunctionForCodeGeneration(*function);
|
||||
}
|
||||
templ("cases", move(functions));
|
||||
return templ.render();
|
||||
@ -141,3 +168,4 @@ std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message
|
||||
{
|
||||
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -61,6 +62,15 @@ public:
|
||||
|
||||
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)>
|
||||
void setMostDerivedContract(ContractDefinition const& _mostDerivedContract)
|
||||
{
|
||||
@ -82,7 +92,6 @@ public:
|
||||
|
||||
std::string functionName(FunctionDefinition const& _function);
|
||||
std::string functionName(VariableDeclaration const& _varDecl);
|
||||
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
|
||||
|
||||
std::string newYulVariable();
|
||||
|
||||
@ -99,6 +108,10 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings m_revertStrings;
|
||||
@ -109,6 +122,15 @@ private:
|
||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||
MultiUseYulFunctionCollector m_functions;
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -101,21 +101,13 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
||||
t("memoryInit", memoryInit());
|
||||
t("constructor", constructorCode(_contract));
|
||||
t("deploy", deployCode(_contract));
|
||||
// We generate code for all functions and rely on the optimizer to remove them again
|
||||
// TODO it would probably be better to only generate functions when internalDispatch or
|
||||
// virtualFunctionName is called - same below.
|
||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||
for (auto const* fun: contract->definedFunctions())
|
||||
generateFunction(*fun);
|
||||
generateQueuedFunctions();
|
||||
t("functions", m_context.functionCollector().requestedFunctions());
|
||||
|
||||
resetContext(_contract);
|
||||
m_context.setMostDerivedContract(_contract);
|
||||
t("RuntimeObject", runtimeObjectName(_contract));
|
||||
t("dispatch", dispatchRoutine(_contract));
|
||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||
for (auto const* fun: contract->definedFunctions())
|
||||
generateFunction(*fun);
|
||||
generateQueuedFunctions();
|
||||
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
||||
return t.render();
|
||||
}
|
||||
@ -127,6 +119,13 @@ string IRGenerator::generate(Block const& _block)
|
||||
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 functionName = m_context.functionName(_function);
|
||||
@ -291,7 +290,7 @@ string IRGenerator::constructorCode(ContractDefinition const& _contract)
|
||||
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));
|
||||
t("constructorName", m_context.enqueueFunctionForCodeGeneration(*constructor));
|
||||
|
||||
out << t.render();
|
||||
}
|
||||
@ -352,7 +351,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
vector<map<string, string>> functions;
|
||||
for (auto const& function: _contract.interfaceFunctions())
|
||||
{
|
||||
functions.push_back({});
|
||||
functions.emplace_back();
|
||||
map<string, string>& templ = functions.back();
|
||||
templ["functionSelector"] = "0x" + function.first.hex();
|
||||
FunctionTypePointer const& type = function.second;
|
||||
@ -370,7 +369,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
|
||||
|
||||
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()))
|
||||
templ["function"] = generateGetter(*varDecl);
|
||||
else
|
||||
@ -386,14 +385,14 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
string fallbackCode;
|
||||
if (!fallback->isPayable())
|
||||
fallbackCode += callValueCheck();
|
||||
fallbackCode += generateFunction(*fallback) + "() stop()";
|
||||
fallbackCode += m_context.enqueueFunctionForCodeGeneration(*fallback) + "() stop()";
|
||||
|
||||
t("fallback", fallbackCode);
|
||||
}
|
||||
else
|
||||
t("fallback", "revert(0, 0)");
|
||||
if (FunctionDefinition const* etherReceiver = _contract.receiveFunction())
|
||||
t("receiveEther", generateFunction(*etherReceiver) + "() stop()");
|
||||
t("receiveEther", m_context.enqueueFunctionForCodeGeneration(*etherReceiver) + "() stop()");
|
||||
else
|
||||
t("receiveEther", "");
|
||||
return t.render();
|
||||
@ -413,6 +412,10 @@ string IRGenerator::memoryInit()
|
||||
|
||||
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(
|
||||
m_context.functionGenerationQueueEmpty(),
|
||||
"Reset function generation queue while it still had functions."
|
||||
);
|
||||
solAssert(
|
||||
m_context.functionCollector().requestedFunctions().empty(),
|
||||
"Reset context while it still had functions."
|
||||
|
@ -56,6 +56,9 @@ private:
|
||||
std::string generate(ContractDefinition const& _contract);
|
||||
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.
|
||||
std::string generateFunction(FunctionDefinition const& _function);
|
||||
/// Generates a getter for the given declaration and returns its name
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/codegen/ReturnInfo.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libevmasm/GasMeter.h>
|
||||
@ -41,6 +42,7 @@
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
|
||||
using namespace std;
|
||||
@ -255,14 +257,14 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
solUnimplementedAssert(false, "");
|
||||
else
|
||||
{
|
||||
bool lValueRequested = _tuple.annotation().lValueRequested;
|
||||
if (lValueRequested)
|
||||
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!m_currentLValue, "");
|
||||
if (_tuple.components().size() == 1)
|
||||
{
|
||||
solAssert(_tuple.components().front(), "");
|
||||
_tuple.components().front()->accept(*this);
|
||||
if (lValueRequested)
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!!m_currentLValue, "");
|
||||
else
|
||||
define(_tuple, *_tuple.components().front());
|
||||
@ -274,7 +276,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
if (auto const& component = _tuple.components()[i])
|
||||
{
|
||||
component->accept(*this);
|
||||
if (lValueRequested)
|
||||
if (willBeWrittenTo)
|
||||
{
|
||||
solAssert(!!m_currentLValue, "");
|
||||
lvalues.emplace_back(std::move(m_currentLValue));
|
||||
@ -283,10 +285,10 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
else
|
||||
define(IRVariable(_tuple).tupleComponent(i), *component);
|
||||
}
|
||||
else if (lValueRequested)
|
||||
else if (willBeWrittenTo)
|
||||
lvalues.emplace_back();
|
||||
|
||||
if (_tuple.annotation().lValueRequested)
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
m_currentLValue.emplace(IRLValue{
|
||||
*_tuple.annotation().type,
|
||||
IRLValue::Tuple{std::move(lvalues)}
|
||||
@ -575,7 +577,9 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
|
||||
{
|
||||
define(_functionCall) <<
|
||||
m_context.virtualFunctionName(*functionDef) <<
|
||||
m_context.enqueueFunctionForCodeGeneration(
|
||||
functionDef->resolveVirtual(m_context.mostDerivedContract())
|
||||
) <<
|
||||
"(" <<
|
||||
joinHumanReadable(args) <<
|
||||
")\n";
|
||||
@ -584,6 +588,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
}
|
||||
|
||||
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()
|
||||
@ -840,11 +845,18 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
case Type::Category::Function:
|
||||
if (member == "selector")
|
||||
{
|
||||
solUnimplementedAssert(
|
||||
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
|
||||
FunctionType::Kind::External, ""
|
||||
FunctionType const& functionType = dynamic_cast<FunctionType const&>(
|
||||
*_memberAccess.expression().annotation().type
|
||||
);
|
||||
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")
|
||||
{
|
||||
@ -971,6 +983,78 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
solAssert(false, "Illegal fixed bytes member.");
|
||||
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:
|
||||
solAssert(false, "Member access to unknown type.");
|
||||
}
|
||||
@ -1186,28 +1270,7 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
|
||||
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
|
||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
{
|
||||
// TODO for the constant case, we have to be careful:
|
||||
// If the value is visited twice, `defineExpression` is called twice on
|
||||
// the same expression.
|
||||
solUnimplementedAssert(!varDecl->isConstant(), "");
|
||||
solUnimplementedAssert(!varDecl->immutable(), "");
|
||||
if (m_context.isLocalVariable(*varDecl))
|
||||
setLValue(_identifier, IRLValue{
|
||||
*varDecl->annotation().type,
|
||||
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.");
|
||||
}
|
||||
handleVariableReference(*varDecl, _identifier);
|
||||
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||
{
|
||||
solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported.");
|
||||
@ -1249,6 +1312,33 @@ bool IRGeneratorForStatements::visit(Literal const& _literal)
|
||||
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(
|
||||
FunctionCall const& _functionCall,
|
||||
vector<ASTPointer<Expression const>> const& _arguments
|
||||
@ -1260,39 +1350,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
_arguments.size() == funType.parameterTypes().size(), ""
|
||||
);
|
||||
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::BareCallCode, "Callcode has been removed.");
|
||||
|
||||
bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
|
||||
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
|
||||
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
||||
bool const 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 haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||
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();
|
||||
}
|
||||
ReturnInfo const returnInfo{m_context.evmVersion(), funType};
|
||||
|
||||
TypePointers argumentTypes;
|
||||
vector<string> argumentStrings;
|
||||
@ -1311,8 +1377,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
// (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
|
||||
// would remove that, so we use MSTORE here.
|
||||
if (!funType.gasSet() && estimatedReturnSize > 0)
|
||||
m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n";
|
||||
if (!funType.gasSet() && returnInfo.estimatedReturnSize > 0)
|
||||
m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
|
||||
}
|
||||
|
||||
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
||||
@ -1323,30 +1389,61 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
||||
</checkExistence>
|
||||
|
||||
// storage for arguments and returned data
|
||||
let <pos> := <freeMemory>
|
||||
|
||||
mstore(<pos>, <shl28>(<funId>))
|
||||
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
||||
|
||||
let <result> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||
if iszero(<result>) { <forwardingRevert>() }
|
||||
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||
<?noTryCall>
|
||||
if iszero(<success>) { <forwardingRevert>() }
|
||||
</noTryCall>
|
||||
<?hasRetVars> let <retVars> </hasRetVars>
|
||||
if <success> {
|
||||
<?dynamicReturnSize>
|
||||
// copy dynamic return data out
|
||||
returndatacopy(<pos>, 0, returndatasize())
|
||||
</dynamicReturnSize>
|
||||
|
||||
<?dynamicReturnSize>
|
||||
returndatacopy(<pos>, 0, returndatasize())
|
||||
</dynamicReturnSize>
|
||||
// update freeMemoryPointer according to dynamic return size
|
||||
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
|
||||
|
||||
mstore(<freeMemoryPointer>, add(<pos>, and(add(<returnSize>, 0x1f), not(0x1f))))
|
||||
<?returns> let <retVars> := </returns> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||
// decode return parameters from external try-call into retVars
|
||||
<?hasRetVars> <retVars> := </hasRetVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||
}
|
||||
)");
|
||||
templ("pos", m_context.newYulVariable());
|
||||
templ("end", m_context.newYulVariable());
|
||||
templ("result", m_context.newYulVariable());
|
||||
if (_functionCall.annotation().tryCall)
|
||||
templ("success", m_context.trySuccessConditionVariable(_functionCall));
|
||||
else
|
||||
templ("success", m_context.newYulVariable());
|
||||
templ("freeMemory", freeMemory());
|
||||
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
|
||||
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
||||
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
||||
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.
|
||||
// Move arguments to memory, will not update the free memory pointer (but will update the memory
|
||||
// pointer on the stack).
|
||||
@ -1401,24 +1498,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
|
||||
templ("forwardingRevert", m_utils.forwardingRevertFunction());
|
||||
|
||||
solUnimplementedAssert(!returnSuccessConditionAndReturndata, "");
|
||||
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
|
||||
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();
|
||||
}
|
||||
|
||||
@ -1471,14 +1553,17 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable
|
||||
else
|
||||
m_code << (_declare ? "let ": "") << _lhs.part(stackItemName).name() << " := " << _rhs.part(stackItemName).name() << "\n";
|
||||
else
|
||||
m_code <<
|
||||
(_declare ? "let ": "") <<
|
||||
_lhs.commaSeparatedList() <<
|
||||
" := " <<
|
||||
m_context.utils().conversionFunction(_rhs.type(), _lhs.type()) <<
|
||||
{
|
||||
if (_lhs.type().sizeOnStack() > 0)
|
||||
m_code <<
|
||||
(_declare ? "let ": "") <<
|
||||
_lhs.commaSeparatedList() <<
|
||||
" := ";
|
||||
m_code << m_context.utils().conversionFunction(_rhs.type(), _lhs.type()) <<
|
||||
"(" <<
|
||||
_rhs.commaSeparatedList() <<
|
||||
")\n";
|
||||
}
|
||||
}
|
||||
|
||||
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
|
||||
@ -1540,6 +1625,15 @@ string IRGeneratorForStatements::binaryOperation(
|
||||
case Token::Mod:
|
||||
fun = m_utils.checkedIntModFunction(*type);
|
||||
break;
|
||||
case Token::BitOr:
|
||||
fun = "or";
|
||||
break;
|
||||
case Token::BitXor:
|
||||
fun = "xor";
|
||||
break;
|
||||
case Token::BitAnd:
|
||||
fun = "and";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -1688,7 +1782,7 @@ void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue
|
||||
{
|
||||
solAssert(!m_currentLValue, "");
|
||||
|
||||
if (_expression.annotation().lValueRequested)
|
||||
if (_expression.annotation().willBeWrittenTo)
|
||||
{
|
||||
m_currentLValue.emplace(std::move(_lvalue));
|
||||
solAssert(!_lvalue.type.dataStoredIn(DataLocation::CallData), "");
|
||||
@ -1749,3 +1843,118 @@ Type const& IRGeneratorForStatements::type(Expression const& _expression)
|
||||
solAssert(_expression.annotation().type, "Type of expression not set.");
|
||||
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/IRVariable.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
@ -70,7 +72,26 @@ public:
|
||||
void endVisit(Identifier const& _identifier) override;
|
||||
bool visit(Literal const& _literal) override;
|
||||
|
||||
bool visit(TryStatement const& _tryStatement) override;
|
||||
bool visit(TryCatchClause const& _tryCatchClause) override;
|
||||
|
||||
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.
|
||||
/// All involved expressions have already been visited.
|
||||
void appendExternalFunctionCall(
|
||||
@ -123,7 +144,7 @@ private:
|
||||
/// @returns a fresh IR variable containing the value of the lvalue @a _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.
|
||||
void setLValue(Expression const& _expression, IRLValue _lvalue);
|
||||
void generateLoop(
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
@ -130,15 +131,10 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
clearIndices(&_contract);
|
||||
|
||||
auto errorFunctionSort = make_shared<smt::FunctionSort>(
|
||||
vector<smt::SortPointer>(),
|
||||
smt::SortProvider::boolSort
|
||||
);
|
||||
|
||||
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_implicitConstructorPredicate = createSymbolicBlock(interfaceSort(), "implicit_constructor_" + suffix);
|
||||
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix);
|
||||
auto stateExprs = currentStateVariables();
|
||||
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||
|
||||
@ -148,15 +144,7 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
void CHC::endVisit(ContractDefinition const& _contract)
|
||||
{
|
||||
for (auto const& var: m_stateVariables)
|
||||
{
|
||||
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());
|
||||
auto implicitConstructor = (*m_implicitConstructorPredicate)({});
|
||||
connectBlocks(genesis(), implicitConstructor);
|
||||
m_currentBlock = implicitConstructor;
|
||||
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*> stateVars;
|
||||
for (auto const& contract: _contract.annotation().linearizedBaseContracts)
|
||||
for (auto var: contract->stateVariables())
|
||||
stateVars.push_back(var);
|
||||
return stateVars;
|
||||
return fold(
|
||||
_contract.annotation().linearizedBaseContracts,
|
||||
vector<VariableDeclaration const*>{},
|
||||
[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
|
||||
);
|
||||
}
|
||||
|
||||
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
||||
{
|
||||
vector<smt::SortPointer> stateSorts;
|
||||
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
||||
stateSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
return stateSorts;
|
||||
return applyMap(
|
||||
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
|
||||
);
|
||||
}
|
||||
|
||||
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:
|
||||
/// - Index of failed assertion. 0 means no assertion failed.
|
||||
/// - 2 sets of state variables:
|
||||
@ -695,12 +691,9 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
||||
/// - 1 set of output variables
|
||||
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||
{
|
||||
vector<smt::SortPointer> inputSorts;
|
||||
for (auto const& var: _function.parameters())
|
||||
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
vector<smt::SortPointer> outputSorts;
|
||||
for (auto const& var: _function.returnParameters())
|
||||
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<smt::FunctionSort>(
|
||||
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
||||
smt::SortProvider::boolSort
|
||||
@ -715,11 +708,9 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
|
||||
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
|
||||
solAssert(fSort, "");
|
||||
|
||||
vector<smt::SortPointer> varSorts;
|
||||
for (auto const& var: m_currentFunction->localVariables())
|
||||
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
return make_shared<smt::FunctionSort>(
|
||||
fSort->domain + varSorts,
|
||||
fSort->domain + applyMap(m_currentFunction->localVariables(), smtSort),
|
||||
smt::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
@ -729,11 +720,9 @@ smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractD
|
||||
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
auto sorts = stateSorts(_contract);
|
||||
|
||||
vector<smt::SortPointer> inputSorts, outputSorts;
|
||||
for (auto const& var: _function.parameters())
|
||||
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
for (auto const& var: _function.returnParameters())
|
||||
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<smt::FunctionSort>(
|
||||
vector<smt::SortPointer>{smt::SortProvider::intSort} + sorts + inputSorts + sorts + outputSorts,
|
||||
smt::SortProvider::boolSort
|
||||
@ -769,9 +758,10 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||
|
||||
smt::Expression CHC::interface()
|
||||
{
|
||||
vector<smt::Expression> paramExprs;
|
||||
for (auto const& var: m_stateVariables)
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
auto paramExprs = applyMap(
|
||||
m_stateVariables,
|
||||
[this](auto _var) { return m_context.variable(*_var)->currentValue(); }
|
||||
);
|
||||
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()};
|
||||
auto contract = _function.annotation().contract;
|
||||
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
|
||||
for (auto const& var: _function.parameters())
|
||||
args.push_back(m_context.variable(*var)->valueAtIndex(0));
|
||||
args += applyMap(_function.parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); });
|
||||
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||
for (auto const& var: _function.returnParameters())
|
||||
args.push_back(m_context.variable(*var)->currentValue());
|
||||
args += applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||
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)
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
vector<smt::Expression> exprs;
|
||||
for (auto const& var: m_stateVariables)
|
||||
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
||||
return exprs;
|
||||
return applyMap(m_stateVariables, [&](auto _var) { return valueAtIndex(*_var, _index); });
|
||||
}
|
||||
|
||||
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
|
||||
{
|
||||
vector<smt::Expression> exprs;
|
||||
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
||||
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
||||
return exprs;
|
||||
return applyMap(
|
||||
stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[&](auto _var) { return valueAtIndex(*_var, _index); }
|
||||
);
|
||||
}
|
||||
|
||||
vector<smt::Expression> CHC::currentStateVariables()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
vector<smt::Expression> exprs;
|
||||
for (auto const& var: m_stateVariables)
|
||||
exprs.push_back(m_context.variable(*var)->currentValue());
|
||||
return exprs;
|
||||
return applyMap(m_stateVariables, [this](auto _var) { return currentValue(*_var); });
|
||||
}
|
||||
|
||||
vector<smt::Expression> CHC::currentFunctionVariables()
|
||||
@ -886,9 +868,7 @@ vector<smt::Expression> CHC::currentFunctionVariables()
|
||||
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
|
||||
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
}
|
||||
vector<smt::Expression> returnExprs;
|
||||
for (auto const& var: m_currentFunction->returnParameters())
|
||||
returnExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
auto returnExprs = applyMap(m_currentFunction->returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||
return vector<smt::Expression>{m_error.currentValue()} +
|
||||
initialStateVariables() +
|
||||
initInputExprs +
|
||||
@ -899,11 +879,10 @@ vector<smt::Expression> CHC::currentFunctionVariables()
|
||||
|
||||
vector<smt::Expression> CHC::currentBlockVariables()
|
||||
{
|
||||
vector<smt::Expression> paramExprs;
|
||||
if (m_currentFunction)
|
||||
for (auto const& var: m_currentFunction->localVariables())
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
return currentFunctionVariables() + paramExprs;
|
||||
return currentFunctionVariables() + applyMap(m_currentFunction->localVariables(), [this](auto _var) { return currentValue(*_var); });
|
||||
|
||||
return currentFunctionVariables();
|
||||
}
|
||||
|
||||
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();
|
||||
else
|
||||
createVariable(*param);
|
||||
for (auto const& var: function->returnParameters())
|
||||
args.push_back(m_context.variable(*var)->currentValue());
|
||||
args += applyMap(function->returnParameters(), [this](auto _var) { return currentValue(*_var); });
|
||||
|
||||
if (contract->isLibrary())
|
||||
return (*m_summaries.at(contract).at(function))(args);
|
||||
|
@ -106,6 +106,7 @@ private:
|
||||
smt::SortPointer constructorSort();
|
||||
smt::SortPointer interfaceSort();
|
||||
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||
smt::SortPointer arity0FunctionSort();
|
||||
smt::SortPointer sort(FunctionDefinition const& _function);
|
||||
smt::SortPointer sort(ASTNode const* _block);
|
||||
/// @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, "");
|
||||
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, "");
|
||||
}
|
||||
@ -229,6 +239,11 @@ CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)
|
||||
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ void EncodingContext::pushSolver()
|
||||
if (m_accumulateAssertions)
|
||||
m_assertions.push_back(assertions());
|
||||
else
|
||||
m_assertions.push_back(smt::Expression(true));
|
||||
m_assertions.emplace_back(true);
|
||||
}
|
||||
|
||||
void EncodingContext::popSolver()
|
||||
|
@ -107,6 +107,9 @@ void SMTEncoder::endVisit(ContractDefinition const& _contract)
|
||||
|
||||
solAssert(m_currentContract == &_contract, "");
|
||||
m_currentContract = nullptr;
|
||||
|
||||
if (m_callStack.empty())
|
||||
m_context.popSolver();
|
||||
}
|
||||
|
||||
void SMTEncoder::endVisit(VariableDeclaration const& _varDecl)
|
||||
@ -296,16 +299,22 @@ void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
|
||||
{
|
||||
auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*init));
|
||||
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();
|
||||
solAssert(components.size() == declarations.size(), "");
|
||||
solAssert(symbComponents.size() == declarations.size(), "");
|
||||
for (unsigned i = 0; i < declarations.size(); ++i)
|
||||
if (
|
||||
components.at(i) &&
|
||||
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()))
|
||||
@ -354,7 +363,7 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
|
||||
{
|
||||
auto const& type = _assignment.annotation().type;
|
||||
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()));
|
||||
solAssert(symbTupleLeft, "");
|
||||
@ -365,17 +374,16 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
|
||||
auto const& rightComponents = symbTupleRight->components();
|
||||
solAssert(leftComponents.size() == rightComponents.size(), "");
|
||||
|
||||
for (unsigned i = 0; i < leftComponents.size(); ++i)
|
||||
{
|
||||
auto const& left = leftComponents.at(i);
|
||||
auto const& right = rightComponents.at(i);
|
||||
/// Right hand side tuple component cannot be empty.
|
||||
solAssert(right, "");
|
||||
if (left)
|
||||
rightArguments.push_back(right->currentValue(left->originalType()));
|
||||
else
|
||||
rightArguments.push_back(right->currentValue());
|
||||
}
|
||||
auto tupleTypeLeft = dynamic_cast<TupleType const*>(_assignment.leftHandSide().annotation().type);
|
||||
solAssert(tupleTypeLeft, "");
|
||||
solAssert(tupleTypeLeft->components().size() == leftComponents.size(), "");
|
||||
auto const& typesLeft = tupleTypeLeft->components();
|
||||
|
||||
solAssert(tupleTypeRight->components().size() == rightComponents.size(), "");
|
||||
auto const& typesRight = tupleTypeRight->components();
|
||||
|
||||
for (unsigned i = 0; i < rightComponents.size(); ++i)
|
||||
rightArguments.push_back(symbTupleRight->component(i, typesRight.at(i), typesLeft.at(i)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -418,17 +426,16 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
|
||||
solAssert(symbComponents.size() == tupleComponents->size(), "");
|
||||
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
||||
{
|
||||
auto sComponent = symbComponents.at(i);
|
||||
auto tComponent = tupleComponents->at(i);
|
||||
if (sComponent && tComponent)
|
||||
if (tComponent)
|
||||
{
|
||||
if (auto varDecl = identifierToVariable(*tComponent))
|
||||
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
|
||||
m_context.addAssertion(symbTuple->component(i) == currentValue(*varDecl));
|
||||
else
|
||||
{
|
||||
if (!m_context.knownExpression(*tComponent))
|
||||
createExpr(*tComponent);
|
||||
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
|
||||
m_context.addAssertion(symbTuple->component(i) == expr(*tComponent));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -463,7 +470,7 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
|
||||
{
|
||||
|
||||
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()))
|
||||
{
|
||||
auto decl = identifierToVariable(*identifier);
|
||||
@ -658,7 +665,6 @@ void SMTEncoder::initFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
solAssert(m_callStack.empty(), "");
|
||||
solAssert(m_currentContract, "");
|
||||
m_context.reset();
|
||||
m_context.pushSolver();
|
||||
m_pathConditions.clear();
|
||||
pushCallStack({&_function, nullptr});
|
||||
@ -700,7 +706,7 @@ void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
|
||||
|
||||
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.
|
||||
}
|
||||
@ -807,13 +813,15 @@ void SMTEncoder::endVisit(Return const& _return)
|
||||
{
|
||||
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*_return.expression()));
|
||||
solAssert(symbTuple, "");
|
||||
auto const& components = symbTuple->components();
|
||||
solAssert(components.size() == returnParams.size(), "");
|
||||
solAssert(symbTuple->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)
|
||||
{
|
||||
solAssert(components.at(i), "");
|
||||
m_context.addAssertion(components.at(i)->currentValue(returnParams.at(i)->type()) == m_context.newValue(*returnParams.at(i)));
|
||||
}
|
||||
m_context.addAssertion(symbTuple->component(i, types.at(i), returnParams.at(i)->type()) == m_context.newValue(*returnParams.at(i)));
|
||||
}
|
||||
else if (returnParams.size() == 1)
|
||||
m_context.addAssertion(expr(*_return.expression(), returnParams.front()->type()) == m_context.newValue(*returnParams.front()));
|
||||
@ -1676,14 +1684,10 @@ void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
|
||||
solAssert(symbComponents.size() == returnParams.size(), "");
|
||||
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
||||
{
|
||||
auto sComponent = symbComponents.at(i);
|
||||
auto param = returnParams.at(i);
|
||||
solAssert(param, "");
|
||||
if (sComponent)
|
||||
{
|
||||
solAssert(m_context.knownVariable(*param), "");
|
||||
m_context.addAssertion(sComponent->currentValue() == currentValue(*param));
|
||||
}
|
||||
solAssert(m_context.knownVariable(*param), "");
|
||||
m_context.addAssertion(symbTuple->component(i) == currentValue(*param));
|
||||
}
|
||||
}
|
||||
else if (returnParams.size() == 1)
|
||||
|
@ -28,6 +28,8 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -37,10 +39,10 @@ using namespace solidity::frontend::smt;
|
||||
|
||||
SMTLib2Interface::SMTLib2Interface(
|
||||
map<h256, string> const& _queryResponses,
|
||||
ReadCallback::Callback const& _smtCallback
|
||||
ReadCallback::Callback _smtCallback
|
||||
):
|
||||
m_queryResponses(_queryResponses),
|
||||
m_smtCallback(_smtCallback)
|
||||
m_smtCallback(std::move(_smtCallback))
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@ -50,6 +52,7 @@ void SMTLib2Interface::reset()
|
||||
m_accumulatedOutput.clear();
|
||||
m_accumulatedOutput.emplace_back();
|
||||
m_variables.clear();
|
||||
m_userSorts.clear();
|
||||
write("(set-option :produce-models true)");
|
||||
write("(set-logic ALL)");
|
||||
}
|
||||
@ -145,6 +148,14 @@ string SMTLib2Interface::toSExpr(smt::Expression const& _expr)
|
||||
sexpr += "(as const " + toSmtLibSort(*arraySort) + ") ";
|
||||
sexpr += toSExpr(_expr.arguments.at(1));
|
||||
}
|
||||
else if (_expr.name == "tuple_get")
|
||||
{
|
||||
solAssert(_expr.arguments.size() == 2, "");
|
||||
auto tupleSort = dynamic_pointer_cast<TupleSort>(_expr.arguments.at(0).sort);
|
||||
unsigned index = std::stoi(_expr.arguments.at(1).name);
|
||||
solAssert(index < tupleSort->members.size(), "");
|
||||
sexpr += tupleSort->members.at(index) + " " + toSExpr(_expr.arguments.at(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
sexpr += _expr.name;
|
||||
@ -169,6 +180,22 @@ string SMTLib2Interface::toSmtLibSort(Sort const& _sort)
|
||||
solAssert(arraySort.domain && arraySort.range, "");
|
||||
return "(Array " + toSmtLibSort(*arraySort.domain) + ' ' + toSmtLibSort(*arraySort.range) + ')';
|
||||
}
|
||||
case Kind::Tuple:
|
||||
{
|
||||
auto const& tupleSort = dynamic_cast<TupleSort const&>(_sort);
|
||||
if (!m_userSorts.count(tupleSort.name))
|
||||
{
|
||||
m_userSorts.insert(tupleSort.name);
|
||||
string decl("(declare-datatypes ((" + tupleSort.name + " 0)) (((" + tupleSort.name);
|
||||
solAssert(tupleSort.members.size() == tupleSort.components.size(), "");
|
||||
for (unsigned i = 0; i < tupleSort.members.size(); ++i)
|
||||
decl += " (" + tupleSort.members.at(i) + " " + toSmtLibSort(*tupleSort.components.at(i)) + ")";
|
||||
decl += "))))";
|
||||
write(decl);
|
||||
}
|
||||
|
||||
return tupleSort.name;
|
||||
}
|
||||
default:
|
||||
solAssert(false, "Invalid SMT sort");
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class SMTLib2Interface: public SolverInterface, public boost::noncopyable
|
||||
public:
|
||||
explicit SMTLib2Interface(
|
||||
std::map<util::h256, std::string> const& _queryResponses,
|
||||
ReadCallback::Callback const& _smtCallback
|
||||
ReadCallback::Callback _smtCallback
|
||||
);
|
||||
|
||||
void reset() override;
|
||||
@ -74,6 +74,7 @@ private:
|
||||
|
||||
std::vector<std::string> m_accumulatedOutput;
|
||||
std::map<std::string, SortPointer> m_variables;
|
||||
std::set<std::string> m_userSorts;
|
||||
|
||||
std::map<util::h256, std::string> const& m_queryResponses;
|
||||
std::vector<std::string> m_unhandledQueries;
|
||||
|
@ -94,7 +94,8 @@ public:
|
||||
{"mod", 2},
|
||||
{"select", 2},
|
||||
{"store", 3},
|
||||
{"const_array", 2}
|
||||
{"const_array", 2},
|
||||
{"tuple_get", 2}
|
||||
};
|
||||
return operatorsArity.count(name) && operatorsArity.at(name) == arguments.size();
|
||||
}
|
||||
@ -166,6 +167,19 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
static Expression tuple_get(Expression _tuple, size_t _index)
|
||||
{
|
||||
solAssert(_tuple.sort->kind == Kind::Tuple, "");
|
||||
std::shared_ptr<TupleSort> tupleSort = std::dynamic_pointer_cast<TupleSort>(_tuple.sort);
|
||||
solAssert(tupleSort, "");
|
||||
solAssert(_index < tupleSort->components.size(), "");
|
||||
return Expression(
|
||||
"tuple_get",
|
||||
std::vector<Expression>{std::move(_tuple), Expression(_index)},
|
||||
tupleSort->components.at(_index)
|
||||
);
|
||||
}
|
||||
|
||||
friend Expression operator!(Expression _a)
|
||||
{
|
||||
return Expression("not", std::move(_a), Kind::Bool);
|
||||
|
@ -33,7 +33,8 @@ enum class Kind
|
||||
Bool,
|
||||
Function,
|
||||
Array,
|
||||
Sort
|
||||
Sort,
|
||||
Tuple
|
||||
};
|
||||
|
||||
struct Sort
|
||||
@ -115,6 +116,46 @@ struct SortSort: public Sort
|
||||
SortPointer inner;
|
||||
};
|
||||
|
||||
struct TupleSort: public Sort
|
||||
{
|
||||
TupleSort(
|
||||
std::string _name,
|
||||
std::vector<std::string> _members,
|
||||
std::vector<SortPointer> _components
|
||||
):
|
||||
Sort(Kind::Tuple),
|
||||
name(std::move(_name)),
|
||||
members(std::move(_members)),
|
||||
components(std::move(_components))
|
||||
{}
|
||||
|
||||
bool operator==(Sort const& _other) const override
|
||||
{
|
||||
if (!Sort::operator==(_other))
|
||||
return false;
|
||||
auto _otherTuple = dynamic_cast<TupleSort const*>(&_other);
|
||||
solAssert(_otherTuple, "");
|
||||
if (name != _otherTuple->name)
|
||||
return false;
|
||||
if (members != _otherTuple->members)
|
||||
return false;
|
||||
if (components.size() != _otherTuple->components.size())
|
||||
return false;
|
||||
if (!std::equal(
|
||||
components.begin(),
|
||||
components.end(),
|
||||
_otherTuple->components.begin(),
|
||||
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
|
||||
))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string const name;
|
||||
std::vector<std::string> const members;
|
||||
std::vector<SortPointer> const components;
|
||||
};
|
||||
|
||||
/** Frequently used sorts.*/
|
||||
struct SortProvider
|
||||
{
|
||||
|
@ -75,6 +75,21 @@ SortPointer smtSort(frontend::Type const& _type)
|
||||
return make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
|
||||
}
|
||||
}
|
||||
case Kind::Tuple:
|
||||
{
|
||||
auto tupleType = dynamic_cast<frontend::TupleType const*>(&_type);
|
||||
solAssert(tupleType, "");
|
||||
vector<string> members;
|
||||
auto const& tupleName = _type.identifier();
|
||||
auto const& components = tupleType->components();
|
||||
for (unsigned i = 0; i < components.size(); ++i)
|
||||
members.emplace_back(tupleName + "_accessor_" + to_string(i));
|
||||
return make_shared<TupleSort>(
|
||||
tupleName,
|
||||
members,
|
||||
smtSortAbstractFunction(tupleType->components())
|
||||
);
|
||||
}
|
||||
default:
|
||||
// Abstract case.
|
||||
return SortProvider::intSort;
|
||||
@ -96,6 +111,17 @@ SortPointer smtSortAbstractFunction(frontend::Type const& _type)
|
||||
return smtSort(_type);
|
||||
}
|
||||
|
||||
vector<SortPointer> smtSortAbstractFunction(vector<frontend::TypePointer> const& _types)
|
||||
{
|
||||
vector<SortPointer> sorts;
|
||||
for (auto const& type: _types)
|
||||
if (type)
|
||||
sorts.push_back(smtSortAbstractFunction(*type));
|
||||
else
|
||||
sorts.push_back(SortProvider::intSort);
|
||||
return sorts;
|
||||
}
|
||||
|
||||
Kind smtKind(frontend::Type::Category _category)
|
||||
{
|
||||
if (isNumber(_category))
|
||||
@ -106,6 +132,8 @@ Kind smtKind(frontend::Type::Category _category)
|
||||
return Kind::Function;
|
||||
else if (isMapping(_category) || isArray(_category))
|
||||
return Kind::Array;
|
||||
else if (isTuple(_category))
|
||||
return Kind::Tuple;
|
||||
// Abstract case.
|
||||
return Kind::Int;
|
||||
}
|
||||
@ -350,4 +378,17 @@ void setSymbolicUnknownValue(Expression _expr, frontend::TypePointer const& _typ
|
||||
}
|
||||
}
|
||||
|
||||
optional<Expression> symbolicTypeConversion(TypePointer _from, TypePointer _to)
|
||||
{
|
||||
if (_to && _from)
|
||||
// StringLiterals are encoded as SMT arrays in the generic case,
|
||||
// but they can also be compared/assigned to fixed bytes, in which
|
||||
// case they'd need to be encoded as numbers.
|
||||
if (auto strType = dynamic_cast<StringLiteralType const*>(_from))
|
||||
if (_to->category() == frontend::Type::Category::FixedBytes)
|
||||
return smt::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add)));
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ std::vector<SortPointer> smtSort(std::vector<frontend::TypePointer> const& _type
|
||||
/// If _type has type Function, abstract it to Integer.
|
||||
/// Otherwise return smtSort(_type).
|
||||
SortPointer smtSortAbstractFunction(frontend::Type const& _type);
|
||||
std::vector<SortPointer> smtSortAbstractFunction(std::vector<frontend::TypePointer> const& _types);
|
||||
/// Returns the SMT kind that models the Solidity type type category _category.
|
||||
Kind smtKind(frontend::Type::Category _category);
|
||||
|
||||
@ -69,4 +70,5 @@ void setSymbolicZeroValue(Expression _expr, frontend::TypePointer const& _type,
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context);
|
||||
void setSymbolicUnknownValue(Expression _expr, frontend::TypePointer const& _type, EncodingContext& _context);
|
||||
|
||||
std::optional<Expression> symbolicTypeConversion(TypePointer _from, TypePointer _to);
|
||||
}
|
||||
|
@ -230,16 +230,9 @@ SymbolicArrayVariable::SymbolicArrayVariable(
|
||||
|
||||
smt::Expression SymbolicArrayVariable::currentValue(frontend::TypePointer const& _targetType) const
|
||||
{
|
||||
if (_targetType)
|
||||
{
|
||||
solAssert(m_originalType, "");
|
||||
// StringLiterals are encoded as SMT arrays in the generic case,
|
||||
// but they can also be compared/assigned to fixed bytes, in which
|
||||
// case they'd need to be encoded as numbers.
|
||||
if (auto strType = dynamic_cast<StringLiteralType const*>(m_originalType))
|
||||
if (_targetType->category() == frontend::Type::Category::FixedBytes)
|
||||
return smt::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add)));
|
||||
}
|
||||
optional<smt::Expression> conversion = symbolicTypeConversion(m_originalType, _targetType);
|
||||
if (conversion)
|
||||
return *conversion;
|
||||
|
||||
return SymbolicVariable::currentValue(_targetType);
|
||||
}
|
||||
@ -262,16 +255,34 @@ SymbolicTupleVariable::SymbolicTupleVariable(
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isTuple(m_type->category()), "");
|
||||
auto const& tupleType = dynamic_cast<TupleType const&>(*m_type);
|
||||
auto const& componentsTypes = tupleType.components();
|
||||
for (unsigned i = 0; i < componentsTypes.size(); ++i)
|
||||
if (componentsTypes.at(i))
|
||||
{
|
||||
string componentName = m_uniqueName + "_component_" + to_string(i);
|
||||
auto result = smt::newSymbolicVariable(*componentsTypes.at(i), componentName, m_context);
|
||||
solAssert(result.second, "");
|
||||
m_components.emplace_back(move(result.second));
|
||||
}
|
||||
else
|
||||
m_components.emplace_back(nullptr);
|
||||
}
|
||||
|
||||
SymbolicTupleVariable::SymbolicTupleVariable(
|
||||
SortPointer _sort,
|
||||
string _uniqueName,
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_sort), move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(m_sort->kind == Kind::Tuple, "");
|
||||
}
|
||||
|
||||
vector<SortPointer> const& SymbolicTupleVariable::components()
|
||||
{
|
||||
auto tupleSort = dynamic_pointer_cast<TupleSort>(m_sort);
|
||||
solAssert(tupleSort, "");
|
||||
return tupleSort->components;
|
||||
}
|
||||
|
||||
smt::Expression SymbolicTupleVariable::component(
|
||||
size_t _index,
|
||||
TypePointer _fromType,
|
||||
TypePointer _toType
|
||||
)
|
||||
{
|
||||
optional<smt::Expression> conversion = symbolicTypeConversion(_fromType, _toType);
|
||||
if (conversion)
|
||||
return *conversion;
|
||||
|
||||
return smt::Expression::tuple_get(currentValue(), _index);
|
||||
}
|
||||
|
@ -249,14 +249,18 @@ public:
|
||||
std::string _uniqueName,
|
||||
EncodingContext& _context
|
||||
);
|
||||
SymbolicTupleVariable(
|
||||
SortPointer _sort,
|
||||
std::string _uniqueName,
|
||||
EncodingContext& _context
|
||||
);
|
||||
|
||||
std::vector<std::shared_ptr<SymbolicVariable>> const& components()
|
||||
{
|
||||
return m_components;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<SymbolicVariable>> m_components;
|
||||
std::vector<SortPointer> const& components();
|
||||
Expression component(
|
||||
size_t _index,
|
||||
TypePointer _fromType = nullptr,
|
||||
TypePointer _toType = nullptr
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -40,15 +40,15 @@ set<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _
|
||||
|
||||
void VariableUsage::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
if (_identifier.annotation().lValueRequested)
|
||||
if (_identifier.annotation().willBeWrittenTo)
|
||||
checkIdentifier(_identifier);
|
||||
}
|
||||
|
||||
void VariableUsage::endVisit(IndexAccess const& _indexAccess)
|
||||
{
|
||||
if (_indexAccess.annotation().lValueRequested)
|
||||
if (_indexAccess.annotation().willBeWrittenTo)
|
||||
{
|
||||
/// identifier.annotation().lValueRequested == false, that's why we
|
||||
/// identifier.annotation().willBeWrittenTo == false, that's why we
|
||||
/// need to check that before.
|
||||
auto identifier = dynamic_cast<Identifier const*>(SMTEncoder::leftmostBase(_indexAccess));
|
||||
if (identifier)
|
||||
|
@ -193,6 +193,11 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
|
||||
solAssert(arraySort && arraySort->domain, "");
|
||||
return z3::const_array(z3Sort(*arraySort->domain), arguments[1]);
|
||||
}
|
||||
else if (n == "tuple_get")
|
||||
{
|
||||
size_t index = std::stoi(_expr.arguments[1].name);
|
||||
return z3::func_decl(m_context, Z3_get_tuple_sort_field_decl(m_context, z3Sort(*_expr.arguments[0].sort), index))(arguments[0]);
|
||||
}
|
||||
|
||||
solAssert(false, "");
|
||||
}
|
||||
@ -217,6 +222,28 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort)
|
||||
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
|
||||
return m_context.array_sort(z3Sort(*arraySort.domain), z3Sort(*arraySort.range));
|
||||
}
|
||||
case Kind::Tuple:
|
||||
{
|
||||
auto const& tupleSort = dynamic_cast<TupleSort const&>(_sort);
|
||||
vector<char const*> cMembers;
|
||||
for (auto const& member: tupleSort.members)
|
||||
cMembers.emplace_back(member.c_str());
|
||||
/// Using this instead of the function below because with that one
|
||||
/// we can't use `&sorts[0]` here.
|
||||
vector<z3::sort> sorts;
|
||||
for (auto const& sort: tupleSort.components)
|
||||
sorts.push_back(z3Sort(*sort));
|
||||
z3::func_decl_vector projs(m_context);
|
||||
z3::func_decl tupleConstructor = m_context.tuple_sort(
|
||||
tupleSort.name.c_str(),
|
||||
tupleSort.members.size(),
|
||||
&cMembers[0],
|
||||
&sorts[0],
|
||||
projs
|
||||
);
|
||||
return tupleConstructor.range();
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <libsolidity/analysis/ControlFlowAnalyzer.h>
|
||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||
#include <libsolidity/analysis/DocStringAnalyser.h>
|
||||
#include <libsolidity/analysis/GlobalContext.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
@ -67,6 +68,7 @@
|
||||
#include <json/json.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <utility>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -78,8 +80,8 @@ using solidity::util::toHex;
|
||||
|
||||
static int g_compilerStackCounts = 0;
|
||||
|
||||
CompilerStack::CompilerStack(ReadCallback::Callback const& _readFile):
|
||||
m_readFile{_readFile},
|
||||
CompilerStack::CompilerStack(ReadCallback::Callback _readFile):
|
||||
m_readFile{std::move(_readFile)},
|
||||
m_enabledSMTSolvers{smt::SMTSolverChoice::All()},
|
||||
m_generateIR{false},
|
||||
m_generateEwasm{false},
|
||||
@ -348,6 +350,11 @@ bool CompilerStack::analyze()
|
||||
|
||||
}
|
||||
|
||||
DeclarationTypeChecker declarationTypeChecker(m_errorReporter, m_evmVersion);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !declarationTypeChecker.check(*source->ast))
|
||||
return false;
|
||||
|
||||
// Next, we check inheritance, overrides, function collisions and other things at
|
||||
// contract or function level.
|
||||
// This also calculates whether a contract is abstract, which is needed by the
|
||||
|
@ -108,7 +108,7 @@ public:
|
||||
/// Creates a new compiler stack.
|
||||
/// @param _readFile callback used to read files for import statements. Must return
|
||||
/// and must not emit exceptions.
|
||||
explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback());
|
||||
explicit CompilerStack(ReadCallback::Callback _readFile = ReadCallback::Callback());
|
||||
|
||||
~CompilerStack();
|
||||
|
||||
|
@ -200,7 +200,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil
|
||||
/// for Contract-level targets try both contract name and wildcard
|
||||
vector<string> contracts{ _contract };
|
||||
if (!_contract.empty())
|
||||
contracts.push_back("*");
|
||||
contracts.emplace_back("*");
|
||||
for (auto const& contract: contracts)
|
||||
if (
|
||||
_outputSelection[file].isMember(contract) &&
|
||||
@ -591,7 +591,7 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
|
||||
));
|
||||
else
|
||||
{
|
||||
ret.sources[sourceName] = result.responseOrErrorMessage;
|
||||
ret.sources[sourceName] = result.responseOrErrorMessage;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
@ -24,8 +24,9 @@
|
||||
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
|
||||
#include <optional>
|
||||
#include <boost/variant.hpp>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
@ -40,8 +41,8 @@ public:
|
||||
/// Creates a new StandardCompiler.
|
||||
/// @param _readFile callback used to read files for import statements. Must return
|
||||
/// and must not emit exceptions.
|
||||
explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()):
|
||||
m_readFile(_readFile)
|
||||
explicit StandardCompiler(ReadCallback::Callback _readFile = ReadCallback::Callback()):
|
||||
m_readFile(std::move(_readFile))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -695,7 +695,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
);
|
||||
|
||||
bool isIndexed = false;
|
||||
VariableDeclaration::Constantness constantness = VariableDeclaration::Constantness::Mutable;
|
||||
VariableDeclaration::Mutability mutability = VariableDeclaration::Mutability::Mutable;
|
||||
ASTPointer<OverrideSpecifier> overrides = nullptr;
|
||||
Visibility visibility(Visibility::Default);
|
||||
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
|
||||
@ -732,15 +732,15 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
isIndexed = true;
|
||||
else if (token == Token::Constant || token == Token::Immutable)
|
||||
{
|
||||
if (constantness != VariableDeclaration::Constantness::Mutable)
|
||||
if (mutability != VariableDeclaration::Mutability::Mutable)
|
||||
parserError(
|
||||
string("Constantness already set to ") +
|
||||
(constantness == VariableDeclaration::Constantness::Constant ? "\"constant\"" : "\"immutable\"")
|
||||
string("Mutability already set to ") +
|
||||
(mutability == VariableDeclaration::Mutability::Constant ? "\"constant\"" : "\"immutable\"")
|
||||
);
|
||||
else if (token == Token::Constant)
|
||||
constantness = VariableDeclaration::Constantness::Constant;
|
||||
mutability = VariableDeclaration::Mutability::Constant;
|
||||
else if (token == Token::Immutable)
|
||||
constantness = VariableDeclaration::Constantness::Immutable;
|
||||
mutability = VariableDeclaration::Mutability::Immutable;
|
||||
}
|
||||
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
|
||||
{
|
||||
@ -800,7 +800,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
||||
visibility,
|
||||
_options.isStateVariable,
|
||||
isIndexed,
|
||||
constantness,
|
||||
mutability,
|
||||
overrides,
|
||||
location
|
||||
);
|
||||
|
@ -114,6 +114,10 @@ struct BreadthFirstSearch
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
void abort()
|
||||
{
|
||||
verticesToTraverse.clear();
|
||||
}
|
||||
|
||||
std::set<V> verticesToTraverse;
|
||||
std::set<V> visited{};
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@ -120,7 +121,7 @@ inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes)
|
||||
class ScopeGuard
|
||||
{
|
||||
public:
|
||||
explicit ScopeGuard(std::function<void(void)> _f): m_f(_f) {}
|
||||
explicit ScopeGuard(std::function<void(void)> _f): m_f(std::move(_f)) {}
|
||||
~ScopeGuard() { m_f(); }
|
||||
|
||||
private:
|
||||
|
@ -34,11 +34,12 @@
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
/// Operators need to stay in the global namespace.
|
||||
|
||||
/// Concatenate the contents of a container onto a vector
|
||||
template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U const& _b)
|
||||
template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U& _b)
|
||||
{
|
||||
for (auto const& i: _b)
|
||||
_a.push_back(i);
|
||||
@ -51,7 +52,7 @@ template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U&& _
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a multiset
|
||||
template <class U, class... T> std::multiset<T...>& operator+=(std::multiset<T...>& _a, U const& _b)
|
||||
template <class U, class... T> std::multiset<T...>& operator+=(std::multiset<T...>& _a, U& _b)
|
||||
{
|
||||
_a.insert(_b.begin(), _b.end());
|
||||
return _a;
|
||||
@ -64,7 +65,7 @@ template <class U, class... T> std::multiset<T...>& operator+=(std::multiset<T..
|
||||
return _a;
|
||||
}
|
||||
/// Concatenate the contents of a container onto a set
|
||||
template <class U, class... T> std::set<T...>& operator+=(std::set<T...>& _a, U const& _b)
|
||||
template <class U, class... T> std::set<T...>& operator+=(std::set<T...>& _a, U& _b)
|
||||
{
|
||||
_a.insert(_b.begin(), _b.end());
|
||||
return _a;
|
||||
@ -141,6 +142,36 @@ inline std::multiset<T...>& operator-=(std::multiset<T...>& _a, C const& _b)
|
||||
namespace solidity::util
|
||||
{
|
||||
|
||||
/// Functional map.
|
||||
/// Returns a container _oc applying @param _op to each element in @param _c.
|
||||
/// By default _oc is a vector.
|
||||
/// If another return type is desired, an empty contained of that type
|
||||
/// is given as @param _oc.
|
||||
template<class Container, class Callable, class OutputContainer =
|
||||
std::vector<std::invoke_result_t<
|
||||
Callable,
|
||||
decltype(*std::begin(std::declval<Container>()))
|
||||
>>>
|
||||
auto applyMap(Container const& _c, Callable&& _op, OutputContainer _oc = OutputContainer{})
|
||||
{
|
||||
std::transform(std::begin(_c), std::end(_c), std::inserter(_oc, std::end(_oc)), _op);
|
||||
return _oc;
|
||||
}
|
||||
|
||||
/// Functional fold.
|
||||
/// Given a container @param _c, an initial value @param _acc,
|
||||
/// and a binary operator @param _binaryOp(T, U), accumulate
|
||||
/// the elements of _c over _acc.
|
||||
/// Note that <numeric> has a similar function `accumulate` which
|
||||
/// until C++20 does *not* std::move the partial accumulated.
|
||||
template<class C, class T, class Callable>
|
||||
auto fold(C const& _c, T _acc, Callable&& _binaryOp)
|
||||
{
|
||||
for (auto const& e: _c)
|
||||
_acc = _binaryOp(std::move(_acc), e);
|
||||
return _acc;
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
T convertContainer(U const& _from)
|
||||
{
|
||||
|
@ -160,7 +160,7 @@ bytes solidity::util::ipfsHash(string _data)
|
||||
|
||||
Chunks allChunks;
|
||||
|
||||
for (unsigned long chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++)
|
||||
for (size_t chunkIndex = 0; chunkIndex < chunkCount; chunkIndex++)
|
||||
{
|
||||
bytes chunkBytes = asBytes(
|
||||
_data.substr(chunkIndex * maxChunkSize, min(maxChunkSize, _data.length() - chunkIndex * maxChunkSize))
|
||||
|
@ -36,7 +36,7 @@ namespace solidity::util
|
||||
///
|
||||
|
||||
template <class ResultType>
|
||||
class Result
|
||||
class [[nodiscard]] Result
|
||||
{
|
||||
public:
|
||||
/// Constructs a result with _value and an empty message.
|
||||
|
@ -255,14 +255,14 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
yulAssert(!_funCall.functionName.name.empty(), "");
|
||||
vector<YulString> const* parameterTypes = nullptr;
|
||||
vector<YulString> const* returnTypes = nullptr;
|
||||
bool needsLiteralArguments = false;
|
||||
vector<bool> const* needsLiteralArguments = nullptr;
|
||||
|
||||
if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name))
|
||||
{
|
||||
parameterTypes = &f->parameters;
|
||||
returnTypes = &f->returns;
|
||||
if (f->literalArguments)
|
||||
needsLiteralArguments = true;
|
||||
needsLiteralArguments = &f->literalArguments.value();
|
||||
}
|
||||
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
|
||||
[&](Scope::Variable const&)
|
||||
@ -293,11 +293,13 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
);
|
||||
|
||||
vector<YulString> argTypes;
|
||||
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
|
||||
for (size_t i = _funCall.arguments.size(); i > 0; i--)
|
||||
{
|
||||
Expression const& arg = _funCall.arguments[i - 1];
|
||||
|
||||
argTypes.emplace_back(expectExpression(arg));
|
||||
|
||||
if (needsLiteralArguments)
|
||||
if (needsLiteralArguments && (*needsLiteralArguments)[i - 1])
|
||||
{
|
||||
if (!holds_alternative<Literal>(arg))
|
||||
typeError(
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
@ -58,14 +59,14 @@ public:
|
||||
AsmAnalysisInfo& _analysisInfo,
|
||||
langutil::ErrorReporter& _errorReporter,
|
||||
Dialect const& _dialect,
|
||||
ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver(),
|
||||
std::set<YulString> const& _dataNames = {}
|
||||
ExternalIdentifierAccess::Resolver _resolver = ExternalIdentifierAccess::Resolver(),
|
||||
std::set<YulString> _dataNames = {}
|
||||
):
|
||||
m_resolver(_resolver),
|
||||
m_resolver(std::move(_resolver)),
|
||||
m_info(_analysisInfo),
|
||||
m_errorReporter(_errorReporter),
|
||||
m_dialect(_dialect),
|
||||
m_dataNames(_dataNames)
|
||||
m_dataNames(std::move(_dataNames))
|
||||
{
|
||||
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&m_dialect))
|
||||
m_evmVersion = evmDialect->evmVersion();
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <optional>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
@ -46,8 +47,8 @@ struct BuiltinFunction
|
||||
ControlFlowSideEffects controlFlowSideEffects;
|
||||
/// If true, this is the msize instruction.
|
||||
bool isMSize = false;
|
||||
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
||||
bool literalArguments = false;
|
||||
/// If set, same length as the arguments, if true at index i, the i'th argument has to be a literal which means it can't be moved to variables.
|
||||
std::optional<std::vector<bool>> literalArguments;
|
||||
};
|
||||
|
||||
struct Dialect: boost::noncopyable
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
using namespace std;
|
||||
@ -100,7 +101,7 @@ CodeTransform::CodeTransform(
|
||||
EVMDialect const& _dialect,
|
||||
BuiltinContext& _builtinContext,
|
||||
bool _evm15,
|
||||
ExternalIdentifierAccess const& _identifierAccess,
|
||||
ExternalIdentifierAccess _identifierAccess,
|
||||
bool _useNamedLabelsForFunctions,
|
||||
shared_ptr<Context> _context
|
||||
):
|
||||
@ -111,8 +112,8 @@ CodeTransform::CodeTransform(
|
||||
m_allowStackOpt(_allowStackOpt),
|
||||
m_evm15(_evm15),
|
||||
m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions),
|
||||
m_identifierAccess(_identifierAccess),
|
||||
m_context(_context)
|
||||
m_identifierAccess(std::move(_identifierAccess)),
|
||||
m_context(std::move(_context))
|
||||
{
|
||||
if (!m_context)
|
||||
{
|
||||
|
@ -152,7 +152,7 @@ protected:
|
||||
EVMDialect const& _dialect,
|
||||
BuiltinContext& _builtinContext,
|
||||
bool _evm15,
|
||||
ExternalIdentifierAccess const& _identifierAccess,
|
||||
ExternalIdentifierAccess _identifierAccess,
|
||||
bool _useNamedLabelsForFunctions,
|
||||
std::shared_ptr<Context> _context
|
||||
);
|
||||
|
@ -55,7 +55,7 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
||||
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
||||
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
|
||||
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
||||
f.literalArguments = false;
|
||||
f.literalArguments.reset();
|
||||
f.instruction = _instruction;
|
||||
f.generateCode = [_instruction](
|
||||
FunctionCall const&,
|
||||
@ -75,17 +75,22 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
|
||||
size_t _params,
|
||||
size_t _returns,
|
||||
SideEffects _sideEffects,
|
||||
bool _literalArguments,
|
||||
vector<bool> _literalArguments,
|
||||
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
|
||||
)
|
||||
{
|
||||
solAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
|
||||
|
||||
YulString name{std::move(_name)};
|
||||
BuiltinFunctionForEVM f;
|
||||
f.name = name;
|
||||
f.parameters.resize(_params);
|
||||
f.returns.resize(_returns);
|
||||
f.sideEffects = std::move(_sideEffects);
|
||||
f.literalArguments = _literalArguments;
|
||||
if (!_literalArguments.empty())
|
||||
f.literalArguments = std::move(_literalArguments);
|
||||
else
|
||||
f.literalArguments.reset();
|
||||
f.isMSize = false;
|
||||
f.instruction = {};
|
||||
f.generateCode = std::move(_generateCode);
|
||||
@ -107,7 +112,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
|
||||
if (_objectAccess)
|
||||
{
|
||||
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, true, [](
|
||||
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {true}, [](
|
||||
FunctionCall const& _call,
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext& _context,
|
||||
@ -128,7 +133,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
_assembly.appendDataSize(_context.subIDs.at(dataName));
|
||||
}
|
||||
}));
|
||||
builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, true, [](
|
||||
builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, {true}, [](
|
||||
FunctionCall const& _call,
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext& _context,
|
||||
@ -154,7 +159,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
3,
|
||||
0,
|
||||
SideEffects{false, false, false, false, true},
|
||||
false,
|
||||
{},
|
||||
[](
|
||||
FunctionCall const&,
|
||||
AbstractAssembly& _assembly,
|
||||
@ -262,7 +267,7 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA
|
||||
m_functions["popbool"_yulstring] = m_functions["pop"_yulstring];
|
||||
m_functions["popbool"_yulstring].name = "popbool"_yulstring;
|
||||
m_functions["popbool"_yulstring].parameters = {"bool"_yulstring};
|
||||
m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, false, [](
|
||||
m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, {}, [](
|
||||
FunctionCall const&,
|
||||
AbstractAssembly&,
|
||||
BuiltinContext&,
|
||||
@ -272,7 +277,7 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA
|
||||
}));
|
||||
m_functions["bool_to_u256"_yulstring].parameters = {"bool"_yulstring};
|
||||
m_functions["bool_to_u256"_yulstring].returns = {"u256"_yulstring};
|
||||
m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, false, [](
|
||||
m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, {}, [](
|
||||
FunctionCall const&,
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext&,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user