Skip to main content

scan_vec_into_sexp_calls

Function scan_vec_into_sexp_calls 

Source
fn scan_vec_into_sexp_calls(lines: &[&str], data: &mut FileData)
Expand description

Scan raw source text for into_sexp() / into_sexp_unchecked() calls that appear inside a vec! or &[...] literal — the use-after-free idiom (#307, #1025).

Each into_sexp allocates a new SEXP. When several appear as elements of one literal (vec![(k, a.into_sexp()), (k, b.into_sexp())]), building b can trigger a GC that collects the still-unprotected a (and vice versa), because nothing roots the earlier elements until the whole Vec is handed to List::from_raw_pairs. The fix is to route each element through the protected builder path (__scope.protect_raw(x.into_sexp()) with a ProtectScope), exactly as the IntoList / DataFrameRow derives now do.

§Heuristic and scope

This is a deliberately narrow raw-text scanner (consistent with MXL300/MXL301): it tracks bracket depth opened by a vec![ or &[ literal and flags an into_sexp( call only while that depth is open. This is what makes it precise:

  • Flags vec![ ... into_sexp() ... ] — the call is an element inside the literal.
  • Does NOT flag vec![1, 2, 3].into_sexp() — the ] closes the literal before the .into_sexp() call, so depth is back to 0 (the whole Vec is converted as one SEXP, which is safe — there are no sibling unprotected SEXPs).

The protected builder path is treated as a true negative: an into_sexp( whose element routes through a protect_raw(...) / protect(...) / protect_with_index(...) call (__scope.protect_raw(x.into_sexp())) is not flagged. This is exactly the form the IntoList / DataFrameRow derives emit, and the recommended hand-written fix.

Known limits (false negatives, by design — to keep zero false positives):

  • Only vec![ and &[ literal opens are tracked; bare [ ... ] array literals are not, because a leading [ is ambiguous with indexing (arr[i]). Array-literal sites are rare in this codebase; promote the scanner if one appears.
  • The protect-detection is per-element and line-local in spirit: an element whose protect(...)/protect_raw(...) wrapper sits on an earlier line than its into_sexp( is still recognised (the flag persists until the next element-separating ,), but a contrived element that opens a protect call yet smuggles an unprotected sibling into_sexp( after the same comma-free span would be missed. No such shape exists in the corpus.