Skip to content

Commit a07c04b

Browse files
l46kokcopybara-github
authored andcommitted
Reject Java nulls uniformly in map and list adaptation
PiperOrigin-RevId: 988453145
1 parent 9aeeccd commit a07c04b

5 files changed

Lines changed: 276 additions & 16 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ java_library(
167167
":preadapted_list",
168168
"//:auto_value",
169169
"//common/annotations",
170+
"//common/exceptions:invalid_argument",
170171
"//common/types",
171172
"//common/types:type_providers",
172173
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -218,6 +219,7 @@ cel_android_library(
218219
":preadapted_list_android",
219220
"//:auto_value",
220221
"//common/annotations",
222+
"//common/exceptions:invalid_argument",
221223
"//common/types:type_providers_android",
222224
"//common/types:types_android",
223225
"@maven//:com_google_errorprone_error_prone_annotations",

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

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@
1717
import com.google.common.base.Preconditions;
1818
import com.google.common.collect.ImmutableList;
1919
import com.google.common.collect.ImmutableMap;
20+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2021
import com.google.errorprone.annotations.Immutable;
2122
import dev.cel.common.annotations.Internal;
23+
import dev.cel.common.exceptions.CelInvalidArgumentException;
2224
import java.util.Collection;
2325
import java.util.Iterator;
2426
import java.util.List;
2527
import java.util.Map;
2628
import java.util.Optional;
2729
import java.util.RandomAccess;
2830
import java.util.function.Function;
31+
import org.jspecify.annotations.Nullable;
2932

3033
/**
3134
* {@code CelValueConverter} handles bidirectional conversion between native Java objects to {@link
@@ -74,7 +77,7 @@ protected Object mapContainer(Object value, Function<Object, Object> mapper) {
7477
if (value instanceof List && value instanceof RandomAccess) {
7578
List<Object> list = (List<Object>) value;
7679
for (int i = 0; i < list.size(); i++) {
77-
Object element = list.get(i);
80+
Object element = checkListElement(list.get(i), i);
7881
Object mapped = mapper.apply(element);
7982

8083
if (mapped != element) {
@@ -85,7 +88,7 @@ protected Object mapContainer(Object value, Function<Object, Object> mapper) {
8588
}
8689
builder.add(mapped);
8790
for (int j = i + 1; j < list.size(); j++) {
88-
builder.add(mapper.apply(list.get(j)));
91+
builder.add(mapper.apply(checkListElement(list.get(j), j)));
8992
}
9093
return builder.build();
9194
}
@@ -100,8 +103,9 @@ protected Object mapContainer(Object value, Function<Object, Object> mapper) {
100103
Collection<Object> collection = (Collection<Object>) value;
101104
ImmutableList.Builder<Object> builder =
102105
ImmutableList.builderWithExpectedSize(collection.size());
106+
int index = 0;
103107
for (Object element : collection) {
104-
builder.add(mapper.apply(element));
108+
builder.add(mapper.apply(checkListElement(element, index++)));
105109
}
106110
return builder.build();
107111
}
@@ -112,6 +116,7 @@ protected Object mapContainer(Object value, Function<Object, Object> mapper) {
112116

113117
while (iterator.hasNext()) {
114118
Map.Entry<Object, Object> entry = iterator.next();
119+
checkMapEntry(entry);
115120
Object mappedKey = mapper.apply(entry.getKey());
116121
Object mappedValue = mapper.apply(entry.getValue());
117122

@@ -128,6 +133,7 @@ protected Object mapContainer(Object value, Function<Object, Object> mapper) {
128133
builder.put(mappedKey, mappedValue);
129134
while (iterator.hasNext()) {
130135
Map.Entry<Object, Object> nextEntry = iterator.next();
136+
checkMapEntry(nextEntry);
131137
builder.put(mapper.apply(nextEntry.getKey()), mapper.apply(nextEntry.getValue()));
132138
}
133139
return builder.buildOrThrow();
@@ -162,6 +168,59 @@ public Object toRuntimeValue(Object value) {
162168
return normalizePrimitive(value);
163169
}
164170

171+
/**
172+
* Adapts {@code value} for an intermediate field selection hop.
173+
*
174+
* <p>{@link Map} instances are returned as-is to avoid O(N) whole-map normalization per hop; the
175+
* accessed entry is validated on lookup via {@link #findMapValue} or {@link #containsMapKey}.
176+
* Callers materializing a final evaluation result must use {@link #toRuntimeValue} instead.
177+
*/
178+
public final Object toTraversalTarget(Object value) {
179+
if (value instanceof Map) {
180+
return value;
181+
}
182+
183+
return toRuntimeValue(value);
184+
}
185+
186+
/**
187+
* Returns the unadapted value bound to {@code key} in {@code map}, or {@link Optional#empty()} if
188+
* absent.
189+
*
190+
* @throws CelInvalidArgumentException if {@code key} is bound to {@code null}.
191+
*/
192+
public static Optional<Object> findMapValue(Map<?, ?> map, Object key) {
193+
Object value = map.get(key);
194+
if (value != null) {
195+
return Optional.of(value);
196+
}
197+
198+
if (map.containsKey(key)) {
199+
throw new CelInvalidArgumentException(
200+
String.format("Map value cannot be null for key: %s", key));
201+
}
202+
203+
return Optional.empty();
204+
}
205+
206+
/**
207+
* Returns whether {@code key} is present in {@code map}.
208+
*
209+
* @throws CelInvalidArgumentException if {@code key} is bound to {@code null}.
210+
*/
211+
public static boolean containsMapKey(Map<?, ?> map, Object key) {
212+
if (map.get(key) != null) {
213+
return true;
214+
}
215+
216+
if (map.containsKey(key)) {
217+
throw new CelInvalidArgumentException(
218+
String.format("Map value cannot be null for key: %s", key));
219+
}
220+
221+
return false;
222+
}
223+
165224
protected Object normalizePrimitive(Object value) {
166225
Preconditions.checkNotNull(value);
167226

@@ -196,6 +255,28 @@ private Object unwrap(CelValue celValue) {
196255
return celValue.value();
197256
}
198257

258+
private static void checkMapEntry(Map.Entry<?, ?> entry) {
259+
Object key = entry.getKey();
260+
if (key == null) {
261+
throw new CelInvalidArgumentException("Map key cannot be null.");
262+
}
263+
264+
if (entry.getValue() == null) {
265+
throw new CelInvalidArgumentException(
266+
String.format("Map value cannot be null for key: %s", key));
267+
}
268+
}
269+
270+
@CanIgnoreReturnValue
271+
private static Object checkListElement(@Nullable Object element, int index) {
272+
if (element == null) {
273+
throw new CelInvalidArgumentException(
274+
String.format("List element cannot be null at index: %d", index));
275+
}
276+
277+
return element;
278+
}
279+
199280
protected CelValueConverter() {
200281
this.maybeUnwrapFunction = this::maybeUnwrap;
201282
this.toRuntimeValueFunction = this::toRuntimeValue;

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

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,13 @@ public Set<Entry<Object, Object>> entrySet() {
105105

106106
@Override
107107
public Object select(Object field) {
108-
Object val = internalMap.get(field);
109-
if (val != null) {
110-
return val;
111-
}
112-
if (!internalMap.containsKey(field)) {
113-
throw CelAttributeNotFoundException.forMissingMapKey(field.toString());
114-
}
115-
throw CelAttributeNotFoundException.of(
116-
String.format("Map value cannot be null for key: %s", field));
108+
return CelValueConverter.findMapValue(internalMap, field)
109+
.orElseThrow(() -> CelAttributeNotFoundException.forMissingMapKey(field.toString()));
117110
}
118111

119112
@Override
120113
public Optional<?> find(Object field) {
121-
if (internalMap.containsKey(field)) {
122-
return Optional.ofNullable(internalMap.get(field));
123-
}
124-
return Optional.empty();
114+
return CelValueConverter.findMapValue(internalMap, field);
125115
}
126116

127117
@Override

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ java_library(
1414
"//bundle:cel",
1515
"//common:cel_ast",
1616
"//common:cel_descriptor_util",
17+
"//common:error_codes",
1718
"//common:options",
1819
"//common/exceptions:attribute_not_found",
20+
"//common/exceptions:invalid_argument",
1921
"//common/internal:cel_descriptor_pools",
2022
"//common/internal:cel_lite_descriptor_pool",
2123
"//common/internal:default_lite_descriptor_pool",

0 commit comments

Comments
 (0)