1use std::collections::{hash_map, BTreeMap, HashMap, HashSet};
2
3use crate::{
4 descriptor::{
5 build::{
6 join_path,
7 options::option_to_bool,
8 visit::{visit, Visitor},
9 DescriptorPoolOffsets,
10 },
11 error::{DescriptorError, DescriptorErrorKind, Label},
12 tag, to_index,
13 types::{
14 DescriptorProto, EnumDescriptorProto, EnumValueDescriptorProto, FieldDescriptorProto,
15 FileDescriptorProto, MethodDescriptorProto, OneofDescriptorProto,
16 ServiceDescriptorProto,
17 },
18 Definition, DefinitionKind, DescriptorPoolInner, EnumDescriptorInner, EnumIndex,
19 EnumValueDescriptorInner, EnumValueIndex, ExtensionIndex, FieldIndex, FileDescriptorInner,
20 FileIndex, Identity, MessageDescriptorInner, MessageIndex, MethodIndex,
21 OneofDescriptorInner, OneofIndex, ServiceIndex,
22 },
23 Syntax,
24};
25
26impl DescriptorPoolInner {
27 pub(super) fn collect_names(
28 &mut self,
29 offsets: DescriptorPoolOffsets,
30 files: &[FileDescriptorProto],
31 ) -> Result<(), DescriptorError> {
32 let mut visitor = NameVisitor {
33 pool: self,
34 errors: vec![],
35 };
36 visit(offsets, files, &mut visitor);
37 if visitor.errors.is_empty() {
38 Ok(())
39 } else {
40 Err(DescriptorError::new(visitor.errors))
41 }
42 }
43}
44
45struct NameVisitor<'a> {
46 pool: &'a mut DescriptorPoolInner,
47 errors: Vec<DescriptorErrorKind>,
48}
49
50impl Visitor for NameVisitor<'_> {
51 fn visit_file(&mut self, path: &[i32], index: FileIndex, file: &FileDescriptorProto) {
52 debug_assert_eq!(to_index(self.pool.files.len()), index);
53
54 let syntax = match file.syntax.as_deref() {
55 None | Some("proto2") => Syntax::Proto2,
56 Some("proto3") => Syntax::Proto3,
57 Some(syntax) => {
58 self.errors.push(DescriptorErrorKind::UnknownSyntax {
59 syntax: syntax.to_owned(),
60 found: Label::new(
61 &self.pool.files,
62 "found here",
63 index,
64 join_path(path, &[tag::file::SYNTAX]),
65 ),
66 });
67 return;
68 }
69 };
70
71 if self
72 .pool
73 .file_names
74 .insert(file.name().into(), index)
75 .is_some()
76 {
77 self.errors.push(DescriptorErrorKind::DuplicateFileName {
78 name: file.name().to_owned(),
79 });
80 }
81 self.pool.files.push(FileDescriptorInner {
82 syntax,
83 raw: file.clone(),
84 prost: Default::default(), dependencies: Vec::with_capacity(file.dependency.len()),
86 transitive_dependencies: HashSet::default(),
87 });
88
89 if !file.package().is_empty() {
90 for (i, _) in file.package().match_indices('.') {
91 self.add_name(
92 index,
93 &file.package()[..i],
94 path,
95 &[tag::file::PACKAGE],
96 DefinitionKind::Package,
97 );
98 }
99 self.add_name(
100 index,
101 file.package(),
102 path,
103 &[tag::file::PACKAGE],
104 DefinitionKind::Package,
105 );
106 }
107 }
108
109 fn visit_message(
110 &mut self,
111 path: &[i32],
112 full_name: &str,
113 file: FileIndex,
114 parent: Option<MessageIndex>,
115 index: MessageIndex,
116 message: &DescriptorProto,
117 ) {
118 self.add_name(
119 file,
120 full_name,
121 path,
122 &[tag::message::NAME],
123 DefinitionKind::Message(index),
124 );
125
126 debug_assert_eq!(to_index(self.pool.messages.len()), index);
127 self.pool.messages.push(MessageDescriptorInner {
128 id: Identity::new(file, path, full_name, message.name()),
129 fields: Vec::with_capacity(message.field.len()),
130 field_numbers: BTreeMap::new(),
131 field_names: HashMap::with_capacity(message.field.len()),
132 field_json_names: HashMap::with_capacity(message.field.len()),
133 oneofs: Vec::with_capacity(message.oneof_decl.len()),
134 extensions: Vec::new(),
135 parent,
136 });
137
138 if self.pool.files[file as usize].syntax != Syntax::Proto2 {
139 self.check_message_field_camel_case_names(file, path, message);
140 }
141 }
142
143 fn visit_field(
144 &mut self,
145 path: &[i32],
146 full_name: &str,
147 file: FileIndex,
148 message: MessageIndex,
149 _: FieldIndex,
150 _: &FieldDescriptorProto,
151 ) {
152 self.add_name(
153 file,
154 full_name,
155 path,
156 &[tag::field::NAME],
157 DefinitionKind::Field(message),
158 );
159 }
160
161 fn visit_oneof(
162 &mut self,
163 path: &[i32],
164 full_name: &str,
165 file: FileIndex,
166 message: MessageIndex,
167 index: OneofIndex,
168 oneof: &OneofDescriptorProto,
169 ) {
170 self.add_name(
171 file,
172 full_name,
173 path,
174 &[tag::oneof::NAME],
175 DefinitionKind::Oneof(message),
176 );
177
178 debug_assert_eq!(
179 to_index(self.pool.messages[message as usize].oneofs.len()),
180 index
181 );
182 self.pool.messages[message as usize]
183 .oneofs
184 .push(OneofDescriptorInner {
185 id: Identity::new(file, path, full_name, oneof.name()),
186 fields: Vec::new(),
187 });
188 }
189
190 fn visit_service(
191 &mut self,
192 path: &[i32],
193 full_name: &str,
194 file: FileIndex,
195 index: ServiceIndex,
196 _: &ServiceDescriptorProto,
197 ) {
198 self.add_name(
199 file,
200 full_name,
201 path,
202 &[tag::service::NAME],
203 DefinitionKind::Service(index),
204 );
205 }
206
207 fn visit_method(
208 &mut self,
209 path: &[i32],
210 full_name: &str,
211 file: FileIndex,
212 service: ServiceIndex,
213 _: MethodIndex,
214 _: &MethodDescriptorProto,
215 ) {
216 self.add_name(
217 file,
218 full_name,
219 path,
220 &[tag::service::NAME],
221 DefinitionKind::Method(service),
222 );
223 }
224
225 fn visit_enum(
226 &mut self,
227 path: &[i32],
228 full_name: &str,
229 file: FileIndex,
230 parent: Option<MessageIndex>,
231 index: EnumIndex,
232 enum_: &EnumDescriptorProto,
233 ) {
234 self.add_name(
235 file,
236 full_name,
237 path,
238 &[tag::enum_::NAME],
239 DefinitionKind::Enum(index),
240 );
241
242 if enum_.value.is_empty() {
243 self.errors.push(DescriptorErrorKind::EmptyEnum {
244 found: Label::new(&self.pool.files, "enum defined here", file, path.into()),
245 });
246 } else if self.pool.files[file as usize].syntax != Syntax::Proto2
247 && enum_.value[0].number() != 0
248 {
249 self.errors
250 .push(DescriptorErrorKind::InvalidProto3EnumDefault {
251 found: Label::new(
252 &self.pool.files,
253 "defined here",
254 file,
255 join_path(path, &[tag::enum_::VALUE, 0, tag::enum_value::NUMBER]),
256 ),
257 });
258 }
259
260 let allow_alias = enum_.options.as_ref().is_some_and(|o| {
261 o.value.allow_alias()
262 || o.value.uninterpreted_option.iter().any(|u| {
263 u.name.len() == 1
264 && u.name[0].name_part == "allow_alias"
265 && !u.name[0].is_extension
266 && option_to_bool(u).unwrap_or(false)
267 })
268 });
269
270 debug_assert_eq!(to_index(self.pool.enums.len()), index);
271 self.pool.enums.push(EnumDescriptorInner {
272 id: Identity::new(file, path, full_name, enum_.name()),
273 parent,
274 values: Vec::with_capacity(enum_.value.len()),
275 value_numbers: Vec::with_capacity(enum_.value.len()),
276 value_names: HashMap::with_capacity(enum_.value.len()),
277 allow_alias,
278 });
279 }
280
281 fn visit_enum_value(
282 &mut self,
283 path: &[i32],
284 full_name: &str,
285 file: FileIndex,
286 enum_: EnumIndex,
287 index: EnumValueIndex,
288 value: &EnumValueDescriptorProto,
289 ) {
290 self.add_name(
291 file,
292 full_name,
293 path,
294 &[tag::enum_value::NAME],
295 DefinitionKind::EnumValue(enum_),
296 );
297
298 debug_assert_eq!(
299 to_index(self.pool.enums[enum_ as usize].values.len()),
300 index
301 );
302 self.pool.enums[enum_ as usize]
303 .values
304 .push(EnumValueDescriptorInner {
305 id: Identity::new(file, path, full_name, value.name()),
306 number: value.number(),
307 });
308 }
309
310 fn visit_extension(
311 &mut self,
312 path: &[i32],
313 full_name: &str,
314 file: FileIndex,
315 _: Option<MessageIndex>,
316 index: ExtensionIndex,
317 _: &FieldDescriptorProto,
318 ) {
319 self.add_name(
320 file,
321 full_name,
322 path,
323 &[tag::field::NAME],
324 DefinitionKind::Extension(index),
325 );
326 }
327}
328
329impl NameVisitor<'_> {
330 fn add_name(
331 &mut self,
332 file: FileIndex,
333 name: &str,
334 path1: &[i32],
335 path2: &[i32],
336 kind: DefinitionKind,
337 ) {
338 let path = join_path(path1, path2);
339
340 match self.pool.names.entry(name.into()) {
341 hash_map::Entry::Vacant(entry) => {
342 entry.insert(Definition { file, kind, path });
343 }
344 hash_map::Entry::Occupied(_) => {
345 let entry = &self.pool.names[name];
346
347 if matches!(kind, DefinitionKind::Package)
348 && matches!(entry.kind, DefinitionKind::Package)
349 {
350 return;
351 }
352
353 self.errors.push(DescriptorErrorKind::DuplicateName {
354 name: name.to_owned(),
355 first: Label::new(
356 &self.pool.files,
357 "first defined here",
358 entry.file,
359 entry.path.clone(),
360 ),
361 second: Label::new(&self.pool.files, "defined again here", file, path),
362 })
363 }
364 }
365 }
366
367 fn check_message_field_camel_case_names(
368 &mut self,
369 file: FileIndex,
370 path: &[i32],
371 message: &DescriptorProto,
372 ) {
373 let mut names: HashMap<String, (&str, i32)> = HashMap::new();
374 for (index, field) in message.field.iter().enumerate() {
375 let name = field.name();
376 let index = index as i32;
377
378 match names.entry(to_lower_without_underscores(name)) {
379 hash_map::Entry::Occupied(entry) => {
380 self.errors
381 .push(DescriptorErrorKind::DuplicateFieldCamelCaseName {
382 first_name: entry.get().0.to_owned(),
383 first: Label::new(
384 &self.pool.files,
385 "first defined here",
386 file,
387 join_path(
388 path,
389 &[tag::message::FIELD, entry.get().1, tag::field::NAME],
390 ),
391 ),
392 second_name: name.to_owned(),
393 second: Label::new(
394 &self.pool.files,
395 "defined again here",
396 file,
397 join_path(path, &[tag::message::FIELD, index, tag::field::NAME]),
398 ),
399 })
400 }
401 hash_map::Entry::Vacant(entry) => {
402 entry.insert((name, index));
403 }
404 }
405 }
406 }
407}
408
409fn to_lower_without_underscores(name: &str) -> String {
410 name.chars()
411 .filter_map(|ch| match ch {
412 '_' => None,
413 _ => Some(ch.to_ascii_lowercase()),
414 })
415 .collect()
416}