1use rustc_abi::{Scalar, Size, TagEncoding, Variants, WrappingRange};
2use rustc_data_structures::thin_vec::ThinVec;
3use rustc_hir::attrs::lang_items::LangItem;
4use rustc_index::IndexVec;
5use rustc_middle::bug;
6use rustc_middle::mir::visit::Visitor;
7use rustc_middle::mir::*;
8use rustc_middle::ty::layout::PrimitiveExt;
9use rustc_middle::ty::{self, Ty, TyCtxt, TypingEnv};
10use rustc_session::Session;
11use tracing::debug;
12
13use crate::PassPolicy;
14
15pub(super) struct CheckEnums;
19
20impl<'tcx> crate::MirPass<'tcx> for CheckEnums {
21 fn policy(&self, sess: &Session) -> PassPolicy {
22 PassPolicy::optional_non_optimization(sess.ub_checks())
24 }
25
26 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
27 if tcx.lang_items().get(LangItem::PanicImpl).is_none() {
30 return;
31 }
32
33 let typing_env = body.typing_env(tcx);
34 let basic_blocks = body.basic_blocks.as_mut();
35 let local_decls = &mut body.local_decls;
36
37 for block in basic_blocks.indices().rev() {
42 for statement_index in (0..basic_blocks[block].statements.len()).rev() {
43 let location = Location { block, statement_index };
44 let statement = &basic_blocks[block].statements[statement_index];
45 let source_info = statement.source_info;
46
47 let mut finder = EnumFinder::new(tcx, local_decls, typing_env);
48 finder.visit_statement(statement, location);
49
50 for check in finder.into_found_enums() {
51 debug!("Inserting enum check");
52 let new_block = split_block(basic_blocks, location);
53
54 match check {
55 EnumCheckType::Direct { op_size, .. }
56 | EnumCheckType::WithNiche { op_size, .. }
57 if op_size.bytes() == 0 =>
58 {
59 tcx.dcx().span_delayed_bug(
62 source_info.span,
63 "cannot build enum discriminant from zero-sized type",
64 );
65 basic_blocks[block].terminator = Some(Terminator {
66 source_info,
67 kind: TerminatorKind::Goto { target: new_block },
68 attributes: ThinVec::new(),
69 });
70 }
71 EnumCheckType::Direct { source_op, discr, op_size, valid_discrs } => {
72 insert_direct_enum_check(
73 tcx,
74 local_decls,
75 basic_blocks,
76 block,
77 source_op,
78 discr,
79 op_size,
80 valid_discrs,
81 source_info,
82 new_block,
83 )
84 }
85 EnumCheckType::Uninhabited => insert_uninhabited_enum_check(
86 tcx,
87 local_decls,
88 &mut basic_blocks[block],
89 source_info,
90 new_block,
91 ),
92 EnumCheckType::WithNiche {
93 source_op,
94 discr,
95 op_size,
96 offset,
97 valid_range,
98 } => insert_niche_check(
99 tcx,
100 local_decls,
101 &mut basic_blocks[block],
102 source_op,
103 valid_range,
104 discr,
105 op_size,
106 offset,
107 source_info,
108 new_block,
109 ),
110 }
111 }
112 }
113 }
114 }
115}
116
117enum EnumCheckType<'tcx> {
119 Uninhabited,
121 Direct {
124 source_op: Operand<'tcx>,
125 discr: TyAndSize<'tcx>,
126 op_size: Size,
127 valid_discrs: Vec<u128>,
128 },
129 WithNiche {
131 source_op: Operand<'tcx>,
132 discr: TyAndSize<'tcx>,
133 op_size: Size,
134 offset: Size,
135 valid_range: WrappingRange,
136 },
137}
138
139#[derive(Debug, Copy, Clone)]
140struct TyAndSize<'tcx> {
141 pub ty: Ty<'tcx>,
142 pub size: Size,
143}
144
145struct EnumFinder<'a, 'tcx> {
148 tcx: TyCtxt<'tcx>,
149 local_decls: &'a mut LocalDecls<'tcx>,
150 typing_env: TypingEnv<'tcx>,
151 enums: Vec<EnumCheckType<'tcx>>,
152}
153
154impl<'a, 'tcx> EnumFinder<'a, 'tcx> {
155 fn new(
156 tcx: TyCtxt<'tcx>,
157 local_decls: &'a mut LocalDecls<'tcx>,
158 typing_env: TypingEnv<'tcx>,
159 ) -> Self {
160 EnumFinder { tcx, local_decls, typing_env, enums: Vec::new() }
161 }
162
163 fn into_found_enums(self) -> Vec<EnumCheckType<'tcx>> {
165 self.enums
166 }
167}
168
169impl<'a, 'tcx> Visitor<'tcx> for EnumFinder<'a, 'tcx> {
170 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
171 if let Rvalue::Cast(CastKind::Transmute, op, ty) = rvalue {
172 let ty::Adt(adt_def, _) = ty.kind() else {
173 return;
174 };
175 if !adt_def.is_enum() {
176 return;
177 }
178
179 let Ok(enum_layout) = self.tcx.layout_of(self.typing_env.as_query_input(*ty)) else {
180 return;
181 };
182 let Ok(op_layout) = self
183 .tcx
184 .layout_of(self.typing_env.as_query_input(op.ty(self.local_decls, self.tcx)))
185 else {
186 return;
187 };
188
189 match enum_layout.variants {
190 Variants::Empty if op_layout.is_uninhabited() => return,
191 Variants::Empty => {
194 self.enums.push(EnumCheckType::Uninhabited);
197 }
198 Variants::Single { .. } => {}
200 Variants::Multiple {
202 tag_encoding: TagEncoding::Direct,
203 tag: Scalar::Initialized { value, .. },
204 ..
205 } => {
206 let valid_discrs =
207 adt_def.discriminants(self.tcx).map(|(_, discr)| discr.val).collect();
208
209 let discr =
210 TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) };
211 self.enums.push(EnumCheckType::Direct {
212 source_op: op.to_copy(),
213 discr,
214 op_size: op_layout.size,
215 valid_discrs,
216 });
217 }
218 Variants::Multiple {
220 tag_encoding: TagEncoding::Niche { .. },
221 tag: Scalar::Initialized { value, valid_range, .. },
222 tag_field,
223 ..
224 } => {
225 let discr =
226 TyAndSize { ty: value.to_int_ty(self.tcx), size: value.size(&self.tcx) };
227 self.enums.push(EnumCheckType::WithNiche {
228 source_op: op.to_copy(),
229 discr,
230 op_size: op_layout.size,
231 offset: enum_layout.fields.offset(tag_field.as_usize()),
232 valid_range,
233 });
234 }
235 _ => return,
236 }
237
238 self.super_rvalue(rvalue, location);
239 }
240 }
241}
242
243fn split_block(
244 basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'_>>,
245 location: Location,
246) -> BasicBlock {
247 let block_data = &mut basic_blocks[location.block];
248
249 let new_block = BasicBlockData::new_stmts(
251 block_data.statements.split_off(location.statement_index),
252 block_data.terminator.take(),
253 block_data.is_cleanup,
254 );
255
256 basic_blocks.push(new_block)
257}
258
259fn insert_discr_cast_to_u128<'tcx>(
261 tcx: TyCtxt<'tcx>,
262 local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
263 block_data: &mut BasicBlockData<'tcx>,
264 source_op: Operand<'tcx>,
265 discr: TyAndSize<'tcx>,
266 op_size: Size,
267 offset: Option<Size>,
268 source_info: SourceInfo,
269) -> Place<'tcx> {
270 let get_ty_for_size = |tcx: TyCtxt<'tcx>, size: Size| -> Ty<'tcx> {
271 match size.bytes() {
272 1 => tcx.types.u8,
273 2 => tcx.types.u16,
274 4 => tcx.types.u32,
275 8 => tcx.types.u64,
276 16 => tcx.types.u128,
277 invalid => bug!("Found discriminant with invalid size, has {} bytes", invalid),
278 }
279 };
280
281 let (cast_kind, discr_ty_bits) = if discr.size.bytes() < op_size.bytes() {
282 let mu = Ty::new_maybe_uninit(tcx, tcx.types.u8);
285 let array_len = op_size.bytes();
286 let mu_array_ty = Ty::new_array(tcx, mu, array_len);
287 let mu_array =
288 local_decls.push(LocalDecl::with_source_info(mu_array_ty, source_info)).into();
289 let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, mu_array_ty);
290 block_data
291 .statements
292 .push(Statement::new(source_info, StatementKind::Assign(Box::new((mu_array, rvalue)))));
293
294 let offset = offset.unwrap_or(Size::ZERO);
297 let smaller_mu_array = mu_array.project_deeper(
298 &[ProjectionElem::Subslice {
299 from: offset.bytes(),
300 to: offset.bytes() + discr.size.bytes(),
301 from_end: false,
302 }],
303 tcx,
304 );
305
306 (CastKind::Transmute, Operand::Copy(smaller_mu_array))
307 } else {
308 let operand_int_ty = get_ty_for_size(tcx, op_size);
309
310 let op_as_int =
311 local_decls.push(LocalDecl::with_source_info(operand_int_ty, source_info)).into();
312 let rvalue = Rvalue::Cast(CastKind::Transmute, source_op, operand_int_ty);
313 block_data.statements.push(Statement::new(
314 source_info,
315 StatementKind::Assign(Box::new((op_as_int, rvalue))),
316 ));
317
318 (CastKind::IntToInt, Operand::Copy(op_as_int))
319 };
320
321 let rvalue = Rvalue::Cast(cast_kind, discr_ty_bits, discr.ty);
323 let discr_in_discr_ty =
324 local_decls.push(LocalDecl::with_source_info(discr.ty, source_info)).into();
325 block_data.statements.push(Statement::new(
326 source_info,
327 StatementKind::Assign(Box::new((discr_in_discr_ty, rvalue))),
328 ));
329
330 let const_u128 = Ty::new_uint(tcx, ty::UintTy::U128);
332 let rvalue = Rvalue::Cast(CastKind::IntToInt, Operand::Copy(discr_in_discr_ty), const_u128);
333 let discr = local_decls.push(LocalDecl::with_source_info(const_u128, source_info)).into();
334 block_data
335 .statements
336 .push(Statement::new(source_info, StatementKind::Assign(Box::new((discr, rvalue)))));
337
338 discr
339}
340
341fn insert_direct_enum_check<'tcx>(
342 tcx: TyCtxt<'tcx>,
343 local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
344 basic_blocks: &mut IndexVec<BasicBlock, BasicBlockData<'tcx>>,
345 current_block: BasicBlock,
346 source_op: Operand<'tcx>,
347 discr: TyAndSize<'tcx>,
348 op_size: Size,
349 discriminants: Vec<u128>,
350 source_info: SourceInfo,
351 new_block: BasicBlock,
352) {
353 let invalid_discr_block_data = BasicBlockData::new(None, false);
355 let invalid_discr_block = basic_blocks.push(invalid_discr_block_data);
356 let block_data = &mut basic_blocks[current_block];
357 let discr_place = insert_discr_cast_to_u128(
358 tcx,
359 local_decls,
360 block_data,
361 source_op,
362 discr,
363 op_size,
364 None,
365 source_info,
366 );
367
368 let mask = discr.size.unsigned_int_max();
370 let discr_masked =
371 local_decls.push(LocalDecl::with_source_info(tcx.types.u128, source_info)).into();
372 let rvalue = Rvalue::BinaryOp(
373 BinOp::BitAnd,
374 Box::new((
375 Operand::Copy(discr_place),
376 Operand::Constant(Box::new(ConstOperand {
377 span: source_info.span,
378 user_ty: None,
379 const_: Const::Val(ConstValue::from_u128(mask), tcx.types.u128),
380 })),
381 )),
382 );
383 block_data
384 .statements
385 .push(Statement::new(source_info, StatementKind::Assign(Box::new((discr_masked, rvalue)))));
386
387 block_data.terminator = Some(Terminator {
389 source_info,
390 kind: TerminatorKind::SwitchInt {
391 discr: Operand::Copy(discr_masked),
392 targets: SwitchTargets::new(
393 discriminants
394 .into_iter()
395 .map(|discr_val| (discr.size.truncate(discr_val), new_block)),
396 invalid_discr_block,
397 ),
398 },
399 attributes: ThinVec::new(),
400 });
401
402 basic_blocks[invalid_discr_block].terminator = Some(Terminator {
404 source_info,
405 kind: TerminatorKind::Assert {
406 cond: Operand::Constant(Box::new(ConstOperand {
407 span: source_info.span,
408 user_ty: None,
409 const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool),
410 })),
411 expected: true,
412 target: new_block,
413 msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr_masked))),
414 unwind: UnwindAction::Unreachable,
418 },
419 attributes: ThinVec::new(),
420 });
421}
422
423fn insert_uninhabited_enum_check<'tcx>(
424 tcx: TyCtxt<'tcx>,
425 local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
426 block_data: &mut BasicBlockData<'tcx>,
427 source_info: SourceInfo,
428 new_block: BasicBlock,
429) {
430 let is_ok: Place<'_> =
431 local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
432 block_data.statements.push(Statement::new(
433 source_info,
434 StatementKind::Assign(Box::new((
435 is_ok,
436 Rvalue::Use(
437 Operand::Constant(Box::new(ConstOperand {
438 span: source_info.span,
439 user_ty: None,
440 const_: Const::Val(ConstValue::from_bool(false), tcx.types.bool),
441 })),
442 WithRetag::Yes, ),
444 ))),
445 ));
446
447 block_data.terminator = Some(Terminator {
448 source_info,
449 kind: TerminatorKind::Assert {
450 cond: Operand::Copy(is_ok),
451 expected: true,
452 target: new_block,
453 msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Constant(Box::new(
454 ConstOperand {
455 span: source_info.span,
456 user_ty: None,
457 const_: Const::Val(ConstValue::from_u128(0), tcx.types.u128),
458 },
459 )))),
460 unwind: UnwindAction::Unreachable,
464 },
465 attributes: ThinVec::new(),
466 });
467}
468
469fn insert_niche_check<'tcx>(
470 tcx: TyCtxt<'tcx>,
471 local_decls: &mut IndexVec<Local, LocalDecl<'tcx>>,
472 block_data: &mut BasicBlockData<'tcx>,
473 source_op: Operand<'tcx>,
474 valid_range: WrappingRange,
475 discr: TyAndSize<'tcx>,
476 op_size: Size,
477 offset: Size,
478 source_info: SourceInfo,
479 new_block: BasicBlock,
480) {
481 let discr = insert_discr_cast_to_u128(
482 tcx,
483 local_decls,
484 block_data,
485 source_op,
486 discr,
487 op_size,
488 Some(offset),
489 source_info,
490 );
491
492 let start_const = Operand::Constant(Box::new(ConstOperand {
494 span: source_info.span,
495 user_ty: None,
496 const_: Const::Val(ConstValue::from_u128(valid_range.start), tcx.types.u128),
497 }));
498 let end_start_diff_const = Operand::Constant(Box::new(ConstOperand {
499 span: source_info.span,
500 user_ty: None,
501 const_: Const::Val(
502 ConstValue::from_u128(u128::wrapping_sub(valid_range.end, valid_range.start)),
503 tcx.types.u128,
504 ),
505 }));
506
507 let discr_diff: Place<'_> =
508 local_decls.push(LocalDecl::with_source_info(tcx.types.u128, source_info)).into();
509 block_data.statements.push(Statement::new(
510 source_info,
511 StatementKind::Assign(Box::new((
512 discr_diff,
513 Rvalue::BinaryOp(BinOp::Sub, Box::new((Operand::Copy(discr), start_const))),
514 ))),
515 ));
516
517 let is_ok: Place<'_> =
518 local_decls.push(LocalDecl::with_source_info(tcx.types.bool, source_info)).into();
519 block_data.statements.push(Statement::new(
520 source_info,
521 StatementKind::Assign(Box::new((
522 is_ok,
523 Rvalue::BinaryOp(
524 BinOp::Le,
526 Box::new((Operand::Copy(discr_diff), end_start_diff_const)),
527 ),
528 ))),
529 ));
530
531 block_data.terminator = Some(Terminator {
532 source_info,
533 kind: TerminatorKind::Assert {
534 cond: Operand::Copy(is_ok),
535 expected: true,
536 target: new_block,
537 msg: Box::new(AssertKind::InvalidEnumConstruction(Operand::Copy(discr))),
538 unwind: UnwindAction::Unreachable,
542 },
543 attributes: ThinVec::new(),
544 });
545}