Skip to content

Commit 7579af5

Browse files
l46kokcopybara-github
authored andcommitted
Fix planner to search local scopes for identifiers before container resolution
PiperOrigin-RevId: 852950877
1 parent e966e90 commit 7579af5

File tree

5 files changed

+163
-31
lines changed

5 files changed

+163
-31
lines changed

checker/src/main/java/dev/cel/checker/Env.java

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -454,35 +454,66 @@ public Env add(String name, Type type) {
454454
* <p>Returns {@code null} if the function cannot be found.
455455
*/
456456
public @Nullable CelIdentDecl tryLookupCelIdent(CelContainer container, String name) {
457+
// Attempt to find the decl with just the ident name to account for shadowed variables
458+
CelIdentDecl decl = tryLookupCelIdentFromLocalScopes(name);
459+
if (decl != null) {
460+
return decl;
461+
}
462+
457463
for (String cand : container.resolveCandidateNames(name)) {
458-
// First determine whether we know this name already.
459-
CelIdentDecl decl = findIdentDecl(cand);
464+
decl = tryLookupCelIdent(cand);
460465
if (decl != null) {
461466
return decl;
462467
}
468+
}
463469

464-
// Next try to import the name as a reference to a message type.
465-
// This is done via the type provider.
466-
Optional<CelType> type = typeProvider.lookupCelType(cand);
467-
if (type.isPresent()) {
468-
decl = CelIdentDecl.newIdentDeclaration(cand, type.get());
469-
decls.get(0).putIdent(decl);
470-
return decl;
471-
}
470+
return null;
471+
}
472472

473-
// Next try to import this as an enum value by splitting the name in a type prefix and
474-
// the enum inside.
475-
Integer enumValue = typeProvider.lookupEnumValue(cand);
476-
if (enumValue != null) {
477-
decl =
478-
CelIdentDecl.newBuilder()
479-
.setName(cand)
480-
.setType(SimpleType.INT)
481-
.setConstant(CelConstant.ofValue(enumValue))
482-
.build();
473+
private @Nullable CelIdentDecl tryLookupCelIdent(String cand) {
474+
// First determine whether we know this name already.
475+
CelIdentDecl decl = findIdentDecl(cand);
476+
if (decl != null) {
477+
return decl;
478+
}
483479

484-
decls.get(0).putIdent(decl);
485-
return decl;
480+
// Next try to import the name as a reference to a message type.
481+
// This is done via the type provider.
482+
Optional<CelType> type = typeProvider.lookupCelType(cand);
483+
if (type.isPresent()) {
484+
decl = CelIdentDecl.newIdentDeclaration(cand, type.get());
485+
decls.get(0).putIdent(decl);
486+
return decl;
487+
}
488+
489+
// Next try to import this as an enum value by splitting the name in a type prefix and
490+
// the enum inside.
491+
Integer enumValue = typeProvider.lookupEnumValue(cand);
492+
if (enumValue != null) {
493+
decl =
494+
CelIdentDecl.newBuilder()
495+
.setName(cand)
496+
.setType(SimpleType.INT)
497+
.setConstant(CelConstant.ofValue(enumValue))
498+
.build();
499+
500+
decls.get(0).putIdent(decl);
501+
return decl;
502+
}
503+
return null;
504+
}
505+
506+
private @Nullable CelIdentDecl tryLookupCelIdentFromLocalScopes(String name) {
507+
int firstUserSpaceScope = 2;
508+
// Iterate from the top of the stack down to the first local scope.
509+
// Note that:
510+
// Scope 0: Standard environment
511+
// Scope 1: User defined environment
512+
// Scope 2 and onwards: comprehension scopes
513+
for (int i = decls.size() - 1; i >= firstUserSpaceScope; i--) {
514+
CelIdentDecl ident = decls.get(i).getIdent(name);
515+
if (ident != null) {
516+
return ident;
486517
}
487518
}
488519
return null;

runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.google.auto.value.AutoValue;
1818
import com.google.common.collect.ImmutableList;
1919
import com.google.common.collect.ImmutableMap;
20+
import com.google.common.collect.ImmutableSet;
2021
import com.google.errorprone.annotations.CheckReturnValue;
2122
import javax.annotation.concurrent.ThreadSafe;
2223
import dev.cel.common.CelAbstractSyntaxTree;
@@ -64,7 +65,6 @@ public final class ProgramPlanner {
6465
private final CelContainer container;
6566
private final CelOptions options;
6667

67-
6868
/**
6969
* Plans a {@link Program} from the provided parsed-only or type-checked {@link
7070
* CelAbstractSyntaxTree}.
@@ -155,8 +155,15 @@ private PlannedInterpretable planIdent(CelExpr celExpr, PlannerContext ctx) {
155155
return planCheckedIdent(celExpr.id(), ref, ctx.typeMap());
156156
}
157157

158-
return EvalAttribute.create(
159-
celExpr.id(), attributeFactory.newMaybeAttribute(celExpr.ident().name()));
158+
String name = celExpr.ident().name();
159+
// If the identifier matches a variable that's available in local scope (from a comprehension)
160+
// force it to resolve as an absolute attribute (simple name only), bypassing container
161+
// resolution.
162+
if (ctx.scopedVariables().contains(name)) {
163+
return EvalAttribute.create(celExpr.id(), attributeFactory.newAbsoluteAttribute(name));
164+
}
165+
166+
return EvalAttribute.create(celExpr.id(), attributeFactory.newMaybeAttribute(name));
160167
}
161168

162169
private PlannedInterpretable planCheckedIdent(
@@ -291,9 +298,19 @@ private PlannedInterpretable planComprehension(CelExpr expr, PlannerContext ctx)
291298

292299
PlannedInterpretable accuInit = plan(comprehension.accuInit(), ctx);
293300
PlannedInterpretable iterRange = plan(comprehension.iterRange(), ctx);
294-
PlannedInterpretable loopCondition = plan(comprehension.loopCondition(), ctx);
295-
PlannedInterpretable loopStep = plan(comprehension.loopStep(), ctx);
296-
PlannedInterpretable result = plan(comprehension.result(), ctx);
301+
302+
PlannerContext innerCtx;
303+
if (comprehension.iterVar2().isEmpty()) {
304+
innerCtx = ctx.addScopedIdentifiers(comprehension.iterVar(), comprehension.accuVar());
305+
} else {
306+
innerCtx =
307+
ctx.addScopedIdentifiers(
308+
comprehension.iterVar(), comprehension.iterVar2(), comprehension.accuVar());
309+
}
310+
311+
PlannedInterpretable loopCondition = plan(comprehension.loopCondition(), innerCtx);
312+
PlannedInterpretable loopStep = plan(comprehension.loopStep(), innerCtx);
313+
PlannedInterpretable result = plan(comprehension.result(), innerCtx);
297314

298315
return EvalFold.create(
299316
expr.id(),
@@ -444,8 +461,23 @@ abstract static class PlannerContext {
444461

445462
abstract ImmutableMap<Long, CelType> typeMap();
446463

464+
/** Tracks variables that are defined in the current scope (e.g. comprehensions). */
465+
abstract ImmutableSet<String> scopedVariables();
466+
447467
private static PlannerContext create(CelAbstractSyntaxTree ast) {
448-
return new AutoValue_ProgramPlanner_PlannerContext(ast.getReferenceMap(), ast.getTypeMap());
468+
return new AutoValue_ProgramPlanner_PlannerContext(
469+
ast.getReferenceMap(), ast.getTypeMap(), ImmutableSet.of());
470+
}
471+
472+
/** Returns a new context with the provided variables added to the local scope. */
473+
PlannerContext addScopedIdentifiers(String... variables) {
474+
ImmutableSet.Builder<String> newLocals =
475+
ImmutableSet.builderWithExpectedSize(scopedVariables().size() + variables.length);
476+
newLocals.addAll(scopedVariables());
477+
newLocals.add(variables);
478+
479+
return new AutoValue_ProgramPlanner_PlannerContext(
480+
referenceMap(), typeMap(), newLocals.build());
449481
}
450482
}
451483

runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,13 +851,69 @@ public void plan_comprehension_iterationLimit_success() throws Exception {
851851
ImmutableList.of(2L, 3L), ImmutableList.of(3L, 4L), ImmutableList.of(4L, 5L)));
852852
}
853853

854+
@Test
855+
public void plan_comprehension_withContainer_comprehensionVariableShadowed() throws Exception {
856+
ProgramPlanner planner =
857+
ProgramPlanner.newPlanner(
858+
TYPE_PROVIDER,
859+
ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO),
860+
newDispatcher(),
861+
CEL_VALUE_CONVERTER,
862+
CelContainer.ofName("com"),
863+
CEL_OPTIONS);
864+
CelAbstractSyntaxTree ast = compile("[0].exists(int_var, int_var == 0)");
865+
866+
Program program = planner.plan(ast);
867+
868+
boolean result = (boolean) program.eval(ImmutableMap.of("com.int_var", 1));
869+
870+
assertThat(result).isTrue();
871+
}
872+
873+
@Test
874+
public void plan_ident_withContainer_resolutionOrderHolds() throws Exception {
875+
CelContainer container = CelContainer.ofName("foo");
876+
CelCompiler celCompiler =
877+
CelCompilerFactory.standardCelCompilerBuilder()
878+
.setContainer(container)
879+
.addVar("bar", SimpleType.INT)
880+
.addVar("foo.bar", SimpleType.INT)
881+
.build();
882+
ProgramPlanner planner =
883+
ProgramPlanner.newPlanner(
884+
TYPE_PROVIDER,
885+
ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO),
886+
newDispatcher(),
887+
CEL_VALUE_CONVERTER,
888+
container,
889+
CEL_OPTIONS);
890+
CelAbstractSyntaxTree ast = compile(celCompiler, "bar");
891+
892+
Program program = planner.plan(ast);
893+
894+
Long result =
895+
(Long)
896+
program.eval(
897+
ImmutableMap.of(
898+
"bar", 1,
899+
"foo.bar", 2));
900+
901+
// When there's no shadowed variables, container resolution should always take precedence (e.g:
902+
// `foo.bar`)
903+
assertThat(result).isEqualTo(2);
904+
}
905+
854906
private CelAbstractSyntaxTree compile(String expression) throws Exception {
855-
CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst();
907+
return compile(CEL_COMPILER, expression);
908+
}
909+
910+
private CelAbstractSyntaxTree compile(CelCompiler compiler, String expression) throws Exception {
911+
CelAbstractSyntaxTree ast = compiler.parse(expression).getAst();
856912
if (isParseOnly) {
857913
return ast;
858914
}
859915

860-
return CEL_COMPILER.check(ast).getAst();
916+
return compiler.check(ast).getAst();
861917
}
862918

863919
private static CelByteString concatenateByteArrays(CelByteString bytes1, CelByteString bytes2) {

runtime/src/test/resources/comprehension.baseline

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ Source: [0, 1, 2].exists(x, x > 2)
1212
=====>
1313
bindings: {}
1414
result: false
15+
16+
Source: [0].exists(x, x == 0)
17+
declare com.x {
18+
value int
19+
}
20+
=====>
21+
bindings: {com.x=1}
22+
result: true

testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,11 @@ public void comprehension() throws Exception {
858858

859859
source = "[0, 1, 2].exists(x, x > 2)";
860860
runTest();
861+
862+
declareVariable("com.x", SimpleType.INT);
863+
container = CelContainer.ofName("com");
864+
source = "[0].exists(x, x == 0)";
865+
runTest(ImmutableMap.of("com.x", 1));
861866
}
862867

863868
@Test

0 commit comments

Comments
 (0)