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
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
143 changes: 142 additions & 1 deletion verifier/src/main/java/dev/cel/verifier/CelAstToZ3Translator.java
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ 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);
}
if (isTimeWkt(createStruct.messageName())) {
return translateTimeWktStruct(celExpr, createStruct, ast);
}

// 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,136 @@ 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(0xFFFFFFFFL)));
}
currentVal = typeSystem.withRuntimeError(currentVal, ctx.mkNot(validValue));
return TranslatedValue.propagateStrict(ctx, typeSystem, currentVal, celExpr, entryTvs);
}

private static boolean isTimeWkt(String messageName) {
return messageName.equals(CelTypes.TIMESTAMP_MESSAGE)
|| messageName.equals(CelTypes.DURATION_MESSAGE);
}

private TranslatedValue translateTimeWktStruct(
CelExpr celExpr, CelExpr.CelStruct createStruct, CelAbstractSyntaxTree ast) {
boolean isTimestamp = createStruct.messageName().equals(CelTypes.TIMESTAMP_MESSAGE);
Expr<?> secondsVal = typeSystem.mkInt(0);
Expr<?> nanosVal = typeSystem.mkInt(0);
List<TranslatedValue> entryTvs = new ArrayList<>();

for (CelExpr.CelStruct.Entry entry : createStruct.entries()) {
TranslatedValue entryTv = translateExpr(entry.value(), ast);
Expr<?> val = entryTv.z3Expr();
Expr<?> unpackedVal = val;
BoolExpr hasValue = ctx.mkTrue();
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);
hasValue = typeSystem.optHasValue(optRef);
unpackedVal = typeSystem.getOptionalValue(optRef);
}
if (entry.fieldKey().equals("seconds")) {
secondsVal = ctx.mkITE(hasValue, unpackedVal, secondsVal);
} else {
nanosVal = ctx.mkITE(hasValue, unpackedVal, nanosVal);
}
entryTvs.add(entryTv);
}

IntExpr seconds = typeSystem.getInt(secondsVal);
BoolExpr secondsError =
ctx.mkOr(
ctx.mkNot(typeSystem.isInt(secondsVal)),
isTimestamp
? typeSystem.checkTimestampOverflow(seconds)
: typeSystem.checkDurationOverflow(seconds));

IntExpr nanos = typeSystem.getInt(nanosVal);
BoolExpr nanosError;
if (isTimestamp) {
nanosError =
ctx.mkOr(
ctx.mkNot(typeSystem.isInt(nanosVal)),
ctx.mkLt(nanos, ctx.mkInt(0)),
ctx.mkGe(nanos, ctx.mkInt(1_000_000_000)));
} else {
BoolExpr nanosOutOfRange =
ctx.mkOr(
ctx.mkLt(nanos, ctx.mkInt(-999_999_999)), ctx.mkGe(nanos, ctx.mkInt(1_000_000_000)));
BoolExpr signMismatch =
ctx.mkOr(
ctx.mkAnd(ctx.mkGt(seconds, ctx.mkInt(0)), ctx.mkLt(nanos, ctx.mkInt(0))),
ctx.mkAnd(ctx.mkLt(seconds, ctx.mkInt(0)), ctx.mkGt(nanos, ctx.mkInt(0))));
nanosError = ctx.mkOr(ctx.mkNot(typeSystem.isInt(nanosVal)), nanosOutOfRange, signMismatch);
}
BoolExpr nanosNonZero = ctx.mkNot(ctx.mkEq(nanos, ctx.mkInt(0)));
BoolExpr subSecondApprox = ctx.mkAnd(ctx.mkNot(nanosError), nanosNonZero);
FuncDecl<?> subSecondOffset =
typeSystem.internFuncDecl(
createStruct.messageName() + "_subsecond",
new Sort[] {ctx.getIntSort(), ctx.getIntSort()},
ctx.getIntSort());
IntExpr subSecondTime = (IntExpr) ctx.mkApp(subSecondOffset, seconds, nanos);
IntExpr encodedTime = (IntExpr) ctx.mkITE(subSecondApprox, subSecondTime, seconds);
Expr<?> wrapped =
isTimestamp ? typeSystem.wrapTimestamp(encodedTime) : typeSystem.wrapDuration(encodedTime);
Expr<?> result = typeSystem.withRuntimeError(wrapped, secondsError, nanosError);
TranslatedValue strictResult =
TranslatedValue.propagateStrict(ctx, typeSystem, result, celExpr, entryTvs);
return TranslatedValue.create(
strictResult.z3Expr(),
celExpr,
typeSystem,
CelZ3TypeSystem.mkOrFlattened(ctx, strictResult.isApproximate(), subSecondApprox));
}

