4444import dev .cel .policy .CelCompiledRule .CelCompiledMatch .Result ;
4545import dev .cel .policy .CelCompiledRule .CelCompiledMatch .Result .Kind ;
4646import dev .cel .policy .CelCompiledRule .CelCompiledVariable ;
47+ import dev .cel .policy .CelPolicy .EvaluationSemantic ;
4748import dev .cel .policy .CelPolicy .Import ;
4849import dev .cel .policy .CelPolicy .Match ;
4950import dev .cel .policy .CelPolicy .Variable ;
@@ -91,7 +92,8 @@ public CelCompiledRule compileRule(CelPolicy policy) throws CelPolicyValidationE
9192 extendedCel = extendedCel .toCelBuilder ().setContainer (containerBuilder .build ()).build ();
9293 }
9394
94- CelCompiledRule compiledRule = compileRuleImpl (policy .rule (), extendedCel , compilerContext );
95+ CelCompiledRule compiledRule =
96+ compileRuleImpl (policy .rule (), extendedCel , compilerContext , false );
9597 if (compilerContext .hasError ()) {
9698 throw new CelPolicyValidationException (compilerContext .getIssueString ());
9799 }
@@ -172,7 +174,14 @@ private void assertAstDepthIsSafe(CelAbstractSyntaxTree ast, Cel cel)
172174 }
173175
174176 private CelCompiledRule compileRuleImpl (
175- CelPolicy .Rule rule , Cel ruleCel , CompilerContext compilerContext ) {
177+ CelPolicy .Rule rule ,
178+ Cel ruleCel ,
179+ CompilerContext compilerContext ,
180+ boolean hasAggregateAncestor ) {
181+ if (hasAggregateAncestor && rule .semantic ().equals (EvaluationSemantic .AGGREGATE )) {
182+ compilerContext .addIssue (
183+ rule .id (), CelIssue .formatError (1 , 0 , "nested aggregate rules are not allowed" ));
184+ }
176185 // A local CEL environment used to compile a single rule. This temporary environment
177186 // is used to declare policy variables iteratively in a given policy, ensuring proper scoping
178187 // across a single / nested rule.
@@ -227,8 +236,11 @@ private CelCompiledRule compileRuleImpl(
227236 matchResult = Result .ofOutput (output .id (), outputAst );
228237 break ;
229238 case RULE :
239+ boolean nextHasAggregateAncestor =
240+ hasAggregateAncestor || rule .semantic ().equals (EvaluationSemantic .AGGREGATE );
230241 CelCompiledRule nestedRule =
231- compileRuleImpl (match .result ().rule (), localCel , compilerContext );
242+ compileRuleImpl (
243+ match .result ().rule (), localCel , compilerContext , nextHasAggregateAncestor );
232244 matchResult = Result .ofRule (nestedRule );
233245 break ;
234246 default :
@@ -240,7 +252,12 @@ private CelCompiledRule compileRuleImpl(
240252
241253 CelCompiledRule compiledRule =
242254 CelCompiledRule .create (
243- rule .id (), rule .ruleId (), variableBuilder .build (), matchBuilder .build (), ruleCel );
255+ rule .id (),
256+ rule .ruleId (),
257+ variableBuilder .build (),
258+ matchBuilder .build (),
259+ ruleCel ,
260+ rule .semantic ());
244261
245262 // Validate that all branches in the policy are reachable
246263 checkUnreachableCode (compiledRule , compilerContext );
@@ -255,6 +272,12 @@ private void checkUnreachableCode(CelCompiledRule compiledRule, CompilerContext
255272 CelCompiledMatch compiledMatch = compiledMatches .get (i );
256273 boolean isTriviallyTrue = compiledMatch .isConditionTriviallyTrue ();
257274
275+ // Flag literally false conditions as dead code regardless of semantic
276+ if (isConditionLiterallyFalse (compiledMatch .condition ())) {
277+ compilerContext .addIssue (
278+ compiledMatch .sourceId (), CelIssue .formatError (1 , 0 , "Condition is always false" ));
279+ }
280+
258281 // If the match is a single output or a nested rule that always returns a value, it is
259282 // exhaustive. If the condition is trivially true, then all subsequent branches are
260283 // unreachable.
@@ -263,7 +286,9 @@ private void checkUnreachableCode(CelCompiledRule compiledRule, CompilerContext
263286 && (compiledMatch .result ().kind ().equals (Kind .OUTPUT )
264287 || !compiledMatch .result ().rule ().hasOptionalOutput ());
265288
266- if (isExhaustive && i != matchCount - 1 ) {
289+ if (compiledRule .semantic () == EvaluationSemantic .FIRST_MATCH
290+ && isExhaustive
291+ && i != matchCount - 1 ) {
267292 if (compiledMatch .result ().kind ().equals (Kind .OUTPUT )) {
268293 compilerContext .addIssue (
269294 compiledMatch .sourceId (),
@@ -277,6 +302,12 @@ private void checkUnreachableCode(CelCompiledRule compiledRule, CompilerContext
277302 }
278303 }
279304
305+ private static boolean isConditionLiterallyFalse (CelAbstractSyntaxTree condition ) {
306+ CelExpr celExpr = condition .getExpr ();
307+ return celExpr .constantOrDefault ().getKind ().equals (CelConstant .Kind .BOOLEAN_VALUE )
308+ && !celExpr .constant ().booleanValue ();
309+ }
310+
280311 private static CelAbstractSyntaxTree newErrorAst () {
281312 return CelAbstractSyntaxTree .newParsedAst (
282313 CelExpr .ofConstant (0 , CelConstant .ofValue ("*error*" )), CelSource .newBuilder ().build ());
0 commit comments