Skip to content

Commit 827889e

Browse files
Cristina Borzacopybara-github
authored andcommitted
Fix a bug in the assertAstIdCorrectness method and avoid re-checking the AST in CelOptimizer if it was not modified.
PiperOrigin-RevId: 976199136
1 parent 2bb86d0 commit 827889e

2 files changed

Lines changed: 92 additions & 19 deletions

File tree

‎optimizer/src/main/java/dev/cel/optimizer/CelOptimizerImpl.java‎

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package dev.cel.optimizer;
1616

1717
import static com.google.common.base.Preconditions.checkNotNull;
18+
import static com.google.common.base.Preconditions.checkState;
1819

1920
import com.google.common.collect.ImmutableSet;
2021
import dev.cel.bundle.Cel;
@@ -44,6 +45,9 @@ final class CelOptimizerImpl implements CelOptimizer {
4445
}
4546

4647
@Override
48+
// AstOptimizers return the same AST instance if no changes are made. Using != avoids deep
49+
// .equals() comparison.
50+
@SuppressWarnings("ReferenceEquality")
4751
public CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree ast) throws CelOptimizationException {
4852
if (!ast.isChecked()) {
4953
throw new IllegalArgumentException("AST must be type-checked.");
@@ -64,16 +68,18 @@ public CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree ast) throws CelOptim
6468

6569
OptimizationResult result = optimizer.optimize(optimizedAst, celOptimizerEnv);
6670

67-
if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) {
68-
celOptimizerEnv =
69-
celOptimizerEnv
70-
.toCelBuilder()
71-
.addVarDeclarations(result.newVarDecls())
72-
.addFunctionDeclarations(result.newFunctionDecls())
73-
.build();
71+
if (result.optimizedAst() != optimizedAst) {
72+
if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) {
73+
celOptimizerEnv =
74+
celOptimizerEnv
75+
.toCelBuilder()
76+
.addVarDeclarations(result.newVarDecls())
77+
.addFunctionDeclarations(result.newFunctionDecls())
78+
.build();
79+
}
80+
optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst();
81+
assertAstIdCorrectness(optimizedAst);
7482
}
75-
optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst();
76-
assertAstIdCorrectness(optimizedAst);
7783

7884
for (CelOptimizerListener listener : listeners) {
7985
listener.onPassEnd(optimizer, preAst, optimizedAst);
@@ -131,19 +137,26 @@ private static void assertAstIdCorrectness(CelAbstractSyntaxTree ast) {
131137
return;
132138
}
133139

134-
if (astExpr.exprKind().getKind().equals(Kind.COMPREHENSION)) {
135-
if (!macroExpr.exprKind().getKind().equals(Kind.NOT_SET)) {
136-
throw new IllegalStateException(
137-
String.format(
138-
"Expected macro call node %d to be NOT_SET for comprehension, but"
139-
+ " was %s.",
140-
macroExpr.id(), macroExpr.exprKind().getKind()));
141-
}
140+
if (macroExpr.exprKind().getKind().equals(Kind.NOT_SET)) {
141+
// If a macro node is NOT_SET, its ID must be present in the main AST.
142+
checkState(
143+
ast.getSource().getMacroCalls().containsKey(macroExpr.id()),
144+
"Expected macro call node %s to be present in macro calls map, but was not.",
145+
macroExpr.id());
146+
} else if (astExpr.exprKind().getKind().equals(Kind.COMPREHENSION)) {
147+
// We encountered something other than NOT_SET in macro source for comprehension
148+
// node. This is an error.
149+
throw new IllegalStateException(
150+
String.format(
151+
"Expected macro call node %d to be NOT_SET for comprehension, but was"
152+
+ " %s.",
153+
macroExpr.id(), macroExpr.exprKind().getKind()));
142154
} else if (!macroExpr.exprKind().getKind().equals(astExpr.exprKind().getKind())) {
155+
// Otherwise for all cases, the AST node should match exactly.
143156
throw new IllegalStateException(
144157
String.format(
145-
"Macro call node %d kind mismatch: expected %s (from AST), but was %s"
146-
+ " (in macro call).",
158+
"Macro call node %d kind mismatch: expected %s (from AST), but was %s (in"
159+
+ " macro call).",
147160
macroExpr.id(),
148161
astExpr.exprKind().getKind(),
149162
macroExpr.exprKind().getKind()));

‎optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,66 @@ public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Except
702702
assertThat(e).hasMessageThat().doesNotContain("Cycle detected");
703703
}
704704

705+
@Test
706+
public void cse_nestedMacro_noOp_assertAstIdCorrectness() throws Exception {
707+
Cel cel =
708+
runtimeFlavor
709+
.builder()
710+
.addVar("x", SimpleType.DYN)
711+
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
712+
.setOptions(CelOptions.current().populateMacroCalls(true).build())
713+
.addCompilerLibraries(CelExtensions.comprehensions())
714+
.addRuntimeLibraries(CelExtensions.comprehensions())
715+
.build();
716+
CelOptimizer celOptimizer =
717+
CelOptimizerFactory.standardCelOptimizerBuilder(cel)
718+
.addAstOptimizers(SubexpressionOptimizer.getInstance())
719+
.build();
720+
CelAbstractSyntaxTree ast =
721+
cel.compile("[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(x.a))").getAst();
722+
723+
CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
724+
725+
assertThat(CEL_UNPARSER.unparse(optimizedAst))
726+
.isEqualTo("[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(x.a))");
727+
assertThat(optimizedAst).isSameInstanceAs(ast);
728+
}
729+
730+
@Test
731+
public void cse_nestedMacro_withOptimization_assertAstIdCorrectness() throws Exception {
732+
Cel cel =
733+
runtimeFlavor
734+
.builder()
735+
.addVar("x", SimpleType.DYN)
736+
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
737+
.setOptions(
738+
CelOptions.current()
739+
.populateMacroCalls(true)
740+
.enableHeterogeneousNumericComparisons(true)
741+
.build())
742+
.addCompilerLibraries(CelExtensions.comprehensions())
743+
.addRuntimeLibraries(CelExtensions.comprehensions())
744+
.build();
745+
CelOptimizer celOptimizer =
746+
CelOptimizerFactory.standardCelOptimizerBuilder(cel)
747+
.addAstOptimizers(
748+
SubexpressionOptimizer.newInstance(
749+
SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build()))
750+
.build();
751+
CelAbstractSyntaxTree ast =
752+
cel.compile(
753+
"[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(x.a)) == [{}, {\"a\": 1}, {\"b\":"
754+
+ " 2}].filter(m, has(x.a))")
755+
.getAst();
756+
757+
CelAbstractSyntaxTree optimizedAst = celOptimizer.optimize(ast);
758+
759+
assertThat(CEL_UNPARSER.unparse(optimizedAst))
760+
.isEqualTo(
761+
"cel.@block([[{}, {\"a\": 1}, {\"b\": 2}].filter(@it:0:0, has(x.a))], @index0 =="
762+
+ " @index0)");
763+
}
764+
705765
/**
706766
* Converts AST containing cel.block related test functions to internal functions (e.g: cel.block
707767
* -> cel.@block)

0 commit comments

Comments
 (0)