Skip to content

Commit 579c615

Browse files
l46kokcopybara-github
authored andcommitted
Generate satisfiable model for isSatisfiable
PiperOrigin-RevId: 951064191
1 parent 9617d14 commit 579c615

5 files changed

Lines changed: 96 additions & 13 deletions

File tree

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,20 @@ public enum VerificationStatus {
3434
public abstract VerificationStatus status();
3535

3636
/**
37-
* Returns a message detailing why the verification failed or was inconclusive (e.g., the
38-
* counterexample input or truncation reason). Empty if status is VERIFIED.
37+
* Returns a message detailing the outcome of the verification check, such as a counterexample
38+
* input, satisfying model assignments, or truncation reason. May be empty if status is VERIFIED
39+
* and no model inputs apply (e.g., when verifying isAlwaysTrue without counterexamples).
3940
*/
4041
public abstract String message();
4142

4243
static CelVerificationResult verified() {
4344
return new AutoValue_CelVerificationResult(VerificationStatus.VERIFIED, "");
4445
}
4546

47+
static CelVerificationResult verified(String message) {
48+
return new AutoValue_CelVerificationResult(VerificationStatus.VERIFIED, message);
49+
}
50+
4651
static CelVerificationResult failed(String message) {
4752
return new AutoValue_CelVerificationResult(VerificationStatus.VIOLATED, message);
4853
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
public interface CelVerifier {
2323

2424
/**
25-
* Returns verified if there is at least one input combination where the AST evaluates to true.
25+
* Returns verified if there is at least one input combination where the AST evaluates to true. If
26+
* the expression is satisfiable and depends on input variables, the result message will contain a
27+
* satisfying model (witness) with concrete variable assignments.
2628
*
2729
* @param ast The input expression to verify. Must be a type-checked AST.
2830
*/

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

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,21 @@ public CelVerificationResult verifyEquivalence(
192192
return CelVerificationResult.failed(
193193
"Equivalence violation detected."
194194
+ getCounterexampleString(
195-
ctx, translator.getTypeSystem(), result.model, /* isApproximate= */ false));
195+
ctx,
196+
translator.getTypeSystem(),
197+
result.model,
198+
/* isApproximate= */ false,
199+
/* isCounterexample= */ true));
196200
case APPROXIMATE_MATCH:
197201
return CelVerificationResult.inconclusive(
198202
"Inconclusive: a divergence may exist, but it depends on approximations, missing"
199203
+ " theories, or loop bounds."
200204
+ getCounterexampleString(
201-
ctx, translator.getTypeSystem(), result.model, /* isApproximate= */ true));
205+
ctx,
206+
translator.getTypeSystem(),
207+
result.model,
208+
/* isApproximate= */ true,
209+
/* isCounterexample= */ true));
202210
case TRUNCATED:
203211
return CelVerificationResult.inconclusive(
204212
"Inconclusive: expressions are equivalent within the current loop unroll limit, but"
@@ -250,8 +258,16 @@ private CelVerificationResult checkSatisfiability(
250258
ctx,
251259
translator.getTypeSystem(),
252260
result.model,
253-
/* isApproximate= */ false))
254-
: CelVerificationResult.verified();
261+
/* isApproximate= */ false,
262+
/* isCounterexample= */ true))
263+
: CelVerificationResult.verified(
264+
"Condition is satisfiable."
265+
+ getCounterexampleString(
266+
ctx,
267+
translator.getTypeSystem(),
268+
result.model,
269+
/* isApproximate= */ false,
270+
/* isCounterexample= */ false));
255271

256272
case APPROXIMATE_MATCH:
257273
String prefix =
@@ -263,7 +279,11 @@ private CelVerificationResult checkSatisfiability(
263279
return CelVerificationResult.inconclusive(
264280
prefix
265281
+ getCounterexampleString(
266-
ctx, translator.getTypeSystem(), result.model, /* isApproximate= */ true));
282+
ctx,
283+
translator.getTypeSystem(),
284+
result.model,
285+
/* isApproximate= */ true,
286+
/* isCounterexample= */ searchForCounterexample));
267287

