1- use rustc_abi:: { Scalar , Size , TagEncoding , Variants , WrappingRange } ;
1+ use rustc_abi:: { HasDataLayout , Scalar , Size , TagEncoding , Variants , WrappingRange } ;
22use rustc_hir:: LangItem ;
33use rustc_index:: IndexVec ;
44use rustc_middle:: bug;
5- use rustc_middle:: mir:: visit:: Visitor ;
5+ use rustc_middle:: mir:: visit:: { NonMutatingUseContext , PlaceContext , Visitor } ;
66use rustc_middle:: mir:: * ;
7- use rustc_middle:: ty:: layout:: PrimitiveExt ;
8- use rustc_middle:: ty:: { self , Ty , TyCtxt , TypingEnv } ;
7+ use rustc_middle:: ty:: layout:: { IntegerExt , PrimitiveExt } ;
8+ use rustc_middle:: ty:: { self , AdtDef , Ty , TyCtxt , TypingEnv } ;
99use rustc_session:: Session ;
1010use tracing:: debug;
1111
@@ -148,79 +148,201 @@ impl<'a, 'tcx> EnumFinder<'a, 'tcx> {
148148 fn into_found_enums ( self ) -> Vec < EnumCheckType < ' tcx > > {
149149 self . enums
150150 }
151+
152+ /// Registers a new enum check in the finder.
153+ fn register_new_check (
154+ & mut self ,
155+ enum_ty : Ty < ' tcx > ,
156+ enum_def : AdtDef < ' tcx > ,
157+ source_op : Operand < ' tcx > ,
158+ ) {
159+ let Ok ( enum_layout) = self . tcx . layout_of ( self . typing_env . as_query_input ( enum_ty) ) else {
160+ return ;
161+ } ;
162+ // If the operand is a pointer, we want to pass on the size of the operand to the check,
163+ // as we will dereference the pointer and look at the value directly.
164+ let Ok ( op_layout) = ( if let ty:: RawPtr ( pointee_ty, _) =
165+ source_op. ty ( self . local_decls , self . tcx ) . kind ( )
166+ {
167+ self . tcx . layout_of ( self . typing_env . as_query_input ( * pointee_ty) )
168+ } else {
169+ self . tcx
170+ . layout_of ( self . typing_env . as_query_input ( source_op. ty ( self . local_decls , self . tcx ) ) )
171+ } ) else {
172+ return ;
173+ } ;
174+
175+ match enum_layout. variants {
176+ Variants :: Empty if op_layout. is_uninhabited ( ) => return ,
177+ // An empty enum that tries to be constructed from an inhabited value, this
178+ // is never correct.
179+ Variants :: Empty => {
180+ // The enum layout is uninhabited but we construct it from sth inhabited.
181+ // This is always UB.
182+ self . enums . push ( EnumCheckType :: Uninhabited ) ;
183+ }
184+ // Construction of Single value enums is always fine.
185+ Variants :: Single { .. } => { }
186+ // Construction of an enum with multiple variants but no niche optimizations.
187+ Variants :: Multiple {
188+ tag_encoding : TagEncoding :: Direct ,
189+ tag : Scalar :: Initialized { value, .. } ,
190+ ..
191+ } => {
192+ let valid_discrs =
193+ enum_def. discriminants ( self . tcx ) . map ( |( _, discr) | discr. val ) . collect ( ) ;
194+
195+ let discr = TyAndSize { ty : value. to_ty ( self . tcx ) , size : value. size ( & self . tcx ) } ;
196+ self . enums . push ( EnumCheckType :: Direct {
197+ source_op : source_op. to_copy ( ) ,
198+ discr,
199+ op_size : op_layout. size ,
200+ valid_discrs,
201+ } ) ;
202+ }
203+ // Construction of an enum with multiple variants and niche optimizations.
204+ Variants :: Multiple {
205+ tag_encoding : TagEncoding :: Niche { .. } ,
206+ tag : Scalar :: Initialized { value, valid_range, .. } ,
207+ tag_field,
208+ ..
209+ } => {
210+ let discr = TyAndSize { ty : value. to_ty ( self . tcx ) , size : value. size ( & self . tcx ) } ;
211+ self . enums . push ( EnumCheckType :: WithNiche {
212+ source_op : source_op. to_copy ( ) ,
213+ discr,
214+ op_size : op_layout. size ,
215+ offset : enum_layout. fields . offset ( tag_field. as_usize ( ) ) ,
216+ valid_range,
217+ } ) ;
218+ }
219+ _ => return ,
220+ }
221+ }
151222}
152223
153224impl < ' a , ' tcx > Visitor < ' tcx > for EnumFinder < ' a , ' tcx > {
154- fn visit_rvalue ( & mut self , rvalue : & Rvalue < ' tcx > , location : Location ) {
155- if let Rvalue :: Cast ( CastKind :: Transmute , op, ty) = rvalue {
156- let ty:: Adt ( adt_def, _) = ty. kind ( ) else {
157- return ;
158- } ;
159- if !adt_def. is_enum ( ) {
225+ fn visit_place ( & mut self , place : & Place < ' tcx > , context : PlaceContext , location : Location ) {
226+ self . super_place ( place, context, location) ;
227+ // We only want to emit this check on pointer reads.
228+ match context {
229+ PlaceContext :: NonMutatingUse (
230+ NonMutatingUseContext :: Copy
231+ | NonMutatingUseContext :: Move
232+ | NonMutatingUseContext :: SharedBorrow ,
233+ ) => { }
234+ _ => {
160235 return ;
161236 }
237+ }
162238
163- let Ok ( enum_layout) = self . tcx . layout_of ( self . typing_env . as_query_input ( * ty) ) else {
239+ if !place. is_indirect ( ) {
240+ return ;
241+ }
242+ // Get the place and type we visit.
243+ let pointer_ty = place. ty ( self . local_decls , self . tcx ) . ty ;
244+
245+ // We only want to check places based on raw pointers to enums or ManuallyDrop<Enum>.
246+ let & ty:: RawPtr ( pointee_ty, _) = pointer_ty. kind ( ) else {
247+ return ;
248+ } ;
249+ let ty:: Adt ( enum_adt_def, _) = pointee_ty. kind ( ) else {
250+ return ;
251+ } ;
252+
253+ let ( enum_ty, enum_adt_def) = if enum_adt_def. is_enum ( ) {
254+ ( pointee_ty, enum_adt_def)
255+ } else if enum_adt_def. is_manually_drop ( ) {
256+ // Find the type contained in the ManuallyDrop and check whether it is an enum.
257+ let Some ( ( manual_drop_arg, adt_def) ) =
258+ pointee_ty. walk ( ) . skip ( 1 ) . next ( ) . map_or ( None , |arg| {
259+ if let Some ( ty) = arg. as_type ( )
260+ && let ty:: Adt ( adt_def, _) = ty. kind ( )
261+ {
262+ Some ( ( ty, adt_def) )
263+ } else {
264+ None
265+ }
266+ } )
267+ else {
164268 return ;
165269 } ;
166- let Ok ( op_layout) = self
167- . tcx
168- . layout_of ( self . typing_env . as_query_input ( op. ty ( self . local_decls , self . tcx ) ) )
270+
271+ ( manual_drop_arg, adt_def)
272+ } else {
273+ return ;
274+ } ;
275+ // Exclude c_void.
276+ if enum_ty. is_c_void ( self . tcx ) {
277+ return ;
278+ }
279+
280+ self . register_new_check ( enum_ty, * enum_adt_def, Operand :: Copy ( * place) ) ;
281+ }
282+
283+ fn visit_projection_elem (
284+ & mut self ,
285+ place_ref : PlaceRef < ' tcx > ,
286+ elem : PlaceElem < ' tcx > ,
287+ context : visit:: PlaceContext ,
288+ location : Location ,
289+ ) {
290+ self . super_projection_elem ( place_ref, elem, context, location) ;
291+ // Check whether we are reading an enum or a ManuallyDrop<Enum> from a union.
292+ let ty:: Adt ( union_adt_def, _) = place_ref. ty ( self . local_decls , self . tcx ) . ty . kind ( ) else {
293+ return ;
294+ } ;
295+ if !union_adt_def. is_union ( ) {
296+ return ;
297+ }
298+ let PlaceElem :: Field ( _, extracted_ty) = elem else {
299+ return ;
300+ } ;
301+ let ty:: Adt ( enum_adt_def, _) = extracted_ty. kind ( ) else {
302+ return ;
303+ } ;
304+ let ( enum_ty, enum_adt_def) = if enum_adt_def. is_enum ( ) {
305+ ( extracted_ty, enum_adt_def)
306+ } else if enum_adt_def. is_manually_drop ( ) {
307+ // Find the type contained in the ManuallyDrop and check whether it is an enum.
308+ let Some ( ( manual_drop_arg, adt_def) ) =
309+ extracted_ty. walk ( ) . skip ( 1 ) . next ( ) . map_or ( None , |arg| {
310+ if let Some ( ty) = arg. as_type ( )
311+ && let ty:: Adt ( adt_def, _) = ty. kind ( )
312+ {
313+ Some ( ( ty, adt_def) )
314+ } else {
315+ None
316+ }
317+ } )
169318 else {
170319 return ;
171320 } ;
172321
173- match enum_layout. variants {
174- Variants :: Empty if op_layout. is_uninhabited ( ) => return ,
175- // An empty enum that tries to be constructed from an inhabited value, this
176- // is never correct.
177- Variants :: Empty => {
178- // The enum layout is uninhabited but we construct it from sth inhabited.
179- // This is always UB.
180- self . enums . push ( EnumCheckType :: Uninhabited ) ;
181- }
182- // Construction of Single value enums is always fine.
183- Variants :: Single { .. } => { }
184- // Construction of an enum with multiple variants but no niche optimizations.
185- Variants :: Multiple {
186- tag_encoding : TagEncoding :: Direct ,
187- tag : Scalar :: Initialized { value, .. } ,
188- ..
189- } => {
190- let valid_discrs =
191- adt_def. discriminants ( self . tcx ) . map ( |( _, discr) | discr. val ) . collect ( ) ;
192-
193- let discr =
194- TyAndSize { ty : value. to_int_ty ( self . tcx ) , size : value. size ( & self . tcx ) } ;
195- self . enums . push ( EnumCheckType :: Direct {
196- source_op : op. to_copy ( ) ,
197- discr,
198- op_size : op_layout. size ,
199- valid_discrs,
200- } ) ;
201- }
202- // Construction of an enum with multiple variants and niche optimizations.
203- Variants :: Multiple {
204- tag_encoding : TagEncoding :: Niche { .. } ,
205- tag : Scalar :: Initialized { value, valid_range, .. } ,
206- tag_field,
207- ..
208- } => {
209- let discr =
210- TyAndSize { ty : value. to_int_ty ( self . tcx ) , size : value. size ( & self . tcx ) } ;
211- self . enums . push ( EnumCheckType :: WithNiche {
212- source_op : op. to_copy ( ) ,
213- discr,
214- op_size : op_layout. size ,
215- offset : enum_layout. fields . offset ( tag_field. as_usize ( ) ) ,
216- valid_range,
217- } ) ;
218- }
219- _ => return ,
322+ ( manual_drop_arg, adt_def)
323+ } else {
324+ return ;
325+ } ;
326+
327+ self . register_new_check (
328+ enum_ty,
329+ * enum_adt_def,
330+ Operand :: Copy ( place_ref. to_place ( self . tcx ) ) ,
331+ ) ;
332+ }
333+
334+ fn visit_rvalue ( & mut self , rvalue : & Rvalue < ' tcx > , location : Location ) {
335+ if let Rvalue :: Cast ( CastKind :: Transmute , op, ty) = rvalue {
336+ let ty:: Adt ( adt_def, _) = ty. kind ( ) else {
337+ return ;
338+ } ;
339+ if !adt_def. is_enum ( ) {
340+ return ;
220341 }
221342
222- self . super_rvalue ( rvalue , location ) ;
343+ self . register_new_check ( * ty , * adt_def , op . to_copy ( ) ) ;
223344 }
345+ self . super_rvalue ( rvalue, location) ;
224346 }
225347}
226348
@@ -246,7 +368,7 @@ fn insert_discr_cast_to_u128<'tcx>(
246368 local_decls : & mut IndexVec < Local , LocalDecl < ' tcx > > ,
247369 block_data : & mut BasicBlockData < ' tcx > ,
248370 source_op : Operand < ' tcx > ,
249- discr : TyAndSize < ' tcx > ,
371+ mut discr : TyAndSize < ' tcx > ,
250372 op_size : Size ,
251373 offset : Option < Size > ,
252374 source_info : SourceInfo ,
@@ -262,6 +384,29 @@ fn insert_discr_cast_to_u128<'tcx>(
262384 }
263385 } ;
264386
387+ // If the enum is behind a pointer, cast it to a *[const|mut] MaybeUninit<T> and then extract the discriminant through that.
388+ let source_op = if let ty:: RawPtr ( pointee_ty, mutbl) = source_op. ty ( local_decls, tcx) . kind ( )
389+ && !discr. ty . is_raw_ptr ( )
390+ {
391+ let mu_ptr_ty = Ty :: new_ptr ( tcx, Ty :: new_maybe_uninit ( tcx, * pointee_ty) , * mutbl) ;
392+ let mu_ptr_decl =
393+ local_decls. push ( LocalDecl :: with_source_info ( mu_ptr_ty, source_info) ) . into ( ) ;
394+ let rvalue = Rvalue :: Cast ( CastKind :: Transmute , source_op, mu_ptr_ty) ;
395+ block_data. statements . push ( Statement :: new (
396+ source_info,
397+ StatementKind :: Assign ( Box :: new ( ( mu_ptr_decl, rvalue) ) ) ,
398+ ) ) ;
399+
400+ Operand :: Copy ( mu_ptr_decl. project_deeper ( & [ ProjectionElem :: Deref ] , tcx) )
401+ } else {
402+ source_op
403+ } ;
404+
405+ // Correct the discriminant ty to an integer, to not screw up our casts to the discriminant ty.
406+ if discr. ty . is_raw_ptr ( ) {
407+ discr. ty = tcx. data_layout ( ) . ptr_sized_integer ( ) . to_ty ( tcx, false ) ;
408+ }
409+
265410 let ( cast_kind, discr_ty_bits) = if discr. size . bytes ( ) < op_size. bytes ( ) {
266411 // The discriminant is less wide than the operand, cast the operand into
267412 // [MaybeUninit; N] and then index into it.
@@ -335,7 +480,8 @@ fn insert_direct_enum_check<'tcx>(
335480 new_block : BasicBlock ,
336481) {
337482 // Insert a new target block that is branched to in case of an invalid discriminant.
338- let invalid_discr_block_data = BasicBlockData :: new ( None , false ) ;
483+ let invalid_discr_block_data =
484+ BasicBlockData :: new ( None , basic_blocks[ current_block] . is_cleanup ) ;
339485 let invalid_discr_block = basic_blocks. push ( invalid_discr_block_data) ;
340486 let block_data = & mut basic_blocks[ current_block] ;
341487 let discr_place = insert_discr_cast_to_u128 (
0 commit comments