mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge branch 'develop' of github.com:tfire/solidity into fix/remove-namespace-ast-annotations
This commit is contained in:
commit
047034544e
@ -1,12 +1,14 @@
|
||||
### 0.8.13 (unreleased)
|
||||
|
||||
Language Features:
|
||||
* General: Allow annotating inline assembly as memory-safe to allow optimizations and stack limit evasion that rely on respecting Solidity's memory model.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* JSON-AST: Added selector field for errors and events.
|
||||
|
||||
Bugfixes:
|
||||
* Yul IR Code Generation: Optimize embedded creation code with correct settings. This fixes potential mismatches between the constructor code of a contract compiled in isolation and the bytecode in ``type(C).creationCode``, resp. the bytecode used for ``new C(...)``.
|
||||
|
||||
|
||||
### 0.8.12 (2022-02-16)
|
||||
|
@ -48,3 +48,11 @@ function(detect_stray_source_files FILELIST DIRECTORY)
|
||||
message(SEND_ERROR "The following source files are present but are not compiled: ${sources}")
|
||||
endif()
|
||||
endfunction(detect_stray_source_files)
|
||||
|
||||
# CreateExportedFunctionsForEMSDK(OUTPUT_VARIABLE Symbol1 Symbol2 ... SymbolN)
|
||||
function(CreateExportedFunctionsForEMSDK OUTPUT_VARIABLE)
|
||||
list(TRANSFORM ARGN PREPEND "\"_")
|
||||
list(TRANSFORM ARGN APPEND "\"")
|
||||
list(JOIN ARGN "," ARGN)
|
||||
set(${OUTPUT_VARIABLE} "[${ARGN}]" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
@ -228,6 +228,11 @@ of their block is reached.
|
||||
Conventions in Solidity
|
||||
-----------------------
|
||||
|
||||
.. _assembly-typed-variables:
|
||||
|
||||
Values of Typed Variables
|
||||
=========================
|
||||
|
||||
In contrast to EVM assembly, Solidity has types which are narrower than 256 bits,
|
||||
e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that
|
||||
types can be shorter than 256
|
||||
@ -237,6 +242,11 @@ This means that if you access such a variable
|
||||
from within inline assembly, you might have to manually clean the higher-order bits
|
||||
first.
|
||||
|
||||
.. _assembly-memory-management:
|
||||
|
||||
Memory Management
|
||||
=================
|
||||
|
||||
Solidity manages memory in the following way. There is a "free memory pointer"
|
||||
at position ``0x40`` in memory. If you want to allocate memory, use the memory
|
||||
starting from where this pointer points at and update it.
|
||||
@ -268,3 +278,99 @@ first slot of the array and followed by the array elements.
|
||||
Statically-sized memory arrays do not have a length field, but it might be added later
|
||||
to allow better convertibility between statically- and dynamically-sized arrays, so
|
||||
do not rely on this.
|
||||
|
||||
Memory Safety
|
||||
=============
|
||||
|
||||
Without the use of inline assembly, the compiler can rely on memory to remain in a well-defined
|
||||
state at all times. This is especially relevant for :ref:`the new code generation pipeline via Yul IR <ir-breaking-changes>`:
|
||||
this code generation path can move local variables from stack to memory to avoid stack-too-deep errors and
|
||||
perform additional memory optimizations, if it can rely on certain assumptions about memory use.
|
||||
|
||||
While we recommend to always respect Solidity's memory model, inline assembly allows you to use memory
|
||||
in an incompatible way. Therefore, moving stack variables to memory and additional memory optimizations are,
|
||||
by default, disabled in the presence of any inline assembly block that contains a memory operation or assigns
|
||||
to solidity variables in memory.
|
||||
|
||||
However, you can specifically annotate an assembly block to indicate that it in fact respects Solidity's memory
|
||||
model as follows:
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
assembly ("memory-safe") {
|
||||
...
|
||||
}
|
||||
|
||||
In particular, a memory-safe assembly block may only access the following memory ranges:
|
||||
|
||||
- Memory allocated by yourself using a mechanism like the ``allocate`` function described above.
|
||||
- Memory allocated by Solidity, e.g. memory within the bounds of a memory array you reference.
|
||||
- The scratch space between memory offset 0 and 64 mentioned above.
|
||||
- Temporary memory that is located *after* the value of the free memory pointer at the beginning of the assembly block,
|
||||
i.e. memory that is "allocated" at the free memory pointer without updating the free memory pointer.
|
||||
|
||||
Furthermore, if the assembly block assigns to Solidity variables in memory, you need to assure that accesses to
|
||||
the Solidity variables only access these memory ranges.
|
||||
|
||||
Since this is mainly about the optimizer, these restrictions still need to be followed, even if the assembly block
|
||||
reverts or terminates. As an example, the following assembly snippet is not memory safe:
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
assembly {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
|
||||
But the following is:
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
assembly ("memory-safe") {
|
||||
let p := mload(0x40)
|
||||
returndatacopy(p, 0, returndatasize())
|
||||
revert(p, returndatasize())
|
||||
}
|
||||
|
||||
Note that you do not need to update the free memory pointer if there is no following allocation,
|
||||
but you can only use memory starting from the current offset given by the free memory pointer.
|
||||
|
||||
If the memory operations use a length of zero, it is also fine to just use any offset (not only if it falls into the scratch space):
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
assembly ("memory-safe") {
|
||||
revert(0, 0)
|
||||
}
|
||||
|
||||
Note that not only memory operations in inline assembly itself can be memory-unsafe, but also assignments to
|
||||
solidity variables of reference type in memory. For example the following is not memory-safe:
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
bytes memory x;
|
||||
assembly {
|
||||
x := 0x40
|
||||
}
|
||||
x[0x20] = 0x42;
|
||||
|
||||
Inline assembly that neither involves any operations that access memory nor assigns to any solidity variables
|
||||
in memory is automatically considered memory-safe and does not need to be annotated.
|
||||
|
||||
.. warning::
|
||||
It is your responsibility to make sure that the assembly actually satisfies the memory model. If you annotate
|
||||
an assembly block as memory-safe, but violate one of the memory assumptions, this **will** lead to incorrect and
|
||||
undefined behaviour that cannot easily be discovered by testing.
|
||||
|
||||
In case you are developing a library that is meant to be compatible across multiple versions
|
||||
of solidity, you can use a special comment to annotate an assembly block as memory-safe:
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
...
|
||||
}
|
||||
|
||||
Note that we will disallow the annotation via comment in a future breaking release, so if you are not concerned with
|
||||
backwards-compatibility with older compiler versions, prefer using the dialect string.
|
||||
|
@ -251,6 +251,12 @@ mode AssemblyBlockMode;
|
||||
AssemblyDialect: '"evmasm"';
|
||||
AssemblyLBrace: '{' -> popMode, pushMode(YulMode);
|
||||
|
||||
AssemblyFlagString: '"' DoubleQuotedStringCharacter+ '"';
|
||||
|
||||
AssemblyBlockLParen: '(';
|
||||
AssemblyBlockRParen: ')';
|
||||
AssemblyBlockComma: ',';
|
||||
|
||||
AssemblyBlockWS: [ \t\r\n\u000C]+ -> skip ;
|
||||
AssemblyBlockCOMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
|
||||
AssemblyBlockLINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN) ;
|
||||
|
@ -476,7 +476,13 @@ revertStatement: Revert expression callArgumentList Semicolon;
|
||||
* The contents of an inline assembly block use a separate scanner/lexer, i.e. the set of keywords and
|
||||
* allowed identifiers is different inside an inline assembly block.
|
||||
*/
|
||||
assemblyStatement: Assembly AssemblyDialect? AssemblyLBrace yulStatement* YulRBrace;
|
||||
assemblyStatement: Assembly AssemblyDialect? assemblyFlags? AssemblyLBrace yulStatement* YulRBrace;
|
||||
|
||||
/**
|
||||
* Assembly flags.
|
||||
* Comma-separated list of double-quoted strings as flags.
|
||||
*/
|
||||
assemblyFlags: AssemblyBlockLParen AssemblyFlagString (AssemblyBlockComma AssemblyFlagString)* AssemblyBlockRParen;
|
||||
|
||||
//@doc:inline
|
||||
variableDeclarationList: variableDeclarations+=variableDeclaration (Comma variableDeclarations+=variableDeclaration)*;
|
||||
|
@ -5,10 +5,8 @@ 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.
|
||||
|
||||
Solidity is a `curly-bracket language <https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Curly-bracket_languages>`_.
|
||||
It is influenced by C++, Python and JavaScript, and is designed to target the Ethereum Virtual Machine (EVM).
|
||||
You can find more details about which languages Solidity has been inspired by in
|
||||
the :doc:`language influences <language-influences>` section.
|
||||
Solidity is a `curly-bracket language <https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Curly-bracket_languages>`_ designed to target the Ethereum Virtual Machine (EVM).
|
||||
It is influenced by C++, Python and JavaScript. You can find more details about which languages Solidity has been inspired by in the :doc:`language influences <language-influences>` section.
|
||||
|
||||
Solidity is statically typed, supports inheritance, libraries and complex
|
||||
user-defined types among other features.
|
||||
@ -90,24 +88,25 @@ our `Gitter channel <https://gitter.im/ethereum/solidity/>`_.
|
||||
Translations
|
||||
------------
|
||||
|
||||
Community volunteers help translate this documentation into several languages.
|
||||
They have varying degrees of completeness and up-to-dateness. The English
|
||||
Community contributors help translate this documentation into several languages.
|
||||
Note that they have varying degrees of completeness and up-to-dateness. The English
|
||||
version stands as a reference.
|
||||
|
||||
You can switch between languages by clicking on the flyout menu in the bottom-left corner
|
||||
and selecting the preferred language.
|
||||
|
||||
* `French <https://docs.soliditylang.org/fr/latest/>`_
|
||||
* `Indonesian <https://github.com/solidity-docs/id-indonesian>`_
|
||||
* `Persian <https://github.com/solidity-docs/fa-persian>`_
|
||||
* `Japanese <https://github.com/solidity-docs/ja-japanese>`_
|
||||
* `Korean <https://github.com/solidity-docs/ko-korean>`_
|
||||
* `Chinese <https://github.com/solidity-docs/zh-cn-chinese/>`_
|
||||
|
||||
.. note::
|
||||
|
||||
We recently set up a new GitHub organization and translation workflow to help streamline the
|
||||
community efforts. Please refer to the `translation guide <https://github.com/solidity-docs/translation-guide>`_
|
||||
for information on how to contribute to the community translations moving forward.
|
||||
|
||||
* `French <https://solidity-fr.readthedocs.io>`_ (in progress)
|
||||
* `Italian <https://github.com/damianoazzolini/solidity>`_ (in progress)
|
||||
* `Japanese <https://solidity-jp.readthedocs.io>`_
|
||||
* `Korean <https://solidity-kr.readthedocs.io>`_ (in progress)
|
||||
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
|
||||
* `Simplified Chinese <https://learnblockchain.cn/docs/solidity/>`_ (in progress)
|
||||
* `Spanish <https://solidity-es.readthedocs.io>`_
|
||||
* `Turkish <https://github.com/denizozzgur/Solidity_TR/blob/master/README.md>`_ (partial)
|
||||
for information on how to start a new language or contribute to the community translations.
|
||||
|
||||
Contents
|
||||
========
|
||||
|
@ -1,6 +1,8 @@
|
||||
|
||||
.. index: ir breaking changes
|
||||
|
||||
.. _ir-breaking-changes:
|
||||
|
||||
*********************************
|
||||
Solidity IR-based Codegen Changes
|
||||
*********************************
|
||||
|
@ -410,12 +410,13 @@ Input Description
|
||||
"source1.sol": ["contract1"],
|
||||
"source2.sol": ["contract2", "contract3"]
|
||||
},
|
||||
// Choose whether division and modulo operations should be replaced by
|
||||
// multiplication with slack variables. Default is `true`.
|
||||
// Using `false` here is recommended if you are using the CHC engine
|
||||
// Choose how division and modulo operations should be encoded.
|
||||
// When using `false` they are replaced by multiplication with slack
|
||||
// variables. This is the default.
|
||||
// Using `true` here is recommended if you are using the CHC engine
|
||||
// and not using Spacer as the Horn solver (using Eldarica, for example).
|
||||
// See the Formal Verification section for a more detailed explanation of this option.
|
||||
"divModWithSlacks": true,
|
||||
"divModNoSlacks": false,
|
||||
// Choose which model checker engine to use: all (default), bmc, chc, none.
|
||||
"engine": "chc",
|
||||
// Choose which types of invariants should be reported to the user: contract, reentrancy.
|
||||
|
13
docs/yul.rst
13
docs/yul.rst
@ -1124,6 +1124,11 @@ regular strings in native encoding. For code,
|
||||
|
||||
Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the previous chapter.
|
||||
|
||||
.. note::
|
||||
|
||||
An object with a name that ends in ``_deployed`` is treated as deployed code by the Yul optimizer.
|
||||
The only consequence of this is a different gas cost heuristic in the optimizer.
|
||||
|
||||
.. note::
|
||||
|
||||
Data objects or sub-objects whose names contain a ``.`` can be defined
|
||||
@ -1172,17 +1177,17 @@ An example Yul Object is shown below:
|
||||
|
||||
// now return the runtime object (the currently
|
||||
// executing code is the constructor code)
|
||||
size := datasize("runtime")
|
||||
size := datasize("Contract1_deployed")
|
||||
offset := allocate(size)
|
||||
// This will turn into a memory->memory copy for Ewasm and
|
||||
// a codecopy for EVM
|
||||
datacopy(offset, dataoffset("runtime"), size)
|
||||
datacopy(offset, dataoffset("Contract1_deployed"), size)
|
||||
return(offset, size)
|
||||
}
|
||||
|
||||
data "Table2" hex"4123"
|
||||
|
||||
object "runtime" {
|
||||
object "Contract1_deployed" {
|
||||
code {
|
||||
function allocate(size) -> ptr {
|
||||
ptr := mload(0x40)
|
||||
@ -1204,7 +1209,7 @@ An example Yul Object is shown below:
|
||||
// code here ...
|
||||
}
|
||||
|
||||
object "runtime" {
|
||||
object "Contract2_deployed" {
|
||||
code {
|
||||
// code here ...
|
||||
}
|
||||
|
@ -397,26 +397,6 @@ AssemblyItem Assembly::newImmutableAssignment(string const& _identifier)
|
||||
return AssemblyItem{AssignImmutable, h};
|
||||
}
|
||||
|
||||
Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs)
|
||||
{
|
||||
OptimiserSettings settings;
|
||||
settings.isCreation = _isCreation;
|
||||
settings.runInliner = true;
|
||||
settings.runJumpdestRemover = true;
|
||||
settings.runPeephole = true;
|
||||
if (_enable)
|
||||
{
|
||||
settings.runDeduplicate = true;
|
||||
settings.runCSE = true;
|
||||
settings.runConstantOptimiser = true;
|
||||
}
|
||||
settings.evmVersion = _evmVersion;
|
||||
settings.expectedExecutionsPerDeployment = _runs;
|
||||
optimise(settings);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
Assembly& Assembly::optimise(OptimiserSettings const& _settings)
|
||||
{
|
||||
optimiseInternal(_settings, {});
|
||||
@ -435,9 +415,8 @@ map<u256, u256> const& Assembly::optimiseInternal(
|
||||
for (size_t subId = 0; subId < m_subs.size(); ++subId)
|
||||
{
|
||||
OptimiserSettings settings = _settings;
|
||||
// Disable creation mode for sub-assemblies.
|
||||
settings.isCreation = false;
|
||||
map<u256, u256> const& subTagReplacements = m_subs[subId]->optimiseInternal(
|
||||
Assembly& sub = *m_subs[subId];
|
||||
map<u256, u256> const& subTagReplacements = sub.optimiseInternal(
|
||||
settings,
|
||||
JumpdestRemover::referencedTags(m_items, subId)
|
||||
);
|
||||
@ -456,7 +435,7 @@ map<u256, u256> const& Assembly::optimiseInternal(
|
||||
m_items,
|
||||
_tagsReferencedFromOutside,
|
||||
_settings.expectedExecutionsPerDeployment,
|
||||
_settings.isCreation,
|
||||
isCreation(),
|
||||
_settings.evmVersion
|
||||
}.optimise();
|
||||
|
||||
@ -557,8 +536,8 @@ map<u256, u256> const& Assembly::optimiseInternal(
|
||||
|
||||
if (_settings.runConstantOptimiser)
|
||||
ConstantOptimisationMethod::optimiseConstants(
|
||||
_settings.isCreation,
|
||||
_settings.isCreation ? 1 : _settings.expectedExecutionsPerDeployment,
|
||||
isCreation(),
|
||||
isCreation() ? 1 : _settings.expectedExecutionsPerDeployment,
|
||||
_settings.evmVersion,
|
||||
*this
|
||||
);
|
||||
|
@ -48,7 +48,7 @@ using AssemblyPointer = std::shared_ptr<Assembly>;
|
||||
class Assembly
|
||||
{
|
||||
public:
|
||||
explicit Assembly(std::string _name = std::string()):m_name(std::move(_name)) { }
|
||||
Assembly(bool _creation, std::string _name): m_creation(_creation), m_name(std::move(_name)) { }
|
||||
|
||||
AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); }
|
||||
AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); }
|
||||
@ -117,7 +117,6 @@ public:
|
||||
|
||||
struct OptimiserSettings
|
||||
{
|
||||
bool isCreation = false;
|
||||
bool runInliner = false;
|
||||
bool runJumpdestRemover = false;
|
||||
bool runPeephole = false;
|
||||
@ -134,13 +133,6 @@ public:
|
||||
/// is optimised according to the settings in @a _settings.
|
||||
Assembly& optimise(OptimiserSettings const& _settings);
|
||||
|
||||
/// Modify (if @a _enable is set) and return the current assembly such that creation and
|
||||
/// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly.
|
||||
/// @a _runs specifes an estimate on how often each opcode in this assembly will be executed,
|
||||
/// i.e. use a small value to optimise for size and a large value to optimise for runtime.
|
||||
/// If @a _enable is not set, will perform some simple peephole optimizations.
|
||||
Assembly& optimise(bool _enable, langutil::EVMVersion _evmVersion, bool _isCreation, size_t _runs);
|
||||
|
||||
/// Create a text representation of the assembly.
|
||||
std::string assemblyString(
|
||||
langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
|
||||
@ -164,6 +156,8 @@ public:
|
||||
std::vector<size_t> decodeSubPath(size_t _subObjectId) const;
|
||||
size_t encodeSubPath(std::vector<size_t> const& _subPath);
|
||||
|
||||
bool isCreation() const { return m_creation; }
|
||||
|
||||
protected:
|
||||
/// Does the same operations as @a optimise, but should only be applied to a sub and
|
||||
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
|
||||
@ -221,6 +215,8 @@ protected:
|
||||
mutable std::vector<size_t> m_tagPositionsInBytecode;
|
||||
|
||||
int m_deposit = 0;
|
||||
/// True, if the assembly contains contract creation code.
|
||||
bool const m_creation = false;
|
||||
/// Internal name of the assembly object, only used with the Yul backend
|
||||
/// currently
|
||||
std::string m_name;
|
||||
|
@ -200,9 +200,7 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
|
||||
case Operation:
|
||||
{
|
||||
assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction.");
|
||||
string name = instructionInfo(instruction()).name;
|
||||
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
|
||||
text = name;
|
||||
text = util::toLower(instructionInfo(instruction()).name);
|
||||
break;
|
||||
}
|
||||
case Push:
|
||||
|
@ -41,51 +41,28 @@ struct OptimiserState
|
||||
std::back_insert_iterator<AssemblyItems> out;
|
||||
};
|
||||
|
||||
template <class Method, size_t Arguments>
|
||||
struct ApplyRule
|
||||
template<typename FunctionType>
|
||||
struct FunctionParameterCount;
|
||||
template<typename R, typename... Args>
|
||||
struct FunctionParameterCount<R(Args...)>
|
||||
{
|
||||
};
|
||||
template <class Method>
|
||||
struct ApplyRule<Method, 4>
|
||||
{
|
||||
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
return Method::applySimple(_in[0], _in[1], _in[2], _in[3], _out);
|
||||
}
|
||||
};
|
||||
template <class Method>
|
||||
struct ApplyRule<Method, 3>
|
||||
{
|
||||
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
return Method::applySimple(_in[0], _in[1], _in[2], _out);
|
||||
}
|
||||
};
|
||||
template <class Method>
|
||||
struct ApplyRule<Method, 2>
|
||||
{
|
||||
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
return Method::applySimple(_in[0], _in[1], _out);
|
||||
}
|
||||
};
|
||||
template <class Method>
|
||||
struct ApplyRule<Method, 1>
|
||||
{
|
||||
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
return Method::applySimple(_in[0], _out);
|
||||
}
|
||||
static constexpr auto value = sizeof...(Args);
|
||||
};
|
||||
|
||||
template <class Method, size_t WindowSize>
|
||||
template <class Method>
|
||||
struct SimplePeepholeOptimizerMethod
|
||||
{
|
||||
template <size_t... Indices>
|
||||
static bool applyRule(AssemblyItems::const_iterator _in, back_insert_iterator<AssemblyItems> _out, index_sequence<Indices...>)
|
||||
{
|
||||
return Method::applySimple(_in[Indices]..., _out);
|
||||
}
|
||||
static bool apply(OptimiserState& _state)
|
||||
{
|
||||
static constexpr size_t WindowSize = FunctionParameterCount<decltype(Method::applySimple)>::value - 1;
|
||||
if (
|
||||
_state.i + WindowSize <= _state.items.size() &&
|
||||
ApplyRule<Method, WindowSize>::applyRule(_state.items.begin() + static_cast<ptrdiff_t>(_state.i), _state.out)
|
||||
applyRule(_state.items.begin() + static_cast<ptrdiff_t>(_state.i), _state.out, make_index_sequence<WindowSize>{})
|
||||
)
|
||||
{
|
||||
_state.i += WindowSize;
|
||||
@ -96,7 +73,7 @@ struct SimplePeepholeOptimizerMethod
|
||||
}
|
||||
};
|
||||
|
||||
struct Identity: SimplePeepholeOptimizerMethod<Identity, 1>
|
||||
struct Identity: SimplePeepholeOptimizerMethod<Identity>
|
||||
{
|
||||
static bool applySimple(AssemblyItem const& _item, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
@ -105,7 +82,7 @@ struct Identity: SimplePeepholeOptimizerMethod<Identity, 1>
|
||||
}
|
||||
};
|
||||
|
||||
struct PushPop: SimplePeepholeOptimizerMethod<PushPop, 2>
|
||||
struct PushPop: SimplePeepholeOptimizerMethod<PushPop>
|
||||
{
|
||||
static bool applySimple(AssemblyItem const& _push, AssemblyItem const& _pop, std::back_insert_iterator<AssemblyItems>)
|
||||
{
|
||||
@ -118,7 +95,7 @@ struct PushPop: SimplePeepholeOptimizerMethod<PushPop, 2>
|
||||
}
|
||||
};
|
||||
|
||||
struct OpPop: SimplePeepholeOptimizerMethod<OpPop, 2>
|
||||
struct OpPop: SimplePeepholeOptimizerMethod<OpPop>
|
||||
{
|
||||
static bool applySimple(
|
||||
AssemblyItem const& _op,
|
||||
@ -140,7 +117,7 @@ struct OpPop: SimplePeepholeOptimizerMethod<OpPop, 2>
|
||||
}
|
||||
};
|
||||
|
||||
struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap, 2>
|
||||
struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap>
|
||||
{
|
||||
static size_t applySimple(AssemblyItem const& _s1, AssemblyItem const& _s2, std::back_insert_iterator<AssemblyItems>)
|
||||
{
|
||||
@ -148,7 +125,7 @@ struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap, 2>
|
||||
}
|
||||
};
|
||||
|
||||
struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2>
|
||||
struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush>
|
||||
{
|
||||
static bool applySimple(AssemblyItem const& _push1, AssemblyItem const& _push2, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
@ -163,7 +140,7 @@ struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2>
|
||||
}
|
||||
};
|
||||
|
||||
struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap, 2>
|
||||
struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap>
|
||||
{
|
||||
static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
@ -181,7 +158,7 @@ struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap, 2>
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2>
|
||||
struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison>
|
||||
{
|
||||
static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
@ -207,7 +184,7 @@ struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2>
|
||||
};
|
||||
|
||||
/// Remove swapN after dupN
|
||||
struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap, 2>
|
||||
struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap>
|
||||
{
|
||||
static size_t applySimple(
|
||||
AssemblyItem const& _dupN,
|
||||
@ -230,7 +207,7 @@ struct DupSwap: SimplePeepholeOptimizerMethod<DupSwap, 2>
|
||||
};
|
||||
|
||||
|
||||
struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI, 4>
|
||||
struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI>
|
||||
{
|
||||
static size_t applySimple(
|
||||
AssemblyItem const& _iszero1,
|
||||
@ -256,7 +233,7 @@ struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI, 4>
|
||||
}
|
||||
};
|
||||
|
||||
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
|
||||
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext>
|
||||
{
|
||||
static size_t applySimple(
|
||||
AssemblyItem const& _pushTag,
|
||||
@ -282,7 +259,7 @@ struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
|
||||
}
|
||||
};
|
||||
|
||||
struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3>
|
||||
struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions>
|
||||
{
|
||||
static bool applySimple(
|
||||
AssemblyItem const& _pushTag,
|
||||
@ -317,7 +294,7 @@ struct TagConjunctions: SimplePeepholeOptimizerMethod<TagConjunctions, 3>
|
||||
}
|
||||
};
|
||||
|
||||
struct TruthyAnd: SimplePeepholeOptimizerMethod<TruthyAnd, 3>
|
||||
struct TruthyAnd: SimplePeepholeOptimizerMethod<TruthyAnd>
|
||||
{
|
||||
static bool applySimple(
|
||||
AssemblyItem const& _push,
|
||||
|
@ -40,10 +40,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include <liblangutil/Token.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <liblangutil/Token.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace solidity::langutil
|
||||
@ -180,11 +183,11 @@ tuple<Token, unsigned int, unsigned int> fromIdentifierOrKeyword(string const& _
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto positionM = find_if(_literal.begin(), _literal.end(), ::isdigit);
|
||||
auto positionM = find_if(_literal.begin(), _literal.end(), util::isDigit);
|
||||
if (positionM != _literal.end())
|
||||
{
|
||||
string baseType(_literal.begin(), positionM);
|
||||
auto positionX = find_if_not(positionM, _literal.end(), ::isdigit);
|
||||
auto positionX = find_if_not(positionM, _literal.end(), util::isDigit);
|
||||
int m = parseSize(positionM, positionX);
|
||||
Token keyword = keywordByName(baseType);
|
||||
if (keyword == Token::Bytes)
|
||||
@ -208,7 +211,7 @@ tuple<Token, unsigned int, unsigned int> fromIdentifierOrKeyword(string const& _
|
||||
positionM < positionX &&
|
||||
positionX < _literal.end() &&
|
||||
*positionX == 'x' &&
|
||||
all_of(positionX + 1, _literal.end(), ::isdigit)
|
||||
all_of(positionX + 1, _literal.end(), util::isDigit)
|
||||
) {
|
||||
int n = parseSize(positionX + 1, _literal.end());
|
||||
if (
|
||||
|
@ -1,8 +1,17 @@
|
||||
if (EMSCRIPTEN)
|
||||
CreateExportedFunctionsForEMSDK(
|
||||
ExportedFunctions
|
||||
solidity_license
|
||||
solidity_version
|
||||
solidity_compile
|
||||
solidity_alloc
|
||||
solidity_free
|
||||
solidity_reset
|
||||
)
|
||||
# Specify which functions to export in soljson.js.
|
||||
# Note that additional Emscripten-generated methods needed by solc-js are
|
||||
# defined to be exported in cmake/EthCompilerSettings.cmake.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\",\"_solidity_alloc\",\"_solidity_free\",\"_solidity_reset\"]'")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS='${ExportedFunctions}'")
|
||||
add_executable(soljson libsolc.cpp libsolc.h)
|
||||
target_link_libraries(soljson PRIVATE solidity)
|
||||
else()
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <liblangutil/Common.h>
|
||||
|
||||
#include <range/v3/algorithm/any_of.hpp>
|
||||
#include <range/v3/view/filter.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
@ -162,6 +163,81 @@ bool DocStringTagParser::visit(ErrorDefinition const& _error)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DocStringTagParser::visit(InlineAssembly const& _assembly)
|
||||
{
|
||||
if (!_assembly.documentation())
|
||||
return true;
|
||||
StructuredDocumentation documentation{-1, _assembly.location(), _assembly.documentation()};
|
||||
ErrorList errors;
|
||||
ErrorReporter errorReporter{errors};
|
||||
auto docTags = DocStringParser{documentation, errorReporter}.parse();
|
||||
|
||||
if (!errors.empty())
|
||||
{
|
||||
SecondarySourceLocation ssl;
|
||||
for (auto const& error: errors)
|
||||
if (error->comment())
|
||||
ssl.append(
|
||||
*error->comment(),
|
||||
_assembly.location()
|
||||
);
|
||||
m_errorReporter.warning(
|
||||
7828_error,
|
||||
_assembly.location(),
|
||||
"Inline assembly has invalid NatSpec documentation.",
|
||||
ssl
|
||||
);
|
||||
}
|
||||
|
||||
for (auto const& [tagName, tagValue]: docTags)
|
||||
{
|
||||
if (tagName == "solidity")
|
||||
{
|
||||
vector<string> values;
|
||||
boost::split(values, tagValue.content, isWhiteSpace);
|
||||
|
||||
set<string> valuesSeen;
|
||||
set<string> duplicates;
|
||||
for (auto const& value: values | ranges::views::filter(not_fn(&string::empty)))
|
||||
if (valuesSeen.insert(value).second)
|
||||
{
|
||||
if (value == "memory-safe-assembly")
|
||||
{
|
||||
if (_assembly.annotation().markedMemorySafe)
|
||||
m_errorReporter.warning(
|
||||
8544_error,
|
||||
_assembly.location(),
|
||||
"Inline assembly marked as memory safe using both a NatSpec tag and an assembly flag. "
|
||||
"If you are not concerned with backwards compatibility, only use the assembly flag, "
|
||||
"otherwise only use the NatSpec tag."
|
||||
);
|
||||
_assembly.annotation().markedMemorySafe = true;
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
8787_error,
|
||||
_assembly.location(),
|
||||
"Unexpected value for @solidity tag in inline assembly: " + value
|
||||
);
|
||||
}
|
||||
else if (duplicates.insert(value).second)
|
||||
m_errorReporter.warning(
|
||||
4377_error,
|
||||
_assembly.location(),
|
||||
"Value for @solidity tag in inline assembly specified multiple times: " + value
|
||||
);
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
6269_error,
|
||||
_assembly.location(),
|
||||
"Unexpected NatSpec tag \"" + tagName + "\" with value \"" + tagValue.content + "\" in inline assembly."
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocStringTagParser::checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
|
@ -48,6 +48,7 @@ private:
|
||||
bool visit(ModifierDefinition const& _modifier) override;
|
||||
bool visit(EventDefinition const& _event) override;
|
||||
bool visit(ErrorDefinition const& _error) override;
|
||||
bool visit(InlineAssembly const& _assembly) override;
|
||||
|
||||
void checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
|
@ -334,6 +334,27 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation)
|
||||
|
||||
bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
if (_inlineAssembly.flags())
|
||||
for (auto flag: *_inlineAssembly.flags())
|
||||
{
|
||||
if (*flag == "memory-safe")
|
||||
{
|
||||
if (_inlineAssembly.annotation().markedMemorySafe)
|
||||
m_errorReporter.syntaxError(
|
||||
7026_error,
|
||||
_inlineAssembly.location(),
|
||||
"Inline assembly marked memory-safe multiple times."
|
||||
);
|
||||
_inlineAssembly.annotation().markedMemorySafe = true;
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
4430_error,
|
||||
_inlineAssembly.location(),
|
||||
"Unknown inline assembly flag: \"" + *flag + "\""
|
||||
);
|
||||
}
|
||||
|
||||
if (!m_useYulOptimizer)
|
||||
return false;
|
||||
|
||||
|
@ -763,6 +763,7 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
||||
|
||||
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
bool lvalueAccessToMemoryVariable = false;
|
||||
// External references have already been resolved in a prior stage and stored in the annotation.
|
||||
// We run the resolve step again regardless.
|
||||
yul::ExternalIdentifierAccess::Resolver identifierAccess = [&](
|
||||
@ -787,6 +788,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
{
|
||||
solAssert(var->type(), "Expected variable type!");
|
||||
if (_context == yul::IdentifierContext::LValue && var->type()->dataStoredIn(DataLocation::Memory))
|
||||
lvalueAccessToMemoryVariable = true;
|
||||
if (var->immutable())
|
||||
{
|
||||
m_errorReporter.typeError(3773_error, nativeLocationOf(_identifier), "Assembly access to immutable variables is not supported.");
|
||||
@ -974,8 +977,11 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
identifierAccess
|
||||
);
|
||||
if (!analyzer.analyze(_inlineAssembly.operations()))
|
||||
return false;
|
||||
return true;
|
||||
solAssert(m_errorReporter.hasErrors());
|
||||
_inlineAssembly.annotation().hasMemoryEffects =
|
||||
lvalueAccessToMemoryVariable ||
|
||||
(analyzer.sideEffects().memory != yul::SideEffects::None);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(IfStatement const& _ifStatement)
|
||||
|
@ -1463,19 +1463,26 @@ public:
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
yul::Dialect const& _dialect,
|
||||
ASTPointer<std::vector<ASTPointer<ASTString>>> _flags,
|
||||
std::shared_ptr<yul::Block> _operations
|
||||
):
|
||||
Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(std::move(_operations)) {}
|
||||
Statement(_id, _location, _docString),
|
||||
m_dialect(_dialect),
|
||||
m_flags(move(_flags)),
|
||||
m_operations(std::move(_operations))
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
yul::Dialect const& dialect() const { return m_dialect; }
|
||||
yul::Block const& operations() const { return *m_operations; }
|
||||
ASTPointer<std::vector<ASTPointer<ASTString>>> const& flags() const { return m_flags; }
|
||||
|
||||
InlineAssemblyAnnotation& annotation() const override;
|
||||
|
||||
private:
|
||||
yul::Dialect const& m_dialect;
|
||||
ASTPointer<std::vector<ASTPointer<ASTString>>> m_flags;
|
||||
std::shared_ptr<yul::Block> m_operations;
|
||||
};
|
||||
|
||||
|
@ -219,6 +219,10 @@ struct InlineAssemblyAnnotation: StatementAnnotation
|
||||
std::map<yul::Identifier const*, ExternalIdentifierInfo> externalReferences;
|
||||
/// Information generated during analysis phase.
|
||||
std::shared_ptr<yul::AsmAnalysisInfo> analysisInfo;
|
||||
/// True, if the assembly block was annotated to be memory-safe.
|
||||
bool markedMemorySafe = false;
|
||||
/// True, if the assembly block involves any memory opcode or assigns to variables in memory.
|
||||
SetOnce<bool> hasMemoryEffects;
|
||||
};
|
||||
|
||||
struct BlockAnnotation: StatementAnnotation, ScopableAnnotation
|
||||
|
@ -600,11 +600,23 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
|
||||
for (Json::Value& it: externalReferences | ranges::views::values)
|
||||
externalReferencesJson.append(std::move(it));
|
||||
|
||||
setJsonNode(_node, "InlineAssembly", {
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))),
|
||||
make_pair("externalReferences", std::move(externalReferencesJson)),
|
||||
make_pair("evmVersion", dynamic_cast<solidity::yul::EVMDialect const&>(_node.dialect()).evmVersion().name())
|
||||
});
|
||||
};
|
||||
|
||||
if (_node.flags())
|
||||
{
|
||||
Json::Value flags(Json::arrayValue);
|
||||
for (auto const& flag: *_node.flags())
|
||||
if (flag)
|
||||
flags.append(*flag);
|
||||
else
|
||||
flags.append(Json::nullValue);
|
||||
attributes.emplace_back(make_pair("flags", move(flags)));
|
||||
}
|
||||
setJsonNode(_node, "InlineAssembly", move(attributes));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -626,11 +626,24 @@ ASTPointer<InlineAssembly> ASTJsonImporter::createInlineAssembly(Json::Value con
|
||||
astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!");
|
||||
|
||||
yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value());
|
||||
ASTPointer<vector<ASTPointer<ASTString>>> flags;
|
||||
if (_node.isMember("flags"))
|
||||
{
|
||||
flags = make_shared<vector<ASTPointer<ASTString>>>();
|
||||
Json::Value const& flagsNode = _node["flags"];
|
||||
astAssert(flagsNode.isArray(), "Assembly flags must be an array.");
|
||||
for (Json::ArrayIndex i = 0; i < flagsNode.size(); ++i)
|
||||
{
|
||||
astAssert(flagsNode[i].isString(), "Assembly flag must be a string.");
|
||||
flags->emplace_back(make_shared<ASTString>(flagsNode[i].asString()));
|
||||
}
|
||||
}
|
||||
shared_ptr<yul::Block> operations = make_shared<yul::Block>(yul::AsmJsonImporter(m_sourceNames).createBlock(member(_node, "AST")));
|
||||
return createASTNode<InlineAssembly>(
|
||||
_node,
|
||||
nullOrASTString(_node, "documentation"),
|
||||
dialect,
|
||||
move(flags),
|
||||
operations
|
||||
);
|
||||
}
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <libsolutil/FunctionSelector.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolutil/UTF8.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@ -781,8 +782,8 @@ tuple<bool, rational> RationalNumberType::parseRational(string const& _value)
|
||||
if (radixPoint != _value.end())
|
||||
{
|
||||
if (
|
||||
!all_of(radixPoint + 1, _value.end(), ::isdigit) ||
|
||||
!all_of(_value.begin(), radixPoint, ::isdigit)
|
||||
!all_of(radixPoint + 1, _value.end(), util::isDigit) ||
|
||||
!all_of(_value.begin(), radixPoint, util::isDigit)
|
||||
)
|
||||
return make_tuple(false, rational(0));
|
||||
|
||||
|
@ -572,8 +572,7 @@ void CompilerContext::updateSourceLocation()
|
||||
evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings)
|
||||
{
|
||||
// Constructing it this way so that we notice changes in the fields.
|
||||
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, false, m_evmVersion, 0};
|
||||
asmSettings.isCreation = true;
|
||||
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, m_evmVersion, 0};
|
||||
asmSettings.runInliner = _settings.runInliner;
|
||||
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
|
||||
asmSettings.runPeephole = _settings.runPeephole;
|
||||
|
@ -65,7 +65,7 @@ public:
|
||||
RevertStrings _revertStrings,
|
||||
CompilerContext* _runtimeContext = nullptr
|
||||
):
|
||||
m_asm(std::make_shared<evmasm::Assembly>()),
|
||||
m_asm(std::make_shared<evmasm::Assembly>(_runtimeContext != nullptr, std::string{})),
|
||||
m_evmVersion(_evmVersion),
|
||||
m_revertStrings(_revertStrings),
|
||||
m_reservedMemory{0},
|
||||
|
@ -160,8 +160,8 @@ public:
|
||||
|
||||
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
|
||||
|
||||
bool inlineAssemblySeen() const { return m_inlineAssemblySeen; }
|
||||
void setInlineAssemblySeen() { m_inlineAssemblySeen = true; }
|
||||
bool memoryUnsafeInlineAssemblySeen() const { return m_memoryUnsafeInlineAssemblySeen; }
|
||||
void setMemoryUnsafeInlineAssemblySeen() { m_memoryUnsafeInlineAssemblySeen = true; }
|
||||
|
||||
/// @returns the runtime ID to be used for the function in the dispatch routine
|
||||
/// and for internal function pointers.
|
||||
@ -202,8 +202,8 @@ private:
|
||||
/// Whether to use checked or wrapping arithmetic.
|
||||
Arithmetic m_arithmetic = Arithmetic::Checked;
|
||||
|
||||
/// Flag indicating whether any inline assembly block was seen.
|
||||
bool m_inlineAssemblySeen = false;
|
||||
/// Flag indicating whether any memory-unsafe inline assembly block was seen.
|
||||
bool m_memoryUnsafeInlineAssemblySeen = false;
|
||||
|
||||
/// Function definitions queued for code generation. They're the Solidity functions whose calls
|
||||
/// were discovered by the IR generator during AST traversal.
|
||||
|
@ -213,8 +213,8 @@ string IRGenerator::generate(
|
||||
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
|
||||
// This has to be called only after all other code generation for the creation object is complete.
|
||||
bool creationInvolvesAssembly = m_context.inlineAssemblySeen();
|
||||
t("memoryInitCreation", memoryInit(!creationInvolvesAssembly));
|
||||
bool creationInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen();
|
||||
t("memoryInitCreation", memoryInit(!creationInvolvesMemoryUnsafeAssembly));
|
||||
t("useSrcMapCreation", formatUseSrcMap(m_context));
|
||||
|
||||
resetContext(_contract, ExecutionContext::Deployed);
|
||||
@ -239,8 +239,8 @@ string IRGenerator::generate(
|
||||
t("useSrcMapDeployed", formatUseSrcMap(m_context));
|
||||
|
||||
// This has to be called only after all other code generation for the deployed object is complete.
|
||||
bool deployedInvolvesAssembly = m_context.inlineAssemblySeen();
|
||||
t("memoryInitDeployed", memoryInit(!deployedInvolvesAssembly));
|
||||
bool deployedInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen();
|
||||
t("memoryInitDeployed", memoryInit(!deployedInvolvesMemoryUnsafeAssembly));
|
||||
|
||||
solAssert(_contract.annotation().creationCallGraph->get() != nullptr, "");
|
||||
solAssert(_contract.annotation().deployedCallGraph->get() != nullptr, "");
|
||||
|
@ -203,7 +203,7 @@ private:
|
||||
else
|
||||
solAssert(false);
|
||||
|
||||
if (isdigit(value.front()))
|
||||
if (isDigit(value.front()))
|
||||
return yul::Literal{_identifier.debugData, yul::LiteralKind::Number, yul::YulString{value}, {}};
|
||||
else
|
||||
return yul::Identifier{_identifier.debugData, yul::YulString{value}};
|
||||
@ -2138,7 +2138,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
{
|
||||
setLocation(_inlineAsm);
|
||||
m_context.setInlineAssemblySeen();
|
||||
if (*_inlineAsm.annotation().hasMemoryEffects && !_inlineAsm.annotation().markedMemorySafe)
|
||||
m_context.setMemoryUnsafeInlineAssemblySeen();
|
||||
CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences};
|
||||
|
||||
yul::Statement modified = bodyCopier(_inlineAsm.operations());
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <libsmtutil/CHCSmtLib2Interface.h>
|
||||
#include <liblangutil/CharStreamProvider.h>
|
||||
#include <libsolutil/Algorithms.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#ifdef HAVE_Z3_DLOPEN
|
||||
#include <z3_version.h>
|
||||
@ -1998,9 +1999,9 @@ map<unsigned, vector<unsigned>> CHC::summaryCalls(CHCSolverInterface::CexGraph c
|
||||
// Predicates that do not have a CALLID have a predicate id at the end of <suffix>,
|
||||
// so the assertion below should still hold.
|
||||
auto beg = _s.data();
|
||||
while (beg != _s.data() + _s.size() && !isdigit(*beg)) ++beg;
|
||||
while (beg != _s.data() + _s.size() && !isDigit(*beg)) ++beg;
|
||||
auto end = beg;
|
||||
while (end != _s.data() + _s.size() && isdigit(*end)) ++end;
|
||||
while (end != _s.data() + _s.size() && isDigit(*end)) ++end;
|
||||
|
||||
solAssert(beg != end, "Expected to find numerical call or predicate id.");
|
||||
|
||||
|
@ -1321,13 +1321,28 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con
|
||||
advance();
|
||||
}
|
||||
|
||||
ASTPointer<vector<ASTPointer<ASTString>>> flags;
|
||||
if (m_scanner->currentToken() == Token::LParen)
|
||||
{
|
||||
flags = make_shared<vector<ASTPointer<ASTString>>>();
|
||||
do
|
||||
{
|
||||
advance();
|
||||
expectToken(Token::StringLiteral, false);
|
||||
flags->emplace_back(make_shared<ASTString>(m_scanner->currentLiteral()));
|
||||
advance();
|
||||
}
|
||||
while (m_scanner->currentToken() == Token::Comma);
|
||||
expectToken(Token::RParen);
|
||||
}
|
||||
|
||||
yul::Parser asmParser(m_errorReporter, dialect);
|
||||
shared_ptr<yul::Block> block = asmParser.parseInline(m_scanner);
|
||||
if (block == nullptr)
|
||||
BOOST_THROW_EXCEPTION(FatalError());
|
||||
|
||||
location.end = nativeLocationOf(*block).end;
|
||||
return make_shared<InlineAssembly>(nextID(), location, _docString, dialect, block);
|
||||
return make_shared<InlineAssembly>(nextID(), location, _docString, dialect, move(flags), block);
|
||||
}
|
||||
|
||||
ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _docString)
|
||||
|
@ -20,11 +20,12 @@
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <libsolutil/Exceptions.h>
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <libsolutil/FixedHash.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
@ -154,9 +155,9 @@ string solidity::util::getChecksummedAddress(string const& _addr)
|
||||
char addressCharacter = s[i];
|
||||
uint8_t nibble = hash[i / 2u] >> (4u * (1u - (i % 2u))) & 0xf;
|
||||
if (nibble >= 8)
|
||||
ret += static_cast<char>(toupper(addressCharacter));
|
||||
ret += toUpper(addressCharacter);
|
||||
else
|
||||
ret += static_cast<char>(tolower(addressCharacter));
|
||||
ret += toLower(addressCharacter);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -211,7 +212,7 @@ string solidity::util::escapeAndQuoteString(string const& _input)
|
||||
out += "\\r";
|
||||
else if (c == '\t')
|
||||
out += "\\t";
|
||||
else if (!isprint(c, locale::classic()))
|
||||
else if (!isPrint(c))
|
||||
{
|
||||
ostringstream o;
|
||||
o << "\\x" << std::hex << setfill('0') << setw(2) << (unsigned)(unsigned char)(c);
|
||||
|
@ -27,7 +27,9 @@
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <libsolutil/Numeric.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -197,4 +199,47 @@ inline std::optional<unsigned> toUnsignedInt(std::string const& _value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts parameter _c to its lowercase equivalent if c is an uppercase letter and has a lowercase equivalent. It uses the classic "C" locale semantics.
|
||||
/// @param _c value to be converted
|
||||
/// @return the converted value
|
||||
inline char toLower(char _c)
|
||||
{
|
||||
return tolower(_c, std::locale::classic());
|
||||
}
|
||||
|
||||
/// Converts parameter _c to its uppercase equivalent if c is an lowercase letter and has a uppercase equivalent. It uses the classic "C" locale semantics.
|
||||
/// @param _c value to be converted
|
||||
/// @return the converted value
|
||||
inline char toUpper(char _c)
|
||||
{
|
||||
return toupper(_c, std::locale::classic());
|
||||
}
|
||||
|
||||
/// Converts parameter _s to its lowercase equivalent. It uses the classic "C" locale semantics.
|
||||
/// @param _s value to be converted
|
||||
/// @return the converted value
|
||||
inline std::string toLower(std::string _s)
|
||||
{
|
||||
std::transform(_s.begin(), _s.end(), _s.begin(), [](char _c) {
|
||||
return toLower(_c);
|
||||
});
|
||||
return _s;
|
||||
}
|
||||
|
||||
/// Checks whether _c is a decimal digit character. It uses the classic "C" locale semantics.
|
||||
/// @param _c character to be checked
|
||||
/// @return true if _c is a decimal digit character, false otherwise
|
||||
inline bool isDigit(char _c)
|
||||
{
|
||||
return isdigit(_c, std::locale::classic());
|
||||
}
|
||||
|
||||
// Checks if character is printable using classic "C" locale
|
||||
/// @param _c character to be checked
|
||||
/// @return true if _c is a printable character, false otherwise.
|
||||
inline bool isPrint(char _c)
|
||||
{
|
||||
return isprint(_c, std::locale::classic());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -316,6 +316,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
literalArguments = &f->literalArguments;
|
||||
|
||||
validateInstructions(_funCall);
|
||||
m_sideEffects += f->sideEffects;
|
||||
}
|
||||
else if (m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
|
||||
[&](Scope::Variable const&)
|
||||
|
@ -94,6 +94,8 @@ public:
|
||||
void operator()(Leave const&) { }
|
||||
void operator()(Block const& _block);
|
||||
|
||||
/// @returns the worst side effects encountered during analysis (including within defined functions).
|
||||
SideEffects const& sideEffects() const { return m_sideEffects; }
|
||||
private:
|
||||
/// Visits the expression, expects that it evaluates to exactly one value and
|
||||
/// returns the type. Reports errors on errors and returns the default type.
|
||||
@ -128,6 +130,8 @@ private:
|
||||
/// Names of data objects to be referenced by builtin functions with literal arguments.
|
||||
std::set<YulString> m_dataNames;
|
||||
ForLoop const* m_currentForLoop = nullptr;
|
||||
/// Worst side effects encountered during analysis (including within defined functions).
|
||||
SideEffects m_sideEffects;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <optional>
|
||||
|
||||
using namespace std;
|
||||
@ -71,8 +72,7 @@ evmasm::Assembly::OptimiserSettings translateOptimiserSettings(
|
||||
)
|
||||
{
|
||||
// Constructing it this way so that we notice changes in the fields.
|
||||
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, false, _evmVersion, 0};
|
||||
asmSettings.isCreation = true;
|
||||
evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, _evmVersion, 0};
|
||||
asmSettings.runInliner = _settings.runInliner;
|
||||
asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
|
||||
asmSettings.runPeephole = _settings.runPeephole;
|
||||
@ -194,7 +194,10 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
|
||||
yulAssert(_object.analysisInfo, "");
|
||||
for (auto& subNode: _object.subObjects)
|
||||
if (auto subObject = dynamic_cast<Object*>(subNode.get()))
|
||||
optimize(*subObject, false);
|
||||
{
|
||||
bool isCreation = !boost::ends_with(subObject->name.str(), "_deployed");
|
||||
optimize(*subObject, isCreation);
|
||||
}
|
||||
|
||||
Dialect const& dialect = languageToDialect(m_language, m_evmVersion);
|
||||
unique_ptr<GasMeter> meter;
|
||||
@ -281,7 +284,7 @@ AssemblyStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
|
||||
yulAssert(m_parserResult->code, "");
|
||||
yulAssert(m_parserResult->analysisInfo, "");
|
||||
|
||||
evmasm::Assembly assembly;
|
||||
evmasm::Assembly assembly(true, {});
|
||||
EthAssemblyAdapter adapter(assembly);
|
||||
compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation);
|
||||
|
||||
|
@ -98,7 +98,7 @@ public:
|
||||
/// Append the assembled size as a constant.
|
||||
virtual void appendAssemblySize() = 0;
|
||||
/// Creates a new sub-assembly, which can be referenced using dataSize and dataOffset.
|
||||
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(std::string _name = "") = 0;
|
||||
virtual std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = "") = 0;
|
||||
/// Appends the offset of the given sub-assembly or data.
|
||||
virtual void appendDataOffset(std::vector<SubID> const& _subPath) = 0;
|
||||
/// Appends the size of the given sub-assembly or data.
|
||||
|
@ -21,17 +21,17 @@
|
||||
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/AsmParser.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/backends/evm/AbstractAssembly.h>
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
#include <libevmasm/Instruction.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
#include <range/v3/view/tail.hpp>
|
||||
@ -121,8 +121,7 @@ set<YulString> createReservedIdentifiers(langutil::EVMVersion _evmVersion)
|
||||
set<YulString> reserved;
|
||||
for (auto const& instr: evmasm::c_instructions)
|
||||
{
|
||||
string name = instr.first;
|
||||
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
|
||||
string name = toLower(instr.first);
|
||||
if (!baseFeeException(instr.second))
|
||||
reserved.emplace(name);
|
||||
}
|
||||
@ -142,8 +141,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
map<YulString, BuiltinFunctionForEVM> builtins;
|
||||
for (auto const& instr: evmasm::c_instructions)
|
||||
{
|
||||
string name = instr.first;
|
||||
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
|
||||
string name = toLower(instr.first);
|
||||
auto const opcode = instr.second;
|
||||
|
||||
if (
|
||||
|
@ -25,9 +25,13 @@
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/backends/evm/OptimizedEVMCodeTransform.h>
|
||||
|
||||
#include <libyul/optimiser/FunctionCallFinder.h>
|
||||
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
using namespace solidity::yul;
|
||||
using namespace std;
|
||||
|
||||
@ -46,7 +50,8 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
|
||||
for (auto const& subNode: _object.subObjects)
|
||||
if (auto* subObject = dynamic_cast<Object*>(subNode.get()))
|
||||
{
|
||||
auto subAssemblyAndID = m_assembly.createSubAssembly(subObject->name.str());
|
||||
bool isCreation = !boost::ends_with(subObject->name.str(), "_deployed");
|
||||
auto subAssemblyAndID = m_assembly.createSubAssembly(isCreation, subObject->name.str());
|
||||
context.subIDs[subObject->name] = subAssemblyAndID.second;
|
||||
subObject->subId = subAssemblyAndID.second;
|
||||
compile(*subObject, *subAssemblyAndID.first, m_dialect, _optimize);
|
||||
@ -74,7 +79,22 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
|
||||
OptimizedEVMCodeTransform::UseNamedLabels::ForFirstFunctionOfEachName
|
||||
);
|
||||
if (!stackErrors.empty())
|
||||
BOOST_THROW_EXCEPTION(stackErrors.front());
|
||||
{
|
||||
vector<FunctionCall*> memoryGuardCalls = FunctionCallFinder::run(
|
||||
*_object.code,
|
||||
"memoryguard"_yulstring
|
||||
);
|
||||
auto stackError = stackErrors.front();
|
||||
string msg = stackError.comment() ? *stackError.comment() : "";
|
||||
if (memoryGuardCalls.empty())
|
||||
msg += "\nNo memoryguard was present. "
|
||||
"Consider using memory-safe assembly only and annotating it via "
|
||||
"\"/// @solidity memory-safe-assembly\".";
|
||||
else
|
||||
msg += "\nmemoryguard was present.";
|
||||
stackError << util::errinfo_comment(msg);
|
||||
BOOST_THROW_EXCEPTION(stackError);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -122,9 +122,9 @@ void EthAssemblyAdapter::appendAssemblySize()
|
||||
m_assembly.appendProgramSize();
|
||||
}
|
||||
|
||||
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly(string _name)
|
||||
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> EthAssemblyAdapter::createSubAssembly(bool _creation, string _name)
|
||||
{
|
||||
shared_ptr<evmasm::Assembly> assembly{make_shared<evmasm::Assembly>(std::move(_name))};
|
||||
shared_ptr<evmasm::Assembly> assembly{make_shared<evmasm::Assembly>(_creation, std::move(_name))};
|
||||
auto sub = m_assembly.newSub(assembly);
|
||||
return {make_shared<EthAssemblyAdapter>(*assembly), static_cast<size_t>(sub.data())};
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public:
|
||||
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
|
||||
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||
void appendAssemblySize() override;
|
||||
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(std::string _name = {}) override;
|
||||
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = {}) override;
|
||||
void appendDataOffset(std::vector<SubID> const& _subPath) override;
|
||||
void appendDataSize(std::vector<SubID> const& _subPath) override;
|
||||
SubID appendData(bytes const& _data) override;
|
||||
|
@ -98,7 +98,7 @@ void NoOutputAssembly::appendAssemblySize()
|
||||
appendInstruction(evmasm::Instruction::PUSH1);
|
||||
}
|
||||
|
||||
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly(std::string)
|
||||
pair<shared_ptr<AbstractAssembly>, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly(bool, std::string)
|
||||
{
|
||||
yulAssert(false, "Sub assemblies not implemented.");
|
||||
return {};
|
||||
|
@ -65,7 +65,7 @@ public:
|
||||
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;
|
||||
|
||||
void appendAssemblySize() override;
|
||||
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(std::string _name = "") override;
|
||||
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = "") override;
|
||||
void appendDataOffset(std::vector<SubID> const& _subPath) override;
|
||||
void appendDataSize(std::vector<SubID> const& _subPath) override;
|
||||
SubID appendData(bytes const& _data) override;
|
||||
|
@ -21,15 +21,16 @@
|
||||
|
||||
#include <libyul/optimiser/SimplificationRules.h>
|
||||
|
||||
#include <libyul/optimiser/ASTCopier.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/optimiser/SyntacticalEquality.h>
|
||||
#include <libyul/optimiser/DataFlowAnalyzer.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/optimiser/ASTCopier.h>
|
||||
#include <libyul/optimiser/DataFlowAnalyzer.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/optimiser/SyntacticalEquality.h>
|
||||
|
||||
#include <libevmasm/RuleList.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -249,8 +250,7 @@ Expression Pattern::toExpression(shared_ptr<DebugData const> const& _debugData)
|
||||
for (auto const& arg: m_arguments)
|
||||
arguments.emplace_back(arg.toExpression(_debugData));
|
||||
|
||||
string name = instructionInfo(m_instruction).name;
|
||||
transform(begin(name), end(name), begin(name), [](auto _c) { return tolower(_c); });
|
||||
string name = util::toLower(instructionInfo(m_instruction).name);
|
||||
|
||||
return FunctionCall{_debugData,
|
||||
Identifier{_debugData, YulString{name}},
|
||||
|
@ -83,6 +83,8 @@ set(libsolidity_sources
|
||||
libsolidity/InlineAssembly.cpp
|
||||
libsolidity/LibSolc.cpp
|
||||
libsolidity/Metadata.cpp
|
||||
libsolidity/MemoryGuardTest.cpp
|
||||
libsolidity/MemoryGuardTest.h
|
||||
libsolidity/SemanticTest.cpp
|
||||
libsolidity/SemanticTest.h
|
||||
libsolidity/SemVerMatcher.cpp
|
||||
|
@ -29,7 +29,6 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::util::formatting;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
@ -43,10 +42,11 @@ namespace
|
||||
|
||||
int parseUnsignedInteger(string::iterator& _it, string::iterator _end)
|
||||
{
|
||||
if (_it == _end || !isdigit(*_it))
|
||||
auto isDigit = [](char _c) -> bool {return isdigit(_c, std::locale::classic());};
|
||||
if (_it == _end || !isDigit(*_it))
|
||||
BOOST_THROW_EXCEPTION(runtime_error("Invalid test expectation. Source location expected."));
|
||||
int result = 0;
|
||||
while (_it != _end && isdigit(*_it))
|
||||
while (_it != _end && isDigit(*_it))
|
||||
{
|
||||
result *= 10;
|
||||
result += *_it - '0';
|
||||
@ -195,6 +195,7 @@ string CommonSyntaxTest::errorMessage(Exception const& _e)
|
||||
|
||||
vector<SyntaxTestError> CommonSyntaxTest::parseExpectations(istream& _stream)
|
||||
{
|
||||
auto isDigit = [](char _c) -> bool {return isdigit(_c, std::locale::classic());};
|
||||
vector<SyntaxTestError> expectations;
|
||||
string line;
|
||||
while (getline(_stream, line))
|
||||
@ -207,14 +208,14 @@ vector<SyntaxTestError> CommonSyntaxTest::parseExpectations(istream& _stream)
|
||||
if (it == line.end()) continue;
|
||||
|
||||
auto typeBegin = it;
|
||||
while (it != line.end() && isalpha(*it))
|
||||
while (it != line.end() && isalpha(*it, locale::classic()))
|
||||
++it;
|
||||
string errorType(typeBegin, it);
|
||||
|
||||
skipWhitespace(it, line.end());
|
||||
|
||||
optional<ErrorId> errorId;
|
||||
if (it != line.end() && isdigit(*it))
|
||||
if (it != line.end() && isDigit(*it))
|
||||
errorId = ErrorId{static_cast<unsigned long long>(parseUnsignedInteger(it, line.end()))};
|
||||
|
||||
expect(it, line.end(), ':');
|
||||
@ -227,7 +228,7 @@ vector<SyntaxTestError> CommonSyntaxTest::parseExpectations(istream& _stream)
|
||||
if (it != line.end() && *it == '(')
|
||||
{
|
||||
++it;
|
||||
if (it != line.end() && !isdigit(*it))
|
||||
if (it != line.end() && !isDigit(*it))
|
||||
{
|
||||
auto sourceNameStart = it;
|
||||
while (it != line.end() && *it != ':')
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <test/libsolidity/ABIJsonTest.h>
|
||||
#include <test/libsolidity/ASTJSONTest.h>
|
||||
#include <test/libsolidity/GasTest.h>
|
||||
#include <test/libsolidity/MemoryGuardTest.h>
|
||||
#include <test/libsolidity/SyntaxTest.h>
|
||||
#include <test/libsolidity/SemanticTest.h>
|
||||
#include <test/libsolidity/SMTCheckerTest.h>
|
||||
@ -74,6 +75,7 @@ Testsuite const g_interactiveTestsuites[] = {
|
||||
{"JSON ABI", "libsolidity", "ABIJson", false, false, &ABIJsonTest::create},
|
||||
{"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SMTCheckerTest::create},
|
||||
{"Gas Estimates", "libsolidity", "gasTests", false, false, &GasTest::create},
|
||||
{"Memory Guard Tests", "libsolidity", "memoryGuardTests", false, false, &MemoryGuardTest::create},
|
||||
{"Ewasm Translation", "libyul", "ewasmTranslationTests", false, false, &yul::test::EwasmTranslationTest::create}
|
||||
};
|
||||
|
||||
|
@ -118,7 +118,7 @@ EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(string const& _filena
|
||||
string comparator;
|
||||
size_t versionBegin = 0;
|
||||
for (auto character: versionString)
|
||||
if (!isalpha(character))
|
||||
if (!isalpha(character, locale::classic()))
|
||||
{
|
||||
comparator += character;
|
||||
versionBegin++;
|
||||
|
@ -90,7 +90,7 @@ protected:
|
||||
template<typename IteratorType>
|
||||
static void skipWhitespace(IteratorType& _it, IteratorType _end)
|
||||
{
|
||||
while (_it != _end && isspace(*_it))
|
||||
while (_it != _end && std::isspace<char>(*_it, std::locale::classic()))
|
||||
++_it;
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,15 @@ object "C_12" {
|
||||
code {
|
||||
{
|
||||
/// @src 0:61:418 "contract C {..."
|
||||
mstore(64, 128)
|
||||
let _1 := memoryguard(0x80)
|
||||
mstore(64, _1)
|
||||
if callvalue() { revert(0, 0) }
|
||||
/// @src 0:103:238 "assembly {..."
|
||||
sstore(0, shl(180, 1))
|
||||
/// @src 0:61:418 "contract C {..."
|
||||
let _1 := datasize("C_12_deployed")
|
||||
codecopy(128, dataoffset("C_12_deployed"), _1)
|
||||
return(128, _1)
|
||||
let _2 := datasize("C_12_deployed")
|
||||
codecopy(_1, dataoffset("C_12_deployed"), _2)
|
||||
return(_1, _2)
|
||||
}
|
||||
}
|
||||
/// @use-src 0:"constant_optimizer_yul/input.sol"
|
||||
@ -26,7 +27,7 @@ object "C_12" {
|
||||
code {
|
||||
{
|
||||
/// @src 0:61:418 "contract C {..."
|
||||
mstore(64, 128)
|
||||
mstore(64, memoryguard(0x80))
|
||||
if callvalue() { revert(0, 0) }
|
||||
/// @src 0:279:410 "assembly {..."
|
||||
sstore(0, 0x1000000000000000000000000000000000000000000000)
|
||||
|
@ -3,6 +3,6 @@ pragma solidity >=0.0.0;
|
||||
pragma abicoder v2;
|
||||
|
||||
contract D {
|
||||
constructor() { assembly {}}
|
||||
constructor() { assembly { mstore(0,0) } }
|
||||
function f() public pure {}
|
||||
}
|
||||
|
@ -10,9 +10,12 @@ Optimized IR:
|
||||
object "D_12" {
|
||||
code {
|
||||
{
|
||||
/// @src 0:82:161 "contract D {..."
|
||||
/// @src 0:82:175 "contract D {..."
|
||||
mstore(64, 128)
|
||||
if callvalue() { revert(0, 0) }
|
||||
/// @src 0:115:139 "assembly { mstore(0,0) }"
|
||||
mstore(0, 0)
|
||||
/// @src 0:82:175 "contract D {..."
|
||||
let _1 := datasize("D_12_deployed")
|
||||
codecopy(128, dataoffset("D_12_deployed"), _1)
|
||||
return(128, _1)
|
||||
@ -22,7 +25,7 @@ object "D_12" {
|
||||
object "D_12_deployed" {
|
||||
code {
|
||||
{
|
||||
/// @src 0:82:161 "contract D {..."
|
||||
/// @src 0:82:175 "contract D {..."
|
||||
let _1 := memoryguard(0x80)
|
||||
mstore(64, _1)
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
|
@ -4,6 +4,6 @@ pragma abicoder v2;
|
||||
|
||||
contract D {
|
||||
function f() public pure {
|
||||
assembly {}
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ Optimized IR:
|
||||
object "D_8" {
|
||||
code {
|
||||
{
|
||||
/// @src 0:82:153 "contract D {..."
|
||||
/// @src 0:82:166 "contract D {..."
|
||||
let _1 := memoryguard(0x80)
|
||||
mstore(64, _1)
|
||||
if callvalue() { revert(0, 0) }
|
||||
@ -23,7 +23,7 @@ object "D_8" {
|
||||
object "D_8_deployed" {
|
||||
code {
|
||||
{
|
||||
/// @src 0:82:153 "contract D {..."
|
||||
/// @src 0:82:166 "contract D {..."
|
||||
mstore(64, 128)
|
||||
if iszero(lt(calldatasize(), 4))
|
||||
{
|
||||
@ -32,6 +32,8 @@ object "D_8" {
|
||||
{
|
||||
if callvalue() { revert(_1, _1) }
|
||||
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
|
||||
/// @src 0:134:158 "assembly { mstore(0,0) }"
|
||||
mstore(/** @src 0:82:166 "contract D {..." */ _1, _1)
|
||||
return(128, _1)
|
||||
}
|
||||
}
|
||||
|
1
test/cmdlineTests/viair_subobject_optimization/args
Normal file
1
test/cmdlineTests/viair_subobject_optimization/args
Normal file
@ -0,0 +1 @@
|
||||
--experimental-via-ir --optimize --asm
|
20
test/cmdlineTests/viair_subobject_optimization/input.sol
Normal file
20
test/cmdlineTests/viair_subobject_optimization/input.sol
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.0.0;
|
||||
|
||||
contract C {
|
||||
constructor(uint x) {
|
||||
// In earlier versions of the compiler, the resulting assembly pushed the constant
|
||||
// 0xFFFFFFFFFFFFFFFF42 directly in the subassembly of D, while it was optimized to
|
||||
// ``sub(shl(0x48, 0x01), 0xbe)`` when C was compiled in isolation.
|
||||
// Now the assembly is expected to contain two instances of ``sub(shl(0x48, 0x01), 0xbe)``,
|
||||
// one in the creation code of ``C`` directly, one in a subassembly of ``D``.
|
||||
// The constant 0xFFFFFFFFFFFFFFFF42 should not occur in the assembly output at all.
|
||||
if (x == 0xFFFFFFFFFFFFFFFF42)
|
||||
revert();
|
||||
}
|
||||
}
|
||||
contract D {
|
||||
function f() public pure returns (bytes memory) {
|
||||
return type(C).creationCode;
|
||||
}
|
||||
}
|
379
test/cmdlineTests/viair_subobject_optimization/output
Normal file
379
test/cmdlineTests/viair_subobject_optimization/output
Normal file
@ -0,0 +1,379 @@
|
||||
|
||||
======= viair_subobject_optimization/input.sol:C =======
|
||||
EVM assembly:
|
||||
/* "viair_subobject_optimization/input.sol":61:668 contract C {... */
|
||||
0x80
|
||||
dup1
|
||||
0x40
|
||||
mstore
|
||||
jumpi(tag_6, callvalue)
|
||||
0x1f
|
||||
bytecodeSize
|
||||
codesize
|
||||
dup2
|
||||
swap1
|
||||
sub
|
||||
swap2
|
||||
dup3
|
||||
add
|
||||
not(0x1f)
|
||||
and
|
||||
dup4
|
||||
add
|
||||
swap2
|
||||
sub(shl(0x40, 0x01), 0x01)
|
||||
dup4
|
||||
gt
|
||||
dup5
|
||||
dup5
|
||||
lt
|
||||
or
|
||||
tag_4
|
||||
jumpi
|
||||
dup1
|
||||
dup5
|
||||
swap3
|
||||
0x20
|
||||
swap5
|
||||
0x40
|
||||
mstore
|
||||
dup4
|
||||
codecopy
|
||||
dup2
|
||||
add
|
||||
sub
|
||||
slt
|
||||
tag_6
|
||||
jumpi
|
||||
tag_8
|
||||
swap1
|
||||
mload
|
||||
tag_1
|
||||
jump // in
|
||||
tag_8:
|
||||
mload(0x40)
|
||||
dataSize(sub_0)
|
||||
swap1
|
||||
dup2
|
||||
dataOffset(sub_0)
|
||||
dup3
|
||||
codecopy
|
||||
return
|
||||
tag_6:
|
||||
pop
|
||||
0x00
|
||||
dup1
|
||||
revert
|
||||
tag_4:
|
||||
pop
|
||||
pop
|
||||
pop
|
||||
pop
|
||||
mstore(0x00, shl(0xe0, 0x4e487b71))
|
||||
mstore(0x04, 0x41)
|
||||
revert(0x00, 0x24)
|
||||
/* "viair_subobject_optimization/input.sol":76:666 constructor(uint x) {... */
|
||||
tag_1:
|
||||
sub(shl(0x48, 0x01), 0xbe)
|
||||
/* "viair_subobject_optimization/input.sol":620:645 x == 0xFFFFFFFFFFFFFFFF42 */
|
||||
eq
|
||||
/* "viair_subobject_optimization/input.sol":616:661 if (x == 0xFFFFFFFFFFFFFFFF42)... */
|
||||
tag_6
|
||||
jumpi
|
||||
/* "viair_subobject_optimization/input.sol":76:666 constructor(uint x) {... */
|
||||
jump // out
|
||||
stop
|
||||
|
||||
sub_0: assembly {
|
||||
/* "viair_subobject_optimization/input.sol":61:668 contract C {... */
|
||||
mstore(0x40, 0x80)
|
||||
0x00
|
||||
dup1
|
||||
revert
|
||||
|
||||
auxdata: <AUXDATA REMOVED>
|
||||
}
|
||||
|
||||
|
||||
======= viair_subobject_optimization/input.sol:D =======
|
||||
EVM assembly:
|
||||
/* "viair_subobject_optimization/input.sol":669:772 contract D {... */
|
||||
0x80
|
||||
dup1
|
||||
0x40
|
||||
mstore
|
||||
jumpi(tag_1, callvalue)
|
||||
dataSize(sub_0)
|
||||
swap1
|
||||
dup2
|
||||
dataOffset(sub_0)
|
||||
dup3
|
||||
codecopy
|
||||
return
|
||||
tag_1:
|
||||
pop
|
||||
0x00
|
||||
dup1
|
||||
revert
|
||||
stop
|
||||
|
||||
sub_0: assembly {
|
||||
/* "viair_subobject_optimization/input.sol":669:772 contract D {... */
|
||||
0x80
|
||||
dup1
|
||||
0x40
|
||||
mstore
|
||||
jumpi(tag_2, iszero(lt(calldatasize, 0x04)))
|
||||
tag_3:
|
||||
pop
|
||||
0x00
|
||||
dup1
|
||||
revert
|
||||
tag_2:
|
||||
0x00
|
||||
swap1
|
||||
dup2
|
||||
calldataload
|
||||
0xe0
|
||||
shr
|
||||
0x26121ff0
|
||||
eq
|
||||
tag_4
|
||||
jumpi
|
||||
pop
|
||||
jump(tag_3)
|
||||
tag_4:
|
||||
jumpi(tag_8, callvalue)
|
||||
dup2
|
||||
add(calldatasize, not(0x03))
|
||||
slt
|
||||
tag_8
|
||||
jumpi
|
||||
/* "viair_subobject_optimization/input.sol":745:765 type(C).creationCode */
|
||||
dataSize(sub_0)
|
||||
/* "viair_subobject_optimization/input.sol":669:772 contract D {... */
|
||||
0x3f
|
||||
dup2
|
||||
add
|
||||
not(0x1f)
|
||||
and
|
||||
dup3
|
||||
add
|
||||
0xffffffffffffffff
|
||||
dup2
|
||||
gt
|
||||
dup4
|
||||
dup3
|
||||
lt
|
||||
or
|
||||
tag_10
|
||||
jumpi
|
||||
tag_12
|
||||
swap4
|
||||
pop
|
||||
0x40
|
||||
mstore
|
||||
/* "viair_subobject_optimization/input.sol":745:765 type(C).creationCode */
|
||||
dup1
|
||||
dup3
|
||||
mstore
|
||||
dataOffset(sub_0)
|
||||
0x20
|
||||
dup4
|
||||
add
|
||||
codecopy
|
||||
/* "viair_subobject_optimization/input.sol":669:772 contract D {... */
|
||||
mload(0x40)
|
||||
swap2
|
||||
dup3
|
||||
swap2
|
||||
dup3
|
||||
tag_1
|
||||
jump // in
|
||||
tag_12:
|
||||
sub
|
||||
swap1
|
||||
return
|
||||
tag_10:
|
||||
pop
|
||||
pop
|
||||
shl(0xe0, 0x4e487b71)
|
||||
dup3
|
||||
mstore
|
||||
pop
|
||||
mstore(0x04, 0x41)
|
||||
0x24
|
||||
swap1
|
||||
revert
|
||||
tag_8:
|
||||
pop
|
||||
dup1
|
||||
revert
|
||||
tag_1:
|
||||
swap2
|
||||
swap1
|
||||
swap2
|
||||
0x20
|
||||
dup1
|
||||
dup3
|
||||
mstore
|
||||
dup4
|
||||
mload
|
||||
swap1
|
||||
dup2
|
||||
dup2
|
||||
dup5
|
||||
add
|
||||
mstore
|
||||
0x00
|
||||
swap5
|
||||
tag_13:
|
||||
dup3
|
||||
dup7
|
||||
lt
|
||||
tag_14
|
||||
jumpi
|
||||
pop
|
||||
pop
|
||||
dup1
|
||||
0x40
|
||||
swap4
|
||||
swap5
|
||||
gt
|
||||
tag_16
|
||||
jumpi
|
||||
tag_17:
|
||||
0x1f
|
||||
add
|
||||
not(0x1f)
|
||||
and
|
||||
add
|
||||
add
|
||||
swap1
|
||||
jump // out
|
||||
tag_16:
|
||||
0x00
|
||||
dup4
|
||||
dup3
|
||||
dup5
|
||||
add
|
||||
add
|
||||
mstore
|
||||
jump(tag_17)
|
||||
tag_14:
|
||||
dup6
|
||||
dup2
|
||||
add
|
||||
dup3
|
||||
add
|
||||
mload
|
||||
dup5
|
||||
dup8
|
||||
add
|
||||
0x40
|
||||
add
|
||||
mstore
|
||||
swap5
|
||||
dup2
|
||||
add
|
||||
swap5
|
||||
jump(tag_13)
|
||||
stop
|
||||
|
||||
sub_0: assembly {
|
||||
/* "viair_subobject_optimization/input.sol":61:668 contract C {... */
|
||||
0x80
|
||||
dup1
|
||||
0x40
|
||||
mstore
|
||||
jumpi(tag_6, callvalue)
|
||||
0x1f
|
||||
bytecodeSize
|
||||
codesize
|
||||
dup2
|
||||
swap1
|
||||
sub
|
||||
swap2
|
||||
dup3
|
||||
add
|
||||
not(0x1f)
|
||||
and
|
||||
dup4
|
||||
add
|
||||
swap2
|
||||
sub(shl(0x40, 0x01), 0x01)
|
||||
dup4
|
||||
gt
|
||||
dup5
|
||||
dup5
|
||||
lt
|
||||
or
|
||||
tag_4
|
||||
jumpi
|
||||
dup1
|
||||
dup5
|
||||
swap3
|
||||
0x20
|
||||
swap5
|
||||
0x40
|
||||
mstore
|
||||
dup4
|
||||
codecopy
|
||||
dup2
|
||||
add
|
||||
sub
|
||||
slt
|
||||
tag_6
|
||||
jumpi
|
||||
tag_8
|
||||
swap1
|
||||
mload
|
||||
tag_1
|
||||
jump // in
|
||||
tag_8:
|
||||
mload(0x40)
|
||||
dataSize(sub_0)
|
||||
swap1
|
||||
dup2
|
||||
dataOffset(sub_0)
|
||||
dup3
|
||||
codecopy
|
||||
return
|
||||
tag_6:
|
||||
pop
|
||||
0x00
|
||||
dup1
|
||||
revert
|
||||
tag_4:
|
||||
pop
|
||||
pop
|
||||
pop
|
||||
pop
|
||||
mstore(0x00, shl(0xe0, 0x4e487b71))
|
||||
mstore(0x04, 0x41)
|
||||
revert(0x00, 0x24)
|
||||
/* "viair_subobject_optimization/input.sol":76:666 constructor(uint x) {... */
|
||||
tag_1:
|
||||
sub(shl(0x48, 0x01), 0xbe)
|
||||
/* "viair_subobject_optimization/input.sol":620:645 x == 0xFFFFFFFFFFFFFFFF42 */
|
||||
eq
|
||||
/* "viair_subobject_optimization/input.sol":616:661 if (x == 0xFFFFFFFFFFFFFFFF42)... */
|
||||
tag_6
|
||||
jumpi
|
||||
/* "viair_subobject_optimization/input.sol":76:666 constructor(uint x) {... */
|
||||
jump // out
|
||||
stop
|
||||
|
||||
sub_0: assembly {
|
||||
/* "viair_subobject_optimization/input.sol":61:668 contract C {... */
|
||||
mstore(0x40, 0x80)
|
||||
0x00
|
||||
dup1
|
||||
revert
|
||||
|
||||
auxdata: <AUXDATA REMOVED>
|
||||
}
|
||||
}
|
||||
|
||||
auxdata: <AUXDATA REMOVED>
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
object "RunsTest1" {
|
||||
code {
|
||||
// Deploy the contract
|
||||
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
|
||||
return(0, datasize("Runtime"))
|
||||
datacopy(0, dataoffset("Runtime_deployed"), datasize("Runtime_deployed"))
|
||||
return(0, datasize("Runtime_deployed"))
|
||||
}
|
||||
object "Runtime" {
|
||||
object "Runtime_deployed" {
|
||||
code {
|
||||
let funcSel := shl(224, 0xabc12345)
|
||||
sstore(0, funcSel)
|
||||
|
@ -5,12 +5,12 @@ Pretty printed source:
|
||||
object "RunsTest1" {
|
||||
code {
|
||||
{
|
||||
let _1 := datasize("Runtime")
|
||||
datacopy(0, dataoffset("Runtime"), _1)
|
||||
let _1 := datasize("Runtime_deployed")
|
||||
datacopy(0, dataoffset("Runtime_deployed"), _1)
|
||||
return(0, _1)
|
||||
}
|
||||
}
|
||||
object "Runtime" {
|
||||
object "Runtime_deployed" {
|
||||
code {
|
||||
{
|
||||
sstore(0, 0xabc1234500000000000000000000000000000000000000000000000000000000)
|
||||
@ -24,28 +24,28 @@ Binary representation:
|
||||
602580600c6000396000f3fe7fabc123450000000000000000000000000000000000000000000000000000000060005500
|
||||
|
||||
Text representation:
|
||||
/* "yul_optimize_runs/input.yul":106:125 */
|
||||
/* "yul_optimize_runs/input.yul":115:143 */
|
||||
dataSize(sub_0)
|
||||
/* "yul_optimize_runs/input.yul":83:104 */
|
||||
/* "yul_optimize_runs/input.yul":83:113 */
|
||||
dup1
|
||||
dataOffset(sub_0)
|
||||
/* "yul_optimize_runs/input.yul":80:81 */
|
||||
0x00
|
||||
/* "yul_optimize_runs/input.yul":71:126 */
|
||||
/* "yul_optimize_runs/input.yul":71:144 */
|
||||
codecopy
|
||||
/* "yul_optimize_runs/input.yul":80:81 */
|
||||
0x00
|
||||
/* "yul_optimize_runs/input.yul":135:165 */
|
||||
/* "yul_optimize_runs/input.yul":153:192 */
|
||||
return
|
||||
stop
|
||||
|
||||
sub_0: assembly {
|
||||
/* "yul_optimize_runs/input.yul":237:257 */
|
||||
/* "yul_optimize_runs/input.yul":273:293 */
|
||||
0xabc1234500000000000000000000000000000000000000000000000000000000
|
||||
/* "yul_optimize_runs/input.yul":277:278 */
|
||||
/* "yul_optimize_runs/input.yul":313:314 */
|
||||
0x00
|
||||
/* "yul_optimize_runs/input.yul":270:288 */
|
||||
/* "yul_optimize_runs/input.yul":306:324 */
|
||||
sstore
|
||||
/* "yul_optimize_runs/input.yul":208:298 */
|
||||
/* "yul_optimize_runs/input.yul":244:334 */
|
||||
stop
|
||||
}
|
||||
|
@ -89,6 +89,12 @@ function elementfi_test
|
||||
# TODO: Remove when https://github.com/element-fi/elf-contracts/issues/243 is fixed.
|
||||
sed -i 's|^\s*require(_expiration - block\.timestamp < _unitSeconds);\s*$||g' contracts/ConvergentCurvePool.sol
|
||||
|
||||
# This test file is very flaky. There's one particular cases that fails randomly (see
|
||||
# https://github.com/element-fi/elf-contracts/issues/240) but some others also depends on an external
|
||||
# service which makes tests time out when that service is down.
|
||||
# "ProviderError: Too Many Requests error received from eth-mainnet.alchemyapi.io"
|
||||
rm test/mockERC20YearnVaultTest.ts
|
||||
|
||||
# Several tests fail unless we use the exact versions hard-coded in package-lock.json
|
||||
#neutralize_package_lock
|
||||
|
||||
|
@ -48,7 +48,7 @@ function ens_test
|
||||
"${compile_only_presets[@]}"
|
||||
#ir-no-optimize # Compilation fails with "YulException: Variable var__945 is 1 slot(s) too deep inside the stack."
|
||||
#ir-optimize-evm-only # Compilation fails with "YulException: Variable var__945 is 1 slot(s) too deep inside the stack."
|
||||
#ir-optimize-evm+yul # Compilation fails with "YulException: Variable _5 is 1 too deep in the stack [ _5 usr$i usr$h _7 usr$scratch usr$k usr$f _4 usr$len usr$j_2 RET _2 _1 var_data_mpos usr$totallen usr$x _12 ]"
|
||||
ir-optimize-evm+yul # Needs memory-safe inline assembly patch
|
||||
legacy-optimize-evm-only
|
||||
legacy-optimize-evm+yul
|
||||
)
|
||||
@ -68,6 +68,8 @@ function ens_test
|
||||
replace_version_pragmas
|
||||
neutralize_packaged_contracts
|
||||
|
||||
find . -name "*.sol" -exec sed -i -e 's/^\(\s*\)\(assembly\)/\1\/\/\/ @solidity memory-safe-assembly\n\1\2/' '{}' \;
|
||||
|
||||
for preset in $SELECTED_PRESETS; do
|
||||
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn
|
||||
store_benchmark_report hardhat ens "$repo" "$preset"
|
||||
|
@ -56,7 +56,7 @@ function trident_test
|
||||
"${compile_only_presets[@]}"
|
||||
#ir-no-optimize # Compilation fails with: "YulException: Variable var_amount_165 is 9 slot(s) too deep inside the stack."
|
||||
#ir-optimize-evm-only # Compilation fails with: "YulException: Variable var_amount_165 is 9 slot(s) too deep inside the stack."
|
||||
#ir-optimize-evm+yul # Compilation fails with: "YulException: Cannot swap Variable var_nearestTick with Variable _4: too deep in the stack by 4 slots"
|
||||
ir-optimize-evm+yul # Needs memory-safe inline assembly patch
|
||||
legacy-no-optimize
|
||||
legacy-optimize-evm-only
|
||||
legacy-optimize-evm+yul
|
||||
@ -87,6 +87,7 @@ function trident_test
|
||||
sed -i 's|uint32(-1)|type(uint32).max|g' contracts/flat/BentoBoxV1Flat.sol
|
||||
sed -i 's|IERC20(0)|IERC20(address(0))|g' contracts/flat/BentoBoxV1Flat.sol
|
||||
sed -i 's|IStrategy(0)|IStrategy(address(0))|g' contracts/flat/BentoBoxV1Flat.sol
|
||||
find contracts -name "*.sol" -exec sed -i -e 's/^\(\s*\)\(assembly\)/\1\/\/\/ @solidity memory-safe-assembly\n\1\2/' '{}' \;
|
||||
|
||||
# @sushiswap/core package contains contracts that get built with 0.6.12 and fail our compiler
|
||||
# version check. It's not used by tests so we can remove it.
|
||||
|
@ -58,11 +58,11 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
|
||||
{ "root.asm", 0 },
|
||||
{ "sub.asm", 1 }
|
||||
};
|
||||
Assembly _assembly;
|
||||
Assembly _assembly{false, {}};
|
||||
auto root_asm = make_shared<string>("root.asm");
|
||||
_assembly.setSourceLocation({1, 3, root_asm});
|
||||
|
||||
Assembly _subAsm;
|
||||
Assembly _subAsm{false, {}};
|
||||
auto sub_asm = make_shared<string>("sub.asm");
|
||||
_subAsm.setSourceLocation({6, 8, sub_asm});
|
||||
// PushImmutable
|
||||
@ -197,7 +197,7 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps)
|
||||
{ *subName, 1 }
|
||||
};
|
||||
|
||||
auto subAsm = make_shared<Assembly>();
|
||||
auto subAsm = make_shared<Assembly>(false, string{});
|
||||
for (char i = 0; i < numImmutables; ++i)
|
||||
{
|
||||
for (int r = 0; r < numActualRefs; ++r)
|
||||
@ -207,7 +207,7 @@ BOOST_AUTO_TEST_CASE(immutables_and_its_source_maps)
|
||||
}
|
||||
}
|
||||
|
||||
Assembly assembly;
|
||||
Assembly assembly{true, {}};
|
||||
for (char i = 1; i <= numImmutables; ++i)
|
||||
{
|
||||
assembly.setSourceLocation({10*i, 10*i + 3+i, assemblyName});
|
||||
@ -256,11 +256,11 @@ BOOST_AUTO_TEST_CASE(immutable)
|
||||
{ "root.asm", 0 },
|
||||
{ "sub.asm", 1 }
|
||||
};
|
||||
Assembly _assembly;
|
||||
Assembly _assembly{true, {}};
|
||||
auto root_asm = make_shared<string>("root.asm");
|
||||
_assembly.setSourceLocation({1, 3, root_asm});
|
||||
|
||||
Assembly _subAsm;
|
||||
Assembly _subAsm{false, {}};
|
||||
auto sub_asm = make_shared<string>("sub.asm");
|
||||
_subAsm.setSourceLocation({6, 8, sub_asm});
|
||||
_subAsm.appendImmutable("someImmutable");
|
||||
@ -349,10 +349,10 @@ BOOST_AUTO_TEST_CASE(immutable)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(subobject_encode_decode)
|
||||
{
|
||||
Assembly assembly;
|
||||
Assembly assembly{true, {}};
|
||||
|
||||
shared_ptr<Assembly> subAsmPtr = make_shared<Assembly>();
|
||||
shared_ptr<Assembly> subSubAsmPtr = make_shared<Assembly>();
|
||||
shared_ptr<Assembly> subAsmPtr = make_shared<Assembly>(false, string{});
|
||||
shared_ptr<Assembly> subSubAsmPtr = make_shared<Assembly>(false, string{});
|
||||
|
||||
assembly.appendSubroutine(subAsmPtr);
|
||||
subAsmPtr->appendSubroutine(subSubAsmPtr);
|
||||
|
@ -1250,8 +1250,8 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies)
|
||||
// tag unifications (due to block deduplication) is also
|
||||
// visible at the super-assembly.
|
||||
|
||||
Assembly main;
|
||||
AssemblyPointer sub = make_shared<Assembly>();
|
||||
Assembly main{false, {}};
|
||||
AssemblyPointer sub = make_shared<Assembly>(true, string{});
|
||||
|
||||
sub->append(u256(1));
|
||||
auto t1 = sub->newTag();
|
||||
@ -1278,7 +1278,6 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies)
|
||||
main.append(u256(8));
|
||||
|
||||
Assembly::OptimiserSettings settings;
|
||||
settings.isCreation = false;
|
||||
settings.runInliner = false;
|
||||
settings.runJumpdestRemover = true;
|
||||
settings.runPeephole = true;
|
||||
@ -1287,7 +1286,7 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies)
|
||||
settings.runConstantOptimiser = true;
|
||||
settings.evmVersion = solidity::test::CommonOptions::get().evmVersion();
|
||||
settings.expectedExecutionsPerDeployment = OptimiserSettings{}.expectedExecutionsPerDeployment;
|
||||
;
|
||||
|
||||
main.optimise(settings);
|
||||
|
||||
AssemblyItems expectationMain{
|
||||
|
78
test/libsolidity/MemoryGuardTest.cpp
Normal file
78
test/libsolidity/MemoryGuardTest.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 <test/libsolidity/MemoryGuardTest.h>
|
||||
|
||||
#include <test/libyul/Common.h>
|
||||
#include <libsolidity/codegen/ir/Common.h>
|
||||
#include <libsolutil/Algorithms.h>
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/optimiser/FunctionCallFinder.h>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::util::formatting;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
using namespace solidity::frontend::test;
|
||||
using namespace yul;
|
||||
|
||||
TestCase::TestResult MemoryGuardTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
|
||||
{
|
||||
compiler().reset();
|
||||
compiler().setSources(StringMap{{"", m_source}});
|
||||
compiler().setViaIR(true);
|
||||
compiler().setOptimiserSettings(OptimiserSettings::none());
|
||||
if (!compiler().compile())
|
||||
return TestResult::FatalError;
|
||||
|
||||
m_obtainedResult.clear();
|
||||
for (string contractName: compiler().contractNames())
|
||||
{
|
||||
ErrorList errors;
|
||||
auto [object, analysisInfo] = yul::test::parse(
|
||||
compiler().yulIR(contractName),
|
||||
EVMDialect::strictAssemblyForEVMObjects({}),
|
||||
errors
|
||||
);
|
||||
|
||||
if (!object || !analysisInfo || Error::containsErrors(errors))
|
||||
{
|
||||
AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing IR." << endl;
|
||||
return TestResult::FatalError;
|
||||
}
|
||||
|
||||
auto handleObject = [&](std::string const& _kind, Object const& _object) {
|
||||
m_obtainedResult += contractName + "(" + _kind + ") " + (FunctionCallFinder::run(
|
||||
*_object.code,
|
||||
"memoryguard"_yulstring
|
||||
).empty() ? "false" : "true") + "\n";
|
||||
};
|
||||
handleObject("creation", *object);
|
||||
size_t deployedIndex = object->subIndexByName.at(
|
||||
YulString(IRNames::deployedObject(compiler().contractDefinition(contractName)))
|
||||
);
|
||||
handleObject("runtime", dynamic_cast<Object const&>(*object->subObjects[deployedIndex]));
|
||||
}
|
||||
return checkResult(_stream, _linePrefix, _formatted);
|
||||
}
|
53
test/libsolidity/MemoryGuardTest.h
Normal file
53
test/libsolidity/MemoryGuardTest.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 <test/libsolidity/AnalysisFramework.h>
|
||||
#include <test/TestCase.h>
|
||||
#include <test/CommonSyntaxTest.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libsolutil/AnsiColorized.h>
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace solidity::frontend::test
|
||||
{
|
||||
|
||||
using solidity::test::SyntaxTestError;
|
||||
|
||||
class MemoryGuardTest: public AnalysisFramework, public TestCase
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<TestCase> create(Config const& _config)
|
||||
{
|
||||
return std::make_unique<MemoryGuardTest>(_config.filename);
|
||||
}
|
||||
MemoryGuardTest(std::string const& _filename): TestCase(_filename)
|
||||
{
|
||||
m_source = m_reader.source();
|
||||
m_expectation = m_reader.simpleExpectations();
|
||||
}
|
||||
|
||||
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
|
||||
};
|
||||
|
||||
}
|
@ -84,6 +84,34 @@ struct SolidityEndToEndTestExecutionFramework: public SolidityExecutionFramework
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(SolidityEndToEndTest, SolidityEndToEndTestExecutionFramework)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(creation_code_optimizer)
|
||||
{
|
||||
string codeC = R"(
|
||||
contract C {
|
||||
constructor(uint x) {
|
||||
if (x == 0xFFFFFFFFFFFFFFFF42)
|
||||
revert();
|
||||
}
|
||||
}
|
||||
)";
|
||||
string codeD = R"(
|
||||
contract D {
|
||||
function f() public pure returns (bytes memory) {
|
||||
return type(C).creationCode;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
m_metadataHash = CompilerStack::MetadataHash::None;
|
||||
ALSO_VIA_YUL({
|
||||
bytes bytecodeC = compileContract(codeC);
|
||||
reset();
|
||||
compileAndRun(codeC + codeD);
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(0x20, bytecodeC.size()) + encode(bytecodeC, false));
|
||||
m_doEwasmTestrun = false;
|
||||
})
|
||||
}
|
||||
|
||||
unsigned constexpr roundTo32(unsigned _num)
|
||||
{
|
||||
return (_num + 31) / 32 * 32;
|
||||
|
@ -62,6 +62,7 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
|
||||
m_compiler.enableEvmBytecodeGeneration(!m_compileViaYul);
|
||||
m_compiler.enableIRGeneration(m_compileViaYul);
|
||||
m_compiler.setRevertStringBehaviour(m_revertStrings);
|
||||
m_compiler.setMetadataHash(m_metadataHash);
|
||||
if (!m_compiler.compile())
|
||||
{
|
||||
// The testing framework expects an exception for
|
||||
|
@ -75,11 +75,12 @@ public:
|
||||
/// the latter only if it is forced.
|
||||
static std::string addPreamble(std::string const& _sourceCode);
|
||||
protected:
|
||||
|
||||
solidity::frontend::CompilerStack m_compiler;
|
||||
using CompilerStack = solidity::frontend::CompilerStack;
|
||||
CompilerStack m_compiler;
|
||||
bool m_compileViaYul = false;
|
||||
bool m_compileToEwasm = false;
|
||||
bool m_showMetadata = false;
|
||||
CompilerStack::MetadataHash m_metadataHash = CompilerStack::MetadataHash::IPFS;
|
||||
RevertStrings m_revertStrings = RevertStrings::Default;
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
contract C {
|
||||
constructor() {
|
||||
uint256 x;
|
||||
assembly { x := 0 }
|
||||
f();
|
||||
}
|
||||
function f() internal pure {
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
function g() public pure {
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
@ -0,0 +1,17 @@
|
||||
contract C {
|
||||
constructor() {
|
||||
uint256 x;
|
||||
assembly { x := 0 }
|
||||
f();
|
||||
}
|
||||
function f() internal pure {
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
function g() public pure {
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) false
|
||||
// :C(runtime) true
|
29
test/libsolidity/memoryGuardTests/comment/free_function.sol
Normal file
29
test/libsolidity/memoryGuardTests/comment/free_function.sol
Normal file
@ -0,0 +1,29 @@
|
||||
function safe() pure returns (uint256 x) {
|
||||
assembly { x := 42 }
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
function unsafe() pure returns (uint256 x) {
|
||||
assembly { pop(mload(0)) }
|
||||
}
|
||||
contract C {
|
||||
constructor() {
|
||||
unsafe();
|
||||
}
|
||||
function f() public pure {
|
||||
safe();
|
||||
}
|
||||
}
|
||||
contract D {
|
||||
constructor() {
|
||||
safe();
|
||||
}
|
||||
function f() public pure {
|
||||
unsafe();
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) false
|
||||
// :C(runtime) true
|
||||
// :D(creation) true
|
||||
// :D(runtime) false
|
@ -0,0 +1,17 @@
|
||||
contract C {
|
||||
constructor() {
|
||||
/// @solidity memory-safe-assembly a memory-safe-assembly
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
function f() internal pure {
|
||||
/// @solidity a memory-safe-assembly
|
||||
assembly { mstore(0, 0) }
|
||||
/// @solidity a
|
||||
/// memory-safe-assembly
|
||||
/// b
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) true
|
@ -0,0 +1,25 @@
|
||||
contract C {
|
||||
constructor(uint256 x) {
|
||||
assembly { x := 4 }
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
function f() public pure {
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
}
|
||||
contract D {
|
||||
constructor() {
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
function f(uint256 x) public pure {
|
||||
assembly { x := 4 }
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
||||
// :D(creation) false
|
||||
// :D(runtime) true
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f() external pure {
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {}
|
||||
assembly {}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) true
|
@ -0,0 +1,10 @@
|
||||
contract C {
|
||||
function f() external pure {
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {}
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
@ -0,0 +1,17 @@
|
||||
contract C {
|
||||
constructor() {
|
||||
uint256 x;
|
||||
assembly { x := 0 }
|
||||
f();
|
||||
}
|
||||
function f() internal pure {
|
||||
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||
assembly ("memory-safe") { mstore(0, 0) }
|
||||
}
|
||||
function g() public pure {
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
@ -0,0 +1,16 @@
|
||||
contract C {
|
||||
constructor() {
|
||||
uint256 x;
|
||||
assembly { x := 0 }
|
||||
f();
|
||||
}
|
||||
function f() internal pure {
|
||||
assembly { mstore(0, 0) }
|
||||
}
|
||||
function g() public pure {
|
||||
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) false
|
||||
// :C(runtime) true
|
@ -0,0 +1,28 @@
|
||||
function safe() pure returns (uint256 x) {
|
||||
assembly { x := 42 }
|
||||
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||
}
|
||||
function unsafe() pure returns (uint256 x) {
|
||||
assembly { pop(mload(0)) }
|
||||
}
|
||||
contract C {
|
||||
constructor() {
|
||||
unsafe();
|
||||
}
|
||||
function f() public pure {
|
||||
safe();
|
||||
}
|
||||
}
|
||||
contract D {
|
||||
constructor() {
|
||||
safe();
|
||||
}
|
||||
function f() public pure {
|
||||
unsafe();
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) false
|
||||
// :C(runtime) true
|
||||
// :D(creation) true
|
||||
// :D(runtime) false
|
@ -0,0 +1,23 @@
|
||||
contract C {
|
||||
constructor(uint256 x) {
|
||||
assembly { x := 4 }
|
||||
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||
}
|
||||
function f() public pure {
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
}
|
||||
contract D {
|
||||
constructor() {
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
function f(uint256 x) public pure {
|
||||
assembly { x := 4 }
|
||||
assembly ("memory-safe") { mstore(0, 0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
||||
// :D(creation) false
|
||||
// :D(runtime) true
|
@ -0,0 +1,9 @@
|
||||
contract C {
|
||||
function f() external pure {
|
||||
assembly "evmasm" ("memory-safe") {}
|
||||
assembly {}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) true
|
@ -0,0 +1,9 @@
|
||||
contract C {
|
||||
function f() external pure {
|
||||
assembly "evmasm" ("memory-safe") {}
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
4
test/libsolidity/memoryGuardTests/dialectString/stub.sol
Normal file
4
test/libsolidity/memoryGuardTests/dialectString/stub.sol
Normal file
@ -0,0 +1,4 @@
|
||||
contract C {}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) true
|
4
test/libsolidity/memoryGuardTests/stub.sol
Normal file
4
test/libsolidity/memoryGuardTests/stub.sol
Normal file
@ -0,0 +1,4 @@
|
||||
contract C {}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) true
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f(uint256 x, uint256 y) public pure returns (uint256 z){
|
||||
assembly { z := add(x, y) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) true
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
assembly { mstore(0,0) }
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
@ -0,0 +1,11 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
bytes memory x;
|
||||
assembly {
|
||||
x := 0
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// :C(creation) true
|
||||
// :C(runtime) false
|
@ -26,6 +26,6 @@ contract Main {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f(uint256): 0x34 -> 0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1
|
||||
// gas irOptimized: 113598
|
||||
// gas irOptimized: 113613
|
||||
// gas legacy: 126596
|
||||
// gas legacyOptimized: 113823
|
||||
|
@ -26,6 +26,6 @@ contract Creator {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f(uint256,address[]): 7, 0x40, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -> 7, 8
|
||||
// gas irOptimized: 443960
|
||||
// gas irOptimized: 443989
|
||||
// gas legacy: 590683
|
||||
// gas legacyOptimized: 448326
|
||||
|
@ -26,6 +26,6 @@ contract Creator {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f(uint256,bytes): 7, 0x40, 78, "abcdefghijklmnopqrstuvwxyzabcdef", "ghijklmnopqrstuvwxyzabcdefghijkl", "mnopqrstuvwxyz" -> 7, "h"
|
||||
// gas irOptimized: 300804
|
||||
// gas irOptimized: 300837
|
||||
// gas legacy: 428917
|
||||
// gas legacyOptimized: 298128
|
||||
|
@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @dev Provides a set of functions to operate with Base64 strings.
|
||||
*/
|
||||
library InlineAsmBase64 {
|
||||
/**
|
||||
* @dev Base64 Encoding/Decoding Table
|
||||
*/
|
||||
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
/**
|
||||
* @dev Converts a `bytes` to its Bytes64 `string` representation.
|
||||
*/
|
||||
function encode(bytes memory data) internal pure returns (string memory) {
|
||||
/**
|
||||
* Inspired by OpenZepplin Base64 implementation
|
||||
* https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884/commits/157c32b65a15cb0b58257543643cafa1cebf883a
|
||||
*/
|
||||
if (data.length == 0) return "";
|
||||
|
||||
// Loads the table into memory
|
||||
string memory table = _TABLE;
|
||||
|
||||
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
|
||||
// and split into 4 numbers of 6 bits.
|
||||
// The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
|
||||
// - `data.length + 2` -> Round up
|
||||
// - `/ 3` -> Number of 3-bytes chunks
|
||||
// - `4 *` -> 4 characters for each chunk
|
||||
uint256 encodedLen = 4 * ((data.length + 2) / 3);
|
||||
|
||||
// Add some extra buffer at the end required for the writing
|
||||
string memory result = new string(encodedLen);
|
||||
|
||||
assembly {
|
||||
// Store the actual result length in memory
|
||||
mstore(result, encodedLen)
|
||||
|
||||
// Prepare the lookup table
|
||||
let tablePtr := add(table, 1)
|
||||
|
||||
// Prepare input pointer
|
||||
let dataPtr := data
|
||||
let endPtr := add(dataPtr, mload(data))
|
||||
|
||||
// Prepare result pointer, jump over length
|
||||
let resultPtr := add(result, 32)
|
||||
|
||||
// Run over the input, 3 bytes at a time
|
||||
for {
|
||||
|
||||
} lt(dataPtr, endPtr) {
|
||||
|
||||
} {
|
||||
// Advance 3 bytes
|
||||
dataPtr := add(dataPtr, 3)
|
||||
let input := mload(dataPtr)
|
||||
|
||||
// To write each character, shift the 3 bytes (24 bits) chunk 4
|
||||
// times in blocks of 6 bits for each character (18, 12, 6, 0)
|
||||
// and apply logical AND with 0x3F to extract the 6-bit group.
|
||||
// Add the 6-bit group with the table ptr to index into the
|
||||
// table and acquire the character to write. Finally, write
|
||||
// the character to the result pointer.
|
||||
|
||||
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
|
||||
resultPtr := add(resultPtr, 1) // Advance
|
||||
|
||||
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
|
||||
resultPtr := add(resultPtr, 1) // Advance
|
||||
|
||||
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
|
||||
resultPtr := add(resultPtr, 1) // Advance
|
||||
|
||||
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
|
||||
resultPtr := add(resultPtr, 1) // Advance
|
||||
}
|
||||
|
||||
// When data `bytes` is not exactly 3 bytes long
|
||||
// it is padded with `=` characters at the end
|
||||
switch mod(mload(data), 3)
|
||||
case 1 {
|
||||
mstore8(sub(resultPtr, 1), 0x3d)
|
||||
mstore8(sub(resultPtr, 2), 0x3d)
|
||||
}
|
||||
case 2 {
|
||||
mstore8(sub(resultPtr, 1), 0x3d)
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @dev Provides a set of functions to operate with Base64 strings.
|
||||
*/
|
||||
library NoAsmBase64 {
|
||||
bytes private constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
function encode(bytes memory data) internal pure returns (string memory) {
|
||||
if (data.length == 0) return "";
|
||||
|
||||
bytes memory table = TABLE;
|
||||
bytes memory result = new bytes(4 * ((data.length + 2) / 3));
|
||||
uint256 resultPtr = 0;
|
||||
|
||||
for (uint256 dataPtr = 0; dataPtr < data.length; dataPtr += 3) {
|
||||
uint24 chunk = ( (uint24(uint8(data[dataPtr + 0])) << 16))
|
||||
+ (dataPtr + 1 < data.length ? (uint24(uint8(data[dataPtr + 1])) << 8) : 0)
|
||||
+ (dataPtr + 2 < data.length ? (uint24(uint8(data[dataPtr + 2])) ) : 0);
|
||||
|
||||
result[resultPtr++] = table[uint8(chunk >> 18) & 0x3f];
|
||||
result[resultPtr++] = table[uint8(chunk >> 12) & 0x3f];
|
||||
result[resultPtr++] = table[uint8(chunk >> 6) & 0x3f];
|
||||
result[resultPtr++] = table[uint8(chunk ) & 0x3f];
|
||||
}
|
||||
|
||||
if (data.length % 3 == 1) {
|
||||
result[--resultPtr] = 0x3d;
|
||||
result[--resultPtr] = 0x3d;
|
||||
}
|
||||
else if (data.length % 3 == 2) {
|
||||
result[--resultPtr] = 0x3d;
|
||||
}
|
||||
|
||||
return (string(result));
|
||||
}
|
||||
}
|
61
test/libsolidity/semanticTests/externalContracts/base64.sol
Normal file
61
test/libsolidity/semanticTests/externalContracts/base64.sol
Normal file
@ -0,0 +1,61 @@
|
||||
==== ExternalSource: _base64/base64_inline_asm.sol ====
|
||||
==== ExternalSource: _base64/base64_no_inline_asm.sol ====
|
||||
==== Source: base64.sol ====
|
||||
|
||||
import "_base64/base64_inline_asm.sol";
|
||||
import "_base64/base64_no_inline_asm.sol";
|
||||
|
||||
contract test {
|
||||
function encode_inline_asm(bytes memory data) external pure returns (string memory) {
|
||||
return InlineAsmBase64.encode(data);
|
||||
}
|
||||
|
||||
function encode_no_asm(bytes memory data) external pure returns (string memory) {
|
||||
return NoAsmBase64.encode(data);
|
||||
}
|
||||
|
||||
function encode_inline_asm_large() external {
|
||||
for (uint i = 0; i < 1000; i++) {
|
||||
InlineAsmBase64.encode("foo");
|
||||
}
|
||||
}
|
||||
|
||||
function encode_no_asm_large() external {
|
||||
for (uint i = 0; i < 1000; i++) {
|
||||
NoAsmBase64.encode("foo");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Test cases derived from Base64 specification: RFC4648
|
||||
// https://datatracker.ietf.org/doc/html/rfc4648#section-10
|
||||
//
|
||||
// ====
|
||||
// EVMVersion: >=constantinople
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// constructor()
|
||||
// gas irOptimized: 450044
|
||||
// gas legacy: 766936
|
||||
// gas legacyOptimized: 543094
|
||||
// encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0
|
||||
// encode_inline_asm(bytes): 0x20, 1, "f" -> 0x20, 4, "Zg=="
|
||||
// encode_inline_asm(bytes): 0x20, 2, "fo" -> 0x20, 4, "Zm8="
|
||||
// encode_inline_asm(bytes): 0x20, 3, "foo" -> 0x20, 4, "Zm9v"
|
||||
// encode_inline_asm(bytes): 0x20, 4, "foob" -> 0x20, 8, "Zm9vYg=="
|
||||
// encode_inline_asm(bytes): 0x20, 5, "fooba" -> 0x20, 8, "Zm9vYmE="
|
||||
// encode_inline_asm(bytes): 0x20, 6, "foobar" -> 0x20, 8, "Zm9vYmFy"
|
||||
// encode_no_asm(bytes): 0x20, 0 -> 0x20, 0
|
||||
// encode_no_asm(bytes): 0x20, 1, "f" -> 0x20, 4, "Zg=="
|
||||
// encode_no_asm(bytes): 0x20, 2, "fo" -> 0x20, 4, "Zm8="
|
||||
// encode_no_asm(bytes): 0x20, 3, "foo" -> 0x20, 4, "Zm9v"
|
||||
// encode_no_asm(bytes): 0x20, 4, "foob" -> 0x20, 8, "Zm9vYg=="
|
||||
// encode_no_asm(bytes): 0x20, 5, "fooba" -> 0x20, 8, "Zm9vYmE="
|
||||
// encode_no_asm(bytes): 0x20, 6, "foobar" -> 0x20, 8, "Zm9vYmFy"
|
||||
// encode_inline_asm_large()
|
||||
// gas irOptimized: 1385047
|
||||
// gas legacy: 1658033
|
||||
// gas legacyOptimized: 1210033
|
||||
// encode_no_asm_large()
|
||||
// gas irOptimized: 3335101
|
||||
// gas legacy: 4801077
|
||||
// gas legacyOptimized: 2929077
|
@ -50,7 +50,7 @@ contract test {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// constructor()
|
||||
// gas irOptimized: 1790188
|
||||
// gas irOptimized: 1792108
|
||||
// gas legacy: 2250130
|
||||
// gas legacyOptimized: 1746528
|
||||
// div(uint256,uint256): 3141592653589793238, 88714123 -> 35412542528203691288251815328
|
||||
|
@ -17,7 +17,7 @@ contract D {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// constructor(): 2 ->
|
||||
// gas irOptimized: 203967
|
||||
// gas irOptimized: 203982
|
||||
// gas legacy: 245842
|
||||
// gas legacyOptimized: 195676
|
||||
// f() -> 2
|
||||
|
@ -18,7 +18,7 @@ contract D {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// constructor(): 2 ->
|
||||
// gas irOptimized: 204130
|
||||
// gas irOptimized: 204145
|
||||
// gas legacy: 246202
|
||||
// gas legacyOptimized: 195914
|
||||
// f() -> 2
|
||||
|
@ -42,7 +42,7 @@ contract Main {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// constructor(), 22 wei ->
|
||||
// gas irOptimized: 284287
|
||||
// gas irOptimized: 284321
|
||||
// gas legacy: 402045
|
||||
// gas legacyOptimized: 266772
|
||||
// getFlag() -> true
|
||||
|
@ -22,6 +22,6 @@ contract A {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f(), 10 ether -> 3007, 3008, 3009
|
||||
// gas irOptimized: 272413
|
||||
// gas irOptimized: 272467
|
||||
// gas legacy: 422501
|
||||
// gas legacyOptimized: 287472
|
||||
|
@ -0,0 +1,5 @@
|
||||
function f() pure {
|
||||
assembly "evmasm" ("memory-safe", "memory-safe") {}
|
||||
}
|
||||
// ----
|
||||
// SyntaxError 7026: (24-75): Inline assembly marked memory-safe multiple times.
|
@ -0,0 +1,8 @@
|
||||
function f() pure {
|
||||
assembly "evmasm" ("a", "b", "c", "c") {}
|
||||
}
|
||||
// ----
|
||||
// Warning 4430: (24-65): Unknown inline assembly flag: "a"
|
||||
// Warning 4430: (24-65): Unknown inline assembly flag: "b"
|
||||
// Warning 4430: (24-65): Unknown inline assembly flag: "c"
|
||||
// Warning 4430: (24-65): Unknown inline assembly flag: "c"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user