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 wholeVecis 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 itsinto_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 siblinginto_sexp(after the same comma-free span would be missed. No such shape exists in the corpus.