Fix named error parameter encoding order in IR codegen - #16453
Fix named error parameter encoding order in IR codegen#16453nikola-matic wants to merge 4 commits into
Conversation
01fe6b9 to
0f978ea
Compare
cameel
left a comment
There was a problem hiding this comment.
This needs a lot more tests and a bug list entry.
a6503ed to
8bd6e00
Compare
8bd6e00 to
071ea8a
Compare
071ea8a to
4524716
Compare
6c95a91 to
04f0ce0
Compare
| @@ -1,4 +1,17 @@ | |||
| [ | |||
| { | |||
| "uid": "SOL-2026-2", | |||
There was a problem hiding this comment.
Just a heads-up to bump this in case we merge #16508 first.
There was a problem hiding this comment.
And another heads-up for the date in blog post link.
| auto const& errorConstructorCall = dynamic_cast<FunctionCall const&>(*arguments[1]); | ||
| appendCode() << m_utils.requireWithErrorFunction(errorConstructorCall) << "(" <<IRVariable(*arguments[0]).name(); | ||
| for (auto argument: errorConstructorCall.arguments()) | ||
| for (auto argument: errorConstructorCall.sortedArguments()) |
There was a problem hiding this comment.
I think that there are still some things we can do to make these errors less likely without going for full #16529. One of them is to always use sortedArguments() by default.
We have very few places where we need arguments in call order. I looked through the code and in almost all cases where arguments() is invoked, sortedArguments() would both would work, either because the function has fewer than two arguments or because it's a case where we don't allow named arguments. We should change all of them to sortedArguments(). This may prevent more bugs creeping in when we extend named parameter support to more places. People will also be less likely to choose arguments() just because the surrounding code uses it.
I would also rename them to argumentsInDefinitionOrder() and argumentsInCallOrder(). The current naming makes it seem like the arguments() is the default one and sortedArguments() is something special. The docstring should also have a big fat warning explaining the difference.
There was a problem hiding this comment.
This, along with the extra tests for named parameters could be extracted into a separate PR and merged quicker.
There was a problem hiding this comment.
IIRC you said on the chat you had some problem with sortedArguments() so you left some of them as is. What was that specifically?
There was a problem hiding this comment.
IIRC you said on the chat you had some problem with
sortedArguments()so you left some of them as is. What was that specifically?
The issues with them mostly lie on the frontend side whilst AST & annotations are still being populated/changed, which then causes some asserts to be triggered. All of the backend ones have been replaced though and are fine.
There was a problem hiding this comment.
Also, regarding the naming - I would strongly prefer doing that in a follow up PR as it's going to touch significantly more occurrences than were changed here.
There was a problem hiding this comment.
Sure, doing the renaming in a follow-up is fine.
| revert NamedArgsError3({c: 9, a: 2, b: 7}); | ||
| } | ||
| function trigger4() external pure { | ||
| revert NamedArgsError4({b: "error", a: 2, c: 9, d: true}); |
There was a problem hiding this comment.
Wait, I thought mismatched types would produce an error without the fix, but I just checked this example on 0.8.34 and it still compiles. Makes sense given that the error is in the codegen, not in analysis, but this makes the bug worse than we assumed based on the information we had when we discussed it. Why didn't you say anything about it?
When we discussed this, the assumption was that in most cases bad code would not even compile due to type mismatch and you could at most swap two numbers or something. If any combination compiles, it's much easier to run into the bug.
The consequences are also a bit different. Now you can have errors whose encoded parameters do not match the selector. Depending on how the decoder works, it may result in a decoding failure, which is at least better than accepting broken data (but still worse than a compilation error).
There was a problem hiding this comment.
Have you started working on the blog post? We actually still need to prepare and send the proper impact analysis first. You can explore the consequences of the above in it.
04f0ce0 to
3529a21
Compare
cameel
left a comment
There was a problem hiding this comment.
The fix itself and test coverage seem mostly fine now (just some minor tweaks left), but there are still unresolved comments regarding the bug list entry. We also have to take a second look at the impact analysis in the light of #16453 (comment).
e51ddcb to
5ed545f
Compare
91d80d7 to
b6b4ffe
Compare
b6b4ffe to
3a88bc1
Compare
| // bytesAndBool() -> FAILURE, hex"ea504c15", 0x40, 1, 4, left(0xdeadbeef) | ||
| // mixedDynamic() -> FAILURE, hex"e45ff7f0", 0x80, 7, 0xc0, 1, 4, "test", 2, left(0xcafe) |
There was a problem hiding this comment.
| // bytesAndBool() -> FAILURE, hex"ea504c15", 0x40, 1, 4, left(0xdeadbeef) | |
| // mixedDynamic() -> FAILURE, hex"e45ff7f0", 0x80, 7, 0xc0, 1, 4, "test", 2, left(0xcafe) | |
| // bytesAndBool() -> FAILURE, hex"ea504c15", 0x40, true, 4, left(0xdeadbeef) | |
| // mixedDynamic() -> FAILURE, hex"e45ff7f0", 0x80, 7, 0xc0, true, 4, "test", 2, left(0xcafe) |
Same in require_error_named_parameters_literal_conversions.sol.
| "uid": "SOL-2026-4", | ||
| "name": "MisorderedNamedParametersInRequireWithCustomErrors", | ||
| "summary": "Custom error arguments passed to `require` using named-parameter syntax are ABI-encoded in call-site order instead of declaration order when compiling via IR.", | ||
| "description": "When a custom error is passed as the second argument of `require` and instantiated using named-parameter syntax (e.g., `require(cond, MyError({b: 1, a: 2}))`), the IR-based code generator encodes the arguments in the order they appear at the call site rather than reordering them to match the parameter order declared in the error definition. The arguments are still type-checked against their named slots, but the type system does not require the call-site order to match the declaration, so the bug can be triggered even when the supplied values would type-check in either order. When all arguments occupy the same number of stack slots (e.g., all value types), the resulting payload contains the values in the wrong positions for the error's signature. When the call-site argument types differ in stack-slot width (for example a `calldata` reference next to a value type), the misordering also misaligns the helper's parameters slot-by-slot, producing a payload of an entirely different shape than the error's ABI signature — values land in unrelated positions, dynamic-type lengths are read from arbitrary stack values, and string/bytes data may be copied from arbitrary calldata offsets, in the worst case triggering an EVM-level error during encoding. The bug is specific to custom errors passed as the second argument of `require`. Standalone `revert ErrorName({...})` statements, event emissions, function calls, struct constructor invocations and other constructs supporting named parameters are not affected. The evmasm pipeline is also unaffected.", |
There was a problem hiding this comment.
We use double backticks in these.
| "uid": "SOL-2026-4", | ||
| "name": "MisorderedNamedParametersInRequireWithCustomErrors", | ||
| "summary": "Custom error arguments passed to `require` using named-parameter syntax are ABI-encoded in call-site order instead of declaration order when compiling via IR.", | ||
| "description": "When a custom error is passed as the second argument of `require` and instantiated using named-parameter syntax (e.g., `require(cond, MyError({b: 1, a: 2}))`), the IR-based code generator encodes the arguments in the order they appear at the call site rather than reordering them to match the parameter order declared in the error definition. The arguments are still type-checked against their named slots, but the type system does not require the call-site order to match the declaration, so the bug can be triggered even when the supplied values would type-check in either order. When all arguments occupy the same number of stack slots (e.g., all value types), the resulting payload contains the values in the wrong positions for the error's signature. When the call-site argument types differ in stack-slot width (for example a `calldata` reference next to a value type), the misordering also misaligns the helper's parameters slot-by-slot, producing a payload of an entirely different shape than the error's ABI signature — values land in unrelated positions, dynamic-type lengths are read from arbitrary stack values, and string/bytes data may be copied from arbitrary calldata offsets, in the worst case triggering an EVM-level error during encoding. The bug is specific to custom errors passed as the second argument of `require`. Standalone `revert ErrorName({...})` statements, event emissions, function calls, struct constructor invocations and other constructs supporting named parameters are not affected. The evmasm pipeline is also unaffected.", |
There was a problem hiding this comment.
When all arguments occupy the same number of stack slots (e.g., all value types)
There are value types that occupy two stack slots (external function pointers).
in the worst case triggering an EVM-level error during encoding.
That's actually the best case :) The worst one is successfully encoding the wrong thing.
3a88bc1 to
dc9457b
Compare
dc9457b to
dbc7be8
Compare
Closes #16452