From 99af418cb11fc4fcaed97d13ace58eaa4ec9f7ef Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 1 Jun 2026 15:51:55 -0700 Subject: [PATCH] Fix dead code reachability logic erroneously flagging on unconditional nested rules Port of https://github.com/google/cel-go/pull/1323 PiperOrigin-RevId: 924956895 --- .../java/dev/cel/policy/CelCompiledRule.java | 15 ++++++----- .../dev/cel/policy/CelPolicyCompilerImpl.java | 11 ++++++-- .../cel/policy/CelPolicyCompilerImplTest.java | 26 +++++++++++++++++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/policy/src/main/java/dev/cel/policy/CelCompiledRule.java b/policy/src/main/java/dev/cel/policy/CelCompiledRule.java index 36f1685fc..af40bd74f 100644 --- a/policy/src/main/java/dev/cel/policy/CelCompiledRule.java +++ b/policy/src/main/java/dev/cel/policy/CelCompiledRule.java @@ -52,14 +52,17 @@ public boolean hasOptionalOutput() { for (CelCompiledMatch match : matches()) { if (match.result().kind().equals(CelCompiledMatch.Result.Kind.RULE) && match.result().rule().hasOptionalOutput()) { - return true; - } - - if (match.isConditionTriviallyTrue()) { + // If the nested rule is unconditional, the matching may fallthrough to the next match + // in this context (unwrapping the optional value from the nested rule). + if (!match.isConditionTriviallyTrue()) { + return true; + } + isOptionalOutput = true; + } else if (match.isConditionTriviallyTrue()) { return false; + } else { + isOptionalOutput = true; } - - isOptionalOutput = true; } return isOptionalOutput; diff --git a/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java b/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java index 7841b9827..37a79f98a 100644 --- a/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java +++ b/policy/src/main/java/dev/cel/policy/CelPolicyCompilerImpl.java @@ -249,14 +249,21 @@ private CelCompiledRule compileRuleImpl( } private void checkUnreachableCode(CelCompiledRule compiledRule, CompilerContext compilerContext) { - boolean ruleHasOptional = compiledRule.hasOptionalOutput(); ImmutableList compiledMatches = compiledRule.matches(); int matchCount = compiledMatches.size(); for (int i = matchCount - 1; i >= 0; i--) { CelCompiledMatch compiledMatch = compiledMatches.get(i); boolean isTriviallyTrue = compiledMatch.isConditionTriviallyTrue(); - if (isTriviallyTrue && !ruleHasOptional && i != matchCount - 1) { + // If the match is a single output or a nested rule that always returns a value, it is + // exhaustive. If the condition is trivially true, then all subsequent branches are + // unreachable. + boolean isExhaustive = + isTriviallyTrue + && (compiledMatch.result().kind().equals(Kind.OUTPUT) + || !compiledMatch.result().rule().hasOptionalOutput()); + + if (isExhaustive && i != matchCount - 1) { if (compiledMatch.result().kind().equals(Kind.OUTPUT)) { compilerContext.addIssue( compiledMatch.sourceId(), diff --git a/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java b/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java index e9c2afed5..73950069f 100644 --- a/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java +++ b/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java @@ -111,6 +111,32 @@ public void compileYamlPolicy_withImportsOnNestedRules() throws Exception { assertThat(ast.getResultType()).isEqualTo(OptionalType.create(SimpleType.BOOL)); } + @Test + public void compileYamlPolicy_nestedRuleOptionalFallbackDivergence() throws Exception { + Cel cel = newCel().toCelBuilder().addVar("input_val", SimpleType.INT).build(); + String policySource = + "name: grandparent_policy\n" + + "rule:\n" + + " match:\n" + + " - condition: 'input_val == 2'\n" // conditional grandparent match + + " rule:\n" + + " id: parent_rule\n" + + " match:\n" + + " - condition: 'input_val == 1'\n" // conditional parent match + + " rule:\n" + + " id: nested_rule\n" + + " match:\n" + + " - condition: 'input_val == 3'\n" + + " output: 'true'\n" + + " - output: 'true'\n" // fallback (optional) + + " - output: 'true'\n"; + CelPolicy policy = POLICY_PARSER.parse(policySource); + CelAbstractSyntaxTree ast = + CelPolicyCompilerFactory.newPolicyCompiler(cel).build().compile(policy); + + assertThat(ast.getResultType()).isEqualTo(OptionalType.create(SimpleType.BOOL)); + } + @Test public void compileYamlPolicy_containsCompilationError_throws( @TestParameter TestErrorYamlPolicy testCase) throws Exception {