Skip to main content

macho_header_syntax/
validate.rs

1//! Bounded semantic validation for typed headers.
2
3mod redeclaration;
4
5use std::collections::{BTreeMap, BTreeSet};
6
7use crate::{Decl, IdentifierPath, ObjectiveCForwardKind, TranslationUnit, Type};
8use redeclaration::{Redeclaration, declaration_identity, redeclaration};
9
10/// Stable semantic validation diagnostic code.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum HeaderValidationCode {
13    /// Concrete syntax was invalid.
14    SyntaxError,
15    /// The same declaration was repeated.
16    DuplicateDeclaration,
17    /// Declarations with the same identity disagree.
18    ConflictingRedeclaration,
19    /// A tagged type has no declaration.
20    UnresolvedType,
21    /// A declaration owner has no declaration.
22    UnresolvedOwner,
23    /// The declared linkage is incompatible with the language.
24    InvalidLinkage,
25    /// The storage class is incompatible with the declaration.
26    InvalidStorage,
27    /// The calling convention is incompatible with the language.
28    InvalidCallingConvention,
29    /// A template could not be represented completely.
30    IncompleteTemplateContext,
31    /// An Objective-C selector's colon count differs from its parameter count.
32    SelectorArityMismatch,
33    /// An Objective-C reference resolves to the wrong entity kind.
34    ObjectiveCKindMismatch,
35    /// The declaration dependency graph contains a cycle.
36    DependencyCycle,
37}
38
39/// Diagnostic severity.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum Severity {
42    /// Informational finding.
43    Info,
44    /// Recoverable weakness.
45    Warning,
46    /// Semantic validation failure.
47    Error,
48}
49
50/// One semantic validation diagnostic.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct HeaderValidationDiagnostic {
53    /// Stable diagnostic code.
54    pub code: HeaderValidationCode,
55    /// Severity.
56    pub severity: Severity,
57    /// Human-readable explanation.
58    pub message: String,
59    /// Top-level declaration index, when applicable.
60    pub declaration_index: Option<u32>,
61}
62
63/// Complete syntax and semantic validation result.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct HeaderValidationReport {
66    /// Whether concrete syntax parsing succeeded.
67    pub syntax_valid: bool,
68    /// Whether all semantic checks succeeded.
69    pub semantic_valid: bool,
70    /// Ordered diagnostics.
71    pub diagnostics: Vec<HeaderValidationDiagnostic>,
72}
73
74/// Hard resource bounds for recursive validation.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct ValidationLimits {
77    /// Maximum nested type depth.
78    pub max_type_depth: usize,
79    /// Maximum nested declaration depth.
80    pub max_declaration_depth: usize,
81    /// Maximum number of template arguments or members on one node.
82    pub max_items_per_node: usize,
83    /// Maximum total AST nodes.
84    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/// Typed validation limit failure.
99#[derive(Debug, thiserror::Error, PartialEq, Eq)]
100pub enum ValidationError {
101    /// Type nesting exceeded the configured bound.
102    #[error("type recursion depth {actual} exceeds limit {limit}")]
103    TypeDepth {
104        /// Configured maximum.
105        limit: usize,
106        /// Observed depth.
107        actual: usize,
108    },
109    /// Declaration nesting exceeded the configured bound.
110    #[error("declaration nesting depth {actual} exceeds limit {limit}")]
111    DeclarationDepth {
112        /// Configured maximum.
113        limit: usize,
114        /// Observed depth.
115        actual: usize,
116    },
117    /// One node contained too many children.
118    #[error("{kind} item count {actual} exceeds limit {limit}")]
119    ItemsPerNode {
120        /// Child collection name.
121        kind: &'static str,
122        /// Configured maximum.
123        limit: usize,
124        /// Observed count.
125        actual: usize,
126    },
127    /// The total syntax tree was too large.
128    #[error("AST node count exceeds limit {limit}")]
129    TotalNodes {
130        /// Configured maximum.
131        limit: usize,
132    },
133}
134
135/// Validates a parsed translation unit under the normative resource bounds.
136pub 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    // `id`, `Class`, and `SEL` are supplied by the Objective-C runtime rather
152    // than by a recovered image. They are valid in a standalone projected
153    // header without needing an SDK preamble.
154    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    // Tree-sitter represents Objective-C class pointer spellings such as
199    // `NSString *` as named types.  An `@class NSString;` declaration is the
200    // corresponding declaration authority, so make that namespace available
201    // to named-type validation after the complete declaration pass.
202    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(&parameter.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(&parameter.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(&parameter.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(&parameter.ty, index, tags, protocols, diagnostics);
644            }
645        }
646        Type::ObjectiveCObject {
647            name,
648            protocols: used,
649            ..
650        } => {
651            // Class names may originate in imported SDK headers and are opaque here.
652            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}