Merge remote-tracking branch 'origin/develop' into HEAD

This commit is contained in:
chriseth 2021-10-19 18:24:42 +02:00
commit e239f62b64
364 changed files with 5168 additions and 893 deletions

View File

@ -627,14 +627,14 @@ jobs:
- checkout - checkout
- restore_cache: - restore_cache:
keys: 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 # 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. # WARNING! If you do edit anything here instead, remember to invalidate the cache manually.
- run: - run:
name: Install build dependencies name: Install build dependencies
command: ./.circleci/osx_install_dependencies.sh command: ./.circleci/osx_install_dependencies.sh
- save_cache: - save_cache:
key: dependencies-osx-{{ checksum ".circleci/osx_install_dependencies.sh" }} key: dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
paths: paths:
- /usr/local/bin - /usr/local/bin
- /usr/local/sbin - /usr/local/sbin
@ -663,7 +663,7 @@ jobs:
- checkout - checkout
- restore_cache: - restore_cache:
keys: keys:
- dependencies-osx-{{ checksum ".circleci/osx_install_dependencies.sh" }} - dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
- attach_workspace: - attach_workspace:
at: . at: .
- run: *run_soltest - run: *run_soltest
@ -679,7 +679,7 @@ jobs:
- checkout - checkout
- restore_cache: - restore_cache:
keys: keys:
- dependencies-osx-{{ checksum ".circleci/osx_install_dependencies.sh" }} - dependencies-osx-{{ arch }}-{{ checksum ".circleci/osx_install_dependencies.sh" }}
- attach_workspace: - attach_workspace:
at: . at: .
- run: *run_cmdline_tests - run: *run_cmdline_tests
@ -886,14 +886,14 @@ jobs:
- checkout - checkout
- restore_cache: - restore_cache:
keys: 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 # 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. # WARNING! If you do edit anything here instead, remember to invalidate the cache manually.
- run: - run:
name: "Installing dependencies" name: "Installing dependencies"
command: .\scripts\install_deps.ps1 command: .\scripts\install_deps.ps1
- save_cache: - save_cache:
key: dependencies-win-{{ checksum "scripts/install_deps.ps1" }} key: dependencies-win-{{ arch }}-{{ checksum "scripts/install_deps.ps1" }}
paths: paths:
- .\deps - .\deps
- run: - run:

View File

@ -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 if [ ! -f /usr/local/lib/libz3.a ] # if this file does not exists (cache was not restored), rebuild dependencies
then 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 unlink python
brew install boost brew install boost
brew install cmake brew install cmake

View File

@ -41,6 +41,7 @@ include(EthCcache)
# Let's find our dependencies # Let's find our dependencies
include(EthDependencies) include(EthDependencies)
include(fmtlib)
include(jsoncpp) include(jsoncpp)
include(range-v3) include(range-v3)
include_directories(SYSTEM ${JSONCPP_INCLUDE_DIR}) include_directories(SYSTEM ${JSONCPP_INCLUDE_DIR})

View File

@ -13,12 +13,21 @@ Language Features:
Compiler 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. * 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: 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: 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``). * 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
View 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()

View File

@ -12,3 +12,75 @@ pre {
.rst-content table.docutils td { .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;
}

View File

@ -1,11 +1,9 @@
/* links */ /* links */
a, .rst-content a:not(:visited) {
a:visited { color: #aaddff !important;
color: #aaddff;
} }
/* code directives */ /* code directives */
.method dt, .method dt,
@ -46,6 +44,9 @@ em.property {
background-color: #5a5a5a; background-color: #5a5a5a;
} }
.rst-content pre {
background: none;
}
/* inlined code highlights */ /* inlined code highlights */
@ -70,6 +71,12 @@ code.docutils.literal.notranslate {
color: #ddd; color: #ddd;
} }
/* highlight color search text */
.rst-content .highlighted {
background: #ff5722;
box-shadow: 0 0 0 2px #f0978b;
}
/* notes, warnings, hints */ /* notes, warnings, hints */
@ -620,3 +627,9 @@ 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. */
}

View File

@ -75,3 +75,9 @@ html.transition *:after {
transition: ease-in-out 200ms !important; transition: ease-in-out 200ms !important;
transition-delay: 0 !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;
}

View 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
View File

@ -0,0 +1,7 @@
{% extends "!footer.html" %}
{% block extrafooter %}
<p>
<a href="{{ pathto('credits-and-attribution') }}">Credits and attribution</a>.
</p>
{% endblock %}

View File

@ -2,5 +2,9 @@
{% block menu %} {% block menu %}
{{ super() }} {{ super() }}
<ul>
<li>
<a href="{{ pathto('genindex') }}">Keyword Index</a> <a href="{{ pathto('genindex') }}">Keyword Index</a>
</li>
</ul>
{% endblock %} {% endblock %}

View File

@ -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).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>`. - ``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:: .. note::
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness, Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
unless you know what you are doing. unless you know what you are doing.

View File

@ -44,6 +44,7 @@ def setup(sphinx):
extensions = [ extensions = [
'sphinx_a4doc', 'sphinx_a4doc',
'html_extra_template_renderer', 'html_extra_template_renderer',
'remix_code_links',
] ]
a4_base_path = os.path.dirname(__file__) + '/grammar' 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 # 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 # further. For a list of options available for each theme, see the
# documentation. # 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. # Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = [] #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 # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # 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 # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32

View File

@ -86,12 +86,10 @@ Running the Compiler Tests
Prerequisites Prerequisites
------------- -------------
Some tests require the `evmone <https://github.com/ethereum/evmone/releases>`_ For running all compiler tests you may want to optionally install a few
library, others require `libz3 <https://github.com/Z3Prover/z3>`_. The test script dependencies (`evmone <https://github.com/ethereum/evmone/releases>`_,
tries to discover the location of the ``evmone`` library, which can be located `libz3 <https://github.com/Z3Prover/z3>`_, and
in the current directory, installed on the system level, or the ``deps`` folder `libhera <https://github.com/ewasm/hera>`_).
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.
On macOS some of the testing scripts expect GNU coreutils to be installed. On macOS some of the testing scripts expect GNU coreutils to be installed.
This can be easiest accomplished using Homebrew: ``brew install coreutils``. 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 application ``soltest`` (or its wrapper ``scripts/soltest.sh``), as well as command line tests and
compilation tests. compilation tests.
The test system automatically tries to discover the location of the ``evmone`` library The test system automatically tries to discover the location of
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems, the `evmone <https://github.com/ethereum/evmone/releases>`_ for running the semantic tests.
``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``, The ``evmone`` library must be located in the ``deps`` or ``deps/lib`` directory relative to the
``libsolidity/SolidityEndToEndTest``, part of the soltest suite. To run all tests, download the library from current working directory, to its parent or its parent's parent. Alternatively an explicit location
`GitHub <https://github.com/ethereum/evmone/releases/tag/v0.8.0>`_ for the ``evmone`` shared object can be specified via the ``ETH_EVMONE`` environment variable.
and place it in the project root path or inside the ``deps`` folder.
``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 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 SMT tests by exporting ``SMT_FLAGS=--no-smt`` before running ``./scripts/tests.sh`` or

View 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). |
+-------------------------+-----------------------------------------------------------------------+

View File

@ -11,7 +11,7 @@ sign and verify signatures, and setup the payment channel.
Creating and verifying signatures 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 is the sender and Bob is the recipient.
Alice only needs to send cryptographically signed messages off-chain Alice only needs to send cryptographically signed messages off-chain

View 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,
}

View File

@ -1,11 +1,6 @@
Solidity Solidity
======== ========
.. image:: logo.svg
:width: 120px
:alt: Solidity logo
:align: center
Solidity is an object-oriented, high-level language for implementing smart Solidity is an object-oriented, high-level language for implementing smart
contracts. Smart contracts are programs which govern the behaviour of accounts contracts. Smart contracts are programs which govern the behaviour of accounts
within the Ethereum state. 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. and multi-signature wallets.
When deploying contracts, you should use the latest released When deploying contracts, you should use the latest released
version of Solidity. This is because breaking changes as well as version of Solidity. Apart from exceptional cases, only the latest version receives
new features and bug fixes are introduced regularly. We currently use `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>`_. a 0.x version number `to indicate this fast pace of change <https://semver.org/#spec-item-4>`_.
.. warning:: .. warning::

View File

@ -275,7 +275,7 @@ The following transformation steps are the main components:
- Common Subexpression Eliminator - Common Subexpression Eliminator
- Expression Simplifier - Expression Simplifier
- Redundant Assign Eliminator - Redundant Assign Eliminator
- Full Function Inliner - Full Inliner
Optimizer Steps Optimizer Steps
--------------- ---------------
@ -297,7 +297,8 @@ on the individual steps and their sequence below.
- :ref:`for-loop-condition-into-body`. - :ref:`for-loop-condition-into-body`.
- :ref:`for-loop-condition-out-of-body`. - :ref:`for-loop-condition-out-of-body`.
- :ref:`for-loop-init-rewriter`. - :ref:`for-loop-init-rewriter`.
- :ref:`functional-inliner`. - :ref:`expression-inliner`.
- :ref:`full-inliner`.
- :ref:`function-grouper`. - :ref:`function-grouper`.
- :ref:`function-hoister`. - :ref:`function-hoister`.
- :ref:`function-specializer`. - :ref:`function-specializer`.
@ -1082,9 +1083,9 @@ The actual removal of the function is performed by the Unused Pruner.
Function Inlining Function Inlining
----------------- -----------------
.. _functional-inliner: .. _expression-inliner:
FunctionalInliner ExpressionInliner
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
This component of the optimizer performs restricted function inlining by inlining functions that can be 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. 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 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, 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 code is usually very expensive and we would often rather have shorter

View File

@ -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;1:9:1;2:1:2;2:1:2;2:1:2``
``1:2:1;:9;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.

View File

@ -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 storing a value in the storage, the remaining bits need to be cleaned
because otherwise the garbled value can be observed. 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 following operation is not affected. For instance, since any non-zero
value is considered ``true`` by ``JUMPI`` instruction, we do not clean value is considered ``true`` by ``JUMPI`` instruction, we do not clean
the boolean values before they are used as the condition for the boolean values before they are used as the condition for

View File

@ -1,3 +1,6 @@
.. index: ir breaking changes
********************************* *********************************
Solidity IR-based Codegen 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. 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. 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 In the old code generator, each function parameter and return variable has a fixed slot on the stack.
is run multiple times because ``_;`` is used multiple times or used in a loop, then a change to the If the function is run multiple times because ``_;`` is used multiple times or used in a loop, then a
function parameter's value is visible in the next execution of the function. 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. 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 .. 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 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. 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 of contract initialization has changed in case of inheritance.
The order used to be: The order used to be:

View File

@ -92,6 +92,12 @@ Block and Transaction Properties
``msg.value`` can change for every **external** function call. ``msg.value`` can change for every **external** function call.
This includes calls to library functions. 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:: .. note::
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness, Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
unless you know what you are doing. unless you know what you are doing.

View File

