Chapter 8.2. Verification Target Collection

When verification starts, RAPx scans the crate to identify verification targets — functions whose safety contracts need to be checked — and resolves the contracts for each target.

8.2.1 What Gets Verified

In scan mode (the default), a function is selected as a verification target if:

  • its body contains an unsafe { } block, or
  • it is a method on a struct with #[rapx::invariant] annotations.

For each target, the collector gathers:

  • Every call to an unsafe fn in the function body, along with the callee's resolved contracts.
  • Every raw pointer dereference (*ptr, *ptr = val) in unsafe blocks — these implicitly require ValidPtr + Align + Typed, as if those contracts were declared on a pseudo-callee.
  • Every static mut access — treated similarly.
  • Entry assumptions — the facts the verifier can assume when the function is entered:
    • The function's own #[rapx::requires] annotations.
    • If the function is a method on a struct with #[rapx::invariant], those invariants are included as entry facts.
    • Type-level invariants for function parameters (e.g. a &[T] parameter automatically supplies ValidPtr + NonNull + Allocated + InBound + Typed for the slice's data pointer).

8.2.2 How Contracts Are Resolved

For each unsafe callee, the verifier needs to know what safety properties it requires. Contracts are resolved in four tiers, tried in order:

  1. Direct annotation: #[rapx::requires(...)] on the callee itself. See Chapter 8.3.2.

  2. Trait method inheritance: If the callee is a trait method impl and has no direct annotation, its contracts are inherited from #[rapx::requires] on the trait's method declaration.

  3. JSON database: For standard library functions that can't be annotated upstream, contracts are loaded from a bundled database. See Chapter 8.3.2.

  4. Call-chain inheritance: If the unsafe callee still has no contracts, the verifier looks inside its MIR body for the unsafe functions it calls (its own unsafe callees). If any of those have resolved contracts, they are used as the verification target — the caller only needs to satisfy the contracts of the deepest callee in the chain. This follows a bounded depth. For example, if A calls B, B calls C, and C has #[rapx::requires], verifying the call to B checks A's state against C's contracts.

If all four tiers produce nothing, the callee's contracts are left as Unknown — the callsite cannot be verified.

8.2.3 Inspecting Targets with --prepare-targets

The --prepare-targets flag prints every verification target collected by the scanner — a function or method whose unsafe operations need to be checked — along with its resolved contracts:

cargo rapx verify --prepare-targets

For the linked_list_nonnull case study, the collector finds 13 targets. Output excerpt (annotations explained below):

23:35:35|RAPx|INFO|: Start analysis with RAPx.
23:35:35|RAPx|INFO|: ============================================================
23:35:35|RAPx|INFO|: [rapx::verify] prepare targets for struct: LinkedList
23:35:35|RAPx|INFO|: ============================================================
23:35:35|RAPx|INFO|:   struct invariants:                              ①
23:35:35|RAPx|INFO|:     - Align(head.unwrap_some(), Node<T>)
23:35:35|RAPx|INFO|:     - Allocated(head.unwrap_some(), Node<T>, 1)
23:35:35|RAPx|INFO|:     - Typed(head.unwrap_some(), Node<T>)
23:35:35|RAPx|INFO|:     - Owning(head.unwrap_some())
23:35:35|RAPx|INFO|:     - Align(tail.unwrap_some(), Node<T>)
23:35:35|RAPx|INFO|:     - Allocated(tail.unwrap_some(), Node<T>, 1)
23:35:35|RAPx|INFO|:     - Typed(tail.unwrap_some(), Node<T>)
23:35:35|RAPx|INFO|:     - Owning(tail.unwrap_some())
23:35:35|RAPx|INFO|:   --- method: new ------------------------------------------------- ②
23:35:35|RAPx|INFO|:       return checkpoints: 1 block(s) [0]             ③
23:35:35|RAPx|INFO|:       unsafe checkpoints: <none>
...
23:35:35|RAPx|INFO|:   --- method: drop ------------------------------------------------
23:35:35|RAPx|INFO|:       return checkpoints: 1 block(s) [7]
23:35:35|RAPx|INFO|:       unsafe callee: std::boxed::Box::<T>::from_raw(*mut T) -> std::boxed::Box<T>
23:35:35|RAPx|INFO|:         safety contracts:                           ④
23:35:35|RAPx|INFO|:           - Align(raw, T)
23:35:35|RAPx|INFO|:           - Allocated(raw, T, 1, global)
23:35:35|RAPx|INFO|:           - Typed(raw, T)
23:35:35|RAPx|INFO|:           - Owning(raw)
23:35:35|RAPx|INFO|:           - Alias(raw, ret)
23:35:35|RAPx|INFO|:         path: shortest path: 0 -> 1 -> 2 -> 3 -> 4  ⑤
23:35:35|RAPx|INFO|:       unsafe callee: std::ptr::NonNull::<T>::as_ref(&std::ptr::NonNull<T>) -> &'a T
23:35:35|RAPx|INFO|:         safety contracts:
23:35:35|RAPx|INFO|:           - Ptr2Ref(self.0, T)
23:35:35|RAPx|INFO|:         path: shortest path: 0 -> 1 -> 2
23:35:35|RAPx|INFO|: ============================================================
23:35:35|RAPx|INFO|: [rapx::verify] total: 0 free function(s), 13 method(s), 1 struct(s), 0 trait(s)  ⑥
#WhatDescription
Struct invariantsAll targets that receive the struct assume these hold on entry and must preserve them.
Target nameEach function or method being verified.
Return checkpointsBasic blocks where the function returns.
Safety contractsResolved contracts for each unsafe callee in the body.
PathShortest acyclic path from function entry to the callsite.
Summary lineSummary of targets to be verified.