Skip to content

Commit 39f9db1

Browse files
l46kokcopybara-github
authored andcommitted
Extract CelZ3TypeConstraintGenerator from CelAstToZ3Translator
PiperOrigin-RevId: 1004241090
1 parent f9fcf9f commit 39f9db1

3 files changed

Lines changed: 242 additions & 187 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ java_library(
164164
"CelZ3ExtensionalityAxioms.java",
165165
"CelZ3FunctionRegistry.java",
166166
"CelZ3OperatorTranslator.java",
167+
"CelZ3TypeConstraintGenerator.java",
167168
"TranslatedValue.java",
168169
],
169170
compatible_with = [],

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

Lines changed: 10 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ final class CelAstToZ3Translator {
9292
private final Context ctx;
9393
private final CelZ3TypeSystem typeSystem;
9494
private final CelZ3OperatorTranslator operatorTranslator;
95+
private final CelZ3TypeConstraintGenerator typeConstraintGenerator;
9596
private final Map<String, TranslatedValue> symbolTable;
9697
private final Set<BoolExpr> typeConstraints;
9798
private final Map<Expr<?>, CelType> inputVariableTypes;
@@ -265,7 +266,7 @@ private TranslatedValue translateIdent(CelExpr celExpr, CelAbstractSyntaxTree as
265266
// Basically prevents error being a counterexample of var == var
266267
typeConstraints.add(ctx.mkNot(typeSystem.isError(v)));
267268

268-
BoolExpr constraint = createTypeConstraint(v, exprId, ast);
269+
BoolExpr constraint = typeConstraintGenerator.createTypeConstraint(v, exprId, ast);
269270
if (unknownIdentifiers.contains(name)) {
270271
typeConstraints.add(ctx.mkOr(typeSystem.isUnknown(v), constraint));
271272
} else {
@@ -582,7 +583,8 @@ private TranslatedValue translateWrapperWktStruct(
582583
}
583584
entryTvs.add(entryTv);
584585
}
585-
BoolExpr validValue = createTypeConstraintForType(currentVal, targetType);
586+
BoolExpr validValue =
587+
typeConstraintGenerator.createTypeConstraintForType(currentVal, targetType);
586588
if (typeName.equals(CelTypes.INT32_WRAPPER_MESSAGE)) {
587589
IntExpr intVal = typeSystem.getInt(currentVal);
588590
validValue =
@@ -871,7 +873,7 @@ private TranslatedValue translateSelect(CelExpr celExpr, CelAbstractSyntaxTree a
871873

872874
Expr<?> fieldAccess = select.testOnly() ? typeSystem.wrapBool(presenceResult) : valueResult;
873875

874-
typeConstraints.add(createTypeConstraint(fieldAccess, exprId, ast));
876+
typeConstraints.add(typeConstraintGenerator.createTypeConstraint(fieldAccess, exprId, ast));
875877

876878
TranslatedValue result =
877879
TranslatedValue.propagateStrict(
@@ -936,7 +938,8 @@ private TranslatedValue translateCall(CelExpr expr, CelAbstractSyntaxTree ast) {
936938

937939
Expr<?>[] exprArgs = args.stream().map(TranslatedValue::z3Expr).toArray(Expr[]::new);
938940
Expr<?> callRes = ctx.mkApp(funcDecl, exprArgs);
939-
typeConstraints.add(createTypeConstraint(callRes, exprId, ast));
941+
typeConstraints.add(
942+
typeConstraintGenerator.createTypeConstraint(callRes, exprId, ast));
940943
typeConstraints.add(ctx.mkNot(typeSystem.isUnknown(callRes)));
941944
typeConstraints.add(ctx.mkNot(typeSystem.isError(callRes)));
942945

@@ -1128,33 +1131,6 @@ private TranslatedValue translateDynamicComprehension(
11281131
}
11291132
}
11301133

1131-
private BoolExpr getBoundedMapBijection(
1132-
ArrayExpr mapPresence, SeqExpr<?> seq, ArithExpr lengthExpr) {
1133-
List<BoolExpr> constraints = new ArrayList<>();
1134-
for (int i = 0; i < comprehensionUnrollLimit; i++) {
1135-
for (int j = i + 1; j < comprehensionUnrollLimit; j++) {
1136-
BoolExpr validPair = ctx.mkLt(ctx.mkInt(j), lengthExpr);
1137-
BoolExpr notEqual =
1138-
ctx.mkNot(ctx.mkEq(ctx.mkNth(seq, ctx.mkInt(i)), ctx.mkNth(seq, ctx.mkInt(j))));
1139-
constraints.add(ctx.mkImplies(validPair, notEqual));
1140-
}
1141-
}
1142-
1143-
BoolExpr isNotTruncated = ctx.mkLe(lengthExpr, ctx.mkInt(comprehensionUnrollLimit));
1144-
1145-
ArrayExpr seqMap = ctx.mkConstArray(typeSystem.celValueSort(), ctx.mkFalse());
1146-
for (int i = 0; i < comprehensionUnrollLimit; i++) {
1147-
seqMap =
1148-
(ArrayExpr)
1149-
ctx.mkITE(
1150-
ctx.mkLt(ctx.mkInt(i), lengthExpr),
1151-
ctx.mkStore(seqMap, ctx.mkNth(seq, ctx.mkInt(i)), ctx.mkTrue()),
1152-
seqMap);
1153-
}
1154-
constraints.add(ctx.mkImplies(isNotTruncated, ctx.mkEq(mapPresence, seqMap)));
1155-
return CelZ3Exprs.mkAndFlattened(ctx, constraints);
1156-
}
1157-
11581134
private <T> T withIterationScope(
11591135
CelComprehension comp,
11601136
Expr<?> keyOrIndex,
@@ -1469,161 +1445,6 @@ private static boolean isNotStrictlyFalseLoopCondition(CelComprehension comp, bo
14691445
return arg.identOrDefault().name().equals(comp.accuVar());
14701446
}
14711447

1472-
private BoolExpr createTypeConstraint(Expr<?> val, long exprId, CelAbstractSyntaxTree ast) {
1473-
CelType type =
1474-
ast.getType(exprId)
1475-
.orElseThrow(
1476-
() -> new IllegalArgumentException("Type not found for expr ID: " + exprId));
1477-
BoolExpr typeConstraint = createTypeConstraintForType(val, type);
1478-
return ctx.mkOr(typeSystem.isErrorOrUnknown(val), typeConstraint);
1479-
}
1480-
1481-
private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
1482-
if (type instanceof NullableType) {
1483-
NullableType nullableType = (NullableType) type;
1484-
return ctx.mkOr(
1485-
typeSystem.isNull(val), createTypeConstraintForType(val, nullableType.targetType()));
1486-
}
1487-
if (type instanceof OptionalType) {
1488-
BoolExpr isOpt = typeSystem.isOptional(val);
1489-
CelType paramType = type.parameters().get(0);
1490-
if (paramType.kind().isDyn() || paramType.kind().isTypeParam()) {
1491-
return isOpt;
1492-
}
1493-
Expr<?> optRef = typeSystem.getOptionalRef(val);
1494-
BoolExpr hasValue = typeSystem.optHasValue(optRef);
1495-
Expr<?> optVal = typeSystem.getOptionalValue(optRef);
1496-
BoolExpr optValNotError = ctx.mkNot(typeSystem.isError(optVal));
1497-
BoolExpr valConstraint = createTypeConstraintForType(optVal, paramType);
1498-
return ctx.mkAnd(isOpt, ctx.mkImplies(hasValue, ctx.mkAnd(optValNotError, valConstraint)));
1499-
}
1500-
if (type.equals(SimpleType.BOOL)) {
1501-
return typeSystem.isBool(val);
1502-
}
1503-
if (type.equals(SimpleType.INT)) {
1504-
IntExpr unwrapped = typeSystem.getInt(val);
1505-
return ctx.mkAnd(typeSystem.isInt(val), ctx.mkNot(typeSystem.checkIntOverflow(unwrapped)));
1506-
}
1507-
if (type.equals(SimpleType.UINT)) {
1508-
IntExpr unwrapped = typeSystem.getUint(val);
1509-
return ctx.mkAnd(typeSystem.isUint(val), ctx.mkNot(typeSystem.checkUintOverflow(unwrapped)));
1510-
}
1511-
if (type.equals(SimpleType.DOUBLE)) {
1512-
return typeSystem.isDouble(val);
1513-
}
1514-
if (type.equals(SimpleType.STRING)) {
1515-
return typeSystem.isString(val);
1516-
}
1517-
if (type.equals(SimpleType.BYTES)) {
1518-
return typeSystem.isBytes(val);
1519-
}
1520-
if (type.equals(SimpleType.TIMESTAMP)) {
1521-
IntExpr nanos = typeSystem.getTimestamp(val);
1522-
return ctx.mkAnd(
1523-
typeSystem.isTimestamp(val), ctx.mkNot(typeSystem.checkTimestampOverflow(nanos)));
1524-
}
1525-
if (type.equals(SimpleType.DURATION)) {
1526-
IntExpr nanos = typeSystem.getDuration(val);
1527-
return ctx.mkAnd(
1528-
typeSystem.isDuration(val), ctx.mkNot(typeSystem.checkDurationOverflow(nanos)));
1529-
}
1530-
1531-
if (type instanceof ListType) {
1532-
// Constrain list elements using bounded unrolling up to comprehensionUnrollLimit rather
1533-
// than Z3 forall quantifiers to prevent MBQI quantifier instantiation loops.
1534-
// Assert: isList(val) ∧ for all unrolled 0 <= i < length: ¬isError(seq[i]) ∧
1535-
// typeConstraint(seq[i]).
1536-
BoolExpr isList = typeSystem.isList(val);
1537-
CelType elemType = ((ListType) type).elemType();
1538-
1539-
Expr<?> listRef = typeSystem.getListRef(val);
1540-
SeqExpr seq = typeSystem.getSeq(listRef);
1541-
Expr length = ctx.mkLength(seq);
1542-
1543-
List<BoolExpr> boundsAndTypes = new ArrayList<>();
1544-
boundsAndTypes.add(isList);
1545-
// Elements beyond the limit are unconstrained, which only matters if elemType is known.
1546-
if (CelZ3OperatorTranslator.isStaticallyKnown(elemType)) {
1547-
boundsAndTypes.add(
1548-
ctx.mkImplies(
1549-
ctx.mkGt(length, ctx.mkInt(comprehensionUnrollLimit)),
1550-
typeSystem.hasTruncatedTypeConstraint(val)));
1551-
}
1552-
for (int i = 0; i < comprehensionUnrollLimit; i++) {
1553-
IntExpr idx = ctx.mkInt(i);
1554-
Expr elem = ctx.mkNth(seq, idx);
1555-
BoolExpr validIndex = ctx.mkLt(idx, length);
1556-
// Assert ¬isError(elem) as a domain invariant so Z3 never synthesizes an Error element in
1557-
// list(dyn). For concrete types, this is already implied by createTypeConstraintForType.
1558-
boundsAndTypes.add(ctx.mkImplies(validIndex, ctx.mkNot(typeSystem.isError(elem))));
1559-
BoolExpr elemConstraint = createTypeConstraintForType(elem, elemType);
1560-
boundsAndTypes.add(ctx.mkImplies(validIndex, elemConstraint));
1561-
}
1562-
1563-
return CelZ3Exprs.mkAndFlattened(ctx, boundsAndTypes);
1564-
}
1565-
if (type instanceof MapType) {
1566-
// Do NOT emit a for-all quantifier over map keys or values here.
1567-
// Doing so forces MBQI into an infinite loop. Instead, constrain keys and values using
1568-
// bounded unrolling over the key sequence up to comprehensionUnrollLimit.
1569-
// Assert: isMap(val) ∧ for all unrolled 0 <= i < length: isPrimitiveKey(key) ∧ ¬isError(key)
1570-
// ∧ (presence(key) ⇒ ¬isError(val) ∧ typeConstraint(val))
1571-
BoolExpr isMap = typeSystem.isMap(val);
1572-
MapType mapType = (MapType) type;
1573-
CelType keyType = mapType.keyType();
1574-
CelType valType = mapType.valueType();
1575-
1576-
Expr<?> mapRef = typeSystem.getMapRef(val);
1577-
SeqExpr seq = typeSystem.getMapKeys(mapRef);
1578-
Expr length = ctx.mkLength(seq);
1579-
ArrayExpr mapValues = (ArrayExpr) typeSystem.getMapValues(mapRef);
1580-
ArrayExpr mapPresence = (ArrayExpr) typeSystem.getMapPresence(mapRef);
1581-
1582-
List<BoolExpr> boundsAndTypes = new ArrayList<>();
1583-
boundsAndTypes.add(isMap);
1584-
boundsAndTypes.add(getBoundedMapBijection(mapPresence, seq, (ArithExpr) length));
1585-
// Entries beyond the limit are unconstrained, which matters if keyType or valType is known.
1586-
if (CelZ3OperatorTranslator.isStaticallyKnown(keyType)
1587-
|| CelZ3OperatorTranslator.isStaticallyKnown(valType)) {
1588-
boundsAndTypes.add(
1589-
ctx.mkImplies(
1590-
ctx.mkGt(length, ctx.mkInt(comprehensionUnrollLimit)),
1591-
typeSystem.hasTruncatedTypeConstraint(val)));
1592-
}
1593-
1594-
for (int i = 0; i < comprehensionUnrollLimit; i++) {
1595-
IntExpr idx = ctx.mkInt(i);
1596-
Expr key = ctx.mkNth(seq, idx);
1597-
BoolExpr validIndex = ctx.mkLt(idx, length);
1598-
1599-
BoolExpr isKeyPrim = typeSystem.isPrimitiveKey(key);
1600-
BoolExpr keyNotError = ctx.mkNot(typeSystem.isError(key));
1601-
// Assert isKeyPrim ∧ ¬isError(key) so Z3 never synthesizes a non-primitive or Error key in
1602-
// map(dyn, ...). For concrete map types, this is already implied by keyType constraints.
1603-
boundsAndTypes.add(ctx.mkImplies(validIndex, ctx.mkAnd(isKeyPrim, keyNotError)));
1604-
boundsAndTypes.add(ctx.mkImplies(validIndex, createTypeConstraintForType(key, keyType)));
1605-
1606-
BoolExpr presence = (BoolExpr) ctx.mkSelect(mapPresence, key);
1607-
BoolExpr validEntry = ctx.mkAnd(validIndex, presence);
1608-
1609-
Expr mapVal = ctx.mkSelect(mapValues, key);
1610-
BoolExpr valNotError = ctx.mkNot(typeSystem.isErrorOrUnknown(mapVal));
1611-
boundsAndTypes.add(ctx.mkImplies(validEntry, valNotError));
1612-
boundsAndTypes.add(ctx.mkImplies(validEntry, createTypeConstraintForType(mapVal, valType)));
1613-
}
1614-
1615-
return CelZ3Exprs.mkAndFlattened(ctx, boundsAndTypes);
1616-
}
1617-
if (type.kind() == CelKind.STRUCT) {
1618-
return ctx.mkAnd(
1619-
typeSystem.isMessage(val),
1620-
ctx.mkEq(
1621-
typeSystem.getMsgTypeName(typeSystem.getMessageRef(val)), ctx.mkString(type.name())));
1622-
}
1623-
// Fallback: no type constraint
1624-
return ctx.mkTrue();
1625-
}
1626-
16271448
private static final class IterationElement {
16281449
private final Expr<?> keyOrIndex;
16291450
private final Expr<?> value;
@@ -1683,12 +1504,14 @@ private Expr<?> mkParameterizedUnknown(CelExpr expr, CelAbstractSyntaxTree ast)
16831504
this.typeSystem = new CelZ3TypeSystem(ctx, parameterizeUnknowns);
16841505
this.typeConstraints = new LinkedHashSet<>();
16851506
this.inputVariableTypes = new HashMap<>();
1507+
this.typeConstraintGenerator =
1508+
new CelZ3TypeConstraintGenerator(ctx, typeSystem, comprehensionUnrollLimit);
16861509
this.operatorTranslator =
16871510
new CelZ3OperatorTranslator(
16881511
ctx,
16891512
typeSystem,
16901513
this.typeConstraints::add,
1691-
this::createTypeConstraintForType,
1514+
typeConstraintGenerator::createTypeConstraintForType,
16921515
val -> inputVariableTypes.getOrDefault(val, SimpleType.DYN),
16931516
functionRegistry,
16941517
comprehensionUnrollLimit);

0 commit comments

Comments
 (0)