@ -308,7 +308,18 @@ Input Description
// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects // "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. // "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) // "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 settings (optional)
"metadata": { "metadata": {

View File

@ -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. the additional optimiser steps will be run on it.
.. _yul-verbatim:
verbatim verbatim
^^^^^^^^ ^^^^^^^^
@ -1263,6 +1265,7 @@ Abbreviation Full name
``a`` ``SSATransform`` ``a`` ``SSATransform``
``t`` ``StructuralSimplifier`` ``t`` ``StructuralSimplifier``
``u`` ``UnusedPruner`` ``u`` ``UnusedPruner``
``p`` ``UnusedFunctionParameterPruner``
``d`` ``VarDeclInitializer`` ``d`` ``VarDeclInitializer``
============ =============================== ============ ===============================

View File

@ -48,11 +48,11 @@ using namespace solidity::evmasm;
using namespace solidity::langutil; using namespace solidity::langutil;
using namespace solidity::util; using namespace solidity::util;
AssemblyItem const& Assembly::append(AssemblyItem const& _i) AssemblyItem const& Assembly::append(AssemblyItem _i)
{ {
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
m_deposit += static_cast<int>(_i.deposit()); 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()) if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid())
m_items.back().setLocation(m_currentSourceLocation); m_items.back().setLocation(m_currentSourceLocation);
m_items.back().m_modifierDepth = m_currentModifierDepth; m_items.back().m_modifierDepth = m_currentModifierDepth;
@ -68,7 +68,7 @@ unsigned Assembly::codeSize(unsigned subTagSize) const
ret += i.second.size(); ret += i.second.size();
for (AssemblyItem const& i: m_items) for (AssemblyItem const& i: m_items)
ret += i.bytesRequired(tagSize); ret += i.bytesRequired(tagSize, Precision::Approximate);
if (numberEncodingSize(ret) <= tagSize) if (numberEncodingSize(ret) <= tagSize)
return static_cast<unsigned>(ret); return static_cast<unsigned>(ret);
} }
@ -96,13 +96,13 @@ public:
m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly) 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) if (_item.location().isValid() && _item.location() != m_location)
{ {
flush(); flush();
m_location = _item.location(); m_location = _item.location();
printLocation(); printLocation(_debugInfoSelection);
} }
string expression = _item.toAssemblyText(m_assembly); string expression = _item.toAssemblyText(m_assembly);
@ -142,16 +142,29 @@ public:
m_pending.clear(); m_pending.clear();
} }
void printLocation() void printLocation(DebugInfoSelection const& _debugInfoSelection)
{ {
if (!m_location.isValid()) if (!m_location.isValid() || (!_debugInfoSelection.location && !_debugInfoSelection.snippet))
return; return;
m_out << m_prefix << " /*"; m_out << m_prefix << " /*";
if (_debugInfoSelection.location)
{
if (m_location.sourceName) if (m_location.sourceName)
m_out << " " + escapeAndQuoteString(*m_location.sourceName); m_out << " " + escapeAndQuoteString(*m_location.sourceName);
if (m_location.hasText()) if (m_location.hasText())
m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end); m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
m_out << " " << locationFromSources(m_sourceCodes, m_location); }
if (_debugInfoSelection.snippet)
{
if (_debugInfoSelection.location)
m_out << " ";
m_out << locationFromSources(m_sourceCodes, m_location);
}
m_out << " */" << endl; 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); Functionalizer f(_out, _prefix, _sourceCodes, *this);
for (auto const& i: m_items) for (auto const& i: m_items)
f.feed(i); f.feed(i, _debugInfoSelection);
f.flush(); f.flush();
if (!m_data.empty() || !m_subs.empty()) 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) for (size_t i = 0; i < m_subs.size(); ++i)
{ {
_out << endl << _prefix << "sub_" << i << ": assembly {\n"; _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; _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; _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; ostringstream tmp;
assemblyStream(tmp, "", _sourceCodes); assemblyStream(tmp, _debugInfoSelection, "", _sourceCodes);
return tmp.str(); return tmp.str();
} }
@ -675,8 +696,11 @@ LinkerObject const& Assembly::assemble() const
break; break;
case PushImmutable: case PushImmutable:
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH32)); 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()); 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()); ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
// Advance bytecode by 32 bytes (default initialized).
ret.bytecode.resize(ret.bytecode.size() + 32); ret.bytecode.resize(ret.bytecode.size() + 32);
break; break;
case VerbatimBytecode: case VerbatimBytecode:
@ -684,6 +708,7 @@ LinkerObject const& Assembly::assemble() const
break; break;
case AssignImmutable: case AssignImmutable:
{ {
// Expect 2 elements on stack (source, dest_base)
auto const& offsets = immutableReferencesBySub[i.data()].second; auto const& offsets = immutableReferencesBySub[i.data()].second;
for (size_t i = 0; i < offsets.size(); ++i) for (size_t i = 0; i < offsets.size(); ++i)
{ {

View File

@ -24,6 +24,7 @@
#include <libevmasm/LinkerObject.h> #include <libevmasm/LinkerObject.h>
#include <libevmasm/Exceptions.h> #include <libevmasm/Exceptions.h>
#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
@ -64,7 +65,7 @@ public:
AssemblyItem newPushImmutable(std::string const& _identifier); AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(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)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; } template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }
@ -142,10 +143,12 @@ public:
/// Create a text representation of the assembly. /// Create a text representation of the assembly.
std::string assemblyString( std::string assemblyString(
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
StringMap const& _sourceCodes = StringMap() StringMap const& _sourceCodes = StringMap()
) const; ) const;
void assemblyStream( void assemblyStream(
std::ostream& _out, std::ostream& _out,
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
std::string const& _prefix = "", std::string const& _prefix = "",
StringMap const& _sourceCodes = StringMap() StringMap const& _sourceCodes = StringMap()
) const; ) const;

View File

@ -65,7 +65,7 @@ void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
setData(data); setData(data);
} }
size_t AssemblyItem::bytesRequired(size_t _addressLength) const size_t AssemblyItem::bytesRequired(size_t _addressLength, Precision _precision) const
{ {
switch (m_type) switch (m_type)
{ {
@ -87,10 +87,25 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
case PushImmutable: case PushImmutable:
return 1 + 32; return 1 + 32;
case AssignImmutable: 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 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: case VerbatimBytecode:
return std::get<2>(*m_verbatimBytecode).size(); return std::get<2>(*m_verbatimBytecode).size();
default: default:
@ -322,6 +337,26 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
return _out; 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( std::string AssemblyItem::computeSourceMapping(
AssemblyItems const& _items, AssemblyItems const& _items,
map<string, unsigned> const& _sourceIndicesMap map<string, unsigned> const& _sourceIndicesMap
@ -334,6 +369,7 @@ std::string AssemblyItem::computeSourceMapping(
int prevSourceIndex = -1; int prevSourceIndex = -1;
int prevModifierDepth = -1; int prevModifierDepth = -1;
char prevJump = 0; char prevJump = 0;
for (auto const& item: _items) for (auto const& item: _items)
{ {
if (!ret.empty()) if (!ret.empty())
@ -402,6 +438,9 @@ std::string AssemblyItem::computeSourceMapping(
} }
} }
if (item.opcodeCount() > 1)
ret += string(item.opcodeCount() - 1, ';');
prevStart = location.start; prevStart = location.start;
prevLength = length; prevLength = length;
prevSourceIndex = sourceIndex; prevSourceIndex = sourceIndex;

View File

@ -27,6 +27,7 @@
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libsolutil/Assertions.h> #include <libsolutil/Assertions.h>
#include <optional>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
@ -51,6 +52,8 @@ enum AssemblyItemType
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification. VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
}; };
enum class Precision { Precise , Approximate };
class Assembly; class Assembly;
class AssemblyItem; class AssemblyItem;
using AssemblyItems = std::vector<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 /// @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. /// 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 arguments() const;
size_t returnValues() const; size_t returnValues() const;
size_t deposit() const { return returnValues() - arguments(); } size_t deposit() const { return returnValues() - arguments(); }
@ -169,9 +175,11 @@ public:
size_t m_modifierDepth = 0; 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: private:
size_t opcodeCount() const noexcept;
AssemblyItemType m_type; AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation Instruction m_instruction; ///< Only valid if m_type == Operation
std::shared_ptr<u256> m_data; ///< 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. /// e.g. PushSubSize, PushTag, PushSub, etc.
mutable std::shared_ptr<u256> m_pushedValue; mutable std::shared_ptr<u256> m_pushedValue;
/// Number of PushImmutable's with the same hash. Only used for AssignImmutable. /// 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; size_t size = 0;
for (AssemblyItem const& item: _items) for (AssemblyItem const& item: _items)
size += item.bytesRequired(_addressLength); size += item.bytesRequired(_addressLength, _precision);
return size; return size;
} }

View File

@ -38,4 +38,4 @@ set(sources
) )
add_library(evmasm ${sources}) add_library(evmasm ${sources})
target_link_libraries(evmasm PUBLIC solutil) target_link_libraries(evmasm PUBLIC solutil fmt::fmt-header-only)

View File

@ -103,7 +103,7 @@ bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items) 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( void ConstantOptimisationMethod::replaceConstants(

View File

@ -63,7 +63,7 @@ template<typename RangeType>
uint64_t codeSize(RangeType const& _itemRange) uint64_t codeSize(RangeType const& _itemRange)
{ {
return ranges::accumulate(_itemRange | ranges::views::transform( return ranges::accumulate(_itemRange | ranges::views::transform(
[](auto const& _item) { return _item.bytesRequired(2); } [](auto const& _item) { return _item.bytesRequired(2, Precision::Approximate); }
), 0u); ), 0u);
} }
/// @returns the tag id, if @a _item is a PushTag or Tag into the current subassembly, nullopt otherwise. /// @returns the tag id, if @a _item is a PushTag or Tag into the current subassembly, nullopt otherwise.

View File

@ -388,6 +388,8 @@ size_t numberOfPops(AssemblyItems const& _items)
bool PeepholeOptimiser::optimise() 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)}; OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
while (state.i < m_items.size()) while (state.i < m_items.size())
applyMethods( applyMethods(
@ -398,7 +400,7 @@ bool PeepholeOptimiser::optimise()
); );
if (m_optimisedItems.size() < m_items.size() || ( if (m_optimisedItems.size() < m_items.size() || (
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) numberOfPops(m_optimisedItems) > numberOfPops(m_items)
) )
)) ))

View File

@ -3,6 +3,8 @@ set(sources
Common.h Common.h
CharStream.cpp CharStream.cpp
CharStream.h CharStream.h
DebugInfoSelection.cpp
DebugInfoSelection.h
ErrorReporter.cpp ErrorReporter.cpp
ErrorReporter.h ErrorReporter.h
EVMVersion.h EVMVersion.h
@ -29,4 +31,4 @@ set(sources
) )
add_library(langutil ${sources}) add_library(langutil ${sources})
target_link_libraries(langutil PUBLIC solutil) target_link_libraries(langutil PUBLIC solutil fmt::fmt-header-only)

View 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, ",");
}

View 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);
}

View File

@ -59,6 +59,13 @@ struct SourceLocation
return start <= _other.start && _other.end <= end; 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 bool intersects(SourceLocation const& _other) const
{ {
if (!hasText() || !_other.hasText() || !equalSources(_other)) if (!hasText() || !_other.hasText() || !equalSources(_other))

View File

@ -159,4 +159,4 @@ set(sources
) )
add_library(solidity ${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)

View File

@ -533,12 +533,12 @@ void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
yul::ASTWalker::operator()(_functionCall); yul::ASTWalker::operator()(_functionCall);
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name)) if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
if (builtinFunction->controlFlowSideEffects.terminates)
{ {
if (builtinFunction->controlFlowSideEffects.reverts) if (builtinFunction->controlFlowSideEffects.canTerminate)
connect(m_currentNode, m_revertNode);
else
connect(m_currentNode, m_transactionReturnNode); connect(m_currentNode, m_transactionReturnNode);
if (builtinFunction->controlFlowSideEffects.canRevert)
connect(m_currentNode, m_revertNode);
if (!builtinFunction->controlFlowSideEffects.canContinue)
m_currentNode = newLabel(); m_currentNode = newLabel();
} }
} }

View File

@ -25,7 +25,6 @@
#include <libsolutil/Algorithms.h> #include <libsolutil/Algorithms.h>
#include <libsolutil/FunctionSelector.h> #include <libsolutil/FunctionSelector.h>
#include <boost/range/adaptor/map.hpp>
#include <memory> #include <memory>
using namespace std; using namespace std;

View File

@ -246,7 +246,10 @@ public:
/// @returns the declared name. /// @returns the declared name.
ASTString const& name() const { return *m_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; } SourceLocation const& nameLocation() const noexcept { return m_nameLocation; }
bool noVisibilitySpecified() const { return m_visibility == Visibility::Default; } bool noVisibilitySpecified() const { return m_visibility == Visibility::Default; }
Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; } Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; }
bool isPublic() const { return visibility() >= Visibility::Public; } bool isPublic() const { return visibility() >= Visibility::Public; }

View File

@ -41,7 +41,6 @@
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp> #include <boost/algorithm/string/split.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <range/v3/view/enumerate.hpp> #include <range/v3/view/enumerate.hpp>
#include <range/v3/view/reverse.hpp> #include <range/v3/view/reverse.hpp>
@ -2563,6 +2562,11 @@ string UserDefinedValueType::toString(bool /* _short */) const
return *definition().annotation().canonicalName; return *definition().annotation().canonicalName;
} }
string UserDefinedValueType::canonicalName() const
{
return *definition().annotation().canonicalName;
}
vector<tuple<string, Type const*>> UserDefinedValueType::makeStackItems() const vector<tuple<string, Type const*>> UserDefinedValueType::makeStackItems() const
{ {
return underlyingType().stackItems(); return underlyingType().stackItems();

View File

@ -1141,7 +1141,7 @@ public:
} }
std::string toString(bool _short) const override; 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, ""); } std::string signatureInExternalFunction(bool) const override { solAssert(false, ""); }
protected: protected:

View File

@ -135,11 +135,15 @@ string dispenseLocationComment(langutil::SourceLocation const& _location, IRGene
{ {
solAssert(_location.sourceName, ""); solAssert(_location.sourceName, "");
_context.markSourceUsed(*_location.sourceName); _context.markSourceUsed(*_location.sourceName);
return "/// " + AsmPrinter::formatSourceLocation(
string debugInfo = AsmPrinter::formatSourceLocation(
_location, _location,
_context.sourceIndices(), _context.sourceIndices(),
_context.debugInfoSelection(),
_context.soliditySourceProvider() _context.soliditySourceProvider()
); );
return debugInfo.empty() ? "" : "/// " + debugInfo;
} }
string dispenseLocationComment(ASTNode const& _node, IRGenerationContext& _context) string dispenseLocationComment(ASTNode const& _node, IRGenerationContext& _context)

View File

@ -30,6 +30,7 @@
#include <libsolidity/codegen/ir/Common.h> #include <libsolidity/codegen/ir/Common.h>
#include <liblangutil/CharStreamProvider.h> #include <liblangutil/CharStreamProvider.h>
#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
@ -74,6 +75,7 @@ public:
RevertStrings _revertStrings, RevertStrings _revertStrings,
OptimiserSettings _optimiserSettings, OptimiserSettings _optimiserSettings,
std::map<std::string, unsigned> _sourceIndices, std::map<std::string, unsigned> _sourceIndices,
langutil::DebugInfoSelection const& _debugInfoSelection,
langutil::CharStreamProvider const* _soliditySourceProvider langutil::CharStreamProvider const* _soliditySourceProvider
): ):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
@ -81,6 +83,7 @@ public:
m_revertStrings(_revertStrings), m_revertStrings(_revertStrings),
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings)),
m_sourceIndices(std::move(_sourceIndices)), m_sourceIndices(std::move(_sourceIndices)),
m_debugInfoSelection(_debugInfoSelection),
m_soliditySourceProvider(_soliditySourceProvider) m_soliditySourceProvider(_soliditySourceProvider)
{} {}
@ -174,6 +177,7 @@ public:
bool immutableRegistered(VariableDeclaration const& _varDecl) const { return m_immutableVariables.count(&_varDecl); } 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; } langutil::CharStreamProvider const* soliditySourceProvider() const { return m_soliditySourceProvider; }
private: private:
@ -220,6 +224,7 @@ private:
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects; std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
langutil::DebugInfoSelection m_debugInfoSelection = {};
langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr; langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr;
}; };

