Skip to content
Merged
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
48 changes: 48 additions & 0 deletions verifier/src/main/java/dev/cel/verifier/axioms/AxiomHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

import com.microsoft.z3.BoolExpr;
import com.microsoft.z3.Context;
import com.microsoft.z3.FPExpr;
import com.microsoft.z3.IntExpr;
import com.microsoft.z3.RealExpr;

/** Helper methods for Z3 axioms operations. */
final class AxiomHelpers {
Expand Down Expand Up @@ -47,5 +49,51 @@ static IntExpr mkTruncatedMod(Context ctx, IntExpr a, IntExpr b) {
return (IntExpr) ctx.mkSub(a, ctx.mkMul(mkTruncatedDiv(ctx, a, b), b));
}

/**
* Safe comparison between a Z3 Real (from int/uint) and a Z3 FloatingPoint (double) for {@code
* <}.
*/
static BoolExpr mkRealLtFp(Context ctx, RealExpr real, FPExpr fp) {
return mkSafeFpComparison(ctx, fp, isPosInf(ctx, fp), ctx.mkLt(real, ctx.mkFPToReal(fp)));
}

/**
* Safe comparison between a Z3 FloatingPoint (double) and a Z3 Real (from int/uint) for {@code
* <}.
*/
static BoolExpr mkFpLtReal(Context ctx, FPExpr fp, RealExpr real) {
return mkSafeFpComparison(ctx, fp, isNegInf(ctx, fp), ctx.mkLt(ctx.mkFPToReal(fp), real));
}

/**
* Safe comparison between a Z3 Real (from int/uint) and a Z3 FloatingPoint (double) for {@code
* <=}.
*/
static BoolExpr mkRealLeFp(Context ctx, RealExpr real, FPExpr fp) {
return mkSafeFpComparison(ctx, fp, isPosInf(ctx, fp), ctx.mkLe(real, ctx.mkFPToReal(fp)));
}

/**
* Safe comparison between a Z3 FloatingPoint (double) and a Z3 Real (from int/uint) for {@code
* <=}.
*/
static BoolExpr mkFpLeReal(Context ctx, FPExpr fp, RealExpr real) {
return mkSafeFpComparison(ctx, fp, isNegInf(ctx, fp), ctx.mkLe(ctx.mkFPToReal(fp), real));
}

private static BoolExpr isPosInf(Context ctx, FPExpr fp) {
return ctx.mkAnd(ctx.mkFPIsInfinite(fp), ctx.mkFPIsPositive(fp));
}

private static BoolExpr isNegInf(Context ctx, FPExpr fp) {
return ctx.mkAnd(ctx.mkFPIsInfinite(fp), ctx.mkFPIsNegative(fp));
}

private static BoolExpr mkSafeFpComparison(
Context ctx, FPExpr fp, BoolExpr infCondition, BoolExpr finiteComparison) {
BoolExpr isFinite = ctx.mkNot(ctx.mkOr(ctx.mkFPIsNaN(fp), ctx.mkFPIsInfinite(fp)));
return ctx.mkOr(infCondition, ctx.mkAnd(isFinite, finiteComparison));
}

private AxiomHelpers() {}
}
28 changes: 16 additions & 12 deletions verifier/src/main/java/dev/cel/verifier/axioms/GreaterAxiom.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,33 +88,37 @@ final class GreaterAxiom {
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGt(
ctx.mkInt2Real(typeSystem.getInt(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
AxiomHelpers.mkFpLtReal(
ctx,
(FPExpr) typeSystem.getDouble(rhs),
ctx.mkInt2Real(typeSystem.getInt(lhs))))))
.addBinaryOverloadTranslator(
Comparison.GREATER_UINT64_DOUBLE.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGt(
ctx.mkInt2Real(typeSystem.getUint(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
AxiomHelpers.mkFpLtReal(
ctx,
(FPExpr) typeSystem.getDouble(rhs),
ctx.mkInt2Real(typeSystem.getUint(lhs))))))
.addBinaryOverloadTranslator(
Comparison.GREATER_DOUBLE_INT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGt(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
ctx.mkInt2Real(typeSystem.getInt(rhs))))))
AxiomHelpers.mkRealLtFp(
ctx,
ctx.mkInt2Real(typeSystem.getInt(rhs)),
(FPExpr) typeSystem.getDouble(lhs)))))
.addBinaryOverloadTranslator(
Comparison.GREATER_DOUBLE_UINT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGt(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
ctx.mkInt2Real(typeSystem.getUint(rhs))))))
AxiomHelpers.mkRealLtFp(
ctx,
ctx.mkInt2Real(typeSystem.getUint(rhs)),
(FPExpr) typeSystem.getDouble(lhs)))))
.addBinaryOverloadTranslator(
Comparison.GREATER_INT64_UINT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,33 +88,37 @@ final class GreaterEqualsAxiom {
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGe(
ctx.mkInt2Real(typeSystem.getInt(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
AxiomHelpers.mkFpLeReal(
ctx,
(FPExpr) typeSystem.getDouble(rhs),
ctx.mkInt2Real(typeSystem.getInt(lhs))))))
.addBinaryOverloadTranslator(
Comparison.GREATER_EQUALS_UINT64_DOUBLE.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGe(
ctx.mkInt2Real(typeSystem.getUint(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
AxiomHelpers.mkFpLeReal(
ctx,
(FPExpr) typeSystem.getDouble(rhs),
ctx.mkInt2Real(typeSystem.getUint(lhs))))))
.addBinaryOverloadTranslator(
Comparison.GREATER_EQUALS_DOUBLE_INT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGe(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
ctx.mkInt2Real(typeSystem.getInt(rhs))))))
AxiomHelpers.mkRealLeFp(
ctx,
ctx.mkInt2Real(typeSystem.getInt(rhs)),
(FPExpr) typeSystem.getDouble(lhs)))))
.addBinaryOverloadTranslator(
Comparison.GREATER_EQUALS_DOUBLE_UINT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkGe(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
ctx.mkInt2Real(typeSystem.getUint(rhs))))))
AxiomHelpers.mkRealLeFp(
ctx,
ctx.mkInt2Real(typeSystem.getUint(rhs)),
(FPExpr) typeSystem.getDouble(lhs)))))
.addBinaryOverloadTranslator(
Comparison.GREATER_EQUALS_INT64_UINT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Expand Down
20 changes: 12 additions & 8 deletions verifier/src/main/java/dev/cel/verifier/axioms/LessAxiom.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,32 +88,36 @@ final class LessAxiom {
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLt(
AxiomHelpers.mkRealLtFp(
ctx,
ctx.mkInt2Real(typeSystem.getInt(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
(FPExpr) typeSystem.getDouble(rhs)))))
.addBinaryOverloadTranslator(
Comparison.LESS_UINT64_DOUBLE.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLt(
AxiomHelpers.mkRealLtFp(
ctx,
ctx.mkInt2Real(typeSystem.getUint(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
(FPExpr) typeSystem.getDouble(rhs)))))
.addBinaryOverloadTranslator(
Comparison.LESS_DOUBLE_INT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLt(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
AxiomHelpers.mkFpLtReal(
ctx,
(FPExpr) typeSystem.getDouble(lhs),
ctx.mkInt2Real(typeSystem.getInt(rhs))))))
.addBinaryOverloadTranslator(
Comparison.LESS_DOUBLE_UINT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLt(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
AxiomHelpers.mkFpLtReal(
ctx,
(FPExpr) typeSystem.getDouble(lhs),
ctx.mkInt2Real(typeSystem.getUint(rhs))))))
.addBinaryOverloadTranslator(
Comparison.LESS_INT64_UINT64.celOverloadDecl(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,32 +88,36 @@ final class LessEqualsAxiom {
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLe(
AxiomHelpers.mkRealLeFp(
ctx,
ctx.mkInt2Real(typeSystem.getInt(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
(FPExpr) typeSystem.getDouble(rhs)))))
.addBinaryOverloadTranslator(
Comparison.LESS_EQUALS_UINT64_DOUBLE.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLe(
AxiomHelpers.mkRealLeFp(
ctx,
ctx.mkInt2Real(typeSystem.getUint(lhs)),
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(rhs))))))
(FPExpr) typeSystem.getDouble(rhs)))))
.addBinaryOverloadTranslator(
Comparison.LESS_EQUALS_DOUBLE_INT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLe(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
AxiomHelpers.mkFpLeReal(
ctx,
(FPExpr) typeSystem.getDouble(lhs),
ctx.mkInt2Real(typeSystem.getInt(rhs))))))
.addBinaryOverloadTranslator(
Comparison.LESS_EQUALS_DOUBLE_UINT64.celOverloadDecl(),
(ctx, typeSystem, constraintSink, lhs, rhs) ->
Optional.of(
typeSystem.wrapBool(
ctx.mkLe(
ctx.mkFPToReal((FPExpr) typeSystem.getDouble(lhs)),
AxiomHelpers.mkFpLeReal(
ctx,
(FPExpr) typeSystem.getDouble(lhs),
ctx.mkInt2Real(typeSystem.getUint(rhs))))))
.addBinaryOverloadTranslator(
Comparison.LESS_EQUALS_INT64_UINT64.celOverloadDecl(),
Expand Down
50 changes: 50 additions & 0 deletions verifier/src/test/java/dev/cel/verifier/CelVerifierZ3ImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,34 @@ private enum IsAlwaysTrueTestCase {
IEEE_754_NEG_ZERO_IN_LIST("-0.0 in [0.0]"),
IEEE_754_POS_ZERO_IN_LIST("0.0 in [-0.0]"),
IEEE_754_NAN_IN_LIST_FALSE("!((0.0/0.0) in [1.0, (0.0/0.0)])"),
CROSS_TYPE_NUMERIC_LESS_NAN_INT_FALSE("!(x < (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_LESS_INT_NAN_FALSE("!((0.0 / 0.0) < x)"),
CROSS_TYPE_NUMERIC_LESS_EQUALS_NAN_INT_FALSE("!(x <= (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_GREATER_NAN_INT_FALSE("!(x > (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_GREATER_EQUALS_NAN_INT_FALSE("!(x >= (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_LESS_NAN_UINT_FALSE("!(u < (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_LESS_UINT_NAN_FALSE("!((0.0 / 0.0) < u)"),
CROSS_TYPE_NUMERIC_LESS_EQUALS_NAN_UINT_FALSE("!(u <= (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_GREATER_NAN_UINT_FALSE("!(u > (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_GREATER_EQUALS_NAN_UINT_FALSE("!(u >= (0.0 / 0.0))"),
CROSS_TYPE_NUMERIC_LESS_POS_INF_INT("x < (1.0 / 0.0)"),
CROSS_TYPE_NUMERIC_LESS_EQUALS_POS_INF_INT("x <= (1.0 / 0.0)"),
CROSS_TYPE_NUMERIC_GREATER_POS_INF_INT_FALSE("!(x > (1.0 / 0.0))"),
CROSS_TYPE_NUMERIC_GREATER_EQUALS_POS_INF_INT_FALSE("!(x >= (1.0 / 0.0))"),
CROSS_TYPE_NUMERIC_GREATER_POS_INF_DOUBLE_INT("(1.0 / 0.0) > x"),
CROSS_TYPE_NUMERIC_GREATER_EQUALS_POS_INF_DOUBLE_INT("(1.0 / 0.0) >= x"),
CROSS_TYPE_NUMERIC_LESS_POS_INF_DOUBLE_INT_FALSE("!((1.0 / 0.0) < x)"),
CROSS_TYPE_NUMERIC_LESS_EQUALS_POS_INF_DOUBLE_INT_FALSE("!((1.0 / 0.0) <= x)"),
CROSS_TYPE_NUMERIC_GREATER_NEG_INF_INT("x > (-1.0 / 0.0)"),
CROSS_TYPE_NUMERIC_GREATER_EQUALS_NEG_INF_INT("x >= (-1.0 / 0.0)"),
CROSS_TYPE_NUMERIC_LESS_NEG_INF_INT_FALSE("!(x < (-1.0 / 0.0))"),
CROSS_TYPE_NUMERIC_LESS_EQUALS_NEG_INF_INT_FALSE("!(x <= (-1.0 / 0.0))"),
CROSS_TYPE_NUMERIC_LESS_NEG_INF_DOUBLE_INT("(-1.0 / 0.0) < x"),
CROSS_TYPE_NUMERIC_LESS_EQUALS_NEG_INF_DOUBLE_INT("(-1.0 / 0.0) <= x"),
CROSS_TYPE_NUMERIC_LESS_POS_INF_UINT("u < (1.0 / 0.0)"),
CROSS_TYPE_NUMERIC_LESS_EQUALS_POS_INF_UINT("u <= (1.0 / 0.0)"),
CROSS_TYPE_NUMERIC_GREATER_NEG_INF_UINT("u > (-1.0 / 0.0)"),
CROSS_TYPE_NUMERIC_GREATER_EQUALS_NEG_INF_UINT("u >= (-1.0 / 0.0)"),
STRING_IN_LIST("'b' in ['a', 'b', 'c']"),
INT_IN_MAP("1 in {1: 2}"),
MAP_MISSING_KEY("!(3 in {1: 'a', 2: 'b'})"),
Expand Down Expand Up @@ -1426,6 +1454,14 @@ private enum EquivalenceTestCase {
CROSS_TYPE_NUMERIC_EQUALITY_INT_DOUBLE("request == 1.0", "request == 1"),
CROSS_TYPE_NUMERIC_EQUALITY_UINT_DOUBLE("request == 1u", "request == 1.0"),
CROSS_TYPE_NUMERIC_EQUALITY_INT_UINT("request == 1", "request == 1u"),
CROSS_TYPE_NUMERIC_NAN_LESS_INT("x < (0.0 / 0.0)", "false"),
CROSS_TYPE_NUMERIC_NAN_LESS_UINT("u < (0.0 / 0.0)", "false"),
CROSS_TYPE_NUMERIC_NAN_GREATER_INT("x > (0.0 / 0.0)", "false"),
CROSS_TYPE_NUMERIC_NAN_GREATER_UINT("u > (0.0 / 0.0)", "false"),
CROSS_TYPE_NUMERIC_POS_INF_GREATER_INT("(1.0 / 0.0) > x", "true"),
CROSS_TYPE_NUMERIC_POS_INF_LESS_INT("x < (1.0 / 0.0)", "true"),
CROSS_TYPE_NUMERIC_NEG_INF_GREATER_INT("x > (-1.0 / 0.0)", "true"),
CROSS_TYPE_NUMERIC_NEG_INF_LESS_INT("(-1.0 / 0.0) < x", "true"),
STATIC_DOUBLE_EQUALITY("d + 1.0 == d + 1.0", "d == d"),
MAP_FIELD_SELECT("string_int_map.my_field > 0", "string_int_map['my_field'] > 0"),
HETEROGENEOUS_LIST_SIZES_SAFE_FALSE("!([1, 2] == [1, 2, 3])", "true"),
Expand Down Expand Up @@ -2715,4 +2751,18 @@ public void verifyImplication_loopExceedsLimit_returnsTruncatedInconclusive() th
assertThat(result.message())
.contains("implication holds within the current loop unroll limit");
}

@Test
public void verifyImplication_symbolicNan_crossNumericComparisonReturnsFalse() throws Exception {
// Assumption: d is NaN (d != d)
CelAbstractSyntaxTree assumeAst = CEL.compile("d != d").getAst();
// Assertion: x < d is false when d is NaN
CelAbstractSyntaxTree assertAst = CEL.compile("!(x < d)").getAst();

CelVerifier verifier = CelVerifierFactory.newVerifier().build();
CelVerificationResult result =
((CelVerifierZ3Impl) verifier)
.verifyImplication(assumeAst, assertAst, ImmutableMap.of(), "Implication");
assertThat(result.status()).isEqualTo(VerificationStatus.VERIFIED);
}
}
Loading