Skip to content

Commit f2cf85c

Browse files
Add name mangling to nested names that collide with known generated names, like
the `New` function under messages. This allows C++ codegen to compile on .proto files where it did not work before. PiperOrigin-RevId: 691852276
1 parent f27532d commit f2cf85c

File tree

7 files changed

+225
-24
lines changed

7 files changed

+225
-24
lines changed

src/google/protobuf/compiler/cpp/enum.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ absl::flat_hash_map<absl::string_view, std::string> EnumVars(
4646
return {
4747
{"DEPRECATED", enum_->options().deprecated() ? "[[deprecated]]" : ""},
4848
{"Enum", std::string(enum_->name())},
49-
{"Enum_", ResolveKeyword(enum_->name())},
49+
{"Enum_", ResolveKnownNameCollisions(enum_->name(),
50+
enum_->containing_type() != nullptr
51+
? NameContext::kMessage
52+
: NameContext::kFile,
53+
NameKind::kType)},
5054
{"Msg_Enum", classname},
5155
{"::Msg_Enum", QualifiedClassName(enum_, options)},
5256
{"Msg_Enum_",

src/google/protobuf/compiler/cpp/extension.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ ExtensionGenerator::ExtensionGenerator(const FieldDescriptor* descriptor,
6161
variables_["extendee"] =
6262
QualifiedClassName(descriptor_->containing_type(), options_);
6363
variables_["type_traits"] = type_traits_;
64-
variables_["name"] = ResolveKeyword(descriptor_->name());
64+
variables_["name"] = ResolveKnownNameCollisions(
65+
descriptor_->name(),
66+
descriptor_->extension_scope() != nullptr ? NameContext::kMessage
67+
: NameContext::kFile,
68+
NameKind::kValue);
6569
variables_["constant_name"] = FieldConstantName(descriptor_);
6670
variables_["field_type"] =
6771
absl::StrCat(static_cast<int>(descriptor_->type()));

src/google/protobuf/compiler/cpp/helpers.cc

Lines changed: 103 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,50 @@ namespace {
7373
constexpr absl::string_view kAnyMessageName = "Any";
7474
constexpr absl::string_view kAnyProtoFile = "google/protobuf/any.proto";
7575

76+
const absl::flat_hash_set<absl::string_view>& FileScopeKnownNames() {
77+
static constexpr const char* kValue[] = {
78+
"swap",
79+
};
80+
static const auto* const methods = new absl::flat_hash_set<absl::string_view>(
81+
std::begin(kValue), std::end(kValue));
82+
return *methods;
83+
}
84+
85+
const absl::flat_hash_set<absl::string_view>& MessageKnownMethodsCamelCase() {
86+
static constexpr const char* kMessageKnownMethods[] = {
87+
"GetDescriptor", "GetReflection", "default_instance",
88+
"Swap", "UnsafeArenaSwap", "New",
89+
"CopyFrom", "MergeFrom", "IsInitialized",
90+
"GetMetadata", "Clear",
91+
};
92+
static const auto* const methods = new absl::flat_hash_set<absl::string_view>(
93+
std::begin(kMessageKnownMethods), std::end(kMessageKnownMethods));
94+
return *methods;
95+
}
96+
97+
const absl::flat_hash_set<absl::string_view>&
98+
MessageKnownNullaryMethodsSnakeCase() {
99+
static constexpr const char* kMessageKnownMethods[] = {
100+
"unknown_fields",
101+
"mutable_unknown_fields",
102+
"descriptor",
103+
"default_instance",
104+
};
105+
static const auto* const methods = new absl::flat_hash_set<absl::string_view>(
106+
std::begin(kMessageKnownMethods), std::end(kMessageKnownMethods));
107+
return *methods;
108+
}
109+
110+
const absl::flat_hash_set<absl::string_view>&
111+
MessageKnownNonNullaryMethodsSnakeCase() {
112+
static constexpr const char* kMessageKnownMethods[] = {
113+
"swap",
114+
};
115+
static const auto* const methods = new absl::flat_hash_set<absl::string_view>(
116+
std::begin(kMessageKnownMethods), std::end(kMessageKnownMethods));
117+
return *methods;
118+
}
119+
76120
static const char* const kKeywordList[] = {
77121
// clang-format off
78122
"NULL",
@@ -406,12 +450,14 @@ std::string ClassName(const Descriptor* descriptor) {
406450
if (parent) absl::StrAppend(&res, ClassName(parent), "_");
407451
absl::StrAppend(&res, descriptor->name());
408452
if (IsMapEntryMessage(descriptor)) absl::StrAppend(&res, "_DoNotUse");
409-
return ResolveKeyword(res);
453+
// This is the mangled message name which always goes in file scope.
454+
return ResolveKnownNameCollisions(res, NameContext::kFile, NameKind::kType);
410455
}
411456

412457
std::string ClassName(const EnumDescriptor* enum_descriptor) {
413458
if (enum_descriptor->containing_type() == nullptr) {
414-
return ResolveKeyword(enum_descriptor->name());
459+
return ResolveKnownNameCollisions(enum_descriptor->name(),
460+
NameContext::kFile, NameKind::kType);
415461
} else {
416462
return absl::StrCat(ClassName(enum_descriptor->containing_type()), "_",
417463
enum_descriptor->name());
@@ -436,9 +482,14 @@ std::string QualifiedClassName(const EnumDescriptor* d) {
436482
}
437483

438484
std::string ExtensionName(const FieldDescriptor* d) {
439-
if (const Descriptor* scope = d->extension_scope())
440-
return absl::StrCat(ClassName(scope), "::", ResolveKeyword(d->name()));
441-
return ResolveKeyword(d->name());
485+
if (const Descriptor* scope = d->extension_scope()) {
486+
return absl::StrCat(
487+
ClassName(scope), "::",
488+
ResolveKnownNameCollisions(d->name(), NameContext::kMessage,
489+
NameKind::kValue));
490+
}
491+
return ResolveKnownNameCollisions(d->name(), NameContext::kFile,
492+
NameKind::kValue);
442493
}
443494

444495
std::string QualifiedExtensionName(const FieldDescriptor* d,
@@ -452,23 +503,53 @@ std::string QualifiedExtensionName(const FieldDescriptor* d) {
452503
}
453504

454505
std::string ResolveKeyword(absl::string_view name) {
455-
if (Keywords().count(name) > 0) {
506+
if (Keywords().contains(name)) {
456507
return absl::StrCat(name, "_");
457508
}
458509
return std::string(name);
459510
}
460511

461-
std::string DotsToColons(absl::string_view name) {
462-
std::vector<std::string> scope = absl::StrSplit(name, '.', absl::SkipEmpty());
463-
for (auto& word : scope) {
464-
word = ResolveKeyword(word);
512+
std::string ResolveKnownNameCollisions(absl::string_view name,
513+
NameContext name_context,
514+
NameKind name_kind) {
515+
const auto has_conflict = [&] {
516+
if (Keywords().contains(name)) return true;
517+
518+
switch (name_kind) {
519+
// We assume the style guide: types are CamelCase, fields are snake_case.
520+
case NameKind::kType:
521+
// Types can't overload names of existing functions.
522+
return MessageKnownMethodsCamelCase().contains(name);
523+
case NameKind::kValue:
524+
if (name_context == NameContext::kFile) {
525+
// At file scope we don't have the normal names, except a few.
526+
return FileScopeKnownNames().contains(name);
527+
}
528+
// Values can't overload names of existing functions.
529+
return MessageKnownNullaryMethodsSnakeCase().contains(name) ||
530+
MessageKnownNonNullaryMethodsSnakeCase().contains(name);
531+
case NameKind::kFunction:
532+
// For functions, we can't overload existing nullary functions.
533+
// Non-nullary functions are fine.
534+
return MessageKnownNullaryMethodsSnakeCase().contains(name);
535+
}
536+
return false;
537+
};
538+
if (has_conflict()) {
539+
return absl::StrCat(name, "_");
465540
}
466-
return absl::StrJoin(scope, "::");
541+
return std::string(name);
467542
}
468543

469544
std::string Namespace(absl::string_view package) {
470545
if (package.empty()) return "";
471-
return absl::StrCat("::", DotsToColons(package));
546+
547+
std::vector<std::string> scope =
548+
absl::StrSplit(package, '.', absl::SkipEmpty());
549+
for (auto& word : scope) {
550+
word = ResolveKeyword(word);
551+
}
552+
return absl::StrCat("::", absl::StrJoin(scope, "::"));
472553
}
473554

474555
std::string Namespace(const FileDescriptor* d) { return Namespace(d, {}); }
@@ -555,12 +636,17 @@ std::string SuperClassName(const Descriptor* descriptor,
555636
}
556637

557638
std::string FieldName(const FieldDescriptor* field) {
639+
if (field->containing_type() != nullptr &&
640+
field->containing_type()->options().no_standard_descriptor_accessor() &&
641+
field->name() == "descriptor") {
642+
// Special case for `optional no_standard_descriptor_accessor = true;`
643+
return "descriptor";
644+
}
558645
std::string result = std::string(field->name());
559646
absl::AsciiStrToLower(&result);
560-
if (Keywords().count(result) > 0) {
561-
result.append("_");
562-
}
563-
return result;
647+
ABSL_CHECK(field->containing_type() != nullptr);
648+
return ResolveKnownNameCollisions(result, NameContext::kMessage,
649+
NameKind::kFunction);
564650
}
565651

566652
std::string FieldMemberName(const FieldDescriptor* field, bool split) {
@@ -589,11 +675,7 @@ std::string QualifiedOneofCaseConstantName(const FieldDescriptor* field) {
589675
}
590676

591677
std::string EnumValueName(const EnumValueDescriptor* enum_value) {
592-
std::string result = std::string(enum_value->name());
593-
if (Keywords().count(result) > 0) {
594-
result.append("_");
595-
}
596-
return result;
678+
return ResolveKeyword(enum_value->name());
597679
}
598680

599681
int EstimateAlignmentSize(const FieldDescriptor* field) {

src/google/protobuf/compiler/cpp/helpers.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,24 @@ std::string FileDllExport(const FileDescriptor* file, const Options& options);
188188
std::string SuperClassName(const Descriptor* descriptor,
189189
const Options& options);
190190

191+
// Add an underscore if necessary to prevent conflicting with known names and
192+
// keywords.
193+
// We use the context and the kind of entity to try to determine if mangling is
194+
// necessary or not.
195+
// For example, a message named `New` at file scope is fine, but at message
196+
// scope it needs mangling because it collides with the `New` function.
197+
enum class NameContext {
198+
kFile,
199+
kMessage,
200+
};
201+
enum class NameKind {
202+
kType,
203+
kFunction,
204+
kValue,
205+
};
206+
std::string ResolveKnownNameCollisions(absl::string_view name,
207+
NameContext name_context,
208+
NameKind name_kind);
191209
// Adds an underscore if necessary to prevent conflicting with a keyword.
192210
std::string ResolveKeyword(absl::string_view name);
193211

src/google/protobuf/compiler/cpp/message.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1976,7 +1976,10 @@ void MessageGenerator::GenerateClassDefinition(io::Printer* p) {
19761976
{
19771977
Sub{"nested_full_name", ClassName(nested_type, false)}
19781978
.AnnotatedAs(nested_type),
1979-
Sub{"nested_name", ResolveKeyword(nested_type->name())}
1979+
Sub{"nested_name",
1980+
ResolveKnownNameCollisions(nested_type->name(),
1981+
NameContext::kMessage,
1982+
NameKind::kType)}
19801983
.AnnotatedAs(nested_type),
19811984
},
19821985
R"cc(

src/google/protobuf/compiler/cpp/test_bad_identifiers.proto

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,41 @@ message TestConflictingSymbolNames {
115115
optional uint32 typedecl = 39;
116116
optional uint32 auto = 40;
117117

118+
// Methods generated in the parent type
119+
message BadKnownNamesFields {
120+
optional int32 unknown_fields = 1;
121+
optional int32 mutable_unknown_fields = 2;
122+
optional int32 descriptor = 3;
123+
optional int32 default_instance = 4;
124+
optional int32 swap = 5;
125+
}
126+
message BadKnownNamesFieldsNoStandardDescriptor {
127+
option no_standard_descriptor_accessor = true;
128+
optional int32 descriptor = 3;
129+
}
130+
message BadKnownNamesTypes {
131+
message GetDescriptor {}
132+
message GetReflection {}
133+
message Swap {}
134+
message UnsafeArenaSwap {}
135+
message New {}
136+
message CopyFrom {}
137+
message MergeFrom {}
138+
message GetMetadata {}
139+
message Clear {}
140+
message IsInitialized {}
141+
}
142+
message BadKnownNamesValues { // NO_PROTO3
143+
extensions 1 to 100; // NO_PROTO3
144+
extend BadKnownNamesValues { // NO_PROTO3
145+
optional int32 unknown_fields = 1; // NO_PROTO3
146+
optional int32 mutable_unknown_fields = 2; // NO_PROTO3
147+
optional int32 descriptor = 3; // NO_PROTO3
148+
optional int32 default_instance = 4; // NO_PROTO3
149+
optional int32 swap = 5; // NO_PROTO3
150+
} // NO_PROTO3
151+
} // NO_PROTO3
152+
118153
// The generator used to #define a macro called "DO" inside the .cc file.
119154
message DO {}
120155
optional DO do = 32;
@@ -137,6 +172,26 @@ message TestConflictingSymbolNames {
137172
extensions 1000 to max [verification = UNVERIFIED]; // NO_PROTO3
138173
}
139174

175+
// Special names as above, but at file scope.
176+
message GetDescriptor {}
177+
message GetReflection {}
178+
message Swap {}
179+
message UnsafeArenaSwap {}
180+
message New {}
181+
message CopyFrom {}
182+
message MergeFrom {}
183+
message GetMetadata {}
184+
message Clear {}
185+
message IsInitialized {}
186+
187+
extend TestConflictingSymbolNames.BadKnownNamesValues { // NO_PROTO3
188+
optional int32 unknown_fields = 11; // NO_PROTO3
189+
optional int32 mutable_unknown_fields = 12; // NO_PROTO3
190+
optional int32 descriptor = 13; // NO_PROTO3
191+
optional int32 default_instance = 14; // NO_PROTO3
192+
optional int32 swap = 15; // NO_PROTO3
193+
} // NO_PROTO3
194+
140195
message TestConflictingSymbolNamesExtension { // NO_PROTO3
141196
extend TestConflictingSymbolNames { // NO_PROTO3
142197
repeated int32 repeated_int32_ext = 20423638 [packed = true]; // NO_PROTO3

src/google/protobuf/compiler/cpp/unittest.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,41 @@ TEST(GENERATED_MESSAGE_TEST_NAME, TestConflictingSymbolNames) {
7575
EXPECT_EQ(123, message.GetExtension(ExtensionMessage::repeated_int32_ext, 0));
7676
}
7777

78+
TEST(GENERATED_MESSAGE_TEST_NAME, TestSwapNameIsNotMangledForFields) {
79+
// For backwards compatibility we do not mangle `swap`. It works thanks to
80+
// overload resolution.
81+
int v [[maybe_unused]] =
82+
protobuf_unittest::TestConflictingSymbolNames::BadKnownNamesFields().swap();
83+
84+
// But we do mangle `swap` for extensions because there is no overloading
85+
// there.
86+
v = protobuf_unittest::TestConflictingSymbolNames::BadKnownNamesValues()
87+
.GetExtension(protobuf_unittest::TestConflictingSymbolNames::
88+
BadKnownNamesValues::swap_);
89+
}
90+
91+
TEST(GENERATED_MESSAGE_TEST_NAME, TestNoStandardDescriptorOption) {
92+
// When no_standard_descriptor_accessor = true, we should not mangle fields
93+
// named `descriptor`.
94+
int v [[maybe_unused]] =
95+
protobuf_unittest::TestConflictingSymbolNames::BadKnownNamesFields()
96+
.descriptor_();
97+
v = protobuf_unittest::TestConflictingSymbolNames::
98+
BadKnownNamesFieldsNoStandardDescriptor()
99+
.descriptor();
100+
}
101+
102+
TEST(GENERATED_MESSAGE_TEST_NAME, TestFileVsMessageScope) {
103+
// Special names at message scope are mangled,
104+
int v [[maybe_unused]] =
105+
protobuf_unittest::TestConflictingSymbolNames::BadKnownNamesValues()
106+
.GetExtension(protobuf_unittest::TestConflictingSymbolNames::
107+
BadKnownNamesValues::unknown_fields_);
108+
// But not at file scope.
109+
v = protobuf_unittest::TestConflictingSymbolNames::BadKnownNamesValues()
110+
.GetExtension(protobuf_unittest::unknown_fields);
111+
}
112+
78113
TEST(GENERATED_MESSAGE_TEST_NAME, TestConflictingEnumNames) {
79114
protobuf_unittest::TestConflictingEnumNames message;
80115
message.set_conflicting_enum(

0 commit comments

Comments
 (0)