1mod redeclaration;
4
5use std::collections::{BTreeMap, BTreeSet};
6
7use crate::{Decl, IdentifierPath, ObjectiveCForwardKind, TranslationUnit, Type};
8use redeclaration::{Redeclaration, declaration_identity, redeclaration};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum HeaderValidationCode {
13 SyntaxError,
15 DuplicateDeclaration,
17 ConflictingRedeclaration,
19 UnresolvedType,
21 UnresolvedOwner,
23 InvalidLinkage,
25 InvalidStorage,
27 InvalidCallingConvention,
29 IncompleteTemplateContext,
31 SelectorArityMismatch,
33 ObjectiveCKindMismatch,
35 DependencyCycle,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum Severity {
42 Info,
44 Warning,
46 Error,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct HeaderValidationDiagnostic {
53 pub code: HeaderValidationCode,
55 pub severity: Severity,
57 pub message: String,
59 pub declaration_index: Option<u32>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct HeaderValidationReport {
66 pub syntax_valid: bool,
68 pub semantic_valid: bool,
70 pub diagnostics: Vec<HeaderValidationDiagnostic>,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct ValidationLimits {
77 pub max_type_depth: usize,
79 pub max_declaration_depth: usize,
81 pub max_items_per_node: usize,
83 pub max_total_nodes: usize,
85}
86
87impl Default for ValidationLimits {
88 fn default() -> Self {
89 Self {
90 max_type_depth: 64,
91 max_declaration_depth: 64,
92 max_items_per_node: 1_024,
93 max_total_nodes: 1_000_000,
94 }
95 }
96}
97
98#[derive(Debug, thiserror::Error, PartialEq, Eq)]
100pub enum ValidationError {
101 #[error("type recursion depth {actual} exceeds limit {limit}")]
103 TypeDepth {
104 limit: usize,
106 actual: usize,
108 },
109 #[error("declaration nesting depth {actual} exceeds limit {limit}")]
111 DeclarationDepth {
112 limit: usize,
114 actual: usize,
116 },
117 #[error("{kind} item count {actual} exceeds limit {limit}")]
119 ItemsPerNode {
120 kind: &'static str,
122 limit: usize,
124 actual: usize,
126 },
127 #[error("AST node count exceeds limit {limit}")]
129 TotalNodes {
130 limit: usize,
132 },
133}
134
135pub fn validate(
137 unit: &TranslationUnit,
138 limits: ValidationLimits,
139) -> Result<HeaderValidationReport, ValidationError> {
140 let mut budget = Budget { limits, nodes: 0 };
141 for declaration in &unit.declarations {
142 budget.declaration(declaration, 1)?;
143 }
144
145 let mut diagnostics = Vec::new();
146 let mut declarations = BTreeMap::<String, (usize, &Decl)>::new();
147 let mut declared_tags = BTreeSet::new();
148 let mut objc_classes = BTreeSet::new();
149 let mut objc_protocols = BTreeSet::new();
150
151 if unit.language == crate::Language::ObjectiveC {
155 declared_tags.extend(["id".to_owned(), "Class".to_owned(), "SEL".to_owned()]);
156 }
157
158 for (index, declaration) in unit.declarations.iter().enumerate() {
159 collect_declared_types(
160 declaration,
161 &mut declared_tags,
162 &mut objc_classes,
163 &mut objc_protocols,
164 );
165 if let Some(identity) = declaration_identity(unit.language, declaration) {
166 if let Some((previous_index, previous)) = declarations.get(&identity) {
167 match redeclaration(unit.language, previous, declaration) {
168 Redeclaration::Compatible { replace } => {
169 diagnostics.push(diagnostic_with_severity(
170 HeaderValidationCode::DuplicateDeclaration,
171 Severity::Info,
172 format!(
173 "declaration `{identity}` is compatible with index {previous_index}"
174 ),
175 index,
176 ));
177 if replace {
178 declarations.insert(identity, (index, declaration));
179 }
180 }
181 Redeclaration::Duplicate => diagnostics.push(diagnostic(
182 HeaderValidationCode::DuplicateDeclaration,
183 format!("declaration `{identity}` duplicates index {previous_index}"),
184 index,
185 )),
186 Redeclaration::Conflict => diagnostics.push(diagnostic(
187 HeaderValidationCode::ConflictingRedeclaration,
188 format!("declaration `{identity}` conflicts with index {previous_index}"),
189 index,
190 )),
191 }
192 } else {
193 declarations.insert(identity, (index, declaration));
194 }
195 }
196 }
197
198 declared_tags.extend(objc_classes.iter().cloned());
203
204 for (index, declaration) in unit.declarations.iter().enumerate() {
205 validate_decl(
206 declaration,
207 index,
208 &declared_tags,
209 &objc_classes,
210 &objc_protocols,
211 &mut diagnostics,
212 );
213 }
214 validate_cycles(&unit.declarations, &mut diagnostics);
215 diagnostics.sort_by_key(|diagnostic| {
216 (
217 diagnostic.declaration_index.unwrap_or(u32::MAX),
218 diagnostic.code as u8,
219 diagnostic.message.clone(),
220 )
221 });
222 let semantic_valid = !diagnostics
223 .iter()
224 .any(|diagnostic| diagnostic.severity == Severity::Error);
225 Ok(HeaderValidationReport {
226 syntax_valid: true,
227 semantic_valid,
228 diagnostics,
229 })
230}
231
232struct Budget {
233 limits: ValidationLimits,
234 nodes: usize,
235}
236
237impl Budget {
238 fn node(&mut self) -> Result<(), ValidationError> {
239 self.nodes += 1;
240 if self.nodes > self.limits.max_total_nodes {
241 Err(ValidationError::TotalNodes {
242 limit: self.limits.max_total_nodes,
243 })
244 } else {
245 Ok(())
246 }
247 }
248
249 fn items(&self, kind: &'static str, actual: usize) -> Result<(), ValidationError> {
250 if actual > self.limits.max_items_per_node {
251 Err(ValidationError::ItemsPerNode {
252 kind,
253 limit: self.limits.max_items_per_node,
254 actual,
255 })
256 } else {
257 Ok(())
258 }
259 }
260
261 fn declaration(&mut self, declaration: &Decl, depth: usize) -> Result<(), ValidationError> {
262 if depth > self.limits.max_declaration_depth {
263 return Err(ValidationError::DeclarationDepth {
264 limit: self.limits.max_declaration_depth,
265 actual: depth,
266 });
267 }
268 self.node()?;
269 match declaration {
270 Decl::Function { signature, .. } => self.ty(signature, 1),
271 Decl::Variable { ty, .. } | Decl::Alias { target: ty, .. } => self.ty(ty, 1),
272 Decl::Record {
273 bases,
274 fields,
275 members,
276 ..
277 } => {
278 self.items("record members", bases.len() + fields.len() + members.len())?;
279 for base in bases {
280 self.node()?;
281 self.ty(&base.ty, 1)?;
282 }
283 for field in fields {
284 self.node()?;
285 self.ty(&field.ty, 1)?;
286 }
287 for member in members {
288 self.declaration(member, depth + 1)?;
289 }
290 Ok(())
291 }
292 Decl::Forward { .. } => Ok(()),
293 Decl::ObjectiveCInterface {
294 ivars,
295 methods,
296 properties,
297 ..
298 } => {
299 self.items(
300 "Objective-C members",
301 ivars.len() + methods.len() + properties.len(),
302 )?;
303 for ivar in ivars {
304 self.node()?;
305 self.ty(&ivar.ty, 1)?;
306 }
307 self.objc_members(methods, properties)
308 }
309 Decl::ObjectiveCCategory {
310 methods,
311 properties,
312 ..
313 }
314 | Decl::ObjectiveCProtocol {
315 methods,
316 properties,
317 ..
318 } => {
319 self.items("Objective-C members", methods.len() + properties.len())?;
320 self.objc_members(methods, properties)
321 }
322 Decl::ObjectiveCForward { names, .. } => {
323 self.items("Objective-C forward names", names.len())
324 }
325 }
326 }
327
328 fn objc_members(
329 &mut self,
330 methods: &[crate::ObjectiveCMethod],
331 properties: &[crate::ObjectiveCProperty],
332 ) -> Result<(), ValidationError> {
333 for method in methods {
334 self.node()?;
335 self.ty(&method.return_type, 1)?;
336 self.items("Objective-C method parameters", method.parameters.len())?;
337 for parameter in &method.parameters {
338 self.node()?;
339 self.ty(¶meter.ty, 1)?;
340 }
341 }
342 for property in properties {
343 self.node()?;
344 self.ty(&property.ty, 1)?;
345 }
346 Ok(())
347 }
348
349 fn ty(&mut self, ty: &Type, depth: usize) -> Result<(), ValidationError> {
350 if depth > self.limits.max_type_depth {
351 return Err(ValidationError::TypeDepth {
352 limit: self.limits.max_type_depth,
353 actual: depth,
354 });
355 }
356 self.node()?;
357 match ty {
358 Type::Builtin(_) => Ok(()),
359 Type::Named {
360 template_arguments, ..
361 } => {
362 self.items("template arguments", template_arguments.len())?;
363 for argument in template_arguments {
364 self.node()?;
365 if let crate::TemplateArgument::Type(ty) = argument {
366 self.ty(ty, depth + 1)?;
367 }
368 }
369 Ok(())
370 }
371 Type::Pointer { pointee, .. }
372 | Type::Reference {
373 target: pointee, ..
374 }
375 | Type::Array {
376 element: pointee, ..
377 }
378 | Type::ObjectiveCBlock(pointee) => self.ty(pointee, depth + 1),
379 Type::Function {
380 return_type,
381 parameters,
382 ..
383 } => {
384 self.items("function parameters", parameters.len())?;
385 self.ty(return_type, depth + 1)?;
386 for parameter in parameters {
387 self.node()?;
388 self.ty(¶meter.ty, depth + 1)?;
389 }
390 Ok(())
391 }
392 Type::ObjectiveCObject { protocols, .. } => {
393 self.items("Objective-C protocols", protocols.len())
394 }
395 }
396 }
397}
398
399fn collect_declared_types(
400 declaration: &Decl,
401 tags: &mut BTreeSet<String>,
402 classes: &mut BTreeSet<String>,
403 protocols: &mut BTreeSet<String>,
404) {
405 match declaration {
406 Decl::Record { path, members, .. } => {
407 tags.insert(path_string(path));
408 for member in members {
409 collect_declared_types(member, tags, classes, protocols);
410 }
411 }
412 Decl::Forward { path, .. } | Decl::Alias { path, .. } => {
413 tags.insert(path_string(path));
414 }
415 Decl::ObjectiveCInterface { name, .. } => {
416 classes.insert(name.to_string());
417 }
418 Decl::ObjectiveCProtocol { name, .. } => {
419 protocols.insert(name.to_string());
420 }
421 Decl::ObjectiveCForward { kind, names } => {
422 let destination = match kind {
423 ObjectiveCForwardKind::Class => classes,
424 ObjectiveCForwardKind::Protocol => protocols,
425 };
426 destination.extend(names.iter().map(ToString::to_string));
427 }
428 Decl::Function { .. } | Decl::Variable { .. } | Decl::ObjectiveCCategory { .. } => {}
429 }
430}
431
432fn validate_decl(
433 declaration: &Decl,
434 index: usize,
435 tags: &BTreeSet<String>,
436 classes: &BTreeSet<String>,
437 protocols: &BTreeSet<String>,
438 diagnostics: &mut Vec<HeaderValidationDiagnostic>,
439) {
440 match declaration {
441 Decl::Function { signature, .. } => {
442 validate_type(signature, index, tags, protocols, diagnostics)
443 }
444 Decl::Variable { ty, .. } | Decl::Alias { target: ty, .. } => {
445 validate_type(ty, index, tags, protocols, diagnostics)
446 }
447 Decl::Record {
448 bases,
449 fields,
450 members,
451 ..
452 } => {
453 for base in bases {
454 validate_type(&base.ty, index, tags, protocols, diagnostics);
455 }
456 for field in fields {
457 validate_type(&field.ty, index, tags, protocols, diagnostics);
458 }
459 for member in members {
460 validate_decl(member, index, tags, classes, protocols, diagnostics);
461 }
462 }
463 Decl::Forward { .. } | Decl::ObjectiveCForward { .. } => {}
464 Decl::ObjectiveCInterface {
465 superclass,
466 protocols: adopted,
467 ivars,
468 methods,
469 properties,
470 ..
471 } => {
472 if let Some(superclass) = superclass
473 .as_ref()
474 .filter(|name| !classes.contains(name.as_str()))
475 {
476 diagnostics.push(diagnostic(
477 HeaderValidationCode::UnresolvedOwner,
478 format!("Objective-C superclass `{superclass}` is not declared"),
479 index,
480 ));
481 }
482 for ivar in ivars {
483 validate_type(&ivar.ty, index, tags, protocols, diagnostics);
484 }
485 validate_objc_members(
486 adopted,
487 methods,
488 properties,
489 index,
490 tags,
491 protocols,
492 diagnostics,
493 );
494 }
495 Decl::ObjectiveCCategory {
496 extended_class,
497 protocols: adopted,
498 methods,
499 properties,
500 ..
501 } => {
502 if !classes.contains(extended_class.as_str()) {
503 diagnostics.push(diagnostic(
504 HeaderValidationCode::UnresolvedOwner,
505 format!("Objective-C category owner `{extended_class}` is not declared"),
506 index,
507 ));
508 }
509 validate_objc_members(
510 adopted,
511 methods,
512 properties,
513 index,
514 tags,
515 protocols,
516 diagnostics,
517 );
518 }
519 Decl::ObjectiveCProtocol {
520 protocols: adopted,
521 methods,
522 properties,
523 ..
524 } => validate_objc_members(
525 adopted,
526 methods,
527 properties,
528 index,
529 tags,
530 protocols,
531 diagnostics,
532 ),
533 }
534}
535
536fn validate_objc_members(
537 adopted: &[crate::Identifier],
538 methods: &[crate::ObjectiveCMethod],
539 properties: &[crate::ObjectiveCProperty],
540 index: usize,
541 tags: &BTreeSet<String>,
542 protocols: &BTreeSet<String>,
543 diagnostics: &mut Vec<HeaderValidationDiagnostic>,
544) {
545 for protocol in adopted {
546 if !protocols.contains(protocol.as_str()) {
547 diagnostics.push(diagnostic(
548 HeaderValidationCode::ObjectiveCKindMismatch,
549 format!("Objective-C protocol `{protocol}` is not declared"),
550 index,
551 ));
552 }
553 }
554 let mut identities = BTreeSet::new();
555 for method in methods {
556 let identity = format!("method:{:?}:{}", method.kind, method.selector);
557 if !identities.insert(identity) {
558 diagnostics.push(diagnostic(
559 HeaderValidationCode::DuplicateDeclaration,
560 format!("duplicate Objective-C method `{}`", method.selector),
561 index,
562 ));
563 }
564 let arity = method.selector.bytes().filter(|byte| *byte == b':').count();
565 if arity != method.parameters.len() {
566 diagnostics.push(diagnostic(
567 HeaderValidationCode::SelectorArityMismatch,
568 format!(
569 "selector `{}` has {arity} component(s) but {} parameter(s)",
570 method.selector,
571 method.parameters.len()
572 ),
573 index,
574 ));
575 }
576 validate_type(&method.return_type, index, tags, protocols, diagnostics);
577 for parameter in &method.parameters {
578 validate_type(¶meter.ty, index, tags, protocols, diagnostics);
579 }
580 }
581 for property in properties {
582 if !identities.insert(format!("property:{}", property.name)) {
583 diagnostics.push(diagnostic(
584 HeaderValidationCode::DuplicateDeclaration,
585 format!("duplicate Objective-C property `{}`", property.name),
586 index,
587 ));
588 }
589 validate_type(&property.ty, index, tags, protocols, diagnostics);
590 }
591}
592
593fn validate_type(
594 ty: &Type,
595 index: usize,
596 tags: &BTreeSet<String>,
597 protocols: &BTreeSet<String>,
598 diagnostics: &mut Vec<HeaderValidationDiagnostic>,
599) {
600 match ty {
601 Type::Builtin(_) => {}
602 Type::Named {
603 tag,
604 path,
605 template_arguments,
606 } => {
607 let name = path_string(path);
608 let resolved = if matches!(tag, crate::NamedTypeTag::Protocol) {
609 protocols.contains(&name)
610 } else {
611 tags.contains(&name)
612 };
613 if !resolved {
614 diagnostics.push(diagnostic(
615 HeaderValidationCode::UnresolvedType,
616 format!("named type `{name}` is not declared"),
617 index,
618 ));
619 }
620 for argument in template_arguments {
621 if let crate::TemplateArgument::Type(ty) = argument {
622 validate_type(ty, index, tags, protocols, diagnostics);
623 }
624 }
625 }
626 Type::Pointer { pointee, .. }
627 | Type::Reference {
628 target: pointee, ..
629 }
630 | Type::Array {
631 element: pointee, ..
632 }
633 | Type::ObjectiveCBlock(pointee) => {
634 validate_type(pointee, index, tags, protocols, diagnostics)
635 }
636 Type::Function {
637 return_type,
638 parameters,
639 ..
640 } => {
641 validate_type(return_type, index, tags, protocols, diagnostics);
642 for parameter in parameters {
643 validate_type(¶meter.ty, index, tags, protocols, diagnostics);
644 }
645 }
646 Type::ObjectiveCObject {
647 name,
648 protocols: used,
649 ..
650 } => {
651 let _ = name;
653 for protocol in used {
654 if !protocols.contains(protocol.as_str()) {
655 diagnostics.push(diagnostic(
656 HeaderValidationCode::ObjectiveCKindMismatch,
657 format!("Objective-C protocol `{protocol}` is not declared"),
658 index,
659 ));
660 }
661 }
662 }
663 }
664}
665
666fn validate_cycles(declarations: &[Decl], diagnostics: &mut Vec<HeaderValidationDiagnostic>) {
667 let graph = declarations
668 .iter()
669 .filter_map(|declaration| {
670 let Decl::Record { path, bases, .. } = declaration else {
671 return None;
672 };
673 let dependencies = bases
674 .iter()
675 .filter_map(|base| match &base.ty {
676 Type::Named { path, .. } => Some(path_string(path)),
677 _ => None,
678 })
679 .collect::<BTreeSet<_>>();
680 Some((path_string(path), dependencies))
681 })
682 .collect::<BTreeMap<_, _>>();
683 for (index, declaration) in declarations.iter().enumerate() {
684 let Decl::Record { path, .. } = declaration else {
685 continue;
686 };
687 let start = path_string(path);
688 if reaches(&graph, &start, &start, &mut BTreeSet::new()) {
689 diagnostics.push(diagnostic(
690 HeaderValidationCode::DependencyCycle,
691 format!("record dependency cycle includes `{start}`"),
692 index,
693 ));
694 }
695 }
696}
697
698fn reaches(
699 graph: &BTreeMap<String, BTreeSet<String>>,
700 current: &str,
701 target: &str,
702 seen: &mut BTreeSet<String>,
703) -> bool {
704 if !seen.insert(current.to_owned()) {
705 return false;
706 }
707 graph.get(current).is_some_and(|dependencies| {
708 dependencies
709 .iter()
710 .any(|dependency| dependency == target || reaches(graph, dependency, target, seen))
711 })
712}
713
714fn diagnostic(
715 code: HeaderValidationCode,
716 message: String,
717 index: usize,
718) -> HeaderValidationDiagnostic {
719 diagnostic_with_severity(code, Severity::Error, message, index)
720}
721
722fn diagnostic_with_severity(
723 code: HeaderValidationCode,
724 severity: Severity,
725 message: String,
726 index: usize,
727) -> HeaderValidationDiagnostic {
728 HeaderValidationDiagnostic {
729 code,
730 severity,
731 message,
732 declaration_index: u32::try_from(index).ok(),
733 }
734}
735
736fn path_string(path: &IdentifierPath) -> String {
737 path.components()
738 .iter()
739 .map(ToString::to_string)
740 .collect::<Vec<_>>()
741 .join("::")
742}
743
744#[cfg(test)]
745mod tests {
746 use crate::{
747 BuiltinType, CallingConvention, Decl, FunctionQualifiers, HeaderParser, Identifier,
748 IdentifierPath, Language, Linkage, MethodKind, ObjectiveCMethod, Parameter, ParameterState,
749 RecordKind, StorageClass, TranslationUnit, Type,
750 };
751
752 use super::*;
753
754 #[test]
755 fn duplicate_definitions_are_rejected() {
756 let declaration = Decl::Record {
757 kind: RecordKind::Struct,
758 path: IdentifierPath::new(vec![Identifier::new("Value").unwrap()]).unwrap(),
759 bases: Vec::new(),
760 fields: Vec::new(),
761 members: Vec::new(),
762 };
763 let report = validate(
764 &TranslationUnit {
765 language: Language::C,
766 declarations: vec![declaration.clone(), declaration],
767 declaration_spans: Vec::new(),
768 },
769 ValidationLimits::default(),
770 )
771 .unwrap();
772 assert!(!report.semantic_valid);
773 assert_eq!(
774 report.diagnostics[0].code,
775 HeaderValidationCode::DuplicateDeclaration
776 );
777 }
778
779 #[test]
780 fn compatible_function_redeclarations_are_accepted() {
781 let function = |parameter_name: &str, return_type| Decl::Function {
782 name: Identifier::new("transform").unwrap(),
783 signature: Type::Function {
784 return_type: Box::new(Type::Builtin(return_type)),
785 parameters: vec![Parameter {
786 name: Identifier::new(parameter_name).unwrap(),
787 ty: Type::Builtin(BuiltinType::Int),
788 }],
789 parameter_state: ParameterState::Known,
790 variadic: false,
791 calling_convention: CallingConvention::C,
792 qualifiers: FunctionQualifiers::default(),
793 },
794 storage: StorageClass::Extern,
795 linkage: Linkage::C,
796 };
797 let report = validate(
798 &TranslationUnit {
799 language: Language::C,
800 declarations: vec![
801 function("input", BuiltinType::Int),
802 function("value", BuiltinType::Int),
803 ],
804 declaration_spans: Vec::new(),
805 },
806 ValidationLimits::default(),
807 )
808 .unwrap();
809 assert!(report.semantic_valid);
810 assert_eq!(report.diagnostics[0].severity, Severity::Info);
811 }
812
813 #[test]
814 fn conflicting_function_redeclarations_are_rejected() {
815 let function = |return_type| Decl::Function {
816 name: Identifier::new("transform").unwrap(),
817 signature: Type::Function {
818 return_type: Box::new(Type::Builtin(return_type)),
819 parameters: Vec::new(),
820 parameter_state: ParameterState::Known,
821 variadic: false,
822 calling_convention: CallingConvention::C,
823 qualifiers: FunctionQualifiers::default(),
824 },
825 storage: StorageClass::Extern,
826 linkage: Linkage::C,
827 };
828 let report = validate(
829 &TranslationUnit {
830 language: Language::C,
831 declarations: vec![function(BuiltinType::Int), function(BuiltinType::Long)],
832 declaration_spans: Vec::new(),
833 },
834 ValidationLimits::default(),
835 )
836 .unwrap();
837 assert!(!report.semantic_valid);
838 assert_eq!(
839 report.diagnostics[0].code,
840 HeaderValidationCode::ConflictingRedeclaration
841 );
842 }
843
844 #[test]
845 fn selector_arity_is_checked() {
846 let report = validate(
847 &TranslationUnit {
848 language: Language::ObjectiveC,
849 declarations: vec![Decl::ObjectiveCProtocol {
850 name: Identifier::new("Widget").unwrap(),
851 protocols: Vec::new(),
852 methods: vec![ObjectiveCMethod {
853 kind: MethodKind::Instance,
854 selector: "setValue:".to_owned(),
855 return_type: Type::Builtin(BuiltinType::Void),
856 parameters: Vec::new(),
857 required: Some(true),
858 }],
859 properties: Vec::new(),
860 }],
861 declaration_spans: Vec::new(),
862 },
863 ValidationLimits::default(),
864 )
865 .unwrap();
866 assert_eq!(
867 report.diagnostics[0].code,
868 HeaderValidationCode::SelectorArityMismatch
869 );
870 }
871
872 #[test]
873 fn objc_class_forward_resolves_reparsed_named_pointer_types() {
874 let unit = crate::TreeSitterHeaderParser
875 .parse(
876 Language::ObjectiveC,
877 "@class NSString;\n@interface Widget\n@property NSString * name;\n@end\n",
878 )
879 .unwrap();
880 let report = validate(&unit, ValidationLimits::default()).unwrap();
881 assert!(report.semantic_valid, "{:?}", report.diagnostics);
882 }
883
884 #[test]
885 fn type_depth_is_bounded() {
886 let mut ty = Type::Builtin(BuiltinType::Int);
887 for _ in 0..65 {
888 ty = Type::Pointer {
889 pointee: Box::new(ty),
890 qualifiers: Default::default(),
891 };
892 }
893 let error = validate(
894 &TranslationUnit {
895 language: Language::C,
896 declarations: vec![Decl::Variable {
897 name: Identifier::new("value").unwrap(),
898 ty,
899 storage: StorageClass::None,
900 linkage: Linkage::C,
901 }],
902 declaration_spans: Vec::new(),
903 },
904 ValidationLimits::default(),
905 )
906 .unwrap_err();
907 assert!(matches!(error, ValidationError::TypeDepth { .. }));
908 }
909}