mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #12663 from ethereum/assemblyAnnotationDialectString
Assembly annotation as memory-safe using assembly flags.
This commit is contained in:
commit
57e012da98
@ -297,8 +297,7 @@ model as follows:
|
|||||||
|
|
||||||
.. code-block:: solidity
|
.. code-block:: solidity
|
||||||
|
|
||||||
/// @solidity memory-safe-assembly
|
assembly ("memory-safe") {
|
||||||
assembly {
|
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,8 +326,7 @@ But the following is:
|
|||||||
|
|
||||||
.. code-block:: solidity
|
.. code-block:: solidity
|
||||||
|
|
||||||
/// @solidity memory-safe-assembly
|
assembly ("memory-safe") {
|
||||||
assembly {
|
|
||||||
let p := mload(0x40)
|
let p := mload(0x40)
|
||||||
returndatacopy(p, 0, returndatasize())
|
returndatacopy(p, 0, returndatasize())
|
||||||
revert(p, returndatasize())
|
revert(p, returndatasize())
|
||||||
@ -341,8 +339,7 @@ If the memory operations use a length of zero, it is also fine to just use any o
|
|||||||
|
|
||||||
.. code-block:: solidity
|
.. code-block:: solidity
|
||||||
|
|
||||||
/// @solidity memory-safe-assembly
|
assembly ("memory-safe") {
|
||||||
assembly {
|
|
||||||
revert(0, 0)
|
revert(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,3 +361,16 @@ in memory is automatically considered memory-safe and does not need to be annota
|
|||||||
It is your responsibility to make sure that the assembly actually satisfies the memory model. If you annotate
|
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
|
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.
|
undefined behaviour that cannot easily be discovered by testing.
|
||||||
|
|
||||||
|
In case you are developing a library that is meant to be compatible across multiple versions
|
||||||
|
of solidity, you can use a special comment to annotate an assembly block as memory-safe:
|
||||||
|
|
||||||
|
.. code-block:: solidity
|
||||||
|
|
||||||
|
/// @solidity memory-safe-assembly
|
||||||
|
assembly {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that we will disallow the annotation via comment in a future breaking release, so if you are not concerned with
|
||||||
|
backwards-compatibility with older compiler versions, prefer using the dialect string.
|
||||||
|
@ -251,6 +251,12 @@ mode AssemblyBlockMode;
|
|||||||
AssemblyDialect: '"evmasm"';
|
AssemblyDialect: '"evmasm"';
|
||||||
AssemblyLBrace: '{' -> popMode, pushMode(YulMode);
|
AssemblyLBrace: '{' -> popMode, pushMode(YulMode);
|
||||||
|
|
||||||
|
AssemblyFlagString: '"' DoubleQuotedStringCharacter+ '"';
|
||||||
|
|
||||||
|
AssemblyBlockLParen: '(';
|
||||||
|
AssemblyBlockRParen: ')';
|
||||||
|
AssemblyBlockComma: ',';
|
||||||
|
|
||||||
AssemblyBlockWS: [ \t\r\n\u000C]+ -> skip ;
|
AssemblyBlockWS: [ \t\r\n\u000C]+ -> skip ;
|
||||||
AssemblyBlockCOMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
|
AssemblyBlockCOMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
|
||||||
AssemblyBlockLINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN) ;
|
AssemblyBlockLINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN) ;
|
||||||
|
@ -476,7 +476,13 @@ revertStatement: Revert expression callArgumentList Semicolon;
|
|||||||
* The contents of an inline assembly block use a separate scanner/lexer, i.e. the set of keywords and
|
* 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.
|
* 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
|
//@doc:inline
|
||||||
variableDeclarationList: variableDeclarations+=variableDeclaration (Comma variableDeclarations+=variableDeclaration)*;
|
variableDeclarationList: variableDeclarations+=variableDeclaration (Comma variableDeclarations+=variableDeclaration)*;
|
||||||
|
@ -202,7 +202,17 @@ bool DocStringTagParser::visit(InlineAssembly const& _assembly)
|
|||||||
if (valuesSeen.insert(value).second)
|
if (valuesSeen.insert(value).second)
|
||||||
{
|
{
|
||||||
if (value == "memory-safe-assembly")
|
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;
|
_assembly.annotation().markedMemorySafe = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
m_errorReporter.warning(
|
m_errorReporter.warning(
|
||||||
8787_error,
|
8787_error,
|
||||||
|
@ -334,6 +334,27 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation)
|
|||||||
|
|
||||||
bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
|
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)
|
if (!m_useYulOptimizer)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -1463,19 +1463,26 @@ public:
|
|||||||
SourceLocation const& _location,
|
SourceLocation const& _location,
|
||||||
ASTPointer<ASTString> const& _docString,
|
ASTPointer<ASTString> const& _docString,
|
||||||
yul::Dialect const& _dialect,
|
yul::Dialect const& _dialect,
|
||||||
|
ASTPointer<std::vector<ASTPointer<ASTString>>> _flags,
|
||||||
std::shared_ptr<yul::Block> _operations
|
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(ASTVisitor& _visitor) override;
|
||||||
void accept(ASTConstVisitor& _visitor) const override;
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
yul::Dialect const& dialect() const { return m_dialect; }
|
yul::Dialect const& dialect() const { return m_dialect; }
|
||||||
yul::Block const& operations() const { return *m_operations; }
|
yul::Block const& operations() const { return *m_operations; }
|
||||||
|
ASTPointer<std::vector<ASTPointer<ASTString>>> const& flags() const { return m_flags; }
|
||||||
|
|
||||||
InlineAssemblyAnnotation& annotation() const override;
|
InlineAssemblyAnnotation& annotation() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
yul::Dialect const& m_dialect;
|
yul::Dialect const& m_dialect;
|
||||||
|
ASTPointer<std::vector<ASTPointer<ASTString>>> m_flags;
|
||||||
std::shared_ptr<yul::Block> m_operations;
|
std::shared_ptr<yul::Block> m_operations;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -600,11 +600,23 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
|
|||||||
for (Json::Value& it: externalReferences | ranges::views::values)
|
for (Json::Value& it: externalReferences | ranges::views::values)
|
||||||
externalReferencesJson.append(std::move(it));
|
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("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))),
|
||||||
make_pair("externalReferences", std::move(externalReferencesJson)),
|
make_pair("externalReferences", std::move(externalReferencesJson)),
|
||||||
make_pair("evmVersion", dynamic_cast<solidity::yul::EVMDialect const&>(_node.dialect()).evmVersion().name())
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -626,11 +626,24 @@ ASTPointer<InlineAssembly> ASTJsonImporter::createInlineAssembly(Json::Value con
|
|||||||
astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!");
|
astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!");
|
||||||
|
|
||||||
yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value());
|
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")));
|
shared_ptr<yul::Block> operations = make_shared<yul::Block>(yul::AsmJsonImporter(m_sourceNames).createBlock(member(_node, "AST")));
|
||||||
return createASTNode<InlineAssembly>(
|
return createASTNode<InlineAssembly>(
|
||||||
_node,
|
_node,
|
||||||
nullOrASTString(_node, "documentation"),
|
nullOrASTString(_node, "documentation"),
|
||||||
dialect,
|
dialect,
|
||||||
|
move(flags),
|
||||||
operations
|
operations
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1321,13 +1321,28 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con
|
|||||||
advance();
|
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);
|
yul::Parser asmParser(m_errorReporter, dialect);
|
||||||
shared_ptr<yul::Block> block = asmParser.parseInline(m_scanner);
|
shared_ptr<yul::Block> block = asmParser.parseInline(m_scanner);
|
||||||
if (block == nullptr)
|
if (block == nullptr)
|
||||||
BOOST_THROW_EXCEPTION(FatalError());
|
BOOST_THROW_EXCEPTION(FatalError());
|
||||||
|
|
||||||
location.end = nativeLocationOf(*block).end;
|
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)
|
ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _docString)
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
contract C {
|
||||||
|
constructor() {
|
||||||
|
uint256 x;
|
||||||
|
assembly { x := 0 }
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
function f() internal pure {
|
||||||
|
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||||
|
assembly ("memory-safe") { mstore(0, 0) }
|
||||||
|
}
|
||||||
|
function g() public pure {
|
||||||
|
assembly { mstore(0, 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// :C(creation) true
|
||||||
|
// :C(runtime) false
|
@ -0,0 +1,16 @@
|
|||||||
|
contract C {
|
||||||
|
constructor() {
|
||||||
|
uint256 x;
|
||||||
|
assembly { x := 0 }
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
function f() internal pure {
|
||||||
|
assembly { mstore(0, 0) }
|
||||||
|
}
|
||||||
|
function g() public pure {
|
||||||
|
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// :C(creation) false
|
||||||
|
// :C(runtime) true
|
@ -0,0 +1,28 @@
|
|||||||
|
function safe() pure returns (uint256 x) {
|
||||||
|
assembly { x := 42 }
|
||||||
|
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||||
|
}
|
||||||
|
function unsafe() pure returns (uint256 x) {
|
||||||
|
assembly { pop(mload(0)) }
|
||||||
|
}
|
||||||
|
contract C {
|
||||||
|
constructor() {
|
||||||
|
unsafe();
|
||||||
|
}
|
||||||
|
function f() public pure {
|
||||||
|
safe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contract D {
|
||||||
|
constructor() {
|
||||||
|
safe();
|
||||||
|
}
|
||||||
|
function f() public pure {
|
||||||
|
unsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// :C(creation) false
|
||||||
|
// :C(runtime) true
|
||||||
|
// :D(creation) true
|
||||||
|
// :D(runtime) false
|
@ -0,0 +1,23 @@
|
|||||||
|
contract C {
|
||||||
|
constructor(uint256 x) {
|
||||||
|
assembly { x := 4 }
|
||||||
|
assembly "evmasm" ("memory-safe") { mstore(0, 0) }
|
||||||
|
}
|
||||||
|
function f() public pure {
|
||||||
|
assembly { mstore(0,0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contract D {
|
||||||
|
constructor() {
|
||||||
|
assembly { mstore(0,0) }
|
||||||
|
}
|
||||||
|
function f(uint256 x) public pure {
|
||||||
|
assembly { x := 4 }
|
||||||
|
assembly ("memory-safe") { mstore(0, 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// :C(creation) true
|
||||||
|
// :C(runtime) false
|
||||||
|
// :D(creation) false
|
||||||
|
// :D(runtime) true
|
@ -0,0 +1,9 @@
|
|||||||
|
contract C {
|
||||||
|
function f() external pure {
|
||||||
|
assembly "evmasm" ("memory-safe") {}
|
||||||
|
assembly {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// :C(creation) true
|
||||||
|
// :C(runtime) true
|
@ -0,0 +1,9 @@
|
|||||||
|
contract C {
|
||||||
|
function f() external pure {
|
||||||
|
assembly "evmasm" ("memory-safe") {}
|
||||||
|
assembly { mstore(0,0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// :C(creation) true
|
||||||
|
// :C(runtime) false
|
4
test/libsolidity/memoryGuardTests/dialectString/stub.sol
Normal file
4
test/libsolidity/memoryGuardTests/dialectString/stub.sol
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
contract C {}
|
||||||
|
// ----
|
||||||
|
// :C(creation) true
|
||||||
|
// :C(runtime) true
|
@ -0,0 +1,5 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly "evmasm" ("memory-safe", "memory-safe") {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// SyntaxError 7026: (24-75): Inline assembly marked memory-safe multiple times.
|
@ -0,0 +1,8 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly "evmasm" ("a", "b", "c", "c") {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning 4430: (24-65): Unknown inline assembly flag: "a"
|
||||||
|
// Warning 4430: (24-65): Unknown inline assembly flag: "b"
|
||||||
|
// Warning 4430: (24-65): Unknown inline assembly flag: "c"
|
||||||
|
// Warning 4430: (24-65): Unknown inline assembly flag: "c"
|
@ -0,0 +1,5 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly " evmasm" {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// ParserError 4531: (33-42): Only "evmasm" supported.
|
@ -0,0 +1,5 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly ("memory-safe", "memory-safe") {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// SyntaxError 7026: (24-66): Inline assembly marked memory-safe multiple times.
|
@ -0,0 +1,5 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly () {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// ParserError 2314: (34-35): Expected 'StringLiteral' but got ')'
|
@ -0,0 +1,5 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly "evmasm" () {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// ParserError 2314: (43-44): Expected 'StringLiteral' but got ')'
|
@ -0,0 +1,5 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly ("a" "b") {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// ParserError 2314: (35-38): Expected ')' but got 'StringLiteral'
|
@ -0,0 +1,8 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly ("a", "b", "c", "c") {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning 4430: (24-56): Unknown inline assembly flag: "a"
|
||||||
|
// Warning 4430: (24-56): Unknown inline assembly flag: "b"
|
||||||
|
// Warning 4430: (24-56): Unknown inline assembly flag: "c"
|
||||||
|
// Warning 4430: (24-56): Unknown inline assembly flag: "c"
|
@ -0,0 +1,7 @@
|
|||||||
|
function f() pure {
|
||||||
|
/// @solidity memory-safe-assembly
|
||||||
|
assembly "evmasm" ("memory-safe") {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// Warning 8544: (63-104): 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.
|
@ -0,0 +1,3 @@
|
|||||||
|
function f() pure {
|
||||||
|
assembly "evmasm" ("memory-safe") {}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user