Merge pull request #12620 from ethereum/assemblyAnnotation

Memory-safety annotation for inline assembly.
This commit is contained in:
chriseth 2022-03-07 12:48:41 +01:00 committed by GitHub
commit 4f19d68ee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 588 additions and 26 deletions

View File

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

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

View File

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

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

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

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@ -0,0 +1,6 @@
contract C {
function f() public pure {
// @solidity memory-safe-assembly
assembly {}
}
}

View File

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

View File

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

View File

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