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
1 change: 1 addition & 0 deletions common/src/main/java/dev/cel/common/values/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ java_library(
"//protobuf:cel_lite_descriptor",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:org_jspecify_jspecify",
"@maven_android//:com_google_protobuf_protobuf_javalite",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
import com.google.common.collect.Iterables;
import com.google.common.primitives.UnsignedLong;
import com.google.errorprone.annotations.Immutable;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.Internal.EnumLite;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import com.google.protobuf.MessageLiteOrBuilder;
import com.google.protobuf.WireFormat;
import dev.cel.common.annotations.Internal;
import dev.cel.common.internal.CelLiteDescriptorPool;
Expand Down Expand Up @@ -62,6 +64,9 @@ public final class ProtoLiteCelValueConverter extends BaseProtoCelValueConverter
private static final String MAP_VALUE_FIELD_NAME = "value";
private static final int MAP_KEY_FIELD_NUMBER = 1;
private static final int MAP_VALUE_FIELD_NUMBER = 2;
// Protobuf's default recursion limit. Nested Anys are unwrapped in a loop since each payload is
// parsed afresh, outside that limit.
private static final int MAX_ANY_NESTING_DEPTH = 100;

private final CelLiteDescriptorPool descriptorPool;

Expand Down Expand Up @@ -218,6 +223,34 @@ private Object toRuntimeValue(MessageLite msg, MessageLiteDescriptor descriptor)
return fromWellKnownProto(msg, checkNotNull(wellKnownProto));
}

@Override
protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wellKnownProto) {
if (wellKnownProto != WellKnownProto.ANY_VALUE) {
return super.fromWellKnownProto(msg, wellKnownProto);
}
Any any = (Any) msg;
for (int depth = 0; depth < MAX_ANY_NESTING_DEPTH; depth++) {
String typeUrl = any.getTypeUrl();
int pos = typeUrl.lastIndexOf('/');
if (pos < 0 || pos == typeUrl.length() - 1) {
throw new IllegalArgumentException(
"Unpacking failed for message: google.protobuf.Any (malformed type URL: '"
+ typeUrl
+ "')");
}
// Same path as a message field.
Object unpacked = readMessageField(any.getValue(), typeUrl.substring(pos + 1));
if (!(unpacked instanceof Any)) {
return finalizeFieldValue(unpacked);
}
any = (Any) unpacked;
}
throw new IllegalArgumentException(
"Unpacking failed for message: google.protobuf.Any (nesting exceeds "
+ MAX_ANY_NESTING_DEPTH
+ ")");
}

