+{% endblock %}
diff --git a/docs/cheatsheet.rst b/docs/cheatsheet.rst
index 4ff9118bc..d36a96360 100644
--- a/docs/cheatsheet.rst
+++ b/docs/cheatsheet.rst
@@ -130,6 +130,12 @@ Global Variables
- ``type(T).min`` (``T``): the minimum value representable by the integer type ``T``, see :ref:`Type Information`.
- ``type(T).max`` (``T``): the maximum value representable by the integer type ``T``, see :ref:`Type Information`.
+.. note::
+ When contracts are evaluated off-chain rather than in context of a transaction included in a
+ block, you should not assume that ``block.*`` and ``tx.*`` refer to values from any specific
+ block or transaction. These values are provided by the EVM implementation that executes the
+ contract and can be arbitrary.
+
.. note::
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
unless you know what you are doing.
diff --git a/docs/conf.py b/docs/conf.py
index 60d50fe51..b75eb966d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -44,6 +44,7 @@ def setup(sphinx):
extensions = [
'sphinx_a4doc',
'html_extra_template_renderer',
+ 'remix_code_links',
]
a4_base_path = os.path.dirname(__file__) + '/grammar'
@@ -128,7 +129,11 @@ html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = {}
+html_theme_options = {
+ 'logo_only': True,
+ 'style_nav_header_background': '#65afff',
+ 'display_version': True,
+}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
@@ -142,7 +147,7 @@ html_theme = 'sphinx_rtd_theme'
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+html_logo = "logo.svg"
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
@@ -238,7 +243,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
- ('index', 'solidity.tex', 'Solidity Documentation', 'Ethereum', 'manual'),
+ ('index', 'solidity.tex', 'Solidity Documentation', 'Ethereum', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 6b2db9546..ffb1d345c 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -86,12 +86,10 @@ Running the Compiler Tests
Prerequisites
-------------
-Some tests require the `evmone `_
-library, others require `libz3 `_. The test script
-tries to discover the location of the ``evmone`` library, which can be located
-in the current directory, installed on the system level, or the ``deps`` folder
-in the project top level. The required file is called ``libevmone.so`` on Linux
-systems, ``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS.
+For running all compiler tests you may want to optionally install a few
+dependencies (`evmone `_,
+`libz3 `_, and
+`libhera `_).
On macOS some of the testing scripts expect GNU coreutils to be installed.
This can be easiest accomplished using Homebrew: ``brew install coreutils``.
@@ -108,13 +106,28 @@ including those bundled into the `Boost C++ Test Framework `_
-and place it in the project root path or inside the ``deps`` folder.
+The test system automatically tries to discover the location of
+the `evmone `_ for running the semantic tests.
+
+The ``evmone`` library must be located in the ``deps`` or ``deps/lib`` directory relative to the
+current working directory, to its parent or its parent's parent. Alternatively an explicit location
+for the ``evmone`` shared object can be specified via the ``ETH_EVMONE`` environment variable.
+
+``evmone`` is needed mainly for running semantic and gas tests.
+If you do not have it installed, you can skip these tests by passing the ``--no-semantic-tests``
+flag to ``scripts/soltest.sh``.
+
+Running Ewasm tests is disabled by default and can be explicitly enabled
+via ``./scripts/soltest.sh --ewasm`` and requires `hera `_
+to be found by ``soltest``.
+The mechanism for locating the ``hera`` library is the same as for ``evmone``, except that the
+variable for specifying an explicit location is called ``ETH_HERA``.
+
+The ``evmone`` and ``hera`` libraries should both end with the file name
+extension ``.so`` on Linux, ``.dll`` on Windows systems and ``.dylib`` on macOS.
+
+For running SMT tests, the ``libz3`` library must be installed and locatable
+by ``cmake`` during compiler configure stage.
If the ``libz3`` library is not installed on your system, you should disable the
SMT tests by exporting ``SMT_FLAGS=--no-smt`` before running ``./scripts/tests.sh`` or
diff --git a/docs/credits-and-attribution.rst b/docs/credits-and-attribution.rst
new file mode 100644
index 000000000..f1aa6cfd3
--- /dev/null
+++ b/docs/credits-and-attribution.rst
@@ -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). |
++-------------------------+-----------------------------------------------------------------------+
diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst
index b1d963ee5..3383e6195 100644
--- a/docs/examples/micropayment.rst
+++ b/docs/examples/micropayment.rst
@@ -11,7 +11,7 @@ sign and verify signatures, and setup the payment channel.
Creating and verifying signatures
=================================
-Imagine Alice wants to send a quantity of Ether to Bob, i.e.
+Imagine Alice wants to send some Ether to Bob, i.e.
Alice is the sender and Bob is the recipient.
Alice only needs to send cryptographically signed messages off-chain
diff --git a/docs/ext/remix_code_links.py b/docs/ext/remix_code_links.py
new file mode 100644
index 000000000..2fc15ddda
--- /dev/null
+++ b/docs/ext/remix_code_links.py
@@ -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,
+ }
diff --git a/docs/index.rst b/docs/index.rst
index 44ddbff83..b8a830e0c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,11 +1,6 @@
Solidity
========
-.. image:: logo.svg
- :width: 120px
- :alt: Solidity logo
- :align: center
-
Solidity is an object-oriented, high-level language for implementing smart
contracts. Smart contracts are programs which govern the behaviour of accounts
within the Ethereum state.
@@ -22,8 +17,10 @@ With Solidity you can create contracts for uses such as voting, crowdfunding, bl
and multi-signature wallets.
When deploying contracts, you should use the latest released
-version of Solidity. This is because breaking changes as well as
-new features and bug fixes are introduced regularly. We currently use
+version of Solidity. Apart from exceptional cases, only the latest version receives
+`security fixes`.
+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 `_.
.. warning::
diff --git a/docs/internals/optimizer.rst b/docs/internals/optimizer.rst
index e5de8a197..93179c041 100644
--- a/docs/internals/optimizer.rst
+++ b/docs/internals/optimizer.rst
@@ -275,7 +275,7 @@ The following transformation steps are the main components:
- Common Subexpression Eliminator
- Expression Simplifier
- Redundant Assign Eliminator
-- Full Function Inliner
+- Full Inliner
Optimizer Steps
---------------
@@ -297,7 +297,8 @@ on the individual steps and their sequence below.
- :ref:`for-loop-condition-into-body`.
- :ref:`for-loop-condition-out-of-body`.
- :ref:`for-loop-init-rewriter`.
-- :ref:`functional-inliner`.
+- :ref:`expression-inliner`.
+- :ref:`full-inliner`.
- :ref:`function-grouper`.
- :ref:`function-hoister`.
- :ref:`function-specializer`.
@@ -1082,9 +1083,9 @@ The actual removal of the function is performed by the Unused Pruner.
Function Inlining
-----------------
-.. _functional-inliner:
+.. _expression-inliner:
-FunctionalInliner
+ExpressionInliner
^^^^^^^^^^^^^^^^^
This component of the optimizer performs restricted function inlining by inlining functions that can be
@@ -1107,12 +1108,12 @@ The result of this inlining is always a single expression.
This component can only be used on sources with unique names.
-.. _full-function-inliner:
+.. _full-inliner:
-FullFunctionInliner
-^^^^^^^^^^^^^^^^^^^
+FullInliner
+^^^^^^^^^^^
-The Full Function Inliner replaces certain calls of certain functions
+The Full Inliner replaces certain calls of certain functions
by the function's body. This is not very helpful in most cases, because
it just increases the code size but does not have a benefit. Furthermore,
code is usually very expensive and we would often rather have shorter
diff --git a/docs/internals/source_mappings.rst b/docs/internals/source_mappings.rst
index fbb84e2d1..11027b8ac 100644
--- a/docs/internals/source_mappings.rst
+++ b/docs/internals/source_mappings.rst
@@ -64,3 +64,7 @@ This means the following source mappings represent the same information:
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
``1:2:1;:9;2:1:2;;``
+
+Important to note is that when the :ref:`verbatim ` builtin is used,
+the source mappings will be invalid: The builtin is considered a single
+instruction instead of potentially multiple.
diff --git a/docs/internals/variable_cleanup.rst b/docs/internals/variable_cleanup.rst
index 1718fc66b..9fec6929f 100644
--- a/docs/internals/variable_cleanup.rst
+++ b/docs/internals/variable_cleanup.rst
@@ -14,7 +14,12 @@ hashes or sent as the data of a message call. Similarly, before
storing a value in the storage, the remaining bits need to be cleaned
because otherwise the garbled value can be observed.
-On the other hand, we do not clean the bits if the immediately
+Note that access via inline assembly is not considered such an operation:
+If you use inline assembly to access Solidity variables
+shorter than 256 bits, the compiler does not guarantee that
+the value is properly cleaned up.
+
+Moreover, we do not clean the bits if the immediately
following operation is not affected. For instance, since any non-zero
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
the boolean values before they are used as the condition for
diff --git a/docs/ir/ir-breaking-changes.rst b/docs/ir/ir-breaking-changes.rst
index 9223b3763..8afbc58c3 100644
--- a/docs/ir/ir-breaking-changes.rst
+++ b/docs/ir/ir-breaking-changes.rst
@@ -1,3 +1,6 @@
+
+.. index: ir breaking changes
+
*********************************
Solidity IR-based Codegen Changes
*********************************
@@ -35,13 +38,15 @@ hiding new and different behavior in existing code.
We have the same behavior for implicit delete, for example when array of structs is shortened.
-- Function modifiers are implemented in a slightly different way regarding function parameters.
+- Function modifiers are implemented in a slightly different way regarding function parameters and return variables.
This especially has an effect if the placeholder ``_;`` is evaluated multiple times in a modifier.
- In the old code generator, each function parameter has a fixed slot on the stack. If the function
- is run multiple times because ``_;`` is used multiple times or used in a loop, then a change to the
- function parameter's value is visible in the next execution of the function.
+ In the old code generator, each function parameter and return variable has a fixed slot on the stack.
+ If the function is run multiple times because ``_;`` is used multiple times or used in a loop, then a
+ change to the function parameter's or return variable's value is visible in the next execution of the function.
The new code generator implements modifiers using actual functions and passes function parameters on.
- This means that multiple executions of a function will get the same values for the parameters.
+ This means that multiple evaluations of a function's body will get the same values for the parameters,
+ and the effect on return variables is that they are reset to their default (zero) value for each
+ execution.
.. code-block:: solidity
@@ -57,6 +62,35 @@ hiding new and different behavior in existing code.
If you execute ``f(0)`` in the old code generator, it will return ``2``, while
it will return ``1`` when using the new code generator.
+ .. code-block:: solidity
+
+ // SPDX-License-Identifier: GPL-3.0
+ pragma solidity >=0.7.1 <0.9.0;
+
+ contract C {
+ bool active = true;
+ modifier mod()
+ {
+ _;
+ active = false;
+ _;
+ }
+ function foo() external mod() returns (uint ret)
+ {
+ if (active)
+ ret = 1; // Same as ``return 1``
+ }
+ }
+
+ The function ``C.foo()`` returns the following values:
+
+ - Old code generator: ``1`` as the return variable is initialized to ``0`` only once before the first ``_;``
+ evaluation and then overwritten by the ``return 1;``. It is not initialized again for the second ``_;``
+ evaluation and ``foo()`` does not explicitly assign it either (due to ``active == false``), thus it keeps
+ its first value.
+ - New code generator: ``0`` as all parameters, including return parameters, will be re-initialized before
+ each ``_;`` evaluation.
+
- The order of contract initialization has changed in case of inheritance.
The order used to be:
diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst
index 95438b85a..8a6173280 100644
--- a/docs/units-and-global-variables.rst
+++ b/docs/units-and-global-variables.rst
@@ -92,6 +92,12 @@ Block and Transaction Properties
``msg.value`` can change for every **external** function call.
This includes calls to library functions.
+.. note::
+ When contracts are evaluated off-chain rather than in context of a transaction included in a
+ block, you should not assume that ``block.*`` and ``tx.*`` refer to values from any specific
+ block or transaction. These values are provided by the EVM implementation that executes the
+ contract and can be arbitrary.
+
.. note::
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
unless you know what you are doing.
diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst
index 1b14187ca..88da5d5e6 100644
--- a/docs/using-the-compiler.rst
+++ b/docs/using-the-compiler.rst
@@ -308,7 +308,18 @@ Input Description
// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects
// "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now.
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
- "revertStrings": "default"
+ "revertStrings": "default",
+ // Optional: How much extra debug information to include in comments in the produced EVM
+ // assembly and Yul code. Available components are:
+ // - `location`: Annotations of the form `@src ::` indicating the
+ // location of the corresponding element in the original Solidity file, where:
+ // - `` is the file index matching the `@use-src` annotation,
+ // - `` is the index of the first byte at that location,
+ // - `` 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": {
diff --git a/docs/yul.rst b/docs/yul.rst
index e4819ff22..433f175f0 100644
--- a/docs/yul.rst
+++ b/docs/yul.rst
@@ -1002,6 +1002,8 @@ within one Yul subobject. If at least one ``memoryguard`` call is found in a sub
the additional optimiser steps will be run on it.
+.. _yul-verbatim:
+
verbatim
^^^^^^^^
@@ -1263,6 +1265,7 @@ Abbreviation Full name
``a`` ``SSATransform``
``t`` ``StructuralSimplifier``
``u`` ``UnusedPruner``
+``p`` ``UnusedFunctionParameterPruner``
``d`` ``VarDeclInitializer``
============ ===============================
diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp
index 7884dd191..12c7be230 100644
--- a/libevmasm/Assembly.cpp
+++ b/libevmasm/Assembly.cpp
@@ -48,11 +48,11 @@ using namespace solidity::evmasm;
using namespace solidity::langutil;
using namespace solidity::util;
-AssemblyItem const& Assembly::append(AssemblyItem const& _i)
+AssemblyItem const& Assembly::append(AssemblyItem _i)
{
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
m_deposit += static_cast(_i.deposit());
- m_items.emplace_back(_i);
+ m_items.emplace_back(move(_i));
if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid())
m_items.back().setLocation(m_currentSourceLocation);
m_items.back().m_modifierDepth = m_currentModifierDepth;
@@ -68,7 +68,7 @@ unsigned Assembly::codeSize(unsigned subTagSize) const
ret += i.second.size();
for (AssemblyItem const& i: m_items)
- ret += i.bytesRequired(tagSize);
+ ret += i.bytesRequired(tagSize, Precision::Approximate);
if (numberEncodingSize(ret) <= tagSize)
return static_cast(ret);
}
@@ -96,13 +96,13 @@ public:
m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes), m_assembly(_assembly)
{}
- void feed(AssemblyItem const& _item)
+ void feed(AssemblyItem const& _item, DebugInfoSelection const& _debugInfoSelection)
{
if (_item.location().isValid() && _item.location() != m_location)
{
flush();
m_location = _item.location();
- printLocation();
+ printLocation(_debugInfoSelection);
}
string expression = _item.toAssemblyText(m_assembly);
@@ -142,16 +142,29 @@ public:
m_pending.clear();
}
- void printLocation()
+ void printLocation(DebugInfoSelection const& _debugInfoSelection)
{
- if (!m_location.isValid())
+ if (!m_location.isValid() || (!_debugInfoSelection.location && !_debugInfoSelection.snippet))
return;
+
m_out << m_prefix << " /*";
- if (m_location.sourceName)
- m_out << " " + escapeAndQuoteString(*m_location.sourceName);
- if (m_location.hasText())
- m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
- m_out << " " << locationFromSources(m_sourceCodes, m_location);
+
+ if (_debugInfoSelection.location)
+ {
+ if (m_location.sourceName)
+ m_out << " " + escapeAndQuoteString(*m_location.sourceName);
+ if (m_location.hasText())
+ m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
+ }
+
+ if (_debugInfoSelection.snippet)
+ {
+ if (_debugInfoSelection.location)
+ m_out << " ";
+
+ m_out << locationFromSources(m_sourceCodes, m_location);
+ }
+
m_out << " */" << endl;
}
@@ -167,12 +180,17 @@ private:
}
-void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
+void Assembly::assemblyStream(
+ ostream& _out,
+ DebugInfoSelection const& _debugInfoSelection,
+ string const& _prefix,
+ StringMap const& _sourceCodes
+) const
{
Functionalizer f(_out, _prefix, _sourceCodes, *this);
for (auto const& i: m_items)
- f.feed(i);
+ f.feed(i, _debugInfoSelection);
f.flush();
if (!m_data.empty() || !m_subs.empty())
@@ -185,7 +203,7 @@ void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap co
for (size_t i = 0; i < m_subs.size(); ++i)
{
_out << endl << _prefix << "sub_" << i << ": assembly {\n";
- m_subs[i]->assemblyStream(_out, _prefix + " ", _sourceCodes);
+ m_subs[i]->assemblyStream(_out, _debugInfoSelection, _prefix + " ", _sourceCodes);
_out << _prefix << "}" << endl;
}
}
@@ -194,10 +212,13 @@ void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap co
_out << endl << _prefix << "auxdata: 0x" << util::toHex(m_auxiliaryData) << endl;
}
-string Assembly::assemblyString(StringMap const& _sourceCodes) const
+string Assembly::assemblyString(
+ DebugInfoSelection const& _debugInfoSelection,
+ StringMap const& _sourceCodes
+) const
{
ostringstream tmp;
- assemblyStream(tmp, "", _sourceCodes);
+ assemblyStream(tmp, _debugInfoSelection, "", _sourceCodes);
return tmp.str();
}
@@ -675,8 +696,11 @@ LinkerObject const& Assembly::assemble() const
break;
case PushImmutable:
ret.bytecode.push_back(static_cast(Instruction::PUSH32));
+ // Maps keccak back to the "identifier" string of that immutable.
ret.immutableReferences[i.data()].first = m_immutables.at(i.data());
+ // Record the bytecode offset of the PUSH32 argument.
ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
+ // Advance bytecode by 32 bytes (default initialized).
ret.bytecode.resize(ret.bytecode.size() + 32);
break;
case VerbatimBytecode:
@@ -684,6 +708,7 @@ LinkerObject const& Assembly::assemble() const
break;
case AssignImmutable:
{
+ // Expect 2 elements on stack (source, dest_base)
auto const& offsets = immutableReferencesBySub[i.data()].second;
for (size_t i = 0; i < offsets.size(); ++i)
{
diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h
index 1d91c2f90..f768ffe31 100644
--- a/libevmasm/Assembly.h
+++ b/libevmasm/Assembly.h
@@ -24,6 +24,7 @@
#include
#include
+#include
#include
#include
@@ -64,7 +65,7 @@ public:
AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(std::string const& _identifier);
- AssemblyItem const& append(AssemblyItem const& _i);
+ AssemblyItem const& append(AssemblyItem _i);
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
template Assembly& operator<<(T const& _d) { append(_d); return *this; }
@@ -142,10 +143,12 @@ public:
/// Create a text representation of the assembly.
std::string assemblyString(
+ langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
StringMap const& _sourceCodes = StringMap()
) const;
void assemblyStream(
std::ostream& _out,
+ langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
std::string const& _prefix = "",
StringMap const& _sourceCodes = StringMap()
) const;
diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp
index a34870eda..eeeadb3ef 100644
--- a/libevmasm/AssemblyItem.cpp
+++ b/libevmasm/AssemblyItem.cpp
@@ -65,7 +65,7 @@ void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
setData(data);
}
-size_t AssemblyItem::bytesRequired(size_t _addressLength) const
+size_t AssemblyItem::bytesRequired(size_t _addressLength, Precision _precision) const
{
switch (m_type)
{
@@ -87,10 +87,25 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
case PushImmutable:
return 1 + 32;
case AssignImmutable:
- if (m_immutableOccurrences)
- return 1 + (3 + 32) * *m_immutableOccurrences;
+ {
+ unsigned long immutableOccurrences = 0;
+
+ // Skip exact immutables count if no precise count was requested
+ if (_precision == Precision::Approximate)
+ immutableOccurrences = 1; // Assume one immut. ref.
else
- return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
+ {
+ solAssert(m_immutableOccurrences, "No immutable references. `bytesRequired()` called before assembly()?");
+ immutableOccurrences = m_immutableOccurrences.value();
+ }
+
+ if (immutableOccurrences != 0)
+ // (DUP DUP PUSH ADD MSTORE)* (PUSH ADD MSTORE)
+ return (immutableOccurrences - 1) * (5 + 32) + (3 + 32);
+ else
+ // POP POP
+ return 2;
+ }
case VerbatimBytecode:
return std::get<2>(*m_verbatimBytecode).size();
default:
@@ -322,6 +337,26 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
return _out;
}
+size_t AssemblyItem::opcodeCount() const noexcept
+{
+ switch (m_type)
+ {
+ case AssemblyItemType::AssignImmutable:
+ // Append empty items if this AssignImmutable was referenced more than once.
+ // For n immutable occurrences the first (n - 1) occurrences will
+ // generate 5 opcodes and the last will generate 3 opcodes,
+ // because it is reusing the 2 top-most elements on the stack.
+ solAssert(m_immutableOccurrences, "");
+
+ if (m_immutableOccurrences.value() != 0)
+ return (*m_immutableOccurrences - 1) * 5 + 3;
+ else
+ return 2; // two POP's
+ default:
+ return 1;
+ }
+}
+
std::string AssemblyItem::computeSourceMapping(
AssemblyItems const& _items,
map const& _sourceIndicesMap
@@ -334,6 +369,7 @@ std::string AssemblyItem::computeSourceMapping(
int prevSourceIndex = -1;
int prevModifierDepth = -1;
char prevJump = 0;
+
for (auto const& item: _items)
{
if (!ret.empty())
@@ -402,6 +438,9 @@ std::string AssemblyItem::computeSourceMapping(
}
}
+ if (item.opcodeCount() > 1)
+ ret += string(item.opcodeCount() - 1, ';');
+
prevStart = location.start;
prevLength = length;
prevSourceIndex = sourceIndex;
diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h
index f1b2b5043..1cbfe768d 100644
--- a/libevmasm/AssemblyItem.h
+++ b/libevmasm/AssemblyItem.h
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
@@ -51,6 +52,8 @@ enum AssemblyItemType
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
};
+enum class Precision { Precise , Approximate };
+
class Assembly;
class AssemblyItem;
using AssemblyItems = std::vector;
@@ -147,7 +150,10 @@ public:
/// @returns an upper bound for the number of bytes required by this item, assuming that
/// the value of a jump tag takes @a _addressLength bytes.
- size_t bytesRequired(size_t _addressLength) const;
+ /// @param _precision Whether to return a precise count (which involves
+ /// counting immutable references which are only set after
+ /// a call to `assemble()`) or an approx. count.
+ size_t bytesRequired(size_t _addressLength, Precision _precision = Precision::Precise) const;
size_t arguments() const;
size_t returnValues() const;
size_t deposit() const { return returnValues() - arguments(); }
@@ -169,9 +175,11 @@ public:
size_t m_modifierDepth = 0;
- void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = std::make_shared(_n); }
+ void setImmutableOccurrences(size_t _n) const { m_immutableOccurrences = _n; }
private:
+ size_t opcodeCount() const noexcept;
+
AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation
std::shared_ptr m_data; ///< Only valid if m_type != Operation
@@ -184,14 +192,14 @@ private:
/// e.g. PushSubSize, PushTag, PushSub, etc.
mutable std::shared_ptr m_pushedValue;
/// Number of PushImmutable's with the same hash. Only used for AssignImmutable.
- mutable std::shared_ptr m_immutableOccurrences;
+ mutable std::optional m_immutableOccurrences;
};
-inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
+inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength, Precision _precision = Precision::Precise)
{
size_t size = 0;
for (AssemblyItem const& item: _items)
- size += item.bytesRequired(_addressLength);
+ size += item.bytesRequired(_addressLength, _precision);
return size;
}
diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt
index 5b3afcdb6..4e392aadd 100644
--- a/libevmasm/CMakeLists.txt
+++ b/libevmasm/CMakeLists.txt
@@ -38,4 +38,4 @@ set(sources
)
add_library(evmasm ${sources})
-target_link_libraries(evmasm PUBLIC solutil)
+target_link_libraries(evmasm PUBLIC solutil fmt::fmt-header-only)
diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp
index 6800baef3..a639bc6fc 100644
--- a/libevmasm/ConstantOptimiser.cpp
+++ b/libevmasm/ConstantOptimiser.cpp
@@ -103,7 +103,7 @@ bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items)
{
- return evmasm::bytesRequired(_items, 3); // assume 3 byte addresses
+ return evmasm::bytesRequired(_items, 3, Precision::Approximate); // assume 3 byte addresses
}
void ConstantOptimisationMethod::replaceConstants(
diff --git a/libevmasm/Inliner.cpp b/libevmasm/Inliner.cpp
index 8d95bdc3b..527e6ae38 100644
--- a/libevmasm/Inliner.cpp
+++ b/libevmasm/Inliner.cpp
@@ -63,7 +63,7 @@ template
uint64_t codeSize(RangeType const& _itemRange)
{
return ranges::accumulate(_itemRange | ranges::views::transform(
- [](auto const& _item) { return _item.bytesRequired(2); }
+ [](auto const& _item) { return _item.bytesRequired(2, Precision::Approximate); }
), 0u);
}
/// @returns the tag id, if @a _item is a PushTag or Tag into the current subassembly, nullopt otherwise.
diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp
index 0ee0b5efd..8e4a09fcc 100644
--- a/libevmasm/PeepholeOptimiser.cpp
+++ b/libevmasm/PeepholeOptimiser.cpp
@@ -388,6 +388,8 @@ size_t numberOfPops(AssemblyItems const& _items)
bool PeepholeOptimiser::optimise()
{
+ // Avoid referencing immutables too early by using approx. counting in bytesRequired()
+ auto const approx = evmasm::Precision::Approximate;
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
while (state.i < m_items.size())
applyMethods(
@@ -398,7 +400,7 @@ bool PeepholeOptimiser::optimise()
);
if (m_optimisedItems.size() < m_items.size() || (
m_optimisedItems.size() == m_items.size() && (
- evmasm::bytesRequired(m_optimisedItems, 3) < evmasm::bytesRequired(m_items, 3) ||
+ evmasm::bytesRequired(m_optimisedItems, 3, approx) < evmasm::bytesRequired(m_items, 3, approx) ||
numberOfPops(m_optimisedItems) > numberOfPops(m_items)
)
))
diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt
index 96f2a23d8..626d2945a 100644
--- a/liblangutil/CMakeLists.txt
+++ b/liblangutil/CMakeLists.txt
@@ -3,6 +3,8 @@ set(sources
Common.h
CharStream.cpp
CharStream.h
+ DebugInfoSelection.cpp
+ DebugInfoSelection.h
ErrorReporter.cpp
ErrorReporter.h
EVMVersion.h
@@ -29,4 +31,4 @@ set(sources
)
add_library(langutil ${sources})
-target_link_libraries(langutil PUBLIC solutil)
+target_link_libraries(langutil PUBLIC solutil fmt::fmt-header-only)
diff --git a/liblangutil/DebugInfoSelection.cpp b/liblangutil/DebugInfoSelection.cpp
new file mode 100644
index 000000000..6ff023c01
--- /dev/null
+++ b/liblangutil/DebugInfoSelection.cpp
@@ -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 .
+*/
+// SPDX-License-Identifier: GPL-3.0
+
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+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::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>);
+}
+
+optional DebugInfoSelection::fromComponents(
+ vector 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 selectedComponentNames;
+ for (auto const& [name, member]: _selection.componentMap())
+ if (_selection.*member)
+ selectedComponentNames.push_back(name);
+
+ return _stream << joinHumanReadable(selectedComponentNames, ",");
+}
diff --git a/liblangutil/DebugInfoSelection.h b/liblangutil/DebugInfoSelection.h
new file mode 100644
index 000000000..40ba3d6b2
--- /dev/null
+++ b/liblangutil/DebugInfoSelection.h
@@ -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 .
+*/
+// SPDX-License-Identifier: GPL-3.0
+/**
+ * Handles selections of debug info components.
+ */
+
+#pragma once
+
+#include