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