Merge branch 'develop' of github.com:tfire/solidity into fix/remove-namespace-ast-annotations

This commit is contained in:
Tyler 2022-03-09 18:55:22 -05:00
commit 047034544e
119 changed files with 1744 additions and 253 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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.

View File

@ -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) ;

View File

@ -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)*;

View File

@ -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
========

View File

@ -1,6 +1,8 @@
.. index: ir breaking changes
.. _ir-breaking-changes:
*********************************
Solidity IR-based Codegen Changes
*********************************

View File

@ -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.

View File

@ -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 ...
}

View File

@ -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
);

View File

@ -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;

View File

@ -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:

View File

@ -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,

View File

@ -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 (

View File

@ -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()

View File

@ -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,

View File

@ -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,

View File

@ -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;

View File

@ -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)

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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));

View File

@ -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;

View File

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

View File

@ -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.

View File

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

View File

@ -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());

View File

@ -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.");

View File

@ -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)

View File

@ -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);

View File

@ -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());
}
}

View File

@ -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&)

View File

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

View File

@ -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);

View File

@ -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.

View File

@ -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 (

View File

@ -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
{

View File

@ -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())};
}

View File

@ -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;

View File

@ -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 {};

View File

@ -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;

View File

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

View File

@ -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

View File

@ -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 != ':')

View File

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

View File

@ -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++;

View File

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

View File

@ -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)

View File

@ -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 {}
}

View File

@ -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))

View File

@ -4,6 +4,6 @@ pragma abicoder v2;
contract D {
function f() public pure {
assembly {}
assembly { mstore(0,0) }
}
}

View File

@ -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)
}
}

View File

@ -0,0 +1 @@
--experimental-via-ir --optimize --asm

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

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

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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"

View File

@ -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.

View File

@ -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);

View File

@ -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{

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

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

View File

@ -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;

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,10 @@
contract C {
function f() external pure {
/// @solidity memory-safe-assembly
assembly {}
assembly {}
}
}
// ----
// :C(creation) true
// :C(runtime) true

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
contract C {
function f() external pure {
assembly "evmasm" ("memory-safe") {}
assembly {}
}
}
// ----
// :C(creation) true
// :C(runtime) true

View File

@ -0,0 +1,9 @@
contract C {
function f() external pure {
assembly "evmasm" ("memory-safe") {}
assembly { mstore(0,0) }
}
}
// ----
// :C(creation) true
// :C(runtime) false

View File

@ -0,0 +1,4 @@
contract C {}
// ----
// :C(creation) true
// :C(runtime) true

View File

@ -0,0 +1,4 @@
contract C {}
// ----
// :C(creation) true
// :C(runtime) true

View File

@ -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

View File

@ -0,0 +1,8 @@
contract C {
function f() public pure {
assembly { mstore(0,0) }
}
}
// ----
// :C(creation) true
// :C(runtime) false

View File

@ -0,0 +1,11 @@
contract C {
function f() public pure {
bytes memory x;
assembly {
x := 0
}
}
}
// ----
// :C(creation) true
// :C(runtime) false

View File

@ -26,6 +26,6 @@ contract Main {
// compileViaYul: also
// ----
// f(uint256): 0x34 -> 0x46bddb1178e94d7f2892ff5f366840eb658911794f2c3a44c450aa2c505186c1
// gas irOptimized: 113598
// gas irOptimized: 113613
// gas legacy: 126596
// gas legacyOptimized: 113823

View File

@ -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

View File

@ -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

View File

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

View File

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

View 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

View File

@ -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

View File

@ -17,7 +17,7 @@ contract D {
// compileViaYul: also
// ----
// constructor(): 2 ->
// gas irOptimized: 203967
// gas irOptimized: 203982
// gas legacy: 245842
// gas legacyOptimized: 195676
// f() -> 2

View File

@ -18,7 +18,7 @@ contract D {
// compileViaYul: also
// ----
// constructor(): 2 ->
// gas irOptimized: 204130
// gas irOptimized: 204145
// gas legacy: 246202
// gas legacyOptimized: 195914
// f() -> 2

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
function f() pure {
assembly "evmasm" ("memory-safe", "memory-safe") {}
}
// ----
// SyntaxError 7026: (24-75): Inline assembly marked memory-safe multiple times.

View File

@ -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