Skip to content

Commit f2cd3c1

Browse files
l46kokcopybara-github
authored andcommitted
Validate cel.@block expressions in a single visitor pass
PiperOrigin-RevId: 996873631
1 parent 715d34b commit f2cd3c1

4 files changed

Lines changed: 109 additions & 49 deletions

File tree

‎common/ast/BUILD.bazel‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ java_library(
4848
exports = ["//common/src/main/java/dev/cel/common/ast:cel_expr_visitor"],
4949
)
5050

51+
cel_android_library(
52+
name = "cel_expr_visitor_android",
53+
exports = ["//common/src/main/java/dev/cel/common/ast:cel_expr_visitor_android"],
54+
)
55+
5156
java_library(
5257
name = "expr_factory",
5358
exports = ["//common/src/main/java/dev/cel/common/ast:expr_factory"],

‎common/src/main/java/dev/cel/common/ast/BUILD.bazel‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package(
77
],
88
default_visibility = [
99
"//common/ast:__pkg__",
10+
"//publish:__pkg__",
1011
],
1112
)
1213

@@ -64,9 +65,9 @@ java_library(
6465
],
6566
deps = [
6667
":ast",
68+
":cel_expr_visitor",
6769
"//common:cel_ast",
6870
"//common/annotations",
69-
"//common/navigation",
7071
"@maven//:com_google_guava_guava",
7172
],
7273
)
@@ -78,9 +79,9 @@ cel_android_library(
7879
],
7980
deps = [
8081
":ast_android",
82+
":cel_expr_visitor_android",
8183
"//common:cel_ast_android",
8284
"//common/annotations",
83-
"//common/navigation:navigation_android",
8485
"@maven_android//:com_google_guava_guava",
8586
],
8687
)
@@ -143,6 +144,17 @@ java_library(
143144
],
144145
)
145146