View File

@ -95,7 +95,12 @@ pair<string, string> IRGenerator::run(
{ {
string const ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources)); 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)) if (!asmStack.parseAndAnalyze("", ir))
{ {
string errorMessage; string errorMessage;
@ -340,8 +345,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
return m_context.functionCollector().createFunction(functionName, [&]() { return m_context.functionCollector().createFunction(functionName, [&]() {
m_context.resetLocalVariables(); m_context.resetLocalVariables();
Whiskers t(R"( Whiskers t(R"(
/// @ast-id <astID> <astIDComment><sourceLocationComment>
<sourceLocationComment>
function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> { function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> {
<retInit> <retInit>
<body> <body>
@ -349,7 +353,10 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
<contractSourceLocationComment> <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("sourceLocationComment", dispenseLocationComment(_function));
t( t(
"contractSourceLocationComment", "contractSourceLocationComment",
@ -409,8 +416,7 @@ string IRGenerator::generateModifier(
return m_context.functionCollector().createFunction(functionName, [&]() { return m_context.functionCollector().createFunction(functionName, [&]() {
m_context.resetLocalVariables(); m_context.resetLocalVariables();
Whiskers t(R"( Whiskers t(R"(
/// @ast-id <astID> <astIDComment><sourceLocationComment>
<sourceLocationComment>
function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> { function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> {
<assignRetParams> <assignRetParams>
<evalArgs> <evalArgs>
@ -418,6 +424,7 @@ string IRGenerator::generateModifier(
} }
<contractSourceLocationComment> <contractSourceLocationComment>
)"); )");
t("functionName", functionName); t("functionName", functionName);
vector<string> retParamsIn; vector<string> retParamsIn;
for (auto const& varDecl: _function.returnParameters()) for (auto const& varDecl: _function.returnParameters())
@ -440,7 +447,11 @@ string IRGenerator::generateModifier(
_modifierInvocation.name().annotation().referencedDeclaration _modifierInvocation.name().annotation().referencedDeclaration
); );
solAssert(modifier, ""); 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("sourceLocationComment", dispenseLocationComment(*modifier));
t( t(
"contractSourceLocationComment", "contractSourceLocationComment",
@ -546,14 +557,18 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
solAssert(paramTypes.empty(), ""); solAssert(paramTypes.empty(), "");
solUnimplementedAssert(type->sizeOnStack() == 1); solUnimplementedAssert(type->sizeOnStack() == 1);
return Whiskers(R"( return Whiskers(R"(
/// @ast-id <astID> <astIDComment><sourceLocationComment>
<sourceLocationComment>
function <functionName>() -> rval { function <functionName>() -> rval {
rval := loadimmutable("<id>") rval := loadimmutable("<id>")
} }
<contractSourceLocationComment> <contractSourceLocationComment>
)") )")
("astID", to_string(_varDecl.id())) (
"astIDComment",
m_context.debugInfoSelection().astID ?
"/// @ast-id " + to_string(_varDecl.id()) + "\n" :
""
)
("sourceLocationComment", dispenseLocationComment(_varDecl)) ("sourceLocationComment", dispenseLocationComment(_varDecl))
( (
"contractSourceLocationComment", "contractSourceLocationComment",
@ -567,14 +582,18 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
{ {
solAssert(paramTypes.empty(), ""); solAssert(paramTypes.empty(), "");
return Whiskers(R"( return Whiskers(R"(
/// @ast-id <astID> <astIDComment><sourceLocationComment>
<sourceLocationComment>
function <functionName>() -> <ret> { function <functionName>() -> <ret> {
<ret> := <constantValueFunction>() <ret> := <constantValueFunction>()
} }
<contractSourceLocationComment> <contractSourceLocationComment>
)") )")
("astID", to_string(_varDecl.id())) (
"astIDComment",
m_context.debugInfoSelection().astID ?
"/// @ast-id " + to_string(_varDecl.id()) + "\n" :
""
)
("sourceLocationComment", dispenseLocationComment(_varDecl)) ("sourceLocationComment", dispenseLocationComment(_varDecl))
( (
"contractSourceLocationComment", "contractSourceLocationComment",
@ -691,8 +710,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
} }
return Whiskers(R"( return Whiskers(R"(
/// @ast-id <astID> <astIDComment><sourceLocationComment>
<sourceLocationComment>
function <functionName>(<params>) -> <retVariables> { function <functionName>(<params>) -> <retVariables> {
<code> <code>
} }
@ -702,7 +720,12 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
("params", joinHumanReadable(parameters)) ("params", joinHumanReadable(parameters))
("retVariables", joinHumanReadable(returnVariables)) ("retVariables", joinHumanReadable(returnVariables))
("code", std::move(code)) ("code", std::move(code))
("astID", to_string(_varDecl.id())) (
"astIDComment",
m_context.debugInfoSelection().astID ?
"/// @ast-id " + to_string(_varDecl.id()) + "\n" :
""
)
("sourceLocationComment", dispenseLocationComment(_varDecl)) ("sourceLocationComment", dispenseLocationComment(_varDecl))
( (
"contractSourceLocationComment", "contractSourceLocationComment",
@ -829,7 +852,7 @@ void IRGenerator::generateConstructors(ContractDefinition const& _contract)
for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters()) for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters())
params += m_context.addLocalVariable(*varDecl).stackSlots(); 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"); t("astIDComment", "/// @ast-id " + to_string(contract->constructor()->id()) + "\n");
else else
t("astIDComment", ""); t("astIDComment", "");
@ -944,7 +967,7 @@ string IRGenerator::callValueCheck()
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
{ {
Whiskers t(R"X( Whiskers t(R"X(
if iszero(lt(calldatasize(), 4)) <?+cases>if iszero(lt(calldatasize(), 4))
{ {
let selector := <shr224>(calldataload(0)) let selector := <shr224>(calldataload(0))
switch selector switch selector
@ -962,8 +985,8 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
} }
</cases> </cases>
default {} default {}
} }</+cases>
if iszero(calldatasize()) { <receiveEther> } <?+receiveEther>if iszero(calldatasize()) { <receiveEther> }</+receiveEther>
<fallback> <fallback>
)X"); )X");
t("shr224", m_utils.shiftRightFunction(224)); t("shr224", m_utils.shiftRightFunction(224));
@ -1085,6 +1108,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionCon
m_context.revertStrings(), m_context.revertStrings(),
m_optimiserSettings, m_optimiserSettings,
m_context.sourceIndices(), m_context.sourceIndices(),
m_context.debugInfoSelection(),
m_context.soliditySourceProvider() m_context.soliditySourceProvider()
); );
newContext.copyFunctionIDsFrom(m_context); newContext.copyFunctionIDsFrom(m_context);

View File

@ -49,6 +49,7 @@ public:
RevertStrings _revertStrings, RevertStrings _revertStrings,
OptimiserSettings _optimiserSettings, OptimiserSettings _optimiserSettings,
std::map<std::string, unsigned> _sourceIndices, std::map<std::string, unsigned> _sourceIndices,
langutil::DebugInfoSelection const& _debugInfoSelection,
langutil::CharStreamProvider const* _soliditySourceProvider langutil::CharStreamProvider const* _soliditySourceProvider
): ):
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
@ -59,6 +60,7 @@ public:
_revertStrings, _revertStrings,
std::move(_optimiserSettings), std::move(_optimiserSettings),
std::move(_sourceIndices), std::move(_sourceIndices),
_debugInfoSelection,
_soliditySourceProvider _soliditySourceProvider
), ),
m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector()) m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector())

View File

@ -991,7 +991,7 @@ void CHC::resetSourceAnalysis()
if (usesZ3) if (usesZ3)
{ {
/// z3::fixedpoint does not have a reset mechanism, so we need to create another. /// 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()); auto z3Interface = dynamic_cast<Z3CHCInterface const*>(m_interface.get());
solAssert(z3Interface, ""); solAssert(z3Interface, "");
m_context.setSolver(z3Interface->z3Interface()); m_context.setSolver(z3Interface->z3Interface());

View File

@ -201,7 +201,7 @@ void CompilerStack::findAndReportCyclicContractDependencies()
void CompilerStack::setRemappings(vector<ImportRemapper::Remapping> _remappings) void CompilerStack::setRemappings(vector<ImportRemapper::Remapping> _remappings)
{ {
if (m_stackState >= ParsedAndImported) 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) for (auto const& remapping: _remappings)
solAssert(!remapping.prefix.empty(), ""); solAssert(!remapping.prefix.empty(), "");
m_importRemapper.setRemappings(move(_remappings)); m_importRemapper.setRemappings(move(_remappings));
@ -210,28 +210,28 @@ void CompilerStack::setRemappings(vector<ImportRemapper::Remapping> _remappings)
void CompilerStack::setViaIR(bool _viaIR) void CompilerStack::setViaIR(bool _viaIR)
{ {
if (m_stackState >= ParsedAndImported) 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; m_viaIR = _viaIR;
} }
void CompilerStack::setEVMVersion(langutil::EVMVersion _version) void CompilerStack::setEVMVersion(langutil::EVMVersion _version)
{ {
if (m_stackState >= ParsedAndImported) 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; m_evmVersion = _version;
} }
void CompilerStack::setModelCheckerSettings(ModelCheckerSettings _settings) void CompilerStack::setModelCheckerSettings(ModelCheckerSettings _settings)
{ {
if (m_stackState >= ParsedAndImported) 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; m_modelCheckerSettings = _settings;
} }
void CompilerStack::setLibraries(std::map<std::string, util::h160> const& _libraries) void CompilerStack::setLibraries(std::map<std::string, util::h160> const& _libraries)
{ {
if (m_stackState >= ParsedAndImported) 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; m_libraries = _libraries;
} }
@ -245,14 +245,14 @@ void CompilerStack::setOptimiserSettings(bool _optimize, size_t _runs)
void CompilerStack::setOptimiserSettings(OptimiserSettings _settings) void CompilerStack::setOptimiserSettings(OptimiserSettings _settings)
{ {
if (m_stackState >= ParsedAndImported) 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); m_optimiserSettings = std::move(_settings);
} }
void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings) void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
{ {
if (m_stackState >= ParsedAndImported) 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); solUnimplementedAssert(_revertStrings != RevertStrings::VerboseDebug);
m_revertStrings = _revertStrings; m_revertStrings = _revertStrings;
} }
@ -260,21 +260,28 @@ void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources) void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources)
{ {
if (m_stackState >= ParsedAndImported) 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; m_metadataLiteralSources = _metadataLiteralSources;
} }
void CompilerStack::setMetadataHash(MetadataHash _metadataHash) void CompilerStack::setMetadataHash(MetadataHash _metadataHash)
{ {
if (m_stackState >= ParsedAndImported) 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; 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) void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _response)
{ {
if (m_stackState >= ParsedAndImported) 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; m_smtlib2Responses[_hash] = _response;
} }
@ -310,9 +317,9 @@ void CompilerStack::reset(bool _keepSettings)
void CompilerStack::setSources(StringMap _sources) void CompilerStack::setSources(StringMap _sources)
{ {
if (m_stackState == SourcesSet) 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) 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) for (auto source: _sources)
m_sources[source.first].charStream = make_unique<CharStream>(/*content*/std::move(source.second), /*name*/source.first); m_sources[source.first].charStream = make_unique<CharStream>(/*content*/std::move(source.second), /*name*/source.first);
m_stackState = SourcesSet; m_stackState = SourcesSet;
@ -321,7 +328,7 @@ void CompilerStack::setSources(StringMap _sources)
bool CompilerStack::parse() bool CompilerStack::parse()
{ {
if (m_stackState != SourcesSet) 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(); m_errorReporter.clear();
if (SemVerVersion{string(VersionString)}.isPrerelease()) if (SemVerVersion{string(VersionString)}.isPrerelease())
@ -369,7 +376,7 @@ bool CompilerStack::parse()
void CompilerStack::importASTs(map<string, Json::Value> const& _sources) void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
{ {
if (m_stackState != Empty) 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; m_sourceJsons = _sources;
map<string, ASTPointer<SourceUnit>> reconstructedSources = ASTJsonImporter(m_evmVersion).jsonToSourceUnit(m_sourceJsons); map<string, ASTPointer<SourceUnit>> reconstructedSources = ASTJsonImporter(m_evmVersion).jsonToSourceUnit(m_sourceJsons);
for (auto& src: reconstructedSources) for (auto& src: reconstructedSources)
@ -392,7 +399,7 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
bool CompilerStack::analyze() bool CompilerStack::analyze()
{ {
if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed) 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(); resolveImports();
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
@ -619,7 +626,7 @@ bool CompilerStack::compile(State _stopAfter)
return true; return true;
if (m_hasError) 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. // Only compile contracts individually which have been requested.
map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers; map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;
@ -691,7 +698,7 @@ void CompilerStack::link()
vector<string> CompilerStack::contractNames() const vector<string> CompilerStack::contractNames() const
{ {
if (m_stackState < Parsed) if (m_stackState < Parsed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); solThrow(CompilerError, "Parsing was not successful.");
vector<string> contractNames; vector<string> contractNames;
for (auto const& contract: m_contracts) for (auto const& contract: m_contracts)
contractNames.push_back(contract.first); contractNames.push_back(contract.first);
@ -701,7 +708,7 @@ vector<string> CompilerStack::contractNames() const
string const CompilerStack::lastContractName(optional<string> const& _sourceName) const string const CompilerStack::lastContractName(optional<string> const& _sourceName) const
{ {
if (m_stackState < AnalysisPerformed) 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 // try to find some user-supplied contract
string contractName; string contractName;
for (auto const& it: m_sources) 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 evmasm::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) 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); Contract const& currentContract = contract(_contractName);
return currentContract.evmAssembly ? &currentContract.evmAssembly->items() : nullptr; return currentContract.evmAssembly ? &currentContract.evmAssembly->items() : nullptr;
@ -723,7 +730,7 @@ evmasm::AssemblyItems const* CompilerStack::assemblyItems(string const& _contrac
evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _contractName) const evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) 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); Contract const& currentContract = contract(_contractName);
return currentContract.evmRuntimeAssembly ? &currentContract.evmRuntimeAssembly->items() : nullptr; return currentContract.evmRuntimeAssembly ? &currentContract.evmRuntimeAssembly->items() : nullptr;
@ -732,7 +739,7 @@ evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _
Json::Value CompilerStack::generatedSources(string const& _contractName, bool _runtime) const Json::Value CompilerStack::generatedSources(string const& _contractName, bool _runtime) const
{ {
if (m_stackState != CompilationSuccessful) 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); Contract const& c = contract(_contractName);
util::LazyInit<Json::Value const> const& sources = 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 string const* CompilerStack::sourceMapping(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) 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); Contract const& c = contract(_contractName);
if (!c.sourceMapping) if (!c.sourceMapping)
@ -789,7 +796,7 @@ string const* CompilerStack::sourceMapping(string const& _contractName) const
string const* CompilerStack::runtimeSourceMapping(string const& _contractName) const string const* CompilerStack::runtimeSourceMapping(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) 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); Contract const& c = contract(_contractName);
if (!c.runtimeSourceMapping) if (!c.runtimeSourceMapping)
@ -805,7 +812,7 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const
{ {
if (m_stackState < AnalysisPerformed) 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) // Look up the contract (by its fully-qualified name)
Contract const& matchContract = m_contracts.at(_contractName); 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 string const& CompilerStack::yulIR(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); solThrow(CompilerError, "Compilation was not successful.");
return contract(_contractName).yulIR; return contract(_contractName).yulIR;
} }
@ -837,7 +844,7 @@ string const& CompilerStack::yulIR(string const& _contractName) const
string const& CompilerStack::yulIROptimized(string const& _contractName) const string const& CompilerStack::yulIROptimized(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); solThrow(CompilerError, "Compilation was not successful.");
return contract(_contractName).yulIROptimized; return contract(_contractName).yulIROptimized;
} }
@ -845,7 +852,7 @@ string const& CompilerStack::yulIROptimized(string const& _contractName) const
string const& CompilerStack::ewasm(string const& _contractName) const string const& CompilerStack::ewasm(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); solThrow(CompilerError, "Compilation was not successful.");
return contract(_contractName).ewasm; 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 evmasm::LinkerObject const& CompilerStack::ewasmObject(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); solThrow(CompilerError, "Compilation was not successful.");
return contract(_contractName).ewasmObject; 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 evmasm::LinkerObject const& CompilerStack::object(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); solThrow(CompilerError, "Compilation was not successful.");
return contract(_contractName).object; 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 evmasm::LinkerObject const& CompilerStack::runtimeObject(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); solThrow(CompilerError, "Compilation was not successful.");
return contract(_contractName).runtimeObject; 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 string CompilerStack::assemblyString(string const& _contractName, StringMap const& _sourceCodes) const
{ {
if (m_stackState != CompilationSuccessful) 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); Contract const& currentContract = contract(_contractName);
if (currentContract.evmAssembly) if (currentContract.evmAssembly)
return currentContract.evmAssembly->assemblyString(_sourceCodes); return currentContract.evmAssembly->assemblyString(m_debugInfoSelection, _sourceCodes);
else else
return string(); return string();
} }
@ -891,7 +898,7 @@ string CompilerStack::assemblyString(string const& _contractName, StringMap cons
Json::Value CompilerStack::assemblyJSON(string const& _contractName) const Json::Value CompilerStack::assemblyJSON(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) 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); Contract const& currentContract = contract(_contractName);
if (currentContract.evmAssembly) if (currentContract.evmAssembly)
@ -922,7 +929,7 @@ map<string, unsigned> CompilerStack::sourceIndices() const
Json::Value const& CompilerStack::contractABI(string const& _contractName) const Json::Value const& CompilerStack::contractABI(string const& _contractName) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
return contractABI(contract(_contractName)); 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 Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
solAssert(_contract.contract, ""); 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 Json::Value const& CompilerStack::storageLayout(string const& _contractName) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
return storageLayout(contract(_contractName)); 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 Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
solAssert(_contract.contract, ""); 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 Json::Value const& CompilerStack::natspecUser(string const& _contractName) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
return natspecUser(contract(_contractName)); 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 Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
solAssert(_contract.contract, ""); 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 Json::Value const& CompilerStack::natspecDev(string const& _contractName) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
return natspecDev(contract(_contractName)); 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 Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
solAssert(_contract.contract, ""); solAssert(_contract.contract, "");
@ -994,7 +1001,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
{ {
if (m_stackState < AnalysisPerformed) 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); Json::Value methodIdentifiers(Json::objectValue);
for (auto const& it: contractDefinition(_contractName).interfaceFunctions()) 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 bytes CompilerStack::cborMetadata(string const& _contractName, bool _forIR) const
{ {
if (m_stackState < AnalysisPerformed) 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); 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 string const& CompilerStack::metadata(Contract const& _contract) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
solAssert(_contract.contract, ""); solAssert(_contract.contract, "");
@ -1023,7 +1030,7 @@ string const& CompilerStack::metadata(Contract const& _contract) const
CharStream const& CompilerStack::charStream(string const& _sourceName) const CharStream const& CompilerStack::charStream(string const& _sourceName) const
{ {
if (m_stackState < SourcesSet) if (m_stackState < SourcesSet)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No sources set.")); solThrow(CompilerError, "No sources set.");
solAssert(source(_sourceName).charStream, ""); solAssert(source(_sourceName).charStream, "");
@ -1033,9 +1040,9 @@ CharStream const& CompilerStack::charStream(string const& _sourceName) const
SourceUnit const& CompilerStack::ast(string const& _sourceName) const SourceUnit const& CompilerStack::ast(string const& _sourceName) const
{ {
if (m_stackState < Parsed) 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) 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; return *source(_sourceName).ast;
} }
@ -1043,7 +1050,7 @@ SourceUnit const& CompilerStack::ast(string const& _sourceName) const
ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const
{ {
if (m_stackState < AnalysisPerformed) if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solThrow(CompilerError, "Analysis was not successful.");
return *contract(_contractName).contract; return *contract(_contractName).contract;
} }
@ -1054,7 +1061,7 @@ size_t CompilerStack::functionEntryPoint(
) const ) const
{ {
if (m_stackState != CompilationSuccessful) 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) for (auto&& [name, data]: contract(_contractName).runtimeObject.functionDebugData)
if (data.sourceID == _function.id()) if (data.sourceID == _function.id())
@ -1256,7 +1263,7 @@ void CompilerStack::compileContract(
solAssert(!m_viaIR, ""); solAssert(!m_viaIR, "");
solAssert(m_stackState >= AnalysisPerformed, ""); solAssert(m_stackState >= AnalysisPerformed, "");
if (m_hasError) if (m_hasError)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors.")); solThrow(CompilerError, "Called compile with errors.");
if (_otherCompilers.count(&_contract)) if (_otherCompilers.count(&_contract))
return; return;
@ -1294,7 +1301,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
{ {
solAssert(m_stackState >= AnalysisPerformed, ""); solAssert(m_stackState >= AnalysisPerformed, "");
if (m_hasError) 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()); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
if (!compiledContract.yulIR.empty()) if (!compiledContract.yulIR.empty())
@ -1319,7 +1326,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
for (auto const& pair: m_contracts) for (auto const& pair: m_contracts)
otherYulSources.emplace(pair.second.contract, pair.second.yulIR); 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( tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(
_contract, _contract,
createCBORMetadata(compiledContract, /* _forIR */ true), createCBORMetadata(compiledContract, /* _forIR */ true),
@ -1331,7 +1338,7 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
{ {
solAssert(m_stackState >= AnalysisPerformed, ""); solAssert(m_stackState >= AnalysisPerformed, "");
if (m_hasError) if (m_hasError)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEVMFromIR with errors.")); solThrow(CompilerError, "Called generateEVMFromIR with errors.");
if (!_contract.canBeDeployed()) if (!_contract.canBeDeployed())
return; return;
@ -1342,7 +1349,12 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
return; return;
// Re-parse the Yul IR in EVM dialect // 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.parseAndAnalyze("", compiledContract.yulIROptimized);
stack.optimize(); stack.optimize();
@ -1358,7 +1370,7 @@ void CompilerStack::generateEwasm(ContractDefinition const& _contract)
{ {
solAssert(m_stackState >= AnalysisPerformed, ""); solAssert(m_stackState >= AnalysisPerformed, "");
if (m_hasError) if (m_hasError)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEwasm with errors.")); solThrow(CompilerError, "Called generateEwasm with errors.");
if (!_contract.canBeDeployed()) if (!_contract.canBeDeployed())
return; return;
@ -1369,7 +1381,12 @@ void CompilerStack::generateEwasm(ContractDefinition const& _contract)
return; return;
// Re-parse the Yul IR in EVM dialect // 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.parseAndAnalyze("", compiledContract.yulIROptimized);
stack.optimize(); stack.optimize();
@ -1412,14 +1429,14 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa
} }
// If we get here, both lookup methods failed. // 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 CompilerStack::Source const& CompilerStack::source(string const& _sourceName) const
{ {
auto it = m_sources.find(_sourceName); auto it = m_sources.find(_sourceName);
if (it == m_sources.end()) 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; return it->second;
} }
@ -1658,7 +1675,7 @@ Json::Value gasToJson(GasEstimator::GasConsumption const& _gas)
Json::Value CompilerStack::gasEstimates(string const& _contractName) const Json::Value CompilerStack::gasEstimates(string const& _contractName) const
{ {
if (m_stackState != CompilationSuccessful) 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)) if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName))
return Json::Value(); return Json::Value();

View File

@ -35,10 +35,11 @@
#include <libsmtutil/SolverInterface.h> #include <libsmtutil/SolverInterface.h>
#include <liblangutil/CharStreamProvider.h>
#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
#include <liblangutil/CharStreamProvider.h>
#include <libevmasm/LinkerObject.h> #include <libevmasm/LinkerObject.h>
@ -203,6 +204,9 @@ public:
/// @param _metadataHash can be IPFS, Bzzr1, None /// @param _metadataHash can be IPFS, Bzzr1, None
void setMetadataHash(MetadataHash _metadataHash); 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. /// Sets the sources. Must be set before parsing.
void setSources(StringMap _sources); void setSources(StringMap _sources);
@ -505,6 +509,7 @@ private:
langutil::ErrorReporter m_errorReporter; langutil::ErrorReporter m_errorReporter;
bool m_metadataLiteralSources = false; bool m_metadataLiteralSources = false;
MetadataHash m_metadataHash = MetadataHash::IPFS; MetadataHash m_metadataHash = MetadataHash::IPFS;
langutil::DebugInfoSelection m_debugInfoSelection = langutil::DebugInfoSelection::Default();
bool m_parserErrorRecovery = false; bool m_parserErrorRecovery = false;
State m_stackState = Empty; State m_stackState = Empty;
bool m_importedSources = false; bool m_importedSources = false;

View File

@ -104,10 +104,7 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
try try
{ {
if (_kind != ReadCallback::kindString(ReadCallback::Kind::ReadFile)) if (_kind != ReadCallback::kindString(ReadCallback::Kind::ReadFile))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment( solAssert(false, "ReadFile callback used as callback kind " + _kind);
"ReadFile callback used as callback kind " +
_kind
));
string strippedSourceUnitName = _sourceUnitName; string strippedSourceUnitName = _sourceUnitName;
if (strippedSourceUnitName.find("file://") == 0) if (strippedSourceUnitName.find("file://") == 0)
strippedSourceUnitName.erase(0, 7); strippedSourceUnitName.erase(0, 7);
@ -171,7 +168,7 @@ ReadCallback::Result FileReader::readFile(string const& _kind, string const& _so
} }
catch (...) catch (...)
{ {
return ReadCallback::Result{false, "Unknown exception in read callback."}; return ReadCallback::Result{false, "Unknown exception in read callback: " + boost::current_exception_diagnostic_information()};
} }
} }

View File

@ -44,20 +44,20 @@ struct OptimiserSettings
static char constexpr DefaultYulOptimiserSteps[] = static char constexpr DefaultYulOptimiserSteps[] =
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse "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 "cCTUtTOntnfDIul" // Perform structural simplification
"Lcul" // Simplify again "Lcul" // Simplify again
"Vcul jj" // Reverse SSA "Vcul [j]" // Reverse SSA
// should have good "compilability" property here. // should have good "compilability" property here.
"Tpeul" // Run functional expression inliner "Tpeul" // Run functional expression inliner
"xarulrul" // Prune a bit more in SSA "xa[rul]" // Prune a bit more in SSA
"xarrcL" // Turn into SSA again and simplify "xa[r]cL" // Turn into SSA again and simplify
"gvif" // Run full inliner "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. /// No optimisations at all - not recommended.
static OptimiserSettings none() static OptimiserSettings none()

View File

@ -28,9 +28,13 @@
#include <libyul/AssemblyStack.h> #include <libyul/AssemblyStack.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libyul/optimiser/Suite.h> #include <libyul/optimiser/Suite.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <libevmasm/Instruction.h> #include <libevmasm/Instruction.h>
#include <libsmtutil/Exceptions.h> #include <libsmtutil/Exceptions.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <libsolutil/JSON.h> #include <libsolutil/JSON.h>
#include <libsolutil/Keccak256.h> #include <libsolutil/Keccak256.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
@ -793,7 +797,7 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
if (settings.isMember("debug")) 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; return *result;
if (settings["debug"].isMember("revertStrings")) if (settings["debug"].isMember("revertStrings"))
@ -810,6 +814,31 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
); );
ret.revertStrings = *revertStrings; 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()) if (settings.isMember("remappings") && !settings["remappings"].isArray())
@ -1029,6 +1058,8 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
compilerStack.setRemappings(move(_inputsAndSettings.remappings)); compilerStack.setRemappings(move(_inputsAndSettings.remappings));
compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings));
compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings); compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings);
if (_inputsAndSettings.debugInfoSelection.has_value())
compilerStack.selectDebugInfo(_inputsAndSettings.debugInfoSelection.value());
compilerStack.setLibraries(_inputsAndSettings.libraries); compilerStack.setLibraries(_inputsAndSettings.libraries);
compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources);
compilerStack.setMetadataHash(_inputsAndSettings.metadataHash); compilerStack.setMetadataHash(_inputsAndSettings.metadataHash);
@ -1151,13 +1182,13 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
"Exception during compilation: " + boost::diagnostic_information(_exception) "Exception during compilation: " + boost::diagnostic_information(_exception)
)); ));
} }
catch (std::exception const& _e) catch (std::exception const& _exception)
{ {
errors.append(formatError( errors.append(formatError(
Error::Severity::Error, Error::Severity::Error,
"Exception", "Exception",
"general", "general",
"Unknown exception during compilation" + (_e.what() ? ": " + string(_e.what()) : ".") "Unknown exception during compilation: " + boost::diagnostic_information(_exception)
)); ));
} }
catch (...) catch (...)
@ -1166,7 +1197,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
Error::Severity::Error, Error::Severity::Error,
"Exception", "Exception",
"general", "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( AssemblyStack stack(
_inputsAndSettings.evmVersion, _inputsAndSettings.evmVersion,
AssemblyStack::Language::StrictAssembly, 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& sourceName = _inputsAndSettings.sources.begin()->first;
string const& sourceContents = _inputsAndSettings.sources.begin()->second; string const& sourceContents = _inputsAndSettings.sources.begin()->second;
@ -1481,7 +1515,7 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
} }
catch (...) catch (...)
{ {
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile"); return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::current_exception_diagnostic_information());
} }
} }

View File

@ -26,6 +26,8 @@
#include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/CompilerStack.h>
#include <libsolutil/JSON.h> #include <libsolutil/JSON.h>
#include <liblangutil/DebugInfoSelection.h>
#include <optional> #include <optional>
#include <utility> #include <utility>
#include <variant> #include <variant>
@ -78,6 +80,7 @@ private:
std::vector<ImportRemapper::Remapping> remappings; std::vector<ImportRemapper::Remapping> remappings;
RevertStrings revertStrings = RevertStrings::Default; RevertStrings revertStrings = RevertStrings::Default;
OptimiserSettings optimiserSettings = OptimiserSettings::minimal(); OptimiserSettings optimiserSettings = OptimiserSettings::minimal();
std::optional<langutil::DebugInfoSelection> debugInfoSelection;
std::map<std::string, util::h160> libraries; std::map<std::string, util::h160> libraries;
bool metadataLiteralSources = false; bool metadataLiteralSources = false;
CompilerStack::MetadataHash metadataHash = CompilerStack::MetadataHash::IPFS; CompilerStack::MetadataHash metadataHash = CompilerStack::MetadataHash::IPFS;

View File

@ -61,14 +61,9 @@ inline std::string stringOrDefault(std::string _string, std::string _defaultStri
do \ do \
{ \ { \
if (!(_condition)) \ if (!(_condition)) \
::boost::throw_exception( \ solThrow( \
_exceptionType() << \ _exceptionType, \
::solidity::util::errinfo_comment( \
::solidity::util::assertions::stringOrDefault(_description, _defaultDescription) \ ::solidity::util::assertions::stringOrDefault(_description, _defaultDescription) \
) << \
::boost::throw_function(ETH_FUNC) << \
::boost::throw_file(__FILE__) << \
::boost::throw_line(__LINE__) \
); \ ); \
} \ } \
while (false) while (false)

View File

@ -43,6 +43,19 @@ struct Exception: virtual std::exception, virtual boost::exception
private: 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; } } #define DEV_SIMPLE_EXCEPTION(X) struct X: virtual ::solidity::util::Exception { const char* what() const noexcept override { return #X; } }
DEV_SIMPLE_EXCEPTION(InvalidAddress); DEV_SIMPLE_EXCEPTION(InvalidAddress);

View File

@ -189,11 +189,13 @@ string Whiskers::replace(
if (conditionName[0] == '+') if (conditionName[0] == '+')
{ {
string tag = conditionName.substr(1); string tag = conditionName.substr(1);
assertThrow(
_parameters.count(tag), if (_parameters.count(tag))
WhiskersError, "Tag " + tag + " used as condition but was not set."
);
conditionValue = !_parameters.at(tag).empty(); 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 else
{ {

View File

@ -60,12 +60,13 @@ DEV_SIMPLE_EXCEPTION(WhiskersError);
* - Condition parameter: <?name>...<!name>...</name>, where "<!name>" is optional * - Condition parameter: <?name>...<!name>...</name>, where "<!name>" is optional
* replaced (and recursively expanded) by the first part if the condition is true * 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 * 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> * - List parameter: <#list>...</list>
* The part between the tags is repeated as often as values are provided * 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. * 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 class Whiskers
{ {

View File

@ -37,6 +37,8 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <fmt/format.h>
#include <memory> #include <memory>
#include <functional> #include <functional>
@ -618,7 +620,7 @@ void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _locati
m_errorReporter.typeError( m_errorReporter.typeError(
5473_error, 5473_error,
_location, _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( m_errorReporter.typeError(
3781_error, 3781_error,
_location, _location,
"Expected a value of type \"" + fmt::format("Expected a value of type \"{}\" but got \"{}\".", _expectedType, _givenType)
_expectedType.str() +
"\" but got \"" +
_givenType.str() +
"\""
); );
} }
@ -664,14 +662,12 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
m_errorReporter.typeError( m_errorReporter.typeError(
_errorId, _errorId,
_location, _location,
"The \"" + fmt::format(
boost::to_lower_copy(instructionInfo(_instr).name) "The \"{instruction}\" instruction is {kind} VMs (you are currently compiling for \"{version}\").",
+ "\" instruction is " + fmt::arg("instruction", boost::to_lower_copy(instructionInfo(_instr).name)),
vmKindMessage + fmt::arg("kind", vmKindMessage),
" VMs " + fmt::arg("version", m_evmVersion.name())
"(you are currently compiling for \"" + )
m_evmVersion.name() +
"\")."
); );
}; };

View File

@ -261,10 +261,16 @@ string AsmPrinter::appendTypeName(YulString _type, bool _isBoolLiteral) const
string AsmPrinter::formatSourceLocation( string AsmPrinter::formatSourceLocation(
SourceLocation const& _location, SourceLocation const& _location,
map<string, unsigned> const& _nameToSourceIndex, map<string, unsigned> const& _nameToSourceIndex,
DebugInfoSelection const& _debugInfoSelection,
CharStreamProvider const* _soliditySourceProvider CharStreamProvider const* _soliditySourceProvider
) )
{ {
yulAssert(!_nameToSourceIndex.empty(), ""); 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 sourceIndex = "-1";
string solidityCodeSnippet = ""; string solidityCodeSnippet = "";
@ -272,7 +278,7 @@ string AsmPrinter::formatSourceLocation(
{ {
sourceIndex = to_string(_nameToSourceIndex.at(*_location.sourceName)); sourceIndex = to_string(_nameToSourceIndex.at(*_location.sourceName));
if (_soliditySourceProvider) if (_debugInfoSelection.snippet && _soliditySourceProvider)
{ {
solidityCodeSnippet = escapeAndQuoteString( solidityCodeSnippet = escapeAndQuoteString(
_soliditySourceProvider->charStream(*_location.sourceName).singleLineSnippet(_location) _soliditySourceProvider->charStream(*_location.sourceName).singleLineSnippet(_location)
@ -298,11 +304,12 @@ string AsmPrinter::formatSourceLocation(
string AsmPrinter::formatDebugData(shared_ptr<DebugData const> const& _debugData, bool _statement) string AsmPrinter::formatDebugData(shared_ptr<DebugData const> const& _debugData, bool _statement)
{ {
if (!_debugData) if (!_debugData || m_debugInfoSelection.none())
return ""; return "";
vector<string> items; vector<string> items;
if (auto id = _debugData->astID) if (auto id = _debugData->astID)
if (m_debugInfoSelection.astID)
items.emplace_back("@ast-id " + to_string(*id)); items.emplace_back("@ast-id " + to_string(*id));
if ( if (
@ -315,6 +322,7 @@ string AsmPrinter::formatDebugData(shared_ptr<DebugData const> const& _debugData
items.emplace_back(formatSourceLocation( items.emplace_back(formatSourceLocation(
_debugData->originLocation, _debugData->originLocation,
m_nameToSourceIndex, m_nameToSourceIndex,
m_debugInfoSelection,
m_soliditySourceProvider m_soliditySourceProvider
)); ));
} }

View File

@ -29,6 +29,7 @@
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <liblangutil/CharStreamProvider.h> #include <liblangutil/CharStreamProvider.h>
#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
#include <map> #include <map>
@ -48,9 +49,11 @@ public:
explicit AsmPrinter( explicit AsmPrinter(
Dialect const* _dialect = nullptr, Dialect const* _dialect = nullptr,
std::optional<std::map<unsigned, std::shared_ptr<std::string const>>> _sourceIndexToName = {}, std::optional<std::map<unsigned, std::shared_ptr<std::string const>>> _sourceIndexToName = {},
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
): ):
m_dialect(_dialect), m_dialect(_dialect),
m_debugInfoSelection(_debugInfoSelection),
m_soliditySourceProvider(_soliditySourceProvider) m_soliditySourceProvider(_soliditySourceProvider)
{ {
if (_sourceIndexToName) if (_sourceIndexToName)
@ -58,12 +61,12 @@ public:
m_nameToSourceIndex[*name] = index; m_nameToSourceIndex[*name] = index;
} }
explicit AsmPrinter( explicit AsmPrinter(
Dialect const& _dialect, Dialect const& _dialect,
std::optional<std::map<unsigned, std::shared_ptr<std::string const>>> _sourceIndexToName = {}, std::optional<std::map<unsigned, std::shared_ptr<std::string const>>> _sourceIndexToName = {},
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
): AsmPrinter(&_dialect, _sourceIndexToName, _soliditySourceProvider) {} ): AsmPrinter(&_dialect, _sourceIndexToName, _debugInfoSelection, _soliditySourceProvider) {}
std::string operator()(Literal const& _literal); std::string operator()(Literal const& _literal);
std::string operator()(Identifier const& _identifier); std::string operator()(Identifier const& _identifier);
@ -83,6 +86,7 @@ public:
static std::string formatSourceLocation( static std::string formatSourceLocation(
langutil::SourceLocation const& _location, langutil::SourceLocation const& _location,
std::map<std::string, unsigned> const& _nameToSourceIndex, std::map<std::string, unsigned> const& _nameToSourceIndex,
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr
); );
@ -100,6 +104,7 @@ private:
Dialect const* const m_dialect = nullptr; Dialect const* const m_dialect = nullptr;
std::map<std::string, unsigned> m_nameToSourceIndex; std::map<std::string, unsigned> m_nameToSourceIndex;
langutil::SourceLocation m_lastLocation = {}; langutil::SourceLocation m_lastLocation = {};
langutil::DebugInfoSelection m_debugInfoSelection = {};
langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr; langutil::CharStreamProvider const* m_soliditySourceProvider = nullptr;
}; };

View File

@ -249,7 +249,7 @@ AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
MachineAssemblyObject creationObject; MachineAssemblyObject creationObject;
creationObject.bytecode = make_shared<evmasm::LinkerObject>(creationAssembly->assemble()); creationObject.bytecode = make_shared<evmasm::LinkerObject>(creationAssembly->assemble());
yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables."); yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
creationObject.assembly = creationAssembly->assemblyString(); creationObject.assembly = creationAssembly->assemblyString(m_debugInfoSelection);
creationObject.sourceMappings = make_unique<string>( creationObject.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping( evmasm::AssemblyItem::computeSourceMapping(
creationAssembly->items(), creationAssembly->items(),
@ -261,7 +261,7 @@ AssemblyStack::assembleWithDeployed(optional<string_view> _deployName) const
if (deployedAssembly) if (deployedAssembly)
{ {
deployedObject.bytecode = make_shared<evmasm::LinkerObject>(deployedAssembly->assemble()); deployedObject.bytecode = make_shared<evmasm::LinkerObject>(deployedAssembly->assemble());
deployedObject.assembly = deployedAssembly->assemblyString(); deployedObject.assembly = deployedAssembly->assemblyString(m_debugInfoSelection);
deployedObject.sourceMappings = make_unique<string>( deployedObject.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping( evmasm::AssemblyItem::computeSourceMapping(
deployedAssembly->items(), deployedAssembly->items(),
@ -314,11 +314,13 @@ AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
return {make_shared<evmasm::Assembly>(assembly), {}}; 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, "");
yulAssert(m_parserResult->code, ""); 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 shared_ptr<Object> AssemblyStack::parserResult() const

View File

@ -22,9 +22,10 @@
#pragma once #pragma once
#include <liblangutil/CharStreamProvider.h>
#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <liblangutil/CharStreamProvider.h>
#include <libyul/Object.h> #include <libyul/Object.h>
#include <libyul/ObjectParser.h> #include <libyul/ObjectParser.h>
@ -69,12 +70,24 @@ public:
enum class Machine { EVM, Ewasm }; enum class Machine { EVM, Ewasm };
AssemblyStack(): 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_language(_language),
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
m_optimiserSettings(std::move(_optimiserSettings)), m_optimiserSettings(std::move(_optimiserSettings)),
m_debugInfoSelection(_debugInfoSelection),
m_errorReporter(m_errors) m_errorReporter(m_errors)
{} {}
@ -116,7 +129,9 @@ public:
langutil::ErrorList const& errors() const { return m_errors; } langutil::ErrorList const& errors() const { return m_errors; }
/// Pretty-print the input after having parsed it. /// 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. /// Return the parsed and analyzed object.
std::shared_ptr<Object> parserResult() const; std::shared_ptr<Object> parserResult() const;
@ -132,6 +147,7 @@ private:
Language m_language = Language::Assembly; Language m_language = Language::Assembly;
langutil::EVMVersion m_evmVersion; langutil::EVMVersion m_evmVersion;
solidity::frontend::OptimiserSettings m_optimiserSettings; solidity::frontend::OptimiserSettings m_optimiserSettings;
langutil::DebugInfoSelection m_debugInfoSelection{};
std::unique_ptr<langutil::CharStream> m_charStream; std::unique_ptr<langutil::CharStream> m_charStream;

View File

@ -34,6 +34,9 @@ add_library(yul
AssemblyStack.cpp AssemblyStack.cpp
CompilabilityChecker.cpp CompilabilityChecker.cpp
CompilabilityChecker.h CompilabilityChecker.h
ControlFlowSideEffects.h
ControlFlowSideEffectsCollector.cpp
ControlFlowSideEffectsCollector.h
Dialect.cpp Dialect.cpp
Dialect.h Dialect.h
Exceptions.h Exceptions.h
@ -216,4 +219,4 @@ add_library(yul
optimiser/VarNameCleaner.h 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)

View File

@ -18,22 +18,32 @@
#pragma once #pragma once
#include <set>
namespace solidity::yul 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 struct ControlFlowSideEffects
{ {
/// If true, this code terminates the control flow. /// If true, the function contains at least one reachable branch that terminates successfully.
/// State may or may not be reverted as indicated by the ``reverts`` flag. bool canTerminate = false;
bool terminates = false; /// If true, the function contains at least one reachable branch that reverts.
/// If true, this code reverts all state changes in the transaction. bool canRevert = false;
/// Whenever this is true, ``terminates`` has to be true as well. /// If true, the function has a regular outgoing control-flow.
bool reverts = false; bool canContinue = true;
bool terminatesOrReverts() const
{
return (canTerminate || canRevert) && !canContinue;
}
}; };
} }

View 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);
}

View 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;
};
}

View File

@ -22,8 +22,8 @@
#pragma once #pragma once
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <libyul/SideEffects.h>
#include <libyul/ControlFlowSideEffects.h> #include <libyul/ControlFlowSideEffects.h>
#include <libyul/SideEffects.h>
#include <vector> #include <vector>
#include <set> #include <set>

View File

@ -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) + "\""; return "data \"" + name.str() + "\" hex\"" + util::toHex(data) + "\"";
} }
string Object::toString( string Object::toString(
Dialect const* _dialect, Dialect const* _dialect,
DebugInfoSelection const& _debugInfoSelection,
CharStreamProvider const* _soliditySourceProvider CharStreamProvider const* _soliditySourceProvider
) const ) const
{ {
@ -74,10 +75,15 @@ string Object::toString(
})) + })) +
"\n"; "\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) 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}"; return useSrcComment + "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}";
} }

View File

@ -25,6 +25,7 @@
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <liblangutil/CharStreamProvider.h> #include <liblangutil/CharStreamProvider.h>
#include <liblangutil/DebugInfoSelection.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
@ -54,6 +55,7 @@ struct ObjectNode
YulString name; YulString name;
virtual std::string toString( virtual std::string toString(
Dialect const* _dialect, Dialect const* _dialect,
langutil::DebugInfoSelection const& _debugInfoSelection,
langutil::CharStreamProvider const* _soliditySourceProvider langutil::CharStreamProvider const* _soliditySourceProvider
) const = 0; ) const = 0;
}; };
@ -69,6 +71,7 @@ struct Data: public ObjectNode
std::string toString( std::string toString(
Dialect const* _dialect, Dialect const* _dialect,
langutil::DebugInfoSelection const& _debugInfoSelection,
langutil::CharStreamProvider const* _soliditySourceProvider langutil::CharStreamProvider const* _soliditySourceProvider
) const override; ) const override;
}; };
@ -89,6 +92,7 @@ public:
/// @returns a (parseable) string representation. /// @returns a (parseable) string representation.
std::string toString( std::string toString(
Dialect const* _dialect, Dialect const* _dialect,
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
langutil::CharStreamProvider const* _soliditySourceProvider = nullptr langutil::CharStreamProvider const* _soliditySourceProvider = nullptr
) const; ) const;

View File

@ -21,6 +21,8 @@
#pragma once #pragma once
#include <fmt/format.h>
#include <unordered_map> #include <unordered_map>
#include <memory> #include <memory>
#include <vector> #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 namespace std
{ {
template<> struct hash<solidity::yul::YulString> template<> struct hash<solidity::yul::YulString>

View File

@ -23,7 +23,6 @@
#include <libyul/AST.h> #include <libyul/AST.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libyul/Utilities.h> #include <libyul/Utilities.h>
#include <libyul/AsmPrinter.h>
#include <libsolutil/cxx20.h> #include <libsolutil/cxx20.h>
#include <libsolutil/Visitor.h> #include <libsolutil/Visitor.h>
@ -230,7 +229,7 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
// not only for builtins. // not only for builtins.
if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression)) if (auto const* funCall = get_if<FunctionCall>(&_exprStmt.expression))
if (BuiltinFunction const* builtin = m_dialect.builtin(funCall->functionName.name)) 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->exit = CFG::BasicBlock::Terminated{};
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));

View File

@ -57,8 +57,20 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.parameters.resize(static_cast<size_t>(info.args)); f.parameters.resize(static_cast<size_t>(info.args));
f.returns.resize(static_cast<size_t>(info.ret)); f.returns.resize(static_cast<size_t>(info.ret));
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction); f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction); if (evmasm::SemanticInformation::terminatesControlFlow(_instruction))
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_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.isMSize = _instruction == evmasm::Instruction::MSIZE;
f.literalArguments.clear(); f.literalArguments.clear();
f.instruction = _instruction; f.instruction = _instruction;

View File

@ -484,7 +484,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
yulAssert(!_block.operations.empty(), ""); yulAssert(!_block.operations.empty(), "");
CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation); CFG::BuiltinCall const* builtinCall = get_if<CFG::BuiltinCall>(&_block.operations.back().operation);
yulAssert(builtinCall, ""); yulAssert(builtinCall, "");
yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminates, ""); yulAssert(builtinCall->builtin.get().controlFlowSideEffects.terminatesOrReverts(), "");
} }
}, _block.exit); }, _block.exit);
// TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump. // TODO: We could assert that the last emitted assembly item terminated or was an (unconditional) jump.

