Skip to content

Commit 0516cfd

Browse files
l46kokcopybara-github
authored andcommitted
Unpack google.protobuf.Any in the lite runtime's value converter.
PiperOrigin-RevId: 1002605997
1 parent cb6c109 commit 0516cfd

5 files changed

Lines changed: 145 additions & 5 deletions

File tree

‎common/src/main/java/dev/cel/common/values/BUILD.bazel‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ java_library(
345345
"//protobuf:cel_lite_descriptor",
346346
"@maven//:com_google_errorprone_error_prone_annotations",
347347
"@maven//:com_google_guava_guava",
348+
"@maven//:com_google_protobuf_protobuf_java",
348349
"@maven//:org_jspecify_jspecify",
349350
"@maven_android//:com_google_protobuf_protobuf_javalite",
350351
],

‎common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
import com.google.common.collect.Iterables;
2323
import com.google.common.primitives.UnsignedLong;
2424
import com.google.errorprone.annotations.Immutable;
25+
import com.google.protobuf.Any;
2526
import com.google.protobuf.ByteString;
2627
import com.google.protobuf.CodedInputStream;
2728
import com.google.protobuf.ExtensionRegistryLite;
2829
import com.google.protobuf.Internal.EnumLite;
2930
import com.google.protobuf.InvalidProtocolBufferException;
3031
import com.google.protobuf.MessageLite;
32+
import com.google.protobuf.MessageLiteOrBuilder;
3133
import com.google.protobuf.WireFormat;
3234
import dev.cel.common.annotations.Internal;
3335
import dev.cel.common.internal.CelLiteDescriptorPool;
@@ -62,6 +64,9 @@ public final class ProtoLiteCelValueConverter extends BaseProtoCelValueConverter
6264
private static final String MAP_VALUE_FIELD_NAME = "value";
6365
private static final int MAP_KEY_FIELD_NUMBER = 1;
6466
private static final int MAP_VALUE_FIELD_NUMBER = 2;
67+
// Protobuf's default recursion limit. Nested Anys are unwrapped in a loop since each payload is
68+
// parsed afresh, outside that limit.
69+
private static final int MAX_ANY_NESTING_DEPTH = 100;
6570

6671
private final CelLiteDescriptorPool descriptorPool;
6772

@@ -218,6 +223,34 @@ private Object toRuntimeValue(MessageLite msg, MessageLiteDescriptor descriptor)
218223
return fromWellKnownProto(msg, checkNotNull(wellKnownProto));
219224
}
220225

