Skip to content

Commit de04acf

Browse files
l46kokcopybara-github
authored andcommitted
Add CLI for Verifier
PiperOrigin-RevId: 956259271
1 parent 989c8c5 commit de04acf

17 files changed

Lines changed: 2053 additions & 18 deletions

verifier/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,3 +433,7 @@ What this means for verification:
433433
default unless you have a specific need and bounded inputs.
434434

435435
---
436+
437+
## Tools & CLI
438+
439+
For command-line verification and interactive execution, see the [CLI Tool documentation](tools/README.md).

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

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,9 +1247,10 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12471247
}
12481248
Expr<?> optRef = typeSystem.getOptionalRef(val);
12491249
BoolExpr hasValue = typeSystem.optHasValue(optRef);
1250-
BoolExpr valConstraint =
1251-
createTypeConstraintForType(typeSystem.getOptionalValue(optRef), paramType);
1252-
return ctx.mkAnd(isOpt, ctx.mkImplies(hasValue, valConstraint));
1250+
Expr<?> optVal = typeSystem.getOptionalValue(optRef);
1251+
BoolExpr optValNotError = ctx.mkNot(typeSystem.isError(optVal));
1252+
BoolExpr valConstraint = createTypeConstraintForType(optVal, paramType);
1253+
return ctx.mkAnd(isOpt, ctx.mkImplies(hasValue, ctx.mkAnd(optValNotError, valConstraint)));
12531254
}
12541255
if (type.equals(SimpleType.BOOL)) {
12551256
return (BoolExpr) ctx.mkApp(typeSystem.boolCons().getTesterDecl(), val);
@@ -1289,15 +1290,13 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
12891290
}
12901291

