Skip to content

Yul expression simplifier: Don't substitute out of scope variables #16161

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

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Compiler Features:

Bugfixes:
* Assembler: Fix not using a fixed-width type for IDs being assigned to subassemblies nested more than one level away, resulting in inconsistent `--asm-json` output between target architectures.
* Yul Optimizer: Fix edge case in which invalid Yul code is produced by ExpressionSimplifier due to expressions being substituted that contain out-of-scope variables.

### 0.8.30 (2025-05-07)

Expand Down
15 changes: 14 additions & 1 deletion libyul/optimiser/ExpressionSimplifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <libyul/optimiser/SimplificationRules.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/AST.h>
#include <libyul/Utilities.h>

Expand All @@ -45,7 +46,19 @@ void ExpressionSimplifier::visit(Expression& _expression)
while (auto const* match = SimplificationRules::findFirstMatch(
_expression,
m_dialect,
[this](YulName _var) { return variableValue(_var); }
[this](YulName const& _var) -> AssignedValue const* {
AssignedValue const* value = variableValue(_var);
if (!value || !value->value)
return nullptr;

// check that all variables in the value expression are in current scope
MovableChecker const checker(m_dialect, *value->value);
for (YulName const& referencedVar: checker.referencedVariables())
if (!inScope(referencedVar))
return nullptr; // don't substitute if any referenced var is out of scope

return value;
}
))
{
auto const* evmDialect = dynamic_cast<EVMDialect const*>(&m_dialect);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--strict-assembly --optimize --yul-optimizations T:ts --bin
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
function identity(value) -> ret {
ret := value
}
function fun_fun_31() -> ret {
let x := 0x01
let expr_9 := identity(x)

let expr_10 := 0x01
let expr_25 := 0x05
switch expr_10
case 0 { }
default {
let expr_14 := 0x06
let expr_18 := identity(x)
expr_25 := or(expr_14, expr_18)
}
// expression simplifier spuriously gets a match on this: `expr_25 == or(0x06, expr_18)` where expr_18 is
// from the inner scope
ret := or(expr_9, expr_25)
leave
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

======= input.yul (EVM) =======

Binary representation:
00