-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
eof: Fix EOF builtin names unintentionally reserved outside of EOF #15700
base: develop
Are you sure you want to change the base?
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
7c98754
to
4e697bf
Compare
function dataloadn() {} | ||
function rjump() {} | ||
function rjumpi() {} | ||
function callf() {} | ||
function jumpf() {} | ||
function retf() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are any of these implemented in EOF, i.e. callable, like ext* calls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are not callable from yul level, but legacy jump
and jumpi
are reserved but also not callable from yul. So I assumed they should be reserved also.
4e697bf
to
a6c3c05
Compare
test/libyul/yulSyntaxTests/eof/eof_identifiers_not_reserved_in_legacy.yul
Outdated
Show resolved
Hide resolved
// bytecodeFormat: >=EOFv1 | ||
// ---- | ||
// ParserError 5568: (41-53): Cannot use builtin function name "auxdataloadn" as identifier name. | ||
// ParserError 8143: (41-53): Expected keyword "data" or "object" or "}". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like for some reason a clash with a builtin name is a fatal error. There's no good reason for that. I submitted a PR to fix that: #15712. When it's merged, you'll be able to put all the EOF builtins in a single test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth noting that when I changed the test to version without object
and code
section. The test results looks differently. Not sure if it's expected behavior.
yulSyntaxTests/eof/auxdataloadn_reserved_in_eof.yul: FAIL
Contract:
{
function auxdataloadn() {}
}
// ====
// bytecodeFormat: >=EOFv1
Expected result:
ParserError 5568: (41-53): Cannot use builtin function name "auxdataloadn" as identifier name.
ParserError 8143: (41-53): Expected keyword "data" or "object" or "}".
Obtained result:
ParserError 5568: (15-27): Cannot use builtin function name "auxdataloadn" as identifier name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it looks fine.
The Yul parser is split into two parts: the main one which parses code blocks (and produces ASTs) and a smaller one that only handles the object structure (and produces Object
tree).
What was originally happening here was that the object parser would run the code parser to parse a code block, which in turn would hit a fatal error (resulting in an exception). The object parser would probably catch that error but not stop and instead try to parse another nested object or code block at that point. This would produce the second error.
Now that you dropped the object, the object parser is no longer involved and you only get the first error. #15712 would have the same effect in this particular case but via a different route - by making the first error non-fatal and letting the code parser continue until the end of the code block, leaving the object parser at the right spot to parse the next thing.
test/libyul/yulSyntaxTests/eof/eof_identifiers_not_defined_in_legacy.yul
Outdated
Show resolved
Hide resolved
a6c3c05
to
577e2cc
Compare
577e2cc
to
d597ad7
Compare
( | ||
_instr == evmasm::Instruction::EXTCALL || | ||
_instr == evmasm::Instruction::EXTSTATICCALL || | ||
_instr == evmasm::Instruction::EXTDELEGATECALL | ||
_instr == evmasm::Instruction::EXTDELEGATECALL || | ||
_instr == evmasm::Instruction::DATALOADN || | ||
_instr == evmasm::Instruction::EOFCREATE || | ||
_instr == evmasm::Instruction::RETURNCONTRACT || | ||
_instr == evmasm::Instruction::RJUMP || | ||
_instr == evmasm::Instruction::RJUMPI || | ||
_instr == evmasm::Instruction::CALLF || | ||
_instr == evmasm::Instruction::JUMPF || | ||
_instr == evmasm::Instruction::RETF | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use hasOpcode()
to avoid repeating these:
EVMVersion::prague().hasOpcode(_instr, 1) && !EVMVersion::prague().hasOpcode(_instr, std::nullopt)
With an extra assert that EOF version is 1 and EVM version is <= Prague because this will need to be adjusted once new versions are out (if they and add/remove instructions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, validateInstructions()
should have such an assert too, because it hard-coded EOF version to 1 and we can easily forget to update it once we have EOF v2.
{ | ||
auto const functionName = resolveFunctionName(_funCall.functionName, m_dialect); | ||
|
||
if (functionName == "auxdataloadn" || functionName == "eofcreate" || functionName == "returncontract") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A more general way to check this would be:
if (resolveBuiltinFunction(
funCall.functionName,
EVMDialect::strictAssemblyForEVMObjects(EVMVersion::prague(), 1)
))
And that also with an assert that EOF version <= 1 and EVM version <= Prague.
And a comment that strictAssemblyForEVMObjects()
is expected to be the dialect that contains all the possible builtins. Or maybe we should just check both it and strictAssemblyForEVM()
to be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I think it would fit better in validateInstructions()
(the overload that takes a string). Just before return false
, when we know we're not dealing with an instruction.
7223_error, | ||
nativeLocationOf(_funCall.functionName), | ||
fmt::format( | ||
"The \"{}\" instruction is only available in EOF.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"The \"{}\" instruction is only available in EOF.", | |
"Builtin function \"{}\" is only available in EOF.", |
Resolves: #15672