View File

@ -129,8 +129,9 @@ WasmDialect::WasmDialect()
m_functions["unreachable"_yulstring].sideEffects.storage = SideEffects::None; m_functions["unreachable"_yulstring].sideEffects.storage = SideEffects::None;
m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None; m_functions["unreachable"_yulstring].sideEffects.memory = SideEffects::None;
m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None; m_functions["unreachable"_yulstring].sideEffects.otherState = SideEffects::None;
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true; m_functions["unreachable"_yulstring].controlFlowSideEffects.canTerminate = false;
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true; m_functions["unreachable"_yulstring].controlFlowSideEffects.canRevert = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.canContinue = false;
addFunction("datasize", {i64}, {i64}, true, {LiteralKind::String}); addFunction("datasize", {i64}, {i64}, true, {LiteralKind::String});
addFunction("dataoffset", {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", "log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
{"eth", "getBlockNumber", {}, {i64}}, {"eth", "getBlockNumber", {}, {i64}},
{"eth", "getTxOrigin", {i32ptr}, {}}, {"eth", "getTxOrigin", {i32ptr}, {}},
{"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}}, {"eth", "finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false, false}},
{"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}}, {"eth", "revert", {i32ptr, i32}, {}, ControlFlowSideEffects{false, true, false}},
{"eth", "getReturnDataSize", {}, {i32}}, {"eth", "getReturnDataSize", {}, {i32}},
{"eth", "returnDataCopy", {i32ptr, i32, i32}, {}}, {"eth", "returnDataCopy", {i32ptr, i32, i32}, {}},
{"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}}, {"eth", "selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{false, true, false}},
{"eth", "getBlockTimestamp", {}, {i64}}, {"eth", "getBlockTimestamp", {}, {i64}},
{"debug", "print32", {i32}, {}}, {"debug", "print32", {i32}, {}},
{"debug", "print64", {i64}, {}}, {"debug", "print64", {i64}, {}},
@ -240,7 +241,7 @@ void WasmDialect::addExternals()
// TODO some of them are side effect free. // TODO some of them are side effect free.
f.sideEffects = SideEffects::worst(); f.sideEffects = SideEffects::worst();
f.sideEffects.cannotLoop = true; f.sideEffects.cannotLoop = true;
f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminates; f.sideEffects.movableApartFromEffects = !ext.controlFlowSideEffects.terminatesOrReverts();
f.controlFlowSideEffects = ext.controlFlowSideEffects; f.controlFlowSideEffects = ext.controlFlowSideEffects;
f.isMSize = false; f.isMSize = false;
f.literalArguments.clear(); f.literalArguments.clear();

View File

@ -22,7 +22,7 @@ using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::yul; using namespace solidity::yul;
map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block& _block) map<YulString, FunctionDefinition const*> FunctionDefinitionCollector::run(Block const& _block)
{ {
FunctionDefinitionCollector functionDefinitionCollector; FunctionDefinitionCollector functionDefinitionCollector;
functionDefinitionCollector(_block); functionDefinitionCollector(_block);

View File

@ -34,7 +34,7 @@ namespace solidity::yul
class FunctionDefinitionCollector: ASTWalker class FunctionDefinitionCollector: ASTWalker
{ {
public: public:
static std::map<YulString, FunctionDefinition const*> run(Block& _block); static std::map<YulString, FunctionDefinition const*> run(Block const& _block);
private: private:
using ASTWalker::operator(); using ASTWalker::operator();
void operator()(FunctionDefinition const& _functionDefinition) override; void operator()(FunctionDefinition const& _functionDefinition) override;

View File

@ -78,6 +78,9 @@
#include <range/v3/view/map.hpp> #include <range/v3/view/map.hpp>
#include <range/v3/action/remove.hpp> #include <range/v3/action/remove.hpp>
#include <limits>
#include <tuple>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::yul; using namespace solidity::yul;
@ -87,7 +90,7 @@ void OptimiserSuite::run(
GasMeter const* _meter, GasMeter const* _meter,
Object& _object, Object& _object,
bool _optimizeStackAllocation, bool _optimizeStackAllocation,
string const& _optimisationSequence, string_view _optimisationSequence,
optional<size_t> _expectedExecutionsPerDeployment, optional<size_t> _expectedExecutionsPerDeployment,
set<YulString> const& _externallyUsedIdentifiers set<YulString> const& _externallyUsedIdentifiers
) )
@ -267,9 +270,9 @@ map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
return lookupTable; 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) for (char abbreviation: _stepAbbreviations)
switch (abbreviation) switch (abbreviation)
{ {
@ -277,12 +280,12 @@ void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
case '\n': case '\n':
break; break;
case '[': case '[':
assertThrow(!insideLoop, OptimizerException, "Nested brackets are not supported"); assertThrow(nestingLevel < numeric_limits<int8_t>::max(), OptimizerException, "Brackets nested too deep");
insideLoop = true; nestingLevel++;
break; break;
case ']': case ']':
assertThrow(insideLoop, OptimizerException, "Unbalanced brackets"); nestingLevel--;
insideLoop = false; assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets");
break; break;
default: default:
{ {
@ -301,43 +304,99 @@ void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
OptimizerException, OptimizerException,
"'"s + abbreviation + "' is invalid in the current environment: " + *invalid "'"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); validateSequence(_stepAbbreviations);
string input = _stepAbbreviations; // This splits 'aaa[bbb]ccc...' into 'aaa' and '[bbb]ccc...'.
ranges::actions::remove(input, ' '); auto extractNonNestedPrefix = [](string_view _tail) -> tuple<string_view, string_view>
ranges::actions::remove(input, '\n'); {
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; vector<string> steps;
for (char abbreviation: _sequence) for (char abbreviation: _sequence)
if (abbreviation != ' ' && abbreviation != '\n')
steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation)); steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation));
return steps; return steps;
}; };
// The sequence has now been validated and must consist of pairs of segments that look like this: `aaa[bbb]` vector<tuple<string_view, bool>> subsequences;
// `aaa` or `[bbb]` can be empty. For example we consider a sequence like `fgo[aaf]Oo` to have string_view tail = _stepAbbreviations;
// four segments, the last of which is an empty bracket. while (!tail.empty())
size_t currentPairStart = 0;
while (currentPairStart < input.size())
{ {
size_t openingBracket = input.find('[', currentPairStart); string_view subsequence;
size_t closingBracket = input.find(']', openingBracket); tie(subsequence, tail) = extractNonNestedPrefix(tail);
size_t firstCharInside = (openingBracket == string::npos ? input.size() : openingBracket + 1); if (subsequence.size() > 0)
yulAssert((openingBracket == string::npos) == (closingBracket == string::npos), ""); subsequences.push_back({subsequence, false});
runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingBracket - currentPairStart)), _ast); if (tail.empty())
runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingBracket - firstCharInside)), _ast); 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);
}
}

