Skip to main content

rapx/verify/contract/
def.rs

1//! User-defined contract `def` layer.
2//!
3//! Users (downloading a prebuilt `rapx` binary) can define *new* named safety
4//! contracts as boolean combinations of the 21 primitive safety properties, and
5//! reference them from `#[rapx::requires(MyTag(...))]` — without recompiling
6//! `rapx`.
7//!
8//! A `def` is a DNF macro over primitive property calls:
9//!
10//! ```text
11//! def MySafeRead(p: Target, T: Ty, n: Expr) =
12//!     NonNull(p) && Align(p, T) && Allocated(p, T, n);
13//!
14//! def StrOrBytes(s: Target, T: Ty, n: Expr) =
15//!     ValidCStr(s, n) || (Allocated(s, T, n) && Init(s, T, n));
16//! ```
17//!
18//! The DSL only *composes* existing primitives; it cannot invent new primitive
19//! semantics (those live in `property_checker.rs`).  Expansion is a pure
20//! front-end that produces ordinary `Property` values consumed by the existing
21//! checker.
22
23use std::collections::{HashMap, HashSet};
24use std::sync::{OnceLock, RwLock};
25
26use pest::iterators::Pair;
27use pest::Parser;
28use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
29use safety_parser::syn::visit_mut::{self, VisitMut};
30use safety_parser::syn::Expr;
31
32use super::pest_grammar::{ContractParser, Rule};
33use super::types::{Property, PropertyKind};
34
35/// A single argument in a `def` body: a reference to a formal parameter, or a
36/// literal (kept as source text, re-parsed as `syn::Expr` at expansion time).
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum DefArg {
39    Param(usize),
40    Lit(String),
41}
42
43/// The body of a `def`, structured as DNF (Or of And of calls).
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum DefBody {
46    And(Vec<DefBody>),
47    Or(Vec<DefBody>),
48    Call { tag: String, args: Vec<DefArg> },
49}
50
51/// A parsed `def` declaration.
52#[derive(Debug, Clone)]
53pub struct DefSpec {
54    pub name: String,
55    pub params: Vec<String>,
56    pub param_tys: Vec<String>,
57    pub body: DefBody,
58    pub doc: Vec<String>,
59}
60
61/// Parse a source fragment containing block-shaped contract definitions
62/// (`Name(params) { body }`) into a list of `DefSpec`s.
63///
64/// This is the format produced by the `pred!` macro and used by the bundled
65/// `assets/*-contracts.rs` files:
66///
67/// ```text
68/// MySafeRead(p: Ptr, T: Ty, n: Expr) { NonNull(p) && Align(p, T) && Allocated(p, T, n) }
69/// ```
70///
71/// Each def may be preceded by `///` doc lines (shown as the human-readable
72/// meaning in reports); `//` comments and blank lines are skipped.  The body
73/// supports `&&` (conjunction) and `||` (disjunction) of `Tag(arg, ...)` calls,
74/// plus `( ... )` grouping for a conjunction used as a single disjunct.
75pub fn parse_defs(source: &str) -> Vec<DefSpec> {
76    let mut defs = Vec::new();
77    let mut doc: Vec<String> = Vec::new();
78
79    let mut s = source.trim_start();
80    loop {
81        // Skip blank lines and `//` comments; collect `///` doc lines.
82        loop {
83            if let Some(r) = s.strip_prefix("///") {
84                let end = r.find('\n').unwrap_or(r.len());
85                doc.push(r[..end].trim().to_string());
86                s = r[end..].trim_start();
87            } else if let Some(r) = s.strip_prefix("//") {
88                let end = r.find('\n').unwrap_or(r.len());
89                s = r[end..].trim_start();
90            } else {
91                break;
92            }
93        }
94        if s.is_empty() {
95            break;
96        }
97
98        let (Some(mut def), consumed) = parse_one_def_block(s) else {
99            break;
100        };
101        def.doc = std::mem::take(&mut doc);
102        defs.push(def);
103        s = s[consumed..].trim_start();
104    }
105
106    defs
107}
108
109/// Parse a single leading `Name(p: Ptr, T: Ty, ...) { body }` block from `s`.
110/// Returns the `DefSpec` (without doc) and the number of bytes consumed through
111/// the closing `}`.
112///
113/// The block is emitted by the `pred!` proc-macro as a single line of
114/// space-separated tokens, so parameter names and role annotations (`Ptr`,
115/// `Ty`, `Expr`, `Ident`) are split on `:` and `,` after trimming whitespace.
116/// The braces delimit the body, so nested `==`/`<=` inside a `ValidNum`
117/// predicate cannot be confused with a definition separator.
118fn parse_one_def_block(s: &str) -> (Option<DefSpec>, usize) {
119    let Some(open) = s.find('(') else {
120        return (None, 0);
121    };
122    let name = s[..open].trim().to_string();
123    if name.is_empty() {
124        return (None, 0);
125    }
126
127    // Params: match the first `)` — parameter annotations are `ident: ident`,
128    // so they never contain nested parens.
129    let Some(rel_close) = s[open + 1..].find(')') else {
130        return (None, 0);
131    };
132    let close = open + 1 + rel_close;
133    let params_str = &s[open + 1..close];
134
135    // Expect a `{` after the parameter list.
136    let after = s[close + 1..].trim_start();
137    if !after.starts_with('{') {
138        return (None, 0);
139    }
140    let brace_open = s.len() - after.len();
141    let body_start = brace_open + 1;
142
143    // Match braces to the closing `}` (the body may contain `if { } else { }`).
144    let bytes = s.as_bytes();
145    let mut depth = 1usize;
146    let mut j = body_start;
147    while j < bytes.len() {
148        match bytes[j] {
149            b'{' => depth += 1,
150            b'}' => {
151                depth -= 1;
152                if depth == 0 {
153                    break;
154                }
155            }
156            _ => {}
157        }
158        j += 1;
159    }
160    if depth != 0 {
161        return (None, 0);
162    }
163    let body = s[body_start..j].trim();
164
165    let (params, param_tys) = parse_equation_params(params_str);
166    let Some(body_ast) = parse_body(body, &params) else {
167        return (None, 0);
168    };
169
170    let def = DefSpec {
171        name,
172        params,
173        param_tys,
174        body: body_ast,
175        doc: Vec::new(),
176    };
177    (Some(def), j + 1)
178}
179
180fn parse_equation_params(params_str: &str) -> (Vec<String>, Vec<String>) {
181    let mut params = Vec::new();
182    let mut param_tys = Vec::new();
183    for seg in params_str.split(',') {
184        let seg = seg.trim();
185        if seg.is_empty() {
186            continue;
187        }
188        match seg.split_once(':') {
189            Some((p, ty)) => {
190                params.push(p.trim().to_string());
191                param_tys.push(ty.trim().to_string());
192            }
193            None => {
194                params.push(seg.to_string());
195                param_tys.push(String::new());
196            }
197        }
198    }
199    (params, param_tys)
200}
201
202/// Render a `syn::Expr` back to source-like text.  `proc_macro2` stringifies
203/// tokens space-separated (`self . 0`, `size_of (T)`), so collapse the spaces
204/// around punctuation for a readable form.
205fn render_expr_src(e: &Expr) -> String {
206    quote::ToTokens::to_token_stream(e)
207        .to_string()
208        .replace(" . ", ".")
209        .replace(" ,", ",")
210        .replace(" (", "(")
211        .replace(" :: ", "::")
212}
213
214/// Resolve a call-site argument expression to its display form, following the
215/// def parameter's declared role so internal placeholders (e.g. `Arg_0` from a
216/// JSON contract) render as the actual parameter name.
217fn resolve_arg_string<'tcx>(
218    tcx: rustc_middle::ty::TyCtxt<'tcx>,
219    def_id: rustc_hir::def_id::DefId,
220    param_ty: &str,
221    expr: &Expr,
222) -> String {
223    match param_ty {
224        "Ptr" => super::resolve::parse_target_arg(tcx, def_id, expr)
225            .display_for_report(tcx, None, Some(def_id)),
226        "Ty" => super::resolve::parse_type(tcx, def_id, expr, "def")
227            .map(|ty| ty.to_string())
228            .unwrap_or_else(|| render_expr_src(expr)),
229        "Expr" => {
230            let ce = super::resolve::expr_to_pest(tcx, def_id, expr);
231            super::render::display_expr_user_friendly(&ce, tcx, None, Some(def_id))
232        }
233        _ => render_expr_src(expr),
234    }
235}
236
237/// Parse the body into a DNF tree.  `||` binds looser than `&&`.
238fn parse_body(body: &str, params: &[String]) -> Option<DefBody> {
239    let mut pairs = ContractParser::parse(Rule::def_body, body).ok()?;
240    let def_body = pairs.next()?;
241    let or_expr = def_body.into_inner().next()?;
242    Some(conv_def_or(or_expr, params))
243}
244
245fn conv_def_or(pair: Pair<Rule>, params: &[String]) -> DefBody {
246    let parts: Vec<DefBody> = pair.into_inner().map(|p| conv_def_and(p, params)).collect();
247    if parts.len() == 1 {
248        parts.into_iter().next().unwrap()
249    } else {
250        DefBody::Or(parts)
251    }
252}
253
254fn conv_def_and(pair: Pair<Rule>, params: &[String]) -> DefBody {
255    let parts: Vec<DefBody> = pair.into_inner().map(|p| conv_def_leaf(p, params)).collect();
256    if parts.len() == 1 {
257        parts.into_iter().next().unwrap()
258    } else {
259        DefBody::And(parts)
260    }
261}
262
263fn conv_def_leaf(pair: Pair<Rule>, params: &[String]) -> DefBody {
264    match pair.into_inner().next() {
265        Some(inner) => match inner.as_rule() {
266            Rule::tag_call => conv_def_call(inner, params),
267            Rule::or_expr => conv_def_or(inner, params),
268            _ => DefBody::Call {
269                tag: String::new(),
270                args: Vec::new(),
271            },
272        },
273        None => DefBody::Call {
274            tag: String::new(),
275            args: Vec::new(),
276        },
277    }
278}
279
280fn conv_def_call(pair: Pair<Rule>, params: &[String]) -> DefBody {
281    let mut inner = pair.into_inner();
282    let Some(tag) = inner.next() else {
283        return DefBody::Call {
284            tag: String::new(),
285            args: Vec::new(),
286        };
287    };
288    let tag = tag.as_str().to_string();
289    let args = match inner.next() {
290        Some(arg_list) => arg_list
291            .into_inner()
292            .map(|arg| {
293                let text = arg.as_str().trim().to_string();
294                match params.iter().position(|n| n == &text) {
295                    Some(i) => DefArg::Param(i),
296                    None => DefArg::Lit(text),
297                }
298            })
299            .collect(),
300        None => Vec::new(),
301    };
302    DefBody::Call { tag, args }
303}
304
305/// Substitute def formal parameters with the concrete call-site arguments inside
306/// a literal expression (e.g. turn `size_of(T) * n` into `size_of(u32) * len`,
307/// or `p.unwrap_some()` into `head.unwrap_some()`).
308///
309/// Only a bare single-segment path that exactly matches a formal parameter name
310/// is replaced, so builtin function names (`size_of`), method names
311/// (`unwrap_some`) and field names are never rewritten.
312struct Subst<'a> {
313    params: &'a [String],
314    args: &'a [Expr],
315}
316
317impl VisitMut for Subst<'_> {
318    fn visit_expr_mut(&mut self, node: &mut Expr) {
319        if let Expr::Path(path) = node {
320            if path.qself.is_none()
321                && path.path.leading_colon.is_none()
322                && path.path.segments.len() == 1
323            {
324                let ident = path.path.segments[0].ident.to_string();
325                if let Some(i) = self.params.iter().position(|n| *n == ident) {
326                    if let Some(arg) = self.args.get(i) {
327                        // The substituted argument comes from the call site and
328                        // never refers to this def's formals, so stop recursing.
329                        *node = arg.clone();
330                        return;
331                    }
332                }
333            }
334        }
335        visit_mut::visit_expr_mut(self, node);
336    }
337}
338
339/// Whether a def parameter annotation matches a primitive argument role.
340fn def_ty_matches_arg_kind(def_ty: &str, kind: super::spec::ArgKind) -> bool {
341    use super::spec::ArgKind;
342    match (def_ty, kind) {
343        ("Ptr", ArgKind::Target) => true,
344        ("Ty", ArgKind::Ty) => true,
345        ("Expr", ArgKind::Expr) => true,
346        ("Ident", ArgKind::Ident) => true,
347        _ => false,
348    }
349}
350
351/// Expand a `DefBody` into the property list it denotes.
352///
353/// `and` produces multiple `Property` values (the caller's `requires` list is
354/// already a conjunction); `or` produces a single `Property::Or` property
355/// whose `groups` encode the DNF groups.
356fn expand_body<'tcx>(
357    tcx: rustc_middle::ty::TyCtxt<'tcx>,
358    def_id: rustc_hir::def_id::DefId,
359    body: &DefBody,
360    exprs: &[Expr],
361    params: &[String],
362    param_tys: &[String],
363) -> Vec<Property<'tcx>> {
364    match body {
365        DefBody::And(parts) => parts
366            .iter()
367            .flat_map(|p| expand_body(tcx, def_id, p, exprs, params, param_tys))
368            .collect(),
369        DefBody::Or(parts) => {
370            let mut groups: Vec<Vec<Box<Property<'tcx>>>> = Vec::new();
371            for part in parts {
372                let group: Vec<Box<Property<'tcx>>> =
373                    expand_body(tcx, def_id, part, exprs, params, param_tys)
374                        .into_iter()
375                        .map(Box::new)
376                        .collect();
377                if !group.is_empty() {
378                    groups.push(group);
379                }
380            }
381            vec![Property::new_or(groups)]
382        }
383        DefBody::Call { tag, args } => {
384            // Validate the def's parameter annotations against the primitive's
385            // declared argument roles (e.g. a `Ptr` param used in a `Ty` slot).
386            if let Some(spec) = super::spec::find_spec(tag) {
387                match spec.build {
388                    // Variadic target list (Alias/Alive): every param must be Ptr.
389                    super::spec::BuildKind::Targets => {
390                        for (pos, a) in args.iter().enumerate() {
391                            if let DefArg::Param(i) = a
392                                && let Some(def_ty) = param_tys.get(*i)
393                                && !def_ty_matches_arg_kind(
394                                    def_ty,
395                                    super::spec::ArgKind::Target,
396                                )
397                            {
398                                let pname = params.get(*i).map(String::as_str).unwrap_or("?");
399                                rap_warn!(
400                                    "contract def type mismatch: `{tag}` arg {pos} expects \
401                                     {:?}, but param `{pname}` is annotated `{def_ty}`",
402                                    super::spec::ArgKind::Target
403                                );
404                            }
405                        }
406                    }
407                    // Accepts-anything placeholder: no constraints.
408                    super::spec::BuildKind::TobeSpecified => {}
409                    // Fixed-arity tag: match a form by call arity, then check each
410                    // positional parameter annotation against the declared role.
411                    _ => {
412                        if let Some(form) = spec.forms.iter().find(|f| f.len() == args.len()) {
413                            for (pos, a) in args.iter().enumerate() {
414                                if let DefArg::Param(i) = a
415                                    && let (Some(def_ty), Some(&arg_kind)) =
416                                        (param_tys.get(*i), form.get(pos))
417                                    && !def_ty_matches_arg_kind(def_ty, arg_kind)
418                                {
419                                    let pname = params.get(*i).map(String::as_str).unwrap_or("?");
420                                    rap_warn!(
421                                        "contract def type mismatch: `{tag}` arg {pos} expects \
422                                         {:?}, but param `{pname}` is annotated `{def_ty}`",
423                                        arg_kind
424                                    );
425                                }
426                            }
427                        }
428                    }
429                }
430            }
431
432            let mut resolved: Vec<Expr> = Vec::with_capacity(args.len());
433            for a in args {
434                match a {
435                    DefArg::Param(i) => {
436                        let Some(e) = exprs.get(*i) else {
437                            return vec![unknown_property()];
438                        };
439                        resolved.push(e.clone());
440                    }
441                    DefArg::Lit(s) => {
442                        let Ok(mut e) = syn::parse_str::<Expr>(s) else {
443                            return vec![unknown_property()];
444                        };
445                        Subst { params, args: exprs }.visit_expr_mut(&mut e);
446                        resolved.push(e);
447                    }
448                }
449            }
450            // Recurse through the normal property parser so nested defs and
451            // primitives are handled uniformly.
452            Property::parse_list(tcx, def_id, tag, &resolved)
453        }
454    }
455}
456
457fn unknown_property<'tcx>() -> Property<'tcx> {
458    Property::new_leaf(PropertyKind::Unknown, Vec::new())
459}
460
461// ── Registry ───────────────────────────────────────────────────
462
463/// Builtin defs shipped with `rapx`: the standard compound safety properties
464/// (`std-contracts.rs`) plus user extensions (`user-contracts.rs`).  Immutable
465/// and shared across every crate.
466fn builtin_defs_map() -> &'static HashMap<String, DefSpec> {
467    static BUILTIN: OnceLock<HashMap<String, DefSpec>> = OnceLock::new();
468    BUILTIN.get_or_init(builtin_defs)
469}
470
471/// Per-crate user defs, registered from `#[rapx::def_contract]` attributes.
472///
473/// Keyed by `CrateNum` so that defs defined in one crate cannot leak into (or
474/// collide with) another crate analyzed in the same process.  A crate's own
475/// defs shadow builtin defs of the same name.
476fn user_defs_map() -> &'static RwLock<HashMap<CrateNum, HashMap<String, DefSpec>>> {
477    static USER: OnceLock<RwLock<HashMap<CrateNum, HashMap<String, DefSpec>>>> = OnceLock::new();
478    USER.get_or_init(|| RwLock::new(HashMap::new()))
479}
480
481/// Builtin defs shipped with `rapx`: the standard compound safety properties
482/// (`std-contracts.rs`) plus user extensions (`user-contracts.rs`).
483fn builtin_defs() -> HashMap<String, DefSpec> {
484    let mut map = HashMap::new();
485    for def in parse_defs(include_str!("assets/std-contracts.rs")) {
486        map.insert(def.name.clone(), def);
487    }
488    for def in parse_defs(include_str!("assets/user-contracts.rs")) {
489        map.insert(def.name.clone(), def);
490    }
491    map
492}
493
494/// Look up a `def` by name, in the given crate's namespace first, then the
495/// builtin namespace.
496pub fn find_def(krate: CrateNum, name: &str) -> Option<DefSpec> {
497    if let Some(d) = user_defs_map()
498        .read()
499        .ok()
500        .and_then(|t| t.get(&krate).and_then(|m| m.get(name).cloned()))
501    {
502        return Some(d);
503    }
504    builtin_defs_map().get(name).cloned()
505}
506
507/// Expand a named def against concrete argument expressions.
508pub fn expand_def<'tcx>(
509    tcx: rustc_middle::ty::TyCtxt<'tcx>,
510    def_id: rustc_hir::def_id::DefId,
511    name: &str,
512    exprs: &[Expr],
513) -> Option<Vec<Property<'tcx>>> {
514    let def = find_def(def_id.krate, name)?;
515    if def.params.len() != exprs.len() {
516        return None;
517    }
518    // Guard against self-referential defs (direct or mutual) before recursing;
519    // otherwise `expand_body` → `Property::parse_list` → `expand_def` would
520    // recurse forever and overflow the stack.
521    if let Some(cycle) = find_def_cycle(def_id.krate, name) {
522        rap_error!("contract def cycle detected: {}", cycle.join(" -> "));
523        return None;
524    }
525    let mut props = expand_body(tcx, def_id, &def.body, exprs, &def.params, &def.param_tys);
526    // Tag expanded properties with the def name, its full call-site arguments,
527    // and its doc-derived meaning so reports can show the compound as a single
528    // entry instead of the underlying primitives.
529    let arg_strings: Vec<String> = exprs
530        .iter()
531        .enumerate()
532        .map(|(i, e)| {
533            let param_ty = def.param_tys.get(i).map(|s| s.as_str()).unwrap_or("");
534            resolve_arg_string(tcx, def_id, param_ty, e)
535        })
536        .collect();
537    let meaning = if def.doc.is_empty() {
538        None
539    } else {
540        Some(def.doc.join(" "))
541    };
542    for p in &mut props {
543        p.set_origin(name.to_string(), arg_strings.clone(), meaning.clone());
544    }
545    Some(props)
546}
547
548/// Return a cycle path (e.g. `["A", "B", "A"]`) if expanding `start` can reach
549/// itself again through def-to-def references, `None` otherwise.
550///
551/// Only edges that resolve to another registered def are followed; calls to
552/// primitives (`Allocated`, `Align`, ...) terminate the walk.  The crate's own
553/// defs are overlaid on the builtin namespace.
554pub fn find_def_cycle(krate: CrateNum, start: &str) -> Option<Vec<String>> {
555    let mut combined = builtin_defs_map().clone();
556    if let Ok(user) = user_defs_map().read()
557        && let Some(crate_defs) = user.get(&krate)
558    {
559        for (name, def) in crate_defs {
560            combined.insert(name.clone(), def.clone());
561        }
562    }
563    find_cycle_in(start, &combined)
564}
565
566fn find_cycle_in(start: &str, table: &HashMap<String, DefSpec>) -> Option<Vec<String>> {
567    fn dfs(
568        name: &str,
569        table: &HashMap<String, DefSpec>,
570        path: &mut Vec<String>,
571        done: &mut HashSet<String>,
572    ) -> Option<Vec<String>> {
573        if let Some(pos) = path.iter().position(|n| n == name) {
574            let mut cycle: Vec<String> = path[pos..].to_vec();
575            cycle.push(name.to_string());
576            return Some(cycle);
577        }
578        if done.contains(name) {
579            return None;
580        }
581        let Some(def) = table.get(name) else {
582            return None;
583        };
584        path.push(name.to_string());
585        for tag in def_refs(&def.body) {
586            if let Some(cycle) = dfs(&tag, table, path, done) {
587                return Some(cycle);
588            }
589        }
590        path.pop();
591        done.insert(name.to_string());
592        None
593    }
594
595    let mut path = Vec::new();
596    let mut done = HashSet::new();
597    dfs(start, table, &mut path, &mut done)
598}
599
600/// Collect the tag names referenced by a `DefBody`, in left-to-right order.
601fn def_refs(body: &DefBody) -> Vec<String> {
602    let mut out = Vec::new();
603    collect_def_refs(body, &mut out);
604    out
605}
606
607fn collect_def_refs(body: &DefBody, out: &mut Vec<String>) {
608    match body {
609        DefBody::And(parts) | DefBody::Or(parts) => {
610            for part in parts {
611                collect_def_refs(part, out);
612            }
613        }
614        DefBody::Call { tag, .. } => out.push(tag.clone()),
615    }
616}
617
618// ── Registration of user-defined defs ─────────────────────────
619
620/// Parse `def` declarations from `source` and insert them into the given
621/// crate's namespace.  Returns the number of defs registered.
622pub fn register_defs_from_source(krate: CrateNum, source: &str) -> usize {
623    let defs = parse_defs(source);
624    let n = defs.len();
625    if n == 0 {
626        return 0;
627    }
628    let mut table = user_defs_map().write().expect("def table poisoned");
629    let entry = table.entry(krate).or_default();
630    for def in defs {
631        entry.insert(def.name.clone(), def);
632    }
633    n
634}
635
636// ── Procedural-macro contract definitions ─────────────────────
637
638/// Scan the local crate for `#[rapx::def_contract("...")]` tool attributes
639/// (emitted by the `rapx_macros::pred` proc-macro) and register each embedded
640/// `def` string.  Returns the number of defs registered.
641pub fn register_contract_defs(tcx: rustc_middle::ty::TyCtxt<'_>) -> usize {
642    struct Visitor<'tcx> {
643        tcx: rustc_middle::ty::TyCtxt<'tcx>,
644        count: usize,
645    }
646
647    impl<'tcx> rustc_hir::intravisit::Visitor<'tcx> for Visitor<'tcx> {
648        fn visit_item(&mut self, item: &'tcx rustc_hir::Item<'tcx>) {
649            let attrs = self.tcx.hir_attrs(item.hir_id());
650            for attr in attrs {
651                if !is_contract_def_attr(attr) {
652                    continue;
653                }
654                let attr_str = crate::compat::attribute_to_string(self.tcx, attr);
655                if let Some(def_str) = extract_contract_def_string(&attr_str) {
656                    let n = register_defs_from_source(LOCAL_CRATE, &def_str);
657                    if n > 0 {
658                        rap_info!("rapx: registered {n} contract def(s) from #[rapx::def_contract]");
659                    }
660                    self.count += n;
661                }
662            }
663            rustc_hir::intravisit::walk_item(self, item);
664        }
665    }
666
667    let mut v = Visitor { tcx, count: 0 };
668    tcx.hir_visit_all_item_likes_in_crate(&mut v);
669    v.count
670}
671
672/// Whether an attribute path is `rapx::def_contract` (or the bare form with the
673/// tool prefix stripped).
674fn is_contract_def_attr(attr: &rustc_hir::Attribute) -> bool {
675    let path = attr.path();
676    if path.len() >= 2
677        && path[path.len() - 2].as_str() == "rapx"
678        && path[path.len() - 1].as_str() == "def_contract"
679    {
680        return true;
681    }
682    path.len() == 1 && path[0].as_str() == "def_contract"
683}
684
685/// Extract the string literal from a `#[rapx::def_contract("def ...")]`
686/// attribute's textual representation.
687fn extract_contract_def_string(attr_str: &str) -> Option<String> {
688    struct OneAttr {
689        attr: syn::Attribute,
690    }
691    impl syn::parse::Parse for OneAttr {
692        fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
693            let attrs = syn::Attribute::parse_outer(input)?;
694            let attr = attrs
695                .into_iter()
696                .next()
697                .ok_or_else(|| input.error("expected one attribute"))?;
698            Ok(OneAttr { attr })
699        }
700    }
701
702    let one: OneAttr = syn::parse_str(attr_str).ok()?;
703    let syn::Meta::List(list) = one.attr.meta else {
704        return None;
705    };
706    let lit: syn::LitStr = syn::parse2(list.tokens).ok()?;
707    Some(lit.value())
708}
709