Skip to content

Commit 57e012d

Browse files
authored
Merge pull request #12663 from ethereum/assemblyAnnotationDialectString
Assembly annotation as memory-safe using assembly flags.
2 parents 79eba92 + 46d8611 commit 57e012d

33 files changed

+273
-11
lines changed

docs/assembly.rst

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,7 @@ model as follows:
297297

298298
.. code-block:: solidity
299299
300-
/// @solidity memory-safe-assembly
301-
assembly {
300+
assembly ("memory-safe") {
302301
...
303302
}
304303
@@ -327,8 +326,7 @@ But the following is:
327326

328327
.. code-block:: solidity
329328
330-
/// @solidity memory-safe-assembly
331-
assembly {
329+
assembly ("memory-safe") {
332330
let p := mload(0x40)
333331
returndatacopy(p, 0, returndatasize())
334332
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
341339

342340
.. code-block:: solidity
343341
344-
/// @solidity memory-safe-assembly
345-
assembly {
342+
assembly ("memory-safe") {
346343
revert(0, 0)
347344
}
348345
@@ -364,3 +361,16 @@ in memory is automatically considered memory-safe and does not need to be annota
364361
It is your responsibility to make sure that the assembly actually satisfies the memory model. If you annotate
365362
an assembly block as memory-safe, but violate one of the memory assumptions, this **will** lead to incorrect and
366363
undefined behaviour that cannot easily be discovered by testing.
364+
365+
In case you are developing a library that is meant to be compatible across multiple versions
366+
of solidity, you can use a special comment to annotate an assembly block as memory-safe:
367+
368+
.. code-block:: solidity
369+
370+
/// @solidity memory-safe-assembly
371+
assembly {
372+
...
373+
}
374+
375+
Note that we will disallow the annotation via comment in a future breaking release, so if you are not concerned with
376+
backwards-compatibility with older compiler versions, prefer using the dialect string.

docs/grammar/SolidityLexer.g4

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ mode AssemblyBlockMode;
251251
AssemblyDialect: '"evmasm"';
252252
AssemblyLBrace: '{' -> popMode, pushMode(YulMode);
253253
254+
AssemblyFlagString: '"' DoubleQuotedStringCharacter+ '"';
255+
256+
AssemblyBlockLParen: '(';
257+
AssemblyBlockRParen: ')';
258+
AssemblyBlockComma: ',';
259+
254260
AssemblyBlockWS: [ \t\r\n\u000C]+ -> skip ;
255261
AssemblyBlockCOMMENT: '/*' .*? '*/' -> channel(HIDDEN) ;
256262
AssemblyBlockLINE_COMMENT: '//' ~[\r\n]* -> channel(HIDDEN) ;

docs/grammar/SolidityParser.g4

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,13 @@ revertStatement: Revert expression callArgumentList Semicolon;
476476
* The contents of an inline assembly block use a separate scanner/lexer, i.e. the set of keywords and
477477
* allowed identifiers is different inside an inline assembly block.
478478
*/
479-
assemblyStatement: Assembly AssemblyDialect? AssemblyLBrace yulStatement* YulRBrace;
479+
assemblyStatement: Assembly AssemblyDialect? assemblyFlags? AssemblyLBrace yulStatement* YulRBrace;
480+
481+
/**
482+
* Assembly flags.
483+
* Comma-separated list of double-quoted strings as flags.
484+
*/
485+
assemblyFlags: AssemblyBlockLParen AssemblyFlagString (AssemblyBlockComma AssemblyFlagString)* AssemblyBlockRParen;
480486

481487
//@doc:inline
482488
variableDeclarationList: variableDeclarations+=variableDeclaration (Comma variableDeclarations+=variableDeclaration)*;

libsolidity/analysis/DocStringTagParser.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,17 @@ bool DocStringTagParser::visit(InlineAssembly const& _assembly)
202202
if (valuesSeen.insert(value).second)
203203
{
204204
if (value == "memory-safe-assembly")
205+
{
206+
if (_assembly.annotation().markedMemorySafe)
207+
m_errorReporter.warning(
208+
8544_error,
209+
_assembly.location(),
210+
"Inline assembly marked as memory safe using both a NatSpec tag and an assembly flag. "
211+
"If you are not concerned with backwards compatibility, only use the assembly flag, "
212+
"otherwise only use the NatSpec tag."
213+
);
205214
_assembly.annotation().markedMemorySafe = true;
215+
}
206216
else
207217
m_errorReporter.warning(
208218
8787_error,

libsolidity/analysis/SyntaxChecker.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,27 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation)
334334

335335
bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
336336
{
337+
if (_inlineAssembly.flags())
338+
for (auto flag: *_inlineAssembly.flags())
339+
{
340+
if (*flag == "memory-safe")
341+
{
342+
if (_inlineAssembly.annotation().markedMemorySafe)
343+
m_errorReporter.syntaxError(
344+
7026_error,
345+
_inlineAssembly.location(),
346+
"Inline assembly marked memory-safe multiple times."
347+
);
348+
_inlineAssembly.annotation().markedMemorySafe = true;
349+
}
350+
else
351+
m_errorReporter.warning(
352+
4430_error,
353+
_inlineAssembly.location(),
354+
"Unknown inline assembly flag: \"" + *flag + "\""
355+
);
356+
}
357+
337358
if (!m_useYulOptimizer)
338359
return false;
339360

libsolidity/ast/AST.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1463,19 +1463,26 @@ class InlineAssembly: public Statement
14631463
SourceLocation const& _location,
14641464
ASTPointer<ASTString> const& _docString,
14651465
yul::Dialect const& _dialect,
1466+
ASTPointer<std::vector<ASTPointer<ASTString>>> _flags,
14661467
std::shared_ptr<yul::Block> _operations
14671468
):
1468-
Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(std::move(_operations)) {}
1469+
Statement(_id, _location, _docString),
1470+
m_dialect(_dialect),
1471+
m_flags(move(_flags)),
1472+
m_operations(std::move(_operations))
1473+
{}
14691474
void accept(ASTVisitor& _visitor) override;
14701475
void accept(ASTConstVisitor& _visitor) const override;
14711476

14721477
yul::Dialect const& dialect() const { return m_dialect; }
14731478
yul::Block const& operations() const { return *m_operations; }
1479+
ASTPointer<std::vector<ASTPointer<ASTString>>> const& flags() const { return m_flags; }
14741480

14751481
InlineAssemblyAnnotation& annotation() const override;
14761482

14771483
private:
14781484
yul::Dialect const& m_dialect;
1485+
ASTPointer<std::vector<ASTPointer<ASTString>>> m_flags;
14791486
std::shared_ptr<yul::Block> m_operations;
14801487
};
14811488

libsolidity/ast/ASTJsonConverter.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,23 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
600600
for (Json::Value& it: externalReferences | ranges::views::values)
601601
externalReferencesJson.append(std::move(it));
602602

603-
setJsonNode(_node, "InlineAssembly", {
603+
std::vector<pair<string, Json::Value>> attributes = {
604604
make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))),
605605
make_pair("externalReferences", std::move(externalReferencesJson)),
606606
make_pair("evmVersion", dynamic_cast<solidity::yul::EVMDialect const&>(_node.dialect()).evmVersion().name())
607-
});
607+
};
608+
609+
if (_node.flags())
610+
{
611+
Json::Value flags(Json::arrayValue);
612+
for (auto const& flag: *_node.flags())
613+
if (flag)
614+
flags.append(*flag);
615+
else
616+
flags.append(Json::nullValue);
617+
attributes.emplace_back(make_pair("flags", move(flags)));
618+
}
619+
setJsonNode(_node, "InlineAssembly", move(attributes));
608620

609621
return false;
610622
}

