Skip to content

Do not remove custom errors in IR codegen when strip revert strings is requested#16466

Open
matheusaaguiar wants to merge 3 commits intodevelopfrom
fixRevertStringFlagInconsistentHandling
Open

Do not remove custom errors in IR codegen when strip revert strings is requested#16466
matheusaaguiar wants to merge 3 commits intodevelopfrom
fixRevertStringFlagInconsistentHandling

Conversation

@matheusaaguiar
Copy link
Contributor

Fix #16465.

@argotorg argotorg deleted a comment from stackenbotten3000 Feb 11, 2026
@matheusaaguiar matheusaaguiar changed the title Make legacy codege remove custom errors when compiler option revert-strings strip is requested Make legacy codegen remove custom errors when compiler option revert-strings strip is requested Feb 11, 2026
@matheusaaguiar matheusaaguiar changed the title Make legacy codegen remove custom errors when compiler option revert-strings strip is requested Make legacy codegen remove custom errors when compiler option revert-strings=strip is requested Feb 11, 2026
@cameel
Copy link
Collaborator

cameel commented Feb 11, 2026

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.

Copy link
Contributor

@nikola-matic nikola-matic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@matheusaaguiar matheusaaguiar force-pushed the fixRevertStringFlagInconsistentHandling branch 3 times, most recently from 4913b7d to 8450ab7 Compare February 26, 2026 05:43
@matheusaaguiar matheusaaguiar changed the title Make legacy codegen remove custom errors when compiler option revert-strings=strip is requested Do not remove custom errors in IR codegen when strip revert strings is requested Feb 26, 2026
@cameel cameel requested a review from rodiazet March 16, 2026 13:25
@matheusaaguiar matheusaaguiar force-pushed the fixRevertStringFlagInconsistentHandling branch 3 times, most recently from f3491de to 1de843a Compare March 17, 2026 01:38
Copy link
Contributor

@rodiazet rodiazet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add:

pragma solidity >=0.0;
// SPDX-License-Identifier: GPL-3.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

@matheusaaguiar matheusaaguiar Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()));
Copy link
Contributor

@rodiazet rodiazet Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about tests where require's first arguments cannot be evaluated at compile time? Do you think it makes sense to add them?

Copy link
Contributor Author

@matheusaaguiar matheusaaguiar Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

@rodiazet rodiazet Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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())
Copy link
Contributor

@rodiazet rodiazet Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Would also add a test case, in the same contract, with stripped plain string message and un-stripped custom error message,
  2. require_error_evaluation_order_2.sol tests proper order of require arguments evaluation. Does it also test this in case of revertStrings: Strip? Probably not.
  3. Similar require_inherited_error.sol also is tested only for non-strip version.
  4. Same for require_error_condition_evaluated_only_once.sol it's only tested for non-strip version, and it should be tested because it old code had different control flow.

@matheusaaguiar matheusaaguiar force-pushed the fixRevertStringFlagInconsistentHandling branch from 1de843a to f761ded Compare March 18, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compiler option revert-strings with value strip has different treatments in legacy and IR pipeline

4 participants