Skip to content

Commit 8b56e12

Browse files
l46kokcopybara-github
authored andcommitted
Policy invariant diagnostics and execution path tracing
PiperOrigin-RevId: 965286724
1 parent 426fa24 commit 8b56e12

18 files changed

Lines changed: 3159 additions & 209 deletions

verifier/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ java_library(
3535
exports = ["//verifier/src/main/java/dev/cel/verifier:policy_verifier_factory"],
3636
)
3737

38+
java_library(
39+
name = "policy_verifier_impl",
40+
compatible_with = [],
41+
visibility = [":verifier_internal"],
42+
exports = ["//verifier/src/main/java/dev/cel/verifier:policy_verifier_impl"],
43+
)
44+
3845
java_library(
3946
name = "verifier_factory",
4047
compatible_with = [],

verifier/src/main/java/dev/cel/verifier/BUILD.bazel

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ package(
1111
java_library(
1212
name = "verifier",
1313
srcs = [
14+
"CelCounterexample.java",
15+
"CelPolicyDiagnostic.java",
16+
"CelPolicyEquivalenceDiagnostic.java",
1417
"CelVerificationException.java",
1518
"CelVerificationResult.java",
1619
"CelVerifier.java",
@@ -21,8 +24,13 @@ java_library(
2124
deps = [
2225
"//:auto_value",
2326
"//common:cel_ast",
27+
"//common:compiler_common",
28+
"//common:source",
29+
"//common:source_location",
2430
"//common/types:type_providers",
2531
"@maven//:com_google_errorprone_error_prone_annotations",
32+
"@maven//:com_google_guava_guava",
33+
"@maven//:org_jspecify_jspecify",
2634
],
2735
)
2836

@@ -76,7 +84,10 @@ java_library(
7684

7785
java_library(
7886
name = "policy_verifier_impl",
79-
srcs = ["CelPolicyVerifierImpl.java"],
87+
srcs = [
88+
"CelPolicyPathTracer.java",
89+
"CelPolicyVerifierImpl.java",
90+
],
8091
compatible_with = [],
8192
tags = [
8293
],
@@ -88,11 +99,14 @@ java_library(
8899
"//common:cel_ast",
89100
"//common:cel_source",
90101
"//common:compiler_common",
102+
"//common:source_location",
91103
"//common/formats:value_string",
92104
"//policy",
93105
"//policy:compiled_rule",
94106
"//policy:compiler",
107+
"//policy:source",
95108
"//policy:validation_exception",
109+
"//runtime:evaluation_exception",
96110
"@maven//:com_google_guava_guava",
97111
],
98112
)
@@ -153,6 +167,7 @@ java_library(
153167
java_library(
154168
name = "z3_impl",
155169
srcs = [
170+
"CegarRefiner.java",
156171
"CelAstAlphaHasher.java",
157172
"CelAstToZ3Translator.java",
158173
"CelVerifierZ3Impl.java",
@@ -180,9 +195,12 @@ java_library(
180195
"//common/types",
181196
"//common/types:cel_types",
182197
"//common/types:type_providers",
198+
"//common/values:cel_byte_string",
199+
"//common/values:cel_value_provider",
183200
"//optimizer",
184201
"//optimizer:optimization_exception",
185202
"//optimizer:optimizer_builder",
203+
"//runtime:evaluation_exception",
186204
"//verifier/axioms",
187205
"@maven//:com_google_errorprone_error_prone_annotations",
188206
"@maven//:com_google_guava_guava",
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.verifier;
16+
17+
import com.google.common.base.Preconditions;
18+
import com.google.common.collect.ImmutableMap;
19+
import com.google.errorprone.annotations.Immutable;
20+
import dev.cel.bundle.Cel;
21+
import dev.cel.common.CelAbstractSyntaxTree;
22+
import dev.cel.runtime.CelEvaluationException;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
import java.util.Objects;
26+
import java.util.Optional;
27+
28+
/**
29+
* Evaluates candidate counterexample models against the concrete CEL runtime to confirm or refute
30+
* potential violations (CEGAR refinement loop).
31+
*/
32+
@Immutable
33+
final class CegarRefiner {
34+
35+
@Immutable
36+
static final class CegarOutcome {
37+
private final boolean isViolation;
38+
private final Optional<String> evaluationErrorMessage;
39+
40+
static CegarOutcome violation() {
41+
return new CegarOutcome(true, Optional.empty());
42+
}
43+
44+
static CegarOutcome spurious() {
45+
return new CegarOutcome(false, Optional.empty());
46+
}
47+
48+
static CegarOutcome evaluationError(String errorMessage) {
49+
return new CegarOutcome(false, Optional.of(errorMessage));
50+
}
51+
52+
private CegarOutcome(boolean isViolation, Optional<String> evaluationErrorMessage) {
53+
this.isViolation = isViolation;
54+
this.evaluationErrorMessage = evaluationErrorMessage;
55+
}
56+
57+
boolean isViolation() {
58+
return isViolation;
59+
}
60+
61+
Optional<String> evaluationErrorMessage() {
62+
return evaluationErrorMessage;
63+
}
64+
}
65+
66+
private final Cel cel;
67+
68+
CegarRefiner(Cel cel) {
69+
this.cel = Preconditions.checkNotNull(cel);
70+
}
71+
72+
CegarOutcome refineEquivalence(
73+
CelAbstractSyntaxTree astA, CelAbstractSyntaxTree astB, CelCounterexample model) {
74+
if (model.isSatisfyingInput()) {
75+
return CegarOutcome.spurious();
76+
}
77+
try {
78+
ImmutableMap<String, Object> evalContext = model.toEvaluationContext();
79+
Object resA = cel.createProgram(astA).eval(evalContext);
80+
Object resB = cel.createProgram(astB).eval(evalContext);
81+
// If concrete evaluation produces identical results, the candidate SMT divergence was an
82+
// artifact of abstraction (spurious). Otherwise, concrete outputs diverge (violation).
83+
return Objects.equals(resA, resB) ? CegarOutcome.spurious() : CegarOutcome.violation();
84+
} catch (CelEvaluationException e) {
85+
return CegarOutcome.evaluationError(e.getMessage());
86+
}
87+
}
88+
89+
CegarOutcome refineSatisfiability(
90+
CelAbstractSyntaxTree ast, boolean searchForCounterexample, CelCounterexample model) {
91+
if (searchForCounterexample ? model.isSatisfyingInput() : !model.isSatisfyingInput()) {
92+
return CegarOutcome.spurious();
93+
}
94+
try {
95+
ImmutableMap<String, Object> evalContext = model.toEvaluationContext();
96+
Object res = cel.createProgram(ast).eval(evalContext);
97+
boolean isEvaluationTrue = Objects.equals(res, true);
98+
// For universal truth (searchForCounterexample=true), evaluating to true refutes the
99+
// candidate counterexample (spurious). For satisfiability search
100+
// (searchForCounterexample=false),
101+
// evaluating to true confirms the candidate satisfying model (violation).
102+
boolean isSpurious = searchForCounterexample == isEvaluationTrue;
103+
return isSpurious ? CegarOutcome.spurious() : CegarOutcome.violation();
104+
} catch (CelEvaluationException e) {
105+
return CegarOutcome.evaluationError(e.getMessage());
106+
}
107+
}
108+
109+
CegarOutcome refineImplication(
110+
CelAbstractSyntaxTree assumeAst,
111+
CelAbstractSyntaxTree assertAst,
112+
Map<String, CelAbstractSyntaxTree> boundSymbols,
113+
CelCounterexample model) {
114+
if (model.isSatisfyingInput()) {
115+
return CegarOutcome.spurious();
116+
}
117+
try {
118+
Map<String, Object> evalContext = new HashMap<>(model.toEvaluationContext());
119+
for (Map.Entry<String, CelAbstractSyntaxTree> entry : boundSymbols.entrySet()) {
120+
Object boundVal = cel.createProgram(entry.getValue()).eval(evalContext);
121+
evalContext.put(entry.getKey(), boundVal);
122+
}
123+
Object assumeVal = cel.createProgram(assumeAst).eval(evalContext);
124+
if (Objects.equals(assumeVal, true)) {
125+
Object assertVal = cel.createProgram(assertAst).eval(evalContext);
126+
// If the premise holds and the conclusion evaluates to true, the candidate counterexample
127+
// is refuted (spurious). If the conclusion fails under true premise, implication is
128+
// violated.
129+
return Objects.equals(assertVal, true) ? CegarOutcome.spurious() : CegarOutcome.violation();
130+
}
131+
// The candidate input did not satisfy the premise, so it cannot serve as a counterexample.
132+
return CegarOutcome.spurious();
133+
} catch (CelEvaluationException e) {
134+
return CegarOutcome.evaluationError(e.getMessage());
135+
}
136+
}
137+
}

verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ private FieldAccess getMapAccess(Expr<?> operand, String field, BoolExpr typeGua
610610
typeConstraints.add(
611611
ctx.mkImplies(
612612
CelZ3TypeSystem.mkAndFlattened(ctx, typeGuard, (BoolExpr) presence), valNotError));
613-
if (unknownIdentifiers.isEmpty()) {
613+
if (!unknownIdentifiers.contains(field)) {
614614
BoolExpr valNotUnknown = ctx.mkNot(typeSystem.isUnknown(value));
615615
typeConstraints.add(
616616
ctx.mkImplies(
@@ -630,7 +630,7 @@ private FieldAccess getMsgAccess(Expr<?> operand, String field, BoolExpr typeGua
630630
typeConstraints.add(
631631
ctx.mkImplies(
632632
CelZ3TypeSystem.mkAndFlattened(ctx, typeGuard, (BoolExpr) presence), valNotError));
633-
if (unknownIdentifiers.isEmpty()) {
633+
if (!unknownIdentifiers.contains(field)) {
634634
BoolExpr valNotUnknown = ctx.mkNot(typeSystem.isUnknown(value));
635635
typeConstraints.add(
636636
ctx.mkImplies(
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.verifier;
16+
17+
import com.google.auto.value.AutoValue;
18+
import com.google.common.collect.ImmutableMap;
19+
import com.google.errorprone.annotations.Immutable;
20+
import dev.cel.common.types.CelType;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
import org.jspecify.annotations.Nullable;
24+
25+
/** Encapsulates a structured variable assignment model produced by formal verification. */
26+
@AutoValue
27+
@AutoValue.CopyAnnotations
28+
@Immutable
29+
public abstract class CelCounterexample {
30+
31+
/** Represents a single variable binding within a counterexample. */
32+
@AutoValue
33+
@AutoValue.CopyAnnotations
34+
@Immutable
35+
@SuppressWarnings("Immutable") // Values are deeply immutable.
36+
public abstract static class Binding {
37+
/** Returns the name of the variable. */
38+
public abstract String name();
39+
40+
/** Returns the inferred CEL type of the variable. */
41+
public abstract CelType type();
42+
43+
/**
44+
* Returns the native Java representation of the value (e.g., Long, Boolean, String, Instant,
45+
* Duration, ImmutableList, ImmutableMap, Message, etc.), or empty if unassigned or unavailable.
46+
*/
47+
public abstract Optional<Object> nativeValue();
48+
49+
/**
50+
* Returns the CEL literal representation of the value (e.g., "80", "\"admin\"", "true", "[1,
51+
* 2]").
52+
*/
53+
public abstract String celString();
54+
55+
public static Binding of(
56+
String name, CelType type, @Nullable Object nativeValue, String celString) {
57+
return new AutoValue_CelCounterexample_Binding(
58+
name, type, Optional.ofNullable(nativeValue), celString);
59+
}
60+
}
61+
62+
/** Returns all variable bindings keyed by variable name. */
63+
public abstract ImmutableMap<String, Binding> bindings();
64+
65+
/** Returns true if this counterexample was derived from an approximate solver model. */
66+
public abstract boolean isApproximate();
67+
68+
/** Returns true if this model represents a satisfying assignment rather than a counterexample. */
69+
public abstract boolean isSatisfyingInput();
70+
71+
/** Returns the formatted display string representation. */
72+
public abstract String toDisplayString();
73+
74+
/** Looks up a variable binding by name. */
75+
public Optional<Binding> get(String variableName) {
76+
return Optional.ofNullable(bindings().get(variableName));
77+
}
78+
79+
/**
80+
* Returns a native Java variable map suitable for evaluating expressions in CelRuntime (e.g.,
81+
* Cel.createProgram().eval(toEvaluationContext())).
82+
*/
83+
public ImmutableMap<String, Object> toEvaluationContext() {
84+
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
85+
for (Binding binding : bindings().values()) {
86+
binding.nativeValue().ifPresent(value -> builder.put(binding.name(), value));
87+
}
88+
return builder.buildOrThrow();
89+
}
90+
91+
public static CelCounterexample create(
92+
Map<String, Binding> bindings,
93+
boolean isApproximate,
94+
boolean isSatisfyingInput,
95+
String toDisplayString) {
96+
return new AutoValue_CelCounterexample(
97+
ImmutableMap.copyOf(bindings), isApproximate, isSatisfyingInput, toDisplayString);
98+
}
99+
}

0 commit comments

Comments
 (0)