Skip to content

Commit 80263e1

Browse files
authored
Rollup merge of #156235 - rajgandhi1:compiler_stack_overflow_fix, r=adwinwhite
fix: Guard SizeSkeleton::compute against stack overflow Fixes #156137 Fix: extract the recursion into a private `compute_inner` that carries a depth counter. When depth exceeds the crate's recursion limit, return `LayoutError::Unknown` and let the existing transmute size-check produce a normal error instead of crashing. A regression test is included in `tests/ui/transmute/`.
2 parents 71e64b9 + 7a900df commit 80263e1

6 files changed

Lines changed: 107 additions & 15 deletions

File tree

compiler/rustc_hir_typeck/src/intrinsicck.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,14 @@ fn check_transmute<'tcx>(
7373
to: Ty<'tcx>,
7474
hir_id: HirId,
7575
) {
76-
let span = || tcx.hir_span(hir_id);
76+
let span = tcx.hir_span(hir_id);
7777
let normalize = |ty| {
7878
if let Ok(ty) = tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)) {
7979
ty
8080
} else {
8181
Ty::new_error_with_message(
8282
tcx,
83-
span(),
83+
span,
8484
format!("tried to normalize non-wf type {ty:#?} in check_transmute"),
8585
)
8686
}
@@ -95,8 +95,8 @@ fn check_transmute<'tcx>(
9595
return;
9696
}
9797

98-
let sk_from = SizeSkeleton::compute(from, tcx, typing_env);
99-
let sk_to = SizeSkeleton::compute(to, tcx, typing_env);
98+
let sk_from = SizeSkeleton::compute(from, tcx, typing_env, span);
99+
let sk_to = SizeSkeleton::compute(to, tcx, typing_env, span);
100100
trace!(?sk_from, ?sk_to);
101101

102102
// Check for same size using the skeletons.
@@ -114,7 +114,7 @@ fn check_transmute<'tcx>(
114114
&& let SizeSkeleton::Known(size_to, _) = sk_to
115115
&& size_to == Pointer(tcx.data_layout.instruction_address_space).size(&tcx)
116116
{
117-
struct_span_code_err!(tcx.sess.dcx(), span(), E0591, "can't transmute zero-sized type")
117+
struct_span_code_err!(tcx.sess.dcx(), span, E0591, "can't transmute zero-sized type")
118118
.with_note(format!("source type: {from}"))
119119
.with_note(format!("target type: {to}"))
120120
.with_help("cast with `as` to a pointer instead")
@@ -125,7 +125,7 @@ fn check_transmute<'tcx>(
125125

126126
let mut err = struct_span_code_err!(
127127
tcx.sess.dcx(),
128-
span(),
128+
span,
129129
E0512,
130130
"cannot transmute between types of different sizes, or dependently-sized types"
131131
);

compiler/rustc_lint/src/types.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_middle::bug;
88
use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton};
99
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Unnormalized};
1010
use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
11-
use rustc_span::{Span, Symbol, sym};
11+
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
1212
use tracing::debug;
1313

1414
mod improper_ctypes; // these files do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations
@@ -877,7 +877,8 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
877877
// At this point, the field's type is known to be nonnull and the parent enum is Option-like.
878878
// If the computed size for the field and the enum are different, the nonnull optimization isn't
879879
// being applied (and we've got a problem somewhere).
880-
let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, typing_env).ok();
880+
let compute_size_skeleton =
881+
|t| SizeSkeleton::compute(t, tcx, typing_env, DUMMY_SP).ok();
881882
if !compute_size_skeleton(ty)?.same_size(compute_size_skeleton(field_ty)?) {
882883
bug!("improper_ctypes: Option nonnull optimization not applied?");
883884
}

compiler/rustc_middle/src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ pub(crate) struct RecursionLimitReached<'tcx> {
6262
pub suggested_limit: rustc_hir::limit::Limit,
6363
}
6464

