@@ -12,9 +12,45 @@ pub(crate) struct RegLowerer<'a> {
1212 /// `==`/`!=` could compare a closure-containing operand. Shared across all
1313 /// function lowerings so it summarizes the whole program.
1414 pub ( crate ) closure_identity_observable : & ' a std:: cell:: Cell < bool > ,
15+ /// Registers proven to hold a `Copy` scalar (`Int`/`Bool`/`Float`/`Char`/…,
16+ /// per [`scalar_param_type_needs_no_deep_copy`]). Such a value is inline with
17+ /// no interior `Rc`, so extracting it from a collection/struct/variant cannot
18+ /// alias the source. The DeepCopy-elision taint analysis reads this set to
19+ /// avoid over-tainting an extraction's source collection (see
20+ /// [`deepcopy_elidable_param_regs`]). Conservative by construction: a register
21+ /// is only inserted when the extracted static type is a known scalar; absence
22+ /// means "unknown", which keeps the (sound) over-tainting behavior.
23+ pub ( crate ) scalar_regs : std:: collections:: HashSet < Reg > ,
24+ }
25+
26+ /// The element type produced by extracting from a collection type, or `None`
27+ /// when it cannot be determined (⇒ conservative: not marked scalar ⇒ copy kept).
28+ /// `List<T>`/`Deque<T>` → `T`; `Map<K, V>`/`SortedMap<K, V>` → `V` (the VALUE, the
29+ /// type `MapGet` yields). Any other root (or a missing generic arg) → `None`.
30+ fn list_elem_type ( collection_ty : & str ) -> Option < & str > {
31+ let root = crate :: text_util:: type_root_name ( collection_ty) ;
32+ let args = crate :: text_util:: type_arg_names ( collection_ty) ?;
33+ match root {
34+ "List" | "Deque" => args. first ( ) . copied ( ) ,
35+ "Map" | "SortedMap" => args. get ( 1 ) . copied ( ) ,
36+ _ => None ,
37+ }
1538}
1639
1740impl RegLowerer < ' _ > {
41+ /// Record that `dst` holds a `Copy` scalar when `elem_ty` is a known scalar
42+ /// type (`Int`/`Bool`/`Float`/`Char`/…). Extracting such a value is a bit-copy
43+ /// that cannot alias its source, so the elision taint analysis skips it. A
44+ /// `None` / non-scalar `elem_ty` (e.g. `String`, unknown) leaves `dst`
45+ /// unmarked — the sound, over-tainting default.
46+ fn note_scalar ( & mut self , dst : Reg , elem_ty : Option < & str > ) {
47+ if elem_ty. is_some_and ( |ty| {
48+ scalar_param_type_needs_no_deep_copy ( crate :: text_util:: strip_fresh_type ( ty) )
49+ } ) {
50+ self . scalar_regs . insert ( dst) ;
51+ }
52+ }
53+
1854 pub ( crate ) fn local ( & mut self , name : & str ) -> Reg {
1955 if let Some ( reg) = self . function . local_regs . get ( name) {
2056 return * reg;
@@ -502,6 +538,8 @@ impl RegLowerer<'_> {
502538 list,
503539 index,
504540 } ) ;
541+ let elem_ty = reg_expr_type_name ( iterable) . and_then ( list_elem_type) ;
542+ self . note_scalar ( item, elem_ty) ;
505543
506544 self . loop_stack . push ( LoopPatch {
507545 cleanup_base : self . cleanup_stack . len ( ) ,
@@ -850,13 +888,18 @@ impl RegLowerer<'_> {
850888 name : name. clone ( ) ,
851889 } ) ;
852890 }
891+ // `access.type_name` is the field's own static type — the value
892+ // this extraction yields — so it feeds `note_scalar` directly.
893+ self . note_scalar ( dst, access. type_name . as_deref ( ) ) ;
853894 Ok ( dst)
854895 }
855896 HirExpr :: Index { base, index, .. } => {
856897 let list = self . expr ( base) ?;
898+ let elem_ty = reg_expr_type_name ( base) . and_then ( list_elem_type) ;
857899 let index = self . expr ( index) ?;
858900 let dst = self . temp ( ) ;
859901 self . emit ( RegInstr :: ListGet { dst, list, index } ) ;
902+ self . note_scalar ( dst, elem_ty) ;
860903 Ok ( dst)
861904 }
862905 HirExpr :: Effect { value, .. } => self . expr ( value) ,
@@ -927,6 +970,7 @@ impl RegLowerer<'_> {
927970 loop_stack : Vec :: new ( ) ,
928971 cleanup_stack : Vec :: new ( ) ,
929972 closure_identity_observable : self . closure_identity_observable ,
973+ scalar_regs : std:: collections:: HashSet :: new ( ) ,
930974 } ;
931975 for capture in & capture_names {
932976 lowerer. local ( capture) ;
@@ -1244,6 +1288,11 @@ impl RegLowerer<'_> {
12441288 dst,
12451289 deque : arg_regs[ 0 ] ,
12461290 } ) ;
1291+ let elem_ty = args
1292+ . first ( )
1293+ . and_then ( |arg| reg_expr_type_name ( & arg. value ) )
1294+ . and_then ( list_elem_type) ;
1295+ self . note_scalar ( dst, elem_ty) ;
12471296 return Ok ( dst) ;
12481297 }
12491298 ( "Deque" , "pop_front" ) => {
@@ -1257,6 +1306,11 @@ impl RegLowerer<'_> {
12571306 dst,
12581307 deque : arg_regs[ 0 ] ,
12591308 } ) ;
1309+ let elem_ty = args
1310+ . first ( )
1311+ . and_then ( |arg| reg_expr_type_name ( & arg. value ) )
1312+ . and_then ( list_elem_type) ;
1313+ self . note_scalar ( dst, elem_ty) ;
12601314 return Ok ( dst) ;
12611315 }
12621316 ( "Deque" , "push_back" ) => {
@@ -1381,6 +1435,11 @@ impl RegLowerer<'_> {
13811435 list : arg_regs[ 0 ] ,
13821436 index : arg_regs[ 1 ] ,
13831437 } ) ;
1438+ let elem_ty = args
1439+ . first ( )
1440+ . and_then ( |arg| reg_expr_type_name ( & arg. value ) )
1441+ . and_then ( list_elem_type) ;
1442+ self . note_scalar ( dst, elem_ty) ;
13841443 return Ok ( dst) ;
13851444 }
13861445 ( "List" , "len" ) => {
@@ -1533,6 +1592,14 @@ impl RegLowerer<'_> {
15331592 map : arg_regs[ 0 ] ,
15341593 key : arg_regs[ 1 ] ,
15351594 } ) ;
1595+ // `list_elem_type` returns the Map VALUE type (`V` of
1596+ // `Map<K, V>`), which is what `MapGet` yields (as a
1597+ // fresh `Option<V>` of a cloned value).
1598+ let value_ty = args
1599+ . first ( )
1600+ . and_then ( |arg| reg_expr_type_name ( & arg. value ) )
1601+ . and_then ( list_elem_type) ;
1602+ self . note_scalar ( dst, value_ty) ;
15361603 return Ok ( dst) ;
15371604 }
15381605 ( "Map" , "insert" ) => {
0 commit comments