diff --git a/.circleci/config.yml b/.circleci/config.yml index 23da90b8f..bfadb1139 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: diff --git a/.circleci/osx_install_dependencies.sh b/.circleci/osx_install_dependencies.sh index 59e7c2234..2409cbad7 100755 --- a/.circleci/osx_install_dependencies.sh +++ b/.circleci/osx_install_dependencies.sh @@ -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 diff --git a/Changelog.md b/Changelog.md index 73159ed9a..d62d7ca06 100644 --- a/Changelog.md +++ b/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. diff --git a/docs/_static/css/dark.css b/docs/_static/css/dark.css new file mode 100644 index 000000000..f9b445898 --- /dev/null +++ b/docs/_static/css/dark.css @@ -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 */ \ No newline at end of file diff --git a/docs/_static/css/toggle.css b/docs/_static/css/toggle.css new file mode 100644 index 000000000..ebbd0658a --- /dev/null +++ b/docs/_static/css/toggle.css @@ -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; +} \ No newline at end of file diff --git a/docs/_static/js/toggle.js b/docs/_static/js/toggle.js new file mode 100644 index 000000000..f46a3a666 --- /dev/null +++ b/docs/_static/js/toggle.js @@ -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); + }) + +}); \ No newline at end of file diff --git a/docs/_templates/versions.html b/docs/_templates/versions.html new file mode 100644 index 000000000..f680b6506 --- /dev/null +++ b/docs/_templates/versions.html @@ -0,0 +1,36 @@ +{# Add rst-badge after rst-versions for small badge style. #} +
+ + RTD + + + + + + + v: {{ current_version }} + + +
+
+
{{ _('Versions') }}
{% for slug, url in versions %} +
{{ slug }}
+ {% endfor %} +
+
+
{{ _('Downloads') }}
{% for type, url in downloads %} +
{{ type }}
+ {% endfor %} +
+
+ {# Translators: The phrase "Read the Docs" is not translated #} +
{{ _('On Read the Docs') }}
+
+ {{ _('Project Home') }} +
+
+ {{ _('Builds') }} +
+
+
+
\ No newline at end of file diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 053b6dad4..eb344ea8a 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -202,7 +202,7 @@ on the type of ``X`` being - ``uint``: ``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``: ``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``: ``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`` - ``fixedx``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``. - ``fixed``: as in the ``fixed128x18`` case diff --git a/docs/bugs.json b/docs/bugs.json index 066e17e93..42a5277b1 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -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.", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index b12393016..7ccd0c66a 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -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" } } \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index c8d60690b..9a9d574fc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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. diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index 3195b5a40..5557aea87 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -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 `. 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 ` 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 diff --git a/docs/contracts/visibility-and-getters.rst b/docs/contracts/visibility-and-getters.rst index 2a6869adf..fda4e5055 100644 --- a/docs/contracts/visibility-and-getters.rst +++ b/docs/contracts/visibility-and-getters.rst @@ -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. diff --git a/docs/contributing.rst b/docs/contributing.rst index 34b2db383..ce9cd97c4 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -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 `_ +library, others require `libz3 `_. 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 `_ 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 `_ 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 `_ -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 `_ +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) `_ 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 -------------------------------- diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst index 8d3b2e1f3..477246e0e 100644 --- a/docs/examples/micropayment.rst +++ b/docs/examples/micropayment.rst @@ -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 `_ of this code. + such as openzepplin's `version `_ 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 `_ 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: :: diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index d02651a2e..27a20b3bc 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -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 `_. 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 ====== diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 17740fcda..b051a97f8 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -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``, diff --git a/docs/natspec-format.rst b/docs/natspec-format.rst index 6f241eda3..ed9705117 100644 --- a/docs/natspec-format.rst +++ b/docs/natspec-format.rst @@ -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 diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 2b0bfee34..2dd7ef877 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -24,8 +24,11 @@ solidity code. The goal of this guide is *consistency*. A quote from python's `pep8 `_ 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 `_ 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 `_ with the comments added looks like the one below:: diff --git a/docs/types.rst b/docs/types.rst index b9c06f6c8..d929a274f 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -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` dependent on its type. To handle any unexpected values, you should use the :ref:`revert function` 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 \ No newline at end of file +.. include:: types/conversion.rst diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 98c3336c4..ba79adcb7 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -332,7 +332,7 @@ the :ref:`address type
`. 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. diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 6c625e997..75ed7546f 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -140,15 +140,19 @@ Error Handling See the dedicated section on :ref:`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
` 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 ------------------------ -``
.balance`` (``uint256``): +``
.balance`` (``uint256``) balance of the :ref:`address` in Wei -``
.transfer(uint256 amount)``: + +``
.transfer(uint256 amount)`` send given amount of Wei to :ref:`address`, reverts on failure, forwards 2300 gas stipend, not adjustable -``
.send(uint256 amount) returns (bool)``: + +``
.send(uint256 amount) returns (bool)`` send given amount of Wei to :ref:`address`, returns ``false`` on failure, forwards 2300 gas stipend, not adjustable -``
.call(bytes memory) returns (bool, bytes memory)``: + +``
.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 -``
.delegatecall(bytes memory) returns (bool, bytes memory)``: + +``
.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 -``
.staticcall(bytes memory) returns (bool, bytes memory)``: + +``
.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 diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index acf3a416e..3151480c3 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -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; diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index 644f4a274..62780b19f 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -22,15 +22,17 @@ */ #include -#include -#include -#include -#include -#include #include #include #include +#include +#include + +#include +#include +#include + using namespace std; using namespace solidity; using namespace solidity::evmasm; diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 64b772c6e..a6c0f29d9 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -28,6 +28,7 @@ #include #include +#include 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 const& _state, langutil::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0): - m_state(_state), m_evmVersion(_evmVersion), m_largestMemoryAccess(_largestMemoryAccess) {} + GasMeter(std::shared_ptr _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. diff --git a/libevmasm/KnownState.h b/libevmasm/KnownState.h index a9cc4f201..b19585036 100644 --- a/libevmasm/KnownState.h +++ b/libevmasm/KnownState.h @@ -23,6 +23,7 @@ #pragma once +#include #include #include #include @@ -83,7 +84,7 @@ public: explicit KnownState( std::shared_ptr _expressionClasses = std::make_shared() - ): m_expressionClasses(_expressionClasses) + ): m_expressionClasses(std::move(_expressionClasses)) { } diff --git a/liblangutil/CharStream.h b/liblangutil/CharStream.h index eb9b1f12b..6962864b9 100644 --- a/liblangutil/CharStream.h +++ b/liblangutil/CharStream.h @@ -55,6 +55,7 @@ #include #include #include +#include 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(); } diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index 575847ed9..b8688afa6 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -22,15 +22,16 @@ #pragma once -#include -#include -#include -#include #include #include #include #include +#include +#include +#include +#include + namespace solidity::langutil { class Error; diff --git a/liblangutil/SemVerHandler.h b/liblangutil/SemVerHandler.h index f5cdf860d..d3dfd5a5b 100644 --- a/liblangutil/SemVerHandler.h +++ b/liblangutil/SemVerHandler.h @@ -23,7 +23,9 @@ #pragma once #include + #include +#include #include namespace solidity::langutil @@ -80,8 +82,8 @@ struct SemVerMatchExpression class SemVerMatchExpressionParser { public: - SemVerMatchExpressionParser(std::vector const& _tokens, std::vector const& _literals): - m_tokens(_tokens), m_literals(_literals) + SemVerMatchExpressionParser(std::vector _tokens, std::vector _literals): + m_tokens(std::move(_tokens)), m_literals(std::move(_literals)) {} SemVerMatchExpression parse(); diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index efb553e1a..fe1bbad7a 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -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 diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h index e92113bb8..f07e6783d 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -24,6 +24,8 @@ #include +#include + namespace solidity::langutil { class ErrorReporter; @@ -47,7 +49,7 @@ public: ): m_errorReporter(_errorReporter), m_depth(_newDepth), - m_types(_types) + m_types(std::move(_types)) { } diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index f4b6b5d5f..a0c557a3e 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -591,7 +591,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier) if (auto const* variableDeclaration = dynamic_cast(_identifier.annotation().referencedDeclaration)) m_currentNode->variableOccurrences.emplace_back( *variableDeclaration, - static_cast(_identifier).annotation().lValueRequested ? + static_cast(_identifier).annotation().willBeWrittenTo ? VariableOccurrence::Kind::Assignment : VariableOccurrence::Kind::Access, _identifier.location() diff --git a/libsolidity/analysis/ControlFlowGraph.h b/libsolidity/analysis/ControlFlowGraph.h index 93e2e3b89..4d11ef8f6 100644 --- a/libsolidity/analysis/ControlFlowGraph.h +++ b/libsolidity/analysis/ControlFlowGraph.h @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace solidity::frontend @@ -48,8 +49,8 @@ public: Assignment, InlineAssembly }; - VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional const& _occurrence = {}): - m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence) + VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional _occurrence = {}): + m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(std::move(_occurrence)) { } diff --git a/libsolidity/analysis/DeclarationTypeChecker.cpp b/libsolidity/analysis/DeclarationTypeChecker.cpp new file mode 100644 index 000000000..0c53732d6 --- /dev/null +++ b/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -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 . +*/ + +#include + +#include + +#include + +#include + +#include + +#include + +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 const& member: _struct.members()) + { + Type const* memberType = member->annotation().type; + while (auto arrayType = dynamic_cast(memberType)) + { + if (arrayType->isDynamicallySized()) + break; + memberType = arrayType->baseType(); + } + if (auto structType = dynamic_cast(memberType)) + if (_cycleDetector.run(structType->structDefinition())) + return; + } + }; + if (util::CycleDetector(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(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(declaration)) + _typeName.annotation().type = TypeProvider::enumType(*enumDef); + else if (ContractDefinition const* contract = dynamic_cast(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(&_mapping.keyType())) + { + if (auto const* contractType = dynamic_cast(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(&_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(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 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(_variable.scope()) || + dynamic_cast(_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(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; +} diff --git a/libsolidity/analysis/DeclarationTypeChecker.h b/libsolidity/analysis/DeclarationTypeChecker.h new file mode 100644 index 000000000..b704c4fd2 --- /dev/null +++ b/libsolidity/analysis/DeclarationTypeChecker.h @@ -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 . +*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +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 m_currentStructsSeen; +}; + +} diff --git a/libsolidity/analysis/ImmutableValidator.cpp b/libsolidity/analysis/ImmutableValidator.cpp index 6e0d0f6c7..b03d314eb 100644 --- a/libsolidity/analysis/ImmutableValidator.cpp +++ b/libsolidity/analysis/ImmutableValidator.cpp @@ -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( diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 3643c6401..65831ec86 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -195,51 +195,6 @@ Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector c return nullptr; } -vector NameAndTypeResolver::cleanedDeclarations( - Identifier const& _identifier, - vector const& _declarations -) -{ - solAssert(_declarations.size() > 1, ""); - vector uniqueFunctions; - - for (Declaration const* declaration: _declarations) - { - solAssert(declaration, ""); - // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1 - solAssert( - dynamic_cast(declaration) || - dynamic_cast(declaration) || - dynamic_cast(declaration) || - dynamic_cast(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) diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index dc4d32bb9..845b2870f 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -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 const& _path) const; - /// returns the vector of declarations without repetitions - std::vector cleanedDeclarations( - Identifier const& _identifier, - std::vector const& _declarations - ); - /// Generate and store warnings about variables that are named like instructions. void warnVariablesNamedLikeInstructions(); diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 94a16d6f1..f72253c1f 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -22,9 +22,7 @@ #include #include -#include #include -#include #include #include @@ -37,7 +35,6 @@ #include #include -#include 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(declaration)) - _typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage); - else if (EnumDefinition const* enumDef = dynamic_cast(declaration)) - _typeName.annotation().type = TypeProvider::enumType(*enumDef); - else if (ContractDefinition const* contract = dynamic_cast(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(&_mapping.keyType())) - { - if (auto const* contractType = dynamic_cast(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(&_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(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 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(_variable.scope()) || - dynamic_cast(_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(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; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 488562f21..2a4daf3db 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -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); diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2c57457ec..3112e6d0e 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -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 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& _cycleDetector, size_t _depth) - { - if (_depth >= 256) - m_errorReporter.fatalDeclarationError(_struct.location(), "Struct definition exhausting cyclic dependency validator."); - - for (ASTPointer const& member: _struct.members()) - { - Type const* memberType = type(*member); - while (auto arrayType = dynamic_cast(memberType)) - { - if (arrayType->isDynamicallySized()) - break; - memberType = arrayType->baseType(); - } - if (auto structType = dynamic_cast(memberType)) - if (_cycleDetector.run(structType->structDefinition())) - return; - } - }; - if (CycleDetector(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(varType)) { - case Type::Category::Array: - if (auto arrayType = dynamic_cast(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(*_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> 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(*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(*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(*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(*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 TypeChecker::cleanOverloadedDeclarations( + Identifier const& _identifier, + vector const& _candidates +) +{ + solAssert(_candidates.size() > 1, ""); + vector uniqueDeclarations; + + for (Declaration const* declaration: _candidates) + { + solAssert(declaration, ""); + // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1 + solAssert( + dynamic_cast(declaration) || + dynamic_cast(declaration) || + dynamic_cast(declaration) || + dynamic_cast(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); diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index a26ab81bd..585f7d95f 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -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 cleanOverloadedDeclarations( + Identifier const& _reference, + std::vector 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); diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 2468b0338..5381fd166 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -23,6 +23,7 @@ #include #include +#include #include using namespace std; @@ -41,7 +42,7 @@ public: std::function _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(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()); } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index d9425937f..ecb24de4c 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -28,16 +28,18 @@ #include #include + #include #include +#include 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(); + return initAnnotation(); } 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(scope())) + if (callable->visibility() == Visibility::Public) + return !isReturnParameter(); + + return false; +} + bool VariableDeclaration::isInternalCallableParameter() const { if (!isCallableOrCatchParameter()) @@ -614,12 +629,20 @@ set 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::Storage }; - else - // TODO: add Location::Calldata once implemented for local variables. - return set{ Location::Memory, Location::Storage }; + auto dataLocations = [](TypePointer _type, auto&& _recursion) -> set { + solAssert(_type, "Can only be called after reference resolution"); + switch (_type->category()) + { + case Type::Category::Array: + return _recursion(dynamic_cast(_type)->baseType(), _recursion); + case Type::Category::Mapping: + return set{ Location::Storage }; + default: + // TODO: add Location::Calldata once implemented for local variables. + return set{ 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; +} diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index b1328d008..0b24acb6c 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -38,6 +38,7 @@ #include #include #include +#include #include 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 ASTNode::filteredNodes(std::vector> co class SourceUnit: public ASTNode { public: - SourceUnit(int64_t _id, SourceLocation const& _location, std::vector> const& _nodes): - ASTNode(_id, _location), m_nodes(_nodes) {} + SourceUnit(int64_t _id, SourceLocation const& _location, std::vector> _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 const& _name, + ASTPointer _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 const& _tokens, - std::vector const& _literals - ): ASTNode(_id, _location), m_tokens(_tokens), m_literals(_literals) + std::vector _tokens, + std::vector _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 const& _path, + ASTPointer _path, ASTPointer 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 const& _text - ): ASTNode(_id, _location), m_text(_text) + ASTPointer _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 const& _documentation): m_documentation(_documentation) {} + explicit Documented(ASTPointer _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 const& _documentation): m_documentation(_documentation) {} + explicit StructurallyDocumented(ASTPointer _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 const& _name, ASTPointer const& _documentation, - std::vector> const& _baseContracts, - std::vector> const& _subNodes, + std::vector> _baseContracts, + std::vector> _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 const& _baseName, + ASTPointer _baseName, std::unique_ptr>> _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 const& _libraryName, - ASTPointer const& _typeName + ASTPointer _libraryName, + ASTPointer _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 const& _name, - std::vector> const& _members + std::vector> _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> m_members; @@ -619,9 +620,9 @@ public: int64_t _id, SourceLocation const& _location, ASTPointer const& _name, - std::vector> const& _members + std::vector> _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> const& _parameters + std::vector> _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 const& _name, Visibility _visibility, - ASTPointer const& _parameters, + ASTPointer _parameters, bool _isVirtual = false, - ASTPointer const& _overrides = nullptr, - ASTPointer const& _returnParameters = ASTPointer() + ASTPointer _overrides = nullptr, + ASTPointer _returnParameters = ASTPointer() ): 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> const& _overrides + std::vector> _overrides ): ASTNode(_id, _location), - m_overrides(_overrides) + m_overrides(std::move(_overrides)) { } @@ -771,7 +772,7 @@ public: ASTPointer const& _overrides, ASTPointer const& _documentation, ASTPointer const& _parameters, - std::vector> const& _modifiers, + std::vector> _modifiers, ASTPointer const& _returnParameters, ASTPointer 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 const& _type, + ASTPointer _type, ASTPointer const& _name, ASTPointer _value, Visibility _visibility, bool _isStateVar = false, bool _isIndexed = false, - Constantness _constantness = Constantness::Mutable, - ASTPointer const& _overrides = nullptr, + Mutability _mutability = Mutability::Mutable, + ASTPointer _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 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 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 const& _parameters, bool _isVirtual, ASTPointer const& _overrides, - ASTPointer const& _body + ASTPointer _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 const& _name, + ASTPointer _name, std::unique_ptr>> _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 const& _namePath): - TypeName(_id, _location), m_namePath(_namePath) {} + UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector _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 const& _parameterTypes, - ASTPointer const& _returnTypes, + ASTPointer _parameterTypes, + ASTPointer _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 const& _keyType, - ASTPointer const& _valueType + ASTPointer _keyType, + ASTPointer _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 const& _baseType, - ASTPointer const& _length + ASTPointer _baseType, + ASTPointer _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 const& _docString, yul::Dialect const& _dialect, - std::shared_ptr const& _operations + std::shared_ptr _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 const& _docString, - std::vector> const& _statements + std::vector> _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 const& _docString, - ASTPointer const& _condition, - ASTPointer const& _trueBody, - ASTPointer const& _falseBody + ASTPointer _condition, + ASTPointer _trueBody, + ASTPointer _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 const& _errorName, - ASTPointer const& _parameters, - ASTPointer const& _block + ASTPointer _errorName, + ASTPointer _parameters, + ASTPointer _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 const& _docString, - ASTPointer const& _externalCall, - std::vector> const& _clauses + ASTPointer _externalCall, + std::vector> _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> const& clauses() const { return m_clauses; } + TryCatchClause const* successClause() const; + TryCatchClause const* structuredClause() const; + TryCatchClause const* fallbackClause() const; + private: ASTPointer m_externalCall; std::vector> m_clauses; @@ -1465,11 +1483,11 @@ public: int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, - ASTPointer const& _condition, - ASTPointer const& _body, + ASTPointer _condition, + ASTPointer _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 const& _docString, - ASTPointer const& _initExpression, - ASTPointer const& _conditionExpression, - ASTPointer const& _loopExpression, - ASTPointer const& _body + ASTPointer _initExpression, + ASTPointer _conditionExpression, + ASTPointer _loopExpression, + ASTPointer _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 const& _docString, ASTPointer _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 const& _docString, - ASTPointer const& _functionCall + ASTPointer _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 const& _docString, - std::vector> const& _variables, - ASTPointer const& _initialValue + std::vector> _variables, + ASTPointer _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 const& _docString, ASTPointer _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 const& _condition, - ASTPointer const& _trueExpression, - ASTPointer const& _falseExpression + ASTPointer _condition, + ASTPointer _trueExpression, + ASTPointer _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 const& _leftHandSide, + ASTPointer _leftHandSide, Token _assignmentOperator, - ASTPointer const& _rightHandSide + ASTPointer _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> const& _components, + std::vector> _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 const& _subExpression, + ASTPointer _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 const& _left, + ASTPointer _left, Token _operator, - ASTPointer const& _right + ASTPointer _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 const& _expression, - std::vector> const& _arguments, - std::vector> const& _names + ASTPointer _expression, + std::vector> _arguments, + std::vector> _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 const& _expression, - std::vector> const& _options, - std::vector> const& _names + ASTPointer _expression, + std::vector> _options, + std::vector> _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 const& _typeName + ASTPointer _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, - ASTPointer const& _memberName + ASTPointer _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 const& _base, - ASTPointer const& _index + ASTPointer _base, + ASTPointer _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 const& _base, - ASTPointer const& _start, - ASTPointer const& _end + ASTPointer _base, + ASTPointer _start, + ASTPointer _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 const& _name + ASTPointer _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 const& _type + ASTPointer _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 const& _value, + ASTPointer _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; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 021a742f5..2589103cb 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -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 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 candidateDeclarations; /// List of possible declarations it could refer to. std::vector overloadedDeclarations; }; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index ed3c6fff9..c9d3e1389 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -45,7 +46,7 @@ namespace solidity::frontend ASTJsonConverter::ASTJsonConverter(bool _legacy, map _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), diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index 26a7c7b7e..7d388877b 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -411,11 +411,24 @@ ASTPointer 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( _node, @@ -425,7 +438,7 @@ ASTPointer 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 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( _node, namePath diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index db67dc392..52e90b0d7 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -23,9 +23,11 @@ #pragma once #include + #include #include #include +#include namespace solidity::frontend { @@ -299,7 +301,7 @@ public: SimpleASTVisitor( std::function _onVisit, std::function _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 _onNode, std::function _onEdge - ): m_onNode(_onNode), m_onEdge(_onEdge) + ): m_onNode(std::move(_onNode)), m_onEdge(std::move(_onEdge)) { } diff --git a/libsolidity/ast/AsmJsonImporter.h b/libsolidity/ast/AsmJsonImporter.h index e7f4821c2..498b4e489 100644 --- a/libsolidity/ast/AsmJsonImporter.h +++ b/libsolidity/ast/AsmJsonImporter.h @@ -26,6 +26,8 @@ #include #include +#include + 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: diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3fc649caf..8f1e6c910 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -43,6 +43,7 @@ #include #include +#include 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(baseType())) - if (!arrayBaseType->validForCalldata()) - return false; - return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits::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(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::max()) + return BoolResult::err("Type too large for memory."); + break; + } + case DataLocation::CallData: + { + if (unlimitedStaticCalldataSize(true) >= numeric_limits::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 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& _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 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(memberType)) - memberType = dynamic_cast(memberType)->baseType(); - - if (StructType const* innerStruct = dynamic_cast(memberType)) - if ( - innerStruct->m_recursive == true || - _cycleDetector.run(innerStruct->structDefinition()) - ) + util::BreadthFirstSearch 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 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(memberType)) + memberType = dynamic_cast(memberType)->baseType(); + + if (StructType const* innerStruct = dynamic_cast(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(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(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 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; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index d0e774cb7..c1f66df4d 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -38,6 +38,7 @@ #include #include #include +#include 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; - 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 copyForLocation(DataLocation _location, bool _isPointer) const override; @@ -970,7 +966,6 @@ private: // Caches for interfaceType(bool) mutable std::optional m_interfaceType; mutable std::optional m_interfaceType_library; - mutable std::optional 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), diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 5cca1c1e4..8909651be 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -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(); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index d4f35ad79..3d0117707 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -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; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a599de6a4..18de2ba84 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -949,43 +949,7 @@ void ContractCompiler::handleCatch(vector> 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 := - if iszero(eq(sig, 0x)) { 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; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a02497561..b41950fef 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -22,12 +22,14 @@ #include -#include -#include +#include #include #include #include +#include +#include + #include #include #include @@ -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()); - 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; diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index f75fbc8ab..0216b260b 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -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 lvalue = std::make_unique(m_context, _arguments...); - if (_expression.annotation().lValueRequested) + if (_expression.annotation().willBeWrittenTo) m_currentLValue = move(lvalue); else lvalue->retrieveValue(_expression.location(), true); diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.cpp b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp index 2be3d29ae..e8994ac04 100644 --- a/libsolidity/codegen/MultiUseYulFunctionCollector.cpp +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp @@ -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; diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.h b/libsolidity/codegen/MultiUseYulFunctionCollector.h index d839a31be..031d08bde 100644 --- a/libsolidity/codegen/MultiUseYulFunctionCollector.h +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.h @@ -41,10 +41,15 @@ public: std::string createFunction(std::string const& _name, std::function 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 m_requestedFunctions; diff --git a/libsolidity/codegen/ReturnInfo.cpp b/libsolidity/codegen/ReturnInfo.cpp new file mode 100644 index 000000000..b522addd4 --- /dev/null +++ b/libsolidity/codegen/ReturnInfo.cpp @@ -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 . +*/ + +#include + +#include +#include + +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(); + } +} diff --git a/libsolidity/codegen/ReturnInfo.h b/libsolidity/codegen/ReturnInfo.h new file mode 100644 index 000000000..01aa30862 --- /dev/null +++ b/libsolidity/codegen/ReturnInfo.h @@ -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 . +*/ +/** + * Component that computes information relevant during decoding an external function + * call's return values. + */ +#pragma once + +#include +#include + +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; +}; + +} diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 28a4f69fb..e520c586f 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -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 () -> { + function () { } )") ("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 () -> ret { + if lt(returndatasize(), 0x44) { leave } + + returndatacopy(0, 0, 4) + let sig := (mload(0)) + if iszero(eq(sig, 0x)) { leave } + + let data := mload() + 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(, add(add(msg, 0x20), (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 () -> data { + + switch returndatasize() + case 0 { + data := () + } + default { + // allocate some memory into data of size returndatasize() + PADDING + data := ((add(returndatasize(), 0x20))) + + // store array length into the front + mstore(data, returndatasize()) + + // append to data + returndatacopy(add(data, 0x20), 0, returndatasize()) + } + + data := () + + } + )") + ("functionName", functionName) + ("supportsReturndata", m_evmVersion.supportsReturndata()) + ("allocate", allocationFunction()) + ("roundUp", roundUpFunction()) + ("emptyArray", zeroValueFunction(*TypeProvider::bytesMemory())) + .render(); + }); +} + diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index b902f9f64..9550b9e89 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -22,6 +22,7 @@ #include +#include #include #include @@ -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. diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 26f6f0309..c3b896ee1 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -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(_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); } + diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index b0fc92cdb..acbe6250b 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -30,6 +30,7 @@ #include +#include #include #include #include @@ -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> 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 m_functionGenerationQueue; }; } diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 5ae4b71d7..7499d3909 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -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> functions; for (auto const& function: _contract.interfaceFunctions()) { - functions.push_back({}); + functions.emplace_back(); map& 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(&type->declaration())) - templ["function"] = generateFunction(*funDef); + templ["function"] = m_context.enqueueFunctionForCodeGeneration(*funDef); else if (VariableDeclaration const* varDecl = dynamic_cast(&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." diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index e0c1dcd4f..c4035141c 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -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 diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 57c8cb152..e375739d0 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,7 @@ #include #include +#include #include 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(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(*_memberAccess.expression().annotation().type).kind() == - FunctionType::Kind::External, "" + FunctionType const& functionType = dynamic_cast( + *_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( + *_memberAccess.expression().annotation().type + ).actualType(); + + if (actualType.category() == Type::Category::Contract) + { + if (auto const* variable = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) + handleVariableReference(*variable, _memberAccess); + else if (auto const* funType = dynamic_cast(_memberAccess.annotation().type)) + { + switch (funType->kind()) + { + case FunctionType::Kind::Declaration: + break; + case FunctionType::Kind::Internal: + if (auto const* function = dynamic_cast(_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(_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(_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(&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(declaration)) define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n"; else if (VariableDeclaration const* varDecl = dynamic_cast(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(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> 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 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(
)) { revert(0, 0) } + // storage for arguments and returned data let := - mstore(, ()) let := (add(, 4) ) - let := (,
, , , sub(, ), , ) - if iszero() { () } + let := (,
, , , sub(, ), , ) + + if iszero() { () } + + let + if { + + // copy dynamic return data out + returndatacopy(, 0, returndatasize()) + - - returndatacopy(, 0, returndatasize()) - + // update freeMemoryPointer according to dynamic return size + mstore(, add(, ())) - mstore(, add(, and(add(, 0x1f), not(0x1f)))) - let := (, add(, )) + // decode return parameters from external try-call into retVars + := (, add(, )) + } )"); 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 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; +} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 710961cd7..c0cf11ba0 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -24,6 +24,8 @@ #include #include +#include + 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( diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp index 5067e1396..59e77d7cd 100644 --- a/libsolidity/formal/CHC.cpp +++ b/libsolidity/formal/CHC.cpp @@ -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( - vector(), - 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 CHC::transactionAssertions(ASTNode const* vector CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract) { - vector 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{}, + [](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); } + ); } vector CHC::stateSorts(ContractDefinition const& _contract) { - vector 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( + vector(), + 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 inputSorts; - for (auto const& var: _function.parameters()) - inputSorts.push_back(smt::smtSortAbstractFunction(*var->type())); - vector 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( vector{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(sort(*m_currentFunction)); solAssert(fSort, ""); - vector 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( - 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 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( vector{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 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 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 CHC::initialStateVariables() vector CHC::stateVariablesAtIndex(int _index) { solAssert(m_currentContract, ""); - vector 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 CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract) { - vector 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 CHC::currentStateVariables() { solAssert(m_currentContract, ""); - vector 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 CHC::currentFunctionVariables() @@ -886,9 +868,7 @@ vector CHC::currentFunctionVariables() initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0)); mutableInputExprs.push_back(m_context.variable(*var)->currentValue()); } - vector 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{m_error.currentValue()} + initialStateVariables() + initInputExprs + @@ -899,11 +879,10 @@ vector CHC::currentFunctionVariables() vector CHC::currentBlockVariables() { - vector 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); diff --git a/libsolidity/formal/CHC.h b/libsolidity/formal/CHC.h index 4f601a2d5..7fc752451 100644 --- a/libsolidity/formal/CHC.h +++ b/libsolidity/formal/CHC.h @@ -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. diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 9aecc2586..067786061 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -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 = std::dynamic_pointer_cast(_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(_sort); return m_context.mkArrayType(cvc4Sort(*arraySort.domain), cvc4Sort(*arraySort.range)); } + case Kind::Tuple: + { + auto const& tupleSort = dynamic_cast(_sort); + return m_context.mkTupleType(cvc4Sort(tupleSort.components)); + } default: break; } diff --git a/libsolidity/formal/EncodingContext.cpp b/libsolidity/formal/EncodingContext.cpp index 7ba51d657..3e0e4fa7e 100644 --- a/libsolidity/formal/EncodingContext.cpp +++ b/libsolidity/formal/EncodingContext.cpp @@ -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() diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index f02d32aae..820af022e 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -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(m_context.expression(*init)); solAssert(symbTuple, ""); - auto const& components = symbTuple->components(); + auto const& symbComponents = symbTuple->components(); + + auto tupleType = dynamic_cast(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 rightArguments; - if (_assignment.rightHandSide().annotation().type->category() == Type::Category::Tuple) + if (auto const* tupleTypeRight = dynamic_cast(_assignment.rightHandSide().annotation().type)) { auto symbTupleLeft = dynamic_pointer_cast(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(_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(&_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(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(_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) diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index 74529b4c5..76527baeb 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include using namespace std; using namespace solidity; @@ -37,10 +39,10 @@ using namespace solidity::frontend::smt; SMTLib2Interface::SMTLib2Interface( map 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(_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(_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"); } diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index 17d385cb2..5310f7e95 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -39,7 +39,7 @@ class SMTLib2Interface: public SolverInterface, public boost::noncopyable public: explicit SMTLib2Interface( std::map const& _queryResponses, - ReadCallback::Callback const& _smtCallback + ReadCallback::Callback _smtCallback ); void reset() override; @@ -74,6 +74,7 @@ private: std::vector m_accumulatedOutput; std::map m_variables; + std::set m_userSorts; std::map const& m_queryResponses; std::vector m_unhandledQueries; diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 106ada820..cac412c4b 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -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 = std::dynamic_pointer_cast(_tuple.sort); + solAssert(tupleSort, ""); + solAssert(_index < tupleSort->components.size(), ""); + return Expression( + "tuple_get", + std::vector{std::move(_tuple), Expression(_index)}, + tupleSort->components.at(_index) + ); + } + friend Expression operator!(Expression _a) { return Expression("not", std::move(_a), Kind::Bool); diff --git a/libsolidity/formal/Sorts.h b/libsolidity/formal/Sorts.h index d26644462..2acd46975 100644 --- a/libsolidity/formal/Sorts.h +++ b/libsolidity/formal/Sorts.h @@ -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 _members, + std::vector _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(&_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 const members; + std::vector const components; +}; + /** Frequently used sorts.*/ struct SortProvider { diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 1d9655a9c..0364a0458 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -75,6 +75,21 @@ SortPointer smtSort(frontend::Type const& _type) return make_shared(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType())); } } + case Kind::Tuple: + { + auto tupleType = dynamic_cast(&_type); + solAssert(tupleType, ""); + vector 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( + 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 smtSortAbstractFunction(vector const& _types) +{ + vector 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 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(_from)) + if (_to->category() == frontend::Type::Category::FixedBytes) + return smt::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add))); + + return std::nullopt; +} + } diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 55c88811d..a05d606fe 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -31,6 +31,7 @@ std::vector smtSort(std::vector const& _type /// If _type has type Function, abstract it to Integer. /// Otherwise return smtSort(_type). SortPointer smtSortAbstractFunction(frontend::Type const& _type); +std::vector smtSortAbstractFunction(std::vector 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 symbolicTypeConversion(TypePointer _from, TypePointer _to); } diff --git a/libsolidity/formal/SymbolicVariables.cpp b/libsolidity/formal/SymbolicVariables.cpp index 549fc05e9..35cd4f9c2 100644 --- a/libsolidity/formal/SymbolicVariables.cpp +++ b/libsolidity/formal/SymbolicVariables.cpp @@ -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(m_originalType)) - if (_targetType->category() == frontend::Type::Category::FixedBytes) - return smt::Expression(u256(toHex(util::asBytes(strType->value()), util::HexPrefix::Add))); - } + optional 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(*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 const& SymbolicTupleVariable::components() +{ + auto tupleSort = dynamic_pointer_cast(m_sort); + solAssert(tupleSort, ""); + return tupleSort->components; +} + +smt::Expression SymbolicTupleVariable::component( + size_t _index, + TypePointer _fromType, + TypePointer _toType +) +{ + optional conversion = symbolicTypeConversion(_fromType, _toType); + if (conversion) + return *conversion; + + return smt::Expression::tuple_get(currentValue(), _index); } diff --git a/libsolidity/formal/SymbolicVariables.h b/libsolidity/formal/SymbolicVariables.h index e1c28a8b5..6f7ad6ec4 100644 --- a/libsolidity/formal/SymbolicVariables.h +++ b/libsolidity/formal/SymbolicVariables.h @@ -249,14 +249,18 @@ public: std::string _uniqueName, EncodingContext& _context ); + SymbolicTupleVariable( + SortPointer _sort, + std::string _uniqueName, + EncodingContext& _context + ); - std::vector> const& components() - { - return m_components; - } - -private: - std::vector> m_components; + std::vector const& components(); + Expression component( + size_t _index, + TypePointer _fromType = nullptr, + TypePointer _toType = nullptr + ); }; } diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp index 62e60e3f7..1871c0908 100644 --- a/libsolidity/formal/VariableUsage.cpp +++ b/libsolidity/formal/VariableUsage.cpp @@ -40,15 +40,15 @@ set 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(SMTEncoder::leftmostBase(_indexAccess)); if (identifier) diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 20150c337..f175e45d2 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -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(_sort); return m_context.array_sort(z3Sort(*arraySort.domain), z3Sort(*arraySort.range)); } + case Kind::Tuple: + { + auto const& tupleSort = dynamic_cast(_sort); + vector 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 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; } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index fe0268fb1..17348c242 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,7 @@ #include #include +#include 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 diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index b0ea0603f..8bc8828c6 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -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(); diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index a97114e83..1b35040aa 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -200,7 +200,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil /// for Contract-level targets try both contract name and wildcard vector 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 StandardCompile )); else { - ret.sources[sourceName] = result.responseOrErrorMessage; + ret.sources[sourceName] = result.responseOrErrorMessage; found = true; break; } diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index b37025576..885417d8b 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -24,8 +24,9 @@ #include -#include #include +#include +#include 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)) { } diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 2f23c8c22..befd3c696 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -695,7 +695,7 @@ ASTPointer Parser::parseVariableDeclaration( ); bool isIndexed = false; - VariableDeclaration::Constantness constantness = VariableDeclaration::Constantness::Mutable; + VariableDeclaration::Mutability mutability = VariableDeclaration::Mutability::Mutable; ASTPointer overrides = nullptr; Visibility visibility(Visibility::Default); VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified; @@ -732,15 +732,15 @@ ASTPointer 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 Parser::parseVariableDeclaration( visibility, _options.isStateVariable, isIndexed, - constantness, + mutability, overrides, location ); diff --git a/libsolutil/Algorithms.h b/libsolutil/Algorithms.h index b9028f19b..3897d65d2 100644 --- a/libsolutil/Algorithms.h +++ b/libsolutil/Algorithms.h @@ -114,6 +114,10 @@ struct BreadthFirstSearch } return *this; } + void abort() + { + verticesToTraverse.clear(); + } std::set verticesToTraverse; std::set visited{}; diff --git a/libsolutil/Common.h b/libsolutil/Common.h index 35bd37fe8..f2d9c1d84 100644 --- a/libsolutil/Common.h +++ b/libsolutil/Common.h @@ -47,6 +47,7 @@ #include #include +#include #include #include #include @@ -120,7 +121,7 @@ inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes) class ScopeGuard { public: - explicit ScopeGuard(std::function _f): m_f(_f) {} + explicit ScopeGuard(std::function _f): m_f(std::move(_f)) {} ~ScopeGuard() { m_f(); } private: diff --git a/libsolutil/CommonData.h b/libsolutil/CommonData.h index f969ad212..bd1133511 100644 --- a/libsolutil/CommonData.h +++ b/libsolutil/CommonData.h @@ -34,11 +34,12 @@ #include #include #include +#include /// Operators need to stay in the global namespace. /// Concatenate the contents of a container onto a vector -template std::vector& operator+=(std::vector& _a, U const& _b) +template std::vector& operator+=(std::vector& _a, U& _b) { for (auto const& i: _b) _a.push_back(i); @@ -51,7 +52,7 @@ template std::vector& operator+=(std::vector& _a, U&& _ return _a; } /// Concatenate the contents of a container onto a multiset -template std::multiset& operator+=(std::multiset& _a, U const& _b) +template std::multiset& operator+=(std::multiset& _a, U& _b) { _a.insert(_b.begin(), _b.end()); return _a; @@ -64,7 +65,7 @@ template std::multiset& operator+=(std::multiset std::set& operator+=(std::set& _a, U const& _b) +template std::set& operator+=(std::set& _a, U& _b) { _a.insert(_b.begin(), _b.end()); return _a; @@ -141,6 +142,36 @@ inline std::multiset& operator-=(std::multiset& _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())) +>>> +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 has a similar function `accumulate` which +/// until C++20 does *not* std::move the partial accumulated. +template +auto fold(C const& _c, T _acc, Callable&& _binaryOp) +{ + for (auto const& e: _c) + _acc = _binaryOp(std::move(_acc), e); + return _acc; +} + template T convertContainer(U const& _from) { diff --git a/libsolutil/IpfsHash.cpp b/libsolutil/IpfsHash.cpp index 95605cdc1..1f9435748 100644 --- a/libsolutil/IpfsHash.cpp +++ b/libsolutil/IpfsHash.cpp @@ -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)) diff --git a/libsolutil/Result.h b/libsolutil/Result.h index a8150a825..a2c471097 100644 --- a/libsolutil/Result.h +++ b/libsolutil/Result.h @@ -36,7 +36,7 @@ namespace solidity::util /// template -class Result +class [[nodiscard]] Result { public: /// Constructs a result with _value and an empty message. diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 91f818100..ca3f88c75 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -255,14 +255,14 @@ vector AsmAnalyzer::operator()(FunctionCall const& _funCall) yulAssert(!_funCall.functionName.name.empty(), ""); vector const* parameterTypes = nullptr; vector const* returnTypes = nullptr; - bool needsLiteralArguments = false; + vector 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 AsmAnalyzer::operator()(FunctionCall const& _funCall) ); vector 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(arg)) typeError( diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 853f9f654..09ca73ee2 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -34,6 +34,7 @@ #include #include #include +#include namespace solidity::langutil { @@ -58,14 +59,14 @@ public: AsmAnalysisInfo& _analysisInfo, langutil::ErrorReporter& _errorReporter, Dialect const& _dialect, - ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver(), - std::set const& _dataNames = {} + ExternalIdentifierAccess::Resolver _resolver = ExternalIdentifierAccess::Resolver(), + std::set _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(&m_dialect)) m_evmVersion = evmDialect->evmVersion(); diff --git a/libyul/Dialect.h b/libyul/Dialect.h index a2a596177..870205833 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -28,6 +28,7 @@ #include #include +#include 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> literalArguments; }; struct Dialect: boost::noncopyable diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index cd24db738..6ca55821d 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -29,6 +29,7 @@ #include +#include #include 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 ): @@ -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) { diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index eae870815..6447aa7b1 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -152,7 +152,7 @@ protected: EVMDialect const& _dialect, BuiltinContext& _builtinContext, bool _evm15, - ExternalIdentifierAccess const& _identifierAccess, + ExternalIdentifierAccess _identifierAccess, bool _useNamedLabelsForFunctions, std::shared_ptr _context ); diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index ea6586323..52cd1d23f 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -55,7 +55,7 @@ pair 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 createFunction( size_t _params, size_t _returns, SideEffects _sideEffects, - bool _literalArguments, + vector _literalArguments, std::function)> _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 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 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 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&, diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 2141ab98f..00852fe34 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -45,7 +45,7 @@ struct BuiltinContext std::map subIDs; }; -struct BuiltinFunctionForEVM: BuiltinFunction +struct BuiltinFunctionForEVM: public BuiltinFunction { std::optional instruction; /// Function to generate code for the given function call and append it to the abstract diff --git a/libyul/backends/wasm/WasmCodeTransform.cpp b/libyul/backends/wasm/WasmCodeTransform.cpp index f3ff04cd9..cb1294d44 100644 --- a/libyul/backends/wasm/WasmCodeTransform.cpp +++ b/libyul/backends/wasm/WasmCodeTransform.cpp @@ -136,11 +136,15 @@ wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call) } typeConversionNeeded = true; } - else if (builtin->literalArguments) + else if (builtin->literalArguments && contains(builtin->literalArguments.value(), true)) { vector literals; - for (auto const& arg: _call.arguments) - literals.emplace_back(wasm::StringLiteral{std::get(arg).value.str()}); + for (size_t i = 0; i < _call.arguments.size(); i++) + if (builtin->literalArguments.value()[i]) + literals.emplace_back(wasm::StringLiteral{std::get(_call.arguments[i]).value.str()}); + else + literals.emplace_back(visitReturnByValue(_call.arguments[i])); + return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)}; } else diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index c9817af76..c9dfe05c7 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -102,8 +102,8 @@ WasmDialect::WasmDialect() m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true; m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true; - addFunction("datasize", {i64}, {i64}, true, true); - addFunction("dataoffset", {i64}, {i64}, true, true); + addFunction("datasize", {i64}, {i64}, true, {true}); + addFunction("dataoffset", {i64}, {i64}, true, {true}); addEthereumExternals(); } @@ -204,7 +204,7 @@ void WasmDialect::addEthereumExternals() f.controlFlowSideEffects = ext.controlFlowSideEffects; f.isMSize = false; f.sideEffects.invalidatesStorage = (ext.name == "storageStore"); - f.literalArguments = false; + f.literalArguments.reset(); } } @@ -213,7 +213,7 @@ void WasmDialect::addFunction( vector _params, vector _returns, bool _movable, - bool _literalArguments + std::vector _literalArguments ) { YulString name{move(_name)}; @@ -224,5 +224,8 @@ void WasmDialect::addFunction( f.returns = std::move(_returns); f.sideEffects = _movable ? SideEffects{} : SideEffects::worst(); f.isMSize = false; - f.literalArguments = _literalArguments; + if (!_literalArguments.empty()) + f.literalArguments = std::move(_literalArguments); + else + f.literalArguments.reset(); } diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 1de65dfdc..e5f3c9fc4 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -61,7 +61,7 @@ private: std::vector _params, std::vector _returns, bool _movable = true, - bool _literalArguments = false + std::vector _literalArguments = std::vector{} ); std::map m_functions; diff --git a/libyul/backends/wasm/WordSizeTransform.cpp b/libyul/backends/wasm/WordSizeTransform.cpp index eec04fae6..edcc08923 100644 --- a/libyul/backends/wasm/WordSizeTransform.cpp +++ b/libyul/backends/wasm/WordSizeTransform.cpp @@ -41,15 +41,24 @@ void WordSizeTransform::operator()(FunctionDefinition& _fd) void WordSizeTransform::operator()(FunctionCall& _fc) { + vector const* literalArguments = nullptr; + if (BuiltinFunction const* fun = m_inputDialect.builtin(_fc.functionName.name)) if (fun->literalArguments) + literalArguments = &fun->literalArguments.value(); + + vector newArgs; + + for (size_t i = 0; i < _fc.arguments.size(); i++) + if (!literalArguments || !(*literalArguments)[i]) + newArgs += expandValueToVector(_fc.arguments[i]); + else { - for (Expression& arg: _fc.arguments) - get(arg).type = m_targetDialect.defaultType; - return; + get(_fc.arguments[i]).type = m_targetDialect.defaultType; + newArgs.emplace_back(std::move(_fc.arguments[i])); } - rewriteFunctionCallArguments(_fc.arguments); + _fc.arguments = std::move(newArgs); } void WordSizeTransform::operator()(If& _if) @@ -97,14 +106,14 @@ void WordSizeTransform::operator()(Block& _block) // Special handling for datasize and dataoffset - they will only need one variable. if (BuiltinFunction const* f = m_inputDialect.builtin(std::get(*varDecl.value).functionName.name)) - if (f->literalArguments) + if (f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring) { - yulAssert(f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring, ""); + yulAssert(f->literalArguments && f->literalArguments.value()[0], ""); yulAssert(varDecl.variables.size() == 1, ""); auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name); vector ret; for (int i = 0; i < 3; i++) - ret.push_back(VariableDeclaration{ + ret.emplace_back(VariableDeclaration{ varDecl.location, {TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}}, make_unique(Literal{ @@ -114,7 +123,7 @@ void WordSizeTransform::operator()(Block& _block) m_targetDialect.defaultType }) }); - ret.push_back(VariableDeclaration{ + ret.emplace_back(VariableDeclaration{ varDecl.location, {TypedName{varDecl.location, newLhs[3], m_targetDialect.defaultType}}, std::move(varDecl.value) @@ -135,8 +144,7 @@ void WordSizeTransform::operator()(Block& _block) auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name); vector ret; for (int i = 0; i < 4; i++) - ret.push_back( - VariableDeclaration{ + ret.emplace_back(VariableDeclaration{ varDecl.location, {TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}}, std::move(newRhs[i]) @@ -158,14 +166,14 @@ void WordSizeTransform::operator()(Block& _block) // Special handling for datasize and dataoffset - they will only need one variable. if (BuiltinFunction const* f = m_inputDialect.builtin(std::get(*assignment.value).functionName.name)) - if (f->literalArguments) + if (f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring) { - yulAssert(f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring, ""); + yulAssert(f->literalArguments && f->literalArguments.value()[0], ""); yulAssert(assignment.variableNames.size() == 1, ""); auto newLhs = generateU64IdentifierNames(assignment.variableNames[0].name); vector ret; for (int i = 0; i < 3; i++) - ret.push_back(Assignment{ + ret.emplace_back(Assignment{ assignment.location, {Identifier{assignment.location, newLhs[i]}}, make_unique(Literal{ @@ -175,7 +183,7 @@ void WordSizeTransform::operator()(Block& _block) m_targetDialect.defaultType }) }); - ret.push_back(Assignment{ + ret.emplace_back(Assignment{ assignment.location, {Identifier{assignment.location, newLhs[3]}}, std::move(assignment.value) @@ -196,8 +204,7 @@ void WordSizeTransform::operator()(Block& _block) YulString lhsName = assignment.variableNames[0].name; vector ret; for (int i = 0; i < 4; i++) - ret.push_back( - Assignment{ + ret.emplace_back(Assignment{ assignment.location, {Identifier{assignment.location, m_variableMapping.at(lhsName)[i]}}, std::move(newRhs[i]) @@ -268,17 +275,6 @@ void WordSizeTransform::rewriteIdentifierList(vector& _ids) ); } -void WordSizeTransform::rewriteFunctionCallArguments(vector& _args) -{ - iterateReplacing( - _args, - [&](Expression& _e) -> std::optional> - { - return expandValueToVector(_e); - } - ); -} - vector WordSizeTransform::handleSwitchInternal( langutil::SourceLocation const& _location, vector const& _splitExpressions, diff --git a/libyul/backends/wasm/WordSizeTransform.h b/libyul/backends/wasm/WordSizeTransform.h index 67dfe8864..5771afea8 100644 --- a/libyul/backends/wasm/WordSizeTransform.h +++ b/libyul/backends/wasm/WordSizeTransform.h @@ -83,7 +83,6 @@ private: void rewriteVarDeclList(std::vector&); void rewriteIdentifierList(std::vector&); - void rewriteFunctionCallArguments(std::vector&); std::vector handleSwitch(Switch& _switch); std::vector handleSwitchInternal( diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp index 84074ad27..3b0b6bec1 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -58,12 +58,21 @@ void CommonSubexpressionEliminator::visit(Expression& _e) // If this is a function call to a function that requires literal arguments, // do not try to simplify there. if (holds_alternative(_e)) - if (BuiltinFunction const* builtin = m_dialect.builtin(std::get(_e).functionName.name)) - if (builtin->literalArguments) + { + FunctionCall& funCall = std::get(_e); + + if (BuiltinFunction const* builtin = m_dialect.builtin(funCall.functionName.name)) + { + for (size_t i = funCall.arguments.size(); i > 0; i--) // We should not modify function arguments that have to be literals // Note that replacing the function call entirely is fine, // if the function call is movable. - descend = false; + if (!builtin->literalArguments || !builtin->literalArguments.value()[i - 1]) + visit(funCall.arguments[i - 1]); + + descend = false; + } + } // We visit the inner expression first to first simplify inner expressions, // which hopefully allows more matches. diff --git a/libyul/optimiser/ExpressionSplitter.cpp b/libyul/optimiser/ExpressionSplitter.cpp index 7b2ac14f6..8144de49a 100644 --- a/libyul/optimiser/ExpressionSplitter.cpp +++ b/libyul/optimiser/ExpressionSplitter.cpp @@ -47,13 +47,15 @@ void ExpressionSplitter::run(OptimiserStepContext& _context, Block& _ast) void ExpressionSplitter::operator()(FunctionCall& _funCall) { + vector const* literalArgs = nullptr; + if (BuiltinFunction const* builtin = m_dialect.builtin(_funCall.functionName.name)) if (builtin->literalArguments) - // We cannot outline function arguments that have to be literals - return; + literalArgs = &builtin->literalArguments.value(); - for (auto& arg: _funCall.arguments | boost::adaptors::reversed) - outlineExpression(arg); + for (size_t i = _funCall.arguments.size(); i > 0; i--) + if (!literalArgs || !(*literalArgs)[i - 1]) + outlineExpression(_funCall.arguments[i - 1]); } void ExpressionSplitter::operator()(If& _if) diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index 81c608de8..1c1ad0d95 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -31,6 +31,7 @@ #include #include +#include namespace solidity::yul { @@ -147,10 +148,10 @@ class BodyCopier: public ASTCopier public: BodyCopier( NameDispenser& _nameDispenser, - std::map const& _variableReplacements + std::map _variableReplacements ): m_nameDispenser(_nameDispenser), - m_variableReplacements(_variableReplacements) + m_variableReplacements(std::move(_variableReplacements)) {} using ASTCopier::operator (); diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index a2fec4cc4..d1c6a6dd4 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -67,6 +67,8 @@ #include +#include + using namespace std; using namespace solidity; using namespace solidity::yul; @@ -91,206 +93,30 @@ void OptimiserSuite::run( OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast); - suite.runSequence({ - VarDeclInitializer::name, - FunctionHoister::name, - BlockFlattener::name, - ForLoopInitRewriter::name, - DeadCodeEliminator::name, - FunctionGrouper::name, - EquivalentFunctionCombiner::name, - UnusedPruner::name, - CircularReferencesPruner::name, - BlockFlattener::name, - ControlFlowSimplifier::name, - LiteralRematerialiser::name, - ConditionalUnsimplifier::name, - StructuralSimplifier::name, - ControlFlowSimplifier::name, - ForLoopConditionIntoBody::name, - BlockFlattener::name - }, ast); + suite.runSequence( + "dhfoDgvulfnTUtnIf" // None of these can make stack problems worse + "(" + "xarrscLM" // Turn into SSA and simplify + "cCTUtTOntnfDIul" // Perform structural simplification + "Lcul" // Simplify again + "Vcul jj" // Reverse SSA - // None of the above can make stack problems worse. + // should have good "compilability" property here. - size_t codeSize = 0; - for (size_t rounds = 0; rounds < 12; ++rounds) - { - { - size_t newSize = CodeSize::codeSizeIncludingFunctions(ast); - if (newSize == codeSize) - break; - codeSize = newSize; - } - - { - // Turn into SSA and simplify - suite.runSequence({ - ExpressionSplitter::name, - SSATransform::name, - RedundantAssignEliminator::name, - RedundantAssignEliminator::name, - ExpressionSimplifier::name, - CommonSubexpressionEliminator::name, - LoadResolver::name, - LoopInvariantCodeMotion::name - }, ast); - } - - { - // perform structural simplification - suite.runSequence({ - CommonSubexpressionEliminator::name, - ConditionalSimplifier::name, - LiteralRematerialiser::name, - ConditionalUnsimplifier::name, - StructuralSimplifier::name, - LiteralRematerialiser::name, - ForLoopConditionOutOfBody::name, - ControlFlowSimplifier::name, - StructuralSimplifier::name, - ControlFlowSimplifier::name, - BlockFlattener::name, - DeadCodeEliminator::name, - ForLoopConditionIntoBody::name, - UnusedPruner::name, - CircularReferencesPruner::name - }, ast); - } - - { - // simplify again - suite.runSequence({ - LoadResolver::name, - CommonSubexpressionEliminator::name, - UnusedPruner::name, - CircularReferencesPruner::name, - }, ast); - } - - { - // reverse SSA - suite.runSequence({ - SSAReverser::name, - CommonSubexpressionEliminator::name, - UnusedPruner::name, - CircularReferencesPruner::name, - - ExpressionJoiner::name, - ExpressionJoiner::name, - }, ast); - } - - // should have good "compilability" property here. - - { - // run functional expression inliner - suite.runSequence({ - ExpressionInliner::name, - UnusedPruner::name, - CircularReferencesPruner::name, - }, ast); - } - - { - // Prune a bit more in SSA - suite.runSequence({ - ExpressionSplitter::name, - SSATransform::name, - RedundantAssignEliminator::name, - UnusedPruner::name, - CircularReferencesPruner::name, - RedundantAssignEliminator::name, - UnusedPruner::name, - CircularReferencesPruner::name, - }, ast); - } - - { - // Turn into SSA again and simplify - suite.runSequence({ - ExpressionSplitter::name, - SSATransform::name, - RedundantAssignEliminator::name, - RedundantAssignEliminator::name, - CommonSubexpressionEliminator::name, - LoadResolver::name, - }, ast); - } - - { - // run full inliner - suite.runSequence({ - FunctionGrouper::name, - EquivalentFunctionCombiner::name, - FullInliner::name, - BlockFlattener::name - }, ast); - } - - { - // SSA plus simplify - suite.runSequence({ - ConditionalSimplifier::name, - LiteralRematerialiser::name, - ConditionalUnsimplifier::name, - CommonSubexpressionEliminator::name, - SSATransform::name, - RedundantAssignEliminator::name, - RedundantAssignEliminator::name, - LoadResolver::name, - ExpressionSimplifier::name, - LiteralRematerialiser::name, - ForLoopConditionOutOfBody::name, - StructuralSimplifier::name, - BlockFlattener::name, - DeadCodeEliminator::name, - ControlFlowSimplifier::name, - CommonSubexpressionEliminator::name, - SSATransform::name, - RedundantAssignEliminator::name, - RedundantAssignEliminator::name, - ForLoopConditionIntoBody::name, - UnusedPruner::name, - CircularReferencesPruner::name, - CommonSubexpressionEliminator::name, - }, ast); - } - } - - // Make source short and pretty. - - suite.runSequence({ - ExpressionJoiner::name, - Rematerialiser::name, - UnusedPruner::name, - CircularReferencesPruner::name, - ExpressionJoiner::name, - UnusedPruner::name, - CircularReferencesPruner::name, - ExpressionJoiner::name, - UnusedPruner::name, - CircularReferencesPruner::name, - - SSAReverser::name, - CommonSubexpressionEliminator::name, - LiteralRematerialiser::name, - ForLoopConditionOutOfBody::name, - CommonSubexpressionEliminator::name, - UnusedPruner::name, - CircularReferencesPruner::name, - - ExpressionJoiner::name, - Rematerialiser::name, - UnusedPruner::name, - CircularReferencesPruner::name, - }, ast); + "eul" // Run functional expression inliner + "xarulrul" // Prune a bit more in SSA + "xarrcL" // Turn into SSA again and simplify + "gvif" // Run full inliner + "CTUcarrLsTOtfDncarrIulc" // SSA plus simplify + ")" + "jmuljuljul VcTOcul jmul", // Make source short and pretty + ast + ); // This is a tuning parameter, but actually just prevents infinite loops. size_t stackCompressorMaxIterations = 16; - suite.runSequence({ - FunctionGrouper::name - }, ast); + suite.runSequence("g", ast); + // We ignore the return value because we will get a much better error // message once we perform code generation. StackCompressor::run( @@ -299,16 +125,7 @@ void OptimiserSuite::run( _optimizeStackAllocation, stackCompressorMaxIterations ); - suite.runSequence({ - BlockFlattener::name, - DeadCodeEliminator::name, - ControlFlowSimplifier::name, - LiteralRematerialiser::name, - ForLoopConditionOutOfBody::name, - CommonSubexpressionEliminator::name, - - FunctionGrouper::name, - }, ast); + suite.runSequence("fDnTOc g", ast); if (EVMDialect const* dialect = dynamic_cast(&_dialect)) { @@ -429,6 +246,59 @@ map const& OptimiserSuite::stepAbbreviationToNameMap() return lookupTable; } +void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) +{ + string input = _stepAbbreviations; + boost::remove_erase(input, ' '); + boost::remove_erase(input, '\n'); + + bool insideLoop = false; + for (char abbreviation: input) + switch (abbreviation) + { + case '(': + assertThrow(!insideLoop, OptimizerException, "Nested parentheses not supported"); + insideLoop = true; + break; + case ')': + assertThrow(insideLoop, OptimizerException, "Unbalanced parenthesis"); + insideLoop = false; + break; + default: + assertThrow( + stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(), + OptimizerException, + "Invalid optimisation step abbreviation" + ); + } + assertThrow(!insideLoop, OptimizerException, "Unbalanced parenthesis"); + + auto abbreviationsToSteps = [](string const& _sequence) -> vector + { + vector steps; + for (char abbreviation: _sequence) + steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation)); + return steps; + }; + + // The sequence has now been validated and must consist of pairs of segments that look like this: `aaa(bbb)` + // `aaa` or `(bbb)` can be empty. For example we consider a sequence like `fgo(aaf)Oo` to have + // four segments, the last of which is an empty parenthesis. + size_t currentPairStart = 0; + while (currentPairStart < input.size()) + { + size_t openingParenthesis = input.find('(', currentPairStart); + size_t closingParenthesis = input.find(')', openingParenthesis); + size_t firstCharInside = (openingParenthesis == string::npos ? input.size() : openingParenthesis + 1); + yulAssert((openingParenthesis == string::npos) == (closingParenthesis == string::npos), ""); + + runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingParenthesis - currentPairStart)), _ast); + runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingParenthesis - firstCharInside)), _ast); + + currentPairStart = (closingParenthesis == string::npos ? input.size() : closingParenthesis + 1); + } +} + void OptimiserSuite::runSequence(std::vector const& _steps, Block& _ast) { unique_ptr copy; @@ -453,3 +323,21 @@ void OptimiserSuite::runSequence(std::vector const& _steps, Block& _ast) } } } + +void OptimiserSuite::runSequenceUntilStable( + std::vector const& _steps, + Block& _ast, + size_t maxRounds +) +{ + size_t codeSize = 0; + for (size_t rounds = 0; rounds < maxRounds; ++rounds) + { + size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast); + if (newSize == codeSize) + break; + codeSize = newSize; + + runSequence(_steps, _ast); + } +} diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 6ce6b485c..3b0ed0163 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -45,6 +45,8 @@ struct Object; class OptimiserSuite { public: + static constexpr size_t MaxRounds = 12; + enum class Debug { None, @@ -60,6 +62,12 @@ public: ); void runSequence(std::vector const& _steps, Block& _ast); + void runSequence(std::string const& _stepAbbreviations, Block& _ast); + void runSequenceUntilStable( + std::vector const& _steps, + Block& _ast, + size_t maxRounds = MaxRounds + ); static std::map> const& allSteps(); static std::map const& stepNameToAbbreviationMap(); diff --git a/scripts/ASTImportTest.sh b/scripts/ASTImportTest.sh index b3a1b6724..6f8d8b06e 100755 --- a/scripts/ASTImportTest.sh +++ b/scripts/ASTImportTest.sh @@ -5,8 +5,8 @@ # and exporting it again. The second JSON should be identical to the first REPO_ROOT=$(readlink -f "$(dirname "$0")"/..) -SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} -SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-${REPO_ROOT}/build} +SOLC=${SOLIDITY_BUILD_DIR}/solc/solc SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests" @@ -21,8 +21,8 @@ UNCOMPILABLE=0 TESTED=0 if [ $(ls | wc -l) -ne 0 ]; then - echo "Test directory not empty. Skipping!" - exit -1 + echo "Test directory not empty. Skipping!" + exit -1 fi # function tests whether exporting and importing again leaves the JSON ast unchanged @@ -40,11 +40,11 @@ function testImportExportEquivalence { $SOLC --import-ast --combined-json ast,compact-format --pretty-json expected.json > obtained.json 2> /dev/null if [ $? -ne 0 ] then - # For investigating, use exit 1 here so the script stops at the - # first failing test - # exit 1 - FAILED=$((FAILED + 1)) - return 1 + # For investigating, use exit 1 here so the script stops at the + # first failing test + # exit 1 + FAILED=$((FAILED + 1)) + return 1 fi DIFF="$(diff expected.json obtained.json)" if [ "$DIFF" != "" ] diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index b457b741d..cf6cbdd79 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -29,9 +29,9 @@ set -e if test -z "$1"; then - BUILD_DIR="emscripten_build" + BUILD_DIR="emscripten_build" else - BUILD_DIR="$1" + BUILD_DIR="$1" fi docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.39.3-64bit \ diff --git a/scripts/docs_version_pragma_check.sh b/scripts/docs_version_pragma_check.sh index c56bdc6c9..520be3e5e 100755 --- a/scripts/docs_version_pragma_check.sh +++ b/scripts/docs_version_pragma_check.sh @@ -28,7 +28,7 @@ set -e ## GLOBAL VARIABLES REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) -SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-${REPO_ROOT}/build} source "${REPO_ROOT}/scripts/common.sh" source "${REPO_ROOT}/scripts/common_cmdline.sh" @@ -184,4 +184,4 @@ SOLTMPDIR=$(mktemp -d) done ) rm -rf "$SOLTMPDIR" -echo "Done." \ No newline at end of file +echo "Done." diff --git a/scripts/install_deps.cmake b/scripts/install_deps.cmake index 0cb0ed621..d90d4ec32 100644 --- a/scripts/install_deps.cmake +++ b/scripts/install_deps.cmake @@ -43,7 +43,7 @@ function(download_and_unpack PACKAGE_URL DST_DIR) if (STATUS) message("Unpacking ${FILE_NAME} to ${DST_DIR}") execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${DST_FILE} - WORKING_DIRECTORY ${DST_DIR}) + WORKING_DIRECTORY ${DST_DIR}) endif() endfunction(download_and_unpack) @@ -59,8 +59,8 @@ function(create_package NAME DIR) set(PACKAGE_FILE "${PACKAGES_DIR}/${NAME}.tar.gz") execute_process(COMMAND ${CMAKE_COMMAND} -E - tar -czf ${PACKAGE_FILE} ${TOP_FILES} - WORKING_DIRECTORY ${DIR}) + tar -czf ${PACKAGE_FILE} ${TOP_FILES} + WORKING_DIRECTORY ${DIR}) endfunction(create_package) # Downloads the source code of the package and unpacks it to dedicated 'src' diff --git a/scripts/isolate_tests.py b/scripts/isolate_tests.py index 3a41d54c9..9dea81aa5 100755 --- a/scripts/isolate_tests.py +++ b/scripts/isolate_tests.py @@ -20,17 +20,17 @@ def extract_test_cases(path): tests = [] for l in lines: - if inside: - if l.strip().endswith(')' + delimiter + '";'): - inside = False + if inside: + if l.strip().endswith(')' + delimiter + '";'): + inside = False + else: + tests[-1] += l + '\n' else: - tests[-1] += l + '\n' - else: - m = re.search(r'R"([^(]*)\($', l.strip()) - if m: - inside = True - delimiter = m.group(1) - tests += [''] + m = re.search(r'R"([^(]*)\($', l.strip()) + if m: + inside = True + delimiter = m.group(1) + tests += [''] return tests @@ -75,20 +75,20 @@ def write_cases(f, tests): open(sol_filename, mode='w', encoding='utf8').write(remainder) def extract_and_write(f, path): - if docs: - cases = extract_docs_cases(path) + if docs: + cases = extract_docs_cases(path) + else: + if f.endswith('.sol'): + cases = [open(path, mode='r', encoding='utf8').read()] else: - if f.endswith('.sol'): - cases = [open(path, mode='r', encoding='utf8').read()] - else: - cases = extract_test_cases(path) - write_cases(f, cases) + cases = extract_test_cases(path) + write_cases(f, cases) if __name__ == '__main__': path = sys.argv[1] docs = False if len(sys.argv) > 2 and sys.argv[2] == 'docs': - docs = True + docs = True if isfile(path): extract_and_write(path, path) diff --git a/scripts/release.bat b/scripts/release.bat index cfd0c838e..caa56fc9a 100644 --- a/scripts/release.bat +++ b/scripts/release.bat @@ -31,7 +31,7 @@ set VERSION=%2 set "DLLS=MSVC_DLLS_NOT_FOUND" FOR /d %%d IN ("C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Redist\MSVC\*" - "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\*") DO set "DLLS=%%d\x86\Microsoft.VC141.CRT\msvc*.dll" + "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Redist\MSVC\*") DO set "DLLS=%%d\x86\Microsoft.VC141.CRT\msvc*.dll" 7z a solidity-windows.zip ^ .\build\solc\%CONFIGURATION%\solc.exe .\build\test\%CONFIGURATION%\soltest.exe ^ diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 49634a977..d84b3fd36 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -86,16 +86,16 @@ else if [ $distribution = focal ] then SMTDEPENDENCY="libz3-dev, - libcvc4-dev, - " + libcvc4-dev, + " elif [ $distribution = disco ] then SMTDEPENDENCY="libz3-static-dev, - libcvc4-dev, - " + libcvc4-dev, + " else SMTDEPENDENCY="libz3-static-dev, - " + " fi CMAKE_OPTIONS="" fi diff --git a/scripts/report_errors.sh b/scripts/report_errors.sh index 55fc2e8c6..296feb84b 100755 --- a/scripts/report_errors.sh +++ b/scripts/report_errors.sh @@ -39,11 +39,33 @@ function post_error_to_github FORMATTED_ERROR_MSG=$(echo $ESCAPED_ERROR_MSG | sed 's/\$/\\n/g' | tr -d '\n') curl --request POST \ - --url $GITHUB_API_URL \ - --header 'accept: application/vnd.github.v3+json' \ - --header 'content-type: application/json' \ - -u stackenbotten:$GITHUB_ACCESS_TOKEN \ - --data "{\"body\": \"There was an error when running \`$CIRCLE_JOB\` for commit \`$CIRCLE_SHA1\`:\n\`\`\`\n$FORMATTED_ERROR_MSG\n\`\`\`\nPlease check that your changes are working as intended.\"}" + --url $GITHUB_API_URL \ + --header 'accept: application/vnd.github.v3+json' \ + --header 'content-type: application/json' \ + -u stackenbotten:$GITHUB_ACCESS_TOKEN \ + --data "{\"body\": \"There was an error when running \`$CIRCLE_JOB\` for commit \`$CIRCLE_SHA1\`:\n\`\`\`\n$FORMATTED_ERROR_MSG\n\`\`\`\nPlease check that your changes are working as intended.\"}" + + post_review_comment_to_github +} + +function post_review_comment_to_github +{ + GITHUB_API_URL="https://api.github.com/repos/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/pulls/$CIRCLE_PR_NUMBER/comments" + + sed -i 1d $ERROR_LOG + + while read line + do + ERROR_PATH=$(echo $line | grep -oE ".*\.cpp") + ERROR_LINE=$(echo $line | grep -oE "[0-9]*") + + curl --request POST \ + --url $GITHUB_API_URL \ + --header 'accept: application/vnd.github.v3+json, application/vnd.github.comfort-fade-preview+json' \ + --header 'content-type: application/json' \ + -u stackenbotten:$GITHUB_ACCESS_TOKEN \ + --data "{\"commit_id\": \"$CIRCLE_SHA1\", \"path\": \"$ERROR_PATH\", \"line\": $ERROR_LINE, \"side\": \"RIGHT\", \"body\": \"Coding style error\"}" + done < $ERROR_LOG } trap report_error_to_github EXIT diff --git a/scripts/soltest.sh b/scripts/soltest.sh index d1c41404f..bb4c48691 100755 --- a/scripts/soltest.sh +++ b/scripts/soltest.sh @@ -6,10 +6,10 @@ USE_DEBUGGER=0 DEBUGGER="gdb --args" BOOST_OPTIONS= SOLTEST_OPTIONS= -SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-${REPO_ROOT}/build} usage() { - echo 2>&1 " + echo 2>&1 " Usage: $0 [options] [soltest-options] Runs BOOST C++ unit test program, soltest. @@ -23,7 +23,7 @@ Options: Important environment variables: -SOLIDITY_BUILD_DIR: Sets directory under the repository root of where test/soltest should be found. +SOLIDITY_BUILD_DIR: Sets directory where test/soltest should be found. The default is \"${SOLIDITY_BUILD_DIR}\". " } @@ -64,4 +64,4 @@ if [ "$USE_DEBUGGER" -ne "0" ]; then DEBUG_PREFIX=${DEBUGGER} fi -exec ${DEBUG_PREFIX} ${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/test/soltest ${BOOST_OPTIONS} -- --testpath ${REPO_ROOT}/test ${SOLTEST_OPTIONS} +exec ${DEBUG_PREFIX} ${SOLIDITY_BUILD_DIR}/test/soltest ${BOOST_OPTIONS} -- --testpath ${REPO_ROOT}/test ${SOLTEST_OPTIONS} diff --git a/scripts/tests.sh b/scripts/tests.sh index 266c3184e..c0601a12d 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -29,7 +29,7 @@ set -e REPO_ROOT="$(dirname "$0")/.." -SOLIDITY_BUILD_DIR="${SOLIDITY_BUILD_DIR:-build}" +SOLIDITY_BUILD_DIR="${SOLIDITY_BUILD_DIR:-${REPO_ROOT}/build}" source "${REPO_ROOT}/scripts/common.sh" @@ -88,43 +88,43 @@ fi # and homestead / byzantium VM for optimize in "" "--optimize" do - for vm in $EVM_VERSIONS - do - FORCE_ABIV2_RUNS="no" - if [[ "$vm" == "istanbul" ]] - then - FORCE_ABIV2_RUNS="no yes" # run both in istanbul - fi - for abiv2 in $FORCE_ABIV2_RUNS + for vm in $EVM_VERSIONS do - force_abiv2_flag="" - if [[ "$abiv2" == "yes" ]] + FORCE_ABIV2_RUNS="no" + if [[ "$vm" == "istanbul" ]] then - force_abiv2_flag="--abiencoderv2 --optimize-yul" + FORCE_ABIV2_RUNS="no yes" # run both in istanbul fi - printTask "--> Running tests using "$optimize" --evm-version "$vm" $force_abiv2_flag..." + for abiv2 in $FORCE_ABIV2_RUNS + do + force_abiv2_flag="" + if [[ "$abiv2" == "yes" ]] + then + force_abiv2_flag="--abiencoderv2 --optimize-yul" + fi + printTask "--> Running tests using "$optimize" --evm-version "$vm" $force_abiv2_flag..." - log="" - if [ -n "$log_directory" ] - then - if [ -n "$optimize" ] - then - log=--logger=JUNIT,error,$log_directory/opt_$vm.xml $testargs - else - log=--logger=JUNIT,error,$log_directory/noopt_$vm.xml $testargs_no_opt - fi - fi + log="" + if [ -n "$log_directory" ] + then + if [ -n "$optimize" ] + then + log=--logger=JUNIT,error,$log_directory/opt_$vm.xml $testargs + else + log=--logger=JUNIT,error,$log_directory/noopt_$vm.xml $testargs_no_opt + fi + fi - set +e - "$REPO_ROOT"/${SOLIDITY_BUILD_DIR}/test/soltest --show-progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $force_abiv2_flag + set +e + "${SOLIDITY_BUILD_DIR}"/test/soltest --show-progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $force_abiv2_flag - if test "0" -ne "$?"; then - exit 1 - fi - set -e + if test "0" -ne "$?"; then + exit 1 + fi + set -e + done done - done done if [[ -n $CMDLINE_PID ]] && ! wait $CMDLINE_PID diff --git a/scripts/yul_coverage.sh b/scripts/yul_coverage.sh new file mode 100755 index 000000000..d49065f69 --- /dev/null +++ b/scripts/yul_coverage.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash + +#------------------------------------------------------------------------------ +# Bash script to determine the percantage of tests that are compilable via Yul. +# +# Usage: +# ./yul_coverage.sh [--no-stats] [--successful] [--internal-compiler-errors] +# [--unimplemented-feature-errors] [--other-errors] [--list-files] +# +# --no-stats will not print the stats to stdout +# --successful print output of successful test-case compilations to stdout +# --internal-compiler-errors print output of test-case compilations that resulted in +# internal compilation errors to stdout +# --unimplemented-feature-errors print output of test-case compilations that resulted in +# unimplemented feature errors to stdout +# --other-errors print output of test-case compilations that resulted in +# errors that where not internal compiler errors or unimplemented feature errors +# to stdout +# --list-files will not print the compiler output to stdout, it will just print the files +# e.g. ./yul_coverage.sh --successful --list-files will just return a list of +# files where it's compilation result was successful +# Environment Variables +# SOLC can be set to change the used compiler. +# +# ./yul_coverage.sh +# run the script without any parameters to execute the tests will return stats. +# +# SOLC= ./yul_coverage.sh +# To change the used compiler, just set the SOLC environment variable. +# +# The documentation for solidity is hosted at: +# +# https://solidity.readthedocs.org +# +# ------------------------------------------------------------------------------ +# 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 +# +# (c) 2020 solidity contributors. +#------------------------------------------------------------------------------ + +set -e + +ROOT_DIR="$(dirname "$0")"/.. + +for arg in "$@"; do + case "$arg" in + --no-stats) NO_STATS=1 ;; + --successful) SHOW_SUCCESSFUL=1 ;; + --internal-compiler-errors) SHOW_INTERNAL_COMPILER_ERRORS=1 ;; + --unimplemented-feature-errors) SHOW_UNIMPLEMENTED_FEATURE_ERRORS=1 ;; + --other-errors) SHOW_OTHER_ERRORS=1 ;; + --list-files) ONLY_LIST_FILES=1 ;; + *) + echo "Usage:" + echo " $(basename "${0}") [--no-stats] [--successful] [--internal-compiler-errors] [--unimplemented-feature-errors] [--other-errors] [--list-files]" + echo " --no-stats will not print the stats to stdout" + echo " --successful print output of successful test-case compilations to stdout" + echo " --internal-compiler-errors print output of test-case compilations that resulted in" + echo " internal compilation errors to stdout" + echo " --unimplemented-feature-errors print output of test-case compilations that resulted in" + echo " unimplemented feature errors to stdout" + echo " --other-errors print output of test-case compilations that resulted in" + echo " errors that where not internal compiler errors or unimplemented feature errors" + echo " to stdout" + echo " --list-files will not print the compiler output to stdout, it will just print the files" + echo " e.g. './yul_coverage.sh --successful --list-files' will just return a list of" + echo " files where it's compilation result was successful" + exit 0 + ;; + esac +done + +show_output_if() { + local VAR=${1} + if [ -n "${VAR}" ]; then + echo "${SOL_FILE}" + if [ -z "${ONLY_LIST_FILES}" ]; then + echo "${OUTPUT}" + echo "" + fi + fi +} + +FAILED=() +SUCCESS=() +SOLC=${SOLC:-"$(command -v -- solc)"} +if [ ! -f "${SOLC}" ]; then + echo "error: solc '${SOLC}' not found." + exit 1 +fi + +test_file() { + local SOL_FILE + local OUTPUT + SOL_FILE=${1} + + if OUTPUT=$("${SOLC}" --ir "${SOL_FILE}" 2>&1); then + SUCCESS+=("${SOL_FILE}") + show_output_if ${SHOW_SUCCESSFUL} + else + FAILED+=("${SOL_FILE}") + if [[ ${OUTPUT} == *"UnimplementedFeatureError"* ]]; then + UNIMPLEMENTED_FEATURE_ERRORS+=("${SOL_FILE}") + show_output_if ${SHOW_UNIMPLEMENTED_FEATURE_ERRORS} + elif [[ ${OUTPUT} == *"InternalCompilerError"* ]]; then + INTERNAL_COMPILER_ERRORS+=("${SOL_FILE}") + show_output_if ${SHOW_INTERNAL_COMPILER_ERRORS} + else + OTHER_ERRORS+=("${SOL_FILE}") + show_output_if ${SHOW_OTHER_ERRORS} + fi + fi +} + +# we only want to use files that do not contain errors or multi-source files. +SOL_FILES=() +while IFS='' read -r line; do + SOL_FILES+=("$line") +done < <( + grep -riL -E \ + "^\/\/ (DocstringParsing|Syntax|Type|Parser|Declaration)Error|^==== Source:" \ + "${ROOT_DIR}/test/libsolidity/syntaxTests" \ + "${ROOT_DIR}/test/libsolidity/semanticTests" +) + +for SOL_FILE in "${SOL_FILES[@]}"; do + test_file "${SOL_FILE}" +done + +if [ -z "${NO_STATS}" ]; then + SUM=$((${#SUCCESS[@]} + ${#FAILED[@]})) + PERCENTAGE=$(echo "scale=4; ${#SUCCESS[@]} / ${SUM}" | bc) + echo "${#SUCCESS[@]} / ${SUM} = ${PERCENTAGE}" + echo "UnimplementedFeatureError(s): ${#UNIMPLEMENTED_FEATURE_ERRORS[@]}" + echo "InternalCompilerError(s): ${#INTERNAL_COMPILER_ERRORS[@]}" + echo "OtherError(s): ${#OTHER_ERRORS[@]}" +fi diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 90c2931c5..79da75289 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -746,15 +746,6 @@ Allowed options)", "Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, " "byzantium, constantinople, petersburg, istanbul (default) or berlin." ) - (g_argOptimize.c_str(), "Enable bytecode optimizer.") - ( - g_argOptimizeRuns.c_str(), - po::value()->value_name("n")->default_value(200), - "Set for how many contract runs to optimize." - "Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage." - ) - (g_strOptimizeYul.c_str(), "Enable Yul optimizer in Solidity. Legacy option: the yul optimizer is enabled as part of the general --optimize option.") - (g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity.") (g_argPrettyJson.c_str(), "Output JSON in pretty format. Currently it only works with the combined JSON output.") ( g_argLibraries.c_str(), @@ -787,8 +778,8 @@ Allowed options)", ) ( g_argImportAst.c_str(), - "Import ASTs to be compiled, assumes input holds the AST in compact JSON format." - " Supported Inputs is the output of the standard-json or the one produced by --combined-json ast,compact-format" + "Import ASTs to be compiled, assumes input holds the AST in compact JSON format. " + "Supported Inputs is the output of the --standard-json or the one produced by --combined-json ast,compact-format" ) ( @@ -834,6 +825,18 @@ Allowed options)", (g_argOldReporter.c_str(), "Enables old diagnostics reporter.") (g_argErrorRecovery.c_str(), "Enables additional parser error recovery.") (g_argIgnoreMissingFiles.c_str(), "Ignore missing files."); + po::options_description optimizerOptions("Optimizer options"); + optimizerOptions.add_options() + (g_argOptimize.c_str(), "Enable bytecode optimizer.") + ( + g_argOptimizeRuns.c_str(), + po::value()->value_name("n")->default_value(200), + "Set for how many contract runs to optimize. " + "Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage." + ) + (g_strOptimizeYul.c_str(), "Legacy option, ignored. Use the general --optimize to enable Yul optimizer.") + (g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity."); + desc.add(optimizerOptions); po::options_description outputComponents("Output Components"); outputComponents.add_options() (g_argAstJson.c_str(), "AST of all source files in JSON format.") diff --git a/test/Common.h b/test/Common.h index a65c95539..513e7154b 100644 --- a/test/Common.h +++ b/test/Common.h @@ -29,13 +29,13 @@ namespace solidity::test #ifdef _WIN32 static constexpr auto evmoneFilename = "evmone.dll"; -static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-windows-amd64.zip"; +static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.4.1/evmone-0.4.1-windows-amd64.zip"; #elif defined(__APPLE__) static constexpr auto evmoneFilename = "libevmone.dylib"; -static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-darwin-x86_64.tar.gz"; +static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.4.1/evmone-0.4.1-darwin-x86_64.tar.gz"; #else static constexpr auto evmoneFilename = "libevmone.so"; -static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-linux-x86_64.tar.gz"; +static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.4.1/evmone-0.4.1-linux-x86_64.tar.gz"; #endif diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 9001c02f4..e99969808 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -31,19 +31,19 @@ set -e ## GLOBAL VARIABLES REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) -SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-${REPO_ROOT}/build} source "${REPO_ROOT}/scripts/common.sh" source "${REPO_ROOT}/scripts/common_cmdline.sh" case "$OSTYPE" in msys) - SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/Release/solc.exe" + SOLC="${SOLIDITY_BUILD_DIR}/solc/Release/solc.exe" # prevents msys2 path translation for a remapping test export MSYS2_ARG_CONV_EXCL="=" ;; *) - SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/solc" + SOLC="${SOLIDITY_BUILD_DIR}/solc/solc" ;; esac echo "${SOLC}" @@ -214,7 +214,7 @@ printTask "Testing unknown options..." then echo "Passed" else - printError "Incorrect response to unknown options: $STDERR" + printError "Incorrect response to unknown options: $output" exit 1 fi ) @@ -385,7 +385,7 @@ SOLTMPDIR=$(mktemp -d) # This should fail if [[ !("$output" =~ "No input files given") || ($result == 0) ]] then - printError "Incorrect response to empty input arg list: $STDERR" + printError "Incorrect response to empty input arg list: $output" exit 1 fi @@ -431,8 +431,8 @@ SOLTMPDIR=$(mktemp -d) "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/ "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/ docs - echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/${SOLIDITY_BUILD_DIR}/test/tools/solfuzzer --quiet --input-files - echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/${SOLIDITY_BUILD_DIR}/test/tools/solfuzzer --without-optimizer --quiet --input-files + echo *.sol | xargs -P 4 -n 50 "${SOLIDITY_BUILD_DIR}/test/tools/solfuzzer" --quiet --input-files + echo *.sol | xargs -P 4 -n 50 "${SOLIDITY_BUILD_DIR}/test/tools/solfuzzer" --without-optimizer --quiet --input-files ) rm -rf "$SOLTMPDIR" diff --git a/test/cmdlineTests/recovery_ast_constructor/output b/test/cmdlineTests/recovery_ast_constructor/output index e7dd79791..f2cc2ccf8 100644 --- a/test/cmdlineTests/recovery_ast_constructor/output +++ b/test/cmdlineTests/recovery_ast_constructor/output @@ -160,6 +160,7 @@ JSON AST: "attributes": { "constant": false, + "mutability": "mutable", "name": "", "overrides": null, "scope": 17, diff --git a/test/cmdlineTests/standard_irOptimized_requested/output.json b/test/cmdlineTests/standard_irOptimized_requested/output.json index 87e73dee7..78ebe59c3 100644 --- a/test/cmdlineTests/standard_irOptimized_requested/output.json +++ b/test/cmdlineTests/standard_irOptimized_requested/output.json @@ -10,8 +10,6 @@ object \"C_6\" { mstore(64, 128) codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) return(0, datasize(\"C_6_deployed\")) - function fun_f_5() - { } } object \"C_6_deployed\" { code { diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index cb89a6911..2ceee9da0 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -18,11 +18,6 @@ object \"C_6\" { return(0, datasize(\"C_6_deployed\")) - function fun_f_5() { - - - } - } object \"C_6_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json index 24a2a10aa..0e88aa5aa 100644 --- a/test/cmdlineTests/yul_string_format_ascii/output.json +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -18,35 +18,6 @@ object \"C_10\" { return(0, datasize(\"C_10_deployed\")) - function allocateMemory(size) -> memPtr { - memPtr := mload(64) - let newFreePtr := add(memPtr, size) - // protect against overflow - if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } - mstore(64, newFreePtr) - } - - function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() -> converted { - converted := allocateMemory(64) - mstore(converted, 6) - - mstore(add(converted, 32), \"abcabc\") - - } - - function fun_f_9() -> vloc__4_mpos { - let zero_value_for_type_t_string_memory_ptr_1_mpos := zero_value_for_split_t_string_memory_ptr() - vloc__4_mpos := zero_value_for_type_t_string_memory_ptr_1_mpos - - vloc__4_mpos := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() - leave - - } - - function zero_value_for_split_t_string_memory_ptr() -> ret { - ret := 96 - } - } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json index 8028e5dd6..256b1e4e1 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -18,23 +18,6 @@ object \"C_10\" { return(0, datasize(\"C_10_deployed\")) - function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() -> converted { - converted := 0x6162636162630000000000000000000000000000000000000000000000000000 - } - - function fun_f_9() -> vloc__4 { - let zero_value_for_type_t_bytes32_1 := zero_value_for_split_t_bytes32() - vloc__4 := zero_value_for_type_t_bytes32_1 - - vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() - leave - - } - - function zero_value_for_split_t_bytes32() -> ret { - ret := 0 - } - } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json index a9588d145..abb929c19 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -18,35 +18,6 @@ object \"C_10\" { return(0, datasize(\"C_10_deployed\")) - function cleanup_t_rational_1633837924_by_1(value) -> cleaned { - cleaned := value - } - - function convert_t_rational_1633837924_by_1_to_t_bytes4(value) -> converted { - converted := shift_left_224(cleanup_t_rational_1633837924_by_1(value)) - } - - function fun_f_9() -> vloc__4 { - let zero_value_for_type_t_bytes4_1 := zero_value_for_split_t_bytes4() - vloc__4 := zero_value_for_type_t_bytes4_1 - - let expr_6 := 0x61626364 - vloc__4 := convert_t_rational_1633837924_by_1_to_t_bytes4(expr_6) - leave - - } - - function shift_left_224(value) -> newValue { - newValue := - - shl(224, value) - - } - - function zero_value_for_split_t_bytes4() -> ret { - ret := 0 - } - } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json index 552712154..991f9d0fd 100644 --- a/test/cmdlineTests/yul_string_format_ascii_long/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -18,39 +18,6 @@ object \"C_10\" { return(0, datasize(\"C_10_deployed\")) - function allocateMemory(size) -> memPtr { - memPtr := mload(64) - let newFreePtr := add(memPtr, size) - // protect against overflow - if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } - mstore(64, newFreePtr) - } - - function convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() -> converted { - converted := allocateMemory(128) - mstore(converted, 85) - - mstore(add(converted, 32), \"abcdabcdcafecafeabcdabcdcafecafe\") - - mstore(add(converted, 64), \"ffffzzzzoooo0123456789,.<,>.?:;'\") - - mstore(add(converted, 96), \"[{]}|`~!@#$%^&*()-_=+\") - - } - - function fun_f_9() -> vloc__4_mpos { - let zero_value_for_type_t_string_memory_ptr_1_mpos := zero_value_for_split_t_string_memory_ptr() - vloc__4_mpos := zero_value_for_type_t_string_memory_ptr_1_mpos - - vloc__4_mpos := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() - leave - - } - - function zero_value_for_split_t_string_memory_ptr() -> ret { - ret := 96 - } - } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json index 48c1146fa..ff84b1c32 100644 --- a/test/cmdlineTests/yul_string_format_hex/output.json +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -18,35 +18,6 @@ object \"C_10\" { return(0, datasize(\"C_10_deployed\")) - function cleanup_t_rational_2864434397_by_1(value) -> cleaned { - cleaned := value - } - - function convert_t_rational_2864434397_by_1_to_t_bytes4(value) -> converted { - converted := shift_left_224(cleanup_t_rational_2864434397_by_1(value)) - } - - function fun_f_9() -> vloc__4 { - let zero_value_for_type_t_bytes4_1 := zero_value_for_split_t_bytes4() - vloc__4 := zero_value_for_type_t_bytes4_1 - - let expr_6 := 0xaabbccdd - vloc__4 := convert_t_rational_2864434397_by_1_to_t_bytes4(expr_6) - leave - - } - - function shift_left_224(value) -> newValue { - newValue := - - shl(224, value) - - } - - function zero_value_for_split_t_bytes4() -> ret { - ret := 0 - } - } object \"C_10_deployed\" { code { diff --git a/test/evmc/README.md b/test/evmc/README.md index 37417222c..436a47e68 100644 --- a/test/evmc/README.md +++ b/test/evmc/README.md @@ -1,3 +1,3 @@ # EVMC -This is an import of [EVMC](https://github.com/ethereum/evmc) version [7.0.0](https://github.com/ethereum/evmc/releases/tag/v7.0.0). +This is an import of [EVMC](https://github.com/ethereum/evmc) version [7.1.0](https://github.com/ethereum/evmc/releases/tag/v7.1.0). diff --git a/test/formal/opcodes.py b/test/formal/opcodes.py index cdd60ddae..e65ecef29 100644 --- a/test/formal/opcodes.py +++ b/test/formal/opcodes.py @@ -42,6 +42,9 @@ def ISZERO(x): def AND(x, y): return x & y +def OR(x, y): + return x | y + def SHL(x, y): return y << x diff --git a/test/formal/repeated_and.py b/test/formal/repeated_and.py new file mode 100644 index 000000000..2e8431b3c --- /dev/null +++ b/test/formal/repeated_and.py @@ -0,0 +1,39 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +AND(AND(X, Y), Y) -> AND(X, Y) +AND(Y, AND(X, Y)) -> AND(X, Y) +AND(AND(Y, X), Y) -> AND(Y, X) +AND(Y, AND(Y, X)) -> AND(Y, X) +Requirements: +""" + +rule = Rule() + +n_bits = 256 + +# Input vars +X = BitVec('X', n_bits) +Y = BitVec('Y', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements + +# Non optimized result +nonopt_1 = AND(AND(X, Y), Y) +nonopt_2 = AND(Y, AND(X, Y)) +nonopt_3 = AND(AND(Y, X), Y) +nonopt_4 = AND(Y, AND(Y, X)) + +# Optimized result +opt_1 = AND(X, Y) +opt_2 = AND(Y, X) + +rule.check(nonopt_1, opt_1) +rule.check(nonopt_2, opt_1) +rule.check(nonopt_3, opt_2) +rule.check(nonopt_4, opt_2) diff --git a/test/formal/repeated_or.py b/test/formal/repeated_or.py new file mode 100644 index 000000000..c1b2ebd09 --- /dev/null +++ b/test/formal/repeated_or.py @@ -0,0 +1,39 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +OR(OR(X, Y), Y) -> OR(X, Y) +OR(Y, OR(X, Y)) -> OR(X, Y) +OR(OR(Y, X), Y) -> OR(Y, X) +OR(Y, OR(Y, X)) -> OR(Y, X) +Requirements: +""" + +rule = Rule() + +n_bits = 256 + +# Input vars +X = BitVec('X', n_bits) +Y = BitVec('Y', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements + +# Non optimized result +nonopt_1 = OR(OR(X, Y), Y) +nonopt_2 = OR(Y, OR(X, Y)) +nonopt_3 = OR(OR(Y, X), Y) +nonopt_4 = OR(Y, OR(Y, X)) + +# Optimized result +opt_1 = OR(X, Y) +opt_2 = OR(Y, X) + +rule.check(nonopt_1, opt_1) +rule.check(nonopt_2, opt_1) +rule.check(nonopt_3, opt_2) +rule.check(nonopt_4, opt_2) diff --git a/test/libsolidity/ASTJSON/address_payable.json b/test/libsolidity/ASTJSON/address_payable.json index fbf2f3e59..ef965ac79 100644 --- a/test/libsolidity/ASTJSON/address_payable.json +++ b/test/libsolidity/ASTJSON/address_payable.json @@ -31,6 +31,7 @@ "constant": false, "functionSelector": "97682884", "id": 4, + "mutability": "mutable", "name": "m", "nodeType": "VariableDeclaration", "overrides": null, @@ -100,6 +101,7 @@ { "constant": false, "id": 12, + "mutability": "mutable", "name": "a", "nodeType": "VariableDeclaration", "overrides": null, @@ -241,6 +243,7 @@ { "constant": false, "id": 22, + "mutability": "mutable", "name": "c", "nodeType": "VariableDeclaration", "overrides": null, @@ -507,6 +510,7 @@ { "constant": false, "id": 6, + "mutability": "mutable", "name": "arg", "nodeType": "VariableDeclaration", "overrides": null, @@ -547,6 +551,7 @@ { "constant": false, "id": 9, + "mutability": "mutable", "name": "r", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/address_payable_legacy.json b/test/libsolidity/ASTJSON/address_payable_legacy.json index c45e75c4a..dd33e6408 100644 --- a/test/libsolidity/ASTJSON/address_payable_legacy.json +++ b/test/libsolidity/ASTJSON/address_payable_legacy.json @@ -41,6 +41,7 @@ { "constant": false, "functionSelector": "97682884", + "mutability": "mutable", "name": "m", "overrides": null, "scope": 39, @@ -118,6 +119,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "arg", "overrides": null, "scope": 38, @@ -157,6 +159,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "r", "overrides": null, "scope": 38, @@ -206,6 +209,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "a", "overrides": null, "scope": 37, @@ -359,6 +363,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "c", "overrides": null, "scope": 37, diff --git a/test/libsolidity/ASTJSON/array_type_name.json b/test/libsolidity/ASTJSON/array_type_name.json index 6fea9f532..e97518cb8 100644 --- a/test/libsolidity/ASTJSON/array_type_name.json +++ b/test/libsolidity/ASTJSON/array_type_name.json @@ -30,6 +30,7 @@ { "constant": false, "id": 3, + "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/array_type_name_legacy.json b/test/libsolidity/ASTJSON/array_type_name_legacy.json index a706c2f3a..37175bb37 100644 --- a/test/libsolidity/ASTJSON/array_type_name_legacy.json +++ b/test/libsolidity/ASTJSON/array_type_name_legacy.json @@ -40,6 +40,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "i", "overrides": null, "scope": 4, diff --git a/test/libsolidity/ASTJSON/assembly/nested_functions.json b/test/libsolidity/ASTJSON/assembly/nested_functions.json index b990a963f..f52cbfc08 100644 --- a/test/libsolidity/ASTJSON/assembly/nested_functions.json +++ b/test/libsolidity/ASTJSON/assembly/nested_functions.json @@ -121,6 +121,7 @@ { "constant": false, "id": 3, + "mutability": "mutable", "name": "x", "nodeType": "VariableDeclaration", "overrides": null, @@ -130,8 +131,8 @@ "storageLocation": "default", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": null, + "typeString": null }, "typeName": { @@ -141,8 +142,8 @@ "src": "49:4:1", "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" + "typeIdentifier": null, + "typeString": null } }, "value": null, diff --git a/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json b/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json index abaf60fb7..c0164e3f7 100644 --- a/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/nested_functions_legacy.json @@ -77,12 +77,13 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "x", "overrides": null, "scope": 7, "stateVariable": false, "storageLocation": "default", - "type": "uint256", + "type": null, "value": null, "visibility": "internal" }, @@ -92,7 +93,7 @@ "attributes": { "name": "uint", - "type": "uint256" + "type": null }, "id": 2, "name": "ElementaryTypeName", diff --git a/test/libsolidity/ASTJSON/assembly/slot_offset.json b/test/libsolidity/ASTJSON/assembly/slot_offset.json index 68a7603ea..75c22243c 100644 --- a/test/libsolidity/ASTJSON/assembly/slot_offset.json +++ b/test/libsolidity/ASTJSON/assembly/slot_offset.json @@ -35,6 +35,7 @@ { "constant": false, "id": 2, + "mutability": "mutable", "name": "x", "nodeType": "VariableDeclaration", "overrides": null, @@ -72,6 +73,7 @@ { "constant": false, "id": 5, + "mutability": "mutable", "name": "s", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json b/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json index 800e5b627..083f8dd52 100644 --- a/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json @@ -50,6 +50,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "x", "overrides": null, "scope": 3, @@ -85,6 +86,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "s", "overrides": null, "scope": 11, diff --git a/test/libsolidity/ASTJSON/assembly/var_access.json b/test/libsolidity/ASTJSON/assembly/var_access.json index 1e66ef6b3..0ef180ced 100644 --- a/test/libsolidity/ASTJSON/assembly/var_access.json +++ b/test/libsolidity/ASTJSON/assembly/var_access.json @@ -45,6 +45,7 @@ { "constant": false, "id": 4, + "mutability": "mutable", "name": "x", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/assembly/var_access_legacy.json b/test/libsolidity/ASTJSON/assembly/var_access_legacy.json index 0442ace76..21cda71e8 100644 --- a/test/libsolidity/ASTJSON/assembly/var_access_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/var_access_legacy.json @@ -101,6 +101,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "x", "overrides": null, "scope": 7, diff --git a/test/libsolidity/ASTJSON/function_type.json b/test/libsolidity/ASTJSON/function_type.json index 33ba4b22a..e1e83a316 100644 --- a/test/libsolidity/ASTJSON/function_type.json +++ b/test/libsolidity/ASTJSON/function_type.json @@ -53,6 +53,7 @@ { "constant": false, "id": 6, + "mutability": "mutable", "name": "x", "nodeType": "VariableDeclaration", "overrides": null, @@ -85,6 +86,7 @@ { "constant": false, "id": 3, + "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, @@ -139,6 +141,7 @@ { "constant": false, "id": 13, + "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, @@ -171,6 +174,7 @@ { "constant": false, "id": 10, + "mutability": "mutable", "name": "", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/function_type_legacy.json b/test/libsolidity/ASTJSON/function_type_legacy.json index 8fa58f9c9..e86eb16a6 100644 --- a/test/libsolidity/ASTJSON/function_type_legacy.json +++ b/test/libsolidity/ASTJSON/function_type_legacy.json @@ -64,6 +64,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "x", "overrides": null, "scope": 16, @@ -104,6 +105,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "", "overrides": null, "scope": 5, @@ -157,6 +159,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "", "overrides": null, "scope": 16, @@ -197,6 +200,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "", "overrides": null, "scope": 12, diff --git a/test/libsolidity/ASTJSON/global_struct.json b/test/libsolidity/ASTJSON/global_struct.json index 6eaaee8d9..64cf62a53 100644 --- a/test/libsolidity/ASTJSON/global_struct.json +++ b/test/libsolidity/ASTJSON/global_struct.json @@ -19,6 +19,7 @@ { "constant": false, "id": 2, + "mutability": "mutable", "name": "a", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/global_struct_legacy.json b/test/libsolidity/ASTJSON/global_struct_legacy.json index fd68cee57..ef0f71c37 100644 --- a/test/libsolidity/ASTJSON/global_struct_legacy.json +++ b/test/libsolidity/ASTJSON/global_struct_legacy.json @@ -26,6 +26,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "a", "overrides": null, "scope": 3, diff --git a/test/libsolidity/ASTJSON/long_type_name_binary_operation.json b/test/libsolidity/ASTJSON/long_type_name_binary_operation.json index e6d8b4d20..6e025f5f9 100644 --- a/test/libsolidity/ASTJSON/long_type_name_binary_operation.json +++ b/test/libsolidity/ASTJSON/long_type_name_binary_operation.json @@ -45,6 +45,7 @@ { "constant": false, "id": 4, + "mutability": "mutable", "name": "a", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/long_type_name_binary_operation_legacy.json b/test/libsolidity/ASTJSON/long_type_name_binary_operation_legacy.json index 977f783dd..6ad38d194 100644 --- a/test/libsolidity/ASTJSON/long_type_name_binary_operation_legacy.json +++ b/test/libsolidity/ASTJSON/long_type_name_binary_operation_legacy.json @@ -100,6 +100,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "a", "overrides": null, "scope": 9, diff --git a/test/libsolidity/ASTJSON/long_type_name_identifier.json b/test/libsolidity/ASTJSON/long_type_name_identifier.json index 5c8951d29..eddeacd04 100644 --- a/test/libsolidity/ASTJSON/long_type_name_identifier.json +++ b/test/libsolidity/ASTJSON/long_type_name_identifier.json @@ -30,6 +30,7 @@ { "constant": false, "id": 3, + "mutability": "mutable", "name": "a", "nodeType": "VariableDeclaration", "overrides": null, @@ -87,6 +88,7 @@ { "constant": false, "id": 10, + "mutability": "mutable", "name": "b", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/long_type_name_identifier_legacy.json b/test/libsolidity/ASTJSON/long_type_name_identifier_legacy.json index caf807c24..bc90e0d6a 100644 --- a/test/libsolidity/ASTJSON/long_type_name_identifier_legacy.json +++ b/test/libsolidity/ASTJSON/long_type_name_identifier_legacy.json @@ -40,6 +40,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "a", "overrides": null, "scope": 15, @@ -143,6 +144,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "b", "overrides": null, "scope": 13, diff --git a/test/libsolidity/ASTJSON/mappings.json b/test/libsolidity/ASTJSON/mappings.json index 0bd1ecd98..5c24e3a12 100644 --- a/test/libsolidity/ASTJSON/mappings.json +++ b/test/libsolidity/ASTJSON/mappings.json @@ -58,6 +58,7 @@ { "constant": false, "id": 8, + "mutability": "mutable", "name": "a", "nodeType": "VariableDeclaration", "overrides": null, @@ -113,6 +114,7 @@ { "constant": false, "id": 12, + "mutability": "mutable", "name": "b", "nodeType": "VariableDeclaration", "overrides": null, @@ -166,6 +168,7 @@ { "constant": false, "id": 16, + "mutability": "mutable", "name": "c", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/mappings_legacy.json b/test/libsolidity/ASTJSON/mappings_legacy.json index 79814effe..700097b66 100644 --- a/test/libsolidity/ASTJSON/mappings_legacy.json +++ b/test/libsolidity/ASTJSON/mappings_legacy.json @@ -80,6 +80,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "a", "overrides": null, "scope": 17, @@ -134,6 +135,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "b", "overrides": null, "scope": 17, @@ -186,6 +188,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "c", "overrides": null, "scope": 17, diff --git a/test/libsolidity/ASTJSON/modifier_definition.json b/test/libsolidity/ASTJSON/modifier_definition.json index 19f7ed184..a3c71159a 100644 --- a/test/libsolidity/ASTJSON/modifier_definition.json +++ b/test/libsolidity/ASTJSON/modifier_definition.json @@ -56,6 +56,7 @@ { "constant": false, "id": 2, + "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/modifier_definition_legacy.json b/test/libsolidity/ASTJSON/modifier_definition_legacy.json index 8f028a474..369562171 100644 --- a/test/libsolidity/ASTJSON/modifier_definition_legacy.json +++ b/test/libsolidity/ASTJSON/modifier_definition_legacy.json @@ -54,6 +54,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "i", "overrides": null, "scope": 6, diff --git a/test/libsolidity/ASTJSON/modifier_invocation.json b/test/libsolidity/ASTJSON/modifier_invocation.json index 19f7ed184..a3c71159a 100644 --- a/test/libsolidity/ASTJSON/modifier_invocation.json +++ b/test/libsolidity/ASTJSON/modifier_invocation.json @@ -56,6 +56,7 @@ { "constant": false, "id": 2, + "mutability": "mutable", "name": "i", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/modifier_invocation_legacy.json b/test/libsolidity/ASTJSON/modifier_invocation_legacy.json index 8f028a474..369562171 100644 --- a/test/libsolidity/ASTJSON/modifier_invocation_legacy.json +++ b/test/libsolidity/ASTJSON/modifier_invocation_legacy.json @@ -54,6 +54,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "i", "overrides": null, "scope": 6, diff --git a/test/libsolidity/ASTJSON/mutability.json b/test/libsolidity/ASTJSON/mutability.json new file mode 100644 index 000000000..81d668702 --- /dev/null +++ b/test/libsolidity/ASTJSON/mutability.json @@ -0,0 +1,189 @@ +{ + "absolutePath": "a", + "exportedSymbols": + { + "C": + [ + 10 + ] + }, + "id": 11, + "nodeType": "SourceUnit", + "nodes": + [ + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 10, + "linearizedBaseContracts": + [ + 10 + ], + "name": "C", + "nodeType": "ContractDefinition", + "nodes": + [ + { + "constant": false, + "functionSelector": "0dbe671f", + "id": 3, + "mutability": "immutable", + "name": "a", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 10, + "src": "17:27:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": + { + "id": 1, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "17:4:1", + "typeDescriptions": + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": + { + "argumentTypes": null, + "hexValue": "34", + "id": 2, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "43:1:1", + "subdenomination": null, + "typeDescriptions": + { + "typeIdentifier": "t_rational_4_by_1", + "typeString": "int_const 4" + }, + "value": "4" + }, + "visibility": "public" + }, + { + "constant": true, + "functionSelector": "4df7e3d0", + "id": 6, + "mutability": "constant", + "name": "b", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 10, + "src": "50:26:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": + { + "id": 4, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "50:4:1", + "typeDescriptions": + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": + { + "argumentTypes": null, + "hexValue": "32", + "id": 5, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "75:1:1", + "subdenomination": null, + "typeDescriptions": + { + "typeIdentifier": "t_rational_2_by_1", + "typeString": "int_const 2" + }, + "value": "2" + }, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c3da42b8", + "id": 9, + "mutability": "mutable", + "name": "c", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 10, + "src": "82:17:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": + { + "id": 7, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "82:4:1", + "typeDescriptions": + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": + { + "argumentTypes": null, + "hexValue": "33", + "id": 8, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "98:1:1", + "subdenomination": null, + "typeDescriptions": + { + "typeIdentifier": "t_rational_3_by_1", + "typeString": "int_const 3" + }, + "value": "3" + }, + "visibility": "public" + } + ], + "scope": 11, + "src": "0:102:1" + } + ], + "src": "0:103:1" +} diff --git a/test/libsolidity/ASTJSON/mutability.sol b/test/libsolidity/ASTJSON/mutability.sol new file mode 100644 index 000000000..7688186e7 --- /dev/null +++ b/test/libsolidity/ASTJSON/mutability.sol @@ -0,0 +1,8 @@ +contract C +{ + uint public immutable a = 4; + uint public constant b = 2; + uint public c = 3; +} + +// ---- diff --git a/test/libsolidity/ASTJSON/mutability_legacy.json b/test/libsolidity/ASTJSON/mutability_legacy.json new file mode 100644 index 000000000..98f28f636 --- /dev/null +++ b/test/libsolidity/ASTJSON/mutability_legacy.json @@ -0,0 +1,195 @@ +{ + "attributes": + { + "absolutePath": "a", + "exportedSymbols": + { + "C": + [ + 10 + ] + } + }, + "children": + [ + { + "attributes": + { + "abstract": false, + "baseContracts": + [ + null + ], + "contractDependencies": + [ + null + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "linearizedBaseContracts": + [ + 10 + ], + "name": "C", + "scope": 11 + }, + "children": + [ + { + "attributes": + { + "constant": false, + "functionSelector": "0dbe671f", + "mutability": "immutable", + "name": "a", + "overrides": null, + "scope": 10, + "stateVariable": true, + "storageLocation": "default", + "type": "uint256", + "visibility": "public" + }, + "children": + [ + { + "attributes": + { + "name": "uint", + "type": "uint256" + }, + "id": 1, + "name": "ElementaryTypeName", + "src": "17:4:1" + }, + { + "attributes": + { + "argumentTypes": null, + "hexvalue": "34", + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "subdenomination": null, + "token": "number", + "type": "int_const 4", + "value": "4" + }, + "id": 2, + "name": "Literal", + "src": "43:1:1" + } + ], + "id": 3, + "name": "VariableDeclaration", + "src": "17:27:1" + }, + { + "attributes": + { + "constant": true, + "functionSelector": "4df7e3d0", + "mutability": "constant", + "name": "b", + "overrides": null, + "scope": 10, + "stateVariable": true, + "storageLocation": "default", + "type": "uint256", + "visibility": "public" + }, + "children": + [ + { + "attributes": + { + "name": "uint", + "type": "uint256" + }, + "id": 4, + "name": "ElementaryTypeName", + "src": "50:4:1" + }, + { + "attributes": + { + "argumentTypes": null, + "hexvalue": "32", + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "subdenomination": null, + "token": "number", + "type": "int_const 2", + "value": "2" + }, + "id": 5, + "name": "Literal", + "src": "75:1:1" + } + ], + "id": 6, + "name": "VariableDeclaration", + "src": "50:26:1" + }, + { + "attributes": + { + "constant": false, + "functionSelector": "c3da42b8", + "mutability": "mutable", + "name": "c", + "overrides": null, + "scope": 10, + "stateVariable": true, + "storageLocation": "default", + "type": "uint256", + "visibility": "public" + }, + "children": + [ + { + "attributes": + { + "name": "uint", + "type": "uint256" + }, + "id": 7, + "name": "ElementaryTypeName", + "src": "82:4:1" + }, + { + "attributes": + { + "argumentTypes": null, + "hexvalue": "33", + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "subdenomination": null, + "token": "number", + "type": "int_const 3", + "value": "3" + }, + "id": 8, + "name": "Literal", + "src": "98:1:1" + } + ], + "id": 9, + "name": "VariableDeclaration", + "src": "82:17:1" + } + ], + "id": 10, + "name": "ContractDefinition", + "src": "0:102:1" + } + ], + "id": 11, + "name": "SourceUnit", + "src": "0:103:1" +} diff --git a/test/libsolidity/ASTJSON/non_utf8.json b/test/libsolidity/ASTJSON/non_utf8.json index 18f6b12dc..c925389d7 100644 --- a/test/libsolidity/ASTJSON/non_utf8.json +++ b/test/libsolidity/ASTJSON/non_utf8.json @@ -45,6 +45,7 @@ { "constant": false, "id": 3, + "mutability": "mutable", "name": "x", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/non_utf8_legacy.json b/test/libsolidity/ASTJSON/non_utf8_legacy.json index 9f7933020..d3a212b0c 100644 --- a/test/libsolidity/ASTJSON/non_utf8_legacy.json +++ b/test/libsolidity/ASTJSON/non_utf8_legacy.json @@ -100,6 +100,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "x", "overrides": null, "scope": 6, diff --git a/test/libsolidity/ASTJSON/short_type_name.json b/test/libsolidity/ASTJSON/short_type_name.json index 887f29a1d..75defa9ab 100644 --- a/test/libsolidity/ASTJSON/short_type_name.json +++ b/test/libsolidity/ASTJSON/short_type_name.json @@ -45,6 +45,7 @@ { "constant": false, "id": 7, + "mutability": "mutable", "name": "x", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/short_type_name_legacy.json b/test/libsolidity/ASTJSON/short_type_name_legacy.json index 4d1de106b..0d428bcb1 100644 --- a/test/libsolidity/ASTJSON/short_type_name_legacy.json +++ b/test/libsolidity/ASTJSON/short_type_name_legacy.json @@ -101,6 +101,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "x", "overrides": null, "scope": 9, diff --git a/test/libsolidity/ASTJSON/short_type_name_ref.json b/test/libsolidity/ASTJSON/short_type_name_ref.json index 501184c9c..46096a914 100644 --- a/test/libsolidity/ASTJSON/short_type_name_ref.json +++ b/test/libsolidity/ASTJSON/short_type_name_ref.json @@ -45,6 +45,7 @@ { "constant": false, "id": 8, + "mutability": "mutable", "name": "rows", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/short_type_name_ref_legacy.json b/test/libsolidity/ASTJSON/short_type_name_ref_legacy.json index 4eec368b6..b1f4b88d2 100644 --- a/test/libsolidity/ASTJSON/short_type_name_ref_legacy.json +++ b/test/libsolidity/ASTJSON/short_type_name_ref_legacy.json @@ -101,6 +101,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "rows", "overrides": null, "scope": 10, diff --git a/test/libsolidity/ASTJSON/source_location.json b/test/libsolidity/ASTJSON/source_location.json index cc6dd33c9..54bffe378 100644 --- a/test/libsolidity/ASTJSON/source_location.json +++ b/test/libsolidity/ASTJSON/source_location.json @@ -45,6 +45,7 @@ { "constant": false, "id": 3, + "mutability": "mutable", "name": "x", "nodeType": "VariableDeclaration", "overrides": null, diff --git a/test/libsolidity/ASTJSON/source_location_legacy.json b/test/libsolidity/ASTJSON/source_location_legacy.json index dfde18975..dbd578dd2 100644 --- a/test/libsolidity/ASTJSON/source_location_legacy.json +++ b/test/libsolidity/ASTJSON/source_location_legacy.json @@ -100,6 +100,7 @@ "attributes": { "constant": false, + "mutability": "mutable", "name": "x", "overrides": null, "scope": 9, diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index f762849e8..2af81c932 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -60,6 +61,7 @@ evmasm::AssemblyItems compileContract(std::shared_ptr _sourceCode) map> scopes; GlobalContext globalContext; NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), scopes, errorReporter); + DeclarationTypeChecker declarationTypeChecker(errorReporter, solidity::test::CommonOptions::get().evmVersion()); solAssert(Error::containsOnlyWarnings(errorReporter.errors()), ""); resolver.registerDeclarations(*sourceUnit); for (ASTPointer const& node: sourceUnit->nodes()) @@ -69,6 +71,12 @@ evmasm::AssemblyItems compileContract(std::shared_ptr _sourceCode) if (!Error::containsOnlyWarnings(errorReporter.errors())) return AssemblyItems(); } + for (ASTPointer const& node: sourceUnit->nodes()) + { + BOOST_REQUIRE_NO_THROW(declarationTypeChecker.check(*node)); + if (!Error::containsOnlyWarnings(errorReporter.errors())) + return AssemblyItems(); + } for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 5dfefc0b7..875a042fd 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -118,10 +119,12 @@ bytes compileFirstExpression( map> scopes; NameAndTypeResolver resolver(globalContext, solidity::test::CommonOptions::get().evmVersion(), scopes, errorReporter); resolver.registerDeclarations(*sourceUnit); - for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) BOOST_REQUIRE_MESSAGE(resolver.resolveNamesAndTypes(*contract), "Resolving names failed"); + DeclarationTypeChecker declarationTypeChecker(errorReporter, solidity::test::CommonOptions::get().evmVersion()); + for (ASTPointer const& node: sourceUnit->nodes()) + BOOST_REQUIRE(declarationTypeChecker.check(*node)); for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp index c6cd25c56..b21c8f2e1 100644 --- a/test/libsolidity/SolidityTypes.cpp +++ b/test/libsolidity/SolidityTypes.cpp @@ -184,6 +184,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers) BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2"); StructDefinition s(++id, {}, make_shared("Struct"), {}); + s.annotation().recursive = false; BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type$_t_struct$_Struct_$3_storage_ptr_$"); EnumDefinition e(++id, {}, make_shared("Enum"), {}); diff --git a/test/libsolidity/semanticTests/builtinFunctions/function_types_sig.sol b/test/libsolidity/semanticTests/builtinFunctions/function_types_sig.sol index 2e771032b..e753cbbe9 100644 --- a/test/libsolidity/semanticTests/builtinFunctions/function_types_sig.sol +++ b/test/libsolidity/semanticTests/builtinFunctions/function_types_sig.sol @@ -19,6 +19,8 @@ contract C { return this.x.selector; } } +// ==== +// compileViaYul: also // ---- // f() -> 0x26121ff000000000000000000000000000000000000000000000000000000000 // g() -> 0x26121ff000000000000000000000000000000000000000000000000000000000 diff --git a/test/libsolidity/semanticTests/enums/using_contract_enums_with_explicit_contract_name.sol b/test/libsolidity/semanticTests/enums/using_contract_enums_with_explicit_contract_name.sol index 5d60a7466..c6830abf8 100644 --- a/test/libsolidity/semanticTests/enums/using_contract_enums_with_explicit_contract_name.sol +++ b/test/libsolidity/semanticTests/enums/using_contract_enums_with_explicit_contract_name.sol @@ -6,5 +6,7 @@ contract test { } } +// ==== +// compileViaYul: also // ---- // answer() -> 1 diff --git a/test/libsolidity/semanticTests/enums/using_enums.sol b/test/libsolidity/semanticTests/enums/using_enums.sol index ae497f409..883726f7c 100644 --- a/test/libsolidity/semanticTests/enums/using_enums.sol +++ b/test/libsolidity/semanticTests/enums/using_enums.sol @@ -12,5 +12,7 @@ contract test { ActionChoices choices; } +// ==== +// compileViaYul: also // ---- // getChoice() -> 2 diff --git a/test/libsolidity/semanticTests/enums/using_inherited_enum.sol b/test/libsolidity/semanticTests/enums/using_inherited_enum.sol index c39bfe064..187ec962e 100644 --- a/test/libsolidity/semanticTests/enums/using_inherited_enum.sol +++ b/test/libsolidity/semanticTests/enums/using_inherited_enum.sol @@ -8,6 +8,7 @@ contract test is base { _ret = Choice.B; } } - +// ==== +// compileViaYul: also // ---- // answer() -> 1 diff --git a/test/libsolidity/semanticTests/enums/using_inherited_enum_excplicitly.sol b/test/libsolidity/semanticTests/enums/using_inherited_enum_excplicitly.sol index 0be3f80d4..3bd2b0cea 100644 --- a/test/libsolidity/semanticTests/enums/using_inherited_enum_excplicitly.sol +++ b/test/libsolidity/semanticTests/enums/using_inherited_enum_excplicitly.sol @@ -8,6 +8,7 @@ contract test is base { _ret = base.Choice.B; } } - +// ==== +// compileViaYul: also // ---- // answer() -> 1 diff --git a/test/libsolidity/semanticTests/expressions/bit_operators.sol b/test/libsolidity/semanticTests/expressions/bit_operators.sol new file mode 100644 index 000000000..32ec16bf6 --- /dev/null +++ b/test/libsolidity/semanticTests/expressions/bit_operators.sol @@ -0,0 +1,19 @@ +contract test { + uint8 x; + uint v; + function f() public returns (uint x, uint y, uint z) { + uint16 a; + uint32 b; + assembly { + a := 0x0f0f0f0f0f + b := 0xff0fff0fff + } + x = a & b; + y = a | b; + z = a ^ b; + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 3855, 268374015, 268370160 diff --git a/test/libsolidity/semanticTests/functionSelector/function_selector_via_contract_name.sol b/test/libsolidity/semanticTests/functionSelector/function_selector_via_contract_name.sol index 0a9e6d3ff..b6a666d5b 100644 --- a/test/libsolidity/semanticTests/functionSelector/function_selector_via_contract_name.sol +++ b/test/libsolidity/semanticTests/functionSelector/function_selector_via_contract_name.sol @@ -15,6 +15,8 @@ contract C { return (a.f.selector, a.g.selector, b.f.selector, b.g.selector); } } +// ==== +// compileViaYul: also // ---- // test1() -> left(0x26121ff0), left(0xe420264a), left(0x26121ff0), left(0xe420264a) // test2() -> left(0x26121ff0), left(0xe420264a), left(0x26121ff0), left(0xe420264a) diff --git a/test/libsolidity/semanticTests/intheritance/explicit_base_class.sol b/test/libsolidity/semanticTests/intheritance/explicit_base_class.sol index 44553b3f1..7481a0441 100644 --- a/test/libsolidity/semanticTests/intheritance/explicit_base_class.sol +++ b/test/libsolidity/semanticTests/intheritance/explicit_base_class.sol @@ -21,7 +21,8 @@ contract Derived is Base { return 3; } } - +// ==== +// compileViaYul: also // ---- // g() -> 3 // f() -> 1 diff --git a/test/libsolidity/semanticTests/intheritance/inherited_function.sol b/test/libsolidity/semanticTests/intheritance/inherited_function.sol index 23f9bee37..fc01100de 100644 --- a/test/libsolidity/semanticTests/intheritance/inherited_function.sol +++ b/test/libsolidity/semanticTests/intheritance/inherited_function.sol @@ -14,6 +14,7 @@ contract B is A { return A.f(); } } - +// ==== +// compileViaYul: also // ---- // g() -> 1 diff --git a/test/libsolidity/semanticTests/tryCatch/lowLevel.sol b/test/libsolidity/semanticTests/tryCatch/lowLevel.sol index ac275522d..c707cca59 100644 --- a/test/libsolidity/semanticTests/tryCatch/lowLevel.sol +++ b/test/libsolidity/semanticTests/tryCatch/lowLevel.sol @@ -12,6 +12,7 @@ contract C { } } // ==== +// compileViaYul: also // EVMVersion: >=byzantium // ---- // f(bool): true -> 1, 2, 96, 0 diff --git a/test/libsolidity/semanticTests/tryCatch/nested.sol b/test/libsolidity/semanticTests/tryCatch/nested.sol index 7c56a81b7..03e6ad553 100644 --- a/test/libsolidity/semanticTests/tryCatch/nested.sol +++ b/test/libsolidity/semanticTests/tryCatch/nested.sol @@ -26,6 +26,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // f(bool,bool): true, true -> 1, 2, 96, 7, "success" // f(bool,bool): true, false -> 12, 0, 96, 7, "failure" diff --git a/test/libsolidity/semanticTests/tryCatch/return_function.sol b/test/libsolidity/semanticTests/tryCatch/return_function.sol index 82d5dc821..b9c2d903f 100644 --- a/test/libsolidity/semanticTests/tryCatch/return_function.sol +++ b/test/libsolidity/semanticTests/tryCatch/return_function.sol @@ -13,5 +13,7 @@ contract C { } function fun() public pure {} } +// ==== +// compileViaYul: also // ---- // f() -> 0x1, 0xfdd67305928fcac8d213d1e47bfa6165cd0b87b946644cd0000000000000000, 9 diff --git a/test/libsolidity/semanticTests/tryCatch/simple.sol b/test/libsolidity/semanticTests/tryCatch/simple.sol index b7f367016..1f12614a7 100644 --- a/test/libsolidity/semanticTests/tryCatch/simple.sol +++ b/test/libsolidity/semanticTests/tryCatch/simple.sol @@ -1,10 +1,10 @@ contract C { - function g(bool b) public pure returns (uint, uint) { + function g(bool b) public pure returns (uint x, uint y) { require(b); return (1, 2); } - function f(bool b) public returns (uint x, uint y) { - try this.g(b) returns (uint a, uint b) { + function f(bool flag) public view returns (uint x, uint y) { + try this.g(flag) returns (uint a, uint b) { (x, y) = (a, b); } catch { (x, y) = (9, 10); @@ -13,6 +13,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // f(bool): true -> 1, 2 // f(bool): false -> 9, 10 diff --git a/test/libsolidity/semanticTests/tryCatch/simple_notuple.sol b/test/libsolidity/semanticTests/tryCatch/simple_notuple.sol new file mode 100644 index 000000000..7b58006f4 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/simple_notuple.sol @@ -0,0 +1,19 @@ +contract C { + function g(bool b) public pure returns (uint x) { + require(b); + return 13; + } + function f(bool flag) public view returns (uint x) { + try this.g(flag) returns (uint a) { + x = a; + } catch { + x = 9; + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileViaYul: also +// ---- +// f(bool): true -> 13 +// f(bool): false -> 9 diff --git a/test/libsolidity/semanticTests/tryCatch/structured.sol b/test/libsolidity/semanticTests/tryCatch/structured.sol index e5aa238b3..e4c04932a 100644 --- a/test/libsolidity/semanticTests/tryCatch/structured.sol +++ b/test/libsolidity/semanticTests/tryCatch/structured.sol @@ -14,6 +14,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // f(bool): true -> 1, 2, 0x60, 7, "success" // f(bool): false -> 0, 0, 0x60, 7, "message" diff --git a/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol b/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol index 8a8cb3d1b..91542d7ba 100644 --- a/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol +++ b/test/libsolidity/semanticTests/tryCatch/structuredAndLowLevel.sol @@ -18,6 +18,7 @@ contract C { } // ==== // EVMVersion: >=byzantium +// compileViaYul: also // ---- // f(bool): true -> 1, 2, 96, 7, "success" // f(bool): false -> 99, 0, 96, 82, "message longer than 32 bytes 32 ", "bytes 32 bytes 32 bytes 32 bytes", " 32 bytes 32 bytes" diff --git a/test/libsolidity/semanticTests/tryCatch/super_trivial.sol b/test/libsolidity/semanticTests/tryCatch/super_trivial.sol new file mode 100644 index 000000000..9c0340522 --- /dev/null +++ b/test/libsolidity/semanticTests/tryCatch/super_trivial.sol @@ -0,0 +1,18 @@ +contract C { + function g(bool x) external pure { + require(x); + } + function f(bool x) public returns (uint) { + try this.g(x) { + return 1; + } catch { + return 2; + } + } +} +// ==== +// EVMVersion: >=byzantium +// compileViaYul: also +// ---- +// f(bool): true -> 1 +// f(bool): false -> 2 diff --git a/test/libsolidity/semanticTests/types/tuple_in_tuple.sol b/test/libsolidity/semanticTests/types/nested_tuples.sol similarity index 64% rename from test/libsolidity/semanticTests/types/tuple_in_tuple.sol rename to test/libsolidity/semanticTests/types/nested_tuples.sol index d9737a807..b94d44370 100644 --- a/test/libsolidity/semanticTests/types/tuple_in_tuple.sol +++ b/test/libsolidity/semanticTests/types/nested_tuples.sol @@ -15,6 +15,16 @@ contract test { (((, a),)) = ((1, 2), 3); return a; } + function f3() public returns(int) { + int a = 3; + ((, ), ) = ((7, 8), 9); + return a; + } + function f4() public returns(int) { + int a; + (a, ) = (4, (8, 16, 32)); + return a; + } } // ==== // compileViaYul: also @@ -22,3 +32,5 @@ contract test { // f0() -> 2, true // f1() -> 1 // f2() -> 2 +// f3() -> 3 +// f4() -> 4 diff --git a/test/libsolidity/semanticTests/types/tuple_assign_multi_slot_grow.sol b/test/libsolidity/semanticTests/types/tuple_assign_multi_slot_grow.sol new file mode 100644 index 000000000..6735563b9 --- /dev/null +++ b/test/libsolidity/semanticTests/types/tuple_assign_multi_slot_grow.sol @@ -0,0 +1,13 @@ +contract C { + + function f() public pure returns (uint, uint, uint) { + bytes memory a; bytes memory b; bytes memory c; + (a, (b, c)) = ("0", ("1", "2")); + return (uint8(a[0]), uint8(b[0]), uint8(c[0])); + } + +} +// ==== +// compileViaYul: also +// ---- +// f() -> 0x30, 0x31, 0x32 diff --git a/test/libsolidity/semanticTests/various/state_variable_local_variable_mixture.sol b/test/libsolidity/semanticTests/various/state_variable_local_variable_mixture.sol index 585914c80..6f712839c 100644 --- a/test/libsolidity/semanticTests/various/state_variable_local_variable_mixture.sol +++ b/test/libsolidity/semanticTests/various/state_variable_local_variable_mixture.sol @@ -6,6 +6,7 @@ contract A { x = A.y; } } - +// ==== +// compileViaYul: also // ---- // a() -> 2 diff --git a/test/libsolidity/semanticTests/various/state_variable_under_contract_name.sol b/test/libsolidity/semanticTests/various/state_variable_under_contract_name.sol index 6bef6f85a..2a2c9b975 100644 --- a/test/libsolidity/semanticTests/various/state_variable_under_contract_name.sol +++ b/test/libsolidity/semanticTests/various/state_variable_under_contract_name.sol @@ -5,6 +5,7 @@ contract Scope { stateVar = Scope.stateVar; } } - +// ==== +// compileViaYul: also // ---- // getStateVar() -> 42 diff --git a/test/libsolidity/semanticTests/virtualFunctions/internal_virtual_function_calls.sol b/test/libsolidity/semanticTests/virtualFunctions/internal_virtual_function_calls.sol new file mode 100644 index 000000000..b06a89c46 --- /dev/null +++ b/test/libsolidity/semanticTests/virtualFunctions/internal_virtual_function_calls.sol @@ -0,0 +1,21 @@ +contract Base { + function f() public returns (uint256 i) { + return g(); + } + + function g() internal virtual returns (uint256 i) { + return 1; + } +} + + +contract Derived is Base { + function g() internal override returns (uint256 i) { + return 2; + } +} + +// ==== +// compileViaYul: also +// ---- +// f() -> 2 diff --git a/test/libsolidity/smtCheckerTests/types/function_type_nested.sol b/test/libsolidity/smtCheckerTests/types/function_type_nested.sol index 5eb637315..90d101f4d 100644 --- a/test/libsolidity/smtCheckerTests/types/function_type_nested.sol +++ b/test/libsolidity/smtCheckerTests/types/function_type_nested.sol @@ -18,5 +18,4 @@ contract C { // Warning: (212-219): Assertion checker does not yet implement this type of function call. // Warning: (255-257): Internal error: Expression undefined for SMT solver. // Warning: (255-257): Assertion checker does not yet implement type function (function (uint256)) -// Warning: (212-214): Assertion checker does not yet implement type function (function (uint256)) // Warning: (212-219): Assertion checker does not yet implement this type of function call. diff --git a/test/libsolidity/smtCheckerTests/types/function_type_nested_return.sol b/test/libsolidity/smtCheckerTests/types/function_type_nested_return.sol index 2e974fef8..d9a10370b 100644 --- a/test/libsolidity/smtCheckerTests/types/function_type_nested_return.sol +++ b/test/libsolidity/smtCheckerTests/types/function_type_nested_return.sol @@ -22,6 +22,4 @@ contract C { // Warning: (284-291): Assertion checker does not yet implement this type of function call. // Warning: (327-329): Internal error: Expression undefined for SMT solver. // Warning: (327-329): Assertion checker does not yet implement type function (function (uint256)) -// Warning: (284-286): Assertion checker does not yet implement type function (function (uint256)) -// Warning: (287-288): Assertion checker does not yet support this global variable. // Warning: (284-291): Assertion checker does not yet implement this type of function call. diff --git a/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol b/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol index 1a266aaa7..b6ee9fa3a 100644 --- a/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol +++ b/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol @@ -19,7 +19,5 @@ contract C { // Warning: (137-141): Assertion checker does not yet implement type struct C.S memory // Warning: (137-141): Assertion checker does not yet implement this expression. // Warning: (193-203): Assertion checker does not yet support the type of this variable. -// Warning: (137-138): Assertion checker does not yet implement type type(struct C.S storage pointer) -// Warning: (137-141): Assertion checker does not yet implement type struct C.S memory // Warning: (137-141): Assertion checker does not yet implement this expression. // Warning: (227-228): Assertion checker does not yet implement type struct C.S memory diff --git a/test/libsolidity/syntaxTests/array/length/array_length_cannot_be_constant_function_parameter.sol b/test/libsolidity/syntaxTests/array/length/array_length_cannot_be_constant_function_parameter.sol index 593281401..270f1908f 100644 --- a/test/libsolidity/syntaxTests/array/length/array_length_cannot_be_constant_function_parameter.sol +++ b/test/libsolidity/syntaxTests/array/length/array_length_cannot_be_constant_function_parameter.sol @@ -6,3 +6,4 @@ contract C { // ---- // DeclarationError: (28-45): The "constant" keyword can only be used for state variables. // TypeError: (69-72): Invalid array length, expected integer literal or constant expression. +// TypeError: (64-75): Data location must be "storage" or "memory" for variable, but none was given. diff --git a/test/libsolidity/syntaxTests/array/length/local_memory_too_large.sol b/test/libsolidity/syntaxTests/array/length/local_memory_too_large.sol new file mode 100644 index 000000000..9e0d6d62d --- /dev/null +++ b/test/libsolidity/syntaxTests/array/length/local_memory_too_large.sol @@ -0,0 +1,14 @@ +contract C { + function f() public pure + { + bytes32[1263941234127518272][500] memory x; + uint[2**30][] memory y; + uint[2**30][2**30][] memory z; + uint[2**16][2**16][] memory w; + } +} +// ---- +// TypeError: (48-90): Type too large for memory. +// TypeError: (96-118): Type too large for memory. +// TypeError: (124-153): Type too large for memory. +// TypeError: (159-188): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/array/length/parameter_too_large.sol b/test/libsolidity/syntaxTests/array/length/parameter_too_large.sol index 02e0a7cc6..7c7cf4fa0 100644 --- a/test/libsolidity/syntaxTests/array/length/parameter_too_large.sol +++ b/test/libsolidity/syntaxTests/array/length/parameter_too_large.sol @@ -2,4 +2,4 @@ contract C { function f(bytes32[1263941234127518272] memory) public pure {} } // ---- -// TypeError: (26-61): Array is too large to be encoded. +// TypeError: (26-61): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim.sol b/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim.sol index 5f96ecd56..737c99feb 100644 --- a/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim.sol +++ b/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim.sol @@ -5,7 +5,7 @@ contract C { function f(uint[2**16][2**16][] memory) public pure {} } // ---- -// TypeError: (26-66): Array is too large to be encoded. -// TypeError: (96-116): Array is too large to be encoded. -// TypeError: (146-173): Array is too large to be encoded. -// TypeError: (203-230): Array is too large to be encoded. +// TypeError: (26-66): Type too large for memory. +// TypeError: (96-116): Type too large for memory. +// TypeError: (146-173): Type too large for memory. +// TypeError: (203-230): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim_ABIv2.sol b/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim_ABIv2.sol index d376bca9a..26a683aa7 100644 --- a/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim_ABIv2.sol +++ b/test/libsolidity/syntaxTests/array/length/parameter_too_large_multidim_ABIv2.sol @@ -5,5 +5,5 @@ contract C { function f(uint[2**30][2**30][][] memory) public pure {} } // ---- -// TypeError: (61-101): Array is too large to be encoded. -// TypeError: (131-160): Array is too large to be encoded. +// TypeError: (61-101): Type too large for memory. +// TypeError: (131-160): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/constructor/constructor_override.sol b/test/libsolidity/syntaxTests/constructor/constructor_override.sol new file mode 100644 index 000000000..48203a27d --- /dev/null +++ b/test/libsolidity/syntaxTests/constructor/constructor_override.sol @@ -0,0 +1,5 @@ +contract C { + constructor() override public {} +} +// ---- +// TypeError: (17-49): Constructors cannot override. diff --git a/test/libsolidity/syntaxTests/constructor/constructor_virtual.sol b/test/libsolidity/syntaxTests/constructor/constructor_virtual.sol new file mode 100644 index 000000000..cd692dbcd --- /dev/null +++ b/test/libsolidity/syntaxTests/constructor/constructor_virtual.sol @@ -0,0 +1,5 @@ +contract C { + constructor() virtual public {} +} +// ---- +// TypeError: (17-48): Constructors cannot be virtual. diff --git a/test/libsolidity/syntaxTests/functionTypes/function_type_struct_undefined_member.sol b/test/libsolidity/syntaxTests/functionTypes/function_type_struct_undefined_member.sol index ca08afe5b..586753b0b 100644 --- a/test/libsolidity/syntaxTests/functionTypes/function_type_struct_undefined_member.sol +++ b/test/libsolidity/syntaxTests/functionTypes/function_type_struct_undefined_member.sol @@ -8,4 +8,3 @@ library L } // ---- // DeclarationError: (32-35): Identifier not found or not unique. -// TypeError: (63-76): Internal type cannot be used for external function type. diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/identifier_collision_return_declare.sol b/test/libsolidity/syntaxTests/iceRegressionTests/identifier_collision_return_declare.sol new file mode 100644 index 000000000..9df2f3f0b --- /dev/null +++ b/test/libsolidity/syntaxTests/iceRegressionTests/identifier_collision_return_declare.sol @@ -0,0 +1,5 @@ +contract C { + function ( uint ) external returns ( a [ ] calldata ) public a = ( 1 / 2 ) ; +} +// ---- +// TypeError: (58-59): Name has to refer to a struct, enum or contract. diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/large_array_in_memory_struct.sol b/test/libsolidity/syntaxTests/iceRegressionTests/large_array_in_memory_struct.sol new file mode 100644 index 000000000..273a75a5b --- /dev/null +++ b/test/libsolidity/syntaxTests/iceRegressionTests/large_array_in_memory_struct.sol @@ -0,0 +1,17 @@ +contract C { + struct X { bytes31 [ 3 ] x1 ; + uint x2 ; + } + struct S { uint256 [ ] [ 0.425781 ether ] s1 ; + uint [ 2 ** 0xFF ] [ 2 ** 0x42 ] s2 ; + X s3 ; + mapping ( uint => address payable ) c ; + uint [ 9 hours ** 16 ] d ; + string s ; + } + function f ( ) public { function ( function ( bytes9 , uint ) external pure returns ( uint ) , uint ) external pure returns ( uint ) [ 3 ] memory s2 ; + S memory s ; + } +} +// ---- +// TypeError: (530-540): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/large_array_in_memory_struct_2.sol b/test/libsolidity/syntaxTests/iceRegressionTests/large_array_in_memory_struct_2.sol new file mode 100644 index 000000000..ada721c54 --- /dev/null +++ b/test/libsolidity/syntaxTests/iceRegressionTests/large_array_in_memory_struct_2.sol @@ -0,0 +1,12 @@ +contract C { + struct R { uint[10][10] y; } + struct S { uint a; uint b; R d; uint[20][20][2999999999999999999999999990] c; } + function f() public pure { + C.S memory y; + C.S[10] memory z; + y.a < 2; + z; y; + } +} +// ---- +// TypeError: (169-181): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/array/length/not_too_large.sol b/test/libsolidity/syntaxTests/iceRegressionTests/large_struct_array.sol similarity index 83% rename from test/libsolidity/syntaxTests/array/length/not_too_large.sol rename to test/libsolidity/syntaxTests/iceRegressionTests/large_struct_array.sol index deb10c5b1..0ba74d7f8 100644 --- a/test/libsolidity/syntaxTests/array/length/not_too_large.sol +++ b/test/libsolidity/syntaxTests/iceRegressionTests/large_struct_array.sol @@ -7,3 +7,4 @@ contract C { } } // ---- +// TypeError: (226-234): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/memory_mapping_array.sol b/test/libsolidity/syntaxTests/iceRegressionTests/memory_mapping_array.sol new file mode 100644 index 000000000..a9a574132 --- /dev/null +++ b/test/libsolidity/syntaxTests/iceRegressionTests/memory_mapping_array.sol @@ -0,0 +1,7 @@ + contract C { + function h ( bool flag ) public returns ( bool c ) { + mapping ( string => uint24 ) [ 1 ] memory val ; + } +} +// ---- +// TypeError: (91-136): Data location must be "storage" for variable, but "memory" was given. diff --git a/test/libsolidity/syntaxTests/iceRegressionTests/recursive_struct_memory.sol b/test/libsolidity/syntaxTests/iceRegressionTests/recursive_struct_memory.sol new file mode 100644 index 000000000..fdb652b06 --- /dev/null +++ b/test/libsolidity/syntaxTests/iceRegressionTests/recursive_struct_memory.sol @@ -0,0 +1,13 @@ +contract Test { + struct RecursiveStruct { + address payable d ; + mapping ( uint => address payable ) c ; + mapping ( uint => address payable [ ] ) d ; + } + function func ( ) private pure { + RecursiveStruct [ 1 ] memory val ; + val ; + } +} +// ---- +// DeclarationError: (157-198): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/immutable/double_specifier.sol b/test/libsolidity/syntaxTests/immutable/double_specifier.sol index 39f0f1e76..fec0f823b 100644 --- a/test/libsolidity/syntaxTests/immutable/double_specifier.sol +++ b/test/libsolidity/syntaxTests/immutable/double_specifier.sol @@ -3,5 +3,5 @@ contract C { uint immutable constant x; } // ---- -// ParserError: (32-41): Constantness already set to "immutable" -// ParserError: (64-72): Constantness already set to "immutable" +// ParserError: (32-41): Mutability already set to "immutable" +// ParserError: (64-72): Mutability already set to "immutable" diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/150_array_with_nonconstant_length.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/150_array_with_nonconstant_length.sol index 49a1851c0..678e7e42b 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/150_array_with_nonconstant_length.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/150_array_with_nonconstant_length.sol @@ -3,3 +3,4 @@ contract c { } // ---- // TypeError: (51-52): Invalid array length, expected integer literal or constant expression. +// TypeError: (45-55): Data location must be "storage" or "memory" for variable, but none was given. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/151_array_with_negative_length.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/151_array_with_negative_length.sol index b87160b0f..ab736e7da 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/151_array_with_negative_length.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/151_array_with_negative_length.sol @@ -3,3 +3,4 @@ contract c { } // ---- // TypeError: (51-53): Array with negative length specified. +// TypeError: (45-56): Data location must be "storage" or "memory" for variable, but none was given. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/207_no_mappings_in_memory_array.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/207_no_mappings_in_memory_array.sol index 5220ee22b..cd2d2f2fc 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/207_no_mappings_in_memory_array.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/207_no_mappings_in_memory_array.sol @@ -4,4 +4,4 @@ contract C { } } // ---- -// TypeError: (47-77): Type mapping(uint256 => uint256)[] memory is only valid in storage. +// TypeError: (47-77): Data location must be "storage" for variable, but "memory" was given. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/318_invalid_array_declaration_with_rational.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/318_invalid_array_declaration_with_rational.sol index 3dd779ec3..7e18c5fe7 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/318_invalid_array_declaration_with_rational.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/318_invalid_array_declaration_with_rational.sol @@ -5,3 +5,4 @@ contract test { } // ---- // TypeError: (55-58): Array with fractional length specified. +// TypeError: (50-61): Data location must be "storage" or "memory" for variable, but none was given. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/319_invalid_array_declaration_with_signed_fixed_type.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/319_invalid_array_declaration_with_signed_fixed_type.sol index 83f0950db..8aef0ac23 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/319_invalid_array_declaration_with_signed_fixed_type.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/319_invalid_array_declaration_with_signed_fixed_type.sol @@ -5,3 +5,4 @@ contract test { } // ---- // TypeError: (55-65): Invalid array length, expected integer literal or constant expression. +// TypeError: (50-68): Data location must be "storage" or "memory" for variable, but none was given. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/320_invalid_array_declaration_with_unsigned_fixed_type.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/320_invalid_array_declaration_with_unsigned_fixed_type.sol index 26d5a85e9..dfd145f8e 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/320_invalid_array_declaration_with_unsigned_fixed_type.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/320_invalid_array_declaration_with_unsigned_fixed_type.sol @@ -5,3 +5,4 @@ contract test { } // ---- // TypeError: (55-66): Invalid array length, expected integer literal or constant expression. +// TypeError: (50-69): Data location must be "storage" or "memory" for variable, but none was given. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/476_too_large_arrays_for_calldata_external.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/476_too_large_arrays_for_calldata_external.sol index 78c38aaf2..80bb632c9 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/476_too_large_arrays_for_calldata_external.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/476_too_large_arrays_for_calldata_external.sol @@ -3,4 +3,4 @@ contract C { } } // ---- -// TypeError: (28-56): Array is too large to be encoded. +// TypeError: (28-56): Type too large for calldata. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/477_too_large_arrays_for_calldata_internal.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/477_too_large_arrays_for_calldata_internal.sol index 7578246ee..5159f57ea 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/477_too_large_arrays_for_calldata_internal.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/477_too_large_arrays_for_calldata_internal.sol @@ -3,4 +3,4 @@ contract C { } } // ---- -// TypeError: (28-54): Array is too large to be encoded. +// TypeError: (28-54): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/478_too_large_arrays_for_calldata_public.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/478_too_large_arrays_for_calldata_public.sol index 2831b6fbb..de42bad1e 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/478_too_large_arrays_for_calldata_public.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/478_too_large_arrays_for_calldata_public.sol @@ -3,4 +3,4 @@ contract C { } } // ---- -// TypeError: (28-54): Array is too large to be encoded. +// TypeError: (28-54): Type too large for memory. diff --git a/test/libsolidity/syntaxTests/parsing/address_invalid_state_mutability.sol b/test/libsolidity/syntaxTests/parsing/address_invalid_state_mutability.sol index 606f5cd0e..4d4a5676f 100644 --- a/test/libsolidity/syntaxTests/parsing/address_invalid_state_mutability.sol +++ b/test/libsolidity/syntaxTests/parsing/address_invalid_state_mutability.sol @@ -18,9 +18,9 @@ contract C { // TypeError: (33-45): Address types can only be payable or non-payable. // TypeError: (52-64): Address types can only be payable or non-payable. // TypeError: (89-101): Address types can only be payable or non-payable. +// TypeError: (138-150): Address types can only be payable or non-payable. +// TypeError: (156-168): Address types can only be payable or non-payable. // TypeError: (195-207): Address types can only be payable or non-payable. // TypeError: (236-248): Address types can only be payable or non-payable. // TypeError: (300-312): Address types can only be payable or non-payable. // TypeError: (352-364): Address types can only be payable or non-payable. -// TypeError: (138-150): Address types can only be payable or non-payable. -// TypeError: (156-168): Address types can only be payable or non-payable. diff --git a/test/libsolidity/syntaxTests/specialFunctions/functionCallOptions_err.sol b/test/libsolidity/syntaxTests/specialFunctions/functionCallOptions_err.sol new file mode 100644 index 000000000..5ad8c4e5f --- /dev/null +++ b/test/libsolidity/syntaxTests/specialFunctions/functionCallOptions_err.sol @@ -0,0 +1,11 @@ +contract C { + function f() public payable { + abi.encode(this.f{value: 2}); + abi.encode(this.f{gas: 2}); + abi.encode(this.f{value: 2, gas: 1}); + } +} +// ---- +// TypeError: (60-76): This type cannot be encoded. +// TypeError: (92-106): This type cannot be encoded. +// TypeError: (122-146): This type cannot be encoded. diff --git a/test/libsolidity/syntaxTests/specialFunctions/types_with_unspecified_encoding_internal_functions.sol b/test/libsolidity/syntaxTests/specialFunctions/types_with_unspecified_encoding_internal_functions.sol index ea11703f9..eac453939 100644 --- a/test/libsolidity/syntaxTests/specialFunctions/types_with_unspecified_encoding_internal_functions.sol +++ b/test/libsolidity/syntaxTests/specialFunctions/types_with_unspecified_encoding_internal_functions.sol @@ -1,12 +1,11 @@ contract C { function f() public pure { - bytes32 h = keccak256(abi.encodePacked(keccak256, f, this.f.gas, blockhash)); + bytes32 h = keccak256(abi.encodePacked(keccak256, f, this.f{gas: 2}, blockhash)); h; } } // ---- -// Warning: (105-115): Using ".gas(...)" is deprecated. Use "{gas: ...}" instead. // TypeError: (91-100): This type cannot be encoded. // TypeError: (102-103): This type cannot be encoded. -// TypeError: (105-115): This type cannot be encoded. -// TypeError: (117-126): This type cannot be encoded. +// TypeError: (105-119): This type cannot be encoded. +// TypeError: (121-130): This type cannot be encoded. diff --git a/test/libsolidity/syntaxTests/structs/member_type_eq_name.sol b/test/libsolidity/syntaxTests/structs/member_type_eq_name.sol index 50f0eb365..4aa5ad742 100644 --- a/test/libsolidity/syntaxTests/structs/member_type_eq_name.sol +++ b/test/libsolidity/syntaxTests/structs/member_type_eq_name.sol @@ -4,4 +4,3 @@ contract C { } // ---- // TypeError: (25-26): Name has to refer to a struct, enum or contract. -// TypeError: (53-61): Internal type cannot be used for external function type. diff --git a/test/libsolidity/syntaxTests/structs/member_type_func.sol b/test/libsolidity/syntaxTests/structs/member_type_func.sol index 709a40e26..1a47c1996 100644 --- a/test/libsolidity/syntaxTests/structs/member_type_func.sol +++ b/test/libsolidity/syntaxTests/structs/member_type_func.sol @@ -5,4 +5,3 @@ contract C { } // ---- // TypeError: (50-51): Name has to refer to a struct, enum or contract. -// TypeError: (78-86): Internal type cannot be used for external function type. diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol index 6e1ad1cf3..573f22744 100644 --- a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_forward_reference.sol @@ -4,7 +4,7 @@ contract C { function f(Data.S memory a) public {} } contract Data { - struct S { S x; } + struct S { S[] x; } } // ---- // TypeError: (63-78): Recursive type not allowed for public or external contract functions. diff --git a/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_function_pointer.sol b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_function_pointer.sol new file mode 100644 index 000000000..dc40ae3b2 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/recursive_struct_function_pointer.sol @@ -0,0 +1,10 @@ +pragma experimental ABIEncoderV2; +contract C { + struct S { + uint a; + function() external returns (S memory) sub; + } + function f() public pure returns (S memory) { + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/structs/struct_var_member.sol b/test/libsolidity/syntaxTests/structs/struct_var_member.sol new file mode 100644 index 000000000..04a274bcb --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/struct_var_member.sol @@ -0,0 +1,7 @@ +contract C { + struct S { + var x; + } +} +// ---- +// ParserError: (27-30): Expected explicit type name. diff --git a/test/libsolidity/syntaxTests/tupleAssignments/tuple_in_tuple_long.sol b/test/libsolidity/syntaxTests/tupleAssignments/tuple_in_tuple_long.sol index 2fa48cfb1..f5d8fb1ba 100644 --- a/test/libsolidity/syntaxTests/tupleAssignments/tuple_in_tuple_long.sol +++ b/test/libsolidity/syntaxTests/tupleAssignments/tuple_in_tuple_long.sol @@ -1,12 +1,11 @@ contract C { - function f() { + function f() public { (((((((((((,2),)),)),),))=4))); } } // ---- -// SyntaxError: (15-69): No visibility specified. Did you intend to add "public"? -// TypeError: (46-47): Expression has to be an lvalue. -// TypeError: (60-61): Type int_const 4 is not implicitly convertible to expected type tuple(tuple(tuple(tuple(tuple(,int_const 2),),),),). -// TypeError: (37-61): Tuple component cannot be empty. -// TypeError: (36-62): Tuple component cannot be empty. -// TypeError: (35-63): Tuple component cannot be empty. +// TypeError: (53-54): Expression has to be an lvalue. +// TypeError: (67-68): Type int_const 4 is not implicitly convertible to expected type tuple(tuple(tuple(tuple(tuple(,int_const 2),),),),). +// TypeError: (44-68): Tuple component cannot be empty. +// TypeError: (43-69): Tuple component cannot be empty. +// TypeError: (42-70): Tuple component cannot be empty. diff --git a/test/libsolidity/syntaxTests/types/cyclic_dependency_check_on_struct_exhausted.sol b/test/libsolidity/syntaxTests/types/cyclic_dependency_check_on_struct_exhausted.sol index db0ff4af6..027db9754 100644 --- a/test/libsolidity/syntaxTests/types/cyclic_dependency_check_on_struct_exhausted.sol +++ b/test/libsolidity/syntaxTests/types/cyclic_dependency_check_on_struct_exhausted.sol @@ -257,4 +257,4 @@ contract Main { struct JW { int i; } } // ---- -// DeclarationError: (6091-6111): Struct definition exhausting cyclic dependency validator. +// DeclarationError: (6091-6111): Struct definition exhausts cyclic dependency validator. diff --git a/test/libsolidity/syntaxTests/types/global_struct_recursive.sol b/test/libsolidity/syntaxTests/types/global_struct_recursive.sol new file mode 100644 index 000000000..dc4becaae --- /dev/null +++ b/test/libsolidity/syntaxTests/types/global_struct_recursive.sol @@ -0,0 +1,8 @@ +struct s1 { s2 x; } +struct s2 { s1 y; } + +contract C { + // whatever +} +// ---- +// TypeError: (0-19): Recursive struct definition. diff --git a/test/libsolidity/syntaxTests/types/mapping/function_type_argument_external.sol b/test/libsolidity/syntaxTests/types/mapping/function_type_argument_external.sol index 34f957019..8638baf85 100644 --- a/test/libsolidity/syntaxTests/types/mapping/function_type_argument_external.sol +++ b/test/libsolidity/syntaxTests/types/mapping/function_type_argument_external.sol @@ -4,4 +4,3 @@ contract C { } // ---- // TypeError: (37-64): Data location must be "memory" for parameter in function, but "storage" was given. -// TypeError: (37-64): Internal type cannot be used for external function type. diff --git a/test/libsolidity/syntaxTests/types/mapping/function_type_return_external.sol b/test/libsolidity/syntaxTests/types/mapping/function_type_return_external.sol index aed9b3878..b9bd5bc3f 100644 --- a/test/libsolidity/syntaxTests/types/mapping/function_type_return_external.sol +++ b/test/libsolidity/syntaxTests/types/mapping/function_type_return_external.sol @@ -4,4 +4,3 @@ contract C { } // ---- // TypeError: (57-84): Data location must be "memory" for return parameter in function, but "storage" was given. -// TypeError: (57-84): Internal type cannot be used for external function type. diff --git a/test/libsolidity/syntaxTests/types/var_decl_val_mismatch.sol b/test/libsolidity/syntaxTests/types/var_decl_val_mismatch.sol index fbefafa61..08e0005c2 100644 --- a/test/libsolidity/syntaxTests/types/var_decl_val_mismatch.sol +++ b/test/libsolidity/syntaxTests/types/var_decl_val_mismatch.sol @@ -1,6 +1,6 @@ contract n { - fallback() + fallback() external { // Used to cause a segfault var (x,y) = (1); @@ -12,5 +12,4 @@ contract n } } // ---- -// SyntaxError: (14-129): No visibility specified. Did you intend to add "external"? -// TypeError: (60-75): Different number of components on the left hand side (2) than on the right hand side (1). +// TypeError: (69-84): Different number of components on the left hand side (2) than on the right hand side (1). diff --git a/test/libsolutil/StringUtils.cpp b/test/libsolutil/StringUtils.cpp index b7e653a0e..e50260e88 100644 --- a/test/libsolutil/StringUtils.cpp +++ b/test/libsolutil/StringUtils.cpp @@ -76,13 +76,13 @@ BOOST_AUTO_TEST_CASE(test_alternatives_list) { vector strings; BOOST_CHECK_EQUAL(quotedAlternativesList(strings), ""); - strings.push_back("a"); + strings.emplace_back("a"); BOOST_CHECK_EQUAL(quotedAlternativesList(strings), "\"a\""); - strings.push_back("b"); + strings.emplace_back("b"); BOOST_CHECK_EQUAL(quotedAlternativesList(strings), "\"a\" or \"b\""); - strings.push_back("c"); + strings.emplace_back("c"); BOOST_CHECK_EQUAL(quotedAlternativesList(strings), "\"a\", \"b\" or \"c\""); - strings.push_back("d"); + strings.emplace_back("d"); BOOST_CHECK_EQUAL(quotedAlternativesList(strings), "\"a\", \"b\", \"c\" or \"d\""); } diff --git a/test/libyul/FunctionSideEffects.cpp b/test/libyul/FunctionSideEffects.cpp index db3a39df0..3d3461b0e 100644 --- a/test/libyul/FunctionSideEffects.cpp +++ b/test/libyul/FunctionSideEffects.cpp @@ -47,15 +47,15 @@ string toString(SideEffects const& _sideEffects) { vector ret; if (_sideEffects.movable) - ret.push_back("movable"); + ret.emplace_back("movable"); if (_sideEffects.sideEffectFree) - ret.push_back("sideEffectFree"); + ret.emplace_back("sideEffectFree"); if (_sideEffects.sideEffectFreeIfNoMSize) - ret.push_back("sideEffectFreeIfNoMSize"); + ret.emplace_back("sideEffectFreeIfNoMSize"); if (_sideEffects.invalidatesStorage) - ret.push_back("invalidatesStorage"); + ret.emplace_back("invalidatesStorage"); if (_sideEffects.invalidatesMemory) - ret.push_back("invalidatesMemory"); + ret.emplace_back("invalidatesMemory"); return joinHumanReadable(ret); } } diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index 93b528721..7b8e56119 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -539,7 +539,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis) { return _name == "builtin"_yulstring ? &f : nullptr; } - BuiltinFunction f{"builtin"_yulstring, vector(2), vector(3), {}, {}}; + BuiltinFunction f{"builtin"_yulstring, vector(2), vector(3), {}, {}, false, {}}; }; SimpleDialect dialect; diff --git a/test/tools/afl_fuzzer.cpp b/test/tools/afl_fuzzer.cpp index eff4bc55a..81c3a2bfc 100644 --- a/test/tools/afl_fuzzer.cpp +++ b/test/tools/afl_fuzzer.cpp @@ -104,7 +104,7 @@ Allowed options)", else if (arguments.count("input-files")) inputs = arguments["input-files"].as>(); else - inputs.push_back(""); + inputs.emplace_back(""); bool optimize = !arguments.count("without-optimizer"); int retResult = 0; diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index ad689ffcf..7a3c63cff 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -31,10 +31,11 @@ #include #include -#include #include +#include #include #include +#include #if defined(_WIN32) #include @@ -71,7 +72,7 @@ struct TestStats class TestFilter { public: - explicit TestFilter(string const& _filter): m_filter(_filter) + explicit TestFilter(string _filter): m_filter(std::move(_filter)) { string filter{m_filter}; @@ -97,14 +98,14 @@ public: TestTool( TestCreator _testCaseCreator, TestOptions const& _options, - fs::path const& _path, - string const& _name + fs::path _path, + string _name ): m_testCaseCreator(_testCaseCreator), m_options(_options), m_filter(TestFilter{_options.testFilter}), - m_path(_path), - m_name(_name) + m_path(std::move(_path)), + m_name(std::move(_name)) {} enum class Result diff --git a/test/tools/ossfuzz/abiV2FuzzerCommon.cpp b/test/tools/ossfuzz/abiV2FuzzerCommon.cpp index f30a8517a..c9109ecdb 100644 --- a/test/tools/ossfuzz/abiV2FuzzerCommon.cpp +++ b/test/tools/ossfuzz/abiV2FuzzerCommon.cpp @@ -9,13 +9,16 @@ SolidityCompilationFramework::SolidityCompilationFramework(langutil::EVMVersion solidity::bytes SolidityCompilationFramework::compileContract( std::string const& _sourceCode, - std::string const& _contractName + std::string const& _contractName, + std::map const& _libraryAddresses, + frontend::OptimiserSettings _optimization ) { std::string sourceCode = _sourceCode; m_compiler.setSources({{"", sourceCode}}); + m_compiler.setLibraries(_libraryAddresses); m_compiler.setEVMVersion(m_evmVersion); - m_compiler.setOptimiserSettings(m_optimiserSettings); + m_compiler.setOptimiserSettings(_optimization); if (!m_compiler.compile()) { langutil::SourceReferenceFormatter formatter(std::cerr); diff --git a/test/tools/ossfuzz/abiV2FuzzerCommon.h b/test/tools/ossfuzz/abiV2FuzzerCommon.h index 3094fbdc1..e18458b77 100644 --- a/test/tools/ossfuzz/abiV2FuzzerCommon.h +++ b/test/tools/ossfuzz/abiV2FuzzerCommon.h @@ -23,12 +23,13 @@ public: } bytes compileContract( std::string const& _sourceCode, - std::string const& _contractName = {} + std::string const& _contractName, + std::map const& _libraryAddresses = {}, + frontend::OptimiserSettings _optimization = frontend::OptimiserSettings::minimal() ); protected: frontend::CompilerStack m_compiler; langutil::EVMVersion m_evmVersion; - frontend::OptimiserSettings m_optimiserSettings = frontend::OptimiserSettings::none(); }; } diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index cbfdbbe52..6f989b873 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -308,7 +308,7 @@ pair< for (auto const& scope: m_scopes) { // Copy over all functions. - newScopes.push_back({}); + newScopes.emplace_back(); for (auto const& [name, funDef]: scope) if (funDef) newScopes.back().emplace(name, funDef); diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index ab0386165..f50cbe6c4 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -136,7 +136,7 @@ private: /// Evaluates the expression and returns its value. std::vector evaluateMulti(Expression const& _expression); - void openScope() { m_scopes.push_back({}); } + void openScope() { m_scopes.emplace_back(); } /// Unregisters variables and functions. void closeScope(); diff --git a/test/yulPhaser/GeneticAlgorithms.cpp b/test/yulPhaser/GeneticAlgorithms.cpp index f7a5e7a92..1b902d978 100644 --- a/test/yulPhaser/GeneticAlgorithms.cpp +++ b/test/yulPhaser/GeneticAlgorithms.cpp @@ -31,6 +31,7 @@ using namespace std; using namespace boost::unit_test::framework; using namespace boost::test_tools; +using namespace solidity::util; namespace solidity::phaser::test { @@ -41,6 +42,18 @@ protected: shared_ptr m_fitnessMetric = make_shared(); }; +class ClassicGeneticAlgorithmFixture: public GeneticAlgorithmFixture +{ +protected: + ClassicGeneticAlgorithm::Options m_options = { + /* elitePoolSize = */ 0.0, + /* crossoverChance = */ 0.0, + /* mutationChance = */ 0.0, + /* deletionChance = */ 0.0, + /* additionChance = */ 0.0, + }; +}; + BOOST_AUTO_TEST_SUITE(Phaser) BOOST_AUTO_TEST_SUITE(GeneticAlgorithmsTest) BOOST_AUTO_TEST_SUITE(RandomAlgorithmTest) @@ -186,6 +199,197 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_generate_individuals_in_the_crossove })); } +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(ClassicGeneticAlgorithmTest) + +BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_individuals_with_probability_proportional_to_fitness, ClassicGeneticAlgorithmFixture) +{ + constexpr double relativeTolerance = 0.1; + constexpr size_t populationSize = 1000; + assert(populationSize % 4 == 0 && "Choose a number divisible by 4 for this test"); + + auto population = + Population::makeRandom(m_fitnessMetric, populationSize / 4, 0, 0) + + Population::makeRandom(m_fitnessMetric, populationSize / 4, 1, 1) + + Population::makeRandom(m_fitnessMetric, populationSize / 4, 2, 2) + + Population::makeRandom(m_fitnessMetric, populationSize / 4, 3, 3); + + map expectedProbabilities = { + {0, 4.0 / (4 + 3 + 2 + 1)}, + {1, 3.0 / (4 + 3 + 2 + 1)}, + {2, 2.0 / (4 + 3 + 2 + 1)}, + {3, 1.0 / (4 + 3 + 2 + 1)}, + }; + double const expectedValue = ( + 0.0 * expectedProbabilities[0] + + 1.0 * expectedProbabilities[1] + + 2.0 * expectedProbabilities[2] + + 3.0 * expectedProbabilities[3] + ); + double const variance = ( + (0.0 - expectedValue) * (0.0 - expectedValue) * expectedProbabilities[0] + + (1.0 - expectedValue) * (1.0 - expectedValue) * expectedProbabilities[1] + + (2.0 - expectedValue) * (2.0 - expectedValue) * expectedProbabilities[2] + + (3.0 - expectedValue) * (3.0 - expectedValue) * expectedProbabilities[3] + ); + + ClassicGeneticAlgorithm algorithm(m_options); + Population newPopulation = algorithm.runNextRound(population); + + BOOST_TEST(newPopulation.individuals().size() == population.individuals().size()); + + vector newFitness = chromosomeLengths(newPopulation); + BOOST_TEST(abs(mean(newFitness) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(newFitness, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_only_individuals_existing_in_the_original_population, ClassicGeneticAlgorithmFixture) +{ + constexpr size_t populationSize = 1000; + auto population = Population::makeRandom(m_fitnessMetric, populationSize, 1, 10); + + set originalSteps; + for (auto const& individual: population.individuals()) + originalSteps.insert(toString(individual.chromosome)); + + ClassicGeneticAlgorithm algorithm(m_options); + Population newPopulation = algorithm.runNextRound(population); + + for (auto const& individual: newPopulation.individuals()) + BOOST_TEST(originalSteps.count(toString(individual.chromosome)) == 1); +} + +BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_crossover, ClassicGeneticAlgorithmFixture) +{ + auto population = Population(m_fitnessMetric, { + Chromosome("aa"), Chromosome("aa"), Chromosome("aa"), + Chromosome("ff"), Chromosome("ff"), Chromosome("ff"), + Chromosome("gg"), Chromosome("gg"), Chromosome("gg"), + }); + + set originalSteps{"aa", "ff", "gg"}; + set crossedSteps{"af", "fa", "fg", "gf", "ga", "ag"}; + + m_options.crossoverChance = 0.8; + ClassicGeneticAlgorithm algorithm(m_options); + + SimulationRNG::reset(1); + Population newPopulation = algorithm.runNextRound(population); + + size_t totalCrossed = 0; + size_t totalUnchanged = 0; + for (auto const& individual: newPopulation.individuals()) + { + totalCrossed += crossedSteps.count(toString(individual.chromosome)); + totalUnchanged += originalSteps.count(toString(individual.chromosome)); + } + BOOST_TEST(totalCrossed + totalUnchanged == newPopulation.individuals().size()); + BOOST_TEST(totalCrossed >= 2); +} + +BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_mutation, ClassicGeneticAlgorithmFixture) +{ + m_options.mutationChance = 0.6; + ClassicGeneticAlgorithm algorithm(m_options); + + constexpr size_t populationSize = 1000; + constexpr double relativeTolerance = 0.05; + double const expectedValue = m_options.mutationChance; + double const variance = m_options.mutationChance * (1 - m_options.mutationChance); + + Chromosome chromosome("aaaaaaaaaa"); + vector chromosomes(populationSize, chromosome); + Population population(m_fitnessMetric, chromosomes); + + SimulationRNG::reset(1); + Population newPopulation = algorithm.runNextRound(population); + + vector bernoulliTrials; + for (auto const& individual: newPopulation.individuals()) + { + string steps = toString(individual.chromosome); + for (char step: steps) + bernoulliTrials.push_back(static_cast(step != 'a')); + } + + BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_deletion, ClassicGeneticAlgorithmFixture) +{ + m_options.deletionChance = 0.6; + ClassicGeneticAlgorithm algorithm(m_options); + + constexpr size_t populationSize = 1000; + constexpr double relativeTolerance = 0.05; + double const expectedValue = m_options.deletionChance; + double const variance = m_options.deletionChance * (1 - m_options.deletionChance); + + Chromosome chromosome("aaaaaaaaaa"); + vector chromosomes(populationSize, chromosome); + Population population(m_fitnessMetric, chromosomes); + + SimulationRNG::reset(1); + Population newPopulation = algorithm.runNextRound(population); + + vector bernoulliTrials; + for (auto const& individual: newPopulation.individuals()) + { + string steps = toString(individual.chromosome); + for (size_t i = 0; i < chromosome.length(); ++i) + bernoulliTrials.push_back(static_cast(i >= steps.size())); + } + + BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_addition, ClassicGeneticAlgorithmFixture) +{ + m_options.additionChance = 0.6; + ClassicGeneticAlgorithm algorithm(m_options); + + constexpr size_t populationSize = 1000; + constexpr double relativeTolerance = 0.05; + double const expectedValue = m_options.additionChance; + double const variance = m_options.additionChance * (1 - m_options.additionChance); + + Chromosome chromosome("aaaaaaaaaa"); + vector chromosomes(populationSize, chromosome); + Population population(m_fitnessMetric, chromosomes); + + SimulationRNG::reset(1); + Population newPopulation = algorithm.runNextRound(population); + + vector bernoulliTrials; + for (auto const& individual: newPopulation.individuals()) + { + string steps = toString(individual.chromosome); + for (size_t i = 0; i < chromosome.length() + 1; ++i) + { + BOOST_REQUIRE(chromosome.length() <= steps.size() && steps.size() <= 2 * chromosome.length() + 1); + bernoulliTrials.push_back(static_cast(i < steps.size() - chromosome.length())); + } + } + + BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_FIXTURE_TEST_CASE(runNextRound_should_preserve_elite, ClassicGeneticAlgorithmFixture) +{ + auto population = Population::makeRandom(m_fitnessMetric, 4, 3, 3) + Population::makeRandom(m_fitnessMetric, 6, 5, 5); + assert((chromosomeLengths(population) == vector{3, 3, 3, 3, 5, 5, 5, 5, 5, 5})); + + m_options.elitePoolSize = 0.5; + m_options.deletionChance = 1.0; + ClassicGeneticAlgorithm algorithm(m_options); + Population newPopulation = algorithm.runNextRound(population); + + BOOST_TEST((chromosomeLengths(newPopulation) == vector{0, 0, 0, 0, 0, 3, 3, 3, 3, 5})); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/test/yulPhaser/Mutations.cpp b/test/yulPhaser/Mutations.cpp index df58cec54..33c623f84 100644 --- a/test/yulPhaser/Mutations.cpp +++ b/test/yulPhaser/Mutations.cpp @@ -212,6 +212,39 @@ BOOST_AUTO_TEST_CASE(alternativeMutations_should_always_choose_second_mutation_i BOOST_TEST(mutation(chromosome) == Chromosome("f")); } +BOOST_AUTO_TEST_CASE(mutationSequence_should_apply_all_mutations) +{ + Chromosome chromosome("aaaaa"); + function mutation = mutationSequence({ + geneSubstitution(3, Chromosome("g").optimisationSteps()[0]), + geneSubstitution(2, Chromosome("f").optimisationSteps()[0]), + geneSubstitution(1, Chromosome("c").optimisationSteps()[0]), + }); + + BOOST_TEST(mutation(chromosome) == Chromosome("acfga")); +} + +BOOST_AUTO_TEST_CASE(mutationSequence_apply_mutations_in_the_order_they_are_given) +{ + Chromosome chromosome("aa"); + function mutation = mutationSequence({ + geneSubstitution(0, Chromosome("g").optimisationSteps()[0]), + geneSubstitution(1, Chromosome("c").optimisationSteps()[0]), + geneSubstitution(0, Chromosome("f").optimisationSteps()[0]), + geneSubstitution(1, Chromosome("o").optimisationSteps()[0]), + }); + + BOOST_TEST(mutation(chromosome) == Chromosome("fo")); +} + +BOOST_AUTO_TEST_CASE(mutationSequence_should_return_unmodified_chromosome_if_given_no_mutations) +{ + Chromosome chromosome("aa"); + function mutation = mutationSequence({}); + + BOOST_TEST(mutation(chromosome) == chromosome); +} + BOOST_AUTO_TEST_CASE(randomPointCrossover_should_swap_chromosome_parts_at_random_point) { function crossover = randomPointCrossover(); @@ -225,6 +258,20 @@ BOOST_AUTO_TEST_CASE(randomPointCrossover_should_swap_chromosome_parts_at_random BOOST_TEST(result2 == Chromosome("cccaaaaaaa")); } +BOOST_AUTO_TEST_CASE(symmetricRandomPointCrossover_should_swap_chromosome_parts_at_random_point) +{ + function crossover = symmetricRandomPointCrossover(); + + SimulationRNG::reset(1); + tuple result1 = crossover(Chromosome("aaaaaaaaaa"), Chromosome("cccccc")); + tuple expectedPair1 = {Chromosome("aaaccc"), Chromosome("cccaaaaaaa")}; + BOOST_TEST(result1 == expectedPair1); + + tuple result2 = crossover(Chromosome("cccccc"), Chromosome("aaaaaaaaaa")); + tuple expectedPair2 = {Chromosome("ccccccaaaa"), Chromosome("aaaaaa")}; + BOOST_TEST(result2 == expectedPair2); +} + BOOST_AUTO_TEST_CASE(randomPointCrossover_should_only_consider_points_available_on_both_chromosomes) { SimulationRNG::reset(1); diff --git a/test/yulPhaser/PairSelections.cpp b/test/yulPhaser/PairSelections.cpp index 64109470f..62a4dd4bc 100644 --- a/test/yulPhaser/PairSelections.cpp +++ b/test/yulPhaser/PairSelections.cpp @@ -119,6 +119,78 @@ BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_collection_has_one_el BOOST_TEST(RandomPairSelection(2.0).materialise(1).empty()); } +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(PairsFromRandomSubsetTest) + +BOOST_AUTO_TEST_CASE(materialise_should_return_random_values_with_equal_probabilities) +{ + constexpr int collectionSize = 1000; + constexpr double selectionChance = 0.7; + constexpr double relativeTolerance = 0.001; + constexpr double expectedValue = selectionChance; + constexpr double variance = selectionChance * (1 - selectionChance); + + SimulationRNG::reset(1); + vector> pairs = PairsFromRandomSubset(selectionChance).materialise(collectionSize); + vector bernoulliTrials(collectionSize, 0); + for (auto& pair: pairs) + { + BOOST_REQUIRE(get<1>(pair) < collectionSize); + BOOST_REQUIRE(get<1>(pair) < collectionSize); + bernoulliTrials[get<0>(pair)] = 1.0; + bernoulliTrials[get<1>(pair)] = 1.0; + } + + BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_only_values_that_can_be_used_as_collection_indices) +{ + const size_t collectionSize = 200; + constexpr double selectionChance = 0.5; + + vector> pairs = PairsFromRandomSubset(selectionChance).materialise(collectionSize); + + BOOST_TEST(all_of(pairs.begin(), pairs.end(), [&](auto const& pair){ return get<0>(pair) <= collectionSize; })); + BOOST_TEST(all_of(pairs.begin(), pairs.end(), [&](auto const& pair){ return get<1>(pair) <= collectionSize; })); +} + +BOOST_AUTO_TEST_CASE(materialise_should_use_unique_indices) +{ + constexpr size_t collectionSize = 200; + constexpr double selectionChance = 0.5; + + vector> pairs = PairsFromRandomSubset(selectionChance).materialise(collectionSize); + set indices; + for (auto& pair: pairs) + { + indices.insert(get<0>(pair)); + indices.insert(get<1>(pair)); + } + + BOOST_TEST(indices.size() == 2 * pairs.size()); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_collection_is_empty) +{ + BOOST_TEST(PairsFromRandomSubset(0.0).materialise(0).empty()); + BOOST_TEST(PairsFromRandomSubset(0.5).materialise(0).empty()); + BOOST_TEST(PairsFromRandomSubset(1.0).materialise(0).empty()); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_selection_chance_is_zero) +{ + BOOST_TEST(PairsFromRandomSubset(0.0).materialise(0).empty()); + BOOST_TEST(PairsFromRandomSubset(0.0).materialise(100).empty()); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_all_pairs_if_selection_chance_is_one) +{ + BOOST_TEST(PairsFromRandomSubset(1.0).materialise(0).empty()); + BOOST_TEST(PairsFromRandomSubset(1.0).materialise(100).size() == 50); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(PairMosaicSelectionTest) diff --git a/test/yulPhaser/Phaser.cpp b/test/yulPhaser/Phaser.cpp index 633462865..c365ce310 100644 --- a/test/yulPhaser/Phaser.cpp +++ b/test/yulPhaser/Phaser.cpp @@ -52,6 +52,11 @@ protected: /* gewepDeletionVsAdditionChance = */ 0.3, /* gewepGenesToRandomise = */ 0.4, /* gewepGenesToAddOrDelete = */ 0.2, + /* classicElitePoolSize = */ 0.0, + /* classicCrossoverChance = */ 0.75, + /* classicMutationChance = */ 0.2, + /* classicDeletionChance = */ 0.2, + /* classicAdditionChance = */ 0.2, }; }; @@ -122,6 +127,18 @@ BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_opt BOOST_TEST(gewepAlgorithm->options().deletionVsAdditionChance == m_options.gewepDeletionVsAdditionChance); BOOST_TEST(gewepAlgorithm->options().percentGenesToRandomise == m_options.gewepGenesToRandomise.value()); BOOST_TEST(gewepAlgorithm->options().percentGenesToAddOrDelete == m_options.gewepGenesToAddOrDelete.value()); + + m_options.algorithm = Algorithm::Classic; + unique_ptr algorithm3 = GeneticAlgorithmFactory::build(m_options, 100); + BOOST_REQUIRE(algorithm3 != nullptr); + + auto classicAlgorithm = dynamic_cast(algorithm3.get()); + BOOST_REQUIRE(classicAlgorithm != nullptr); + BOOST_TEST(classicAlgorithm->options().elitePoolSize == m_options.classicElitePoolSize); + BOOST_TEST(classicAlgorithm->options().crossoverChance == m_options.classicCrossoverChance); + BOOST_TEST(classicAlgorithm->options().mutationChance == m_options.classicMutationChance); + BOOST_TEST(classicAlgorithm->options().deletionChance == m_options.classicDeletionChance); + BOOST_TEST(classicAlgorithm->options().additionChance == m_options.classicAdditionChance); } BOOST_FIXTURE_TEST_CASE(build_should_set_random_algorithm_elite_pool_size_based_on_population_size_if_not_specified, GeneticAlgorithmFactoryFixture) diff --git a/test/yulPhaser/Population.cpp b/test/yulPhaser/Population.cpp index 7a9172749..1657e97c9 100644 --- a/test/yulPhaser/Population.cpp +++ b/test/yulPhaser/Population.cpp @@ -48,6 +48,14 @@ namespace solidity::phaser::test class PopulationFixture { protected: + static ChromosomePair twoStepSwap(Chromosome const& _chromosome1, Chromosome const& _chromosome2) + { + return ChromosomePair{ + Chromosome(vector{_chromosome1.optimisationSteps()[0], _chromosome2.optimisationSteps()[1]}), + Chromosome(vector{_chromosome2.optimisationSteps()[0], _chromosome1.optimisationSteps()[1]}), + }; + } + shared_ptr m_fitnessMetric = make_shared(); }; @@ -104,6 +112,23 @@ BOOST_FIXTURE_TEST_CASE(constructor_should_copy_chromosomes_compute_fitness_and_ BOOST_TEST(individuals[2].chromosome == chromosomes[1]); } +BOOST_FIXTURE_TEST_CASE(constructor_should_accept_individuals_without_recalculating_fitness, PopulationFixture) +{ + vector customIndividuals = { + Individual(Chromosome("aaaccc"), 20), + Individual(Chromosome("aaa"), 10), + Individual(Chromosome("aaaf"), 30), + }; + assert(customIndividuals[0].fitness != m_fitnessMetric->evaluate(customIndividuals[0].chromosome)); + assert(customIndividuals[1].fitness != m_fitnessMetric->evaluate(customIndividuals[1].chromosome)); + assert(customIndividuals[2].fitness != m_fitnessMetric->evaluate(customIndividuals[2].chromosome)); + + Population population(m_fitnessMetric, customIndividuals); + + vector expectedIndividuals{customIndividuals[1], customIndividuals[0], customIndividuals[2]}; + BOOST_TEST(population.individuals() == expectedIndividuals); +} + BOOST_FIXTURE_TEST_CASE(makeRandom_should_get_chromosome_lengths_from_specified_generator, PopulationFixture) { size_t chromosomeCount = 30; @@ -292,6 +317,61 @@ BOOST_FIXTURE_TEST_CASE(crossover_should_return_empty_population_if_selection_is BOOST_TEST(population.crossover(selection, fixedPointCrossover(0.5)).individuals().empty()); } +BOOST_FIXTURE_TEST_CASE(symmetricCrossoverWithRemainder_should_return_crossed_population_and_remainder, PopulationFixture) +{ + Population population(m_fitnessMetric, {Chromosome("aa"), Chromosome("cc"), Chromosome("gg"), Chromosome("hh")}); + PairMosaicSelection selection({{2, 1}}, 0.25); + assert(selection.materialise(population.individuals().size()) == (vector>{{2, 1}})); + + Population expectedCrossedPopulation(m_fitnessMetric, {Chromosome("gc"), Chromosome("cg")}); + Population expectedRemainder(m_fitnessMetric, {Chromosome("aa"), Chromosome("hh")}); + + BOOST_TEST( + population.symmetricCrossoverWithRemainder(selection, twoStepSwap) == + (tuple{expectedCrossedPopulation, expectedRemainder}) + ); +} + +BOOST_FIXTURE_TEST_CASE(symmetricCrossoverWithRemainder_should_allow_crossing_the_same_individual_multiple_times, PopulationFixture) +{ + Population population(m_fitnessMetric, {Chromosome("aa"), Chromosome("cc"), Chromosome("gg"), Chromosome("hh")}); + PairMosaicSelection selection({{0, 0}, {2, 1}}, 1.0); + assert(selection.materialise(population.individuals().size()) == (vector>{{0, 0}, {2, 1}, {0, 0}, {2, 1}})); + + Population expectedCrossedPopulation(m_fitnessMetric, { + Chromosome("aa"), Chromosome("aa"), + Chromosome("aa"), Chromosome("aa"), + Chromosome("gc"), Chromosome("cg"), + Chromosome("gc"), Chromosome("cg"), + }); + Population expectedRemainder(m_fitnessMetric, {Chromosome("hh")}); + + BOOST_TEST( + population.symmetricCrossoverWithRemainder(selection, twoStepSwap) == + (tuple{expectedCrossedPopulation, expectedRemainder}) + ); +} + +BOOST_FIXTURE_TEST_CASE(symmetricCrossoverWithRemainder_should_return_empty_population_if_selection_is_empty, PopulationFixture) +{ + Population population(m_fitnessMetric, {Chromosome("aa"), Chromosome("cc")}); + PairMosaicSelection selection({}, 0.0); + assert(selection.materialise(population.individuals().size()).empty()); + + BOOST_TEST( + population.symmetricCrossoverWithRemainder(selection, twoStepSwap) == + (tuple{Population(m_fitnessMetric), population}) + ); +} + +BOOST_FIXTURE_TEST_CASE(combine_should_add_two_populations_from_a_pair, PopulationFixture) +{ + Population population1(m_fitnessMetric, {Chromosome("aa"), Chromosome("hh")}); + Population population2(m_fitnessMetric, {Chromosome("gg"), Chromosome("cc")}); + + BOOST_TEST(Population::combine({population1, population2}) == population1 + population2); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/test/yulPhaser/Selections.cpp b/test/yulPhaser/Selections.cpp index 02a85f4f3..d25766ae6 100644 --- a/test/yulPhaser/Selections.cpp +++ b/test/yulPhaser/Selections.cpp @@ -25,9 +25,11 @@ #include #include +#include #include using namespace std; +using namespace solidity::util; namespace solidity::phaser::test { @@ -199,6 +201,60 @@ BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_collection_is_empty BOOST_TEST(RandomSelection(2.0).materialise(0).empty()); } +BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(RandomSubsetTest) + +BOOST_AUTO_TEST_CASE(materialise_should_return_random_values_with_equal_probabilities) +{ + constexpr int collectionSize = 1000; + constexpr double selectionChance = 0.7; + constexpr double relativeTolerance = 0.001; + constexpr double expectedValue = selectionChance; + constexpr double variance = selectionChance * (1 - selectionChance); + + SimulationRNG::reset(1); + auto indices = convertContainer>(RandomSubset(selectionChance).materialise(collectionSize)); + + vector bernoulliTrials(collectionSize); + for (size_t i = 0; i < collectionSize; ++i) + bernoulliTrials[i] = indices.count(i); + + BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_only_values_that_can_be_used_as_collection_indices) +{ + const size_t collectionSize = 200; + vector indices = RandomSubset(0.5).materialise(collectionSize); + + BOOST_TEST(all_of(indices.begin(), indices.end(), [&](auto const& index){ return index <= collectionSize; })); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_indices_in_the_same_order_they_are_in_the_container) +{ + const size_t collectionSize = 200; + vector indices = RandomSubset(0.5).materialise(collectionSize); + + for (size_t i = 1; i < indices.size(); ++i) + BOOST_TEST(indices[i - 1] < indices[i]); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_collection_is_empty) +{ + BOOST_TEST(RandomSubset(0.5).materialise(0).empty()); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_no_indices_if_selection_chance_is_zero) +{ + BOOST_TEST(RandomSubset(0.0).materialise(10).empty()); +} + +BOOST_AUTO_TEST_CASE(materialise_should_return_all_indices_if_selection_chance_is_one) +{ + BOOST_TEST(RandomSubset(1.0).materialise(10).size() == 10); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/test/yulPhaser/TestHelpers.h b/test/yulPhaser/TestHelpers.h index 2bf755b18..514d2eda7 100644 --- a/test/yulPhaser/TestHelpers.h +++ b/test/yulPhaser/TestHelpers.h @@ -33,12 +33,39 @@ #include #include +#include + #include #include #include #include +#include #include +// OPERATORS FOR BOOST::TEST + +/// Output operator for arbitrary two-element tuples. +/// Necessary to make BOOST_TEST() work with such tuples. +template +std::ostream& operator<<(std::ostream& _output, std::tuple const& _tuple) +{ + _output << "(" << std::get<0>(_tuple) << ", " << std::get<1>(_tuple) << ")"; + return _output; +} + +namespace boost::test_tools::tt_detail +{ + +// Boost won't find find the << operator unless we put it in the std namespace which is illegal. +// The recommended solution is to overload print_log_value<> struct and make it use our global operator. +template +struct print_log_value> +{ + void operator()(std::ostream& _output, std::tuple const& _tuple) { ::operator<<(_output, _tuple); } +}; + +} + namespace solidity::phaser::test { diff --git a/tools/solidityUpgrade/Upgrade050.cpp b/tools/solidityUpgrade/Upgrade050.cpp index ac939c2a1..18a495080 100644 --- a/tools/solidityUpgrade/Upgrade050.cpp +++ b/tools/solidityUpgrade/Upgrade050.cpp @@ -32,8 +32,7 @@ void ConstructorKeyword::endVisit(ContractDefinition const& _contract) { for (auto const* function: _contract.definedFunctions()) if (function->name() == _contract.name()) - m_changes.push_back( - UpgradeChange{ + m_changes.emplace_back( UpgradeChange::Level::Safe, function->location(), SourceTransform::replaceFunctionName( @@ -41,18 +40,15 @@ void ConstructorKeyword::endVisit(ContractDefinition const& _contract) function->name(), "constructor" ) - } ); } void VisibilitySpecifier::endVisit(FunctionDefinition const& _function) { if (_function.noVisibilitySpecified()) - m_changes.push_back( - UpgradeChange{ + m_changes.emplace_back( UpgradeChange::Level::Safe, _function.location(), SourceTransform::insertAfterRightParenthesis(_function.location(), "public") - } ); } diff --git a/tools/solidityUpgrade/Upgrade060.cpp b/tools/solidityUpgrade/Upgrade060.cpp index 9cd996f53..9ed7cbc74 100644 --- a/tools/solidityUpgrade/Upgrade060.cpp +++ b/tools/solidityUpgrade/Upgrade060.cpp @@ -106,12 +106,10 @@ void AbstractContract::endVisit(ContractDefinition const& _contract) !_contract.abstract() && !_contract.isInterface() ) - m_changes.push_back( - UpgradeChange{ + m_changes.emplace_back( UpgradeChange::Level::Safe, _contract.location(), SourceTransform::insertBeforeKeyword(_contract.location(), "contract", "abstract") - } ); } @@ -132,12 +130,10 @@ void OverridingFunction::endVisit(ContractDefinition const& _contract) /// Add override with contract list, if needed. if (!function->overrides() && expectedContracts.size() > 1) - m_changes.push_back( - UpgradeChange{ + m_changes.emplace_back( UpgradeChange::Level::Safe, function->location(), appendOverride(*function, expectedContracts) - } ); for (auto [begin, end] = inheritedFunctions.equal_range(proxy); begin != end; begin++) @@ -151,12 +147,10 @@ void OverridingFunction::endVisit(ContractDefinition const& _contract) /// If function does not specify override and no override with /// contract list was added before. if (!function->overrides() && expectedContracts.size() <= 1) - m_changes.push_back( - UpgradeChange{ + m_changes.emplace_back( UpgradeChange::Level::Safe, function->location(), appendOverride(*function, expectedContracts) - } ); } } @@ -181,12 +175,10 @@ void VirtualFunction::endVisit(ContractDefinition const& _contract) function->visibility() > Visibility::Private ) { - m_changes.push_back( - UpgradeChange{ + m_changes.emplace_back( UpgradeChange::Level::Safe, function->location(), appendVirtual(*function) - } ); } @@ -198,12 +190,10 @@ void VirtualFunction::endVisit(ContractDefinition const& _contract) !super.virtualSemantics() ) { - m_changes.push_back( - UpgradeChange{ + m_changes.emplace_back( UpgradeChange::Level::Safe, function->location(), appendVirtual(*function) - } ); } } diff --git a/tools/solidityUpgrade/UpgradeChange.h b/tools/solidityUpgrade/UpgradeChange.h index 4d279fa60..19f08c6a0 100644 --- a/tools/solidityUpgrade/UpgradeChange.h +++ b/tools/solidityUpgrade/UpgradeChange.h @@ -8,7 +8,7 @@ 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 + 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 @@ -21,6 +21,7 @@ #include #include +#include namespace solidity::tools { @@ -50,7 +51,7 @@ public: : m_location(_location), m_source(_location.source->source()), - m_patch(_patch), + m_patch(std::move(_patch)), m_level(_level) {} ~UpgradeChange() {} diff --git a/tools/yulPhaser/AlgorithmRunner.cpp b/tools/yulPhaser/AlgorithmRunner.cpp index c402e5d84..402735e0d 100644 --- a/tools/yulPhaser/AlgorithmRunner.cpp +++ b/tools/yulPhaser/AlgorithmRunner.cpp @@ -187,16 +187,16 @@ Population AlgorithmRunner::randomiseDuplicates( if (_population.individuals().size() == 0) return _population; - vector chromosomes{_population.individuals()[0].chromosome}; + vector individuals{_population.individuals()[0]}; size_t duplicateCount = 0; for (size_t i = 1; i < _population.individuals().size(); ++i) if (_population.individuals()[i].chromosome == _population.individuals()[i - 1].chromosome) ++duplicateCount; else - chromosomes.push_back(_population.individuals()[i].chromosome); + individuals.push_back(_population.individuals()[i]); return ( - Population(_population.fitnessMetric(), chromosomes) + + Population(_population.fitnessMetric(), individuals) + Population::makeRandom(_population.fitnessMetric(), duplicateCount, _minChromosomeLength, _maxChromosomeLength) ); } diff --git a/tools/yulPhaser/GeneticAlgorithms.cpp b/tools/yulPhaser/GeneticAlgorithms.cpp index 432f3fe38..701bdf28c 100644 --- a/tools/yulPhaser/GeneticAlgorithms.cpp +++ b/tools/yulPhaser/GeneticAlgorithms.cpp @@ -43,24 +43,86 @@ Population RandomAlgorithm::runNextRound(Population _population) Population GenerationalElitistWithExclusivePools::runNextRound(Population _population) { double elitePoolSize = 1.0 - (m_options.mutationPoolSize + m_options.crossoverPoolSize); - RangeSelection elite(0.0, elitePoolSize); + + RangeSelection elitePool(0.0, elitePoolSize); + RandomSelection mutationPoolFromElite(m_options.mutationPoolSize / elitePoolSize); + RandomPairSelection crossoverPoolFromElite(m_options.crossoverPoolSize / elitePoolSize); + + std::function mutationOperator = alternativeMutations( + m_options.randomisationChance, + geneRandomisation(m_options.percentGenesToRandomise), + alternativeMutations( + m_options.deletionVsAdditionChance, + geneDeletion(m_options.percentGenesToAddOrDelete), + geneAddition(m_options.percentGenesToAddOrDelete) + ) + ); + std::function crossoverOperator = randomPointCrossover(); return - _population.select(elite) + - _population.select(elite).mutate( - RandomSelection(m_options.mutationPoolSize / elitePoolSize), - alternativeMutations( - m_options.randomisationChance, - geneRandomisation(m_options.percentGenesToRandomise), - alternativeMutations( - m_options.deletionVsAdditionChance, - geneDeletion(m_options.percentGenesToAddOrDelete), - geneAddition(m_options.percentGenesToAddOrDelete) - ) - ) - ) + - _population.select(elite).crossover( - RandomPairSelection(m_options.crossoverPoolSize / elitePoolSize), - randomPointCrossover() - ); + _population.select(elitePool) + + _population.select(elitePool).mutate(mutationPoolFromElite, mutationOperator) + + _population.select(elitePool).crossover(crossoverPoolFromElite, crossoverOperator); +} + +Population ClassicGeneticAlgorithm::runNextRound(Population _population) +{ + Population elite = _population.select(RangeSelection(0.0, m_options.elitePoolSize)); + Population rest = _population.select(RangeSelection(m_options.elitePoolSize, 1.0)); + + Population selectedPopulation = select(_population, rest.individuals().size()); + + Population crossedPopulation = Population::combine( + selectedPopulation.symmetricCrossoverWithRemainder( + PairsFromRandomSubset(m_options.crossoverChance), + symmetricRandomPointCrossover() + ) + ); + + std::function mutationOperator = mutationSequence({ + geneRandomisation(m_options.mutationChance), + geneDeletion(m_options.deletionChance), + geneAddition(m_options.additionChance), + }); + + RangeSelection all(0.0, 1.0); + Population mutatedPopulation = crossedPopulation.mutate(all, mutationOperator); + + return elite + mutatedPopulation; +} + +Population ClassicGeneticAlgorithm::select(Population _population, size_t _selectionSize) +{ + if (_population.individuals().size() == 0) + return _population; + + size_t maxFitness = 0; + for (auto const& individual: _population.individuals()) + maxFitness = max(maxFitness, individual.fitness); + + size_t rouletteRange = 0; + for (auto const& individual: _population.individuals()) + // Add 1 to make sure that every chromosome has non-zero probability of being chosen + rouletteRange += maxFitness + 1 - individual.fitness; + + vector selectedIndividuals; + for (size_t i = 0; i < _selectionSize; ++i) + { + uint32_t ball = SimulationRNG::uniformInt(0, rouletteRange - 1); + + size_t cumulativeFitness = 0; + for (auto const& individual: _population.individuals()) + { + size_t pocketSize = maxFitness + 1 - individual.fitness; + if (ball < cumulativeFitness + pocketSize) + { + selectedIndividuals.push_back(individual); + break; + } + cumulativeFitness += pocketSize; + } + } + + assert(selectedIndividuals.size() == _selectionSize); + return Population(_population.fitnessMetric(), selectedIndividuals); } diff --git a/tools/yulPhaser/GeneticAlgorithms.h b/tools/yulPhaser/GeneticAlgorithms.h index a2a7484d6..0d1f0375f 100644 --- a/tools/yulPhaser/GeneticAlgorithms.h +++ b/tools/yulPhaser/GeneticAlgorithms.h @@ -139,4 +139,59 @@ private: Options m_options; }; +/** + * A typical genetic algorithm that works in three distinct phases, each resulting in a new, + * modified population: + * - selection: chromosomes are selected from the population with probability proportional to their + * fitness. A chromosome can be selected more than once. The new population has the same size as + * the old one. + * - crossover: first, for each chromosome we decide whether it undergoes crossover or not + * (according to crossover chance parameter). Then each selected chromosome is randomly paired + * with one other selected chromosome. Each pair produces a pair of children and gets replaced by + * it in the population. + * - mutation: we go over each gene in the population and independently decide whether to mutate it + * or not (according to mutation chance parameters). This is repeated for every mutation type so + * one gene can undergo mutations of multiple types in a single round. + * + * This implementation also has the ability to preserve the top chromosomes in each round. + */ +class ClassicGeneticAlgorithm: public GeneticAlgorithm +{ +public: + struct Options + { + double elitePoolSize; ///< Percentage of the population treated as the elite. + double crossoverChance; ///< The chance of a particular chromosome being selected for crossover. + double mutationChance; ///< The chance of a particular gene being randomised in @a geneRandomisation mutation. + double deletionChance; ///< The chance of a particular gene being deleted in @a geneDeletion mutation. + double additionChance; ///< The chance of a particular gene being added in @a geneAddition mutation. + + bool isValid() const + { + return ( + 0 <= elitePoolSize && elitePoolSize <= 1.0 && + 0 <= crossoverChance && crossoverChance <= 1.0 && + 0 <= mutationChance && mutationChance <= 1.0 && + 0 <= deletionChance && deletionChance <= 1.0 && + 0 <= additionChance && additionChance <= 1.0 + ); + } + }; + + ClassicGeneticAlgorithm(Options const& _options): + m_options(_options) + { + assert(_options.isValid()); + } + + Options const& options() const { return m_options; } + + Population runNextRound(Population _population) override; + +private: + static Population select(Population _population, size_t _selectionSize); + + Options m_options; +}; + } diff --git a/tools/yulPhaser/Mutations.cpp b/tools/yulPhaser/Mutations.cpp index 86f815198..98689a810 100644 --- a/tools/yulPhaser/Mutations.cpp +++ b/tools/yulPhaser/Mutations.cpp @@ -95,10 +95,22 @@ function phaser::alternativeMutations( }; } +function phaser::mutationSequence(vector> _mutations) +{ + return [=](Chromosome const& _chromosome) + { + Chromosome mutatedChromosome = _chromosome; + for (size_t i = 0; i < _mutations.size(); ++i) + mutatedChromosome = _mutations[i](move(mutatedChromosome)); + + return mutatedChromosome; + }; +} + namespace { -Chromosome buildChromosomesBySwappingParts( +ChromosomePair fixedPointSwap( Chromosome const& _chromosome1, Chromosome const& _chromosome2, size_t _crossoverPoint @@ -109,11 +121,19 @@ Chromosome buildChromosomesBySwappingParts( auto begin1 = _chromosome1.optimisationSteps().begin(); auto begin2 = _chromosome2.optimisationSteps().begin(); + auto end1 = _chromosome1.optimisationSteps().end(); + auto end2 = _chromosome2.optimisationSteps().end(); - return Chromosome( - vector(begin1, begin1 + _crossoverPoint) + - vector(begin2 + _crossoverPoint, _chromosome2.optimisationSteps().end()) - ); + return { + Chromosome( + vector(begin1, begin1 + _crossoverPoint) + + vector(begin2 + _crossoverPoint, end2) + ), + Chromosome( + vector(begin2, begin2 + _crossoverPoint) + + vector(begin1 + _crossoverPoint, end1) + ), + }; } } @@ -129,7 +149,22 @@ function phaser::randomPointCrossover() assert(minPoint <= minLength); size_t randomPoint = SimulationRNG::uniformInt(minPoint, minLength); - return buildChromosomesBySwappingParts(_chromosome1, _chromosome2, randomPoint); + return get<0>(fixedPointSwap(_chromosome1, _chromosome2, randomPoint)); + }; +} + +function phaser::symmetricRandomPointCrossover() +{ + return [=](Chromosome const& _chromosome1, Chromosome const& _chromosome2) + { + size_t minLength = min(_chromosome1.length(), _chromosome2.length()); + + // Don't use position 0 (because this just swaps the values) unless it's the only choice. + size_t minPoint = (minLength > 0? 1 : 0); + assert(minPoint <= minLength); + + size_t randomPoint = SimulationRNG::uniformInt(minPoint, minLength); + return fixedPointSwap(_chromosome1, _chromosome2, randomPoint); }; } @@ -142,6 +177,6 @@ function phaser::fixedPointCrossover(double _crossoverPoint) size_t minLength = min(_chromosome1.length(), _chromosome2.length()); size_t concretePoint = static_cast(round(minLength * _crossoverPoint)); - return buildChromosomesBySwappingParts(_chromosome1, _chromosome2, concretePoint); + return get<0>(fixedPointSwap(_chromosome1, _chromosome2, concretePoint)); }; } diff --git a/tools/yulPhaser/Mutations.h b/tools/yulPhaser/Mutations.h index bff48c52b..d545aa093 100644 --- a/tools/yulPhaser/Mutations.h +++ b/tools/yulPhaser/Mutations.h @@ -28,8 +28,11 @@ namespace solidity::phaser { +using ChromosomePair = std::tuple; + using Mutation = Chromosome(Chromosome const&); using Crossover = Chromosome(Chromosome const&, Chromosome const&); +using SymmetricCrossover = ChromosomePair(Chromosome const&, Chromosome const&); // MUTATIONS @@ -55,12 +58,19 @@ std::function alternativeMutations( std::function _mutation2 ); +/// Creates a mutation operator that sequentially applies all the operators given in @a _mutations. +std::function mutationSequence(std::vector> _mutations); + // CROSSOVER /// Creates a crossover operator that randomly selects a number between 0 and 1 and uses it as the /// position at which to perform perform @a fixedPointCrossover. std::function randomPointCrossover(); +/// Symmetric version of @a randomPointCrossover(). Creates an operator that returns a pair +/// containing both possible results for the same crossover point. +std::function symmetricRandomPointCrossover(); + /// Creates a crossover operator that always chooses a point that lies at @a _crossoverPoint /// percent of the length of the shorter chromosome. Then creates a new chromosome by /// splitting both inputs at the crossover point and stitching output from the first half or first diff --git a/tools/yulPhaser/PairSelections.cpp b/tools/yulPhaser/PairSelections.cpp index 8f3fd0f7f..7e744ff70 100644 --- a/tools/yulPhaser/PairSelections.cpp +++ b/tools/yulPhaser/PairSelections.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -41,12 +42,49 @@ vector> RandomPairSelection::materialise(size_t _poolSize) index2 = SimulationRNG::uniformInt(0, _poolSize - 1); } while (index1 == index2); - selection.push_back({index1, index2}); + selection.emplace_back(index1, index2); } return selection; } +vector> PairsFromRandomSubset::materialise(size_t _poolSize) const +{ + vector selectedIndices = RandomSubset(m_selectionChance).materialise(_poolSize); + + if (selectedIndices.size() % 2 != 0) + { + if (selectedIndices.size() < _poolSize && SimulationRNG::bernoulliTrial(0.5)) + { + do + { + size_t extraIndex = SimulationRNG::uniformInt(0, selectedIndices.size() - 1); + if (find(selectedIndices.begin(), selectedIndices.end(), extraIndex) == selectedIndices.end()) + selectedIndices.push_back(extraIndex); + } while (selectedIndices.size() % 2 != 0); + } + else + selectedIndices.erase(selectedIndices.begin() + SimulationRNG::uniformInt(0, selectedIndices.size() - 1)); + } + assert(selectedIndices.size() % 2 == 0); + + vector> selectedPairs; + for (size_t i = selectedIndices.size() / 2; i > 0; --i) + { + size_t position1 = SimulationRNG::uniformInt(0, selectedIndices.size() - 1); + size_t value1 = selectedIndices[position1]; + selectedIndices.erase(selectedIndices.begin() + position1); + size_t position2 = SimulationRNG::uniformInt(0, selectedIndices.size() - 1); + size_t value2 = selectedIndices[position2]; + selectedIndices.erase(selectedIndices.begin() + position2); + + selectedPairs.push_back({value1, value2}); + } + assert(selectedIndices.size() == 0); + + return selectedPairs; +} + vector> PairMosaicSelection::materialise(size_t _poolSize) const { if (_poolSize < 2) @@ -58,7 +96,7 @@ vector> PairMosaicSelection::materialise(size_t _poolSize) for (size_t i = 0; i < count; ++i) { tuple pair = m_pattern[i % m_pattern.size()]; - selection.push_back({min(get<0>(pair), _poolSize - 1), min(get<1>(pair), _poolSize - 1)}); + selection.emplace_back(min(get<0>(pair), _poolSize - 1), min(get<1>(pair), _poolSize - 1)); } return selection; diff --git a/tools/yulPhaser/PairSelections.h b/tools/yulPhaser/PairSelections.h index 7778d6567..a80fff897 100644 --- a/tools/yulPhaser/PairSelections.h +++ b/tools/yulPhaser/PairSelections.h @@ -69,6 +69,28 @@ private: double m_selectionSize; }; + +/** + * A selection that goes over all elements in a container, for each one independently decides + * whether to select it or not and then randomly combines those elements into pairs. If the number + * of elements is odd, randomly decides whether to take one more or exclude one. + * + * Each element has the same chance of being selected and can be selected at most once. + * The number of selected elements is random and can be different with each call to + * @a materialise(). + */ +class PairsFromRandomSubset: public PairSelection +{ +public: + explicit PairsFromRandomSubset(double _selectionChance): + m_selectionChance(_selectionChance) {} + + std::vector> materialise(size_t _poolSize) const override; + +private: + double m_selectionChance; +}; + /** * A selection that selects pairs of elements at specific, fixed positions indicated by a repeating * "pattern". If the positions in the pattern exceed the size of the container, they are capped at diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 2e38edbf0..6f7be3257 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -58,6 +58,7 @@ map const AlgorithmToStringMap = { {Algorithm::Random, "random"}, {Algorithm::GEWEP, "GEWEP"}, + {Algorithm::Classic, "classic"}, }; map const StringToAlgorithmMap = invertMap(AlgorithmToStringMap); @@ -107,6 +108,11 @@ GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLi _arguments.count("gewep-genes-to-add-or-delete") > 0 ? _arguments["gewep-genes-to-add-or-delete"].as() : optional{}, + _arguments["classic-elite-pool-size"].as(), + _arguments["classic-crossover-chance"].as(), + _arguments["classic-mutation-chance"].as(), + _arguments["classic-deletion-chance"].as(), + _arguments["classic-addition-chance"].as(), }; } @@ -151,6 +157,16 @@ unique_ptr GeneticAlgorithmFactory::build( /* percentGenesToAddOrDelete = */ percentGenesToAddOrDelete, }); } + case Algorithm::Classic: + { + return make_unique(ClassicGeneticAlgorithm::Options{ + /* elitePoolSize = */ _options.classicElitePoolSize, + /* crossoverChance = */ _options.classicCrossoverChance, + /* mutationChance = */ _options.classicMutationChance, + /* deletionChance = */ _options.classicDeletionChance, + /* additionChance = */ _options.classicAdditionChance, + }); + } default: assertThrow(false, solidity::util::Exception, "Invalid Algorithm value."); } @@ -475,6 +491,36 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ; keywordDescription.add(gewepAlgorithmDescription); + po::options_description classicGeneticAlgorithmDescription("CLASSIC GENETIC ALGORITHM", lineLength, minDescriptionLength); + classicGeneticAlgorithmDescription.add_options() + ( + "classic-elite-pool-size", + po::value()->value_name("")->default_value(0), + "Percentage of population to regenerate using mutations in each round." + ) + ( + "classic-crossover-chance", + po::value()->value_name("")->default_value(0.75), + "Chance of a chromosome being selected for crossover." + ) + ( + "classic-mutation-chance", + po::value()->value_name("")->default_value(0.01), + "Chance of a gene being mutated." + ) + ( + "classic-deletion-chance", + po::value()->value_name("")->default_value(0.01), + "Chance of a gene being deleted." + ) + ( + "classic-addition-chance", + po::value()->value_name("")->default_value(0.01), + "Chance of a random gene being added." + ) + ; + keywordDescription.add(classicGeneticAlgorithmDescription); + po::options_description randomAlgorithmDescription("RANDOM ALGORITHM", lineLength, minDescriptionLength); randomAlgorithmDescription.add_options() ( diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index 77e9e48c8..2896dd090 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -58,6 +58,7 @@ enum class Algorithm { Random, GEWEP, + Classic, }; enum class MetricChoice @@ -101,6 +102,11 @@ public: double gewepDeletionVsAdditionChance; std::optional gewepGenesToRandomise; std::optional gewepGenesToAddOrDelete; + double classicElitePoolSize; + double classicCrossoverChance; + double classicMutationChance; + double classicDeletionChance; + double classicAdditionChance; static Options fromCommandLine(boost::program_options::variables_map const& _arguments); }; diff --git a/tools/yulPhaser/Population.cpp b/tools/yulPhaser/Population.cpp index b336daf73..1b846122e 100644 --- a/tools/yulPhaser/Population.cpp +++ b/tools/yulPhaser/Population.cpp @@ -117,6 +117,37 @@ Population Population::crossover(PairSelection const& _selection, function Population::symmetricCrossoverWithRemainder( + PairSelection const& _selection, + function _symmetricCrossover +) const +{ + vector indexSelected(m_individuals.size(), false); + + vector crossedIndividuals; + for (auto const& [i, j]: _selection.materialise(m_individuals.size())) + { + auto children = _symmetricCrossover( + m_individuals[i].chromosome, + m_individuals[j].chromosome + ); + crossedIndividuals.emplace_back(move(get<0>(children)), *m_fitnessMetric); + crossedIndividuals.emplace_back(move(get<1>(children)), *m_fitnessMetric); + indexSelected[i] = true; + indexSelected[j] = true; + } + + vector remainder; + for (size_t i = 0; i < indexSelected.size(); ++i) + if (!indexSelected[i]) + remainder.emplace_back(m_individuals[i]); + + return { + Population(m_fitnessMetric, crossedIndividuals), + Population(m_fitnessMetric, remainder), + }; +} + namespace solidity::phaser { @@ -132,6 +163,11 @@ Population operator+(Population _a, Population _b) } +Population Population::combine(std::tuple _populationPair) +{ + return get<0>(_populationPair) + get<1>(_populationPair); +} + bool Population::operator==(Population const& _other) const { // We consider populations identical only if they share the same exact instance of the metric. diff --git a/tools/yulPhaser/Population.h b/tools/yulPhaser/Population.h index 40d51498b..c405f702c 100644 --- a/tools/yulPhaser/Population.h +++ b/tools/yulPhaser/Population.h @@ -81,6 +81,9 @@ public: _fitnessMetric, chromosomesToIndividuals(*_fitnessMetric, std::move(_chromosomes)) ) {} + explicit Population(std::shared_ptr _fitnessMetric, std::vector _individuals): + m_fitnessMetric(std::move(_fitnessMetric)), + m_individuals{sortedIndividuals(std::move(_individuals))} {} static Population makeRandom( std::shared_ptr _fitnessMetric, @@ -97,8 +100,13 @@ public: Population select(Selection const& _selection) const; Population mutate(Selection const& _selection, std::function _mutation) const; Population crossover(PairSelection const& _selection, std::function _crossover) const; + std::tuple symmetricCrossoverWithRemainder( + PairSelection const& _selection, + std::function _symmetricCrossover + ) const; friend Population operator+(Population _a, Population _b); + static Population combine(std::tuple _populationPair); std::shared_ptr fitnessMetric() { return m_fitnessMetric; } std::vector const& individuals() const { return m_individuals; } @@ -112,10 +120,6 @@ public: friend std::ostream& operator<<(std::ostream& _stream, Population const& _population); private: - explicit Population(std::shared_ptr _fitnessMetric, std::vector _individuals): - m_fitnessMetric(std::move(_fitnessMetric)), - m_individuals{sortedIndividuals(std::move(_individuals))} {} - static std::vector chromosomesToIndividuals( FitnessMetric& _fitnessMetric, std::vector _chromosomes diff --git a/tools/yulPhaser/Selections.cpp b/tools/yulPhaser/Selections.cpp index abc080fde..920a7d7f1 100644 --- a/tools/yulPhaser/Selections.cpp +++ b/tools/yulPhaser/Selections.cpp @@ -20,6 +20,7 @@ #include #include +#include using namespace std; using namespace solidity::phaser; @@ -58,3 +59,12 @@ vector RandomSelection::materialise(size_t _poolSize) const return selection; } +vector RandomSubset::materialise(size_t _poolSize) const +{ + vector selection; + for (size_t index = 0; index < _poolSize; ++index) + if (SimulationRNG::bernoulliTrial(m_selectionChance)) + selection.push_back(index); + + return selection; +} diff --git a/tools/yulPhaser/Selections.h b/tools/yulPhaser/Selections.h index 46d975bbd..a0ed2657f 100644 --- a/tools/yulPhaser/Selections.h +++ b/tools/yulPhaser/Selections.h @@ -118,4 +118,26 @@ private: double m_selectionSize; }; +/** + * A selection that goes over all elements in a container, for each one independently deciding + * whether to select it or not. Each element has the same chance of being selected and can be + * selected at most once. The order of selected elements is the same as the order of elements in + * the container. The number of selected elements is random and can be different with each call + * to @a materialise(). + */ +class RandomSubset: public Selection +{ +public: + explicit RandomSubset(double _selectionChance): + m_selectionChance(_selectionChance) + { + assert(0.0 <= _selectionChance && _selectionChance <= 1.0); + } + + std::vector materialise(size_t _poolSize) const override; + +private: + double m_selectionChance; +}; + }