147+
cel_android_library(
148+
name = "cel_expr_visitor_android",
149+
srcs = ["CelExprVisitor.java"],
150+
tags = [
151+
],
152+
deps = [
153+
":ast_android",
154+
"//common:cel_ast_android",
155+
],
156+
)
157+
146158
java_library(
147159
name = "expr_factory",
148160
srcs = EXPR_FACTORY_SOURCES,

‎common/src/main/java/dev/cel/common/ast/CelBlock.java‎

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414

1515
package dev.cel.common.ast;
1616

17-
import static com.google.common.collect.ImmutableList.toImmutableList;
18-
1917
import com.google.common.base.Preconditions;
2018
import com.google.common.collect.ImmutableList;
2119
import dev.cel.common.CelAbstractSyntaxTree;
2220
import dev.cel.common.annotations.Internal;
23-
import dev.cel.common.navigation.CelNavigableExpr;
21+
import dev.cel.common.ast.CelExpr.CelCall;
22+
import dev.cel.common.ast.CelExpr.CelIdent;
23+
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
2424
import java.util.Optional;
2525

2626
/**
@@ -61,27 +61,17 @@ public CelExpr expr() {
6161
* @throws IllegalArgumentException if the block is malformed or its indices are invalid.
6262
*/
6363
public static Optional<CelBlock> extract(CelAbstractSyntaxTree ast) {
64-
CelNavigableExpr celNavigableExpr = CelNavigableExpr.fromExpr(ast.getExpr());
65-
66-
ImmutableList<CelExpr> allCelBlocks =
67-
celNavigableExpr
68-
.allNodes()
69-
.map(CelNavigableExpr::expr)
70-
.filter(expr -> expr.callOrDefault().function().equals(FUNCTION_NAME))
71-
.collect(toImmutableList());
72-
if (allCelBlocks.isEmpty()) {
64+
CelExpr root = ast.getExpr();
65+
if (!isBlockCall(root)) {
66+
new BlockValidator(root).validate(root, 0);
7367
return Optional.empty();
7468
}
7569

76-
Preconditions.checkArgument(
77-
allCelBlocks.size() == 1,
78-
"Expected 1 cel.block function to be present but found %s",
79-
allCelBlocks.size());
80-
Preconditions.checkArgument(
81-
celNavigableExpr.expr().equals(allCelBlocks.get(0)),
82-
"Expected cel.block to be present at root");
70+
return Optional.of(fromExpr(root));
71+
}
8372

84-
return Optional.of(fromExpr(allCelBlocks.get(0)));
73+
private static boolean isBlockCall(CelExpr expr) {
74+
return expr.getKind().equals(Kind.CALL) && expr.call().function().equals(FUNCTION_NAME);
8575
}
8676

8777
/**
@@ -100,45 +90,71 @@ private static CelBlock fromExpr(CelExpr expr) {
10090
Preconditions.checkArgument(
10191
expr.call().args().get(0).exprKind().getKind() == CelExpr.ExprKind.Kind.LIST,
10292
"Expected first argument of cel.@block to be a list");
93+
Preconditions.checkArgument(
94+
!expr.call().target().isPresent(), "Expected cel.@block to be a global call");
10395

10496
CelBlock block = new CelBlock(expr);
97+
BlockValidator validator = new BlockValidator(expr);
10598

10699
// Assert correctness on block indices used in subexpressions
107100
ImmutableList<CelExpr> subexprs = block.indices();
108101
for (int i = 0; i < subexprs.size(); i++) {
109-
verifyBlockIndex(subexprs.get(i), i, expr);
102+
validator.validate(subexprs.get(i), i);
110103
}
111104

112105
// Assert correctness on block indices used in block result
113-
CelExpr blockResult = block.result();
114-
verifyBlockIndex(blockResult, subexprs.size(), expr);
115-
boolean resultHasAtLeastOneBlockIndex =
116-
CelNavigableExpr.fromExpr(blockResult)
117-
.allNodes()
118-
.map(CelNavigableExpr::expr)
119-
.anyMatch(e -> e.identOrDefault().name().startsWith(INDEX_PREFIX));
120106
Preconditions.checkArgument(
121-
resultHasAtLeastOneBlockIndex,
107+
validator.validate(block.result(), subexprs.size()),
122108
"Expected at least one reference of index in cel.block result");
123109

124110
return block;
125111
}
126112

127-
private static void verifyBlockIndex(CelExpr celExpr, int maxIndexValue, CelExpr rootBlock) {
128-
boolean areAllIndicesValid =
129-
CelNavigableExpr.fromExpr(celExpr)
130-
.allNodes()
131-
.map(CelNavigableExpr::expr)
132-
.filter(expr -> expr.identOrDefault().name().startsWith(INDEX_PREFIX))
133-
.map(CelExpr::ident)
134-
.allMatch(
135-
blockIdent ->
136-
Integer.parseInt(blockIdent.name().substring(INDEX_PREFIX.length()))
137-
< maxIndexValue);
138-
Preconditions.checkArgument(
139-
areAllIndicesValid,
140-
"Illegal block index found. The index value must be less than %s. Expr: %s",
141-
maxIndexValue,
142-
rootBlock);
113+
private static final class BlockValidator extends CelExprVisitor {
114+
private final CelExpr root;
115+
private int maxIndexValue;
116+
private boolean hasBlockIndex;
117+
118+
private boolean validate(CelExpr expr, int maxIndexValue) {
119+
this.maxIndexValue = maxIndexValue;
120+
this.hasBlockIndex = false;
121+
visit(expr);
122+
return hasBlockIndex;
123+
}
124+
125+
@Override
126+
public void visit(CelExpr expr) {
127+
if (!expr.getKind().equals(Kind.NOT_SET)) {
128+
super.visit(expr);
129+
}
130+
}
131+
132+
@Override
133+
protected void visit(CelExpr expr, CelCall call) {
134+
if (call.function().equals(FUNCTION_NAME)) {
135+
throw new IllegalArgumentException(
136+
isBlockCall(root)
137+
? "Expected 1 cel.block function to be present but found 2"
138+
: "Expected cel.block to be present at root");
139+
}
140+
super.visit(expr, call);
141+
}
142+
143+
@Override
144+
protected void visit(CelExpr expr, CelIdent ident) {
145+
if (ident.name().startsWith(INDEX_PREFIX)) {
146+
hasBlockIndex = true;
147+
int indexValue = Integer.parseInt(ident.name().substring(INDEX_PREFIX.length()));
148+
Preconditions.checkArgument(
149+
indexValue >= 0 && indexValue < maxIndexValue,
150+
"Illegal block index found. The index value must be less than %s. Expr: %s",
151+
maxIndexValue,
152+
root);
153+
}
154+
}
155+
156+
private BlockValidator(CelExpr root) {
157+
this.root = root;
158+
}
143159
}
144160
}

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.common.truth.Truth.assertThat;
1818
import static dev.cel.common.CelOverloadDecl.newGlobalOverload;
19+
import static dev.cel.optimizer.optimizers.SubexpressionOptimizer.verifyOptimizedAstCorrectness;
1920
import static org.junit.Assert.assertThrows;
2021

2122
import com.google.common.collect.ImmutableList;
@@ -641,6 +642,32 @@ public void verifyOptimizedAstCorrectness_indexOutOfBounds_throws(String source)
641642
.contains("Illegal block index found. The index value must be less than");
642643
}
643644

645+
@Test
646+
public void verifyOptimizedAstCorrectness_negativeIndex_throws() throws Exception {
647+
CelMutableAst mutableAst =
648+
CelMutableAst.fromCelAst(compileUsingInternalFunctions("cel.block([1, 2], index0)", true));
649+
mutableAst.expr().call().args().get(1).ident().setName("@index-1");
650+
CelAbstractSyntaxTree ast = mutableAst.toParsedAst();
651+
652+
IllegalArgumentException e =
653+
assertThrows(IllegalArgumentException.class, () -> verifyOptimizedAstCorrectness(ast));
654+
assertThat(e)
655+
.hasMessageThat()
656+
.contains("Illegal block index found. The index value must be less than");
657+
}
658+
659+
@Test
660+
public void verifyOptimizedAstCorrectness_celBlockHasTarget_throws() throws Exception {
661+
CelMutableAst mutableAst =
662+
CelMutableAst.fromCelAst(compileUsingInternalFunctions("cel.block([1, 2], index0)", true));
663+
mutableAst.expr().call().setTarget(mutableAst.expr().call().args().get(1));
664+
CelAbstractSyntaxTree ast = mutableAst.toParsedAst();
665+
666+
IllegalArgumentException e =
667+
assertThrows(IllegalArgumentException.class, () -> verifyOptimizedAstCorrectness(ast));
668+
assertThat(e).hasMessageThat().isEqualTo("Expected cel.@block to be a global call");
669+
}
670+
644671
@Test
645672
@TestParameters("{source: 'cel.block([index0], index0)'}")
646673
@TestParameters("{source: 'cel.block([1, index1, 2], index2)'}")
@@ -678,9 +705,9 @@ public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Except
678705
CelAbstractSyntaxTree ast =
679706
compileUsingInternalFunctions(
680707
"cel.block([1/0 > 0], (index0 && false) || (index0 && true))");
708+
Program program = cel.createProgram(ast);
681709

682-
CelEvaluationException e =
683-
assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval());
710+
CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval);
684711

685712
assertThat(e).hasMessageThat().contains("/ by zero");
686713
assertThat(e).hasMessageThat().doesNotContain("Cycle detected");

0 commit comments

Comments
 (0)