Skip to content

Commit 72f8ec0

Browse files
l46kokcopybara-github
authored andcommitted
Structured counterexample and CEGAR
PiperOrigin-RevId: 965286727
1 parent 5cf3ab3 commit 72f8ec0

8 files changed

Lines changed: 865 additions & 120 deletions

File tree

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

Lines changed: 5 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,9 +183,11 @@ 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",
190+
"//runtime:evaluation_exception",
186191
"//verifier/axioms",
187192
"@maven//:com_google_errorprone_error_prone_annotations",
188193
"@maven//:com_google_guava_guava",
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+
}

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

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

1717
import com.google.auto.value.AutoValue;
18+
import com.google.errorprone.annotations.Immutable;
19+
import java.util.Optional;
1820

1921
/** Result object containing the outcome of a CEL AST verification check. */
2022
@AutoValue
23+
@Immutable
2124
public abstract class CelVerificationResult {
2225

2326
/** Represents the outcome of the verification process. */
@@ -38,11 +41,12 @@ public enum VerificationStatus {
3841
*/
3942
public abstract String reason();
4043

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

47+
/** Returns the structured counterexample or satisfying model assignment, if one was found. */
48+
public abstract Optional<CelCounterexample> counterexampleModel();
49+
4650
/**
4751
* Returns a message detailing the outcome of the verification check, such as a counterexample
4852
* input, satisfying model assignments, or truncation reason. May be empty if status is VERIFIED
@@ -53,28 +57,46 @@ public String message() {
5357
}
5458

5559
static CelVerificationResult verified() {
56-
return new AutoValue_CelVerificationResult(VerificationStatus.VERIFIED, "", "");
60+
return new AutoValue_CelVerificationResult(
61+
VerificationStatus.VERIFIED, "", "", Optional.empty());
5762
}
5863

5964
static CelVerificationResult verified(String reason) {
60-
return new AutoValue_CelVerificationResult(VerificationStatus.VERIFIED, reason, "");
65+
return new AutoValue_CelVerificationResult(
66+
VerificationStatus.VERIFIED, reason, "", Optional.empty());
67+
}
68+
69+
static CelVerificationResult verified(String reason, CelCounterexample counterexample) {
70+
return new AutoValue_CelVerificationResult(
71+
VerificationStatus.VERIFIED,
72+
reason,
73+
counterexample.toDisplayString(),
74+
Optional.of(counterexample));
6175
}
6276

6377
static CelVerificationResult failed(String reason) {
64-
return new AutoValue_CelVerificationResult(VerificationStatus.VIOLATED, reason, "");
78+
return new AutoValue_CelVerificationResult(
79+
VerificationStatus.VIOLATED, reason, "", Optional.empty());
6580
}
6681

67-
static CelVerificationResult failed(String reason, String counterexample) {
82+
static CelVerificationResult failed(String reason, CelCounterexample counterexample) {
6883
return new AutoValue_CelVerificationResult(
69-
VerificationStatus.VIOLATED, reason, counterexample);
84+
VerificationStatus.VIOLATED,
85+
reason,
86+
counterexample.toDisplayString(),
87+
Optional.of(counterexample));
7088
}
7189

7290
static CelVerificationResult inconclusive(String reason) {
73-
return new AutoValue_CelVerificationResult(VerificationStatus.INCONCLUSIVE, reason, "");
91+
return new AutoValue_CelVerificationResult(
92+
VerificationStatus.INCONCLUSIVE, reason, "", Optional.empty());
7493
}
7594

76-
static CelVerificationResult inconclusive(String reason, String counterexample) {
95+
static CelVerificationResult inconclusive(String reason, CelCounterexample counterexample) {
7796
return new AutoValue_CelVerificationResult(
78-
VerificationStatus.INCONCLUSIVE, reason, counterexample);
97+
VerificationStatus.INCONCLUSIVE,
98+
reason,
99+
counterexample.toDisplayString(),
100+
Optional.of(counterexample));
79101
}
80102
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ 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+
CelVerifierBuilder setEnableCegarRefinement(boolean enableCegarRefinement);
96+
8597
/** Builds the {@link CelVerifier} instance. */
8698
CelVerifier build();
8799
}

0 commit comments

Comments
 (0)