12911292
if (type instanceof ListType) {
1292-
// Lists are explicitly bounded (sequence theory). We're safe in using for-all quantifiers
1293-
// here.
1293+
// Constrain list elements using bounded unrolling up to comprehensionUnrollLimit rather
1294+
// than Z3 forall quantifiers to prevent MBQI quantifier instantiation loops.
1295+
// Assert: isList(val) ∧ for all unrolled 0 <= i < length: ¬isError(seq[i]) ∧
1296+
// typeConstraint(seq[i])
12941297
BoolExpr isList = typeSystem.isList(val);
12951298
CelType elemType = ((ListType) type).elemType();
1296-
if (elemType.equals(SimpleType.DYN)) {
1297-
return isList;
1298-
}
12991299

1300-
// isList(val) ∧ ∀i. (0 <= i < length) ⇒ elemType(seq[i])
13011300
Expr<?> listRef = typeSystem.getListRef(val);
13021301
SeqExpr seq = typeSystem.getSeq(listRef);
13031302
Expr length = ctx.mkLength(seq);
@@ -1307,20 +1306,69 @@ private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
13071306
for (int i = 0; i < comprehensionUnrollLimit; i++) {
13081307
IntExpr idx = ctx.mkInt(i);
13091308
Expr elem = ctx.mkNth(seq, idx);
1310-
BoolExpr elemConstraint = createTypeConstraintForType(elem, elemType);
13111309
BoolExpr validIndex = ctx.mkLt(idx, length);
1312-
boundsAndTypes.add(ctx.mkImplies(validIndex, elemConstraint));
1313-
BoolExpr outOfBounds = ctx.mkGe(idx, length);
1314-
boundsAndTypes.add(ctx.mkImplies(outOfBounds, ctx.mkEq(elem, typeSystem.mkUnknown())));
1310+
// Assert ¬isError(elem) as a domain invariant so Z3 never synthesizes an Error element in
1311+
// list(dyn). For concrete types, this is already implied by createTypeConstraintForType.
1312+
boundsAndTypes.add(ctx.mkImplies(validIndex, ctx.mkNot(typeSystem.isError(elem))));
1313+
// Short-circuit DYN element types to prevent generating redundant validIndex ⇒ TRUE
1314+
// clauses.
1315+
if (!elemType.equals(SimpleType.DYN)) {
1316+
BoolExpr elemConstraint = createTypeConstraintForType(elem, elemType);
1317+
boundsAndTypes.add(ctx.mkImplies(validIndex, elemConstraint));
1318+
}
13151319
}
13161320

13171321
return CelZ3TypeSystem.mkAndFlattened(ctx, boundsAndTypes);
13181322
}
13191323
if (type instanceof MapType) {
1320-
// Do NOT emit a for-all quantifier over map keys here.
1321-
// Doing so forces MBQI into an infinite loop. Structural equivalence of dynamic keys is
1322-
// naturally constrained by the primitive key assertions in getStructuralEquality().
1323-
return typeSystem.isMap(val);
1324+
// Do NOT emit a for-all quantifier over map keys or values here.
1325+
// Doing so forces MBQI into an infinite loop. Instead, constrain keys and values using
1326+
// bounded unrolling over the key sequence up to comprehensionUnrollLimit.
1327+
// Assert: isMap(val) ∧ for all unrolled 0 <= i < length: isPrimitiveKey(key) ∧ ¬isError(key)
1328+
// ∧ (presence(key) ⇒ ¬isError(val) ∧ typeConstraint(val))
1329+
BoolExpr isMap = typeSystem.isMap(val);
1330+
MapType mapType = (MapType) type;
1331+
CelType keyType = mapType.keyType();
1332+
CelType valType = mapType.valueType();
1333+
1334+
Expr<?> mapRef = typeSystem.getMapRef(val);
1335+
SeqExpr seq = typeSystem.getMapKeys(mapRef);
1336+
Expr length = ctx.mkLength(seq);
1337+
ArrayExpr mapValues = (ArrayExpr) typeSystem.getMapValues(mapRef);
1338+
ArrayExpr mapPresence = (ArrayExpr) typeSystem.getMapPresence(mapRef);
1339+
1340+
List<BoolExpr> boundsAndTypes = new ArrayList<>();
1341+
boundsAndTypes.add(isMap);
1342+
1343+
for (int i = 0; i < comprehensionUnrollLimit; i++) {
1344+
IntExpr idx = ctx.mkInt(i);
1345+
Expr key = ctx.mkNth(seq, idx);
1346+
BoolExpr validIndex = ctx.mkLt(idx, length);
1347+
1348+
BoolExpr isKeyPrim = typeSystem.isPrimitiveKey(key);
1349+
BoolExpr keyNotError = ctx.mkNot(typeSystem.isError(key));
1350+
// Assert isKeyPrim ∧ ¬isError(key) so Z3 never synthesizes a non-primitive or Error key in
1351+
// map(dyn, ...). For concrete map types, this is already implied by keyType constraints.
1352+
boundsAndTypes.add(ctx.mkImplies(validIndex, ctx.mkAnd(isKeyPrim, keyNotError)));
1353+
// Short-circuit DYN key types to prevent generating redundant validIndex ⇒ TRUE clauses.
1354+
if (!keyType.equals(SimpleType.DYN)) {
1355+
boundsAndTypes.add(ctx.mkImplies(validIndex, createTypeConstraintForType(key, keyType)));
1356+
}
1357+
1358+
BoolExpr presence = (BoolExpr) ctx.mkSelect(mapPresence, key);
1359+
BoolExpr validEntry = ctx.mkAnd(validIndex, presence);
1360+
1361+
Expr mapVal = ctx.mkSelect(mapValues, key);
1362+
BoolExpr valNotError = ctx.mkNot(typeSystem.isError(mapVal));
1363+
boundsAndTypes.add(ctx.mkImplies(validEntry, valNotError));
1364+
// Short-circuit DYN value types to prevent generating redundant validEntry ⇒ TRUE clauses.
1365+
if (!valType.equals(SimpleType.DYN)) {
1366+
boundsAndTypes.add(
1367+
ctx.mkImplies(validEntry, createTypeConstraintForType(mapVal, valType)));
1368+
}
1369+
}
1370+
1371+
return CelZ3TypeSystem.mkAndFlattened(ctx, boundsAndTypes);
13241372
}
13251373
if (type.kind() == CelKind.STRUCT) {
13261374
return ctx.mkAnd(

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ private static String formatExpr(
127127
return "Error";
128128
} else if (decl.equals(typeSystem.unknownCons().ConstructorDecl())) {
129129
return "Unknown";
130+
} else if (decl.equals(typeSystem.nullCons().ConstructorDecl())) {
131+
return "null";
130132
} else if (decl.equals(typeSystem.optionalCons().ConstructorDecl())) {
131133
Expr<?> optRef = expr.getArgs()[0];
132134
Expr<?> hasValueExpr =

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,11 @@ public Expr<?> getBytes(Expr<?> val) {
685685
return ctx.mkApp(bytesCons.getAccessorDecls()[0], val);
686686
}
687687

688+
/** Checks if the given CelValue is a valid primitive map key type. */
689+
public BoolExpr isPrimitiveKey(Expr<?> val) {
690+
return ctx.mkOr(isBool(val), isInt(val), isUint(val), isString(val), isBytes(val));
691+
}
692+
688693
/** Checks if the given CelValue is a struct (message). */
689694
public BoolExpr isStruct(Expr<?> val) {
690695
return isMessage(val);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
2+
load("//publish:cel_version.bzl", "CEL_VERSION")
3+
4+
package(
5+
default_applicable_licenses = [
6+
"//:license",
7+
],
8+
default_visibility = [
9+
"//verifier:__subpackages__",
10+
],
11+
)
12+
13+
genrule(
14+
name = "generate_version",
15+
outs = ["CelVersion.java"],
16+
cmd = """cat << 'EOF' > $@
17+
package dev.cel.verifier.tools;
18+
19+
final class CelVersion {
20+
static final String VERSION = "%s";
21+
22+
private CelVersion() {}
23+
}
24+
EOF
25+
""" % CEL_VERSION,
26+
)
27+
28+
java_library(
29+
name = "tools_lib",
30+
srcs = [
31+
"CelVerifierTool.java",
32+
"CelVerifierToolCore.java",
33+
"FormatUtils.java",
34+
"VerificationOptions.java",
35+
":generate_version",
36+
],
37+
tags = [
38+
"alt_dep=//verifier/tools",
39+
],
40+
deps = [
41+
"//bundle:cel",
42+
"//common:cel_ast",
43+
"//common:compiler_common",
44+
"//common:options",
45+
"//common/types",
46+
"//common/types:type_providers",
47+
"//compiler",
48+
"//compiler:compiler_builder",
49+
"//extensions",
50+
"//parser:macro",
51+
"//policy",
52+
"//policy:compiler",
53+
"//policy:compiler_factory",
54+
"//policy:parser",
55+
"//policy:parser_factory",
56+
"//policy:validation_exception",
57+
"//verifier",
58+
"//verifier:policy_verifier",
59+
"//verifier:policy_verifier_factory",
60+
"//verifier:verifier_factory",
61+
"@maven//:com_google_errorprone_error_prone_annotations",
62+
"@maven//:com_google_guava_guava",
63+
"@maven//:info_picocli_picocli",
64+
],
65+
)
66+
67+
java_binary(
68+
name = "cel_verifier_tool",
69+
jvm_flags = ["-Dz3.skipLibraryLoad=true"],
70+
main_class = "dev.cel.verifier.tools.CelVerifierTool",
71+
tags = [
72+
"alt_dep=//verifier/tools:cel_verifier_tool",
73+
],
74+
runtime_deps = [
75+
":tools_lib",
76+
],
77+
)

0 commit comments

Comments
 (0)