mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into HEAD
This commit is contained in:
commit
e239f62b64
@ -627,14 +627,14 @@ jobs:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependencies-osx-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
- dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
# DO NOT EDIT between here and save_cache, but rather edit ./circleci/osx_install_dependencies.sh
|
||||
# WARNING! If you do edit anything here instead, remember to invalidate the cache manually.
|
||||
- run:
|
||||
name: Install build dependencies
|
||||
command: ./.circleci/osx_install_dependencies.sh
|
||||
- save_cache:
|
||||
key: dependencies-osx-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
key: dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
paths:
|
||||
- /usr/local/bin
|
||||
- /usr/local/sbin
|
||||
@ -663,7 +663,7 @@ jobs:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependencies-osx-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
- dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *run_soltest
|
||||
@ -679,7 +679,7 @@ jobs:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependencies-osx-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
- dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: *run_cmdline_tests
|
||||
@ -886,14 +886,14 @@ jobs:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependencies-win-{{ checksum "scripts/install_deps.ps1" }}
|
||||
- dependencies-win-{{ arch }}-{{ checksum "scripts/install_deps.ps1" }}
|
||||
# DO NOT EDIT between here and save_cache, but rather edit .\scripts\install_deps.ps1
|
||||
# WARNING! If you do edit anything here instead, remember to invalidate the cache manually.
|
||||
- run:
|
||||
name: "Installing dependencies"
|
||||
command: .\scripts\install_deps.ps1
|
||||
- save_cache:
|
||||
key: dependencies-win-{{ checksum "scripts/install_deps.ps1" }}
|
||||
key: dependencies-win-{{ arch }}-{{ checksum "scripts/install_deps.ps1" }}
|
||||
paths:
|
||||
- .\deps
|
||||
- run:
|
||||
|
@ -52,9 +52,6 @@ function validate_checksum {
|
||||
|
||||
if [ ! -f /usr/local/lib/libz3.a ] # if this file does not exists (cache was not restored), rebuild dependencies
|
||||
then
|
||||
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
|
||||
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow
|
||||
brew update
|
||||
brew unlink python
|
||||
brew install boost
|
||||
brew install cmake
|
||||
|
@ -41,6 +41,7 @@ include(EthCcache)
|
||||
|
||||
# Let's find our dependencies
|
||||
include(EthDependencies)
|
||||
include(fmtlib)
|
||||
include(jsoncpp)
|
||||
include(range-v3)
|
||||
include_directories(SYSTEM ${JSONCPP_INCLUDE_DIR})
|
||||
|
@ -13,12 +13,21 @@ Language Features:
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Commandline Interface: Accept nested brackets in step sequences passed to ``--yul-optimizations``.
|
||||
* Commandline Interface: Add ``--debug-info`` option for selecting how much extra debug information should be included in the produced EVM assembly and Yul code.
|
||||
* SMTChecker: Output values for ``block.*``, ``msg.*`` and ``tx.*`` variables that are present in the called functions.
|
||||
* Standard JSON: Accept nested brackets in step sequences passed to ``settings.optimizer.details.yulDetails.optimizerSteps``.
|
||||
* Standard JSON: Add ``settings.debug.debugInfo`` option for selecting how much extra debug information should be included in the produced EVM assembly and Yul code.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Code Generator: Fix constructor source mappings for immutables.
|
||||
* Commandline Interface: Fix extra newline character being appended to sources passed through standard input, affecting their hashes.
|
||||
* Commandline Interface: Report output selection options unsupported by the selected input mode instead of ignoring them.
|
||||
* Commandline Interface: Don't return zero exit code when writing linked files to disk fails.
|
||||
* SMTChecker: Fix internal error in magic type access (``block``, ``msg``, ``tx``).
|
||||
* TypeChecker: Fix internal error when using user defined value types in public library functions.
|
||||
* Yul IR Generator: Do not output empty switches/if-bodies for empty contracts.
|
||||
|
||||
|
||||
|
||||
|
20
cmake/fmtlib.cmake
Normal file
20
cmake/fmtlib.cmake
Normal file
@ -0,0 +1,20 @@
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
fmtlib
|
||||
PREFIX "${CMAKE_BINARY_DIR}/deps"
|
||||
DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads"
|
||||
DOWNLOAD_NAME fmt-8.0.1.tar.gz
|
||||
URL https://github.com/fmtlib/fmt/archive/8.0.1.tar.gz
|
||||
URL_HASH SHA256=b06ca3130158c625848f3fb7418f235155a4d389b2abc3a6245fb01cb0eb1e01
|
||||
)
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS "3.14.0")
|
||||
FetchContent_GetProperties(fmtlib)
|
||||
if (NOT fmtlib_POPULATED)
|
||||
FetchContent_Populate(fmtlib)
|
||||
add_subdirectory(${fmtlib_SOURCE_DIR} ${fmtlib_BINARY_DIR})
|
||||
endif()
|
||||
else()
|
||||
FetchContent_MakeAvailable(fmtlib)
|
||||
endif()
|
86
docs/_static/css/custom.css
vendored
86
docs/_static/css/custom.css
vendored
@ -1,14 +1,86 @@
|
||||
pre {
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.wy-table-responsive table td, .wy-table-responsive table th {
|
||||
white-space: normal;
|
||||
white-space: normal;
|
||||
}
|
||||
.rst-content table.docutils td {
|
||||
vertical-align: top;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* links */
|
||||
.rst-content a:not(:visited) {
|
||||
color: #002fa7;
|
||||
}
|
||||
|
||||
.rst-content .highlighted {
|
||||
background: #eac545;
|
||||
}
|
||||
|
||||
/* code block highlights */
|
||||
.rst-content pre {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.wy-side-nav-search img.logo {
|
||||
width: 100px !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wy-side-nav-search > a {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* project version (displayed under project logo) */
|
||||
.wy-side-nav-search .version {
|
||||
color: #272525 !important;
|
||||
}
|
||||
|
||||
/* menu section headers */
|
||||
.wy-menu .caption {
|
||||
color: #65afff !important;
|
||||
}
|
||||
|
||||
/* Link to Remix IDE shown next to code snippets */
|
||||
p.remix-link-container {
|
||||
position: relative;
|
||||
right: -100%; /* Positioned next to the the top-right corner of the code block following it. */
|
||||
}
|
||||
|
||||
a.remix-link {
|
||||
position: absolute; /* Remove it from normal flow not to affect the original position of the snippet. */
|
||||
top: 0.5em;
|
||||
width: 3.236em; /* Size of the margin (= right-side padding in .wy-nav-content in the current theme). */
|
||||
}
|
||||
|
||||
a.remix-link .link-icon {
|
||||
background: url("../img/solid-share-arrow.svg") no-repeat;
|
||||
display: block;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
a.remix-link .link-text {
|
||||
display: none; /* Visible only on hover. */
|
||||
width: 3.3em; /* Narrow enough to get two lines of text. */
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
font-size: 0.8em;
|
||||
line-height: normal;
|
||||
color: black;
|
||||
}
|
||||
|
||||
a.remix-link:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
a.remix-link:hover .link-text {
|
||||
display: block;
|
||||
}
|
||||
|
23
docs/_static/css/dark.css
vendored
23
docs/_static/css/dark.css
vendored
@ -1,11 +1,9 @@
|
||||
/* links */
|
||||
|
||||
a,
|
||||
a:visited {
|
||||
color: #aaddff;
|
||||
.rst-content a:not(:visited) {
|
||||
color: #aaddff !important;
|
||||
}
|
||||
|
||||
|
||||
/* code directives */
|
||||
|
||||
.method dt,
|
||||
@ -46,6 +44,9 @@ em.property {
|
||||
background-color: #5a5a5a;
|
||||
}
|
||||
|
||||
.rst-content pre {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* inlined code highlights */
|
||||
|
||||
@ -70,6 +71,12 @@ code.docutils.literal.notranslate {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
/* highlight color search text */
|
||||
|
||||
.rst-content .highlighted {
|
||||
background: #ff5722;
|
||||
box-shadow: 0 0 0 2px #f0978b;
|
||||
}
|
||||
|
||||
/* notes, warnings, hints */
|
||||
|
||||
@ -619,4 +626,10 @@ code.docutils.literal.notranslate {
|
||||
}
|
||||
|
||||
|
||||
/* Literal.Number.Integer.Long */
|
||||
/* Literal.Number.Integer.Long */
|
||||
|
||||
|
||||
/* Link to Remix IDE shown over code snippets */
|
||||
a.remix-link {
|
||||
filter: invert(1); /* The icon is black. In dark mode we want it white. */
|
||||
}
|
||||
|
8
docs/_static/css/toggle.css
vendored
8
docs/_static/css/toggle.css
vendored
@ -74,4 +74,10 @@ html.transition *:before,
|
||||
html.transition *:after {
|
||||
transition: ease-in-out 200ms !important;
|
||||
transition-delay: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
nav.wy-nav-side {
|
||||
/* The default padding of 2em is too small and the "Keyword Index" link gets obscured
|
||||
* by the version toggle. */
|
||||
padding-bottom: 3em;
|
||||
}
|
||||
|
1
docs/_static/img/solid-share-arrow.svg
vendored
Normal file
1
docs/_static/img/solid-share-arrow.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="share" class="svg-inline--fa fa-share fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M503.691 189.836L327.687 37.851C312.281 24.546 288 35.347 288 56.015v80.053C127.371 137.907 0 170.1 0 322.326c0 61.441 39.581 122.309 83.333 154.132 13.653 9.931 33.111-2.533 28.077-18.631C66.066 312.814 132.917 274.316 288 272.085V360c0 20.7 24.3 31.453 39.687 18.164l176.004-152c11.071-9.562 11.086-26.753 0-36.328z"></path></svg>
|
After Width: | Height: | Size: 547 B |
7
docs/_templates/footer.html
vendored
Normal file
7
docs/_templates/footer.html
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends "!footer.html" %}
|
||||
|
||||
{% block extrafooter %}
|
||||
<p>
|
||||
<a href="{{ pathto('credits-and-attribution') }}">Credits and attribution</a>.
|
||||
</p>
|
||||
{% endblock %}
|
10
docs/_templates/layout.html
vendored
10
docs/_templates/layout.html
vendored
@ -1,6 +1,10 @@
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
{% block menu %}
|
||||
{% block menu %}
|
||||
{{ super() }}
|
||||
<a href="{{ pathto('genindex') }}">Keyword Index</a>
|
||||
{% endblock %}
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ pathto('genindex') }}">Keyword Index</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@ -130,6 +130,12 @@ Global Variables
|
||||
- ``type(T).min`` (``T``): the minimum value representable by the integer type ``T``, see :ref:`Type Information<meta-type>`.
|
||||
- ``type(T).max`` (``T``): the maximum value representable by the integer type ``T``, see :ref:`Type Information<meta-type>`.
|
||||
|
||||
.. note::
|
||||
When contracts are evaluated off-chain rather than in context of a transaction included in a
|
||||
block, you should not assume that ``block.*`` and ``tx.*`` refer to values from any specific
|
||||
block or transaction. These values are provided by the EVM implementation that executes the
|
||||
contract and can be arbitrary.
|
||||
|
||||
.. note::
|
||||
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
|
||||
unless you know what you are doing.
|
||||
|
11
docs/conf.py
11
docs/conf.py
@ -44,6 +44,7 @@ def setup(sphinx):
|
||||
extensions = [
|
||||
'sphinx_a4doc',
|
||||
'html_extra_template_renderer',
|
||||
'remix_code_links',
|
||||
]
|
||||
|
||||
a4_base_path = os.path.dirname(__file__) + '/grammar'
|
||||
@ -128,7 +129,11 @@ html_theme = 'sphinx_rtd_theme'
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
html_theme_options = {
|
||||
'logo_only': True,
|
||||
'style_nav_header_background': '#65afff',
|
||||
'display_version': True,
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
@ -142,7 +147,7 @@ html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
html_logo = "logo.svg"
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
@ -238,7 +243,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'solidity.tex', 'Solidity Documentation', 'Ethereum', 'manual'),
|
||||
('index', 'solidity.tex', 'Solidity Documentation', 'Ethereum', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
|
@ -86,12 +86,10 @@ Running the Compiler Tests
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Some tests require the `evmone <https://github.com/ethereum/evmone/releases>`_
|
||||
library, others require `libz3 <https://github.com/Z3Prover/z3>`_. The test script
|
||||
tries to discover the location of the ``evmone`` library, which can be located
|
||||
in the current directory, installed on the system level, or the ``deps`` folder
|
||||
in the project top level. The required file is called ``libevmone.so`` on Linux
|
||||
systems, ``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS.
|
||||
For running all compiler tests you may want to optionally install a few
|
||||
dependencies (`evmone <https://github.com/ethereum/evmone/releases>`_,
|
||||
`libz3 <https://github.com/Z3Prover/z3>`_, and
|
||||
`libhera <https://github.com/ewasm/hera>`_).
|
||||
|
||||
On macOS some of the testing scripts expect GNU coreutils to be installed.
|
||||
This can be easiest accomplished using Homebrew: ``brew install coreutils``.
|
||||
@ -108,13 +106,28 @@ including those bundled into the `Boost C++ Test Framework <https://www.boost.or
|
||||
application ``soltest`` (or its wrapper ``scripts/soltest.sh``), as well as command line tests and
|
||||
compilation tests.
|
||||
|
||||
The test system automatically tries to discover the location of the ``evmone`` library
|
||||
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
|
||||
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS. If it is not found, tests that
|
||||
use it are skipped. These tests are ``libsolididty/semanticTests``, ``libsolidity/GasCosts``,
|
||||
``libsolidity/SolidityEndToEndTest``, part of the soltest suite. To run all tests, download the library from
|
||||
`GitHub <https://github.com/ethereum/evmone/releases/tag/v0.8.0>`_
|
||||
and place it in the project root path or inside the ``deps`` folder.
|
||||
The test system automatically tries to discover the location of
|
||||
the `evmone <https://github.com/ethereum/evmone/releases>`_ for running the semantic tests.
|
||||
|
||||
The ``evmone`` library must be located in the ``deps`` or ``deps/lib`` directory relative to the
|
||||
current working directory, to its parent or its parent's parent. Alternatively an explicit location
|
||||
for the ``evmone`` shared object can be specified via the ``ETH_EVMONE`` environment variable.
|
||||
|
||||
``evmone`` is needed mainly for running semantic and gas tests.
|
||||
If you do not have it installed, you can skip these tests by passing the ``--no-semantic-tests``
|
||||
flag to ``scripts/soltest.sh``.
|
||||
|
||||
Running Ewasm tests is disabled by default and can be explicitly enabled
|
||||
via ``./scripts/soltest.sh --ewasm`` and requires `hera <https://github.com/ewasm/hera>`_
|
||||
to be found by ``soltest``.
|
||||
The mechanism for locating the ``hera`` library is the same as for ``evmone``, except that the
|
||||
variable for specifying an explicit location is called ``ETH_HERA``.
|
||||
|
||||
The ``evmone`` and ``hera`` libraries should both end with the file name
|
||||
extension ``.so`` on Linux, ``.dll`` on Windows systems and ``.dylib`` on macOS.
|
||||
|
||||
For running SMT tests, the ``libz3`` library must be installed and locatable
|
||||
by ``cmake`` during compiler configure stage.
|
||||
|
||||
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
|
||||
|
21
docs/credits-and-attribution.rst
Normal file
21
docs/credits-and-attribution.rst
Normal file
@ -0,0 +1,21 @@
|
||||
.. This page is meant to be linked to from the footer.
|
||||
|
||||
:orphan:
|
||||
|
||||
#######################
|
||||
Credits and Attribution
|
||||
#######################
|
||||
|
||||
Website icons
|
||||
=============
|
||||
|
||||
.. |icon-share-solid| image:: _static/img/solid-share-arrow.svg
|
||||
.. _share icon: https://fontawesome.com/v5.15/icons/share?style=solid
|
||||
.. _Font Awesome Free License: https://fontawesome.com/license/free
|
||||
|
||||
+-------------------------+-----------------------------------------------------------------------+
|
||||
| Icon | Attribution |
|
||||
+=========================+=======================================================================+
|
||||
| |icon-share-solid| | - Source: `share icon`_ from Font Awesome 5.15.0. |
|
||||
| | - License: `Font Awesome Free License`_ (CC BY 4.0). |
|
||||
+-------------------------+-----------------------------------------------------------------------+
|
@ -11,7 +11,7 @@ sign and verify signatures, and setup the payment channel.
|
||||
Creating and verifying signatures
|
||||
=================================
|
||||
|
||||
Imagine Alice wants to send a quantity of Ether to Bob, i.e.
|
||||
Imagine Alice wants to send some Ether to Bob, i.e.
|
||||
Alice is the sender and Bob is the recipient.
|
||||
|
||||
Alice only needs to send cryptographically signed messages off-chain
|
||||
|
83
docs/ext/remix_code_links.py
Normal file
83
docs/ext/remix_code_links.py
Normal file
@ -0,0 +1,83 @@
|
||||
import base64
|
||||
import docutils # pragma pylint: disable=import-error
|
||||
|
||||
from sphinx.util import logging # pragma pylint: disable=import-error
|
||||
|
||||
# NOTE: 2000 should generally be safe for all browsers, while 8000 for most of them.
|
||||
MAX_SAFE_URL_LENGTH = 10000
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def insert_node_before(child, new_sibling):
|
||||
assert child in child.parent.children
|
||||
|
||||
for position, node in enumerate(child.parent.children):
|
||||
if node == child:
|
||||
child.parent.insert(position, new_sibling)
|
||||
break
|
||||
|
||||
|
||||
def remix_code_url(source_code, language, solidity_version):
|
||||
# NOTE: base64 encoded data may contain +, = and / characters. Remix seems to handle them just
|
||||
# fine without any escaping.
|
||||
base64_encoded_source = base64.b64encode(source_code.encode('utf-8')).decode('ascii')
|
||||
return f"https://remix.ethereum.org/?language={language}&version={solidity_version}&code={base64_encoded_source}"
|
||||
|
||||
|
||||
def build_remix_link_node(url):
|
||||
link_icon_node = docutils.nodes.inline()
|
||||
link_icon_node.set_class('link-icon')
|
||||
|
||||
link_text_node = docutils.nodes.inline(text="open in Remix")
|
||||
link_text_node.set_class('link-text')
|
||||
|
||||
reference_node = docutils.nodes.reference('', '', internal=False, refuri=url)
|
||||
reference_node.set_class('remix-link')
|
||||
reference_node += [link_icon_node, link_text_node]
|
||||
|
||||
paragraph_node = docutils.nodes.paragraph()
|
||||
paragraph_node.set_class('remix-link-container')
|
||||
paragraph_node += reference_node
|
||||
return paragraph_node
|
||||
|
||||
|
||||
def insert_remix_link(app, doctree, solidity_version):
|
||||
if app.builder.format != 'html' or app.builder.name == 'epub':
|
||||
return
|
||||
|
||||
for literal_block_node in doctree.traverse(docutils.nodes.literal_block):
|
||||
assert 'language' in literal_block_node.attributes
|
||||
language = literal_block_node.attributes['language'].lower()
|
||||
if language in ['solidity', 'yul']:
|
||||
text_nodes = list(literal_block_node.traverse(docutils.nodes.Text))
|
||||
assert len(text_nodes) == 1
|
||||
|
||||
remix_url = remix_code_url(text_nodes[0], language, solidity_version)
|
||||
url_length = len(remix_url.encode('utf-8'))
|
||||
if url_length > MAX_SAFE_URL_LENGTH:
|
||||
logger.warning(
|
||||
"Remix URL generated from the code snippet exceeds the maximum safe URL length "
|
||||
" (%d > %d bytes).",
|
||||
url_length,
|
||||
MAX_SAFE_URL_LENGTH,
|
||||
location=(literal_block_node.source, literal_block_node.line),
|
||||
)
|
||||
|
||||
insert_node_before(literal_block_node, build_remix_link_node(remix_url))
|
||||
|
||||
|
||||
def setup(app):
|
||||
# NOTE: Need to access _raw_config here because setup() runs before app.config is ready.
|
||||
solidity_version = app.config._raw_config['version'] # pylint: disable=protected-access
|
||||
|
||||
app.connect(
|
||||
'doctree-resolved',
|
||||
lambda app, doctree, docname: insert_remix_link(app, doctree, solidity_version)
|
||||
)
|
||||
|
||||
return {
|
||||
'version': solidity_version,
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
@ -1,11 +1,6 @@
|
||||
Solidity
|
||||
========
|
||||
|
||||
.. image:: logo.svg
|
||||
:width: 120px
|
||||
:alt: Solidity logo
|
||||
:align: center
|
||||
|
||||
Solidity is an object-oriented, high-level language for implementing smart
|
||||
contracts. Smart contracts are programs which govern the behaviour of accounts
|
||||
within the Ethereum state.
|
||||
@ -22,8 +17,10 @@ With Solidity you can create contracts for uses such as voting, crowdfunding, bl
|
||||
and multi-signature wallets.
|
||||
|
||||
When deploying contracts, you should use the latest released
|
||||
version of Solidity. This is because breaking changes as well as
|
||||
new features and bug fixes are introduced regularly. We currently use
|
||||
version of Solidity. Apart from exceptional cases, only the latest version receives
|
||||
`security fixes<https://github.com/ethereum/solidity/security/policy#supported-versions>`.
|
||||
Furthermore, breaking changes as well as
|
||||
new features are introduced regularly. We currently use
|
||||
a 0.x version number `to indicate this fast pace of change <https://semver.org/#spec-item-4>`_.
|
||||
|
||||
.. warning::
|
||||
|
@ -275,7 +275,7 @@ The following transformation steps are the main components:
|
||||
- Common Subexpression Eliminator
|
||||
- Expression Simplifier
|
||||
- Redundant Assign Eliminator
|
||||
- Full Function Inliner
|
||||
- Full Inliner
|
||||
|
||||
Optimizer Steps
|
||||
---------------
|
||||
@ -297,7 +297,8 @@ on the individual steps and their sequence below.
|
||||
- :ref:`for-loop-condition-into-body`.
|
||||
- :ref:`for-loop-condition-out-of-body`.
|
||||
- :ref:`for-loop-init-rewriter`.
|
||||
- :ref:`functional-inliner`.
|
||||
- :ref:`expression-inliner`.
|
||||
- :ref:`full-inliner`.
|
||||
- :ref:`function-grouper`.
|
||||
- :ref:`function-hoister`.
|
||||
- :ref:`function-specializer`.
|
||||
@ -1082,9 +1083,9 @@ The actual removal of the function is performed by the Unused Pruner.
|
||||
Function Inlining
|
||||
-----------------
|
||||
|
||||
.. _functional-inliner:
|
||||
.. _expression-inliner:
|
||||
|
||||
FunctionalInliner
|
||||
ExpressionInliner
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
This component of the optimizer performs restricted function inlining by inlining functions that can be
|
||||
@ -1107,12 +1108,12 @@ The result of this inlining is always a single expression.
|
||||
|
||||
This component can only be used on sources with unique names.
|
||||
|
||||
.. _full-function-inliner:
|
||||
.. _full-inliner:
|
||||
|
||||
FullFunctionInliner
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
FullInliner
|
||||
^^^^^^^^^^^
|
||||
|
||||
The Full Function Inliner replaces certain calls of certain functions
|
||||
The Full Inliner replaces certain calls of certain functions
|
||||
by the function's body. This is not very helpful in most cases, because
|
||||
it just increases the code size but does not have a benefit. Furthermore,
|
||||
code is usually very expensive and we would often rather have shorter
|
||||
|
@ -64,3 +64,7 @@ This means the following source mappings represent the same information:
|
||||
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
|
||||
|
||||
``1:2:1;:9;2:1:2;;``
|
||||
|
||||
Important to note is that when the :ref:`verbatim <yul-verbatim>` builtin is used,
|
||||
the source mappings will be invalid: The builtin is considered a single
|
||||
instruction instead of potentially multiple.
|
||||
|
@ -14,7 +14,12 @@ hashes or sent as the data of a message call. Similarly, before
|
||||
storing a value in the storage, the remaining bits need to be cleaned
|
||||
because otherwise the garbled value can be observed.
|
||||
|
||||
On the other hand, we do not clean the bits if the immediately
|
||||
Note that access via inline assembly is not considered such an operation:
|
||||
If you use inline assembly to access Solidity variables
|
||||
shorter than 256 bits, the compiler does not guarantee that
|
||||
the value is properly cleaned up.
|
||||
|
||||
Moreover, we do not clean the bits if the immediately
|
||||
following operation is not affected. For instance, since any non-zero
|
||||
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
|
||||
the boolean values before they are used as the condition for
|
||||
|
@ -1,3 +1,6 @@
|
||||
|
||||
.. index: ir breaking changes
|
||||
|
||||
*********************************
|
||||
Solidity IR-based Codegen Changes
|
||||
*********************************
|
||||
@ -35,13 +38,15 @@ hiding new and different behavior in existing code.
|
||||
|
||||
We have the same behavior for implicit delete, for example when array of structs is shortened.
|
||||
|
||||
- Function modifiers are implemented in a slightly different way regarding function parameters.
|
||||
- Function modifiers are implemented in a slightly different way regarding function parameters and return variables.
|
||||
This especially has an effect if the placeholder ``_;`` is evaluated multiple times in a modifier.
|
||||
In the old code generator, each function parameter has a fixed slot on the stack. If the function
|
||||
is run multiple times because ``_;`` is used multiple times or used in a loop, then a change to the
|
||||
function parameter's value is visible in the next execution of the function.
|
||||
In the old code generator, each function parameter and return variable has a fixed slot on the stack.
|
||||
If the function is run multiple times because ``_;`` is used multiple times or used in a loop, then a
|
||||
change to the function parameter's or return variable's value is visible in the next execution of the function.
|
||||
The new code generator implements modifiers using actual functions and passes function parameters on.
|
||||
This means that multiple executions of a function will get the same values for the parameters.
|
||||
This means that multiple evaluations of a function's body will get the same values for the parameters,
|
||||
and the effect on return variables is that they are reset to their default (zero) value for each
|
||||
execution.
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
@ -57,6 +62,35 @@ hiding new and different behavior in existing code.
|
||||
If you execute ``f(0)`` in the old code generator, it will return ``2``, while
|
||||
it will return ``1`` when using the new code generator.
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.7.1 <0.9.0;
|
||||
|
||||
contract C {
|
||||
bool active = true;
|
||||
modifier mod()
|
||||
{
|
||||
_;
|
||||
active = false;
|
||||
_;
|
||||
}
|
||||
function foo() external mod() returns (uint ret)
|
||||
{
|
||||
if (active)
|
||||
ret = 1; // Same as ``return 1``
|
||||
}
|
||||
}
|
||||
|
||||
The function ``C.foo()`` returns the following values:
|
||||
|
||||
- Old code generator: ``1`` as the return variable is initialized to ``0`` only once before the first ``_;``
|
||||
evaluation and then overwritten by the ``return 1;``. It is not initialized again for the second ``_;``
|
||||
evaluation and ``foo()`` does not explicitly assign it either (due to ``active == false``), thus it keeps
|
||||
its first value.
|
||||
- New code generator: ``0`` as all parameters, including return parameters, will be re-initialized before
|
||||
each ``_;`` evaluation.
|
||||
|
||||
- The order of contract initialization has changed in case of inheritance.
|
||||
|
||||
The order used to be:
|
||||
|
@ -92,6 +92,12 @@ Block and Transaction Properties
|
||||
``msg.value`` can change for every **external** function call.
|
||||
This includes calls to library functions.
|
||||
|
||||
.. note::
|
||||
When contracts are evaluated off-chain rather than in context of a transaction included in a
|
||||
block, you should not assume that ``block.*`` and ``tx.*`` refer to values from any specific
|
||||
block or transaction. These values are provided by the EVM implementation that executes the
|
||||
contract and can be arbitrary.
|
||||
|
||||
.. note::
|
||||
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
|
||||
unless you know what you are doing.
|
||||
|
@ -308,7 +308,18 @@ Input Description
|
||||
// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects
|
||||
// "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now.
|
||||
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
|
||||
"revertStrings": "default"
|
||||
"revertStrings": "default",
|
||||
// Optional: How much extra debug information to include in comments in the produced EVM
|
||||
// assembly and Yul code. Available components are:
|
||||
// - `location`: Annotations of the form `@src <index>:<start>:<end>` indicating the
|
||||
// location of the corresponding element in the original Solidity file, where:
|
||||
// - `<index>` is the file index matching the `@use-src` annotation,
|
||||
// - `<start>` is the index of the first byte at that location,
|
||||
// - `<end>` is the index of the first byte after that location.
|
||||
// - `snippet`: A single-line code snippet from the location indicated by `@src`.
|
||||
// The snippet is quoted and follows the corresponding `@src` annotation.
|
||||
// - `*`: Wildcard value that can be used to request everything.
|
||||
"debugInfo": ["location", "snippet"]
|
||||
},
|
||||
// Metadata settings (optional)
|
||||
"metadata": {
|
||||
|
@ -1002,6 +1002,8 @@ within one Yul subobject. If at least one ``memoryguard`` call is found in a sub
|
||||
the additional optimiser steps will be run on it.
|
||||
|
||||
|
||||
.. _yul-verbatim:
|
||||
|
||||
verbatim
|
||||
^^^^^^^^
|
||||
|
||||
@ -1263,6 +1265,7 @@ Abbreviation Full name
|
||||
``a`` ``SSATransform``
|
||||
``t`` ``StructuralSimplifier``
|
||||
``u`` ``UnusedPruner``
|
||||
``p`` ``UnusedFunctionParameterPruner``
|
||||
``d`` ``VarDeclInitializer``
|
||||
============ ===============================
|
||||
|
||||
|
@ -48,11 +48,11 @@ using namespace solidity::evmasm;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::util;
|
||||
|
||||
AssemblyItem const& Assembly::append(AssemblyItem const& _i)
|
||||
AssemblyItem const& Assembly::append(AssemblyItem _i)
|
||||
{
|
||||
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
|
||||
m_deposit += static_cast<int>(_i.deposit());
|
||||
m_items.emplace_back(_i);
|
||||
m_items.emplace_back(move(_i));
|
||||
if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid())
|
||||
m_items.back().setLocation(m_currentSourceLocation);
|
||||
m_items.back().m_modifierDepth = m_currentModifierDepth;
|
||||
@ -68,7 +68,7 @@ unsigned Assembly::codeSize(unsigned subTagSize) const
|
||||
ret += i.second.size();
|
||||
|
||||
for (AssemblyItem const& i: m_items)
|
||||
ret += i.bytesRequired(tagSize);
|
||||
ret += i.bytesRequired(tagSize, Precision::Approximate);
|
||||
if (numberEncodingSize(ret) <= tagSize)
|
||||
return static_cast<unsigned>(ret);
|
||||
}
|
||||
@ -96,13 +96,13 @@ public:
|
||||
m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly)
|
||||
{}
|
||||
|
||||
void feed(AssemblyItem const& _item)
|
||||
void feed(AssemblyItem const& _item, DebugInfoSelection const& _debugInfoSelection)
|
||||
{
|
||||
if (_item.location().isValid() && _item.location() != m_location)
|
||||
{
|
||||
flush();
|
||||
m_location = _item.location();
|
||||
printLocation();
|
||||
printLocation(_debugInfoSelection);
|
||||
}
|
||||
|
||||
string expression = _item.toAssemblyText(m_assembly);
|
||||
@ -142,16 +142,29 @@ public:
|
||||
m_pending.clear();
|
||||
}
|
||||
|
||||
void printLocation()
|
||||
void printLocation(DebugInfoSelection const& _debugInfoSelection)
|
||||
{
|
||||
if (!m_location.isValid())
|
||||
if (!m_location.isValid() || (!_debugInfoSelection.location && !_debugInfoSelection.snippet))
|
||||
return;
|
||||
|
||||
m_out << m_prefix << " /*";
|
||||
if (m_location.sourceName)
|
||||
m_out << " " + escapeAndQuoteString(*m_location.sourceName);
|
||||
if (m_location.hasText())
|
||||
m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
|
||||
m_out << " " << locationFromSources(m_sourceCodes, m_location);
|
||||
|
||||
if (_debugInfoSelection.location)
|
||||
{
|
||||
if (m_location.sourceName)
|
||||
m_out << " " + escapeAndQuoteString(*m_location.sourceName);
|
||||
if (m_location.hasText())
|
||||
m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
|
||||
}
|
||||
|
||||
if (_debugInfoSelection.snippet)
|
||||
{
|
||||
if (_debugInfoSelection.location)
|
||||
m_out << " ";
|
||||
|
||||
m_out << locationFromSources(m_sourceCodes, m_location);
|
||||
}
|
||||
|
||||
m_out << " */" << endl;
|
||||
}
|
||||
|
||||
@ -167,12 +180,17 @@ private:
|
||||
|
||||
}
|
||||
|
||||
void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
|
||||
void Assembly::assemblyStream(
|
||||
ostream& _out,
|
||||
DebugInfoSelection const& _debugInfoSelection,
|
||||
string const& _prefix,
|
||||
StringMap const& _sourceCodes
|
||||
) const
|
||||
{
|
||||
Functionalizer f(_out, _prefix, _sourceCodes, *this);
|
||||
|
||||
for (auto const& i: m_items)
|
||||
f.feed(i);
|
||||
f.feed(i, _debugInfoSelection);
|
||||
f.flush();
|
||||
|
||||
if (!m_data.empty() || !m_subs.empty())
|
||||
@ -185,7 +203,7 @@ void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap co
|
||||
for (size_t i = 0; i < m_subs.size(); ++i)
|
||||
{
|
||||
_out << endl << _prefix << "sub_" << i << ": assembly {\n";
|
||||
m_subs[i]->assemblyStream(_out, _prefix + " ", _sourceCodes);
|
||||
m_subs[i]->assemblyStream(_out, _debugInfoSelection, _prefix + " ", _sourceCodes);
|
||||
_out << _prefix << "}" << endl;
|
||||
}
|
||||
}
|
||||
@ -194,10 +212,13 @@ void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap co
|
||||
_out << endl << _prefix << "auxdata: 0x" << util::toHex(m_auxiliaryData) << endl;
|
||||
}
|
||||
|
||||
string Assembly::assemblyString(StringMap const& _sourceCodes) const
|
||||
string Assembly::assemblyString(
|
||||
DebugInfoSelection const& _debugInfoSelection,
|
||||
StringMap const& _sourceCodes
|
||||
) const
|
||||
{
|
||||
ostringstream tmp;
|
||||
assemblyStream(tmp, "", _sourceCodes);
|
||||
assemblyStream(tmp, _debugInfoSelection, "", _sourceCodes);
|
||||
return tmp.str();
|
||||
}
|
||||
|
||||
@ -675,8 +696,11 @@ LinkerObject const& Assembly::assemble() const
|
||||
break;
|
||||
case PushImmutable:
|
||||
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH32));
|
||||
// Maps keccak back to the "identifier" string of that immutable.
|
||||
ret.immutableReferences[i.data()].first = m_immutables.at(i.data());
|
||||
// Record the bytecode offset of the PUSH32 argument.
|
||||
ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
|
||||
// Advance bytecode by 32 bytes (default initialized).
|
||||
ret.bytecode.resize(ret.bytecode.size() + 32);
|
||||
break;
|
||||
case VerbatimBytecode:
|
||||
@ -684,6 +708,7 @@ LinkerObject const& Assembly::assemble() const
|
||||
break;
|
||||
case AssignImmutable:
|
||||
{
|
||||
// Expect 2 elements on stack (source, dest_base)
|
||||
auto const& offsets = immutableReferencesBySub[i.data()].second;
|
||||
for (size_t i = 0; i < offsets.size(); ++i)
|
||||
{
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <libevmasm/LinkerObject.h>
|
||||
#include <libevmasm/Exceptions.h>
|
||||
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
@ -64,7 +65,7 @@ public:
|
||||
AssemblyItem newPushImmutable(std::string const& _identifier);
|
||||
AssemblyItem newImmutableAssignment(std::string const& _identifier);
|
||||
|
||||
AssemblyItem const& append(AssemblyItem const& _i);
|
||||
AssemblyItem const& append(AssemblyItem _i);
|
||||
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
|
||||
|
||||
template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }
|
||||
@ -142,10 +143,12 @@ public:
|
||||
|
||||
/// Create a text representation of the assembly.
|
||||
std::string assemblyString(
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
|
||||
StringMap const& _sourceCodes = StringMap()
|
||||
) const;
|
||||
void assemblyStream(
|
||||
std::ostream& _out,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
|
||||
std::string const& _prefix = "",
|
||||
StringMap const& _sourceCodes = StringMap()
|
||||
) const;
|
||||
|
@ -65,7 +65,7 @@ void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
|
||||
setData(data);
|
||||
}
|
||||
|
||||
size_t AssemblyItem::bytesRequired(size_t _addressLength) const
|
||||
size_t AssemblyItem::bytesRequired(size_t _addressLength, Precision _precision) const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
@ -87,10 +87,25 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
|
||||
case PushImmutable:
|
||||
return 1 + 32;
|
||||
case AssignImmutable:
|
||||
if (m_immutableOccurrences)
|
||||
return 1 + (3 + 32) * *m_immutableOccurrences;
|
||||
{
|
||||
unsigned long immutableOccurrences = 0;
|
||||
|
||||
// Skip exact immutables count if no precise count was requested
|
||||
if (_precision == Precision::Approximate)
|
||||
immutableOccurrences = 1; // Assume one immut. ref.
|
||||
else
|
||||
return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
|
||||
{
|
||||
solAssert(m_immutableOccurrences, "No immutable references. `bytesRequired()` called before assembly()?");
|
||||
immutableOccurrences = m_immutableOccurrences.value();
|
||||
}
|
||||
|
||||
if (immutableOccurrences != 0)
|
||||
// (DUP DUP PUSH <n> ADD MSTORE)* (PUSH <n> ADD MSTORE)
|
||||
return (immutableOccurrences - 1) * (5 + 32) + (3 + 32);
|
||||
else
|
||||
// POP POP
|
||||
return 2;
|
||||
}
|
||||
case VerbatimBytecode:
|
||||
return std::get<2>(*m_verbatimBytecode).size();
|
||||
default:
|
||||
@ -322,6 +337,26 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
|
||||
return _out;
|
||||
}
|
||||
|
||||
size_t AssemblyItem::opcodeCount() const noexcept
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case AssemblyItemType::AssignImmutable:
|
||||
// Append empty items if this AssignImmutable was referenced more than once.
|
||||
// For n immutable occurrences the first (n - 1) occurrences will
|
||||
// generate 5 opcodes and the last will generate 3 opcodes,
|
||||
// because it is reusing the 2 top-most elements on the stack.
|
||||
solAssert(m_immutableOccurrences, "");
|
||||
|
||||
if (m_immutableOccurrences.value() != 0)
|
||||
return (*m_immutableOccurrences - 1) * 5 + 3;
|
||||
else
|
||||
return 2; // two POP's
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string AssemblyItem::computeSourceMapping(
|
||||
AssemblyItems const& _items,
|
||||
map<string, unsigned> const& _sourceIndicesMap
|
||||
@ -334,6 +369,7 @@ std::string AssemblyItem::computeSourceMapping(
|
||||
int prevSourceIndex = -1;
|
||||
int prevModifierDepth = -1;
|
||||
char prevJump = 0;
|
||||
|
||||
for (auto const& item: _items)
|
||||
{
|
||||
if (!ret.empty())
|
||||
@ -402,6 +438,9 @@ std::string AssemblyItem::computeSourceMapping(
|
||||
}
|
||||
}
|
||||
|
||||
if (item.opcodeCount() > 1)
|
||||
ret += string(item.opcodeCount() - 1, ';');
|
||||
|
||||
prevStart = location.start;
|
||||
prevLength = length;
|
||||
prevSourceIndex = sourceIndex;
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <optional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
@ -51,6 +52,8 @@ enum AssemblyItemType
|
||||
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
|
||||
};
|
||||
|
||||
enum class Precision { Precise , Approximate };
|
||||
|
||||
class Assembly;
|
||||
class AssemblyItem;
|
||||
using AssemblyItems = std::vector<AssemblyItem>;
|
||||
@ -147,7 +150,10 @@ public:
|
||||
|
||||
/// @returns an upper bound for the number of bytes required by this item, assuming that
|
||||
/// the value of a jump tag takes @a _addressLength bytes.
|
||||
size_t bytesRequired(size_t _addressLength) const;
|
||||
/// @param _precision Whether to return a precise count (which involves
|
||||
/// counting immutable references which are only set after
|
||||
/// a call to `assemble()`) or an approx. count.
|
||||
size_t bytesRequired(size_t _addressLength, Precision _precision = Precision::Precise) const;
|
||||
size_t arguments() const;
|
||||
size_t returnValues() const;
|
||||
size_t deposit() const { return returnValues() - arguments(); }
|
||||
@ -169,9 +175,11 @@ public:
|
||||
|
||||
size_t m_modifierDepth = 0;
|
||||
|
||||
void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = std::make_shared<size_t>(_n); }
|
||||
void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = _n; }
|
||||
|
||||
private:
|
||||
size_t opcodeCount() const noexcept;
|
||||
|
||||
AssemblyItemType m_type;
|
||||
Instruction m_instruction; ///< Only valid if m_type == Operation
|
||||
std::shared_ptr<u256> m_data; ///< Only valid if m_type != Operation
|
||||
@ -184,14 +192,14 @@ private:
|
||||
/// e.g. PushSubSize, PushTag, PushSub, etc.
|
||||
mutable std::shared_ptr<u256> m_pushedValue;
|
||||
/// Number of PushImmutable's with the same hash. Only used for AssignImmutable.
|
||||
mutable std::shared_ptr<size_t> m_immutableOccurrences;
|
||||
mutable std::optional<size_t> m_immutableOccurrences;
|
||||
};
|
||||
|
||||
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
|
||||
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength, Precision _precision = Precision::Precise)
|
||||
{
|
||||
size_t size = 0;
|
||||
for (AssemblyItem const& item: _items)
|
||||
size += item.bytesRequired(_addressLength);
|
||||
size += item.bytesRequired(_addressLength, _precision);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -38,4 +38,4 @@ set(sources
|
||||
)
|
||||
|
||||
add_library(evmasm ${sources})
|
||||
target_link_libraries(evmasm PUBLIC solutil)
|
||||
target_link_libraries(evmasm PUBLIC solutil fmt::fmt-header-only)
|
||||
|
@ -103,7 +103,7 @@ bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
|
||||
|
||||
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items)
|
||||
{
|
||||
return evmasm::bytesRequired(_items, 3); // assume 3 byte addresses
|
||||
return evmasm::bytesRequired(_items, 3, Precision::Approximate); // assume 3 byte addresses
|
||||
}
|
||||
|
||||
void ConstantOptimisationMethod::replaceConstants(
|
||||
|
@ -63,7 +63,7 @@ template<typename RangeType>
|
||||
uint64_t codeSize(RangeType const& _itemRange)
|
||||
{
|
||||
return ranges::accumulate(_itemRange | ranges::views::transform(
|
||||
[](auto const& _item) { return _item.bytesRequired(2); }
|
||||
[](auto const& _item) { return _item.bytesRequired(2, Precision::Approximate); }
|
||||
), 0u);
|
||||
}
|
||||
/// @returns the tag id, if @a _item is a PushTag or Tag into the current subassembly, nullopt otherwise.
|
||||
|
@ -388,6 +388,8 @@ size_t numberOfPops(AssemblyItems const& _items)
|
||||
|
||||
bool PeepholeOptimiser::optimise()
|
||||
{
|
||||
// Avoid referencing immutables too early by using approx. counting in bytesRequired()
|
||||
auto const approx = evmasm::Precision::Approximate;
|
||||
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
|
||||
while (state.i < m_items.size())
|
||||
applyMethods(
|
||||
@ -398,7 +400,7 @@ bool PeepholeOptimiser::optimise()
|
||||
);
|
||||
if (m_optimisedItems.size() < m_items.size() || (
|
||||
m_optimisedItems.size() == m_items.size() && (
|
||||
evmasm::bytesRequired(m_optimisedItems, 3) < evmasm::bytesRequired(m_items, 3) ||
|
||||
evmasm::bytesRequired(m_optimisedItems, 3, approx) < evmasm::bytesRequired(m_items, 3, approx) ||
|
||||
numberOfPops(m_optimisedItems) > numberOfPops(m_items)
|
||||
)
|
||||
))
|
||||
|
@ -3,6 +3,8 @@ set(sources
|
||||
Common.h
|
||||
CharStream.cpp
|
||||
CharStream.h
|
||||
DebugInfoSelection.cpp
|
||||
DebugInfoSelection.h
|
||||
ErrorReporter.cpp
|
||||
ErrorReporter.h
|
||||
EVMVersion.h
|
||||
@ -29,4 +31,4 @@ set(sources
|
||||
)
|
||||
|
||||
add_library(langutil ${sources})
|
||||
target_link_libraries(langutil PUBLIC solutil)
|
||||
target_link_libraries(langutil PUBLIC solutil fmt::fmt-header-only)
|
||||
|
157
liblangutil/DebugInfoSelection.cpp
Normal file
157
liblangutil/DebugInfoSelection.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
|
||||
#include <range/v3/range/conversion.hpp>
|
||||
#include <range/v3/view/map.hpp>
|
||||
#include <range/v3/view/split.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::util;
|
||||
|
||||
DebugInfoSelection const DebugInfoSelection::All(bool _value) noexcept
|
||||
{
|
||||
DebugInfoSelection result;
|
||||
for (bool DebugInfoSelection::* member: componentMap() | ranges::views::values)
|
||||
result.*member = _value;
|
||||
return result;
|
||||
}
|
||||
|
||||
DebugInfoSelection const DebugInfoSelection::Only(bool DebugInfoSelection::* _member) noexcept
|
||||
{
|
||||
DebugInfoSelection result{};
|
||||
result.*_member = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
optional<DebugInfoSelection> DebugInfoSelection::fromString(string_view _input)
|
||||
{
|
||||
// TODO: Make more stuff constexpr and make it a static_assert().
|
||||
solAssert(componentMap().count("all") == 0, "");
|
||||
solAssert(componentMap().count("none") == 0, "");
|
||||
|
||||
if (_input == "all")
|
||||
return All();
|
||||
if (_input == "none")
|
||||
return None();
|
||||
|
||||
return fromComponents(_input | ranges::views::split(',') | ranges::to<vector<string>>);
|
||||
}
|
||||
|
||||
optional<DebugInfoSelection> DebugInfoSelection::fromComponents(
|
||||
vector<string> const& _componentNames,
|
||||
bool _acceptWildcards
|
||||
)
|
||||
{
|
||||
solAssert(componentMap().count("*") == 0, "");
|
||||
|
||||
DebugInfoSelection selection;
|
||||
for (auto const& component: _componentNames)
|
||||
{
|
||||
if (component == "*")
|
||||
return (_acceptWildcards ? make_optional(DebugInfoSelection::All()) : nullopt);
|
||||
|
||||
if (!selection.enable(component))
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
bool DebugInfoSelection::enable(string _component)
|
||||
{
|
||||
auto memberIt = componentMap().find(boost::trim_copy(_component));
|
||||
if (memberIt == componentMap().end())
|
||||
return false;
|
||||
|
||||
this->*(memberIt->second) = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugInfoSelection::any() const noexcept
|
||||
{
|
||||
for (bool DebugInfoSelection::* member: componentMap() | ranges::views::values)
|
||||
if (this->*member)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DebugInfoSelection::all() const noexcept
|
||||
{
|
||||
for (bool DebugInfoSelection::* member: componentMap() | ranges::views::values)
|
||||
if (!(this->*member))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DebugInfoSelection& DebugInfoSelection::operator&=(DebugInfoSelection const& _other)
|
||||
{
|
||||
for (bool DebugInfoSelection::* member: componentMap() | ranges::views::values)
|
||||
this->*member &= _other.*member;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DebugInfoSelection& DebugInfoSelection::operator|=(DebugInfoSelection const& _other)
|
||||
{
|
||||
for (bool DebugInfoSelection::* member: componentMap() | ranges::views::values)
|
||||
this->*member |= _other.*member;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DebugInfoSelection DebugInfoSelection::operator&(DebugInfoSelection _other) const noexcept
|
||||
{
|
||||
_other &= *this;
|
||||
return _other;
|
||||
}
|
||||
|
||||
DebugInfoSelection DebugInfoSelection::operator|(DebugInfoSelection _other) const noexcept
|
||||
{
|
||||
_other |= *this;
|
||||
return _other;
|
||||
}
|
||||
|
||||
bool DebugInfoSelection::operator==(DebugInfoSelection const& _other) const noexcept
|
||||
{
|
||||
for (bool DebugInfoSelection::* member: componentMap() | ranges::views::values)
|
||||
if (this->*member != _other.*member)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ostream& langutil::operator<<(ostream& _stream, DebugInfoSelection const& _selection)
|
||||
{
|
||||
vector<string> selectedComponentNames;
|
||||
for (auto const& [name, member]: _selection.componentMap())
|
||||
if (_selection.*member)
|
||||
selectedComponentNames.push_back(name);
|
||||
|
||||
return _stream << joinHumanReadable(selectedComponentNames, ",");
|
||||
}
|
86
liblangutil/DebugInfoSelection.h
Normal file
86
liblangutil/DebugInfoSelection.h
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
/**
|
||||
* Handles selections of debug info components.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
|
||||
/**
|
||||
* Represents a set of flags corresponding to components of debug info selected for some purpose.
|
||||
*
|
||||
* Provides extra functionality for enumerating the components and serializing/deserializing the
|
||||
* selection to/from a comma-separated string.
|
||||
*/
|
||||
struct DebugInfoSelection
|
||||
{
|
||||
static DebugInfoSelection const All(bool _value = true) noexcept;
|
||||
static DebugInfoSelection const None() noexcept { return All(false); }
|
||||
static DebugInfoSelection const Only(bool DebugInfoSelection::* _member) noexcept;
|
||||
static DebugInfoSelection const Default() noexcept { return All(); }
|
||||
|
||||
static std::optional<DebugInfoSelection> fromString(std::string_view _input);
|
||||
static std::optional<DebugInfoSelection> fromComponents(
|
||||
std::vector<std::string> const& _componentNames,
|
||||
bool _acceptWildcards = false
|
||||
);
|
||||
bool enable(std::string _component);
|
||||
|
||||
bool all() const noexcept;
|
||||
bool any() const noexcept;
|
||||
bool none() const noexcept { return !any(); }
|
||||
bool only(bool DebugInfoSelection::* _member) const noexcept { return *this == Only(_member); }
|
||||
|
||||
DebugInfoSelection& operator&=(DebugInfoSelection const& _other);
|
||||
DebugInfoSelection& operator|=(DebugInfoSelection const& _other);
|
||||
DebugInfoSelection operator&(DebugInfoSelection _other) const noexcept;
|
||||
DebugInfoSelection operator|(DebugInfoSelection _other) const noexcept;
|
||||
|
||||
bool operator!=(DebugInfoSelection const& _other) const noexcept { return !(*this == _other); }
|
||||
bool operator==(DebugInfoSelection const& _other) const noexcept;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& _stream, DebugInfoSelection const& _selection);
|
||||
|
||||
static auto const& componentMap()
|
||||
{
|
||||
static std::map<std::string, bool DebugInfoSelection::*> const components = {
|
||||
{"location", &DebugInfoSelection::location},
|
||||
{"snippet", &DebugInfoSelection::snippet},
|
||||
{"ast-id", &DebugInfoSelection::astID},
|
||||
};
|
||||
return components;
|
||||
}
|
||||
|
||||
bool location = false; ///< Include source location. E.g. `@src 3:50:100`
|
||||
bool snippet = false; ///< Include source code snippet next to location. E.g. `@src 3:50:100 "contract C {..."`
|
||||
bool astID = false; ///< Include ID of the Solidity AST node. E.g. `@ast-id 15`
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& _stream, DebugInfoSelection const& _selection);
|
||||
|
||||
}
|
@ -59,6 +59,13 @@ struct SourceLocation
|
||||
return start <= _other.start && _other.end <= end;
|
||||
}
|
||||
|
||||
bool containsOffset(int _pos) const
|
||||
{
|
||||
if (!hasText() || _pos < 0)
|
||||
return false;
|
||||
return start <= _pos && _pos < end;
|
||||
}
|
||||
|
||||
bool intersects(SourceLocation const& _other) const
|
||||
{
|
||||
if (!hasText() || !_other.hasText() || !equalSources(_other))
|
||||
|
@ -159,4 +159,4 @@ set(sources
|
||||
)
|
||||
|
||||
add_library(solidity ${sources})
|
||||
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost)
|
||||
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only)
|
||||
|
@ -533,14 +533,14 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
|
||||
yul::ASTWalker::operator()(_functionCall);
|
||||
|
||||
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
|
||||
if (builtinFunction->controlFlowSideEffects.terminates)
|
||||
{
|
||||
if (builtinFunction->controlFlowSideEffects.reverts)
|
||||
connect(m_currentNode, m_revertNode);
|
||||
else
|
||||
connect(m_currentNode, m_transactionReturnNode);
|
||||
{
|
||||
if (builtinFunction->controlFlowSideEffects.canTerminate)
|
||||
connect(m_currentNode, m_transactionReturnNode);
|
||||
if (builtinFunction->controlFlowSideEffects.canRevert)
|
||||
connect(m_currentNode, m_revertNode);
|
||||
if (!builtinFunction->controlFlowSideEffects.canContinue)
|
||||
m_currentNode = newLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
|
||||
|
@ -25,7 +25,6 @@
|
||||
#include <libsolutil/Algorithms.h>
|
||||
#include <libsolutil/FunctionSelector.h>
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
|
@ -246,7 +246,10 @@ public:
|
||||
|
||||
/// @returns the declared name.
|
||||
ASTString const& name() const { return *m_name; }
|
||||
|
||||
/// @returns the location of the declared name itself or empty location if not available or unknown.
|
||||
SourceLocation const& nameLocation() const noexcept { return m_nameLocation; }
|
||||
|
||||
bool noVisibilitySpecified() const { return m_visibility == Visibility::Default; }
|
||||
Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; }
|
||||
bool isPublic() const { return visibility() >= Visibility::Public; }
|
||||
|
@ -41,7 +41,6 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/range/algorithm/copy.hpp>
|
||||
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
@ -2563,6 +2562,11 @@ string UserDefinedValueType::toString(bool /* _short */) const
|
||||
return *definition().annotation().canonicalName;
|
||||
}
|
||||
|
||||
string UserDefinedValueType::canonicalName() const
|
||||
{
|
||||
return *definition().annotation().canonicalName;
|
||||
}
|
||||
|
||||
vector<tuple<string, Type const*>> UserDefinedValueType::makeStackItems() const
|
||||
{
|
||||
return underlyingType().stackItems();
|
||||
|
@ -1141,7 +1141,7 @@ public:
|
||||
}
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
std::string canonicalName() const override { solAssert(false, ""); }
|
||||
std::string canonicalName() const override;
|
||||
std::string signatureInExternalFunction(bool) const override { solAssert(false, ""); }
|
||||
|
||||
protected:
|
||||
|
@ -135,11 +135,15 @@ string dispenseLocationComment(langutil::SourceLocation const& _location, IRGene
|
||||
{
|
||||
solAssert(_location.sourceName, "");
|
||||
_context.markSourceUsed(*_location.sourceName);
|
||||
return "/// " + AsmPrinter::formatSourceLocation(
|
||||
|
||||
string debugInfo = AsmPrinter::formatSourceLocation(
|
||||
_location,
|
||||
_context.sourceIndices(),
|
||||
_context.debugInfoSelection(),
|
||||
_context.soliditySourceProvider()
|
||||
);
|
||||
|
||||
return debugInfo.empty() ? "" : "/// " + debugInfo;
|
||||
}
|
||||
|
||||
string dispenseLocationComment(ASTNode const& _node, IRGenerationContext& _context)
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <libsolidity/codegen/ir/Common.h>
|
||||
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
@ -74,6 +75,7 @@ public:
|
||||
RevertStrings _revertStrings,
|
||||
OptimiserSettings _optimiserSettings,
|
||||
std::map<std::string, unsigned> _sourceIndices,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection,
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
@ -81,6 +83,7 @@ public:
|
||||
m_revertStrings(_revertStrings),
|
||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||
m_sourceIndices(std::move(_sourceIndices)),
|
||||
m_debugInfoSelection(_debugInfoSelection),
|
||||
m_soliditySourceProvider(_soliditySourceProvider)
|
||||
{}
|
||||
|
||||
@ -174,6 +177,7 @@ public:
|
||||
|
||||
bool immutableRegistered(VariableDeclaration const& _varDecl) const { return m_immutableVariables.count(&_varDecl); }
|
||||
|
||||
langutil::DebugInfoSelection debugInfoSelection() const { return m_debugInfoSelection; }
|
||||
langutil::CharStreamProvider const* soliditySourceProvider() const { return m_soliditySourceProvider; }
|
||||
|
||||
private:
|
||||
@ -220,6 +224,7 @@ private:
|
||||
|
||||
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
|
||||
|
||||
langutil::DebugInfoSelection m_debugInfoSelection = {};
|
||||
langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr;
|
||||
};
|
||||
|
||||
|
@ -95,7 +95,12 @@ pair<string, string> IRGenerator::run(
|
||||
{
|
||||
string const ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources));
|
||||
|
||||
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||
yul::AssemblyStack asmStack(
|
||||
m_evmVersion,
|
||||
yul::AssemblyStack::Language::StrictAssembly,
|
||||
m_optimiserSettings,
|
||||
m_context.debugInfoSelection()
|
||||
);
|
||||
if (!asmStack.parseAndAnalyze("", ir))
|
||||
{
|
||||
string errorMessage;
|
||||
@ -340,8 +345,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||
m_context.resetLocalVariables();
|
||||
Whiskers t(R"(
|
||||
/// @ast-id <astID>
|
||||
<sourceLocationComment>
|
||||
<astIDComment><sourceLocationComment>
|
||||
function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> {
|
||||
<retInit>
|
||||
<body>
|
||||
@ -349,7 +353,10 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||
<contractSourceLocationComment>
|
||||
)");
|
||||
|
||||
t("astID", to_string(_function.id()));
|
||||
if (m_context.debugInfoSelection().astID)
|
||||
t("astIDComment", "/// @ast-id " + to_string(_function.id()) + "\n");
|
||||
else
|
||||
t("astIDComment", "");
|
||||
t("sourceLocationComment", dispenseLocationComment(_function));
|
||||
t(
|
||||
"contractSourceLocationComment",
|
||||
@ -409,8 +416,7 @@ string IRGenerator::generateModifier(
|
||||
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||
m_context.resetLocalVariables();
|
||||
Whiskers t(R"(
|
||||
/// @ast-id <astID>
|
||||
<sourceLocationComment>
|
||||
<astIDComment><sourceLocationComment>
|
||||
function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> {
|
||||
<assignRetParams>
|
||||
<evalArgs>
|
||||
@ -418,6 +424,7 @@ string IRGenerator::generateModifier(
|
||||
}
|
||||
<contractSourceLocationComment>
|
||||
)");
|
||||
|
||||
t("functionName", functionName);
|
||||
vector<string> retParamsIn;
|
||||
for (auto const& varDecl: _function.returnParameters())
|
||||
@ -440,7 +447,11 @@ string IRGenerator::generateModifier(
|
||||
_modifierInvocation.name().annotation().referencedDeclaration
|
||||
);
|
||||
solAssert(modifier, "");
|
||||
t("astID", to_string(modifier->id()));
|
||||
|
||||
if (m_context.debugInfoSelection().astID)
|
||||
t("astIDComment", "/// @ast-id " + to_string(modifier->id()) + "\n");
|
||||
else
|
||||
t("astIDComment", "");
|
||||
t("sourceLocationComment", dispenseLocationComment(*modifier));
|
||||
t(
|
||||
"contractSourceLocationComment",
|
||||
@ -546,14 +557,18 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
solAssert(paramTypes.empty(), "");
|
||||
solUnimplementedAssert(type->sizeOnStack() == 1);
|
||||
return Whiskers(R"(
|
||||
/// @ast-id <astID>
|
||||
<sourceLocationComment>
|
||||
<astIDComment><sourceLocationComment>
|
||||
function <functionName>() -> rval {
|
||||
rval := loadimmutable("<id>")
|
||||
}
|
||||
<contractSourceLocationComment>
|
||||
)")
|
||||
("astID", to_string(_varDecl.id()))
|
||||
(
|
||||
"astIDComment",
|
||||
m_context.debugInfoSelection().astID ?
|
||||
"/// @ast-id " + to_string(_varDecl.id()) + "\n" :
|
||||
""
|
||||
)
|
||||
("sourceLocationComment", dispenseLocationComment(_varDecl))
|
||||
(
|
||||
"contractSourceLocationComment",
|
||||
@ -567,14 +582,18 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(paramTypes.empty(), "");
|
||||
return Whiskers(R"(
|
||||
/// @ast-id <astID>
|
||||
<sourceLocationComment>
|
||||
<astIDComment><sourceLocationComment>
|
||||
function <functionName>() -> <ret> {
|
||||
<ret> := <constantValueFunction>()
|
||||
}
|
||||
<contractSourceLocationComment>
|
||||
)")
|
||||
("astID", to_string(_varDecl.id()))
|
||||
(
|
||||
"astIDComment",
|
||||
m_context.debugInfoSelection().astID ?
|
||||
"/// @ast-id " + to_string(_varDecl.id()) + "\n" :
|
||||
""
|
||||
)
|
||||
("sourceLocationComment", dispenseLocationComment(_varDecl))
|
||||
(
|
||||
"contractSourceLocationComment",
|
||||
@ -691,8 +710,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
}
|
||||
|
||||
return Whiskers(R"(
|
||||
/// @ast-id <astID>
|
||||
<sourceLocationComment>
|
||||
<astIDComment><sourceLocationComment>
|
||||
function <functionName>(<params>) -> <retVariables> {
|
||||
<code>
|
||||
}
|
||||
@ -702,7 +720,12 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
("params", joinHumanReadable(parameters))
|
||||
("retVariables", joinHumanReadable(returnVariables))
|
||||
("code", std::move(code))
|
||||
("astID", to_string(_varDecl.id()))
|
||||
(
|
||||
"astIDComment",
|
||||
m_context.debugInfoSelection().astID ?
|
||||
"/// @ast-id " + to_string(_varDecl.id()) + "\n" :
|
||||
""
|
||||
)
|
||||
("sourceLocationComment", dispenseLocationComment(_varDecl))
|
||||
(
|
||||
"contractSourceLocationComment",
|
||||
@ -829,7 +852,7 @@ void IRGenerator::generateConstructors(ContractDefinition const& _contract)
|
||||
for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters())
|
||||
params += m_context.addLocalVariable(*varDecl).stackSlots();
|
||||
|
||||
if (contract->constructor())
|
||||
if (m_context.debugInfoSelection().astID && contract->constructor())
|
||||
t("astIDComment", "/// @ast-id " + to_string(contract->constructor()->id()) + "\n");
|
||||
else
|
||||
t("astIDComment", "");
|
||||
@ -944,7 +967,7 @@ string IRGenerator::callValueCheck()
|
||||
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
{
|
||||
Whiskers t(R"X(
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
<?+cases>if iszero(lt(calldatasize(), 4))
|
||||
{
|
||||
let selector := <shr224>(calldataload(0))
|
||||
switch selector
|
||||
@ -962,8 +985,8 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
}
|
||||
</cases>
|
||||
default {}
|
||||
}
|
||||
if iszero(calldatasize()) { <receiveEther> }
|
||||
}</+cases>
|
||||
<?+receiveEther>if iszero(calldatasize()) { <receiveEther> }</+receiveEther>
|
||||
<fallback>
|
||||
)X");
|
||||
t("shr224", m_utils.shiftRightFunction(224));
|
||||
@ -1085,6 +1108,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionCon
|
||||
m_context.revertStrings(),
|
||||
m_optimiserSettings,
|
||||
m_context.sourceIndices(),
|
||||
m_context.debugInfoSelection(),
|
||||
m_context.soliditySourceProvider()
|
||||
);
|
||||
newContext.copyFunctionIDsFrom(m_context);
|
||||
|
@ -49,6 +49,7 @@ public:
|
||||
RevertStrings _revertStrings,
|
||||
OptimiserSettings _optimiserSettings,
|
||||
std::map<std::string, unsigned> _sourceIndices,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection,
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
@ -59,6 +60,7 @@ public:
|
||||
_revertStrings,
|
||||
std::move(_optimiserSettings),
|
||||
std::move(_sourceIndices),
|
||||
_debugInfoSelection,
|
||||
_soliditySourceProvider
|
||||
),
|
||||
m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector())
|
||||
|
@ -991,7 +991,7 @@ void CHC::resetSourceAnalysis()
|
||||
if (usesZ3)
|
||||
{
|
||||
/// z3::fixedpoint does not have a reset mechanism, so we need to create another.
|
||||
m_interface.reset(new Z3CHCInterface(m_settings.timeout));
|
||||
m_interface = std::make_unique<Z3CHCInterface>(m_settings.timeout);
|
||||
auto z3Interface = dynamic_cast<Z3CHCInterface const*>(m_interface.get());
|
||||
solAssert(z3Interface, "");
|
||||
m_context.setSolver(z3Interface->z3Interface());
|
||||
|
@ -201,7 +201,7 @@ void CompilerStack::findAndReportCyclicContractDependencies()
|
||||
void CompilerStack::setRemappings(vector<ImportRemapper::Remapping> _remappings)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set remappings before parsing."));
|
||||
solThrow(CompilerError, "Must set remappings before parsing.");
|
||||
for (auto const& remapping: _remappings)
|
||||
solAssert(!remapping.prefix.empty(), "");
|
||||
m_importRemapper.setRemappings(move(_remappings));
|
||||
@ -210,28 +210,28 @@ void CompilerStack::setRemappings(vector<ImportRemapper::Remapping> _remappings)
|
||||
void CompilerStack::setViaIR(bool _viaIR)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set viaIR before parsing."));
|
||||
solThrow(CompilerError, "Must set viaIR before parsing.");
|
||||
m_viaIR = _viaIR;
|
||||
}
|
||||
|
||||
void CompilerStack::setEVMVersion(langutil::EVMVersion _version)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set EVM version before parsing."));
|
||||
solThrow(CompilerError, "Must set EVM version before parsing.");
|
||||
m_evmVersion = _version;
|
||||
}
|
||||
|
||||
void CompilerStack::setModelCheckerSettings(ModelCheckerSettings _settings)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set model checking settings before parsing."));
|
||||
solThrow(CompilerError, "Must set model checking settings before parsing.");
|
||||
m_modelCheckerSettings = _settings;
|
||||
}
|
||||
|
||||
void CompilerStack::setLibraries(std::map<std::string, util::h160> const& _libraries)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set libraries before parsing."));
|
||||
solThrow(CompilerError, "Must set libraries before parsing.");
|
||||
m_libraries = _libraries;
|
||||
}
|
||||
|
||||
@ -245,14 +245,14 @@ void CompilerStack::setOptimiserSettings(bool _optimize, size_t _runs)
|
||||
void CompilerStack::setOptimiserSettings(OptimiserSettings _settings)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set optimiser settings before parsing."));
|
||||
solThrow(CompilerError, "Must set optimiser settings before parsing.");
|
||||
m_optimiserSettings = std::move(_settings);
|
||||
}
|
||||
|
||||
void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set revert string settings before parsing."));
|
||||
solThrow(CompilerError, "Must set revert string settings before parsing.");
|
||||
solUnimplementedAssert(_revertStrings != RevertStrings::VerboseDebug);
|
||||
m_revertStrings = _revertStrings;
|
||||
}
|
||||
@ -260,21 +260,28 @@ void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
|
||||
void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set use literal sources before parsing."));
|
||||
solThrow(CompilerError, "Must set use literal sources before parsing.");
|
||||
m_metadataLiteralSources = _metadataLiteralSources;
|
||||
}
|
||||
|
||||
void CompilerStack::setMetadataHash(MetadataHash _metadataHash)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set metadata hash before parsing."));
|
||||
solThrow(CompilerError, "Must set metadata hash before parsing.");
|
||||
m_metadataHash = _metadataHash;
|
||||
}
|
||||
|
||||
void CompilerStack::selectDebugInfo(DebugInfoSelection _debugInfoSelection)
|
||||
{
|
||||
if (m_stackState >= CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must select debug info components before compilation."));
|
||||
m_debugInfoSelection = _debugInfoSelection;
|
||||
}
|
||||
|
||||
void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _response)
|
||||
{
|
||||
if (m_stackState >= ParsedAndImported)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must add SMTLib2 responses before parsing."));
|
||||
solThrow(CompilerError, "Must add SMTLib2 responses before parsing.");
|
||||
m_smtlib2Responses[_hash] = _response;
|
||||
}
|
||||
|
||||
@ -310,9 +317,9 @@ void CompilerStack::reset(bool _keepSettings)
|
||||
void CompilerStack::setSources(StringMap _sources)
|
||||
{
|
||||
if (m_stackState == SourcesSet)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Cannot change sources once set."));
|
||||
solThrow(CompilerError, "Cannot change sources once set.");
|
||||
if (m_stackState != Empty)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set sources before parsing."));
|
||||
solThrow(CompilerError, "Must set sources before parsing.");
|
||||
for (auto source: _sources)
|
||||
m_sources[source.first].charStream = make_unique<CharStream>(/*content*/std::move(source.second), /*name*/source.first);
|
||||
m_stackState = SourcesSet;
|
||||
@ -321,7 +328,7 @@ void CompilerStack::setSources(StringMap _sources)
|
||||
bool CompilerStack::parse()
|
||||
{
|
||||
if (m_stackState != SourcesSet)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call parse only after the SourcesSet state."));
|
||||
solThrow(CompilerError, "Must call parse only after the SourcesSet state.");
|
||||
m_errorReporter.clear();
|
||||
|
||||
if (SemVerVersion{string(VersionString)}.isPrerelease())
|
||||
@ -369,7 +376,7 @@ bool CompilerStack::parse()
|
||||
void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
|
||||
{
|
||||
if (m_stackState != Empty)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call importASTs only before the SourcesSet state."));
|
||||
solThrow(CompilerError, "Must call importASTs only before the SourcesSet state.");
|
||||
m_sourceJsons = _sources;
|
||||
map<string, ASTPointer<SourceUnit>> reconstructedSources = ASTJsonImporter(m_evmVersion).jsonToSourceUnit(m_sourceJsons);
|
||||
for (auto& src: reconstructedSources)
|
||||
@ -392,7 +399,7 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
|
||||
bool CompilerStack::analyze()
|
||||
{
|
||||
if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was performed."));
|
||||
solThrow(CompilerError, "Must call analyze only after parsing was performed.");
|
||||
resolveImports();
|
||||
|
||||
for (Source const* source: m_sourceOrder)
|
||||
@ -619,7 +626,7 @@ bool CompilerStack::compile(State _stopAfter)
|
||||
return true;
|
||||
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors."));
|
||||
solThrow(CompilerError, "Called compile with errors.");
|
||||
|
||||
// Only compile contracts individually which have been requested.
|
||||
map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;
|
||||
@ -691,7 +698,7 @@ void CompilerStack::link()
|
||||
vector<string> CompilerStack::contractNames() const
|
||||
{
|
||||
if (m_stackState < Parsed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
|
||||
solThrow(CompilerError, "Parsing was not successful.");
|
||||
vector<string> contractNames;
|
||||
for (auto const& contract: m_contracts)
|
||||
contractNames.push_back(contract.first);
|
||||
@ -701,7 +708,7 @@ vector<string> CompilerStack::contractNames() const
|
||||
string const CompilerStack::lastContractName(optional<string> const& _sourceName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
|
||||
solThrow(CompilerError, "Parsing was not successful.");
|
||||
// try to find some user-supplied contract
|
||||
string contractName;
|
||||
for (auto const& it: m_sources)
|
||||
@ -714,7 +721,7 @@ string const CompilerStack::lastContractName(optional<string> const& _sourceName
|
||||
evmasm::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
Contract const& currentContract = contract(_contractName);
|
||||
return currentContract.evmAssembly ? ¤tContract.evmAssembly->items() : nullptr;
|
||||
@ -723,7 +730,7 @@ evmasm::AssemblyItems const* CompilerStack::assemblyItems(string const& _contrac
|
||||
evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
Contract const& currentContract = contract(_contractName);
|
||||
return currentContract.evmRuntimeAssembly ? ¤tContract.evmRuntimeAssembly->items() : nullptr;
|
||||
@ -732,7 +739,7 @@ evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _
|
||||
Json::Value CompilerStack::generatedSources(string const& _contractName, bool _runtime) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
Contract const& c = contract(_contractName);
|
||||
util::LazyInit<Json::Value const> const& sources =
|
||||
@ -775,7 +782,7 @@ Json::Value CompilerStack::generatedSources(string const& _contractName, bool _r
|
||||
string const* CompilerStack::sourceMapping(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
Contract const& c = contract(_contractName);
|
||||
if (!c.sourceMapping)
|
||||
@ -789,7 +796,7 @@ string const* CompilerStack::sourceMapping(string const& _contractName) const
|
||||
string const* CompilerStack::runtimeSourceMapping(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
Contract const& c = contract(_contractName);
|
||||
if (!c.runtimeSourceMapping)
|
||||
@ -805,7 +812,7 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
|
||||
std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found."));
|
||||
solThrow(CompilerError, "No compiled contracts found.");
|
||||
|
||||
// Look up the contract (by its fully-qualified name)
|
||||
Contract const& matchContract = m_contracts.at(_contractName);
|
||||
@ -829,7 +836,7 @@ std::string const CompilerStack::filesystemFriendlyName(string const& _contractN
|
||||
string const& CompilerStack::yulIR(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
return contract(_contractName).yulIR;
|
||||
}
|
||||
@ -837,7 +844,7 @@ string const& CompilerStack::yulIR(string const& _contractName) const
|
||||
string const& CompilerStack::yulIROptimized(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
return contract(_contractName).yulIROptimized;
|
||||
}
|
||||
@ -845,7 +852,7 @@ string const& CompilerStack::yulIROptimized(string const& _contractName) const
|
||||
string const& CompilerStack::ewasm(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
return contract(_contractName).ewasm;
|
||||
}
|
||||
@ -853,7 +860,7 @@ string const& CompilerStack::ewasm(string const& _contractName) const
|
||||
evmasm::LinkerObject const& CompilerStack::ewasmObject(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
return contract(_contractName).ewasmObject;
|
||||
}
|
||||
@ -861,7 +868,7 @@ evmasm::LinkerObject const& CompilerStack::ewasmObject(string const& _contractNa
|
||||
evmasm::LinkerObject const& CompilerStack::object(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
return contract(_contractName).object;
|
||||
}
|
||||
@ -869,7 +876,7 @@ evmasm::LinkerObject const& CompilerStack::object(string const& _contractName) c
|
||||
evmasm::LinkerObject const& CompilerStack::runtimeObject(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
return contract(_contractName).runtimeObject;
|
||||
}
|
||||
@ -878,11 +885,11 @@ evmasm::LinkerObject const& CompilerStack::runtimeObject(string const& _contract
|
||||
string CompilerStack::assemblyString(string const& _contractName, StringMap const& _sourceCodes) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
Contract const& currentContract = contract(_contractName);
|
||||
if (currentContract.evmAssembly)
|
||||
return currentContract.evmAssembly->assemblyString(_sourceCodes);
|
||||
return currentContract.evmAssembly->assemblyString(m_debugInfoSelection, _sourceCodes);
|
||||
else
|
||||
return string();
|
||||
}
|
||||
@ -891,7 +898,7 @@ string CompilerStack::assemblyString(string const& _contractName, StringMap cons
|
||||
Json::Value CompilerStack::assemblyJSON(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
Contract const& currentContract = contract(_contractName);
|
||||
if (currentContract.evmAssembly)
|
||||
@ -922,7 +929,7 @@ map<string, unsigned> CompilerStack::sourceIndices() const
|
||||
Json::Value const& CompilerStack::contractABI(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
return contractABI(contract(_contractName));
|
||||
}
|
||||
@ -930,7 +937,7 @@ Json::Value const& CompilerStack::contractABI(string const& _contractName) const
|
||||
Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
|
||||
@ -940,7 +947,7 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
|
||||
Json::Value const& CompilerStack::storageLayout(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
return storageLayout(contract(_contractName));
|
||||
}
|
||||
@ -948,7 +955,7 @@ Json::Value const& CompilerStack::storageLayout(string const& _contractName) con
|
||||
Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
|
||||
@ -958,7 +965,7 @@ Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const
|
||||
Json::Value const& CompilerStack::natspecUser(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
return natspecUser(contract(_contractName));
|
||||
}
|
||||
@ -966,7 +973,7 @@ Json::Value const& CompilerStack::natspecUser(string const& _contractName) const
|
||||
Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
|
||||
@ -976,7 +983,7 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const
|
||||
Json::Value const& CompilerStack::natspecDev(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
return natspecDev(contract(_contractName));
|
||||
}
|
||||
@ -984,7 +991,7 @@ Json::Value const& CompilerStack::natspecDev(string const& _contractName) const
|
||||
Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
|
||||
@ -994,7 +1001,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
|
||||
Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
Json::Value methodIdentifiers(Json::objectValue);
|
||||
for (auto const& it: contractDefinition(_contractName).interfaceFunctions())
|
||||
@ -1005,7 +1012,7 @@ Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
|
||||
bytes CompilerStack::cborMetadata(string const& _contractName, bool _forIR) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
return createCBORMetadata(contract(_contractName), _forIR);
|
||||
}
|
||||
@ -1013,7 +1020,7 @@ bytes CompilerStack::cborMetadata(string const& _contractName, bool _forIR) cons
|
||||
string const& CompilerStack::metadata(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
|
||||
@ -1023,7 +1030,7 @@ string const& CompilerStack::metadata(Contract const& _contract) const
|
||||
CharStream const& CompilerStack::charStream(string const& _sourceName) const
|
||||
{
|
||||
if (m_stackState < SourcesSet)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No sources set."));
|
||||
solThrow(CompilerError, "No sources set.");
|
||||
|
||||
solAssert(source(_sourceName).charStream, "");
|
||||
|
||||
@ -1033,9 +1040,9 @@ CharStream const& CompilerStack::charStream(string const& _sourceName) const
|
||||
SourceUnit const& CompilerStack::ast(string const& _sourceName) const
|
||||
{
|
||||
if (m_stackState < Parsed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing not yet performed."));
|
||||
solThrow(CompilerError, "Parsing not yet performed.");
|
||||
if (!source(_sourceName).ast && !m_parserErrorRecovery)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
|
||||
solThrow(CompilerError, "Parsing was not successful.");
|
||||
|
||||
return *source(_sourceName).ast;
|
||||
}
|
||||
@ -1043,7 +1050,7 @@ SourceUnit const& CompilerStack::ast(string const& _sourceName) const
|
||||
ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
solThrow(CompilerError, "Analysis was not successful.");
|
||||
|
||||
return *contract(_contractName).contract;
|
||||
}
|
||||
@ -1054,7 +1061,7 @@ size_t CompilerStack::functionEntryPoint(
|
||||
) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
for (auto&& [name, data]: contract(_contractName).runtimeObject.functionDebugData)
|
||||
if (data.sourceID == _function.id())
|
||||
@ -1256,7 +1263,7 @@ void CompilerStack::compileContract(
|
||||
solAssert(!m_viaIR, "");
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors."));
|
||||
solThrow(CompilerError, "Called compile with errors.");
|
||||
|
||||
if (_otherCompilers.count(&_contract))
|
||||
return;
|
||||
@ -1294,7 +1301,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateIR with errors."));
|
||||
solThrow(CompilerError, "Called generateIR with errors.");
|
||||
|
||||
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
|
||||
if (!compiledContract.yulIR.empty())
|
||||
@ -1319,7 +1326,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
|
||||
for (auto const& pair: m_contracts)
|
||||
otherYulSources.emplace(pair.second.contract, pair.second.yulIR);
|
||||
|
||||
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings, sourceIndices(), this);
|
||||
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings, sourceIndices(), m_debugInfoSelection, this);
|
||||
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(
|
||||
_contract,
|
||||
createCBORMetadata(compiledContract, /* _forIR */ true),
|
||||
@ -1331,7 +1338,7 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEVMFromIR with errors."));
|
||||
solThrow(CompilerError, "Called generateEVMFromIR with errors.");
|
||||
|
||||
if (!_contract.canBeDeployed())
|
||||
return;
|
||||
@ -1342,7 +1349,12 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
|
||||
return;
|
||||
|
||||
// Re-parse the Yul IR in EVM dialect
|
||||
yul::AssemblyStack stack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||
yul::AssemblyStack stack(
|
||||
m_evmVersion,
|
||||
yul::AssemblyStack::Language::StrictAssembly,
|
||||
m_optimiserSettings,
|
||||
m_debugInfoSelection
|
||||
);
|
||||
stack.parseAndAnalyze("", compiledContract.yulIROptimized);
|
||||
stack.optimize();
|
||||
|
||||
@ -1358,7 +1370,7 @@ void CompilerStack::generateEwasm(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEwasm with errors."));
|
||||
solThrow(CompilerError, "Called generateEwasm with errors.");
|
||||
|
||||
if (!_contract.canBeDeployed())
|
||||
return;
|
||||
@ -1369,7 +1381,12 @@ void CompilerStack::generateEwasm(ContractDefinition const& _contract)
|
||||
return;
|
||||
|
||||
// Re-parse the Yul IR in EVM dialect
|
||||
yul::AssemblyStack stack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||
yul::AssemblyStack stack(
|
||||
m_evmVersion,
|
||||
yul::AssemblyStack::Language::StrictAssembly,
|
||||
m_optimiserSettings,
|
||||
m_debugInfoSelection
|
||||
);
|
||||
stack.parseAndAnalyze("", compiledContract.yulIROptimized);
|
||||
|
||||
stack.optimize();
|
||||
@ -1412,14 +1429,14 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa
|
||||
}
|
||||
|
||||
// If we get here, both lookup methods failed.
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract \"" + _contractName + "\" not found."));
|
||||
solThrow(CompilerError, "Contract \"" + _contractName + "\" not found.");
|
||||
}
|
||||
|
||||
CompilerStack::Source const& CompilerStack::source(string const& _sourceName) const
|
||||
{
|
||||
auto it = m_sources.find(_sourceName);
|
||||
if (it == m_sources.end())
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Given source file not found."));
|
||||
solThrow(CompilerError, "Given source file not found.");
|
||||
|
||||
return it->second;
|
||||
}
|
||||
@ -1658,7 +1675,7 @@ Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
|
||||
Json::Value CompilerStack::gasEstimates(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
solThrow(CompilerError, "Compilation was not successful.");
|
||||
|
||||
if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName))
|
||||
return Json::Value();
|
||||
|
@ -35,10 +35,11 @@
|
||||
|
||||
#include <libsmtutil/SolverInterface.h>
|
||||
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
|
||||
#include <libevmasm/LinkerObject.h>
|
||||
|
||||
@ -203,6 +204,9 @@ public:
|
||||
/// @param _metadataHash can be IPFS, Bzzr1, None
|
||||
void setMetadataHash(MetadataHash _metadataHash);
|
||||
|
||||
/// Select components of debug info that should be included in comments in generated assembly.
|
||||
void selectDebugInfo(langutil::DebugInfoSelection _debugInfoSelection);
|
||||
|
||||
/// Sets the sources. Must be set before parsing.
|
||||
void setSources(StringMap _sources);
|
||||
|
||||
@ -505,6 +509,7 @@ private:
|
||||
langutil::ErrorReporter m_errorReporter;
|
||||
bool m_metadataLiteralSources = false;
|
||||
MetadataHash m_metadataHash = MetadataHash::IPFS;
|
||||
langutil::DebugInfoSelection m_debugInfoSelection = langutil::DebugInfoSelection::Default();
|
||||
bool m_parserErrorRecovery = false;
|
||||
State m_stackState = Empty;
|
||||
bool m_importedSources = false;
|
||||
|
@ -104,10 +104,7 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
|
||||
try
|
||||
{
|
||||
if (_kind != ReadCallback::kindString(ReadCallback::Kind::ReadFile))
|
||||
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment(
|
||||
"ReadFile callback used as callback kind " +
|
||||
_kind
|
||||
));
|
||||
solAssert(false, "ReadFile callback used as callback kind " + _kind);
|
||||
string strippedSourceUnitName = _sourceUnitName;
|
||||
if (strippedSourceUnitName.find("file://") == 0)
|
||||
strippedSourceUnitName.erase(0, 7);
|
||||
@ -171,7 +168,7 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return ReadCallback::Result{false, "Unknown exception in read callback."};
|
||||
return ReadCallback::Result{false, "Unknown exception in read callback: " + boost::current_exception_diagnostic_information()};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,20 +44,20 @@ struct OptimiserSettings
|
||||
static char constexpr DefaultYulOptimiserSteps[] =
|
||||
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
|
||||
"["
|
||||
"xarrscLM" // Turn into SSA and simplify
|
||||
"xa[r]scLM" // Turn into SSA and simplify
|
||||
"cCTUtTOntnfDIul" // Perform structural simplification
|
||||
"Lcul" // Simplify again
|
||||
"Vcul jj" // Reverse SSA
|
||||
"Vcul [j]" // Reverse SSA
|
||||
|
||||
// should have good "compilability" property here.
|
||||
|
||||
"Tpeul" // Run functional expression inliner
|
||||
"xarulrul" // Prune a bit more in SSA
|
||||
"xarrcL" // Turn into SSA again and simplify
|
||||
"xa[rul]" // Prune a bit more in SSA
|
||||
"xa[r]cL" // Turn into SSA again and simplify
|
||||
"gvif" // Run full inliner
|
||||
"CTUcarrLsTFOtfDncarrIulc" // SSA plus simplify
|
||||
"CTUca[r]LsTFOtfDnca[r]Iulc" // SSA plus simplify
|
||||
"]"
|
||||
"jmuljuljul VcTOcul jmul"; // Make source short and pretty
|
||||
"jmul[jul] VcTOcul jmul"; // Make source short and pretty
|
||||
|
||||
/// No optimisations at all - not recommended.
|
||||
static OptimiserSettings none()
|
||||
|
@ -28,9 +28,13 @@
|
||||
#include <libyul/AssemblyStack.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/optimiser/Suite.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
|
||||
#include <libsmtutil/Exceptions.h>
|
||||
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
#include <libsolutil/JSON.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
@ -793,7 +797,7 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
|
||||
|
||||
if (settings.isMember("debug"))
|
||||
{
|
||||
if (auto result = checkKeys(settings["debug"], {"revertStrings"}, "settings.debug"))
|
||||
if (auto result = checkKeys(settings["debug"], {"revertStrings", "debugInfo"}, "settings.debug"))
|
||||
return *result;
|
||||
|
||||
if (settings["debug"].isMember("revertStrings"))
|
||||
@ -810,6 +814,31 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
|
||||
);
|
||||
ret.revertStrings = *revertStrings;
|
||||
}
|
||||
|
||||
if (settings["debug"].isMember("debugInfo"))
|
||||
{
|
||||
if (!settings["debug"]["debugInfo"].isArray())
|
||||
return formatFatalError("JSONError", "settings.debug.debugInfo must be an array.");
|
||||
|
||||
vector<string> components;
|
||||
for (Json::Value const& arrayValue: settings["debug"]["debugInfo"])
|
||||
components.push_back(arrayValue.asString());
|
||||
|
||||
optional<DebugInfoSelection> debugInfoSelection = DebugInfoSelection::fromComponents(
|
||||
components,
|
||||
true /* _acceptWildcards */
|
||||
);
|
||||
if (!debugInfoSelection.has_value())
|
||||
return formatFatalError("JSONError", "Invalid value in settings.debug.debugInfo.");
|
||||
|
||||
if (debugInfoSelection->snippet && !debugInfoSelection->location)
|
||||
return formatFatalError(
|
||||
"JSONError",
|
||||
"To use 'snippet' with settings.debug.debugInfo you must select also 'location'."
|
||||
);
|
||||
|
||||
ret.debugInfoSelection = debugInfoSelection.value();
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.isMember("remappings") && !settings["remappings"].isArray())
|
||||
@ -1029,6 +1058,8 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
compilerStack.setRemappings(move(_inputsAndSettings.remappings));
|
||||
compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings));
|
||||
compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings);
|
||||
if (_inputsAndSettings.debugInfoSelection.has_value())
|
||||
compilerStack.selectDebugInfo(_inputsAndSettings.debugInfoSelection.value());
|
||||
compilerStack.setLibraries(_inputsAndSettings.libraries);
|
||||
compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources);
|
||||
compilerStack.setMetadataHash(_inputsAndSettings.metadataHash);
|
||||
@ -1151,13 +1182,13 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
"Exception during compilation: " + boost::diagnostic_information(_exception)
|
||||
));
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
catch (std::exception const& _exception)
|
||||
{
|
||||
errors.append(formatError(
|
||||
Error::Severity::Error,
|
||||
"Exception",
|
||||
"general",
|
||||
"Unknown exception during compilation" + (_e.what() ? ": " + string(_e.what()) : ".")
|
||||
"Unknown exception during compilation: " + boost::diagnostic_information(_exception)
|
||||
));
|
||||
}
|
||||
catch (...)
|
||||
@ -1166,7 +1197,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
Error::Severity::Error,
|
||||
"Exception",
|
||||
"general",
|
||||
"Unknown exception during compilation."
|
||||
"Unknown exception during compilation: " + boost::current_exception_diagnostic_information()
|
||||
));
|
||||
}
|
||||
|
||||
@ -1358,7 +1389,10 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
|
||||
AssemblyStack stack(
|
||||
_inputsAndSettings.evmVersion,
|
||||
AssemblyStack::Language::StrictAssembly,
|
||||
_inputsAndSettings.optimiserSettings
|
||||
_inputsAndSettings.optimiserSettings,
|
||||
_inputsAndSettings.debugInfoSelection.has_value() ?
|
||||
_inputsAndSettings.debugInfoSelection.value() :
|
||||
DebugInfoSelection::Default()
|
||||
);
|
||||
string const& sourceName = _inputsAndSettings.sources.begin()->first;
|
||||
string const& sourceContents = _inputsAndSettings.sources.begin()->second;
|
||||
@ -1481,7 +1515,7 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile");
|
||||
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::current_exception_diagnostic_information());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
#include <libsolutil/JSON.h>
|
||||
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
@ -78,6 +80,7 @@ private:
|
||||
std::vector<ImportRemapper::Remapping> remappings;
|
||||
RevertStrings revertStrings = RevertStrings::Default;
|
||||
OptimiserSettings optimiserSettings = OptimiserSettings::minimal();
|
||||
std::optional<langutil::DebugInfoSelection> debugInfoSelection;
|
||||
std::map<std::string, util::h160> libraries;
|
||||
bool metadataLiteralSources = false;
|
||||
CompilerStack::MetadataHash metadataHash = CompilerStack::MetadataHash::IPFS;
|
||||
|
@ -61,14 +61,9 @@ inline std::string stringOrDefault(std::string _string, std::string _defaultStri
|
||||
do \
|
||||
{ \
|
||||
if (!(_condition)) \
|
||||
::boost::throw_exception( \
|
||||
_exceptionType() << \
|
||||
::solidity::util::errinfo_comment( \
|
||||
::solidity::util::assertions::stringOrDefault(_description, _defaultDescription) \
|
||||
) << \
|
||||
::boost::throw_function(ETH_FUNC) << \
|
||||
::boost::throw_file(__FILE__) << \
|
||||
::boost::throw_line(__LINE__) \
|
||||
solThrow( \
|
||||
_exceptionType, \
|
||||
::solidity::util::assertions::stringOrDefault(_description, _defaultDescription) \
|
||||
); \
|
||||
} \
|
||||
while (false)
|
||||
|
@ -43,6 +43,19 @@ struct Exception: virtual std::exception, virtual boost::exception
|
||||
private:
|
||||
};
|
||||
|
||||
/// Throws an exception with a given description and extra information about the location the
|
||||
/// exception was thrown from.
|
||||
/// @param _exceptionType The type of the exception to throw (not an instance).
|
||||
/// @param _description The message that describes the error.
|
||||
#define solThrow(_exceptionType, _description) \
|
||||
::boost::throw_exception( \
|
||||
_exceptionType() << \
|
||||
::solidity::util::errinfo_comment(_description) << \
|
||||
::boost::throw_function(ETH_FUNC) << \
|
||||
::boost::throw_file(__FILE__) << \
|
||||
::boost::throw_line(__LINE__) \
|
||||
)
|
||||
|
||||
#define DEV_SIMPLE_EXCEPTION(X) struct X: virtual ::solidity::util::Exception { const char* what() const noexcept override { return #X; } }
|
||||
|
||||
DEV_SIMPLE_EXCEPTION(InvalidAddress);
|
||||
|
@ -189,11 +189,13 @@ string Whiskers::replace(
|
||||
if (conditionName[0] == '+')
|
||||
{
|
||||
string tag = conditionName.substr(1);
|
||||
assertThrow(
|
||||
_parameters.count(tag),
|
||||
WhiskersError, "Tag " + tag + " used as condition but was not set."
|
||||
);
|
||||
conditionValue = !_parameters.at(tag).empty();
|
||||
|
||||
if (_parameters.count(tag))
|
||||
conditionValue = !_parameters.at(tag).empty();
|
||||
else if (_listParameters.count(tag))
|
||||
conditionValue = !_listParameters.at(tag).empty();
|
||||
else
|
||||
assertThrow(false, WhiskersError, "Tag " + tag + " used as condition but was not set.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -60,12 +60,13 @@ DEV_SIMPLE_EXCEPTION(WhiskersError);
|
||||
* - Condition parameter: <?name>...<!name>...</name>, where "<!name>" is optional
|
||||
* replaced (and recursively expanded) by the first part if the condition is true
|
||||
* and by the second (or empty string if missing) if the condition is false
|
||||
* - Conditional string parameter: <?+name>...<!+name>...</+name>
|
||||
* Works similar to a conditional parameter where the checked condition is
|
||||
* that the regular (string) parameter called "name" is non-empty.
|
||||
* - List parameter: <#list>...</list>
|
||||
* The part between the tags is repeated as often as values are provided
|
||||
* in the mapping. Each list element can have its own parameter -> value mapping.
|
||||
* - Conditional value parameter: <?+name>...<!+name>...</+name>
|
||||
* Works similar to a conditional parameter where the checked condition is
|
||||
* that the string or list parameter called "name" is non-empty or contains
|
||||
* no elements respectively.
|
||||
*/
|
||||
class Whiskers
|
||||
{
|
||||
|
@ -37,6 +37,8 @@
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
@ -618,7 +620,7 @@ void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _locati
|
||||
m_errorReporter.typeError(
|
||||
5473_error,
|
||||
_location,
|
||||
"\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)."
|
||||
fmt::format("\"{}\" is not a valid type (user defined types are not yet supported).", _type)
|
||||
);
|
||||
}
|
||||
|
||||
@ -628,11 +630,7 @@ void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, Sour
|
||||
m_errorReporter.typeError(
|
||||
3781_error,
|
||||
_location,
|
||||
"Expected a value of type \"" +
|
||||
_expectedType.str() +
|
||||
"\" but got \"" +
|
||||
_givenType.str() +
|
||||
"\""
|
||||
fmt::format("Expected a value of type \"{}\" but got \"{}\".", _expectedType, _givenType)
|
||||
);
|
||||
}
|
||||
|
||||
@ -664,14 +662,12 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
|
||||
m_errorReporter.typeError(
|
||||
_errorId,
|
||||
_location,
|
||||
"The \"" +
|
||||
boost::to_lower_copy(instructionInfo(_instr).name)
|
||||
+ "\" instruction is " +
|
||||
vmKindMessage +
|
||||
" VMs " +
|
||||
"(you are currently compiling for \"" +
|
||||
m_evmVersion.name() +
|
||||
"\")."
|
||||
fmt::format(
|
||||
"The \"{instruction}\" instruction is {kind} VMs (you are currently compiling for \"{version}\").",
|
||||
fmt::arg("instruction", boost::to_lower_copy(instructionInfo(_instr).name)),
|
||||
fmt::arg("kind", vmKindMessage),
|
||||
fmt::arg("version", m_evmVersion.name())
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -261,10 +261,16 @@ string AsmPrinter::appendTypeName(YulString _type, bool _isBoolLiteral) const
|
||||
string AsmPrinter::formatSourceLocation(
|
||||
SourceLocation const& _location,
|
||||
map<string, unsigned> const& _nameToSourceIndex,
|
||||
DebugInfoSelection const& _debugInfoSelection,
|
||||
CharStreamProvider const* _soliditySourceProvider
|
||||
)
|
||||
{
|
||||
yulAssert(!_nameToSourceIndex.empty(), "");
|
||||
if (_debugInfoSelection.snippet)
|
||||
yulAssert(_debugInfoSelection.location, "@src tag must always contain the source location");
|
||||
|
||||
if (_debugInfoSelection.none())
|
||||
return "";
|
||||
|
||||
string sourceIndex = "-1";
|
||||
string solidityCodeSnippet = "";
|
||||
@ -272,7 +278,7 @@ string AsmPrinter::formatSourceLocation(
|
||||
{
|
||||
sourceIndex = to_string(_nameToSourceIndex.at(*_location.sourceName));
|
||||
|
||||
if (_soliditySourceProvider)
|
||||
if (_debugInfoSelection.snippet && _soliditySourceProvider)
|
||||
{
|
||||
solidityCodeSnippet = escapeAndQuoteString(
|
||||
_soliditySourceProvider->charStream(*_location.sourceName).singleLineSnippet(_location)
|
||||
@ -298,12 +304,13 @@ string AsmPrinter::formatSourceLocation(
|
||||
|
||||
string AsmPrinter::formatDebugData(shared_ptr<DebugData const> const& _debugData, bool _statement)
|
||||
{
|
||||
if (!_debugData)
|
||||
if (!_debugData || m_debugInfoSelection.none())
|
||||
return "";
|
||||
|
||||
vector<string> items;
|
||||
if (auto id = _debugData->astID)
|
||||
items.emplace_back("@ast-id " + to_string(*id));
|
||||
if (m_debugInfoSelection.astID)
|
||||
items.emplace_back("@ast-id " + to_string(*id));
|
||||
|
||||
if (
|
||||
m_lastLocation != _debugData->originLocation &&
|
||||
@ -315,6 +322,7 @@ string AsmPrinter::formatDebugData(shared_ptr<DebugData const> const& _debugData
|
||||
items.emplace_back(formatSourceLocation(
|
||||
_debugData->originLocation,
|
||||
m_nameToSourceIndex,
|
||||
m_debugInfoSelection,
|
||||
m_soliditySourceProvider
|
||||
));
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
|
||||
#include <map>
|
||||
@ -48,9 +49,11 @@ public:
|
||||
explicit AsmPrinter(
|
||||
Dialect const* _dialect = nullptr,
|
||||
std::optional<std::map<unsigned, std::shared_ptr<std::string const>>> _sourceIndexToName = {},
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_debugInfoSelection(_debugInfoSelection),
|
||||
m_soliditySourceProvider(_soliditySourceProvider)
|
||||
{
|
||||
if (_sourceIndexToName)
|
||||
@ -58,12 +61,12 @@ public:
|
||||
m_nameToSourceIndex[*name] = index;
|
||||
}
|
||||
|
||||
|
||||
explicit AsmPrinter(
|
||||
Dialect const& _dialect,
|
||||
std::optional<std::map<unsigned, std::shared_ptr<std::string const>>> _sourceIndexToName = {},
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
|
||||
): AsmPrinter(&_dialect, _sourceIndexToName, _soliditySourceProvider) {}
|
||||
): AsmPrinter(&_dialect, _sourceIndexToName, _debugInfoSelection, _soliditySourceProvider) {}
|
||||
|
||||
std::string operator()(Literal const& _literal);
|
||||
std::string operator()(Identifier const& _identifier);
|
||||
@ -83,6 +86,7 @@ public:
|
||||
static std::string formatSourceLocation(
|
||||
langutil::SourceLocation const& _location,
|
||||
std::map<std::string, unsigned> const& _nameToSourceIndex,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
|
||||
langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr
|
||||
);
|
||||
|
||||
@ -100,6 +104,7 @@ private:
|
||||
Dialect const* const m_dialect = nullptr;
|
||||
std::map<std::string, unsigned> m_nameToSourceIndex;
|
||||
langutil::SourceLocation m_lastLocation = {};
|
||||
langutil::DebugInfoSelection m_debugInfoSelection = {};
|
||||
langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr;
|
||||
};
|
||||
|
||||
|
@ -249,7 +249,7 @@ AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
|
||||
MachineAssemblyObject creationObject;
|
||||
creationObject.bytecode = make_shared<evmasm::LinkerObject>(creationAssembly->assemble());
|
||||
yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
|
||||
creationObject.assembly = creationAssembly->assemblyString();
|
||||
creationObject.assembly = creationAssembly->assemblyString(m_debugInfoSelection);
|
||||
creationObject.sourceMappings = make_unique<string>(
|
||||
evmasm::AssemblyItem::computeSourceMapping(
|
||||
creationAssembly->items(),
|
||||
@ -261,7 +261,7 @@ AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
|
||||
if (deployedAssembly)
|
||||
{
|
||||
deployedObject.bytecode = make_shared<evmasm::LinkerObject>(deployedAssembly->assemble());
|
||||
deployedObject.assembly = deployedAssembly->assemblyString();
|
||||
deployedObject.assembly = deployedAssembly->assemblyString(m_debugInfoSelection);
|
||||
deployedObject.sourceMappings = make_unique<string>(
|
||||
evmasm::AssemblyItem::computeSourceMapping(
|
||||
deployedAssembly->items(),
|
||||
@ -314,11 +314,13 @@ AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
|
||||
return {make_shared<evmasm::Assembly>(assembly), {}};
|
||||
}
|
||||
|
||||
string AssemblyStack::print(CharStreamProvider const* _soliditySourceProvider) const
|
||||
string AssemblyStack::print(
|
||||
CharStreamProvider const* _soliditySourceProvider
|
||||
) const
|
||||
{
|
||||
yulAssert(m_parserResult, "");
|
||||
yulAssert(m_parserResult->code, "");
|
||||
return m_parserResult->toString(&languageToDialect(m_language, m_evmVersion), _soliditySourceProvider) + "\n";
|
||||
return m_parserResult->toString(&languageToDialect(m_language, m_evmVersion), m_debugInfoSelection, _soliditySourceProvider) + "\n";
|
||||
}
|
||||
|
||||
shared_ptr<Object> AssemblyStack::parserResult() const
|
||||
|
@ -22,9 +22,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/ObjectParser.h>
|
||||
@ -69,12 +70,24 @@ public:
|
||||
enum class Machine { EVM, Ewasm };
|
||||
|
||||
AssemblyStack():
|
||||
AssemblyStack(langutil::EVMVersion{}, Language::Assembly, solidity::frontend::OptimiserSettings::none())
|
||||
AssemblyStack(
|
||||
langutil::EVMVersion{},
|
||||
Language::Assembly,
|
||||
solidity::frontend::OptimiserSettings::none(),
|
||||
langutil::DebugInfoSelection::Default()
|
||||
)
|
||||
{}
|
||||
AssemblyStack(langutil::EVMVersion _evmVersion, Language _language, solidity::frontend::OptimiserSettings _optimiserSettings):
|
||||
|
||||
AssemblyStack(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
Language _language,
|
||||
solidity::frontend::OptimiserSettings _optimiserSettings,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection
|
||||
):
|
||||
m_language(_language),
|
||||
m_evmVersion(_evmVersion),
|
||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||
m_debugInfoSelection(_debugInfoSelection),
|
||||
m_errorReporter(m_errors)
|
||||
{}
|
||||
|
||||
@ -116,7 +129,9 @@ public:
|
||||
langutil::ErrorList const& errors() const { return m_errors; }
|
||||
|
||||
/// Pretty-print the input after having parsed it.
|
||||
std::string print(langutil::CharStreamProvider const* _soliditySourceProvider = nullptr) const;
|
||||
std::string print(
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
|
||||
) const;
|
||||
|
||||
/// Return the parsed and analyzed object.
|
||||
std::shared_ptr<Object> parserResult() const;
|
||||
@ -132,6 +147,7 @@ private:
|
||||
Language m_language = Language::Assembly;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
solidity::frontend::OptimiserSettings m_optimiserSettings;
|
||||
langutil::DebugInfoSelection m_debugInfoSelection{};
|
||||
|
||||
std::unique_ptr<langutil::CharStream> m_charStream;
|
||||
|
||||
|
@ -34,6 +34,9 @@ add_library(yul
|
||||
AssemblyStack.cpp
|
||||
CompilabilityChecker.cpp
|
||||
CompilabilityChecker.h
|
||||
ControlFlowSideEffects.h
|
||||
ControlFlowSideEffectsCollector.cpp
|
||||
ControlFlowSideEffectsCollector.h
|
||||
Dialect.cpp
|
||||
Dialect.h
|
||||
Exceptions.h
|
||||
@ -216,4 +219,4 @@ add_library(yul
|
||||
optimiser/VarNameCleaner.h
|
||||
)
|
||||
|
||||
target_link_libraries(yul PUBLIC evmasm solutil langutil smtutil)
|
||||
target_link_libraries(yul PUBLIC evmasm solutil langutil smtutil fmt::fmt-header-only)
|
||||
|
@ -18,22 +18,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
|
||||
/**
|
||||
* Side effects of code related to control flow.
|
||||
* Side effects of a user-defined or builtin function.
|
||||
*
|
||||
* Each of the three booleans represents a reachability condition. There is an implied
|
||||
* fourth alternative, which is going out of gas while executing the function. Since
|
||||
* this can always happen and depends on the supply of gas, it is not considered.
|
||||
*
|
||||
* If all three booleans are false, it means that the function always leads to infinite
|
||||
* recursion.
|
||||
*/
|
||||
struct ControlFlowSideEffects
|
||||
{
|
||||
/// If true, this code terminates the control flow.
|
||||
/// State may or may not be reverted as indicated by the ``reverts`` flag.
|
||||
bool terminates = false;
|
||||
/// If true, this code reverts all state changes in the transaction.
|
||||
/// Whenever this is true, ``terminates`` has to be true as well.
|
||||
bool reverts = false;
|
||||
/// If true, the function contains at least one reachable branch that terminates successfully.
|
||||
bool canTerminate = false;
|
||||
/// If true, the function contains at least one reachable branch that reverts.
|
||||
bool canRevert = false;
|
||||
/// If true, the function has a regular outgoing control-flow.
|
||||
bool canContinue = true;
|
||||
|
||||
bool terminatesOrReverts() const
|
||||
{
|
||||
return (canTerminate || canRevert) && !canContinue;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
274
libyul/ControlFlowSideEffectsCollector.cpp
Normal file
274
libyul/ControlFlowSideEffectsCollector.cpp
Normal file
@ -0,0 +1,274 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libyul/ControlFlowSideEffectsCollector.h>
|
||||
|
||||
#include <libyul/optimiser/FunctionDefinitionCollector.h>
|
||||
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Dialect.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
#include <range/v3/view/map.hpp>
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
#include <range/v3/algorithm/find_if.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::yul;
|
||||
|
||||
|
||||
ControlFlowBuilder::ControlFlowBuilder(Block const& _ast)
|
||||
{
|
||||
for (auto const& statement: _ast.statements)
|
||||
if (auto const* function = get_if<FunctionDefinition>(&statement))
|
||||
(*this)(*function);
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(FunctionCall const& _functionCall)
|
||||
{
|
||||
walkVector(_functionCall.arguments | ranges::views::reverse);
|
||||
newConnectedNode();
|
||||
m_currentNode->functionCall = _functionCall.functionName.name;
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(If const& _if)
|
||||
{
|
||||
visit(*_if.condition);
|
||||
ControlFlowNode* node = m_currentNode;
|
||||
(*this)(_if.body);
|
||||
newConnectedNode();
|
||||
node->successors.emplace_back(m_currentNode);
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(Switch const& _switch)
|
||||
{
|
||||
visit(*_switch.expression);
|
||||
ControlFlowNode* initialNode = m_currentNode;
|
||||
ControlFlowNode* finalNode = newNode();
|
||||
|
||||
if (_switch.cases.back().value)
|
||||
initialNode->successors.emplace_back(finalNode);
|
||||
|
||||
for (Case const& case_: _switch.cases)
|
||||
{
|
||||
m_currentNode = initialNode;
|
||||
(*this)(case_.body);
|
||||
newConnectedNode();
|
||||
m_currentNode->successors.emplace_back(finalNode);
|
||||
}
|
||||
m_currentNode = finalNode;
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(FunctionDefinition const& _function)
|
||||
{
|
||||
ScopedSaveAndRestore currentNode(m_currentNode, nullptr);
|
||||
yulAssert(!m_leave && !m_break && !m_continue, "Function hoister has not been used.");
|
||||
|
||||
FunctionFlow flow;
|
||||
flow.exit = newNode();
|
||||
m_currentNode = newNode();
|
||||
flow.entry = m_currentNode;
|
||||
m_leave = flow.exit;
|
||||
|
||||
(*this)(_function.body);
|
||||
|
||||
m_currentNode->successors.emplace_back(flow.exit);
|
||||
|
||||
m_functionFlows[_function.name] = move(flow);
|
||||
|
||||
m_leave = nullptr;
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(ForLoop const& _for)
|
||||
{
|
||||
ScopedSaveAndRestore scopedBreakNode(m_break, nullptr);
|
||||
ScopedSaveAndRestore scopedContinueNode(m_continue, nullptr);
|
||||
|
||||
(*this)(_for.pre);
|
||||
|
||||
ControlFlowNode* breakNode = newNode();
|
||||
m_break = breakNode;
|
||||
ControlFlowNode* continueNode = newNode();
|
||||
m_continue = continueNode;
|
||||
|
||||
newConnectedNode();
|
||||
ControlFlowNode* loopNode = m_currentNode;
|
||||
visit(*_for.condition);
|
||||
m_currentNode->successors.emplace_back(m_break);
|
||||
newConnectedNode();
|
||||
|
||||
(*this)(_for.body);
|
||||
|
||||
m_currentNode->successors.emplace_back(m_continue);
|
||||
m_currentNode = continueNode;
|
||||
|
||||
(*this)(_for.post);
|
||||
m_currentNode->successors.emplace_back(loopNode);
|
||||
|
||||
m_currentNode = breakNode;
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(Break const&)
|
||||
{
|
||||
yulAssert(m_break);
|
||||
m_currentNode->successors.emplace_back(m_break);
|
||||
m_currentNode = newNode();
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(Continue const&)
|
||||
{
|
||||
yulAssert(m_continue);
|
||||
m_currentNode->successors.emplace_back(m_continue);
|
||||
m_currentNode = newNode();
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(Leave const&)
|
||||
{
|
||||
yulAssert(m_leave);
|
||||
m_currentNode->successors.emplace_back(m_leave);
|
||||
m_currentNode = newNode();
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::newConnectedNode()
|
||||
{
|
||||
ControlFlowNode* node = newNode();
|
||||
m_currentNode->successors.emplace_back(node);
|
||||
m_currentNode = node;
|
||||
}
|
||||
|
||||
ControlFlowNode* ControlFlowBuilder::newNode()
|
||||
{
|
||||
m_nodes.emplace_back(make_shared<ControlFlowNode>());
|
||||
return m_nodes.back().get();
|
||||
}
|
||||
|
||||
|
||||
ControlFlowSideEffectsCollector::ControlFlowSideEffectsCollector(
|
||||
Dialect const& _dialect,
|
||||
Block const& _ast
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_cfgBuilder(_ast)
|
||||
{
|
||||
for (auto&& [name, flow]: m_cfgBuilder.functionFlows())
|
||||
{
|
||||
yulAssert(!flow.entry->functionCall);
|
||||
m_processedNodes[name] = {};
|
||||
m_pendingNodes[name].push_front(flow.entry);
|
||||
m_functionSideEffects[name] = {false, false, false};
|
||||
}
|
||||
|
||||
// Process functions while we have progress. For now, we are only interested
|
||||
// in `canContinue`.
|
||||
bool progress = true;
|
||||
while (progress)
|
||||
{
|
||||
progress = false;
|
||||
for (auto const& functionName: m_pendingNodes | ranges::views::keys)
|
||||
if (processFunction(functionName))
|
||||
progress = true;
|
||||
}
|
||||
|
||||
// No progress anymore: All remaining nodes are calls
|
||||
// to functions that always recurse.
|
||||
// If we have not set `canContinue` by now, the function's exit
|
||||
// is not reachable.
|
||||
|
||||
for (auto&& [functionName, calls]: m_functionCalls)
|
||||
{
|
||||
ControlFlowSideEffects& sideEffects = m_functionSideEffects[functionName];
|
||||
auto _visit = [&, visited = std::set<YulString>{}](YulString _function, auto&& _recurse) mutable {
|
||||
if (sideEffects.canTerminate && sideEffects.canRevert)
|
||||
return;
|
||||
if (!visited.insert(_function).second)
|
||||
return;
|
||||
|
||||
ControlFlowSideEffects const* calledSideEffects = nullptr;
|
||||
if (BuiltinFunction const* f = _dialect.builtin(_function))
|
||||
calledSideEffects = &f->controlFlowSideEffects;
|
||||
else
|
||||
calledSideEffects = &m_functionSideEffects.at(_function);
|
||||
|
||||
if (calledSideEffects->canTerminate)
|
||||
sideEffects.canTerminate = true;
|
||||
if (calledSideEffects->canRevert)
|
||||
sideEffects.canRevert = true;
|
||||
|
||||
for (YulString callee: util::valueOrDefault(m_functionCalls, _function))
|
||||
_recurse(callee, _recurse);
|
||||
};
|
||||
for (auto const& call: calls)
|
||||
_visit(call, _visit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ControlFlowSideEffectsCollector::processFunction(YulString _name)
|
||||
{
|
||||
bool progress = false;
|
||||
while (ControlFlowNode const* node = nextProcessableNode(_name))
|
||||
{
|
||||
if (node == m_cfgBuilder.functionFlows().at(_name).exit)
|
||||
{
|
||||
m_functionSideEffects[_name].canContinue = true;
|
||||
return true;
|
||||
}
|
||||
for (ControlFlowNode const* s: node->successors)
|
||||
recordReachabilityAndQueue(_name, s);
|
||||
|
||||
progress = true;
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
ControlFlowNode const* ControlFlowSideEffectsCollector::nextProcessableNode(YulString _functionName)
|
||||
{
|
||||
std::list<ControlFlowNode const*>& nodes = m_pendingNodes[_functionName];
|
||||
auto it = ranges::find_if(nodes, [this](ControlFlowNode const* _node) {
|
||||
return !_node->functionCall || sideEffects(*_node->functionCall).canContinue;
|
||||
});
|
||||
if (it == nodes.end())
|
||||
return nullptr;
|
||||
|
||||
ControlFlowNode const* node = *it;
|
||||
nodes.erase(it);
|
||||
return node;
|
||||
}
|
||||
|
||||
ControlFlowSideEffects const& ControlFlowSideEffectsCollector::sideEffects(YulString _functionName) const
|
||||
{
|
||||
if (auto const* builtin = m_dialect.builtin(_functionName))
|
||||
return builtin->controlFlowSideEffects;
|
||||
else
|
||||
return m_functionSideEffects.at(_functionName);
|
||||
}
|
||||
|
||||
void ControlFlowSideEffectsCollector::recordReachabilityAndQueue(
|
||||
YulString _functionName,
|
||||
ControlFlowNode const* _node
|
||||
)
|
||||
{
|
||||
if (_node->functionCall)
|
||||
m_functionCalls[_functionName].insert(*_node->functionCall);
|
||||
if (m_processedNodes[_functionName].insert(_node).second)
|
||||
m_pendingNodes.at(_functionName).push_front(_node);
|
||||
}
|
||||
|
130
libyul/ControlFlowSideEffectsCollector.h
Normal file
130
libyul/ControlFlowSideEffectsCollector.h
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/ControlFlowSideEffects.h>
|
||||
|
||||
#include <set>
|
||||
#include <stack>
|
||||
#include <optional>
|
||||
#include <list>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
|
||||
struct Dialect;
|
||||
|
||||
struct ControlFlowNode
|
||||
{
|
||||
std::vector<ControlFlowNode const*> successors;
|
||||
/// Name of the called function if the node calls a function.
|
||||
std::optional<YulString> functionCall;
|
||||
};
|
||||
|
||||
/**
|
||||
* The control flow of a function with entry and exit nodes.
|
||||
*/
|
||||
struct FunctionFlow
|
||||
{
|
||||
ControlFlowNode const* entry;
|
||||
ControlFlowNode const* exit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Requires: Disambiguator, Function Hoister.
|
||||
*/
|
||||
class ControlFlowBuilder: private ASTWalker
|
||||
{
|
||||
public:
|
||||
/// Computes the control-flows of all function defined in the block.
|
||||
/// Assumes the functions are hoisted to the topmost block.
|
||||
explicit ControlFlowBuilder(Block const& _ast);
|
||||
std::map<YulString, FunctionFlow> const& functionFlows() const { return m_functionFlows; }
|
||||
|
||||
private:
|
||||
using ASTWalker::operator();
|
||||
void operator()(FunctionCall const& _functionCall) override;
|
||||
void operator()(If const& _if) override;
|
||||
void operator()(Switch const& _switch) override;
|
||||
void operator()(FunctionDefinition const& _functionDefinition) override;
|
||||
void operator()(ForLoop const& _forLoop) override;
|
||||
void operator()(Break const& _break) override;
|
||||
void operator()(Continue const& _continue) override;
|
||||
void operator()(Leave const& _leaveStatement) override;
|
||||
|
||||
void newConnectedNode();
|
||||
ControlFlowNode* newNode();
|
||||
|
||||
std::vector<std::shared_ptr<ControlFlowNode>> m_nodes;
|
||||
|
||||
ControlFlowNode* m_currentNode = nullptr;
|
||||
ControlFlowNode const* m_leave = nullptr;
|
||||
ControlFlowNode const* m_break = nullptr;
|
||||
ControlFlowNode const* m_continue = nullptr;
|
||||
|
||||
std::map<YulString, FunctionFlow> m_functionFlows;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Requires: Disambiguator, Function Hoister.
|
||||
*/
|
||||
class ControlFlowSideEffectsCollector
|
||||
{
|
||||
public:
|
||||
explicit ControlFlowSideEffectsCollector(
|
||||
Dialect const& _dialect,
|
||||
Block const& _ast
|
||||
);
|
||||
|
||||
std::map<YulString, ControlFlowSideEffects> const& functionSideEffects() const
|
||||
{
|
||||
return m_functionSideEffects;
|
||||
}
|
||||
private:
|
||||
|
||||
/// @returns false if nothing could be processed.
|
||||
bool processFunction(YulString _name);
|
||||
|
||||
/// @returns the next pending node of the function that is not
|
||||
/// a function call to a function that might not continue.
|
||||
/// De-queues the node or returns nullptr if no such node is found.
|
||||
ControlFlowNode const* nextProcessableNode(YulString _functionName);
|
||||
|
||||
/// @returns the side-effects of either a builtin call or a user defined function
|
||||
/// call (as far as already computed).
|
||||
ControlFlowSideEffects const& sideEffects(YulString _functionName) const;
|
||||
|
||||
/// Queues the given node to be processed (if not already visited)
|
||||
/// and if it is a function call, records that `_functionName` calls
|
||||
/// `*_node->functionCall`.
|
||||
void recordReachabilityAndQueue(YulString _functionName, ControlFlowNode const* _node);
|
||||
|
||||
Dialect const& m_dialect;
|
||||
ControlFlowBuilder m_cfgBuilder;
|
||||
std::map<YulString, ControlFlowSideEffects> m_functionSideEffects;
|
||||
std::map<YulString, std::list<ControlFlowNode const*>> m_pendingNodes;
|
||||
std::map<YulString, std::set<ControlFlowNode const*>> m_processedNodes;
|
||||
/// `x` is in `m_functionCalls[y]` if a direct call to `x` is reachable inside `y`
|
||||
std::map<YulString, std::set<YulString>> m_functionCalls;
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -22,8 +22,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <libyul/YulString.h>
|
||||
#include <libyul/SideEffects.h>
|
||||
#include <libyul/ControlFlowSideEffects.h>
|
||||
#include <libyul/SideEffects.h>
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
@ -51,13 +51,14 @@ string indent(std::string const& _input)
|
||||
|
||||
}
|
||||
|
||||
string Data::toString(Dialect const*, CharStreamProvider const*) const
|
||||
string Data::toString(Dialect const*, DebugInfoSelection const&, CharStreamProvider const*) const
|
||||
{
|
||||
return "data \"" + name.str() + "\" hex\"" + util::toHex(data) + "\"";
|
||||
}
|
||||
|
||||
string Object::toString(
|
||||
Dialect const* _dialect,
|
||||
DebugInfoSelection const& _debugInfoSelection,
|
||||
CharStreamProvider const* _soliditySourceProvider
|
||||
) const
|
||||
{
|
||||
@ -74,10 +75,15 @@ string Object::toString(
|
||||
})) +
|
||||
"\n";
|
||||
|
||||
string inner = "code " + AsmPrinter{_dialect, debugData->sourceNames, _soliditySourceProvider}(*code);
|
||||
string inner = "code " + AsmPrinter(
|
||||
_dialect,
|
||||
debugData->sourceNames,
|
||||
_debugInfoSelection,
|
||||
_soliditySourceProvider
|
||||
)(*code);
|
||||
|
||||
for (auto const& obj: subObjects)
|
||||
inner += "\n" + obj->toString(_dialect, _soliditySourceProvider);
|
||||
inner += "\n" + obj->toString(_dialect, _debugInfoSelection, _soliditySourceProvider);
|
||||
|
||||
return useSrcComment + "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}";
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <libyul/YulString.h>
|
||||
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
|
||||
@ -54,6 +55,7 @@ struct ObjectNode
|
||||
YulString name;
|
||||
virtual std::string toString(
|
||||
Dialect const* _dialect,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection,
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider
|
||||
) const = 0;
|
||||
};
|
||||
@ -69,6 +71,7 @@ struct Data: public ObjectNode
|
||||
|
||||
std::string toString(
|
||||
Dialect const* _dialect,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection,
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider
|
||||
) const override;
|
||||
};
|
||||
@ -89,6 +92,7 @@ public:
|
||||
/// @returns a (parseable) string representation.
|
||||
std::string toString(
|
||||
Dialect const* _dialect,
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
|
||||
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
|
||||
) const;
|
||||
|
||||
|
@ -94,7 +94,7 @@ struct SideEffects
|
||||
cannotLoop && _other.cannotLoop,
|
||||
otherState + _other.otherState,
|
||||
storage + _other.storage,
|
||||
memory + _other.memory
|
||||
memory + _other.memory
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -166,6 +168,26 @@ inline YulString operator "" _yulstring(char const* _string, std::size_t _size)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
template <>
|
||||
struct formatter<solidity::yul::YulString>
|
||||
{
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext& _context)
|
||||
{
|
||||
return _context.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(solidity::yul::YulString _value, FormatContext& _context)
|
||||
{
|
||||
return format_to(_context.out(), "{}", _value.str());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<> struct hash<solidity::yul::YulString>
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/AsmPrinter.h>
|
||||
|
||||
#include <libsolutil/cxx20.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
@ -230,7 +229,7 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
|
||||
// not only for builtins.
|
||||
if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression))
|
||||
if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name))
|
||||
if (builtin->controlFlowSideEffects.terminates)
|
||||
if (builtin->controlFlowSideEffects.terminatesOrReverts())
|
||||
{
|
||||
m_currentBlock->exit = CFG::BasicBlock::Terminated{};
|
||||
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
|
||||
|
@ -57,8 +57,20 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
||||
f.parameters.resize(static_cast<size_t>(info.args));
|
||||
f.returns.resize(static_cast<size_t>(info.ret));
|
||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
||||
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
|
||||
if (evmasm::SemanticInformation::terminatesControlFlow(_instruction))
|
||||
{
|
||||
f.controlFlowSideEffects.canContinue = false;
|
||||
if (evmasm::SemanticInformation::reverts(_instruction))
|
||||
{
|
||||
f.controlFlowSideEffects.canTerminate = false;
|
||||
f.controlFlowSideEffects.canRevert = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
f.controlFlowSideEffects.canTerminate = true;
|
||||
f.controlFlowSideEffects.canRevert = false;
|
||||
}
|
||||
}
|
||||
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
||||
f.literalArguments.clear();
|
||||
f.instruction = _instruction;
|
||||
|
@ -484,7 +484,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
|
||||
yulAssert(!_block.operations.empty(), "");
|
||||
CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation);
|
||||
yulAssert(builtinCall, "");
|
||||
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminates, "");
|
||||
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), "");
|
||||
}
|
||||
}, _block.exit);
|
||||
// TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump.
|
||||
|
@ -129,8 +129,9 @@ WasmDialect::WasmDialect()
|
||||
m_functions["unreachable"_yulstring].sideEffects.storage = SideEffects::None;
|
||||
m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None;
|
||||
m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None;
|
||||
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
|
||||
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
|
||||
m_functions["unreachable"_yulstring].controlFlowSideEffects.canTerminate = false;
|
||||
m_functions["unreachable"_yulstring].controlFlowSideEffects.canRevert = true;
|
||||
m_functions["unreachable"_yulstring].controlFlowSideEffects.canContinue = false;
|
||||
|
||||
addFunction("datasize", {i64}, {i64}, true, {LiteralKind::String});
|
||||
addFunction("dataoffset", {i64}, {i64}, true, {LiteralKind::String});
|
||||
@ -215,11 +216,11 @@ void WasmDialect::addExternals()
|
||||
{"eth", "log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
||||
{"eth", "getBlockNumber", {}, {i64}},
|
||||
{"eth", "getTxOrigin", {i32ptr}, {}},
|
||||
{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
|
||||
{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
|
||||
{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false, false}},
|
||||
{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{false, true, false}},
|
||||
{"eth", "getReturnDataSize", {}, {i32}},
|
||||
{"eth", "returnDataCopy", {i32ptr, i32, i32}, {}},
|
||||
{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
|
||||
{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{false, true, false}},
|
||||
{"eth", "getBlockTimestamp", {}, {i64}},
|
||||
{"debug", "print32", {i32}, {}},
|
||||
{"debug", "print64", {i64}, {}},
|
||||
@ -240,7 +241,7 @@ void WasmDialect::addExternals()
|
||||
// TODO some of them are side effect free.
|
||||
f.sideEffects = SideEffects::worst();
|
||||
f.sideEffects.cannotLoop = true;
|
||||
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminates;
|
||||
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminatesOrReverts();
|
||||
f.controlFlowSideEffects = ext.controlFlowSideEffects;
|
||||
f.isMSize = false;
|
||||
f.literalArguments.clear();
|
||||
|
@ -22,7 +22,7 @@ using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::yul;
|
||||
|
||||
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block& _block)
|
||||
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block const& _block)
|
||||
{
|
||||
FunctionDefinitionCollector functionDefinitionCollector;
|
||||
functionDefinitionCollector(_block);
|
||||
|
@ -34,7 +34,7 @@ namespace solidity::yul
|
||||
class FunctionDefinitionCollector: ASTWalker
|
||||
{
|
||||
public:
|
||||
static std::map<YulString, FunctionDefinition const*> run(Block& _block);
|
||||
static std::map<YulString, FunctionDefinition const*> run(Block const& _block);
|
||||
private:
|
||||
using ASTWalker::operator();
|
||||
void operator()(FunctionDefinition const& _functionDefinition) override;
|
||||
|
@ -78,6 +78,9 @@
|
||||
#include <range/v3/view/map.hpp>
|
||||
#include <range/v3/action/remove.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::yul;
|
||||
@ -87,7 +90,7 @@ void OptimiserSuite::run(
|
||||
GasMeter const* _meter,
|
||||
Object& _object,
|
||||
bool _optimizeStackAllocation,
|
||||
string const& _optimisationSequence,
|
||||
string_view _optimisationSequence,
|
||||
optional<size_t> _expectedExecutionsPerDeployment,
|
||||
set<YulString> const& _externallyUsedIdentifiers
|
||||
)
|
||||
@ -267,9 +270,9 @@ map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
|
||||
return lookupTable;
|
||||
}
|
||||
|
||||
void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
|
||||
void OptimiserSuite::validateSequence(string_view _stepAbbreviations)
|
||||
{
|
||||
bool insideLoop = false;
|
||||
int8_t nestingLevel = 0;
|
||||
for (char abbreviation: _stepAbbreviations)
|
||||
switch (abbreviation)
|
||||
{
|
||||
@ -277,12 +280,12 @@ void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
|
||||
case '\n':
|
||||
break;
|
||||
case '[':
|
||||
assertThrow(!insideLoop, OptimizerException, "Nested brackets are not supported");
|
||||
insideLoop = true;
|
||||
assertThrow(nestingLevel < numeric_limits<int8_t>::max(), OptimizerException, "Brackets nested too deep");
|
||||
nestingLevel++;
|
||||
break;
|
||||
case ']':
|
||||
assertThrow(insideLoop, OptimizerException, "Unbalanced brackets");
|
||||
insideLoop = false;
|
||||
nestingLevel--;
|
||||
assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets");
|
||||
break;
|
||||
default:
|
||||
{
|
||||
@ -301,43 +304,99 @@ void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
|
||||
OptimizerException,
|
||||
"'"s + abbreviation + "' is invalid in the current environment: " + *invalid
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
assertThrow(!insideLoop, OptimizerException, "Unbalanced brackets");
|
||||
assertThrow(nestingLevel == 0, OptimizerException, "Unbalanced brackets");
|
||||
}
|
||||
|
||||
void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
|
||||
void OptimiserSuite::runSequence(string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable)
|
||||
{
|
||||
validateSequence(_stepAbbreviations);
|
||||
|
||||
string input = _stepAbbreviations;
|
||||
ranges::actions::remove(input, ' ');
|
||||
ranges::actions::remove(input, '\n');
|
||||
// This splits 'aaa[bbb]ccc...' into 'aaa' and '[bbb]ccc...'.
|
||||
auto extractNonNestedPrefix = [](string_view _tail) -> tuple<string_view, string_view>
|
||||
{
|
||||
for (size_t i = 0; i < _tail.size(); ++i)
|
||||
{
|
||||
yulAssert(_tail[i] != ']');
|
||||
if (_tail[i] == '[')
|
||||
return {_tail.substr(0, i), _tail.substr(i)};
|
||||
}
|
||||
return {_tail, {}};
|
||||
};
|
||||
|
||||
auto abbreviationsToSteps = [](string const& _sequence) -> vector<string>
|
||||
// This splits '[bbb]ccc...' into 'bbb' and 'ccc...'.
|
||||
auto extractBracketContent = [](string_view _tail) -> tuple<string_view, string_view>
|
||||
{
|
||||
yulAssert(!_tail.empty() && _tail[0] == '[');
|
||||
|
||||
size_t contentLength = 0;
|
||||
int8_t nestingLevel = 1;
|
||||
for (char abbreviation: _tail.substr(1))
|
||||
{
|
||||
if (abbreviation == '[')
|
||||
{
|
||||
yulAssert(nestingLevel < numeric_limits<int8_t>::max());
|
||||
++nestingLevel;
|
||||
}
|
||||
else if (abbreviation == ']')
|
||||
{
|
||||
--nestingLevel;
|
||||
if (nestingLevel == 0)
|
||||
break;
|
||||
}
|
||||
++contentLength;
|
||||
}
|
||||
yulAssert(nestingLevel == 0);
|
||||
yulAssert(_tail[contentLength + 1] == ']');
|
||||
|
||||
return {_tail.substr(1, contentLength), _tail.substr(contentLength + 2)};
|
||||
};
|
||||
|
||||
auto abbreviationsToSteps = [](string_view _sequence) -> vector<string>
|
||||
{
|
||||
vector<string> steps;
|
||||
for (char abbreviation: _sequence)
|
||||
steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation));
|
||||
if (abbreviation != ' ' && abbreviation != '\n')
|
||||
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 bracket.
|
||||
size_t currentPairStart = 0;
|
||||
while (currentPairStart < input.size())
|
||||
vector<tuple<string_view, bool>> subsequences;
|
||||
string_view tail = _stepAbbreviations;
|
||||
while (!tail.empty())
|
||||
{
|
||||
size_t openingBracket = input.find('[', currentPairStart);
|
||||
size_t closingBracket = input.find(']', openingBracket);
|
||||
size_t firstCharInside = (openingBracket == string::npos ? input.size() : openingBracket + 1);
|
||||
yulAssert((openingBracket == string::npos) == (closingBracket == string::npos), "");
|
||||
string_view subsequence;
|
||||
tie(subsequence, tail) = extractNonNestedPrefix(tail);
|
||||
if (subsequence.size() > 0)
|
||||
subsequences.push_back({subsequence, false});
|
||||
|
||||
runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingBracket - currentPairStart)), _ast);
|
||||
runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingBracket - firstCharInside)), _ast);
|
||||
if (tail.empty())
|
||||
break;
|
||||
|
||||
currentPairStart = (closingBracket == string::npos ? input.size() : closingBracket + 1);
|
||||
tie(subsequence, tail) = extractBracketContent(tail);
|
||||
if (subsequence.size() > 0)
|
||||
subsequences.push_back({subsequence, true});
|
||||
}
|
||||
|
||||
size_t codeSize = 0;
|
||||
for (size_t round = 0; round < MaxRounds; ++round)
|
||||
{
|
||||
for (auto const& [subsequence, repeat]: subsequences)
|
||||
{
|
||||
if (repeat)
|
||||
runSequence(subsequence, _ast, true);
|
||||
else
|
||||
runSequence(abbreviationsToSteps(subsequence), _ast);
|
||||
}
|
||||
|
||||
if (!_repeatUntilStable)
|
||||
break;
|
||||
|
||||
size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast);
|
||||
if (newSize == codeSize)
|
||||
break;
|
||||
codeSize = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,24 +424,3 @@ void OptimiserSuite::runSequence(std::vector<string> const& _steps, Block& _ast)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OptimiserSuite::runSequenceUntilStable(
|
||||
std::vector<string> const& _steps,
|
||||
Block& _ast,
|
||||
size_t maxRounds
|
||||
)
|
||||
{
|
||||
if (_steps.empty())
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
|
||||
namespace solidity::yul
|
||||
@ -64,22 +65,17 @@ public:
|
||||
GasMeter const* _meter,
|
||||
Object& _object,
|
||||
bool _optimizeStackAllocation,
|
||||
std::string const& _optimisationSequence,
|
||||
std::string_view _optimisationSequence,
|
||||
std::optional<size_t> _expectedExecutionsPerDeployment,
|
||||
std::set<YulString> const& _externallyUsedIdentifiers = {}
|
||||
);
|
||||
|
||||
/// Ensures that specified sequence of step abbreviations is well-formed and can be executed.
|
||||
/// @throw OptimizerException if the sequence is invalid
|
||||
static void validateSequence(std::string const& _stepAbbreviations);
|
||||
static void validateSequence(std::string_view _stepAbbreviations);
|
||||
|
||||
void runSequence(std::vector<std::string> const& _steps, Block& _ast);
|
||||
void runSequence(std::string const& _stepAbbreviations, Block& _ast);
|
||||
void runSequenceUntilStable(
|
||||
std::vector<std::string> const& _steps,
|
||||
Block& _ast,
|
||||
size_t maxRounds = MaxRounds
|
||||
);
|
||||
void runSequence(std::string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable = false);
|
||||
|
||||
static std::map<std::string, std::unique_ptr<OptimiserStep>> const& allSteps();
|
||||
static std::map<std::string, char> const& stepNameToAbbreviationMap();
|
||||
|
@ -413,7 +413,7 @@ def commandline_parser() -> ArgumentParser:
|
||||
action='store_true',
|
||||
help="Immediately exit and print compiler output if the compiler exits with an error.",
|
||||
)
|
||||
return parser;
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -20,7 +20,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
YULARGS=(--strict-assembly)
|
||||
FULLARGS=(--optimize --ignore-missing --combined-json "abi,asm,ast,bin,bin-runtime,devdoc,hashes,metadata,opcodes,srcmap,srcmap-runtime,userdoc")
|
||||
FULLARGS=(--optimize --combined-json "abi,asm,ast,bin,bin-runtime,devdoc,hashes,metadata,opcodes,srcmap,srcmap-runtime,userdoc")
|
||||
OLDARGS=(--optimize --combined-json "abi,asm,ast,bin,bin-runtime,devdoc,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc")
|
||||
function compileFull()
|
||||
{
|
||||
@ -99,7 +99,7 @@ function compileFull()
|
||||
printError "Inside directory:"
|
||||
echo " $(pwd)"
|
||||
printError "Input was:"
|
||||
cat -- "${files[@]}"
|
||||
echo "${files[@]}"
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ def colorize(left, right, id):
|
||||
reset = "\x1b[0m"
|
||||
colors = [red, yellow]
|
||||
color = colors[id % len(colors)]
|
||||
function, arguments, results = parse_call(right)
|
||||
function, _arguments, _results = parse_call(right)
|
||||
left = left.replace("compileAndRun", color + "compileAndRun" + reset)
|
||||
right = right.replace("constructor", color + "constructor" + reset)
|
||||
if function:
|
||||
@ -158,7 +158,7 @@ def main(argv):
|
||||
interactive = False
|
||||
input_file = None
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "if:")
|
||||
opts, _args = getopt.getopt(argv, "if:")
|
||||
except getopt.GetoptError:
|
||||
print("./remove-testcases.py [-i] [-f <full path to SolidityEndToEndTest.cpp>]")
|
||||
sys.exit(1)
|
||||
|
@ -158,7 +158,7 @@ class TraceAnalyser:
|
||||
for trace_id, trace in enumerate(left.traces):
|
||||
left_trace = trace
|
||||
right_trace = right.traces[trace_id]
|
||||
assert (left_trace.kind == right_trace.kind)
|
||||
assert left_trace.kind == right_trace.kind
|
||||
if str(left_trace) != str(right_trace):
|
||||
mismatch_info = " " + str(left_trace) + "\n"
|
||||
mismatch_info += " " + str(right_trace) + "\n"
|
||||
@ -179,7 +179,7 @@ def main(argv):
|
||||
extracted_tests_trace_file = None
|
||||
end_to_end_trace_file = None
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "s:e:")
|
||||
opts, _args = getopt.getopt(argv, "s:e:")
|
||||
except getopt.GetoptError:
|
||||
print("verify-testcases.py [-s <path to semantic-trace>] [-e <path to endToEndExtraction-trace>]")
|
||||
sys.exit(2)
|
||||
|
@ -261,9 +261,9 @@ def main(argv):
|
||||
no_confirm = False
|
||||
examine_coverage = False
|
||||
next_id = False
|
||||
opts, args = getopt.getopt(argv, "", ["check", "fix", "no-confirm", "examine-coverage", "next"])
|
||||
opts, _args = getopt.getopt(argv, "", ["check", "fix", "no-confirm", "examine-coverage", "next"])
|
||||
|
||||
for opt, arg in opts:
|
||||
for opt, _arg in opts:
|
||||
if opt == "--check":
|
||||
check = True
|
||||
elif opt == "--fix":
|
||||
|
@ -49,9 +49,10 @@ def readDependencies(fname):
|
||||
if line[0] == '\t':
|
||||
library = line.split(' ', 1)[0][1:]
|
||||
if (library.startswith("/usr/local/lib") or
|
||||
library.startswith("/usr/local/opt") or
|
||||
library.startswith("/Users/")):
|
||||
if (os.path.basename(library) != os.path.basename(fname)):
|
||||
library.startswith("/usr/local/opt") or
|
||||
library.startswith("/Users/")
|
||||
):
|
||||
if os.path.basename(library) != os.path.basename(fname):
|
||||
command = "install_name_tool -change " + \
|
||||
library + " @executable_path/./" + \
|
||||
os.path.basename(library) + " " + fname
|
||||
|
@ -20,7 +20,7 @@ repository. The changes are compared against ``origin/develop``.
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from enum import Enum
|
||||
from parsec import *
|
||||
from parsec import generate, ParseError, regex, string
|
||||
from tabulate import tabulate
|
||||
|
||||
class Kind(Enum):
|
||||
@ -56,10 +56,10 @@ def diff_string() -> (Kind, Diff, int):
|
||||
-// gas irOptimized: 138070
|
||||
|
||||
"""
|
||||
diff_kind = yield (minus | plus)
|
||||
diff_kind = yield minus | plus
|
||||
yield comment
|
||||
yield space
|
||||
codegen_kind = yield (gas_ir_optimized ^ gas_legacy_optimized ^ gas_legacy)
|
||||
codegen_kind = yield gas_ir_optimized ^ gas_legacy_optimized ^ gas_legacy
|
||||
yield colon
|
||||
yield space
|
||||
val = yield number()
|
||||
|
@ -179,16 +179,6 @@ case $(uname -s) in
|
||||
. /etc/os-release
|
||||
install_z3=""
|
||||
case $VERSION_ID in
|
||||
7)
|
||||
#wheezy
|
||||
echo "Installing solidity dependencies on Debian Wheezy (7.x)."
|
||||
echo "ERROR - 'install_deps.sh' doesn't have Debian Wheezy support yet."
|
||||
echo "See https://docs.soliditylang.org/en/latest/installing-solidity.html for manual instructions."
|
||||
echo "If you would like to get 'install_deps.sh' working for Debian Wheezy, that would be fantastic."
|
||||
echo "Drop us a message at https://gitter.im/ethereum/solidity-dev."
|
||||
echo "See also https://github.com/ethereum/webthree-umbrella/issues/495 where we are working through Alpine support."
|
||||
exit 1
|
||||
;;
|
||||
8)
|
||||
#jessie
|
||||
echo "Installing solidity dependencies on Debian Jesse (8.x)."
|
||||
@ -277,32 +267,11 @@ case $(uname -s) in
|
||||
echo "Installing solidity dependencies on Ubuntu Trusty Tahr (14.04)."
|
||||
echo "Or, you may also be running Linux Mint Qiana / Rebecca / Rafaela / Rosa (base: Ubuntu Trusty Tahr (14.04).)"
|
||||
;;
|
||||
utopic)
|
||||
echo "Installing solidity dependencies on Ubuntu Utopic Unicorn (14.10)."
|
||||
;;
|
||||
vivid)
|
||||
echo "Installing solidity dependencies on Ubuntu Vivid Vervet (15.04)."
|
||||
;;
|
||||
wily)
|
||||
echo "Installing solidity dependencies on Ubuntu Wily Werewolf (15.10)."
|
||||
;;
|
||||
xenial|sarah|serena|sonya|sylvia)
|
||||
echo "Installing solidity dependencies on Ubuntu Xenial Xerus (16.04)."
|
||||
echo "Or, you may also be running Linux Mint Sarah / Serena / Sonya / Sylvia (base: Ubuntu Xenial Xerus (16.04).)"
|
||||
install_z3="libz3-dev"
|
||||
;;
|
||||
yakkety)
|
||||
echo "Installing solidity dependencies on Ubuntu Yakkety Yak (16.10)."
|
||||
install_z3="libz3-dev"
|
||||
;;
|
||||
zesty)
|
||||
echo "Installing solidity dependencies on Ubuntu Zesty (17.04)."
|
||||
install_z3="libz3-dev"
|
||||
;;
|
||||
artful)
|
||||
echo "Installing solidity dependencies on Ubuntu Artful (17.10)."
|
||||
install_z3="libz3-dev"
|
||||
;;
|
||||
bionic)
|
||||
echo "Installing solidity dependencies."
|
||||
install_z3="libz3-dev"
|
||||
@ -311,6 +280,10 @@ case $(uname -s) in
|
||||
echo "Installing solidity dependencies."
|
||||
install_z3="libz3-dev"
|
||||
;;
|
||||
hirsute)
|
||||
echo "Installing solidity dependencies."
|
||||
install_z3="libz3-dev"
|
||||
;;
|
||||
betsy)
|
||||
#do not try anything for betsy.
|
||||
echo "Linux Mint Betsy is not supported at the moment as it runs off of Debian."
|
||||
@ -325,7 +298,7 @@ case $(uname -s) in
|
||||
echo "ERROR - Unknown or unsupported Ubuntu version ($(lsb_release -cs))"
|
||||
echo "ERROR - This might not work, but we are trying anyway."
|
||||
echo "Please drop us a message at https://gitter.im/ethereum/solidity-dev."
|
||||
echo "We only support Trusty, Utopic, Vivid, Wily, Xenial, Yakkety, Zesty, Artful and Bionic."
|
||||
echo "We only support Trusty, Xenial, Bionic, Focal, and Hirsute."
|
||||
install_z3="libz3-dev"
|
||||
;;
|
||||
esac
|
||||
|
@ -6,7 +6,6 @@
|
||||
# into files for e.g. fuzz testing as
|
||||
# scripts/isolate_tests.py test/libsolidity/*
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import hashlib
|
||||
|
@ -9,7 +9,6 @@ from os import path, walk
|
||||
from sys import exit
|
||||
from textwrap import dedent
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PROJECT_ROOT = path.dirname(path.dirname(path.realpath(__file__)))
|
||||
PYLINT_RCFILE = f"{PROJECT_ROOT}/scripts/pylintrc"
|
||||
|
@ -24,24 +24,15 @@ disable=
|
||||
duplicate-code,
|
||||
invalid-name,
|
||||
missing-docstring,
|
||||
mixed-indentation,
|
||||
no-else-return,
|
||||
no-self-use,
|
||||
pointless-string-statement,
|
||||
redefined-builtin,
|
||||
redefined-outer-name,
|
||||
singleton-comparison,
|
||||
superfluous-parens,
|
||||
too-few-public-methods,
|
||||
trailing-newlines,
|
||||
undefined-variable,
|
||||
ungrouped-imports,
|
||||
unnecessary-semicolon,
|
||||
unused-import,
|
||||
unused-variable,
|
||||
unused-wildcard-import,
|
||||
useless-object-inheritance,
|
||||
wildcard-import
|
||||
too-many-public-methods,
|
||||
ungrouped-imports
|
||||
|
||||
[BASIC]
|
||||
|
||||
|
@ -11,7 +11,7 @@ import time
|
||||
|
||||
DESCRIPTION = """Regressor is a tool to run regression tests in a CI env."""
|
||||
|
||||
class PrintDotsThread(object):
|
||||
class PrintDotsThread:
|
||||
"""Prints a dot every "interval" (default is 300) seconds"""
|
||||
|
||||
def __init__(self, interval=300):
|
||||
@ -30,7 +30,7 @@ class PrintDotsThread(object):
|
||||
print(".")
|
||||
time.sleep(self.interval)
|
||||
|
||||
class regressor():
|
||||
class regressor:
|
||||
_re_sanitizer_log = re.compile(r"""ERROR: (libFuzzer|UndefinedBehaviorSanitizer)""")
|
||||
|
||||
def __init__(self, description, args):
|
||||
|
@ -105,10 +105,11 @@ ppafilesurl=https://launchpad.net/~ethereum/+archive/ubuntu/${pparepo}/+files
|
||||
git clone --depth 2 --recursive https://github.com/ethereum/solidity.git -b "$branch"
|
||||
mv solidity solc
|
||||
|
||||
# Fetch jsoncpp dependency
|
||||
# Fetch dependencies
|
||||
mkdir -p ./solc/deps/downloads/ 2>/dev/null || true
|
||||
wget -O ./solc/deps/downloads/jsoncpp-1.9.3.tar.gz https://github.com/open-source-parsers/jsoncpp/archive/1.9.3.tar.gz
|
||||
wget -O ./solc/deps/downloads/range-v3-0.11.0.tar.gz https://github.com/ericniebler/range-v3/archive/0.11.0.tar.gz
|
||||
wget -O ./solc/deps/downloads/fmt-8.0.1.tar.gz https://github.com/fmtlib/fmt/archive/8.0.1.tar.gz
|
||||
|
||||
# Determine version
|
||||
cd solc
|
||||
|
@ -44,9 +44,7 @@ def writeSourceToFile(lines):
|
||||
os.system("mkdir -p " + filePath)
|
||||
with open(srcName, mode='a+', encoding='utf8', newline='') as f:
|
||||
createdSources.append(srcName)
|
||||
i = 0
|
||||
for idx, line in enumerate(lines[1:]):
|
||||
|
||||
# write to file
|
||||
if line[:12] != "==== Source:":
|
||||
f.write(line)
|
||||
|
@ -7,7 +7,8 @@ import sys
|
||||
import re
|
||||
import os
|
||||
import hashlib
|
||||
from os.path import join, isfile
|
||||
# Pylint for some reason insists that isfile() is unused
|
||||
from os.path import join, isfile # pylint: disable=unused-import
|
||||
|
||||
|
||||
def extract_test_cases(path):
|
||||
|
@ -41,9 +41,9 @@ parts:
|
||||
source-tag: z3-4.8.4
|
||||
plugin: make
|
||||
build-packages: [python3]
|
||||
stage-packages: [libstdc++6]
|
||||
stage-packages: [libstdc++6, libgomp1]
|
||||
override-build: |
|
||||
python scripts/mk_make.py
|
||||
python3 scripts/mk_make.py
|
||||
cd build
|
||||
make -j -l $(grep -c "^processor" /proc/cpuinfo)
|
||||
make install DESTDIR=$SNAPCRAFT_PART_INSTALL
|
||||
|
@ -553,7 +553,7 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da
|
||||
if (fs::exists(pathName) && !m_options.output.overwriteFiles)
|
||||
{
|
||||
serr() << "Refusing to overwrite existing file \"" << pathName << "\" (use --overwrite to force)." << endl;
|
||||
m_error = true;
|
||||
m_outputFailed = true;
|
||||
return;
|
||||
}
|
||||
ofstream outFile(pathName);
|
||||
@ -561,7 +561,7 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da
|
||||
if (!outFile)
|
||||
{
|
||||
serr() << "Could not write to file \"" << pathName << "\"." << endl;
|
||||
m_error = true;
|
||||
m_outputFailed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -631,6 +631,8 @@ bool CommandLineInterface::compile()
|
||||
m_compiler->setViaIR(m_options.output.experimentalViaIR);
|
||||
m_compiler->setEVMVersion(m_options.output.evmVersion);
|
||||
m_compiler->setRevertStringBehaviour(m_options.output.revertStrings);
|
||||
if (m_options.output.debugInfoSelection.has_value())
|
||||
m_compiler->selectDebugInfo(m_options.output.debugInfoSelection.value());
|
||||
// TODO: Perhaps we should not compile unless requested
|
||||
|
||||
m_compiler->enableIRGeneration(m_options.compiler.outputs.ir || m_options.compiler.outputs.irOptimized);
|
||||
@ -699,30 +701,6 @@ bool CommandLineInterface::compile()
|
||||
formatter.printExceptionInformation(_exception, "Compiler error");
|
||||
return false;
|
||||
}
|
||||
catch (InternalCompilerError const& _exception)
|
||||
{
|
||||
serr() <<
|
||||
"Internal compiler error during compilation:" <<
|
||||
endl <<
|
||||
boost::diagnostic_information(_exception);
|
||||
return false;
|
||||
}
|
||||
catch (UnimplementedFeatureError const& _exception)
|
||||
{
|
||||
serr() <<
|
||||
"Unimplemented feature:" <<
|
||||
endl <<
|
||||
boost::diagnostic_information(_exception);
|
||||
return false;
|
||||
}
|
||||
catch (smtutil::SMTLogicError const& _exception)
|
||||
{
|
||||
serr() <<
|
||||
"SMT logic error during analysis:" <<
|
||||
endl <<
|
||||
boost::diagnostic_information(_exception);
|
||||
return false;
|
||||
}
|
||||
catch (Error const& _error)
|
||||
{
|
||||
if (_error.type() == Error::Type::DocstringParsingError)
|
||||
@ -735,23 +713,6 @@ bool CommandLineInterface::compile()
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception const& _exception)
|
||||
{
|
||||
serr() << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl;
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
serr() << "Unknown exception during compilation" << (
|
||||
_e.what() ? ": " + string(_e.what()) : "."
|
||||
) << endl;
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
serr() << "Unknown exception during compilation." << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -894,7 +855,7 @@ bool CommandLineInterface::actOnInput()
|
||||
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, "");
|
||||
outputCompilationResults();
|
||||
}
|
||||
return !m_error;
|
||||
return !m_outputFailed;
|
||||
}
|
||||
|
||||
bool CommandLineInterface::link()
|
||||
@ -976,6 +937,7 @@ void CommandLineInterface::writeLinkedFiles()
|
||||
if (!outFile)
|
||||
{
|
||||
serr() << "Could not write to file " << src.first << ". Aborting." << endl;
|
||||
m_outputFailed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1013,34 +975,16 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
|
||||
auto& stack = assemblyStacks[src.first] = yul::AssemblyStack(
|
||||
m_options.output.evmVersion,
|
||||
_language,
|
||||
m_options.optimiserSettings()
|
||||
m_options.optimiserSettings(),
|
||||
m_options.output.debugInfoSelection.has_value() ?
|
||||
m_options.output.debugInfoSelection.value() :
|
||||
DebugInfoSelection::Default()
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
if (!stack.parseAndAnalyze(src.first, src.second))
|
||||
successful = false;
|
||||
else
|
||||
stack.optimize();
|
||||
}
|
||||
catch (Exception const& _exception)
|
||||
{
|
||||
serr() << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl;
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
serr() <<
|
||||
"Unknown exception during compilation" <<
|
||||
(_e.what() ? ": " + string(_e.what()) : ".") <<
|
||||
endl;
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
serr() << "Unknown exception in assembler." << endl;
|
||||
return false;
|
||||
}
|
||||
if (!stack.parseAndAnalyze(src.first, src.second))
|
||||
successful = false;
|
||||
else
|
||||
stack.optimize();
|
||||
}
|
||||
|
||||
for (auto const& sourceAndStack: assemblyStacks)
|
||||
@ -1074,29 +1018,8 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
|
||||
|
||||
if (_language != yul::AssemblyStack::Language::Ewasm && _targetMachine == yul::AssemblyStack::Machine::Ewasm)
|
||||
{
|
||||
try
|
||||
{
|
||||
stack.translate(yul::AssemblyStack::Language::Ewasm);
|
||||
stack.optimize();
|
||||
}
|
||||
catch (Exception const& _exception)
|
||||
{
|
||||
serr() << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl;
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
serr() <<
|
||||
"Unknown exception during compilation" <<
|
||||
(_e.what() ? ": " + string(_e.what()) : ".") <<
|
||||
endl;
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
serr() << "Unknown exception in assembler." << endl;
|
||||
return false;
|
||||
}
|
||||
stack.translate(yul::AssemblyStack::Language::Ewasm);
|
||||
stack.optimize();
|
||||
|
||||
sout() << endl << "==========================" << endl;
|
||||
sout() << endl << "Translated source:" << endl;
|
||||
@ -1104,28 +1027,8 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
|
||||
}
|
||||
|
||||
yul::MachineAssemblyObject object;
|
||||
try
|
||||
{
|
||||
object = stack.assemble(_targetMachine);
|
||||
object.bytecode->link(m_options.linker.libraries);
|
||||
}
|
||||
catch (Exception const& _exception)
|
||||
{
|
||||
serr() << "Exception while assembling: " << boost::diagnostic_information(_exception) << endl;
|
||||
return false;
|
||||
}
|
||||
catch (std::exception const& _e)
|
||||
{
|
||||
serr() << "Unknown exception during compilation" << (
|
||||
_e.what() ? ": " + string(_e.what()) : "."
|
||||
) << endl;
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
serr() << "Unknown exception while assembling." << endl;
|
||||
return false;
|
||||
}
|
||||
object = stack.assemble(_targetMachine);
|
||||
object.bytecode->link(m_options.linker.libraries);
|
||||
|
||||
sout() << endl << "Binary representation:" << endl;
|
||||
if (object.bytecode)
|
||||
|
@ -121,7 +121,7 @@ private:
|
||||
std::ostream& m_sout;
|
||||
std::ostream& m_serr;
|
||||
bool m_hasOutput = false;
|
||||
bool m_error = false; ///< If true, some error occurred.
|
||||
bool m_outputFailed = false; ///< If true, creation or write to some of the output files failed.
|
||||
FileReader m_fileReader;
|
||||
std::optional<std::string> m_standardJsonInput;
|
||||
std::unique_ptr<frontend::CompilerStack> m_compiler;
|
||||
|
@ -67,6 +67,7 @@ static string const g_strImportAst = "import-ast";
|
||||
static string const g_strInputFile = "input-file";
|
||||
static string const g_strYul = "yul";
|
||||
static string const g_strYulDialect = "yul-dialect";
|
||||
static string const g_strDebugInfo = "debug-info";
|
||||
static string const g_strIPFS = "ipfs";
|
||||
static string const g_strLicense = "license";
|
||||
static string const g_strLibraries = "libraries";
|
||||
@ -137,6 +138,14 @@ static set<string> const g_metadataHashArgs
|
||||
g_strNone
|
||||
};
|
||||
|
||||
static map<InputMode, string> const g_inputModeName = {
|
||||
{InputMode::Compiler, "compiler"},
|
||||
{InputMode::CompilerWithASTImport, "compiler (AST import)"},
|
||||
{InputMode::Assembler, "assembler"},
|
||||
{InputMode::StandardJson, "standard JSON"},
|
||||
{InputMode::Linker, "linker"},
|
||||
};
|
||||
|
||||
void CommandLineParser::printVersionAndExit()
|
||||
{
|
||||
sout() <<
|
||||
@ -244,6 +253,7 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex
|
||||
output.evmVersion == _other.output.evmVersion &&
|
||||
output.experimentalViaIR == _other.output.experimentalViaIR &&
|
||||
output.revertStrings == _other.output.revertStrings &&
|
||||
output.debugInfoSelection == _other.output.debugInfoSelection &&
|
||||
output.stopAfter == _other.output.stopAfter &&
|
||||
input.mode == _other.input.mode &&
|
||||
assembly.targetMachine == _other.assembly.targetMachine &&
|
||||
@ -460,6 +470,47 @@ bool CommandLineParser::parseLibraryOption(string const& _input)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CommandLineParser::parseOutputSelection()
|
||||
{
|
||||
static auto outputSupported = [](InputMode _mode, string_view _outputName)
|
||||
{
|
||||
static set<string> const compilerModeOutputs =
|
||||
CompilerOutputs::componentMap() |
|
||||
ranges::views::keys |
|
||||
ranges::to<set>();
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case InputMode::Compiler:
|
||||
case InputMode::CompilerWithASTImport:
|
||||
return contains(compilerModeOutputs, _outputName);
|
||||
case InputMode::Assembler:
|
||||
case InputMode::StandardJson:
|
||||
case InputMode::Linker:
|
||||
return false;
|
||||
}
|
||||
|
||||
solAssert(false, "");
|
||||
};
|
||||
|
||||
for (auto&& [optionName, outputComponent]: CompilerOutputs::componentMap())
|
||||
m_options.compiler.outputs.*outputComponent = (m_args.count(optionName) > 0);
|
||||
|
||||
vector<string> unsupportedOutputs;
|
||||
for (auto&& [optionName, outputComponent]: CompilerOutputs::componentMap())
|
||||
if (m_options.compiler.outputs.*outputComponent && !outputSupported(m_options.input.mode, optionName))
|
||||
unsupportedOutputs.push_back(optionName);
|
||||
|
||||
if (!unsupportedOutputs.empty())
|
||||
{
|
||||
serr() << "The following outputs are not supported in " << g_inputModeName.at(m_options.input.mode) << " mode: ";
|
||||
serr() << joinOptionNames(unsupportedOutputs) << ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
po::options_description CommandLineParser::optionsDescription()
|
||||
{
|
||||
// Declare the supported options.
|
||||
@ -546,6 +597,13 @@ General Information)").c_str(),
|
||||
po::value<string>()->value_name(joinHumanReadable(g_revertStringsArgs, ",")),
|
||||
"Strip revert (and require) reason strings or add additional debugging information."
|
||||
)
|
||||
(
|
||||
g_strDebugInfo.c_str(),
|
||||
po::value<string>()->default_value(toString(DebugInfoSelection::Default())),
|
||||
("Debug info components to be included in the produced EVM assembly and Yul code. "
|
||||
"Value can be all, none or a comma-separated list containing one or more of the "
|
||||
"following components: " + joinHumanReadable(DebugInfoSelection::componentMap() | ranges::views::keys) + ".").c_str()
|
||||
)
|
||||
(
|
||||
g_strStopAfter.c_str(),
|
||||
po::value<string>()->value_name("stage"),
|
||||
@ -886,6 +944,12 @@ bool CommandLineParser::processArgs()
|
||||
serr() << "Option --" << option << " is only valid in compiler and assembler modes." << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_args[g_strDebugInfo].defaulted())
|
||||
{
|
||||
serr() << "Option --" << g_strDebugInfo << " is only valid in compiler and assembler modes." << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_args.count(g_strColor) > 0)
|
||||
@ -912,6 +976,23 @@ bool CommandLineParser::processArgs()
|
||||
m_options.output.revertStrings = *revertStrings;
|
||||
}
|
||||
|
||||
if (!m_args[g_strDebugInfo].defaulted())
|
||||
{
|
||||
string optionValue = m_args[g_strDebugInfo].as<string>();
|
||||
m_options.output.debugInfoSelection = DebugInfoSelection::fromString(optionValue);
|
||||
if (!m_options.output.debugInfoSelection.has_value())
|
||||
{
|
||||
serr() << "Invalid value for --" << g_strDebugInfo << " option: " << optionValue << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_options.output.debugInfoSelection->snippet && !m_options.output.debugInfoSelection->location)
|
||||
{
|
||||
serr() << "To use 'snippet' with --" << g_strDebugInfo << " you must select also 'location'." << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parseCombinedJsonOption())
|
||||
return false;
|
||||
|
||||
@ -930,8 +1011,8 @@ bool CommandLineParser::processArgs()
|
||||
m_options.formatting.json.indent = m_args[g_strJsonIndent].as<uint32_t>();
|
||||
}
|
||||
|
||||
for (auto&& [optionName, outputComponent]: CompilerOutputs::componentMap())
|
||||
m_options.compiler.outputs.*outputComponent = (m_args.count(optionName) > 0);
|
||||
if (!parseOutputSelection())
|
||||
return false;
|
||||
|
||||
m_options.compiler.estimateGas = (m_args.count(g_strGas) > 0);
|
||||
|
||||
|
@ -24,8 +24,12 @@
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
#include <libsolidity/interface/FileReader.h>
|
||||
#include <libsolidity/interface/ImportRemapper.h>
|
||||
|
||||
#include <libyul/AssemblyStack.h>
|
||||
|
||||
#include <liblangutil/DebugInfoSelection.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libsolutil/JSON.h>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
@ -174,6 +178,7 @@ struct CommandLineOptions
|
||||
langutil::EVMVersion evmVersion;
|
||||
bool experimentalViaIR = false;
|
||||
RevertStrings revertStrings = RevertStrings::Default;
|
||||
std::optional<langutil::DebugInfoSelection> debugInfoSelection;
|
||||
CompilerStack::State stopAfter = CompilerStack::State::CompilationSuccessful;
|
||||
} output;
|
||||
|
||||
@ -286,6 +291,8 @@ private:
|
||||
/// @return false if there are any validation errors, true otherwise.
|
||||
bool parseLibraryOption(std::string const& _input);
|
||||
|
||||
bool parseOutputSelection();
|
||||
|
||||
bool checkMutuallyExclusive(std::vector<std::string> const& _optionNames);
|
||||
[[noreturn]] void printVersionAndExit();
|
||||
[[noreturn]] void printLicenseAndExit();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user