View File

@ -29,6 +29,7 @@
#include <set> #include <set>
#include <string> #include <string>
#include <string_view>
#include <memory> #include <memory>
namespace solidity::yul namespace solidity::yul
@ -64,22 +65,17 @@ public:
GasMeter const* _meter, GasMeter const* _meter,
Object& _object, Object& _object,
bool _optimizeStackAllocation, bool _optimizeStackAllocation,
std::string const& _optimisationSequence, std::string_view _optimisationSequence,
std::optional<size_t> _expectedExecutionsPerDeployment, std::optional<size_t> _expectedExecutionsPerDeployment,
std::set<YulString> const& _externallyUsedIdentifiers = {} std::set<YulString> const& _externallyUsedIdentifiers = {}
); );
/// Ensures that specified sequence of step abbreviations is well-formed and can be executed. /// Ensures that specified sequence of step abbreviations is well-formed and can be executed.
/// @throw OptimizerException if the sequence is invalid /// @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::vector<std::string> const& _steps, Block& _ast);
void runSequence(std::string const& _stepAbbreviations, Block& _ast); void runSequence(std::string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable = false);
void runSequenceUntilStable(
std::vector<std::string> const& _steps,
Block& _ast,
size_t maxRounds = MaxRounds
);
static std::map<std::string, std::unique_ptr<OptimiserStep>> const& allSteps(); static std::map<std::string, std::unique_ptr<OptimiserStep>> const& allSteps();
static std::map<std::string, char> const& stepNameToAbbreviationMap(); static std::map<std::string, char> const& stepNameToAbbreviationMap();