private Expr<?> getDefaultValueForType(CelType type) {
if (type instanceof NullableType) {
return typeSystem.mkNull();
Expand Down
29 changes: 27 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,26 @@ public Optional<CelType> findType(String typeName) {
}
};

private static final ImmutableList<Descriptor> WELL_KNOWN_DESCRIPTORS =
ImmutableList.of(
WellKnownProto.DURATION,
WellKnownProto.JSON_LIST_VALUE,
WellKnownProto.JSON_STRUCT_VALUE,
WellKnownProto.JSON_VALUE,
WellKnownProto.TIMESTAMP,
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 +98,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 +180,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
4 changes: 3 additions & 1 deletion verifier/src/main/java/dev/cel/verifier/tools/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ java_library(
"//common:cel_ast",
"//common:compiler_common",
"//common:options",
"//common/internal:cel_descriptor_pools",
"//common/internal:well_known_proto",
"//common/types",
"//common/types:cel_types",
"//common/types:type_providers",
"//compiler",
"//compiler:compiler_builder",
"//extensions",
"//parser:macro",
Expand All @@ -64,6 +65,7 @@ java_library(
"//verifier:verifier_factory",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:info_picocli_picocli",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ private static void printHelp(String topic, PrintStream out) {
out.println();
out.println("Supported Types:");
out.println(" - Primitive types: int, uint, string, bool, double, bytes, dyn");
out.println(" - Well-known types: timestamp, duration");
out.println(" - Well-known types: timestamp, duration, google.protobuf.*");
out.println(" - List types: list<T> (e.g., list<int>, list<string>)");
out.println(" - Map types: map<K,V> (e.g., map<string,int>, map<string,string>)");
out.println(" - Optional types: optional<T> (e.g., optional<string>, optional<int>)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@

package dev.cel.verifier.tools;

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.protobuf.Descriptors.Descriptor;
import dev.cel.bundle.Cel;
import dev.cel.bundle.CelBuilder;
import dev.cel.bundle.CelFactory;
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelOptions;
import dev.cel.common.internal.DefaultDescriptorPool;
import dev.cel.common.internal.WellKnownProto;
import dev.cel.common.types.CelType;
import dev.cel.compiler.CelCompiler;
import dev.cel.compiler.CelCompilerBuilder;
import dev.cel.compiler.CelCompilerFactory;
import dev.cel.extensions.CelExtensions;
import dev.cel.parser.CelStandardMacro;
import dev.cel.policy.CelPolicy;
Expand All @@ -42,6 +46,26 @@
/** Core decoupled engine that executes formal verification operations. */
final class CelVerifierToolCore {

private static final ImmutableList<Descriptor> WELL_KNOWN_DESCRIPTORS =
ImmutableList.of(
WellKnownProto.DURATION,
WellKnownProto.JSON_LIST_VALUE,
WellKnownProto.JSON_STRUCT_VALUE,
WellKnownProto.JSON_VALUE,
WellKnownProto.TIMESTAMP,
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 CelVerifierToolCore() {}

/** Checks if a single CEL expression is satisfiable. */
Expand Down Expand Up @@ -105,35 +129,24 @@ static CelVerificationResult verifyPolicyEquivalence(
}

static CelCompiler buildCompiler(Map<String, CelType> variables) {
CelCompilerBuilder builder =
CelCompilerFactory.standardCelCompilerBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addLibraries(
CelExtensions.bindings(),
CelExtensions.comprehensions(),
CelExtensions.encoders(CelOptions.DEFAULT),
CelExtensions.lists(),
CelExtensions.math(),
CelExtensions.optional(),
CelExtensions.protos(),
CelExtensions.regex(),
CelExtensions.sets(CelOptions.DEFAULT),
CelExtensions.strings());
for (Map.Entry<String, CelType> entry : variables.entrySet()) {
builder.addVar(entry.getKey(), entry.getValue());
}
return builder.build();
return buildCel(variables);
}

static Cel buildCel(Map<String, CelType> variables) {
CelBuilder celBuilder =
CelFactory.plannerCelBuilder()
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
.addMessageTypes(WELL_KNOWN_DESCRIPTORS)
.addCompilerLibraries(
CelExtensions.optional(),
CelExtensions.bindings(),
CelExtensions.comprehensions(),
CelExtensions.encoders(CelOptions.DEFAULT),
CelExtensions.lists(),
CelExtensions.math(),
CelExtensions.optional(),
CelExtensions.protos(),
CelExtensions.regex(),
CelExtensions.sets(CelOptions.DEFAULT),
CelExtensions.strings());
for (Map.Entry<String, CelType> entry : variables.entrySet()) {
celBuilder.addVar(entry.getKey(), entry.getValue());
Expand Down
Loading
Loading