mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #12620 from ethereum/assemblyAnnotation
Memory-safety annotation for inline assembly.
This commit is contained in:
commit
4f19d68ee9
@ -1,6 +1,7 @@
|
||||
### 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:
|
||||
|
@ -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,89 @@ 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
|
||||
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
...
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
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
|
||||
|
||||
/// @solidity memory-safe-assembly
|
||||
assembly {
|
||||
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.
|
||||
|
@ -1,6 +1,8 @@
|
||||
|
||||
.. index: ir breaking changes
|
||||
|
||||
.. _ir-breaking-changes:
|
||||
|
||||
*********************************
|
||||
Solidity IR-based Codegen Changes
|
||||
*********************************
|
||||
|
@ -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,71 @@ 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")
|
||||
_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,
|
||||
|
@ -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()))
|
||||
solAssert(m_errorReporter.hasErrors());
|
||||
_inlineAssembly.annotation().hasMemoryEffects =
|
||||
lvalueAccessToMemoryVariable ||
|
||||
(analyzer.sideEffects().memory != yul::SideEffects::None);
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(IfStatement const& _ifStatement)
|
||||
|
@ -220,6 +220,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
|
||||
|
@ -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, "");
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
#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>
|
||||
|
||||
@ -74,7 +76,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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
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;
|
||||
};
|
||||
|
||||
}
|
@ -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/free_function.sol
Normal file
29
test/libsolidity/memoryGuardTests/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
|
17
test/libsolidity/memoryGuardTests/multi_annotation.sol
Normal file
17
test/libsolidity/memoryGuardTests/multi_annotation.sol
Normal 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
|
25
test/libsolidity/memoryGuardTests/multiple_contracts.sol
Normal file
25
test/libsolidity/memoryGuardTests/multiple_contracts.sol
Normal 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
|
@ -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
|
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
|
@ -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
|
||||
|
@ -0,0 +1,14 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
/// @test test
|
||||
assembly {}
|
||||
/// @solidity test
|
||||
assembly {}
|
||||
/// @param
|
||||
assembly {}
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 6269: (60-71): Unexpected NatSpec tag "test" with value "test" in inline assembly.
|
||||
// Warning 8787: (95-106): Unexpected value for @solidity tag in inline assembly: test
|
||||
// Warning 7828: (122-133): Inline assembly has invalid NatSpec documentation.
|
@ -0,0 +1,6 @@
|
||||
contract C {
|
||||
function f() public pure {
|
||||
// @solidity memory-safe-assembly
|
||||
assembly {}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
function f() pure {
|
||||
/// @unrelated bogus-value
|
||||
|
||||
/// @before bogus-value
|
||||
///
|
||||
/// @solidity a memory-safe-assembly b c
|
||||
/// d
|
||||
/// @after bogus-value
|
||||
assembly {}
|
||||
/// @solidity memory-safe-assembly a a a
|
||||
/// memory-safe-assembly
|
||||
assembly {}
|
||||
}
|
||||
// ----
|
||||
// Warning 6269: (189-200): Unexpected NatSpec tag "after" with value "bogus-value" in inline assembly.
|
||||
// Warning 6269: (189-200): Unexpected NatSpec tag "before" with value "bogus-value" in inline assembly.
|
||||
// Warning 8787: (189-200): Unexpected value for @solidity tag in inline assembly: a
|
||||
// Warning 8787: (189-200): Unexpected value for @solidity tag in inline assembly: b
|
||||
// Warning 8787: (189-200): Unexpected value for @solidity tag in inline assembly: c
|
||||
// Warning 8787: (189-200): Unexpected value for @solidity tag in inline assembly: d
|
||||
// Warning 8787: (289-300): Unexpected value for @solidity tag in inline assembly: a
|
||||
// Warning 4377: (289-300): Value for @solidity tag in inline assembly specified multiple times: a
|
||||
// Warning 4377: (289-300): Value for @solidity tag in inline assembly specified multiple times: memory-safe-assembly
|
@ -0,0 +1,19 @@
|
||||
function f() pure {
|
||||
/// @unrelated bogus-value
|
||||
|
||||
/// @before
|
||||
///
|
||||
/// @solidity a memory-safe-assembly b c
|
||||
/// d
|
||||
/// @after bogus-value
|
||||
assembly {}
|
||||
/// @solidity memory-safe-assembly a a a
|
||||
/// memory-safe-assembly
|
||||
assembly {}
|
||||
}
|
||||
// ----
|
||||
// Warning 6269: (177-188): Unexpected NatSpec tag "after" with value "bogus-value" in inline assembly.
|
||||
// Warning 6269: (177-188): Unexpected NatSpec tag "before" with value "@solidity a memory-safe-assembly b c d" in inline assembly.
|
||||
// Warning 8787: (277-288): Unexpected value for @solidity tag in inline assembly: a
|
||||
// Warning 4377: (277-288): Value for @solidity tag in inline assembly specified multiple times: a
|
||||
// Warning 4377: (277-288): Value for @solidity tag in inline assembly specified multiple times: memory-safe-assembly
|
@ -23,6 +23,7 @@ add_executable(isoltest
|
||||
../libsolidity/util/TestFileParser.cpp
|
||||
../libsolidity/util/TestFunctionCall.cpp
|
||||
../libsolidity/GasTest.cpp
|
||||
../libsolidity/MemoryGuardTest.cpp
|
||||
../libsolidity/SyntaxTest.cpp
|
||||
../libsolidity/SemanticTest.cpp
|
||||
../libsolidity/AnalysisFramework.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user