View File

@ -413,7 +413,7 @@ def commandline_parser() -> ArgumentParser:
action='store_true', action='store_true',
help="Immediately exit and print compiler output if the compiler exits with an error.", help="Immediately exit and print compiler output if the compiler exits with an error.",
) )
return parser; return parser
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -20,7 +20,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
YULARGS=(--strict-assembly) 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") OLDARGS=(--optimize --combined-json "abi,asm,ast,bin,bin-runtime,devdoc,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc")
function compileFull() function compileFull()
{ {
@ -99,7 +99,7 @@ function compileFull()
printError "Inside directory:" printError "Inside directory:"
echo " $(pwd)" echo " $(pwd)"
printError "Input was:" printError "Input was:"
cat -- "${files[@]}" echo "${files[@]}"
false false
fi fi
} }

View File

@ -39,7 +39,7 @@ def colorize(left, right, id):
reset = "\x1b[0m" reset = "\x1b[0m"
colors = [red, yellow] colors = [red, yellow]
color = colors[id % len(colors)] color = colors[id % len(colors)]
function, arguments, results = parse_call(right) function, _arguments, _results = parse_call(right)
left = left.replace("compileAndRun", color + "compileAndRun" + reset) left = left.replace("compileAndRun", color + "compileAndRun" + reset)
right = right.replace("constructor", color + "constructor" + reset) right = right.replace("constructor", color + "constructor" + reset)
if function: if function:
@ -158,7 +158,7 @@ def main(argv):
interactive = False interactive = False
input_file = None input_file = None
try: try:
opts, args = getopt.getopt(argv, "if:") opts, _args = getopt.getopt(argv, "if:")
except getopt.GetoptError: except getopt.GetoptError:
print("./remove-testcases.py [-i] [-f <full path to SolidityEndToEndTest.cpp>]") print("./remove-testcases.py [-i] [-f <full path to SolidityEndToEndTest.cpp>]")
sys.exit(1) sys.exit(1)

