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
3 changes: 3 additions & 0 deletions verifier/src/main/java/dev/cel/verifier/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ java_library(
"//common:operator",
"//common/ast",
"//common/ast:cel_block",
"//common/internal:cel_descriptor_pools",
"//common/internal:well_known_proto",
"//common/types",
"//common/types:cel_types",
"//common/types:type_providers",
Expand All @@ -187,6 +189,7 @@ java_library(
"//verifier/axioms",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:org_jspecify_jspecify",
"@maven//:tools_aqua_z3_turnkey",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.microsoft.z3.BoolExpr;
import com.microsoft.z3.Context;
import com.microsoft.z3.Expr;
import com.microsoft.z3.FPExpr;
import com.microsoft.z3.FuncDecl;
import com.microsoft.z3.IntExpr;
import com.microsoft.z3.Quantifier;
Expand Down Expand Up @@ -82,6 +83,7 @@ final class CelAstToZ3Translator {
private static final String EMPTY_LIST_PREFIX = "!empty_list";
private static final String EMPTY_MAP_PREFIX = "!empty_map";
private static final String NULL_VALUE_FIELD = "null_value";
private static final long MAX_UINT32 = 0xFFFFFFFFL;
private final Context ctx;
private final CelZ3TypeSystem typeSystem;
private final CelZ3OperatorTranslator operatorTranslator;
Expand Down Expand Up @@ -401,6 +403,10 @@ private TranslatedValue translateStruct(CelExpr celExpr, CelAbstractSyntaxTree a
if (isJsonWkt(createStruct.messageName())) {
return translateJsonWktStruct(celExpr, createStruct, ast);
}
if (CelTypes.isWrapperType(createStruct.messageName())) {
return translateWrapperWktStruct(celExpr, createStruct, ast);
}
// TODO: Support google.protobuf.Timestamp and google.protobuf.Duration.

// Bypass SMT when the struct is empty (return the cached SMT default pointer)
if (createStruct.entries().isEmpty()) {
Expand Down Expand Up @@ -517,7 +523,12 @@ private TranslatedValue translateJsonWktStruct(
&& entry.fieldKey().equals(NULL_VALUE_FIELD);

if (entry.optionalEntry()) {
Expr<?> optRef = typeSystem.getOptionalRef(finalVal);
Expr<?> checkedVal =
typeSystem.withRuntimeError(finalVal, ctx.mkNot(typeSystem.isOptional(finalVal)));
entryTv =
TranslatedValue.create(
checkedVal, entry.value(), typeSystem, entryTv.isApproximate());
Expr<?> optRef = typeSystem.getOptionalRef(checkedVal);
BoolExpr hasValue = typeSystem.optHasValue(optRef);

Expr<?> unpackedVal = typeSystem.getOptionalValue(optRef);
Expand All @@ -533,6 +544,61 @@ private TranslatedValue translateJsonWktStruct(
ctx, typeSystem, finalVal, celExpr, ImmutableList.of(entryTv));
}

private TranslatedValue translateWrapperWktStruct(
CelExpr celExpr, CelExpr.CelStruct createStruct, CelAbstractSyntaxTree ast) {
String typeName = createStruct.messageName();
CelType wrapperType =
CelTypes.getWellKnownCelType(typeName)
.orElseThrow(
() -> new IllegalArgumentException("Unexpected wrapper type: " + typeName));
CelType targetType = ((NullableType) wrapperType).targetType();
Expr<?> currentVal = getDefaultValueForType(targetType);

List<TranslatedValue> entryTvs = new ArrayList<>();
for (CelExpr.CelStruct.Entry entry : createStruct.entries()) {
TranslatedValue entryTv = translateExpr(entry.value(), ast);
Expr<?> val = entryTv.z3Expr();
if (entry.optionalEntry()) {
Expr<?> checkedVal =
typeSystem.withRuntimeError(val, ctx.mkNot(typeSystem.isOptional(val)));
entryTv =
TranslatedValue.create(
checkedVal, entry.value(), typeSystem, entryTv.isApproximate());
Expr<?> optRef = typeSystem.getOptionalRef(checkedVal);
BoolExpr hasValue = typeSystem.optHasValue(optRef);
currentVal = ctx.mkITE(hasValue, typeSystem.getOptionalValue(optRef), currentVal);
} else {
currentVal = val;
}
entryTvs.add(entryTv);
}
BoolExpr validValue = createTypeConstraintForType(currentVal, targetType);
if (typeName.equals(CelTypes.INT32_WRAPPER_MESSAGE)) {
IntExpr intVal = typeSystem.getInt(currentVal);
validValue =
ctx.mkAnd(
validValue,
ctx.mkGe(intVal, ctx.mkInt(Integer.MIN_VALUE)),
ctx.mkLe(intVal, ctx.mkInt(Integer.MAX_VALUE)));
} else if (typeName.equals(CelTypes.UINT32_WRAPPER_MESSAGE)) {
IntExpr uintVal = typeSystem.getUint(currentVal);
validValue =
ctx.mkAnd(
validValue,
ctx.mkGe(uintVal, ctx.mkInt(0)),
ctx.mkLe(uintVal, ctx.mkInt(MAX_UINT32)));
} else if (typeName.equals(CelTypes.FLOAT_WRAPPER_MESSAGE)) {
FPExpr doubleVal = typeSystem.getDouble(currentVal);
FPExpr narrowedFloat =
ctx.mkFPToFP(ctx.mkFPRoundNearestTiesToEven(), doubleVal, ctx.mkFPSortSingle());
FPExpr widenedDouble =
ctx.mkFPToFP(ctx.mkFPRoundNearestTiesToEven(), narrowedFloat, ctx.mkFPSortDouble());
currentVal = typeSystem.wrapDouble(widenedDouble);
}
currentVal = typeSystem.withRuntimeError(currentVal, ctx.mkNot(validValue));
return TranslatedValue.propagateStrict(ctx, typeSystem, currentVal, celExpr, entryTvs);
}

private Expr<?> getDefaultValueForType(CelType type) {
if (type instanceof NullableType) {
return typeSystem.mkNull();
Expand Down
28 changes: 26 additions & 2 deletions verifier/src/main/java/dev/cel/verifier/CelVerifierZ3Impl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.Descriptors.Descriptor;
import com.microsoft.z3.BoolExpr;
import com.microsoft.z3.Context;
import com.microsoft.z3.Model;
Expand All @@ -32,6 +34,8 @@
import dev.cel.bundle.Cel;
import dev.cel.bundle.CelFactory;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.internal.DefaultDescriptorPool;
import dev.cel.common.internal.WellKnownProto;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
import dev.cel.optimizer.CelOptimizationException;
Expand Down Expand Up @@ -64,6 +68,25 @@ public Optional<CelType> findType(String typeName) {
}
};

private static final ImmutableList<Descriptor> WELL_KNOWN_DESCRIPTORS =
ImmutableList.of(
// TODO: Support WellKnownProto.DURATION and WellKnownProto.TIMESTAMP.
WellKnownProto.JSON_LIST_VALUE,
WellKnownProto.JSON_STRUCT_VALUE,
WellKnownProto.JSON_VALUE,
WellKnownProto.FLOAT_VALUE,
WellKnownProto.INT32_VALUE,
WellKnownProto.INT64_VALUE,
WellKnownProto.STRING_VALUE,
WellKnownProto.BOOL_VALUE,
WellKnownProto.BYTES_VALUE,
WellKnownProto.DOUBLE_VALUE,
WellKnownProto.UINT32_VALUE,
WellKnownProto.UINT64_VALUE)
.stream()
.map(DefaultDescriptorPool::getWellKnownProtoDescriptor)
.collect(toImmutableList());

private final Duration timeout;
private final int comprehensionUnrollLimit;
private final ImmutableSet<String> unknownIdentifiers;
Expand All @@ -74,7 +97,8 @@ public Optional<CelType> findType(String typeName) {
private final Cel cel;

static Builder newBuilder() {
return new Builder(CelFactory.plannerCelBuilder().build());
return new Builder(
CelFactory.plannerCelBuilder().addMessageTypes(WELL_KNOWN_DESCRIPTORS).build());
}

static Builder newBuilder(Cel cel) {
Expand Down Expand Up @@ -155,7 +179,7 @@ private Builder(Cel cel) {
this.comprehensionUnrollLimit = 5;
this.unknownIdentifiers = ImmutableSet.builder();
this.functionAxioms = ImmutableList.builder();
this.typeProvider = EMPTY_TYPE_PROVIDER;
this.typeProvider = cel.getTypeProvider();
this.cel = cel;
}
}
Expand Down
64 changes: 61 additions & 3 deletions verifier/src/test/java/dev/cel/verifier/CelVerifierZ3ImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,7 @@ public final class CelVerifierZ3ImplTest {
TestAllTypes.getDescriptor(), TestAllTypes.NestedMessage.getDescriptor()))
.build();

private static final CelVerifier VERIFIER =
CelVerifierFactory.newVerifier(CEL).setTypeProvider(TYPE_PROVIDER).build();
private static final CelVerifier VERIFIER = CelVerifierFactory.newVerifier(CEL).build();

@Before
public void setUp() {
Expand Down Expand Up @@ -2159,6 +2158,54 @@ private enum EquivalenceTestCase {
JSON_VALUE_OPTIONAL_NULL_VALUE_NONE(
"google.protobuf.Value{?null_value: optional.none()}", "null"),
JSON_VALUE_OPTIONAL_NULL_VALUE_OF("google.protobuf.Value{?null_value: optional.of(0)}", "null"),
WRAPPER_INT64_VALUE("google.protobuf.Int64Value{value: 42}", "42"),
WRAPPER_INT64_EMPTY("google.protobuf.Int64Value{}", "0"),
WRAPPER_INT32_VALUE("google.protobuf.Int32Value{value: 42}", "42"),
WRAPPER_INT32_EMPTY("google.protobuf.Int32Value{}", "0"),
WRAPPER_UINT64_VALUE("google.protobuf.UInt64Value{value: 42u}", "42u"),
WRAPPER_UINT64_EMPTY("google.protobuf.UInt64Value{}", "0u"),
WRAPPER_UINT32_VALUE("google.protobuf.UInt32Value{value: 42u}", "42u"),
WRAPPER_UINT32_EMPTY("google.protobuf.UInt32Value{}", "0u"),
WRAPPER_DOUBLE_VALUE("google.protobuf.DoubleValue{value: 1.5}", "1.5"),
WRAPPER_DOUBLE_EMPTY("google.protobuf.DoubleValue{}", "0.0"),
WRAPPER_FLOAT_VALUE("google.protobuf.FloatValue{value: 1.5}", "1.5"),
WRAPPER_FLOAT_EMPTY("google.protobuf.FloatValue{}", "0.0"),
WRAPPER_BOOL_VALUE("google.protobuf.BoolValue{value: true}", "true"),
WRAPPER_BOOL_EMPTY("google.protobuf.BoolValue{}", "false"),
WRAPPER_STRING_VALUE("google.protobuf.StringValue{value: 'hello'}", "'hello'"),
WRAPPER_STRING_EMPTY("google.protobuf.StringValue{}", "''"),
WRAPPER_BYTES_VALUE("google.protobuf.BytesValue{value: b'abc'}", "b'abc'"),
WRAPPER_BYTES_EMPTY("google.protobuf.BytesValue{}", "b''"),
WRAPPER_INT64_OPTIONAL_NONE("google.protobuf.Int64Value{?value: optional.none()}", "0"),
WRAPPER_INT64_OPTIONAL_OF("google.protobuf.Int64Value{?value: optional.of(7)}", "7"),
WRAPPER_INT64_VARIABLE("google.protobuf.Int64Value{value: x}", "x"),
WRAPPER_INT32_BOUNDED_VARIABLE(
"x >= -2147483648 && x <= 2147483647 ? google.protobuf.Int32Value{value: x} == x : true",
"true"),
WRAPPER_UINT32_BOUNDED_VARIABLE(
"u <= 4294967295u ? google.protobuf.UInt32Value{value: u} == u : true", "true"),
WRAPPER_FLOAT_EXACT_MAX_VALUE(
"google.protobuf.FloatValue{value: 3.4028234663852886e38}", "3.4028234663852886e38"),
WRAPPER_FLOAT_EXACT_MIN_VALUE(
"google.protobuf.FloatValue{value: -3.4028234663852886e38}", "-3.4028234663852886e38"),
WRAPPER_FLOAT_PRECISION_LOSS_WIDENED(
"google.protobuf.FloatValue{value: 0.1}", "0.10000000149011612"),
WRAPPER_FLOAT_PRECISION_LOSS_NOT_EQUAL_ORIGINAL(
"google.protobuf.FloatValue{value: 0.1} == 0.1", "false"),
WRAPPER_FLOAT_IDEMPOTENT_LITERAL(
"google.protobuf.FloatValue{value: google.protobuf.FloatValue{value: 0.1}}",
"google.protobuf.FloatValue{value: 0.1}"),
WRAPPER_FLOAT_SYMBOLIC_EXACT_VALUE(
"d == 1.5 ? google.protobuf.FloatValue{value: d} == d : true", "true"),
WRAPPER_FLOAT_OVERFLOW_TO_POSITIVE_INFINITY(
"google.protobuf.FloatValue{value: 1.0e39} == 1.0e39", "false"),
WRAPPER_FLOAT_OVERFLOW_TO_NEGATIVE_INFINITY(
"google.protobuf.FloatValue{value: -1.0e39} == -1.0e39", "false"),
WRAPPER_FLOAT_UNDERFLOW_TO_ZERO("google.protobuf.FloatValue{value: 1.0e-46} == 0.0", "true"),
JSON_VALUE_OPTIONAL_DYN_NON_OPTIONAL_ERROR(
"google.protobuf.Value{?string_value: dyn('hello')} == null", "1 / 0 == 1"),
WRAPPER_OPTIONAL_DYN_NON_OPTIONAL_ERROR(
"google.protobuf.Int64Value{?value: dyn(42)} == 0", "1 / 0 == 1"),
OPTIONAL_INDEX_LIST_UNWRAPPING("optional.of([1, 2, 3])[?0]", "optional.of(1)"),
OPTIONAL_INDEX_MAP_UNWRAPPING("optional.of({'a': 1})[?'a']", "optional.of(1)"),
OPTIONAL_INDEX_UNWRAPPING_NONE("optional.none()[?0]", "optional.none()"),
Expand Down Expand Up @@ -2259,7 +2306,18 @@ private enum EquivalenceViolationTestCase {
"TestAllTypes{single_int64_wrapper: 0}.?single_int64_wrapper", "optional.none()"),
OPTIONAL_DYNAMIC_TARGET_TYPE_MISMATCH("dyn_var.?a == optional.none()", "false"),
OPTIONAL_NESTED_NONE_VS_MISSING(
"{'a': optional.none()}.?a.orValue(optional.of(1))", "optional.of(1)");
"{'a': optional.none()}.?a.orValue(optional.of(1))", "optional.of(1)"),
WRAPPER_INT32_UNBOUNDED_OVERFLOW("google.protobuf.Int32Value{value: x}", "x"),
WRAPPER_UINT32_UNBOUNDED_OVERFLOW("google.protobuf.UInt32Value{value: u}", "u"),
WRAPPER_FLOAT_UNBOUNDED_OVERFLOW("google.protobuf.FloatValue{value: d}", "d"),
WRAPPER_FLOAT_PRECISION_LOSS_VIOLATION("google.protobuf.FloatValue{value: 0.1}", "0.1"),
WRAPPER_FLOAT_BOUNDED_VARIABLE_PRECISION_LOSS(
"d >= -3.4028234663852886e38 && d <= 3.4028234663852886e38 ?"
+ " google.protobuf.FloatValue{value: d} == d : true",
"true"),
WRAPPER_FLOAT_DYN_NON_DOUBLE_FAILS(
"google.protobuf.FloatValue{value: dyn('bad')} == 0.0", "false"),
WRAPPER_INT64_DYN_NULL_FAILS("google.protobuf.Int64Value{value: dyn(null)} == null", "true");

final String exprA;
final String exprB;
Expand Down
Loading