Skip to content

Commit 445148c

Browse files
l46kokcopybara-github
authored andcommitted
Structured counterexample and CEGAR
PiperOrigin-RevId: 965286727
1 parent 849cb3e commit 445148c

8 files changed

Lines changed: 680 additions & 116 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package(
1111
java_library(
1212
name = "verifier",
1313
srcs = [
14+
"CelCounterexample.java",
1415
"CelVerificationException.java",
1516
"CelVerificationResult.java",
1617
"CelVerifier.java",
@@ -23,6 +24,8 @@ java_library(
2324
"//common:cel_ast",
2425
"//common/types:type_providers",
2526
"@maven//:com_google_errorprone_error_prone_annotations",
27+
"@maven//:com_google_guava_guava",
28+
"@maven//:org_jspecify_jspecify",
2629
],
2730
)
2831

@@ -180,6 +183,7 @@ java_library(
180183
"//common/types",
181184
"//common/types:cel_types",
182185
"//common/types:type_providers",
186+
"//common/values:cel_byte_string",
183187
"//optimizer",
184188
"//optimizer:optimization_exception",
185189
"//optimizer:optimizer_builder",
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
@SuppressWarnings("Immutable")
30+
public abstract class CelCounterexample {
31+
32+
/** Represents a single variable binding within a counterexample. */
33+
@AutoValue
34+
@AutoValue.CopyAnnotations
35+
@Immutable
36+
@SuppressWarnings("Immutable")
37+
public abstract static class Binding {
38+
/** Returns the name of the variable. */
39+
public abstract String name();
40+
41+
/** Returns the inferred CEL type of the variable. */
42+
public abstract CelType type();
43+
44+
/**
45+
* Returns the native Java representation of the value (e.g., Long, Boolean, String, Instant,
46+
* Duration, ImmutableList, ImmutableMap, Message, etc.), or null if unassigned or null.
47+
*/
48+
public abstract @Nullable Object nativeValue();
49+
50+
/**
51+
* Returns the CEL literal representation of the value (e.g., "80", "\"admin\"", "true", "[1,
52+
* 2]").
53+
*/
54+
public abstract String celString();
55+
56+
public static Binding of(
57+
String name, CelType type, @Nullable Object nativeValue, String celString) {
58+
return new AutoValue_CelCounterexample_Binding(name, type, 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 b : bindings().values()) {
86+
if (b.nativeValue() != null) {
87+
builder.put(b.name(), b.nativeValue());
88+
}
89+
}
90+
return builder.buildOrThrow();
91+
}
92+
93+
public static CelCounterexample create(
94+
Map<String, Binding> bindings,
95+
boolean isApproximate,
96+
boolean isSatisfyingInput,
97+
String displayString) {
98+
return new AutoValue_CelCounterexample(
99+
ImmutableMap.copyOf(bindings), isApproximate, isSatisfyingInput, displayString);
100+
}
101+
102+
public static CelCounterexample empty() {
103+
return new AutoValue_CelCounterexample(
104+
ImmutableMap.of(),
105+
/* isApproximate= */ false,
106+
/* isSatisfyingInput= */ false,
107+
/* displayString= */ "");
108+
}
109+
}

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

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
package dev.cel.verifier;
1616

1717
import com.google.auto.value.AutoValue;
18+
import com.google.common.collect.ImmutableMap;
19+
import com.google.errorprone.annotations.Immutable;
20+
import java.util.Optional;
1821

1922
/** Result object containing the outcome of a CEL AST verification check. */
2023
@AutoValue
24+
@Immutable
2125
public abstract class CelVerificationResult {
2226

2327
/** Represents the outcome of the verification process. */
@@ -38,11 +42,24 @@ public enum VerificationStatus {
3842
*/
3943
public abstract String reason();
4044

41-
/**
42-
* Returns a detailed counterexample or satisfying model assignment, if one was found.
43-
*/
45+
/** Returns a detailed counterexample or satisfying model assignment string, if one was found. */
4446
public abstract String counterexample();
4547

48+
/** Returns the structured counterexample or satisfying model assignment, if one was found. */
49+
public abstract Optional<CelCounterexample> counterexampleModel();
50+
51+
/** Returns the variable bindings as a map of variable name to stringified CEL literal syntax. */
52+
public ImmutableMap<String, String> counterexampleBindings() {
53+
return counterexampleModel()
54+
.map(
55+
model -> {
56+
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
57+
model.bindings().forEach((k, v) -> builder.put(k, v.celString()));
58+
return builder.buildOrThrow();
59+
})
60+
.orElseGet(ImmutableMap::of);
61+
}
62+
4663
/**
4764
* Returns a message detailing the outcome of the verification check, such as a counterexample
4865
* input, satisfying model assignments, or truncation reason. May be empty if status is VERIFIED
@@ -53,28 +70,56 @@ public String message() {
5370
}
5471

5572
static CelVerificationResult verified() {
56-
return new AutoValue_CelVerificationResult(VerificationStatus.VERIFIED, "", "");
73+
return new AutoValue_CelVerificationResult(
74+
VerificationStatus.VERIFIED, "", "", Optional.empty());
5775
}
5876

5977
static CelVerificationResult verified(String reason) {
60-
return new AutoValue_CelVerificationResult(VerificationStatus.VERIFIED, reason, "");
78+
return new AutoValue_CelVerificationResult(
79+
VerificationStatus.VERIFIED, reason, "", Optional.empty());
80+
}
81+
82+
static CelVerificationResult verified(String reason, CelCounterexample counterexample) {
83+
return new AutoValue_CelVerificationResult(
84+
VerificationStatus.VERIFIED,
85+
reason,
86+
counterexample.toDisplayString(),
87+
Optional.of(counterexample));
6188
}
6289

6390
static CelVerificationResult failed(String reason) {
64-
return new AutoValue_CelVerificationResult(VerificationStatus.VIOLATED, reason, "");
91+
return new AutoValue_CelVerificationResult(
92+
VerificationStatus.VIOLATED, reason, "", Optional.empty());
6593
}
6694

6795
static CelVerificationResult failed(String reason, String counterexample) {
6896
return new AutoValue_CelVerificationResult(
69-
VerificationStatus.VIOLATED, reason, counterexample);
97+
VerificationStatus.VIOLATED, reason, counterexample, Optional.empty());
98+
}
99+
100+
static CelVerificationResult failed(String reason, CelCounterexample counterexample) {
101+
return new AutoValue_CelVerificationResult(
102+
VerificationStatus.VIOLATED,
103+
reason,
104+
counterexample.toDisplayString(),
105+
Optional.of(counterexample));
70106
}
71107

72108
static CelVerificationResult inconclusive(String reason) {
73-
return new AutoValue_CelVerificationResult(VerificationStatus.INCONCLUSIVE, reason, "");
109+
return new AutoValue_CelVerificationResult(
110+
VerificationStatus.INCONCLUSIVE, reason, "", Optional.empty());
74111
}
75112

76113
static CelVerificationResult inconclusive(String reason, String counterexample) {
77114
return new AutoValue_CelVerificationResult(
78-
VerificationStatus.INCONCLUSIVE, reason, counterexample);
115+
VerificationStatus.INCONCLUSIVE, reason, counterexample, Optional.empty());
116+
}
117+
118+
static CelVerificationResult inconclusive(String reason, CelCounterexample counterexample) {
119+
return new AutoValue_CelVerificationResult(
120+
VerificationStatus.INCONCLUSIVE,
121+
reason,
122+
counterexample.toDisplayString(),
123+
Optional.of(counterexample));
79124
}
80125
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ public interface CelVerifierBuilder {
8282
@CanIgnoreReturnValue
8383
CelVerifierBuilder setComprehensionUnrollLimit(int unrollLimit);
8484

85+
/**
86+
* Enables or disables Counterexample-Guided Abstraction Refinement (CEGAR).
87+
*
88+
* <p>When enabled, if the SMT solver returns an approximate model (e.g., due to unmodeled custom
89+
* functions, approximations, or bounded loops), the candidate inputs are validated using concrete
90+
* {@link dev.cel.bundle.Cel} program execution. If concrete evaluation confirms an invariant
91+
* violation or equivalence divergence, the result is upgraded from {@code INCONCLUSIVE} to {@code
92+
* VIOLATED}.
93+
*/
94+
@CanIgnoreReturnValue
95+
default CelVerifierBuilder setEnableCegarRefinement(boolean enableCegarRefinement) {
96+
return this;
97+
}
98+
8599
/** Builds the {@link CelVerifier} instance. */
86100
CelVerifier build();
87101
}

0 commit comments

Comments
 (0)