Do not remove custom errors in IR codegen when strip revert strings is requested#16466
Do not remove custom errors in IR codegen when strip revert strings is requested#16466matheusaaguiar wants to merge 3 commits intodevelopfrom
Conversation
revert-strings strip is requestedrevert-strings strip is requested
revert-strings strip is requestedrevert-strings=strip is requested
|
We need to also adjust documentation as a part of this issue. And consider changing the option or value names to make it more intuitive. |
nikola-matic
left a comment
There was a problem hiding this comment.
The asm output test is definitely necessary, but please add more semantic test coverage; there's the // revertStrings: strip option for this.
| pragma solidity >=0.0; | ||
| // SPDX-License-Identifier: GPL-3.0 | ||
| contract C { | ||
| error MyError(string errorMsg); |
There was a problem hiding this comment.
I'd give this an argument or two more, e.g. error MyError(string errorMsg, uint256 errorId);
| contract C { | ||
| error MyError(string errorMsg); | ||
| function f() pure external { | ||
| require(false, MyError("error")); |
There was a problem hiding this comment.
For the callsite, declare variables, assign them values, and pass them as arguments to the error constructor call.
string errorMsg = "error";
uint256 errorId = 111;
require(false, MyError(errorMsg, errorId));I wanna see some pushes to the stack.
| // Here, the argument is consumed, but in the other branch, it is still there. | ||
| m_context.adjustStackOffset(static_cast<int>(sizeOfErrorArguments)); | ||
| if (m_context.revertStrings() == RevertStrings::Strip) | ||
| m_context.appendRevert(); |
There was a problem hiding this comment.
My suspicion is that you'll leave the stack dirty here (i.e. when we iterate and handle the constructor call arguments (loop at 1279), we'll also push any non literal arguments to the stack, which will not be popped anywhere.
Take a look at the revert with error(string) implementation.
4913b7d to
8450ab7
Compare
revert-strings=strip is requestedf3491de to
1de843a
Compare
rodiazet
left a comment
There was a problem hiding this comment.
LGTM in general. I found a couple of small issues mainly with test coverage. There is also one question I have regarding the way how the old version worked.
There was a problem hiding this comment.
Add:
pragma solidity >=0.0;
// SPDX-License-Identifier: GPL-3.0
There was a problem hiding this comment.
It does not hurt, but usually we don't add that to tests... not sure if there is a real reason, but I guess we don't care.
There was a problem hiding this comment.
Ah, I realize why you suggested that. I copied these tests from the cli tests and there we actually care about that to silence warnings in the output file.
For syntax and semantic tests, that's not the case, i.e, they are filtered out.
| } | ||
| else | ||
| { | ||
| if (m_context.revertStrings() == RevertStrings::Strip) |
There was a problem hiding this comment.
I would add here a comment explaining that revert string behaves differently for custom error and for a raw message.
| function f() pure external { | ||
| uint code = 8; | ||
| string memory eMsg = "error"; | ||
| require(false, MyError(code, eMsg, flag())); |
There was a problem hiding this comment.
How about tests where require's first arguments cannot be evaluated at compile time? Do you think it makes sense to add them?
There was a problem hiding this comment.
Hum, I don't think that matters or affects the codegen but I will double-check.
Can at least add that in some tests to cover for it, but I don't think a specific test is necessary.
| // revertStrings: strip | ||
| // ---- | ||
| // f() -> FAILURE, hex"0b9344e2", hex"0000000000000000000000000000000000000000000000000000000000000001", hex"0000000000000000000000000000000000000000000000000000000000000040", hex"0000000000000000000000000000000000000000000000000000000000000005", hex"6572726f72000000000000000000000000000000000000000000000000000000" | ||
| // counter() -> 0 |
There was a problem hiding this comment.
Here and in other tests I would add an explanation that the counter returns 0 because the transaction failed and the most important check is that the error argument is properly evaluated to 1.
There was a problem hiding this comment.
Added the explanation.
| Type const* messageArgumentType = arguments.size() > 1 ? arguments[1]->annotation().type : nullptr; | ||
|
|
||
| auto const* magicType = dynamic_cast<MagicType const*>(messageArgumentType); | ||
| if (magicType && magicType->kind() == MagicType::Kind::Error) |
There was a problem hiding this comment.
In the new version this fragment of the code also generates code for path when the require conditions evaluates to true, but I cannot find tests for this case except require_error_stack_check.sol. Not sure if it tests revertStrings = Strip. IMO we should also add test which does not revert and check that the stack is properly cleaned and not revert is called.
BTW in previous version with Strip, custom Error was handled by the code in the else branch. Was it correct?
There was a problem hiding this comment.
In the new version this fragment of the code also generates code for path when the require conditions evaluates to true, but I cannot find tests for this case except require_error_stack_check.sol. Not sure if it tests revertStrings = Strip.
The new version now follows the same path of previous version when revertStrings != Strip, so it should have the same result. I will add a variation of that test with revert-strings=string anyway.
BTW in previous version with Strip, custom Error was handled by the code in the else branch. Was it correct?
The handling was the same of a require with no message, i.e., YulUtilFunctions::requireOrAssertFunction will check if the message type is null and then generate code with revert(0, 0) for the false branch of the require condition.
The arguments of require are already visited and their code generated by this point (side-effects guaranteed to happen).
So I think it was correct.
| { | ||
| auto const& errorConstructorCall = dynamic_cast<FunctionCall const&>(*arguments[1]); | ||
| appendCode() << m_utils.requireWithErrorFunction(errorConstructorCall) << "(" <<IRVariable(*arguments[0]).name(); | ||
| for (auto argument: errorConstructorCall.arguments()) |
There was a problem hiding this comment.
Does it make sense to add test case where there is zero custom error arguments to make sure that this corner case (zero for loop iterations) also works properly?
error MyError(); require(false, MyError()) and probably error MyError(); require(true, MyError())
There was a problem hiding this comment.
- Would also add a test case, in the same contract, with stripped plain string message and un-stripped custom error message,
require_error_evaluation_order_2.soltests proper order of require arguments evaluation. Does it also test this in case ofrevertStrings: Strip? Probably not.- Similar
require_inherited_error.solalso is tested only for non-strip version. - Same for
require_error_condition_evaluated_only_once.solit's only tested for non-strip version, and it should be tested because it old code had different control flow.
1de843a to
f761ded
Compare
Fix #16465.