diff --git a/verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java b/verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java index 159a66a77..e3bb1bfaf 100644 --- a/verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java +++ b/verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java @@ -741,8 +741,10 @@ private TranslatedValue translateCall(CelExpr expr, CelAbstractSyntaxTree ast) { typeConstraints.add(ctx.mkNot(typeSystem.isUnknown(callRes))); typeConstraints.add(ctx.mkNot(typeSystem.isError(callRes))); + boolean isDynamic = ast.getType(exprId).map(SimpleType.DYN::equals).orElse(true); + BoolExpr isApprox = ctx.mkBool(!isDynamic); return TranslatedValue.propagateStrict( - ctx, typeSystem, callRes, Optional.of(expr), ctx.mkTrue(), args); + ctx, typeSystem, callRes, Optional.of(expr), isApprox, args); }); } diff --git a/verifier/src/main/java/dev/cel/verifier/CelZ3OperatorTranslator.java b/verifier/src/main/java/dev/cel/verifier/CelZ3OperatorTranslator.java index 59795c6c5..3051fbd87 100644 --- a/verifier/src/main/java/dev/cel/verifier/CelZ3OperatorTranslator.java +++ b/verifier/src/main/java/dev/cel/verifier/CelZ3OperatorTranslator.java @@ -594,7 +594,8 @@ private TranslatedValue translateEquality( // because X == X is a tautology (or propagates errors/unknowns exactly). if (z3Arg0.equals(z3Arg1)) { Expr> finalResult = typeSystem.propagateErrorAndUnknown(equalityExpr, z3Arg0); - return TranslatedValue.create(finalResult, typeSystem, ctx.mkFalse()); + return TranslatedValue.create( + finalResult, typeSystem, ctx.mkOr(arg0.isApproximate(), arg1.isApproximate())); } return TranslatedValue.propagateStrict(ctx, typeSystem, equalityExpr, arg0, arg1) diff --git a/verifier/src/main/java/dev/cel/verifier/CelZ3TypeSystem.java b/verifier/src/main/java/dev/cel/verifier/CelZ3TypeSystem.java index 5c0b87fbe..e9a1872c9 100644 --- a/verifier/src/main/java/dev/cel/verifier/CelZ3TypeSystem.java +++ b/verifier/src/main/java/dev/cel/verifier/CelZ3TypeSystem.java @@ -26,6 +26,7 @@ import com.microsoft.z3.DatatypeSort; import com.microsoft.z3.Expr; import com.microsoft.z3.FPExpr; +import com.microsoft.z3.FPNum; import com.microsoft.z3.FuncDecl; import com.microsoft.z3.IntExpr; import com.microsoft.z3.SeqExpr; @@ -280,6 +281,35 @@ Constructor optionalCons() { return optionalCons; } + /** + * Checks if the given CelValue expression represents a statically known primitive constant. + * + *
This is useful for determining whether an uninterpreted function's result should be treated
+ * as an approximation. If the argument is a known constant, any resulting error is an
+ * approximation (e.g., parsing a literal string). If it's a variable, the error is an exact
+ * runtime failure.
+ */
+ public boolean isPrimitiveConstant(Expr> expr) {
+ if (!expr.isApp()) {
+ return false;
+ }
+ FuncDecl> decl = expr.getFuncDecl();
+ if (decl.equals(stringCons.ConstructorDecl()) || decl.equals(bytesCons.ConstructorDecl())) {
+ return expr.getArgs()[0].isString();
+ } else if (decl.equals(intCons.ConstructorDecl())
+ || decl.equals(uintCons.ConstructorDecl())
+ || decl.equals(timestampCons.ConstructorDecl())
+ || decl.equals(durationCons.ConstructorDecl())) {
+ return expr.getArgs()[0].isNumeral();
+ } else if (decl.equals(doubleCons.ConstructorDecl())) {
+ return expr.getArgs()[0] instanceof FPNum;
+ } else if (decl.equals(boolCons.ConstructorDecl())) {
+ Expr> inner = expr.getArgs()[0];
+ return inner.isTrue() || inner.isFalse();
+ }
+ return false;
+ }
+
/** Creates a CelValue containing a boolean. */
public Expr> mkBool(boolean val) {
return ctx.mkApp(boolCons.ConstructorDecl(), ctx.mkBool(val));
diff --git a/verifier/src/main/java/dev/cel/verifier/axioms/TypeConversionAxioms.java b/verifier/src/main/java/dev/cel/verifier/axioms/TypeConversionAxioms.java
index feccfd04e..8cd844214 100644
--- a/verifier/src/main/java/dev/cel/verifier/axioms/TypeConversionAxioms.java
+++ b/verifier/src/main/java/dev/cel/verifier/axioms/TypeConversionAxioms.java
@@ -19,11 +19,13 @@
import com.google.common.collect.ImmutableList;
import com.microsoft.z3.BoolExpr;
import com.microsoft.z3.Expr;
+import com.microsoft.z3.FPExpr;
import com.microsoft.z3.FuncDecl;
import com.microsoft.z3.IntExpr;
import com.microsoft.z3.Sort;
import dev.cel.checker.CelStandardDeclarations.StandardFunction;
import dev.cel.checker.CelStandardDeclarations.StandardFunction.Overload.Conversions;
+import dev.cel.common.types.SimpleType;
import java.util.Optional;
/** Axiomatization for CEL's type conversion functions. */
@@ -42,14 +44,12 @@ final class TypeConversionAxioms {
return Optional.of(
typeSystem.withRuntimeError(typeSystem.wrapInt(uintVal), outOfBounds));
})
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.DOUBLE_TO_INT64.celOverloadDecl(),
- createUninterpretedConversion(Conversions.DOUBLE_TO_INT64),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.DOUBLE_TO_INT64))
+ .addOverloadTranslator(
Conversions.STRING_TO_INT64.celOverloadDecl(),
- createUninterpretedConversion(Conversions.STRING_TO_INT64),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.STRING_TO_INT64))
.addUnaryOverloadTranslator(
Conversions.TIMESTAMP_TO_INT64.celOverloadDecl(),
(ctx, typeSystem, sink, arg) ->
@@ -69,14 +69,12 @@ final class TypeConversionAxioms {
return Optional.of(
typeSystem.withRuntimeError(typeSystem.wrapUint(intVal), outOfBounds));
})
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.DOUBLE_TO_UINT64.celOverloadDecl(),
- createUninterpretedConversion(Conversions.DOUBLE_TO_UINT64),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.DOUBLE_TO_UINT64))
+ .addOverloadTranslator(
Conversions.STRING_TO_UINT64.celOverloadDecl(),
- createUninterpretedConversion(Conversions.STRING_TO_UINT64),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.STRING_TO_UINT64))
.build();
private static final CelZ3FunctionAxiom DOUBLE_AXIOM =
@@ -84,18 +82,15 @@ final class TypeConversionAxioms {
.addUnaryOverloadTranslator(
Conversions.DOUBLE_TO_DOUBLE.celOverloadDecl(),
(ctx, typeSystem, sink, arg) -> Optional.of(arg))
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.INT64_TO_DOUBLE.celOverloadDecl(),
- createUninterpretedConversion(Conversions.INT64_TO_DOUBLE),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.INT64_TO_DOUBLE))
+ .addOverloadTranslator(
Conversions.UINT64_TO_DOUBLE.celOverloadDecl(),
- createUninterpretedConversion(Conversions.UINT64_TO_DOUBLE),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.UINT64_TO_DOUBLE))
+ .addOverloadTranslator(
Conversions.STRING_TO_DOUBLE.celOverloadDecl(),
- createUninterpretedConversion(Conversions.STRING_TO_DOUBLE),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.STRING_TO_DOUBLE))
.build();
private static final CelZ3FunctionAxiom STRING_AXIOM =
@@ -103,34 +98,27 @@ final class TypeConversionAxioms {
.addUnaryOverloadTranslator(
Conversions.STRING_TO_STRING.celOverloadDecl(),
(ctx, typeSystem, sink, arg) -> Optional.of(arg))
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.INT64_TO_STRING.celOverloadDecl(),
- createUninterpretedConversion(Conversions.INT64_TO_STRING),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.INT64_TO_STRING))
+ .addOverloadTranslator(
Conversions.UINT64_TO_STRING.celOverloadDecl(),
- createUninterpretedConversion(Conversions.UINT64_TO_STRING),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.UINT64_TO_STRING))
+ .addOverloadTranslator(
Conversions.DOUBLE_TO_STRING.celOverloadDecl(),
- createUninterpretedConversion(Conversions.DOUBLE_TO_STRING),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.DOUBLE_TO_STRING))
+ .addOverloadTranslator(
Conversions.BOOL_TO_STRING.celOverloadDecl(),
- createUninterpretedConversion(Conversions.BOOL_TO_STRING),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.BOOL_TO_STRING))
+ .addOverloadTranslator(
Conversions.BYTES_TO_STRING.celOverloadDecl(),
- createUninterpretedConversion(Conversions.BYTES_TO_STRING),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.BYTES_TO_STRING))
+ .addOverloadTranslator(
Conversions.TIMESTAMP_TO_STRING.celOverloadDecl(),
- createUninterpretedConversion(Conversions.TIMESTAMP_TO_STRING),
- /* isApproximated= */ true)
- .addUnaryOverloadTranslator(
+ createUninterpretedConversion(Conversions.TIMESTAMP_TO_STRING))
+ .addOverloadTranslator(
Conversions.DURATION_TO_STRING.celOverloadDecl(),
- createUninterpretedConversion(Conversions.DURATION_TO_STRING),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.DURATION_TO_STRING))
.build();
private static final CelZ3FunctionAxiom BYTES_AXIOM =
@@ -138,10 +126,9 @@ final class TypeConversionAxioms {
.addUnaryOverloadTranslator(
Conversions.BYTES_TO_BYTES.celOverloadDecl(),
(ctx, typeSystem, sink, arg) -> Optional.of(arg))
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.STRING_TO_BYTES.celOverloadDecl(),
- createUninterpretedConversion(Conversions.STRING_TO_BYTES),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.STRING_TO_BYTES))
.build();
private static final CelZ3FunctionAxiom DYN_AXIOM =
@@ -156,10 +143,9 @@ final class TypeConversionAxioms {
.addUnaryOverloadTranslator(
Conversions.DURATION_TO_DURATION.celOverloadDecl(),
(ctx, typeSystem, sink, arg) -> Optional.of(arg))
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.STRING_TO_DURATION.celOverloadDecl(),
- createUninterpretedConversion(Conversions.STRING_TO_DURATION),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.STRING_TO_DURATION))
.build();
private static final CelZ3FunctionAxiom TIMESTAMP_AXIOM =
@@ -167,10 +153,9 @@ final class TypeConversionAxioms {
.addUnaryOverloadTranslator(
Conversions.TIMESTAMP_TO_TIMESTAMP.celOverloadDecl(),
(ctx, typeSystem, sink, arg) -> Optional.of(arg))
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.STRING_TO_TIMESTAMP.celOverloadDecl(),
- createUninterpretedConversion(Conversions.STRING_TO_TIMESTAMP),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.STRING_TO_TIMESTAMP))
.addUnaryOverloadTranslator(
Conversions.INT64_TO_TIMESTAMP.celOverloadDecl(),
(ctx, typeSystem, sink, arg) -> {
@@ -186,10 +171,9 @@ final class TypeConversionAxioms {
.addUnaryOverloadTranslator(
Conversions.BOOL_TO_BOOL.celOverloadDecl(),
(ctx, typeSystem, sink, arg) -> Optional.of(arg))
- .addUnaryOverloadTranslator(
+ .addOverloadTranslator(
Conversions.STRING_TO_BOOL.celOverloadDecl(),
- createUninterpretedConversion(Conversions.STRING_TO_BOOL),
- /* isApproximated= */ true)
+ createUninterpretedConversion(Conversions.STRING_TO_BOOL))
.build();
static final ImmutableList