226+
@Override
227+
protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wellKnownProto) {
228+
if (wellKnownProto != WellKnownProto.ANY_VALUE) {
229+
return super.fromWellKnownProto(msg, wellKnownProto);
230+
}
231+
Any any = (Any) msg;
232+
for (int depth = 0; depth < MAX_ANY_NESTING_DEPTH; depth++) {
233+
String typeUrl = any.getTypeUrl();
234+
int pos = typeUrl.lastIndexOf('/');
235+
if (pos < 0 || pos == typeUrl.length() - 1) {
236+
throw new IllegalArgumentException(
237+
"Unpacking failed for message: google.protobuf.Any (malformed type URL: '"
238+
+ typeUrl
239+
+ "')");
240+
}
241+
// Same path as a message field.
242+
Object unpacked = readMessageField(any.getValue(), typeUrl.substring(pos + 1));
243+
if (!(unpacked instanceof Any)) {
244+
return finalizeFieldValue(unpacked);
245+
}
246+
any = (Any) unpacked;
247+
}
248+
throw new IllegalArgumentException(
249+
"Unpacking failed for message: google.protobuf.Any (nesting exceeds "
250+
+ MAX_ANY_NESTING_DEPTH
251+
+ ")");
252+
}
253+
221254
private Object getDefaultValue(FieldLiteDescriptor fieldDescriptor) {
222255
EncodingType encodingType = fieldDescriptor.getEncodingType();
223256
switch (encodingType) {

‎common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java‎

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.common.collect.ImmutableSet;
2323
import com.google.common.primitives.UnsignedLong;
2424
import com.google.common.truth.Expect;
25+
import com.google.protobuf.Any;
2526
import com.google.protobuf.BoolValue;
2627
import com.google.protobuf.ByteString;
2728
import com.google.protobuf.BytesValue;
@@ -33,6 +34,7 @@
3334
import com.google.protobuf.FloatValue;
3435
import com.google.protobuf.Int32Value;
3536
import com.google.protobuf.Int64Value;
37+
import com.google.protobuf.Message;
3638
import com.google.protobuf.MessageLite;
3739
import com.google.protobuf.StringValue;
3840
import com.google.protobuf.TextFormat;
@@ -148,10 +150,10 @@ private enum WellKnownProtoTestCase {
148150
UINT64(UInt64Value.of(1L), UnsignedLong.valueOf(1L)),
149151
;
150152

151-
private final MessageLite msg;
153+
private final Message msg;
152154
private final Object value;
153155

154-
WellKnownProtoTestCase(MessageLite msg, Object value) {
156+
WellKnownProtoTestCase(Message msg, Object value) {
155157
this.msg = msg;
156158
this.value = value;
157159
}
@@ -176,6 +178,87 @@ public void fromProtoMessageToCelValue_fieldMask_returnsProtoMessageLiteValue()
176178
.isEqualTo(ImmutableList.of("foo", "bar"));
177179
}
178180

181+
@Test
182+
public void toRuntimeValue_anyWithWellKnownProto_unpacksToCelValue(
183+
@TestParameter WellKnownProtoTestCase testCase) {
184+
Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(Any.pack(testCase.msg));
185+
186+
assertThat(adaptedValue).isEqualTo(testCase.value);
187+
}
188+
189+
@Test
190+
public void toRuntimeValue_anyWithRegisteredMessage_returnsProtoMessageLiteValue() {
191+
TestAllTypes msg = TestAllTypes.newBuilder().setSingleInt64(42L).build();
192+
193+
Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(Any.pack(msg));
194+
195+
assertThat(adaptedValue).isInstanceOf(ProtoMessageLiteValue.class);
196+
assertThat(((ProtoMessageLiteValue) adaptedValue).value()).isEqualTo(msg);
197+
}
198+
199+
@Test
200+
public void toRuntimeValue_anyWithUnregisteredMessage_returnsRawValueOfPackedType() {
201+
ProtoLiteCelValueConverter wellKnownOnlyConverter =
202+
ProtoLiteCelValueConverter.newInstance(DefaultLiteDescriptorPool.newInstance());
203+
TestAllTypes msg = TestAllTypes.newBuilder().setSingleInt64(42L).build();
204+
205+
Object adaptedValue = wellKnownOnlyConverter.toRuntimeValue(Any.pack(msg));
206+
207+
assertThat(adaptedValue).isInstanceOf(RawProtoMessageLiteValue.class);
208+
RawProtoMessageLiteValue rawValue = (RawProtoMessageLiteValue) adaptedValue;
209+
assertThat(rawValue.protoTypeName()).isEqualTo(PROTO3_TEST_ALL_TYPES);
210+
assertThat(rawValue.toByteString()).isEqualTo(msg.toByteString());
211+
}
212+
213+
@Test
214+
public void toRuntimeValue_anyNestedToMaxDepth_unpacks() {
215+
Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(nestedAny(100));
216+
217+
assertThat(adaptedValue).isEqualTo(42L);
218+
}
219+
220+
@Test
221+
public void toRuntimeValue_anyNestedBeyondMaxDepth_throws() {
222+
Any any = nestedAny(101);
223+
224+
IllegalArgumentException thrown =
225+
assertThrows(
226+
IllegalArgumentException.class,
227+
() -> PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(any));
228+
229+
assertThat(thrown)
230+
.hasMessageThat()
231+
.isEqualTo("Unpacking failed for message: google.protobuf.Any (nesting exceeds 100)");
232+
}
233+
234+
@Test
235+
public void toRuntimeValue_anyWithMalformedTypeUrl_throws(
236+
@TestParameter({"", "type.googleapis.com/"}) String typeUrl) {
237+
Any any =
238+
Any.newBuilder().setTypeUrl(typeUrl).setValue(Int64Value.of(1L).toByteString()).build();
239+
240+
IllegalArgumentException thrown =
241+
assertThrows(
242+
IllegalArgumentException.class,
243+
() -> PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(any));
244+
245+
assertThat(thrown)
246+
.hasMessageThat()
247+
.isEqualTo(
248+
"Unpacking failed for message: google.protobuf.Any (malformed type URL: '"
249+
+ typeUrl
250+
+ "')");
251+
}
252+
253+
/** Returns {@code layers} Anys nested around an {@code Int64Value} of 42. */
254+
private static Any nestedAny(int layers) {
255+
Any any = Any.pack(Int64Value.of(42L));
256+
for (int i = 1; i < layers; i++) {
257+
any = Any.pack(any);
258+
}
259+
return any;
260+
}
261+
179262
/** Test cases for repeated_int64: 1L,2L,3L */
180263
@SuppressWarnings("ImmutableEnumChecker") // Test only
181264
private enum RepeatedFieldBytesTestCase {

‎common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ private enum SelectFieldTestCase {
426426
DOUBLE_WRAPPER("single_double_wrapper", 8.5d),
427427
STRING_WRAPPER("single_string_wrapper", "hello"),
428428
BYTES_WRAPPER("single_bytes_wrapper", CelByteString.of(new byte[] {0x02})),
429+
ANY("single_any", true),
430+
REPEATED_ANY("repeated_any", ImmutableList.of(true)),
431+
MAP_STRING_ANY("map_string_any", ImmutableMap.of("key", true)),
429432
REPEATED_INT64("repeated_int64", ImmutableList.of(5L, 6L)),
430433
REPEATED_UINT64(
431434
"repeated_uint64", ImmutableList.of(UnsignedLong.valueOf(7L), UnsignedLong.valueOf(8L))),
@@ -473,8 +476,6 @@ private enum SelectFieldTestCase {
473476

474477
@Test
475478
public void selectField_success(@TestParameter SelectFieldTestCase testCase) {
476-
// Converting Any isn't supported in lite yet, so it's set in a singular, a repeated and a map
477-
// field to verify that selecting any other field doesn't attempt to convert it.
478479
Any any = Any.pack(DynamicMessage.newBuilder(BoolValue.of(true)).build());
479480
TestAllTypes testAllTypes =
480481
TestAllTypes.newBuilder()

‎runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeVersionSkewTest.java‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.google.common.collect.ImmutableMap;
2525
import com.google.common.collect.ImmutableSet;
2626
import com.google.common.primitives.UnsignedLong;
27+
import com.google.protobuf.Any;
2728
import com.google.protobuf.ByteString;
2829
import com.google.protobuf.CodedOutputStream;
2930
import com.google.protobuf.DescriptorProtos.DescriptorProto;
@@ -393,6 +394,14 @@ public void select_populatedUnknownScalar_decodesFromWireBytes(
393394
assertThat(result).isEqualTo(testCase.expectedPopulated);
394395
}
395396

397+
@Test
398+
public void select_populatedUnknownScalarInRootAny_decodesFromPackedWireBytes(
399+
@TestParameter UnknownScalarTestCase testCase) throws Exception {
400+
Object result = eval(testCase.expression, Any.pack(POPULATED_SERVER_MESSAGE));
401+
402+
assertThat(result).isEqualTo(testCase.expectedPopulated);
403+
}
404+
396405
@Test
397406
public void select_populatedUnknownOneofEnum_decodesFromWireBytes() throws Exception {
398407
TestAllTypes msg = TestAllTypes.newBuilder().setSingleNestedEnum(NestedEnum.BAR).build();
@@ -2318,6 +2327,19 @@ public void descriptorlessRuntime_zeroRegisteredDescriptors_evaluatesFromWireByt
23182327
assertThat(result).isEqualTo(testCase.expectedResult);
23192328
}
23202329

2330+
@Test
2331+
public void descriptorlessRuntime_rootAny_evaluatesFromPackedWireBytes(
2332+
@TestParameter DescriptorlessEvaluationTestCase testCase) throws Exception {
2333+
CelLiteRuntime descriptorlessRuntime = newDescriptorlessRuntime();
2334+
CelAbstractSyntaxTree optimizedAst =
2335+
serverOptimizer.optimize(serverCompiler.compile(testCase.expression).getAst());
2336+
Program program = descriptorlessRuntime.createProgram(optimizedAst);
2337+
2338+
Object result = program.eval(ImmutableMap.of("msg", Any.pack(testCase.message)));
2339+
2340+
assertThat(result).isEqualTo(testCase.expectedResult);
2341+
}
2342+
23212343
@Test
23222344
public void partialDescriptorRuntime_missingChildDescriptor_evaluatesFromWireBytes(
23232345
@TestParameter DescriptorlessEvaluationTestCase testCase) throws Exception {
@@ -3002,7 +3024,7 @@ private static CelLateFunctionBindings newScoreModelLateBindings() {
30023024
}));
30033025
}
30043026

3005-
private Object eval(String expression, TestAllTypes message) throws Exception {
3027+
private Object eval(String expression, MessageLite message) throws Exception {
30063028
CelAbstractSyntaxTree ast = serverCompiler.compile(expression).getAst();
30073029
CelAbstractSyntaxTree optimizedAst = serverOptimizer.optimize(ast);
30083030
Program program = clientRuntime.createProgram(optimizedAst);

0 commit comments

Comments
 (0)