Skip to content

Commit ef041ed

Browse files
l46kokcopybara-github
authored andcommitted
Fix null assignment to fields
PiperOrigin-RevId: 875509705
1 parent 9e1b5ee commit ef041ed

File tree

8 files changed

+103
-33
lines changed

8 files changed

+103
-33
lines changed

common/src/main/java/dev/cel/common/internal/ProtoAdapter.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,29 @@ public Optional<Object> adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec
204204
@SuppressWarnings({"unchecked", "rawtypes"})
205205
public Optional<Object> adaptValueToFieldType(
206206
FieldDescriptor fieldDescriptor, Object fieldValue) {
207-
if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) {
208-
return Optional.empty();
207+
if (fieldValue instanceof NullValue) {
208+
// `null` assignment to fields indicate that the field would not be set
209+
// in a protobuf message (e.g: Message{msg_field: null} -> Message{})
210+
//
211+
// We explicitly check below for invalid null assignments, such as repeated
212+
// or map fields. (e.g: Message{repeated_field: null} -> Error)
213+
if (fieldDescriptor.isMapField()
214+
|| fieldDescriptor.isRepeated()
215+
|| fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE
216+
|| WellKnownProto.JSON_STRUCT_VALUE
217+
.typeName()
218+
.equals(fieldDescriptor.getMessageType().getFullName())
219+
|| WellKnownProto.JSON_LIST_VALUE
220+
.typeName()
221+
.equals(fieldDescriptor.getMessageType().getFullName())) {
222+
throw new IllegalArgumentException("Unsupported field type");
223+
}
224+
225+
String typeFullName = fieldDescriptor.getMessageType().getFullName();
226+
if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName)
227+
&& !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) {
228+
return Optional.empty();
229+
}
209230
}
210231
if (fieldDescriptor.isMapField()) {
211232
Descriptor entryDescriptor = fieldDescriptor.getMessageType();
@@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) {
370391
return protoType.getFullName();
371392
}
372393

373-
private static boolean isWrapperType(FieldDescriptor fieldDescriptor) {
374-
if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
375-
return false;
376-
}
377-
String fieldTypeName = fieldDescriptor.getMessageType().getFullName();
378-
return WellKnownProto.isWrapperType(fieldTypeName);
379-
}
380-
381394
private static int intCheckedCast(long value) {
382395
try {
383396
return Ints.checkedCast(value);

common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
6767
try {
6868
unpackedMessage = dynamicProto.unpack((Any) message);
6969
} catch (InvalidProtocolBufferException e) {
70-
throw new IllegalStateException(
70+
throw new IllegalArgumentException(
7171
"Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e);
7272
}
7373
return toRuntimeValue(unpackedMessage);

common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,7 @@ public static List<Object[]> data() {
150150
@Test
151151
public void adaptValueToProto_bidirectionalConversion() {
152152
DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE);
153-
ProtoAdapter protoAdapter =
154-
new ProtoAdapter(
155-
dynamicProto,
156-
CelOptions.current().build());
153+
ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build());
157154
assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName()))
158155
.isEqualTo(proto);
159156
assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value);
@@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() {
181178

182179
@RunWith(JUnit4.class)
183180
public static class AsymmetricConversionTest {
181+
182+
@Test
183+
public void unpackAny_celNullValue() throws Exception {
184+
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);
185+
Any any =
186+
(Any)
187+
protoAdapter.adaptValueToProto(
188+
dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any");
189+
Object unpacked = protoAdapter.adaptProtoToValue(any);
190+
assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE);
191+
}
192+
184193
@Test
185194
public void adaptValueToProto_asymmetricFloatConversion() {
186195
ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT);

conformance/src/test/java/dev/cel/conformance/BUILD.bazel

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [
124124
"string_ext/format",
125125
"string_ext/format_errors",
126126

127-
# TODO: Fix null assignment to a field
128-
"proto2/set_null/single_message",
129-
"proto2/set_null/single_duration",
130-
"proto2/set_null/single_timestamp",
131-
"proto3/set_null/single_message",
132-
"proto3/set_null/single_duration",
133-
"proto3/set_null/single_timestamp",
134-
135127
# Future features for CEL 1.0
136128
# TODO: Strong typing support for enums, specified but not implemented.
137129
"enums/strong_proto2",
@@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [
162154
"string_ext/format_errors",
163155

164156
# TODO: Check behavior for go/cpp
165-
"basic/functions/unbound",
166157
"basic/functions/unbound_is_runtime_error",
167158

168159
# TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms.
@@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [
177168
# Skip until fixed.
178169
"parse/receiver_function_names",
179170

180-
# TODO: Fix null assignment to a field
181-
"proto2/set_null/single_message",
182-
"proto2/set_null/single_duration",
183-
"proto2/set_null/single_timestamp",
184-
"proto3/set_null/single_message",
185-
"proto3/set_null/single_duration",
186-
"proto3/set_null/single_timestamp",
187-
188171
# Type inference edgecases around null(able) assignability.
189172
# These type check, but resolve to a different type.
190173
# list(int), want list(wrapper(int))

conformance/src/test/java/dev/cel/conformance/ConformanceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,10 @@ public void evaluate() throws Throwable {
210210
}
211211

212212
CelRuntime runtime = getRuntime(test, usePlanner);
213-
Program program = runtime.createProgram(response.getAst());
214213
ExprValue result = null;
215214
CelEvaluationException error = null;
216215
try {
216+
Program program = runtime.createProgram(response.getAst());
217217
result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType());
218218
} catch (CelEvaluationException e) {
219219
error = e;

runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception {
5454

5555
// All the tests below rely on message creation with fields populated. They are excluded for time
5656
// being until this support is added.
57+
@Override
58+
public void nullAssignability() throws Exception {
59+
skipBaselineVerification();
60+
}
61+
5762
@Override
5863
public void wrappers() throws Exception {
5964
skipBaselineVerification();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null
2+
=====>
3+
bindings: {}
4+
result: true
5+
6+
Source: TestAllTypes{}.single_int64_wrapper == null
7+
=====>
8+
bindings: {}
9+
result: true
10+
11+
Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)
12+
=====>
13+
bindings: {}
14+
result: false
15+
16+
Source: TestAllTypes{single_value: null}.single_value == null
17+
=====>
18+
bindings: {}
19+
result: true
20+
21+
Source: has(TestAllTypes{single_value: null}.single_value)
22+
=====>
23+
bindings: {}
24+
result: true
25+
26+
Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)
27+
=====>
28+
bindings: {}
29+
result: true
30+
31+
Source: has(TestAllTypes{single_timestamp: null}.single_timestamp)
32+
=====>
33+
bindings: {}
34+
result: false
35+

testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,31 @@ public void wrappers() throws Exception {
21222122
runTest();
21232123
}
21242124

2125+
@Test
2126+
public void nullAssignability() throws Exception {
2127+
setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()));
2128+
source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null";
2129+
runTest();
2130+
2131+
source = "TestAllTypes{}.single_int64_wrapper == null";
2132+
runTest();
2133+
2134+
source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)";
2135+
runTest();
2136+
2137+
source = "TestAllTypes{single_value: null}.single_value == null";
2138+
runTest();
2139+
2140+
source = "has(TestAllTypes{single_value: null}.single_value)";
2141+
runTest();
2142+
2143+
source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)";
2144+
runTest();
2145+
2146+
source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)";
2147+
runTest();
2148+
}
2149+
21252150
@Test
21262151
public void longComprehension() {
21272152
ImmutableList<Long> l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList());

0 commit comments

Comments
 (0)