Skip to main content

chipi_core/bindings/
validate.rs

1//! Semantic validation for resolved bindings.
2//!
3//! Reports unknown decoder names. Reports handler-group instructions that
4//! don't exist. Reports IDA and Binja schema issues. Reports missing
5//! required fields.
6
7use std::collections::HashSet;
8
9use crate::error::{Error, ErrorKind, Span};
10use crate::types::{ValidatedDef, ValidatedSubDecoder};
11
12use super::resolve::ResolvedBindings;
13use super::types::*;
14
15pub fn validate(resolved: &ResolvedBindings) -> Result<(), Vec<Error>> {
16    let mut errors: Vec<Error> = Vec::new();
17
18    for target in &resolved.file.targets {
19        match target.kind {
20            TargetKind::Rust => {
21                for d in &target.rust_decoders {
22                    validate_decoder_ref(resolved, d, &mut errors);
23                }
24                for d in &target.rust_dispatches {
25                    validate_dispatch(resolved, d, None, &mut errors);
26                }
27            }
28            TargetKind::Cpp => {
29                for d in &target.cpp_decoders {
30                    validate_decoder_ref(resolved, d, &mut errors);
31                }
32            }
33            TargetKind::Ida => {
34                for p in &target.ida_processors {
35                    validate_processor(resolved, p, &mut errors);
36                }
37            }
38            TargetKind::Binja => {
39                for a in &target.binja_architectures {
40                    validate_architecture(resolved, a, &mut errors);
41                }
42            }
43        }
44    }
45
46    if errors.is_empty() {
47        Ok(())
48    } else {
49        Err(errors)
50    }
51}
52
53fn validate_decoder_ref(resolved: &ResolvedBindings, d: &DecoderBinding, errors: &mut Vec<Error>) {
54    if resolved.find_decoder_or_sub(&d.decoder_name).is_none() {
55        errors.push(Error::new(
56            ErrorKind::UnknownDecoderInBinding {
57                name: d.decoder_name.clone(),
58                suggestion: closest(&d.decoder_name, &resolved.all_decoder_names()),
59            },
60            d.span.clone(),
61        ));
62    }
63    for sd in &d.subdecoders {
64        if resolved.find_subdecoder(&sd.decoder_name).is_none() {
65            errors.push(Error::new(
66                ErrorKind::UnknownDecoderInBinding {
67                    name: sd.decoder_name.clone(),
68                    suggestion: closest(&sd.decoder_name, &resolved.all_decoder_names()),
69                },
70                sd.span.clone(),
71            ));
72        }
73    }
74}
75
76fn validate_dispatch(
77    resolved: &ResolvedBindings,
78    d: &DispatchBinding,
79    parent: Option<&DispatchBinding>,
80    errors: &mut Vec<Error>,
81) {
82    let target = resolved.find_decoder_or_sub(&d.decoder_name);
83    if target.is_none() {
84        errors.push(Error::new(
85            ErrorKind::UnknownDecoderInBinding {
86                name: d.decoder_name.clone(),
87                suggestion: closest(&d.decoder_name, &resolved.all_decoder_names()),
88            },
89            d.span.clone(),
90        ));
91    }
92
93    // Check required fields. invalid_handler may be inherited from parent.
94    let inherited_invalid = parent.and_then(|p| p.invalid_handler.as_ref());
95    if d.invalid_handler.is_none() && inherited_invalid.is_none() {
96        errors.push(Error::new(
97            ErrorKind::MissingInvalidHandler(d.decoder_name.clone()),
98            d.span.clone(),
99        ));
100    }
101
102    let inherited_handlers = parent.and_then(|p| p.handlers.as_ref());
103    if d.handlers.is_none() && inherited_handlers.is_none() {
104        errors.push(Error::new(
105            ErrorKind::MissingBindingsField {
106                block: format!("dispatch {}", d.decoder_name),
107                field: "handlers".to_string(),
108            },
109            d.span.clone(),
110        ));
111    }
112
113    let inherited_context = parent.and_then(|p| p.context.as_ref());
114    if d.context.is_none() && inherited_context.is_none() {
115        errors.push(Error::new(
116            ErrorKind::MissingBindingsField {
117                block: format!("dispatch {}", d.decoder_name),
118                field: "context".to_string(),
119            },
120            d.span.clone(),
121        ));
122    }
123
124    let inherited_strategy = parent.and_then(|p| p.strategy);
125    if d.strategy.is_none() && inherited_strategy.is_none() {
126        errors.push(Error::new(
127            ErrorKind::MissingBindingsField {
128                block: format!("dispatch {}", d.decoder_name),
129                field: "strategy".to_string(),
130            },
131            d.span.clone(),
132        ));
133    }
134
135    if d.output.is_none() && parent.is_none() {
136        // Top-level dispatch must have its own output. Subdispatches may
137        // share the parent's file (we'll reject only if they're declared
138        // top-level here).
139        errors.push(Error::new(
140            ErrorKind::MissingBindingsField {
141                block: format!("dispatch {}", d.decoder_name),
142                field: "output".to_string(),
143            },
144            d.span.clone(),
145        ));
146    }
147
148    // Validate handler groups against the resolved decoder's instructions.
149    if let Some((spec, sub)) = target {
150        let instr_names: Vec<String> = match sub {
151            Some(sd) => sd.instructions.iter().map(|i| i.name.clone()).collect(),
152            None => spec
153                .def
154                .instructions
155                .iter()
156                .map(|i| i.name.clone())
157                .collect(),
158        };
159        let known: HashSet<&str> = instr_names.iter().map(|s| s.as_str()).collect();
160        for group in &d.handler_groups {
161            for (name, span) in &group.instructions {
162                if !known.contains(name.as_str()) {
163                    errors.push(Error::new(
164                        ErrorKind::UnknownInstructionInGroup {
165                            instruction: name.clone(),
166                            suggestion: closest(name, &instr_names),
167                        },
168                        span.clone(),
169                    ));
170                }
171            }
172        }
173    }
174
175    // Recurse into subdispatches.
176    for sd in &d.subdispatches {
177        validate_dispatch(resolved, sd, Some(d), errors);
178    }
179}
180
181fn validate_processor(
182    resolved: &ResolvedBindings,
183    p: &IdaProcessorBinding,
184    errors: &mut Vec<Error>,
185) {
186    require(
187        &p.output,
188        "output",
189        "processor",
190        &p.decoder_name,
191        &p.span,
192        errors,
193    );
194    require(
195        &p.name,
196        "name",
197        "processor",
198        &p.decoder_name,
199        &p.span,
200        errors,
201    );
202    require(
203        &p.long_name,
204        "long_name",
205        "processor",
206        &p.decoder_name,
207        &p.span,
208        errors,
209    );
210    require_n(&p.id, "id", "processor", &p.decoder_name, &p.span, errors);
211    require_n(
212        &p.address_size,
213        "address_size",
214        "processor",
215        &p.decoder_name,
216        &p.span,
217        errors,
218    );
219    require_n(
220        &p.bytes_per_unit,
221        "bytes_per_unit",
222        "processor",
223        &p.decoder_name,
224        &p.span,
225        errors,
226    );
227
228    if p.registers.is_empty() {
229        errors.push(Error::new(
230            ErrorKind::MissingBindingsField {
231                block: format!("processor {}", p.decoder_name),
232                field: "registers".to_string(),
233            },
234            p.span.clone(),
235        ));
236    }
237
238    let target = resolved.find_decoder_or_sub(&p.decoder_name);
239    if target.is_none() {
240        errors.push(Error::new(
241            ErrorKind::UnknownDecoderInBinding {
242                name: p.decoder_name.clone(),
243                suggestion: closest(&p.decoder_name, &resolved.all_decoder_names()),
244            },
245            p.span.clone(),
246        ));
247    }
248
249    let regs: HashSet<&str> = p.registers.iter().map(|s| s.as_str()).collect();
250    for (sr, span) in &p.segment_registers {
251        if !regs.contains(sr.as_str()) {
252            errors.push(Error::new(
253                ErrorKind::SegmentRegisterNotDeclared(sr.clone()),
254                span.clone(),
255            ));
256        }
257    }
258
259    if let Some((spec, sub)) = target {
260        let instr_names: Vec<String> = match sub {
261            Some(sd) => sd.instructions.iter().map(|i| i.name.clone()).collect(),
262            None => spec
263                .def
264                .instructions
265                .iter()
266                .map(|i| i.name.clone())
267                .collect(),
268        };
269        let known: HashSet<&str> = instr_names.iter().map(|s| s.as_str()).collect();
270        for group in [&p.flow.calls, &p.flow.returns, &p.flow.stops] {
271            for (name, span) in group {
272                if !known.contains(name.as_str()) {
273                    errors.push(Error::new(
274                        ErrorKind::UnknownInstructionInFlow {
275                            instruction: name.clone(),
276                            suggestion: closest(name, &instr_names),
277                        },
278                        span.clone(),
279                    ));
280                }
281            }
282        }
283    }
284}
285
286fn validate_architecture(
287    resolved: &ResolvedBindings,
288    a: &BinjaArchitectureBinding,
289    errors: &mut Vec<Error>,
290) {
291    require(
292        &a.output,
293        "output",
294        "architecture",
295        &a.decoder_name,
296        &a.span,
297        errors,
298    );
299    require(
300        &a.name,
301        "name",
302        "architecture",
303        &a.decoder_name,
304        &a.span,
305        errors,
306    );
307    require_n(
308        &a.address_size,
309        "address_size",
310        "architecture",
311        &a.decoder_name,
312        &a.span,
313        errors,
314    );
315    require_n(
316        &a.default_int_size,
317        "default_int_size",
318        "architecture",
319        &a.decoder_name,
320        &a.span,
321        errors,
322    );
323
324    if let Some((value, span)) = &a.endianness {
325        if value != "big" && value != "little" {
326            errors.push(Error::new(
327                ErrorKind::InvalidEndianness(value.clone()),
328                span.clone(),
329            ));
330        }
331    } else {
332        errors.push(Error::new(
333            ErrorKind::MissingBindingsField {
334                block: format!("architecture {}", a.decoder_name),
335                field: "endianness".to_string(),
336            },
337            a.span.clone(),
338        ));
339    }
340
341    if a.registers.is_empty() {
342        errors.push(Error::new(
343            ErrorKind::MissingBindingsField {
344                block: format!("architecture {}", a.decoder_name),
345                field: "registers".to_string(),
346            },
347            a.span.clone(),
348        ));
349    }
350
351    if resolved.find_decoder_or_sub(&a.decoder_name).is_none() {
352        errors.push(Error::new(
353            ErrorKind::UnknownDecoderInBinding {
354                name: a.decoder_name.clone(),
355                suggestion: closest(&a.decoder_name, &resolved.all_decoder_names()),
356            },
357            a.span.clone(),
358        ));
359    }
360}
361
362fn require<T: AsRef<str>>(
363    value: &Option<T>,
364    field: &str,
365    block_kind: &str,
366    name: &str,
367    span: &Span,
368    errors: &mut Vec<Error>,
369) {
370    if value.is_none() {
371        errors.push(Error::new(
372            ErrorKind::MissingBindingsField {
373                block: format!("{} {}", block_kind, name),
374                field: field.to_string(),
375            },
376            span.clone(),
377        ));
378    }
379}
380
381fn require_n<T>(
382    value: &Option<T>,
383    field: &str,
384    block_kind: &str,
385    name: &str,
386    span: &Span,
387    errors: &mut Vec<Error>,
388) {
389    if value.is_none() {
390        errors.push(Error::new(
391            ErrorKind::MissingBindingsField {
392                block: format!("{} {}", block_kind, name),
393                field: field.to_string(),
394            },
395            span.clone(),
396        ));
397    }
398}
399
400/// Suppress unused warnings. `ValidatedDef` and `ValidatedSubDecoder` are
401/// brought in via the `target` lookup above.
402#[allow(dead_code)]
403fn _types_used(_: &ValidatedDef, _: &ValidatedSubDecoder) {}
404
405/// Find the candidate in `pool` closest to `target` by Levenshtein
406/// distance. Returns `None` if the closest match's distance exceeds
407/// `max(2, target.len() / 3)`.
408fn closest(target: &str, pool: &[String]) -> Option<String> {
409    let cap = (target.len() / 3).max(2);
410    let mut best: Option<(usize, String)> = None;
411    for cand in pool {
412        let d = levenshtein(target, cand);
413        if d > cap {
414            continue;
415        }
416        if best.as_ref().map(|(bd, _)| d < *bd).unwrap_or(true) {
417            best = Some((d, cand.clone()));
418        }
419    }
420    best.map(|(_, s)| s)
421}
422
423fn levenshtein(a: &str, b: &str) -> usize {
424    let a: Vec<char> = a.chars().collect();
425    let b: Vec<char> = b.chars().collect();
426    let mut prev: Vec<usize> = (0..=b.len()).collect();
427    let mut cur: Vec<usize> = vec![0; b.len() + 1];
428    for i in 1..=a.len() {
429        cur[0] = i;
430        for j in 1..=b.len() {
431            let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
432            cur[j] = (prev[j] + 1).min(cur[j - 1] + 1).min(prev[j - 1] + cost);
433        }
434        std::mem::swap(&mut prev, &mut cur);
435    }
436    prev[b.len()]
437}