From e80c3ca818459614b065552ffef36bfd2696775a Mon Sep 17 00:00:00 2001 From: Sean Huh Date: Mon, 28 Sep 2026 15:47:52 -0700 Subject: [PATCH] Harden cel.@attribute type identifiers and leaf validation across producers. PiperOrigin-RevId: 989888489 --- .../optimizers/SelectOptimizerTest.java | 36 +++++++++++++++++++ .../java/dev/cel/runtime/planner/BUILD.bazel | 2 ++ .../planner/OptimizedSelectPlanner.java | 19 ++++++++++ .../planner/OptimizedSelectPlannerTest.java | 32 +++++++++++++++++ 4 files changed, 89 insertions(+) diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java index 22ae65ca1..c3aa7d37e 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SelectOptimizerTest.java @@ -26,9 +26,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.primitives.UnsignedLong; +import com.google.protobuf.Any; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; +import com.google.protobuf.Int64Value; +import com.google.protobuf.Struct; import com.google.protobuf.TextFormat; +import com.google.protobuf.Value; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.bundle.Cel; @@ -592,6 +596,38 @@ private enum NativeSelectEvaluationTestCase { "msg.single_timestamp", ImmutableMap.of("msg", TestAllTypes.getDefaultInstance()), Instant.EPOCH), + PROTO3_MAP_OF_ANY_UNPACKS_VALUE( + "msg.map_string_any['k']", + ImmutableMap.of( + "msg", + TestAllTypes.newBuilder().putMapStringAny("k", Any.pack(Int64Value.of(5))).build()), + 5L), + PROTO3_MAP_OF_WRAPPER_UNWRAPS_MAP_VALUES( + "msg.map_string_int64_wrapper", + ImmutableMap.of( + "msg", + TestAllTypes.newBuilder().putMapStringInt64Wrapper("k", Int64Value.of(5)).build()), + ImmutableMap.of("k", 5L)), + PROTO3_MAP_OF_VALUE_CONVERTS_TO_JSON( + "msg.map_string_value['k']", + ImmutableMap.of( + "msg", + TestAllTypes.newBuilder() + .putMapStringValue("k", Value.newBuilder().setNumberValue(1.5).build()) + .build()), + 1.5), + PROTO3_MAP_OF_STRUCT_CONVERTS_TO_MAP( + "msg.map_string_struct['k'].a", + ImmutableMap.of( + "msg", + TestAllTypes.newBuilder() + .putMapStringStruct( + "k", + Struct.newBuilder() + .putFields("a", Value.newBuilder().setNumberValue(1.5).build()) + .build()) + .build()), + 1.5), DEEPLY_NESTED_PROTO2_MESSAGE_POPULATED( "nested_msg.child.payload.single_int64", ImmutableMap.of("nested_msg", newNestedTestAllTypes(999L)), diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index dac5cea2b..a8c5621db 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -533,6 +533,7 @@ java_library( ":planner_helpers", "//common/ast", "//common/types", + "//common/types:cel_types", "//common/types:type_providers", "//common/values", "//common/values:cel_byte_string", @@ -1178,6 +1179,7 @@ cel_android_library( ":planned_interpretable_android", ":planner_helpers_android", "//common/ast:ast_android", + "//common/types:cel_types_android", "//common/types:type_providers_android", "//common/types:types_android", "//common/values:cel_byte_string", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/OptimizedSelectPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/OptimizedSelectPlanner.java index f2606b560..9459d86ef 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/OptimizedSelectPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/OptimizedSelectPlanner.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; import dev.cel.common.ast.CelConstant; @@ -26,6 +27,7 @@ import dev.cel.common.ast.CelExpr.CelCall; import dev.cel.common.ast.CelExpr.ExprKind.Kind; import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypes; import dev.cel.common.types.SimpleType; import dev.cel.common.values.CelByteString; import dev.cel.common.values.CelValueConverter; @@ -56,6 +58,18 @@ final class OptimizedSelectPlanner { private static final String DURATION_TYPE_IDENT = SimpleType.DURATION.name(); private static final String TIMESTAMP_TYPE_IDENT = SimpleType.TIMESTAMP.name(); + /** + * Well-known message types whose CEL semantics (Any unpacking, JSON value conversion) are not + * implemented by the optimized traversal. Wrapper types are rejected via {@link + * CelTypes#isWrapperType}. + */ + private static final ImmutableSet UNSUPPORTED_WELL_KNOWN_TYPE_IDENTS = + ImmutableSet.of( + CelTypes.ANY_MESSAGE, + CelTypes.STRUCT_MESSAGE, + CelTypes.VALUE_MESSAGE, + CelTypes.LIST_VALUE_MESSAGE); + private final AttributeFactory attributeFactory; private final CelValueConverter celValueConverter; @@ -228,6 +242,11 @@ private static void validateLeafTypeIdent( !ScalarType.isScalarTypeIdent(typeIdent), "Leaf MESSAGE type code (11) is incompatible with scalar typeIdent '%s'", typeIdent); + checkArgument( + !CelTypes.isWrapperType(typeIdent) + && !UNSUPPORTED_WELL_KNOWN_TYPE_IDENTS.contains(typeIdent), + "Leaf well-known type '%s' is not supported by the select-optimized runtime", + typeIdent); if (typeIdent.equals(DURATION_TYPE_IDENT)) { checkArgument( Objects.equals(defaultValue, Duration.ZERO), diff --git a/runtime/src/test/java/dev/cel/runtime/planner/OptimizedSelectPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/OptimizedSelectPlannerTest.java index 74a975d9c..fc912cef4 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/OptimizedSelectPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/OptimizedSelectPlannerTest.java @@ -535,6 +535,38 @@ public void plan_invalidAst_messageTypeCodeWithScalarTypeIdent_throwsEvaluationE .contains("Leaf MESSAGE type code (11) is incompatible with scalar typeIdent 'int'"); } + @Test + public void plan_invalidAst_unsupportedWellKnownType_throwsEvaluationException() { + CelAbstractSyntaxTree ast = + CelAbstractSyntaxTree.newParsedAst( + CelExpr.ofCall( + 1L, + OptimizedSelectPlanner.CEL_ATTRIBUTE_FUNCTION_NAME, + ImmutableList.of( + CelExpr.ofIdent(2L, "msg"), + CelExpr.ofList( + 3L, + ImmutableList.of( + CelExpr.ofList( + 4L, + ImmutableList.of( + CelExpr.ofConstant(5L, CelConstant.ofValue(105L)), + CelExpr.ofConstant( + 6L, CelConstant.ofValue("single_int64_wrapper")), + CelExpr.ofConstant(7L, CelConstant.ofValue(11L))), + ImmutableList.of())), + ImmutableList.of()), + CelExpr.ofIdent(8L, "google.protobuf.Int64Value"))), + CelSource.newBuilder().build()); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> PLANNER.plan(ast)); + + assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(e) + .hasMessageThat() + .contains("Leaf well-known type 'google.protobuf.Int64Value' is not supported"); + } + private static CelAbstractSyntaxTree optimizeSelectAst(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL.compile(expression).getAst(); CelAbstractSyntaxTree optimizedAst = SELECT_OPTIMIZER.optimize(ast);