Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import dev.cel.common.types.ListType;
import dev.cel.common.types.MapType;
import dev.cel.common.types.NullableType;
import dev.cel.common.types.OptionalType;
import dev.cel.common.types.SimpleType;
import dev.cel.common.types.StructType;
import dev.cel.common.types.StructTypeReference;
Expand Down Expand Up @@ -287,9 +288,13 @@ private TranslatedValue translateList(CelExpr celExpr, CelAbstractSyntaxTree ast
for (int i = 0; i < elements.size(); i++) {
CelExpr element = elements.get(i);
TranslatedValue elem = translateExpr(element, ast);
elementsTv.add(elem);

if (optionalIndices.contains(i)) {
Expr<?> checkedValue =
typeSystem.withRuntimeError(
elem.z3Expr(), ctx.mkNot(typeSystem.isOptional(elem.z3Expr())));
elem = TranslatedValue.create(checkedValue, element, typeSystem, elem.isApproximate());

Expr<?> optRef = typeSystem.getOptionalRef(elem.z3Expr());
seq =
(SeqExpr)
Expand All @@ -300,6 +305,7 @@ private TranslatedValue translateList(CelExpr celExpr, CelAbstractSyntaxTree ast
} else {
seq = typeSystem.mkConcatSafe(seq, ctx.mkUnit(elem.z3Expr()));
}
elementsTv.add(elem);
}
listRef = typeSystem.mkListRefConst(LIST_REF_PREFIX);
typeConstraints.add(ctx.mkEq(typeSystem.getSeq(listRef), seq));
Expand Down Expand Up @@ -327,15 +333,20 @@ private TranslatedValue translateMap(CelExpr celExpr, CelAbstractSyntaxTree ast)
elementsTv.add(keyTv);
TranslatedValue valueTv = translateExpr(entryAst.value(), ast);
Expr<?> value = valueTv.z3Expr();
elementsTv.add(valueTv);

Expr<?> finalValue = value;
BoolExpr finalPresence = ctx.mkTrue();
if (entryAst.optionalEntry()) {
Expr<?> optRef = typeSystem.getOptionalRef(value);
Expr<?> checkedValue =
typeSystem.withRuntimeError(value, ctx.mkNot(typeSystem.isOptional(value)));
valueTv =
TranslatedValue.create(
checkedValue, entryAst.value(), typeSystem, valueTv.isApproximate());
Expr<?> optRef = typeSystem.getOptionalRef(checkedValue);
finalPresence = typeSystem.optHasValue(optRef);
finalValue = typeSystem.getOptionalValue(optRef);
}
elementsTv.add(valueTv);

BoolExpr keyAlreadyPresent = (BoolExpr) ctx.mkSelect(mapPresence, key);
BoolExpr shouldInsertKey = ctx.mkAnd(ctx.mkNot(keyAlreadyPresent), finalPresence);
Expand Down Expand Up @@ -382,7 +393,6 @@ private TranslatedValue translateStruct(CelExpr celExpr, CelAbstractSyntaxTree a
Expr<?> key = ctx.mkString(entryAst.fieldKey());
TranslatedValue valueTv = translateExpr(entryAst.value(), ast);
Expr<?> value = valueTv.z3Expr();
elementsTv.add(valueTv);

CelType fieldType =
typeProvider
Expand All @@ -397,10 +407,16 @@ private TranslatedValue translateStruct(CelExpr celExpr, CelAbstractSyntaxTree a
Expr<?> finalValue = value;
BoolExpr optionalHasValue = ctx.mkTrue();
if (entryAst.optionalEntry()) {
Expr<?> optRef = typeSystem.getOptionalRef(value);
Expr<?> checkedValue =
typeSystem.withRuntimeError(value, ctx.mkNot(typeSystem.isOptional(value)));
valueTv =
TranslatedValue.create(
checkedValue, entryAst.value(), typeSystem, valueTv.isApproximate());
Expr<?> optRef = typeSystem.getOptionalRef(checkedValue);
optionalHasValue = typeSystem.optHasValue(optRef);
finalValue = typeSystem.getOptionalValue(optRef);
}
elementsTv.add(valueTv);

// Canonicalization Trick:
//
Expand Down Expand Up @@ -436,6 +452,9 @@ private Expr<?> getDefaultValueForType(CelType type) {
if (type instanceof NullableType) {
return typeSystem.mkNull();
}
if (type instanceof OptionalType) {
return typeSystem.mkOptionalNone();
}
if (type.equals(SimpleType.INT)) {
return typeSystem.mkInt(0);
}
Expand Down Expand Up @@ -1147,6 +1166,23 @@ private BoolExpr createTypeConstraint(Expr<?> val, long exprId, CelAbstractSynta
}

private BoolExpr createTypeConstraintForType(Expr<?> val, CelType type) {
if (type instanceof NullableType) {
NullableType nullableType = (NullableType) type;
return ctx.mkOr(
typeSystem.isNull(val), createTypeConstraintForType(val, nullableType.targetType()));
}
if (type instanceof OptionalType) {
BoolExpr isOpt = typeSystem.isOptional(val);
CelType paramType = type.parameters().get(0);
if (paramType.kind().isDyn() || paramType.kind().isTypeParam()) {
return isOpt;
}
Expr<?> optRef = typeSystem.getOptionalRef(val);
BoolExpr hasValue = typeSystem.optHasValue(optRef);
BoolExpr valConstraint =
createTypeConstraintForType(typeSystem.getOptionalValue(optRef), paramType);
return ctx.mkAnd(isOpt, ctx.mkImplies(hasValue, valConstraint));
}
if (type.equals(SimpleType.BOOL)) {
return (BoolExpr) ctx.mkApp(typeSystem.boolCons().getTesterDecl(), val);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,23 @@ private static String formatExpr(
return "Error";
} else if (decl.equals(typeSystem.unknownCons().ConstructorDecl())) {
return "Unknown";
} else if (decl.equals(typeSystem.optionalCons().ConstructorDecl())) {
Expr<?> optRef = expr.getArgs()[0];
Expr<?> hasValueExpr =
evaluateStrict(
model,
typeSystem.optHasValue(optRef),
String.format("Z3 failed to evaluate optHasValue natively for %s", optRef));
if (hasValueExpr.isTrue()) {
Expr<?> valueExpr =
evaluateStrict(
model,
typeSystem.getOptionalValue(optRef),
String.format("Z3 failed to evaluate optValue natively for %s", optRef));
return "optional(" + formatExpr(ctx, typeSystem, model, valueExpr) + ")";
} else if (hasValueExpr.isFalse()) {
return "optional.none()";
}
}

return expr.toString();
Expand Down
4 changes: 4 additions & 0 deletions verifier/src/main/java/dev/cel/verifier/CelZ3TypeSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ Constructor bytesCons() {
return bytesCons;
}

Constructor optionalCons() {
return optionalCons;
}

/** Creates a CelValue containing a boolean. */
public Expr<?> mkBool(boolean val) {
return ctx.mkApp(boolCons.ConstructorDecl(), ctx.mkBool(val));
Expand Down
38 changes: 38 additions & 0 deletions verifier/src/main/java/dev/cel/verifier/axioms/OptionalAxioms.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@
package dev.cel.verifier.axioms;

import com.google.common.collect.ImmutableList;
import com.microsoft.z3.BoolExpr;
import com.microsoft.z3.Context;
import com.microsoft.z3.Expr;
import com.microsoft.z3.FPExpr;
import com.microsoft.z3.SeqExpr;
import dev.cel.common.CelFunctionDecl;
import dev.cel.extensions.CelOptionalLibrary;
import dev.cel.extensions.CelOptionalLibrary.Function;
import dev.cel.verifier.CelZ3TypeSystem;
import java.util.Optional;

/** Axiomatization for CEL's optional library functions. */
@SuppressWarnings({"unchecked", "rawtypes"}) // Z3 Java API uses raw types.
final class OptionalAxioms {

static final ImmutableList<CelZ3FunctionAxiom> ALL_AXIOMS =
Expand All @@ -40,6 +46,17 @@ final class OptionalAxioms {
sink.accept(ts.optHasValue(optRef));
return Optional.of(ts.mkOptionalOf(optRef));
}),
createUnaryAxiom(
Function.OPTIONAL_OF_NON_ZERO_VALUE,
"optional_ofNonZeroValue",
(ctx, ts, sink, value) -> {
Expr<?> optRef = ctx.mkApp(ts.optionalOfRefFunc(), value);
BoolExpr isZero = isZeroValue(ctx, ts, value);
sink.accept(
ctx.mkImplies(ctx.mkNot(isZero), ctx.mkEq(ts.getOptionalValue(optRef), value)));
sink.accept(ctx.mkImplies(ctx.mkNot(isZero), ts.optHasValue(optRef)));
return Optional.of(ctx.mkITE(isZero, ts.mkOptionalNone(), ts.mkOptionalOf(optRef)));
}),
createUnaryAxiom(
Function.HAS_VALUE,
"optional_hasValue",
Expand Down Expand Up @@ -69,6 +86,27 @@ final class OptionalAxioms {
return Optional.of(ctx.mkITE(ts.optHasValue(optRef), val, other));
}));

private static BoolExpr isZeroValue(Context ctx, CelZ3TypeSystem ts, Expr<?> val) {
return ctx.mkOr(
ts.isNull(val),
ctx.mkAnd(ts.isBool(val), ctx.mkEq(ts.unwrapBool(val), ctx.mkFalse())),
ctx.mkAnd(ts.isInt(val), ctx.mkEq(ts.getInt(val), ctx.mkInt(0))),
ctx.mkAnd(ts.isUint(val), ctx.mkEq(ts.getUint(val), ctx.mkInt(0))),
ctx.mkAnd(ts.isDouble(val), ctx.mkFPIsZero((FPExpr) ts.getDouble(val))),
ctx.mkAnd(ts.isString(val), ctx.mkEq(ts.getString(val), ctx.mkString(""))),
ctx.mkAnd(
ts.isBytes(val), ctx.mkEq(ctx.mkLength((SeqExpr) ts.getBytes(val)), ctx.mkInt(0))),
ctx.mkAnd(
ts.isList(val), ctx.mkEq(ctx.mkLength(ts.getSeq(ts.getListRef(val))), ctx.mkInt(0))),
ctx.mkAnd(
ts.isMap(val), ctx.mkEq(ctx.mkLength(ts.getMapKeys(ts.getMapRef(val))), ctx.mkInt(0))),
ctx.mkAnd(
ts.isMessage(val),
ctx.mkEq(
ts.getMsgPresence(ts.getMessageRef(val)),
ctx.mkConstArray(ctx.getStringSort(), ctx.mkFalse()))));
}

private static CelFunctionDecl getDecl(Function funcEnum) {
return CelOptionalLibrary.INSTANCE.functions().stream()
.filter(d -> d.name().equals(funcEnum.getFunction()))
Expand Down
Loading
Loading