libsolidity/ast/ASTJsonImporter.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,11 +626,24 @@ ASTPointer<InlineAssembly> ASTJsonImporter::createInlineAssembly(Json::Value con
626626
astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!");
627627

628628
yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value());
629+
ASTPointer<vector<ASTPointer<ASTString>>> flags;
630+
if (_node.isMember("flags"))
631+
{
632+
flags = make_shared<vector<ASTPointer<ASTString>>>();
633+
Json::Value const& flagsNode = _node["flags"];
634+
astAssert(flagsNode.isArray(), "Assembly flags must be an array.");
635+
for (Json::ArrayIndex i = 0; i < flagsNode.size(); ++i)
636+
{
637+
astAssert(flagsNode[i].isString(), "Assembly flag must be a string.");
638+
flags->emplace_back(make_shared<ASTString>(flagsNode[i].asString()));
639+
}
640+
}
629641
shared_ptr<yul::Block> operations = make_shared<yul::Block>(yul::AsmJsonImporter(m_sourceNames).createBlock(member(_node, "AST")));
630642
return createASTNode<InlineAssembly>(
631643
_node,
632644
nullOrASTString(_node, "documentation"),
633645
dialect,
646+
move(flags),
634647
operations
635648
);
636649
}

libsolidity/parsing/Parser.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1321,13 +1321,28 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con
13211321
advance();
13221322
}
13231323

1324+
ASTPointer<vector<ASTPointer<ASTString>>> flags;
1325+
if (m_scanner->currentToken() == Token::LParen)
1326+
{
1327+
flags = make_shared<vector<ASTPointer<ASTString>>>();
1328+
do
1329+
{
1330+
advance();
1331+
expectToken(Token::StringLiteral, false);
1332+
flags->emplace_back(make_shared<ASTString>(m_scanner->currentLiteral()));
1333+
advance();
1334+
}
1335+
while (m_scanner->currentToken() == Token::Comma);
1336+
expectToken(Token::RParen);
1337+
}
1338+
13241339
yul::Parser asmParser(m_errorReporter, dialect);
13251340
shared_ptr<yul::Block> block = asmParser.parseInline(m_scanner);
13261341
if (block == nullptr)
13271342
BOOST_THROW_EXCEPTION(FatalError());
13281343

13291344
location.end = nativeLocationOf(*block).end;
1330-
return make_shared<InlineAssembly>(nextID(), location, _docString, dialect, block);
1345+
return make_shared<InlineAssembly>(nextID(), location, _docString, dialect, move(flags), block);
13311346
}
13321347

13331348
ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _docString)

0 commit comments

Comments
 (0)