268288
case TRUNCATED:
269289
return CelVerificationResult.inconclusive(
@@ -357,8 +377,13 @@ private Solver newSolver(Context ctx) {
357377
}
358378

359379
private static String getCounterexampleString(
360-
Context ctx, CelZ3TypeSystem typeSystem, Model model, boolean isApproximate) {
361-
return CelZ3CounterexampleGenerator.generate(ctx, typeSystem, model, isApproximate);
380+
Context ctx,
381+
CelZ3TypeSystem typeSystem,
382+
Model model,
383+
boolean isApproximate,
384+
boolean isCounterexample) {
385+
return CelZ3CounterexampleGenerator.generate(
386+
ctx, typeSystem, model, isApproximate, isCounterexample);
362387
}
363388

364389
CelVerifierZ3Impl(

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ final class CelZ3CounterexampleGenerator {
3636
private CelZ3CounterexampleGenerator() {}
3737

3838
static String generate(
39-
Context ctx, CelZ3TypeSystem typeSystem, Model model, boolean isApproximate) {
39+
Context ctx,
40+
CelZ3TypeSystem typeSystem,
41+
Model model,
42+
boolean isApproximate,
43+
boolean isCounterexample) {
4044
FuncDecl[] constDecls = model.getConstDecls();
4145

4246
List<String> bindings = new ArrayList<>();
@@ -55,10 +59,17 @@ static String generate(
5559
}
5660

5761
if (bindings.isEmpty()) {
58-
return " (The expression fails unconditionally, regardless of input state)";
62+
return isCounterexample
63+
? " (The expression fails unconditionally, regardless of input state)"
64+
: " (The expression is satisfiable unconditionally, regardless of input state)";
5965
}
6066

61-
String prefix = isApproximate ? " Potential counterexample input:" : " Counterexample input:";
67+
String prefix;
68+
if (isCounterexample) {
69+
prefix = isApproximate ? " Potential counterexample input:" : " Counterexample input:";
70+
} else {
71+
prefix = isApproximate ? " Potential satisfying input:" : " Satisfying input:";
72+
}
6273
return prefix + String.join("", bindings);
6374
}
6475

@@ -228,6 +239,9 @@ private static void extractKeys(Expr<?> arrayExpr, List<Expr<?>> keys) {
228239
if (++iterations > 100_000) {
229240
throw new IllegalStateException("Exceeded maximum number of extractKeys iterations.");
230241
}
242+
if (!arrayExpr.isApp()) {
243+
break;
244+
}
231245
FuncDecl<?> decl = arrayExpr.getFuncDecl();
232246
String declName = decl.getName().toString();
233247

verifier/src/test/java/dev/cel/verifier/CelVerifierZ3ImplTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,43 @@ public void isSatisfiable_success(@TestParameter IsSatisfiableTestCase testCase)
172172
assertThat(result.status()).isEqualTo(VerificationStatus.VERIFIED);
173173
}
174174

175+
@Test
176+
public void isSatisfiable_withVariable_returnsSatisfyingModel() throws Exception {
177+
CelAbstractSyntaxTree ast = CEL.compile("x > 5").getAst();
178+
179+
CelVerificationResult result = VERIFIER.isSatisfiable(ast);
180+
181+
assertThat(result.status()).isEqualTo(VerificationStatus.VERIFIED);
182+
assertThat(result.message()).contains("Condition is satisfiable.");
183+
assertThat(result.message()).contains("Satisfying input:");
184+
assertThat(result.message()).containsMatch("x = (?:[6-9]|[1-9]\\d+)");
185+
}
186+
187+
@Test
188+
public void isSatisfiable_unconditional_returnsUnconditionalMessage() throws Exception {
189+
CelAbstractSyntaxTree ast = CEL.compile("1 + 1 == 2").getAst();
190+
191+
CelVerificationResult result = VERIFIER.isSatisfiable(ast);
192+
193+
assertThat(result.status()).isEqualTo(VerificationStatus.VERIFIED);
194+
assertThat(result.message())
195+
.isEqualTo(
196+
"Condition is satisfiable. (The expression is satisfiable unconditionally, regardless"
197+
+ " of input state)");
198+
}
199+
200+
@Test
201+
public void isSatisfiable_approximate_returnsPotentialSatisfyingInput() throws Exception {
202+
CelAbstractSyntaxTree ast = CEL.compile("int('123') == 123 ? x > 5 : false").getAst();
203+
204+
CelVerificationResult result = VERIFIER.isSatisfiable(ast);
205+
206+
assertThat(result.status()).isEqualTo(VerificationStatus.INCONCLUSIVE);
207+
assertThat(result.message()).contains("Inconclusive: a satisfying model may exist");
208+
assertThat(result.message()).contains("Potential satisfying input:");
209+
assertThat(result.message()).containsMatch("x = (?:[6-9]|[1-9]\\d+)");
210+
}
211+
175212
private enum IsSatisfiableInconclusiveTestCase {
176213
MASKED_BY_BMC("int_list == [1, 2, 3, 4, 5, 6] ? int_list.exists(x, x == 42) : false"),
177214
MASKED_BY_BMC_ALL("int_list == [1, 2, 3, 4, 5, 6] ? int_list.all(x, x > 0) : false"),

0 commit comments

Comments
 (0)