private Object getDefaultValue(FieldLiteDescriptor fieldDescriptor) {
EncodingType encodingType = fieldDescriptor.getEncodingType();
switch (encodingType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.UnsignedLong;
import com.google.common.truth.Expect;
import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
Expand All @@ -33,6 +34,7 @@
import com.google.protobuf.FloatValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;
import com.google.protobuf.StringValue;
import com.google.protobuf.TextFormat;
Expand Down Expand Up @@ -148,10 +150,10 @@ private enum WellKnownProtoTestCase {
UINT64(UInt64Value.of(1L), UnsignedLong.valueOf(1L)),
;

private final MessageLite msg;
private final Message msg;
private final Object value;

WellKnownProtoTestCase(MessageLite msg, Object value) {
WellKnownProtoTestCase(Message msg, Object value) {
this.msg = msg;
this.value = value;
}
Expand All @@ -176,6 +178,87 @@ public void fromProtoMessageToCelValue_fieldMask_returnsProtoMessageLiteValue()
.isEqualTo(ImmutableList.of("foo", "bar"));
}

@Test
public void toRuntimeValue_anyWithWellKnownProto_unpacksToCelValue(
@TestParameter WellKnownProtoTestCase testCase) {
Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(Any.pack(testCase.msg));

assertThat(adaptedValue).isEqualTo(testCase.value);
}

@Test
public void toRuntimeValue_anyWithRegisteredMessage_returnsProtoMessageLiteValue() {
TestAllTypes msg = TestAllTypes.newBuilder().setSingleInt64(42L).build();

Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(Any.pack(msg));

assertThat(adaptedValue).isInstanceOf(ProtoMessageLiteValue.class);
assertThat(((ProtoMessageLiteValue) adaptedValue).value()).isEqualTo(msg);
}

@Test
public void toRuntimeValue_anyWithUnregisteredMessage_returnsRawValueOfPackedType() {
ProtoLiteCelValueConverter wellKnownOnlyConverter =
ProtoLiteCelValueConverter.newInstance(DefaultLiteDescriptorPool.newInstance());
TestAllTypes msg = TestAllTypes.newBuilder().setSingleInt64(42L).build();

Object adaptedValue = wellKnownOnlyConverter.toRuntimeValue(Any.pack(msg));

assertThat(adaptedValue).isInstanceOf(RawProtoMessageLiteValue.class);
RawProtoMessageLiteValue rawValue = (RawProtoMessageLiteValue) adaptedValue;
assertThat(rawValue.protoTypeName()).isEqualTo(PROTO3_TEST_ALL_TYPES);
assertThat(rawValue.toByteString()).isEqualTo(msg.toByteString());
}

@Test
public void toRuntimeValue_anyNestedToMaxDepth_unpacks() {
Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(nestedAny(100));

assertThat(adaptedValue).isEqualTo(42L);
}

@Test
public void toRuntimeValue_anyNestedBeyondMaxDepth_throws() {
Any any = nestedAny(101);

IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(any));

assertThat(thrown)
.hasMessageThat()
.isEqualTo("Unpacking failed for message: google.protobuf.Any (nesting exceeds 100)");
}

@Test
public void toRuntimeValue_anyWithMalformedTypeUrl_throws(
@TestParameter({"", "type.googleapis.com/"}) String typeUrl) {
Any any =
Any.newBuilder().setTypeUrl(typeUrl).setValue(Int64Value.of(1L).toByteString()).build();

IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(any));

assertThat(thrown)
.hasMessageThat()
.isEqualTo(
"Unpacking failed for message: google.protobuf.Any (malformed type URL: '"
+ typeUrl
+ "')");
}

/** Returns {@code layers} Anys nested around an {@code Int64Value} of 42. */
private static Any nestedAny(int layers) {
Any any = Any.pack(Int64Value.of(42L));
for (int i = 1; i < layers; i++) {
any = Any.pack(any);
}
return any;
}

/** Test cases for repeated_int64: 1L,2L,3L */
@SuppressWarnings("ImmutableEnumChecker") // Test only
private enum RepeatedFieldBytesTestCase {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,9 @@ private enum SelectFieldTestCase {
DOUBLE_WRAPPER("single_double_wrapper", 8.5d),
STRING_WRAPPER("single_string_wrapper", "hello"),
BYTES_WRAPPER("single_bytes_wrapper", CelByteString.of(new byte[] {0x02})),
ANY("single_any", true),
REPEATED_ANY("repeated_any", ImmutableList.of(true)),
MAP_STRING_ANY("map_string_any", ImmutableMap.of("key", true)),
REPEATED_INT64("repeated_int64", ImmutableList.of(5L, 6L)),
REPEATED_UINT64(
"repeated_uint64", ImmutableList.of(UnsignedLong.valueOf(7L), UnsignedLong.valueOf(8L))),
Expand Down Expand Up @@ -473,8 +476,6 @@ private enum SelectFieldTestCase {

@Test
public void selectField_success(@TestParameter SelectFieldTestCase testCase) {
// Converting Any isn't supported in lite yet, so it's set in a singular, a repeated and a map
// field to verify that selecting any other field doesn't attempt to convert it.
Any any = Any.pack(DynamicMessage.newBuilder(BoolValue.of(true)).build());
TestAllTypes testAllTypes =
TestAllTypes.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.UnsignedLong;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
Expand Down Expand Up @@ -393,6 +394,14 @@ public void select_populatedUnknownScalar_decodesFromWireBytes(
assertThat(result).isEqualTo(testCase.expectedPopulated);
}

@Test
public void select_populatedUnknownScalarInRootAny_decodesFromPackedWireBytes(
@TestParameter UnknownScalarTestCase testCase) throws Exception {
Object result = eval(testCase.expression, Any.pack(POPULATED_SERVER_MESSAGE));

assertThat(result).isEqualTo(testCase.expectedPopulated);
}

@Test
public void select_populatedUnknownOneofEnum_decodesFromWireBytes() throws Exception {
TestAllTypes msg = TestAllTypes.newBuilder().setSingleNestedEnum(NestedEnum.BAR).build();
Expand Down Expand Up @@ -2318,6 +2327,19 @@ public void descriptorlessRuntime_zeroRegisteredDescriptors_evaluatesFromWireByt
assertThat(result).isEqualTo(testCase.expectedResult);
}

@Test
public void descriptorlessRuntime_rootAny_evaluatesFromPackedWireBytes(
@TestParameter DescriptorlessEvaluationTestCase testCase) throws Exception {
CelLiteRuntime descriptorlessRuntime = newDescriptorlessRuntime();
CelAbstractSyntaxTree optimizedAst =
serverOptimizer.optimize(serverCompiler.compile(testCase.expression).getAst());
Program program = descriptorlessRuntime.createProgram(optimizedAst);

Object result = program.eval(ImmutableMap.of("msg", Any.pack(testCase.message)));

assertThat(result).isEqualTo(testCase.expectedResult);
}

@Test
public void partialDescriptorRuntime_missingChildDescriptor_evaluatesFromWireBytes(
@TestParameter DescriptorlessEvaluationTestCase testCase) throws Exception {
Expand Down Expand Up @@ -3002,7 +3024,7 @@ private static CelLateFunctionBindings newScoreModelLateBindings() {
}));
}

private Object eval(String expression, TestAllTypes message) throws Exception {
private Object eval(String expression, MessageLite message) throws Exception {
CelAbstractSyntaxTree ast = serverCompiler.compile(expression).getAst();
CelAbstractSyntaxTree optimizedAst = serverOptimizer.optimize(ast);
Program program = clientRuntime.createProgram(optimizedAst);
Expand Down
Loading