View File

@ -158,7 +158,7 @@ class TraceAnalyser:
for trace_id, trace in enumerate(left.traces): for trace_id, trace in enumerate(left.traces):
left_trace = trace left_trace = trace
right_trace = right.traces[trace_id] 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): if str(left_trace) != str(right_trace):
mismatch_info = " " + str(left_trace) + "\n" mismatch_info = " " + str(left_trace) + "\n"
mismatch_info += " " + str(right_trace) + "\n" mismatch_info += " " + str(right_trace) + "\n"
@ -179,7 +179,7 @@ def main(argv):
extracted_tests_trace_file = None extracted_tests_trace_file = None
end_to_end_trace_file = None end_to_end_trace_file = None
try: try:
opts, args = getopt.getopt(argv, "s:e:") opts, _args = getopt.getopt(argv, "s:e:")
except getopt.GetoptError: except getopt.GetoptError:
print("verify-testcases.py [-s <path to semantic-trace>] [-e <path to endToEndExtraction-trace>]") print("verify-testcases.py [-s <path to semantic-trace>] [-e <path to endToEndExtraction-trace>]")
sys.exit(2) sys.exit(2)

View File

@ -261,9 +261,9 @@ def main(argv):
no_confirm = False no_confirm = False
examine_coverage = False examine_coverage = False
next_id = 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": if opt == "--check":
check = True check = True
elif opt == "--fix": elif opt == "--fix":

View File

@ -50,8 +50,9 @@ def readDependencies(fname):
library = line.split(' ', 1)[0][1:] library = line.split(' ', 1)[0][1:]
if (library.startswith("/usr/local/lib") or if (library.startswith("/usr/local/lib") or
library.startswith("/usr/local/opt") or library.startswith("/usr/local/opt") or
library.startswith("/Users/")): library.startswith("/Users/")
if (os.path.basename(library) != os.path.basename(fname)): ):
if os.path.basename(library) != os.path.basename(fname):
command = "install_name_tool -change " + \ command = "install_name_tool -change " + \
library + " @executable_path/./" + \ library + " @executable_path/./" + \
os.path.basename(library) + " " + fname os.path.basename(library) + " " + fname

View File

@ -20,7 +20,7 @@ repository. The changes are compared against ``origin/develop``.
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from enum import Enum from enum import Enum
from parsec import * from parsec import generate, ParseError, regex, string
from tabulate import tabulate from tabulate import tabulate
class Kind(Enum): class Kind(Enum):
@ -56,10 +56,10 @@ def diff_string() -> (Kind, Diff, int):
-// gas irOptimized: 138070 -// gas irOptimized: 138070
""" """
diff_kind = yield (minus | plus) diff_kind = yield minus | plus
yield comment yield comment
yield space 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 colon
yield space yield space
val = yield number() val = yield number()

View File

@ -179,16 +179,6 @@ case $(uname -s) in
. /etc/os-release . /etc/os-release
install_z3="" install_z3=""
case $VERSION_ID in 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) 8)
#jessie #jessie
echo "Installing solidity dependencies on Debian Jesse (8.x)." 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 "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).)" 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) xenial|sarah|serena|sonya|sylvia)
echo "Installing solidity dependencies on Ubuntu Xenial Xerus (16.04)." 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).)" echo "Or, you may also be running Linux Mint Sarah / Serena / Sonya / Sylvia (base: Ubuntu Xenial Xerus (16.04).)"
install_z3="libz3-dev" 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) bionic)
echo "Installing solidity dependencies." echo "Installing solidity dependencies."
install_z3="libz3-dev" install_z3="libz3-dev"
@ -311,6 +280,10 @@ case $(uname -s) in
echo "Installing solidity dependencies." echo "Installing solidity dependencies."
install_z3="libz3-dev" install_z3="libz3-dev"
;; ;;
hirsute)
echo "Installing solidity dependencies."
install_z3="libz3-dev"
;;
betsy) betsy)
#do not try anything for betsy. #do not try anything for betsy.
echo "Linux Mint Betsy is not supported at the moment as it runs off of Debian." 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 - Unknown or unsupported Ubuntu version ($(lsb_release -cs))"
echo "ERROR - This might not work, but we are trying anyway." 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 "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" install_z3="libz3-dev"
;; ;;
esac esac

View File

@ -6,7 +6,6 @@
# into files for e.g. fuzz testing as # into files for e.g. fuzz testing as
# scripts/isolate_tests.py test/libsolidity/* # scripts/isolate_tests.py test/libsolidity/*
import sys
import re import re
import os import os
import hashlib import hashlib

View File

@ -9,7 +9,6 @@ from os import path, walk
from sys import exit from sys import exit
from textwrap import dedent from textwrap import dedent
import subprocess import subprocess
import sys
PROJECT_ROOT = path.dirname(path.dirname(path.realpath(__file__))) PROJECT_ROOT = path.dirname(path.dirname(path.realpath(__file__)))
PYLINT_RCFILE = f"{PROJECT_ROOT}/scripts/pylintrc" PYLINT_RCFILE = f"{PROJECT_ROOT}/scripts/pylintrc"

View File

@ -24,24 +24,15 @@ disable=
duplicate-code, duplicate-code,
invalid-name, invalid-name,
missing-docstring, missing-docstring,
mixed-indentation,
no-else-return, no-else-return,
no-self-use, no-self-use,
pointless-string-statement, pointless-string-statement,
redefined-builtin, redefined-builtin,
redefined-outer-name, redefined-outer-name,
singleton-comparison, singleton-comparison,
superfluous-parens,
too-few-public-methods, too-few-public-methods,
trailing-newlines, too-many-public-methods,
undefined-variable, ungrouped-imports
ungrouped-imports,
unnecessary-semicolon,
unused-import,
unused-variable,
unused-wildcard-import,
useless-object-inheritance,
wildcard-import
[BASIC] [BASIC]

View File

