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 checker/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ cc_test(
":type_checker_builder_factory",
":validation_result",
"//checker/internal:test_ast_helpers",
"//common:ast",
"//common:decl",
"//common:type",
"//internal:status_macros",
Expand Down
8 changes: 8 additions & 0 deletions checker/checker_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ struct CheckerOptions {
// Temporary flag to allow rolling out the change. No functional changes to
// evaluation behavior in either mode.
bool enable_function_name_in_reference = true;

// If true, the checker will use the proto json field names for protobuf
// messages. Unlike protojson parsers, it will not accept the standard proto
// field names as valid json field names.
//
// Note: The checked AST will contain the json field names and an extension
// tag, but will require runtime support for resolving the json field names.
bool use_json_field_names = false;
};

} // namespace cel
Expand Down
36 changes: 30 additions & 6 deletions checker/internal/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ cc_library(
hdrs = ["test_ast_helpers.h"],
deps = [
"//common:ast",
"//extensions/protobuf:ast_converters",
"//internal:status_macros",
"//parser",
"//parser:options",
"//parser:parser_interface",
"@com_google_absl//absl/log:absl_check",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:string_view",
],
Expand Down Expand Up @@ -64,6 +65,7 @@ cc_library(
srcs = ["type_check_env.cc"],
hdrs = ["type_check_env.h"],
deps = [
":descriptor_pool_type_introspector",
"//common:constant",
"//common:decl",
"//common:type",
Expand Down Expand Up @@ -118,6 +120,7 @@ cc_library(
"type_checker_impl.h",
],
deps = [
":descriptor_pool_type_introspector",
":format_type_name",
":namespace_generator",
":type_check_env",
Expand Down Expand Up @@ -261,14 +264,35 @@ cc_library(
],
)

cc_library(
name = "descriptor_pool_type_introspector",
srcs = ["descriptor_pool_type_introspector.cc"],
hdrs = ["descriptor_pool_type_introspector.h"],
deps = [
"//common:type",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/base:nullability",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/log:absl_check",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:string_view",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:span",
"@com_google_protobuf//:protobuf",
],
)

cc_test(
name = "format_type_name_test",
srcs = ["format_type_name_test.cc"],
name = "descriptor_pool_type_introspector_test",
srcs = ["descriptor_pool_type_introspector_test.cc"],
deps = [
":format_type_name",
":descriptor_pool_type_introspector",
"//common:type",
"//internal:testing",
"@com_google_cel_spec//proto/cel/expr/conformance/proto2:test_all_types_cc_proto",
"@com_google_protobuf//:protobuf",
"//internal:testing_descriptor_pool",
"@com_google_absl//absl/status:status_matchers",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/types:optional",
],
)
245 changes: 245 additions & 0 deletions checker/internal/descriptor_pool_type_introspector.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "checker/internal/descriptor_pool_type_introspector.h"

#include <memory>
#include <utility>
#include <vector>

#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "absl/log/absl_check.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
#include "common/type.h"
#include "common/type_introspector.h"
#include "google/protobuf/descriptor.h"

namespace cel::checker_internal {
namespace {

// Standard implementation for field lookups.
// Avoids building a FieldTable and just checks the DescriptorPool directly.
absl::StatusOr<absl::optional<StructTypeField>>
FindStructTypeFieldByNameDirectly(
const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
absl::string_view type, absl::string_view name) {
const google::protobuf::Descriptor* absl_nullable descriptor =
descriptor_pool->FindMessageTypeByName(type);
if (descriptor == nullptr) {
return absl::nullopt;
}
const google::protobuf::FieldDescriptor* absl_nullable field =
descriptor->FindFieldByName(name);
if (field != nullptr) {
return StructTypeField(MessageTypeField(field));
}

field = descriptor_pool->FindExtensionByPrintableName(descriptor, name);
if (field != nullptr) {
return StructTypeField(MessageTypeField(field));
}
return absl::nullopt;
}

// Standard implementation for listing fields.
// Avoids building a FieldTable and just checks the DescriptorPool directly.
absl::StatusOr<
absl::optional<std::vector<TypeIntrospector::StructTypeFieldListing>>>
ListStructTypeFieldsDirectly(
const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
absl::string_view type) {
const google::protobuf::Descriptor* absl_nullable descriptor =
descriptor_pool->FindMessageTypeByName(type);
if (descriptor == nullptr) {
return absl::nullopt;
}

std::vector<const google::protobuf::FieldDescriptor*> extensions;
descriptor_pool->FindAllExtensions(descriptor, &extensions);

std::vector<TypeIntrospector::StructTypeFieldListing> fields;
fields.reserve(descriptor->field_count() + extensions.size());

for (int i = 0; i < descriptor->field_count(); ++i) {
const google::protobuf::FieldDescriptor* field = descriptor->field(i);
fields.push_back({field->name(), StructTypeField(MessageTypeField(field))});
}

return fields;
}

} // namespace

using Field = DescriptorPoolTypeIntrospector::Field;

absl::StatusOr<absl::optional<Type>>
DescriptorPoolTypeIntrospector::FindTypeImpl(absl::string_view name) const {
const google::protobuf::Descriptor* absl_nullable descriptor =
descriptor_pool_->FindMessageTypeByName(name);
if (descriptor != nullptr) {
return Type::Message(descriptor);
}
const google::protobuf::EnumDescriptor* absl_nullable enum_descriptor =
descriptor_pool_->FindEnumTypeByName(name);
if (enum_descriptor != nullptr) {
return Type::Enum(enum_descriptor);
}
return absl::nullopt;
}

absl::StatusOr<absl::optional<TypeIntrospector::EnumConstant>>
DescriptorPoolTypeIntrospector::FindEnumConstantImpl(
absl::string_view type, absl::string_view value) const {
const google::protobuf::EnumDescriptor* absl_nullable enum_descriptor =
descriptor_pool_->FindEnumTypeByName(type);
if (enum_descriptor != nullptr) {
const google::protobuf::EnumValueDescriptor* absl_nullable enum_value_descriptor =
enum_descriptor->FindValueByName(value);
if (enum_value_descriptor == nullptr) {
return absl::nullopt;
}
return EnumConstant{
.type = Type::Enum(enum_descriptor),
.type_full_name = enum_descriptor->full_name(),
.value_name = enum_value_descriptor->name(),
.number = enum_value_descriptor->number(),
};
}
return absl::nullopt;
}

absl::StatusOr<absl::optional<StructTypeField>>
DescriptorPoolTypeIntrospector::FindStructTypeFieldByNameImpl(
absl::string_view type, absl::string_view name) const {
if (!use_json_name_) {
return FindStructTypeFieldByNameDirectly(descriptor_pool_, type, name);
}

const FieldTable* field_table = GetFieldTable(type);

if (field_table == nullptr) {
return absl::nullopt;
}

if (auto it = field_table->json_name_map.find(name);
it != field_table->json_name_map.end()) {
return field_table->fields[it->second].field;
}

if (auto it = field_table->extension_name_map.find(name);
it != field_table->extension_name_map.end()) {
return field_table->fields[it->second].field;
}

return absl::nullopt;
}

absl::StatusOr<
absl::optional<std::vector<TypeIntrospector::StructTypeFieldListing>>>
DescriptorPoolTypeIntrospector::ListFieldsForStructTypeImpl(
absl::string_view type) const {
if (!use_json_name_) {
return ListStructTypeFieldsDirectly(descriptor_pool_, type);
}

const FieldTable* field_table = GetFieldTable(type);
if (field_table == nullptr) {
return absl::nullopt;
}
std::vector<TypeIntrospector::StructTypeFieldListing> fields;
fields.reserve(field_table->non_extensions.size());
for (const auto& field : field_table->non_extensions) {
fields.push_back({field.json_name, field.field});
}
return fields;
}

const DescriptorPoolTypeIntrospector::FieldTable*
DescriptorPoolTypeIntrospector::GetFieldTable(
absl::string_view type_name) const {
absl::MutexLock lock(mu_);
if (auto it = field_tables_.find(type_name); it != field_tables_.end()) {
return it->second.get();
}
if (cel::IsWellKnownMessageType(type_name)) {
return nullptr;
}
const google::protobuf::Descriptor* absl_nullable descriptor =
descriptor_pool_->FindMessageTypeByName(type_name);
if (descriptor == nullptr) {
return nullptr;
}
absl::string_view stable_type_name = descriptor->full_name();
ABSL_DCHECK(stable_type_name == type_name);
std::unique_ptr<FieldTable> field_table = CreateFieldTable(descriptor);
const FieldTable* field_table_ptr = field_table.get();
field_tables_[stable_type_name] = std::move(field_table);
return field_table_ptr;
}

std::unique_ptr<DescriptorPoolTypeIntrospector::FieldTable>
DescriptorPoolTypeIntrospector::CreateFieldTable(
const google::protobuf::Descriptor* absl_nonnull descriptor) const {
ABSL_DCHECK(!IsWellKnownMessageType(descriptor));
std::vector<Field> fields;
absl::flat_hash_map<absl::string_view, int> json_name_map;
absl::flat_hash_map<absl::string_view, int> field_name_map;
absl::flat_hash_map<absl::string_view, int> extension_name_map;

std::vector<const google::protobuf::FieldDescriptor*> extensions;
descriptor_pool_->FindAllExtensions(descriptor, &extensions);
fields.reserve(descriptor->field_count() + extensions.size());

for (int i = 0; i < descriptor->field_count(); i++) {
const google::protobuf::FieldDescriptor* field = descriptor->field(i);
fields.push_back(Field{
.field = StructTypeField(MessageTypeField(field)),
.json_name = field->json_name(),
.is_extension = false,
});
field_name_map[field->name()] = fields.size() - 1;
if (use_json_name_ && !field->json_name().empty()) {
json_name_map[field->json_name()] = fields.size() - 1;
}
}
int non_extension_count = fields.size();

for (const google::protobuf::FieldDescriptor* extension : extensions) {
fields.push_back(Field{
.field = StructTypeField(MessageTypeField(extension)),
.json_name = "",
.is_extension = true,
});
extension_name_map[extension->full_name()] = fields.size() - 1;
}
int extension_count = fields.size() - non_extension_count;
auto result = std::make_unique<FieldTable>();
result->descriptor = descriptor;
result->fields = std::move(fields);
result->non_extensions =
absl::MakeConstSpan(result->fields).subspan(0, non_extension_count);
result->extensions = absl::MakeConstSpan(result->fields)
.subspan(non_extension_count, extension_count);
result->json_name_map = std::move(json_name_map);
result->field_name_map = std::move(field_name_map);
result->extension_name_map = std::move(extension_name_map);
return result;
}

} // namespace cel::checker_internal
Loading