65+
#[derive(Diagnostic)]
66+
#[diag("reached the recursion limit while computing the size of `{$ty}`")]
67+
#[help(
68+
"consider increasing the recursion limit by adding a `#![recursion_limit = \"{$suggested_limit}\"]`"
69+
)]
70+
pub(crate) struct RecursionLimitReachedSizeSkeleton<'tcx> {
71+
#[primary_span]
72+
pub span: Span,
73+
pub ty: Ty<'tcx>,
74+
pub suggested_limit: rustc_hir::limit::Limit,
75+
}
76+
6577
#[derive(Diagnostic)]
6678
#[diag("constant evaluation of enum discriminant resulted in non-integer")]
6779
pub(crate) struct ConstEvalNonIntError {

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,38 @@ impl<'tcx> SizeSkeleton<'tcx> {
332332
ty: Ty<'tcx>,
333333
tcx: TyCtxt<'tcx>,
334334
typing_env: ty::TypingEnv<'tcx>,
335+
span: Span,
336+
) -> Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>> {
337+
Self::compute_inner(ty, tcx, typing_env, span, 0)
338+
}
339+
340+
fn compute_inner(
341+
ty: Ty<'tcx>,
342+
tcx: TyCtxt<'tcx>,
343+
typing_env: ty::TypingEnv<'tcx>,
344+
span: Span,
345+
depth: usize,
335346
) -> Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>> {
336347
debug_assert!(!ty.has_non_region_infer());
337348

349+
// Bail out if we've recursed too deeply (issue #156137); a cyclic type
350+
// alias can otherwise blow the stack here. Using `>=` rather than `>`
351+
// means we fire exactly at the limit, which lets us report the
352+
// cycle-root type (`Thing<T>`) instead of an innocent field type.
353+
let recursion_limit = tcx.recursion_limit();
354+
if depth >= recursion_limit.0 {
355+
let suggested_limit = match recursion_limit {
356+
hir::limit::Limit(0) => hir::limit::Limit(2),
357+
limit => limit * 2,
358+
};
359+
let reported = tcx.dcx().emit_err(crate::error::RecursionLimitReachedSizeSkeleton {
360+
span,
361+
ty,
362+
suggested_limit,
363+
});
364+
return Err(tcx.arena.alloc(LayoutError::ReferencesError(reported)));
365+
}
366+
338367
// First try computing a static layout.
339368
let err = match tcx.layout_of(typing_env.as_query_input(ty)) {
340369
Ok(layout) => {
@@ -407,7 +436,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
407436
return Ok(SizeSkeleton::Known(Size::from_bytes(0), None));
408437
}
409438

410-
match SizeSkeleton::compute(inner, tcx, typing_env)? {
439+
match SizeSkeleton::compute_inner(inner, tcx, typing_env, span, depth + 1)? {
411440
// This may succeed because the multiplication of two types may overflow
412441
// but a single size of a nested array will not.
413442
SizeSkeleton::Known(s, a) => {
@@ -434,10 +463,15 @@ impl<'tcx> SizeSkeleton<'tcx> {
434463
// Get a zero-sized variant or a pointer newtype.
435464
let zero_or_ptr_variant = |i| {
436465
let i = VariantIdx::from_usize(i);
437-
let fields =
438-
def.variant(i).fields.iter().map(|field| {
439-
SizeSkeleton::compute(field.ty(tcx, args), tcx, typing_env)
440-
});
466+
let fields = def.variant(i).fields.iter().map(|field| {
467+
SizeSkeleton::compute_inner(
468+
field.ty(tcx, args),
469+
tcx,
470+
typing_env,
471+
span,
472+
depth + 1,
473+
)
474+
});
441475
let mut ptr = None;
442476
for field in fields {
443477
let field = field?;
@@ -487,13 +521,13 @@ impl<'tcx> SizeSkeleton<'tcx> {
487521
if ty == normalized {
488522
Err(err)
489523
} else {
490-
SizeSkeleton::compute(normalized, tcx, typing_env)
524+
SizeSkeleton::compute_inner(normalized, tcx, typing_env, span, depth + 1)
491525
}
492526
}
493527

494528
ty::Pat(base, pat) => {
495529
// Pattern types are always the same size as their base.
496-
let base = SizeSkeleton::compute(base, tcx, typing_env);
530+
let base = SizeSkeleton::compute_inner(base, tcx, typing_env, span, depth + 1);
497531
match *pat {
498532
ty::PatternKind::Range { .. } | ty::PatternKind::Or(_) => base,
499533
// But in the case of `!null` patterns we need to note that in the
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Regression test for issue #156137.
2+
// Computing the `SizeSkeleton` of a type whose layout depends on itself
3+
// through a normalizing type alias used to recurse without bound and
4+
// blow the stack. We now bail out via the recursion limit and emit a
5+
// regular error instead of ICE-ing.
6+
7+
use std::mem::transmute;
8+
9+
trait Trait {
10+
type Assoc;
11+
}
12+
impl<T> Trait for T {
13+
type Assoc = T;
14+
}
15+
type Alias<T> = <T as Trait>::Assoc;
16+
17+
pub struct Thing<T: ?Sized>(*const T, Alias<Thing<T>>);
18+
19+
pub fn weird<T: ?Sized>(value: Thing<T>) {
20+
let _: i32 = unsafe { transmute(value) };
21+
//~^ ERROR reached the recursion limit while computing the size of `Thing<T>`
22+
//~| ERROR cannot transmute between types of different sizes, or dependently-sized types
23+
}
24+
25+
fn main() {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: reached the recursion limit while computing the size of `Thing<T>`
2+
--> $DIR/recursive-size-skeleton.rs:20:27
3+
|
4+
LL | let _: i32 = unsafe { transmute(value) };
5+
| ^^^^^^^^^
6+
|
7+
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]`
8+
9+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
10+
--> $DIR/recursive-size-skeleton.rs:20:27
11+
|
12+
LL | let _: i32 = unsafe { transmute(value) };
13+
| ^^^^^^^^^
14+
|
15+
= note: source type: `Thing<T>` (the type has an unknown layout)
16+
= note: target type: `i32` (32 bits)
17+
18+
error: aborting due to 2 previous errors
19+
20+
For more information about this error, try `rustc --explain E0512`.

0 commit comments

Comments
 (0)