@ -11,7 +11,7 @@ import time
DESCRIPTION = """Regressor is a tool to run regression tests in a CI env.""" 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""" """Prints a dot every "interval" (default is 300) seconds"""
def __init__(self, interval=300): def __init__(self, interval=300):
@ -30,7 +30,7 @@ class PrintDotsThread(object):
print(".") print(".")
time.sleep(self.interval) time.sleep(self.interval)
class regressor(): class regressor:
_re_sanitizer_log = re.compile(r"""ERROR: (libFuzzer|UndefinedBehaviorSanitizer)""") _re_sanitizer_log = re.compile(r"""ERROR: (libFuzzer|UndefinedBehaviorSanitizer)""")
def __init__(self, description, args): def __init__(self, description, args):

View File

@ -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" git clone --depth 2 --recursive https://github.com/ethereum/solidity.git -b "$branch"
mv solidity solc mv solidity solc
# Fetch jsoncpp dependency # Fetch dependencies
mkdir -p ./solc/deps/downloads/ 2>/dev/null || true 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/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/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 # Determine version
cd solc cd solc

View File

@ -44,9 +44,7 @@ def writeSourceToFile(lines):
os.system("mkdir -p " + filePath) os.system("mkdir -p " + filePath)
with open(srcName, mode='a+', encoding='utf8', newline='') as f: with open(srcName, mode='a+', encoding='utf8', newline='') as f:
createdSources.append(srcName) createdSources.append(srcName)
i = 0
for idx, line in enumerate(lines[1:]): for idx, line in enumerate(lines[1:]):
# write to file # write to file
if line[:12] != "==== Source:": if line[:12] != "==== Source:":
f.write(line) f.write(line)

View File

@ -7,7 +7,8 @@ import sys
import re import re
import os import os
import hashlib 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): def extract_test_cases(path):

View File

@ -41,9 +41,9 @@ parts:
source-tag: z3-4.8.4 source-tag: z3-4.8.4
plugin: make plugin: make
build-packages: [python3] build-packages: [python3]
stage-packages: [libstdc++6] stage-packages: [libstdc++6, libgomp1]
override-build: | override-build: |
python scripts/mk_make.py python3 scripts/mk_make.py
cd build cd build
make -j -l $(grep -c "^processor" /proc/cpuinfo) make -j -l $(grep -c "^processor" /proc/cpuinfo)
make install DESTDIR=$SNAPCRAFT_PART_INSTALL make install DESTDIR=$SNAPCRAFT_PART_INSTALL

View File

@ -553,7 +553,7 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da
if (fs::exists(pathName) && !m_options.output.overwriteFiles) if (fs::exists(pathName) && !m_options.output.overwriteFiles)
{ {
serr() << "Refusing to overwrite existing file \"" << pathName << "\" (use --overwrite to force)." << endl; serr() << "Refusing to overwrite existing file \"" << pathName << "\" (use --overwrite to force)." << endl;
m_error = true; m_outputFailed = true;
return; return;
} }
ofstream outFile(pathName); ofstream outFile(pathName);
@ -561,7 +561,7 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da
if (!outFile) if (!outFile)
{ {
serr() << "Could not write to file \"" << pathName << "\"." << endl; serr() << "Could not write to file \"" << pathName << "\"." << endl;
m_error = true; m_outputFailed = true;
return; return;
} }
} }
@ -631,6 +631,8 @@ bool CommandLineInterface::compile()
m_compiler->setViaIR(m_options.output.experimentalViaIR); m_compiler->setViaIR(m_options.output.experimentalViaIR);
m_compiler->setEVMVersion(m_options.output.evmVersion); m_compiler->setEVMVersion(m_options.output.evmVersion);
m_compiler->setRevertStringBehaviour(m_options.output.revertStrings); 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 // TODO: Perhaps we should not compile unless requested
m_compiler->enableIRGeneration(m_options.compiler.outputs.ir || m_options.compiler.outputs.irOptimized); 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"); formatter.printExceptionInformation(_exception, "Compiler error");
return false; 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) catch (Error const& _error)
{ {
if (_error.type() == Error::Type::DocstringParsingError) if (_error.type() == Error::Type::DocstringParsingError)
@ -735,23 +713,6 @@ bool CommandLineInterface::compile()
return false; 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; return true;
} }
@ -894,7 +855,7 @@ bool CommandLineInterface::actOnInput()
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, "");
outputCompilationResults(); outputCompilationResults();
} }
return !m_error; return !m_outputFailed;
} }
bool CommandLineInterface::link() bool CommandLineInterface::link()
@ -976,6 +937,7 @@ void CommandLineInterface::writeLinkedFiles()
if (!outFile) if (!outFile)
{ {
serr() << "Could not write to file " << src.first << ". Aborting." << endl; serr() << "Could not write to file " << src.first << ". Aborting." << endl;
m_outputFailed = true;
return; return;
} }
} }
@ -1013,35 +975,17 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
auto& stack = assemblyStacks[src.first] = yul::AssemblyStack( auto& stack = assemblyStacks[src.first] = yul::AssemblyStack(
m_options.output.evmVersion, m_options.output.evmVersion,
_language, _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)) if (!stack.parseAndAnalyze(src.first, src.second))
successful = false; successful = false;
else else
stack.optimize(); 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;
}
}
for (auto const& sourceAndStack: assemblyStacks) for (auto const& sourceAndStack: assemblyStacks)
{ {
@ -1073,30 +1017,9 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
sout() << stack.print() << endl; sout() << stack.print() << endl;
if (_language != yul::AssemblyStack::Language::Ewasm && _targetMachine == yul::AssemblyStack::Machine::Ewasm) if (_language != yul::AssemblyStack::Language::Ewasm && _targetMachine == yul::AssemblyStack::Machine::Ewasm)
{
try
{ {
stack.translate(yul::AssemblyStack::Language::Ewasm); stack.translate(yul::AssemblyStack::Language::Ewasm);
stack.optimize(); 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;
}
sout() << endl << "==========================" << endl; sout() << endl << "==========================" << endl;
sout() << endl << "Translated source:" << endl; sout() << endl << "Translated source:" << endl;
@ -1104,28 +1027,8 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
} }
yul::MachineAssemblyObject object; yul::MachineAssemblyObject object;
try
{
object = stack.assemble(_targetMachine); object = stack.assemble(_targetMachine);
object.bytecode->link(m_options.linker.libraries); 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;
}
sout() << endl << "Binary representation:" << endl; sout() << endl << "Binary representation:" << endl;
if (object.bytecode) if (object.bytecode)

View File

@ -121,7 +121,7 @@ private:
std::ostream& m_sout; std::ostream& m_sout;
std::ostream& m_serr; std::ostream& m_serr;
bool m_hasOutput = false; 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; FileReader m_fileReader;
std::optional<std::string> m_standardJsonInput; std::optional<std::string> m_standardJsonInput;
std::unique_ptr<frontend::CompilerStack> m_compiler; std::unique_ptr<frontend::CompilerStack> m_compiler;

View File

@ -67,6 +67,7 @@ static string const g_strImportAst = "import-ast";
static string const g_strInputFile = "input-file"; static string const g_strInputFile = "input-file";
static string const g_strYul = "yul"; static string const g_strYul = "yul";
static string const g_strYulDialect = "yul-dialect"; static string const g_strYulDialect = "yul-dialect";
static string const g_strDebugInfo = "debug-info";
static string const g_strIPFS = "ipfs"; static string const g_strIPFS = "ipfs";
static string const g_strLicense = "license"; static string const g_strLicense = "license";
static string const g_strLibraries = "libraries"; static string const g_strLibraries = "libraries";
@ -137,6 +138,14 @@ static set<string> const g_metadataHashArgs
g_strNone 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() void CommandLineParser::printVersionAndExit()
{ {
sout() << sout() <<
@ -244,6 +253,7 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex
output.evmVersion == _other.output.evmVersion && output.evmVersion == _other.output.evmVersion &&
output.experimentalViaIR == _other.output.experimentalViaIR && output.experimentalViaIR == _other.output.experimentalViaIR &&
output.revertStrings == _other.output.revertStrings && output.revertStrings == _other.output.revertStrings &&
output.debugInfoSelection == _other.output.debugInfoSelection &&
output.stopAfter == _other.output.stopAfter && output.stopAfter == _other.output.stopAfter &&
input.mode == _other.input.mode && input.mode == _other.input.mode &&
assembly.targetMachine == _other.assembly.targetMachine && assembly.targetMachine == _other.assembly.targetMachine &&
@ -460,6 +470,47 @@ bool CommandLineParser::parseLibraryOption(string const& _input)
return true; 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() po::options_description CommandLineParser::optionsDescription()
{ {
// Declare the supported options. // Declare the supported options.
@ -546,6 +597,13 @@ General Information)").c_str(),
po::value<string>()->value_name(joinHumanReadable(g_revertStringsArgs, ",")), po::value<string>()->value_name(joinHumanReadable(g_revertStringsArgs, ",")),
"Strip revert (and require) reason strings or add additional debugging information." "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(), g_strStopAfter.c_str(),
po::value<string>()->value_name("stage"), 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; serr() << "Option --" << option << " is only valid in compiler and assembler modes." << endl;
return false; 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) if (m_args.count(g_strColor) > 0)
@ -912,6 +976,23 @@ bool CommandLineParser::processArgs()
m_options.output.revertStrings = *revertStrings; 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()) if (!parseCombinedJsonOption())
return false; return false;
@ -930,8 +1011,8 @@ bool CommandLineParser::processArgs()
m_options.formatting.json.indent = m_args[g_strJsonIndent].as<uint32_t>(); m_options.formatting.json.indent = m_args[g_strJsonIndent].as<uint32_t>();
} }
for (auto&& [optionName, outputComponent]: CompilerOutputs::componentMap()) if (!parseOutputSelection())
m_options.compiler.outputs.*outputComponent = (m_args.count(optionName) > 0); return false;
m_options.compiler.estimateGas = (m_args.count(g_strGas) > 0); m_options.compiler.estimateGas = (m_args.count(g_strGas) > 0);

View File

@ -24,8 +24,12 @@
#include <libsolidity/interface/DebugSettings.h> #include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/FileReader.h> #include <libsolidity/interface/FileReader.h>
#include <libsolidity/interface/ImportRemapper.h> #include <libsolidity/interface/ImportRemapper.h>
#include <libyul/AssemblyStack.h> #include <libyul/AssemblyStack.h>
#include <liblangutil/DebugInfoSelection.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <libsolutil/JSON.h> #include <libsolutil/JSON.h>
#include <boost/program_options.hpp> #include <boost/program_options.hpp>
@ -174,6 +178,7 @@ struct CommandLineOptions
langutil::EVMVersion evmVersion; langutil::EVMVersion evmVersion;
bool experimentalViaIR = false; bool experimentalViaIR = false;
RevertStrings revertStrings = RevertStrings::Default; RevertStrings revertStrings = RevertStrings::Default;
std::optional<langutil::DebugInfoSelection> debugInfoSelection;
CompilerStack::State stopAfter = CompilerStack::State::CompilationSuccessful; CompilerStack::State stopAfter = CompilerStack::State::CompilationSuccessful;
} output; } output;
@ -286,6 +291,8 @@ private:
/// @return false if there are any validation errors, true otherwise. /// @return false if there are any validation errors, true otherwise.
bool parseLibraryOption(std::string const& _input); bool parseLibraryOption(std::string const& _input);
bool parseOutputSelection();
bool checkMutuallyExclusive(std::vector<std::string> const& _optionNames); bool checkMutuallyExclusive(std::vector<std::string> const& _optionNames);
[[noreturn]] void printVersionAndExit(); [[noreturn]] void printVersionAndExit();
[[noreturn]] void printLicenseAndExit(); [[noreturn]] void printLicenseAndExit();

View File

@ -22,11 +22,16 @@
*/ */
#include <solc/CommandLineInterface.h> #include <solc/CommandLineInterface.h>
#include <liblangutil/Exceptions.h>
#include <boost/exception/all.hpp> #include <boost/exception/all.hpp>
#include <clocale> #include <clocale>
#include <iostream> #include <iostream>
using namespace std; using namespace std;
using namespace solidity;
/* /*
The equivalent of setlocale(LC_ALL, "C") is called before any user code is run. The equivalent of setlocale(LC_ALL, "C") is called before any user code is run.
@ -53,24 +58,52 @@ static void setDefaultOrCLocale()
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
setDefaultOrCLocale();
solidity::frontend::CommandLineInterface cli(cin, cout, cerr);
if (!cli.parseArguments(argc, argv))
return 1;
if (!cli.readInputFiles())
return 1;
if (!cli.processInput())
return 1;
bool success = false;
try try
{ {
success = cli.actOnInput(); setDefaultOrCLocale();
} solidity::frontend::CommandLineInterface cli(cin, cout, cerr);
catch (boost::exception const& _exception) bool success =
{ cli.parseArguments(argc, argv) &&
cerr << "Exception during output generation: " << boost::diagnostic_information(_exception) << endl; cli.readInputFiles() &&
success = false; cli.processInput() &&
} cli.actOnInput();
return success ? 0 : 1; return success ? 0 : 1;
} }
catch (smtutil::SMTLogicError const& _exception)
{
cerr << "SMT logic error:" << endl;
cerr << boost::diagnostic_information(_exception);
return 1;
}
catch (langutil::UnimplementedFeatureError const& _exception)
{
cerr << "Unimplemented feature:" << endl;
cerr << boost::diagnostic_information(_exception);
return 1;
}
catch (langutil::InternalCompilerError const& _exception)
{
cerr << "Internal compiler error:" << endl;
cerr << boost::diagnostic_information(_exception);
return 1;
}
catch (boost::exception const& _exception)
{
cerr << "Uncaught exception:" << endl;
cerr << boost::diagnostic_information(_exception) << endl;
return 1;
}
catch (std::exception const& _exception)
{
cerr << "Uncaught exception:" << endl;
cerr << boost::diagnostic_information(_exception) << endl;
return 1;
}
catch (...)
{
cerr << "Uncaught exception" << endl;
cerr << boost::current_exception_diagnostic_information() << endl;
return 1;
}
}

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