Skip to content

Commit d6e0e11

Browse files
authored
Rollup merge of rust-lang#55617 - oli-obk:stacker, r=oli-obk,nagisa
Prevent compiler stack overflow for deeply recursive code I was unable to write a test that 1. runs in under 1s 2. overflows on my machine without this patch The following reproduces the issue, but I don't think it's sensible to include a test that takes 30s to compile. We can now easily squash newly appearing overflows by the strategic insertion of calls to `ensure_sufficient_stack`. ```rust // compile-pass #![recursion_limit="1000000"] macro_rules! chain { (EE $e:expr) => {$e.sin()}; (RECURSE $i:ident $e:expr) => {chain!($i chain!($i chain!($i chain!($i $e))))}; (Z $e:expr) => {chain!(RECURSE EE $e)}; (Y $e:expr) => {chain!(RECURSE Z $e)}; (X $e:expr) => {chain!(RECURSE Y $e)}; (A $e:expr) => {chain!(RECURSE X $e)}; (B $e:expr) => {chain!(RECURSE A $e)}; (C $e:expr) => {chain!(RECURSE B $e)}; // causes overflow on x86_64 linux // less than 1 second until overflow on test machine // after overflow has been fixed, takes 30s to compile :/ (D $e:expr) => {chain!(RECURSE C $e)}; (E $e:expr) => {chain!(RECURSE D $e)}; (F $e:expr) => {chain!(RECURSE E $e)}; // more than 10 seconds (G $e:expr) => {chain!(RECURSE F $e)}; (H $e:expr) => {chain!(RECURSE G $e)}; (I $e:expr) => {chain!(RECURSE H $e)}; (J $e:expr) => {chain!(RECURSE I $e)}; (K $e:expr) => {chain!(RECURSE J $e)}; (L $e:expr) => {chain!(RECURSE L $e)}; } fn main() { let x = chain!(D 42.0_f32); } ``` fixes rust-lang#55471 fixes rust-lang#41884 fixes rust-lang#40161 fixes rust-lang#34844 fixes rust-lang#32594 cc @alexcrichton @rust-lang/compiler I looked at all code that checks the recursion limit and inserted stack growth calls where appropriate.
2 parents f509b26 + fb10e5c commit d6e0e11

File tree

15 files changed

+248
-156
lines changed

15 files changed

+248
-156
lines changed

Cargo.lock

+23
Original file line numberDiff line numberDiff line change
@@ -2579,6 +2579,15 @@ dependencies = [
25792579
"core",
25802580
]
25812581

2582+
[[package]]
2583+
name = "psm"
2584+
version = "0.1.6"
2585+
source = "registry+https://github.com/rust-lang/crates.io-index"
2586+
checksum = "b14fc68b454f875abc8354c2555e1d56596f74833ddc0f77f87f4871ed6a30e0"
2587+
dependencies = [
2588+
"cc",
2589+
]
2590+
25822591
[[package]]
25832592
name = "publicsuffix"
25842593
version = "1.5.3"
@@ -3122,6 +3131,7 @@ dependencies = [
31223131
"scoped-tls",
31233132
"serialize",
31243133
"smallvec 1.0.0",
3134+
"stacker",
31253135
]
31263136

31273137
[[package]]
@@ -4541,6 +4551,19 @@ version = "1.1.0"
45414551
source = "registry+https://github.com/rust-lang/crates.io-index"
45424552
checksum = "ffbc596e092fe5f598b12ef46cc03754085ac2f4d8c739ad61c4ae266cc3b3fa"
45434553

4554+
[[package]]
4555+
name = "stacker"
4556+
version = "0.1.6"
4557+
source = "registry+https://github.com/rust-lang/crates.io-index"
4558+
checksum = "d96fc4f13a0ac088e9a3cd9af1cc8c5cc1ab5deb2145cef661267dfc9c542f8a"
4559+
dependencies = [
4560+
"cc",
4561+
"cfg-if",
4562+
"libc",
4563+
"psm",
4564+
"winapi 0.3.8",
4565+
]
4566+
45444567
[[package]]
45454568
name = "std"
45464569
version = "0.0.0"

src/librustc/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ byteorder = { version = "1.3" }
3636
smallvec = { version = "1.0", features = ["union", "may_dangle"] }
3737
measureme = "0.7.1"
3838
rustc_session = { path = "../librustc_session" }
39+
stacker = "0.1.6"

src/librustc/middle/limits.rs

+18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ use rustc_span::symbol::{sym, Symbol};
1313

1414
use rustc_data_structures::sync::Once;
1515

16+
// This is the amount of bytes that need to be left on the stack before increasing the size.
17+
// It must be at least as large as the stack required by any code that does not call
18+
// `ensure_sufficient_stack`.
19+
const RED_ZONE: usize = 100 * 1024; // 100k
20+
21+
// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then
22+
// on. This flag has performance relevant characteristics. Don't set it too high.
23+
const STACK_PER_RECURSION: usize = 1 * 1024 * 1024; // 1MB
24+
25+
/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations
26+
/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit
27+
/// from this.
28+
///
29+
/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.
30+
pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {
31+
stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)
32+
}
33+
1634
pub fn update_limits(sess: &Session, krate: &ast::Crate) {
1735
update_limit(sess, krate, &sess.recursion_limit, sym::recursion_limit, 128);
1836
update_limit(sess, krate, &sess.type_length_limit, sym::type_length_limit, 1048576);

src/librustc/ty/inhabitedness/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub use self::def_id_forest::DefIdForest;
22

3+
use crate::middle::limits::ensure_sufficient_stack;
34
use crate::ty;
45
use crate::ty::context::TyCtxt;
56
use crate::ty::TyKind::*;
@@ -196,7 +197,9 @@ impl<'tcx> TyS<'tcx> {
196197
/// Calculates the forest of `DefId`s from which this type is visibly uninhabited.
197198
fn uninhabited_from(&self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> DefIdForest {
198199
match self.kind {
199-
Adt(def, substs) => def.uninhabited_from(tcx, substs, param_env),
200+
Adt(def, substs) => {
201+
ensure_sufficient_stack(|| def.uninhabited_from(tcx, substs, param_env))
202+
}
200203

201204
Never => DefIdForest::full(tcx),
202205

src/librustc/ty/query/plumbing.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ impl<'tcx> TyCtxt<'tcx> {
321321
};
322322

323323
// Use the `ImplicitCtxt` while we execute the query.
324-
tls::enter_context(&new_icx, |_| compute(self))
324+
tls::enter_context(&new_icx, |_| {
325+
crate::middle::limits::ensure_sufficient_stack(|| compute(self))
326+
})
325327
})
326328
}
327329

src/librustc_ast_lowering/expr.rs

+37-29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericArgs};
22

3-
use rustc::bug;
3+
use rustc::middle::limits::ensure_sufficient_stack;
4+
use rustc::{bug, span_bug};
45
use rustc_ast::ast::*;
56
use rustc_ast::attr;
67
use rustc_ast::ptr::P as AstP;
@@ -22,6 +23,37 @@ impl<'hir> LoweringContext<'_, 'hir> {
2223

2324
pub(super) fn lower_expr_mut(&mut self, e: &Expr) -> hir::Expr<'hir> {
2425
let kind = match e.kind {
26+
ExprKind::Paren(ref ex) => {
27+
let mut ex = self.lower_expr_mut(ex);
28+
// Include parens in span, but only if it is a super-span.
29+
if e.span.contains(ex.span) {
30+
ex.span = e.span;
31+
}
32+
// Merge attributes into the inner expression.
33+
let mut attrs = e.attrs.clone();
34+
attrs.extend::<Vec<_>>(ex.attrs.into());
35+
ex.attrs = attrs;
36+
return ex;
37+
}
38+
39+
// Desugar `ExprForLoop`
40+
// from: `[opt_ident]: for <pat> in <head> <body>`
41+
ExprKind::ForLoop(ref pat, ref head, ref body, opt_label) => {
42+
return self.lower_expr_for(e, pat, head, body, opt_label);
43+
}
44+
_ => ensure_sufficient_stack(|| self.lower_expr_kind(e)),
45+
};
46+
47+
hir::Expr {
48+
hir_id: self.lower_node_id(e.id),
49+
kind,
50+
span: e.span,
51+
attrs: e.attrs.iter().map(|a| self.lower_attr(a)).collect::<Vec<_>>().into(),
52+
}
53+
}
54+
55+
fn lower_expr_kind(&mut self, e: &Expr) -> hir::ExprKind<'hir> {
56+
match e.kind {
2557
ExprKind::Box(ref inner) => hir::ExprKind::Box(self.lower_expr(inner)),
2658
ExprKind::Array(ref exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)),
2759
ExprKind::Repeat(ref expr, ref count) => {
@@ -175,37 +207,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
175207
maybe_expr,
176208
)
177209
}
178-
ExprKind::Paren(ref ex) => {
179-
let mut ex = self.lower_expr_mut(ex);
180-
// Include parens in span, but only if it is a super-span.
181-
if e.span.contains(ex.span) {
182-
ex.span = e.span;
183-
}
184-
// Merge attributes into the inner expression.
185-
let mut attrs = e.attrs.clone();
186-
attrs.extend::<Vec<_>>(ex.attrs.into());
187-
ex.attrs = attrs;
188-
return ex;
189-
}
190-
191210
ExprKind::Yield(ref opt_expr) => self.lower_expr_yield(e.span, opt_expr.as_deref()),
192-
193211
ExprKind::Err => hir::ExprKind::Err,
194-
195-
// Desugar `ExprForLoop`
196-
// from: `[opt_ident]: for <pat> in <head> <body>`
197-
ExprKind::ForLoop(ref pat, ref head, ref body, opt_label) => {
198-
return self.lower_expr_for(e, pat, head, body, opt_label);
199-
}
200212
ExprKind::Try(ref sub_expr) => self.lower_expr_try(e.span, sub_expr),
201-
ExprKind::MacCall(_) => panic!("Shouldn't exist here"),
202-
};
203-
204-
hir::Expr {
205-
hir_id: self.lower_node_id(e.id),
206-
kind,
207-
span: e.span,
208-
attrs: e.attrs.iter().map(|a| self.lower_attr(a)).collect::<Vec<_>>().into(),
213+
ExprKind::ForLoop(..) | ExprKind::Paren(_) => {
214+
span_bug!(e.span, "Should have been handled by `lower_expr`")
215+
}
216+
ExprKind::MacCall(_) => span_bug!(e.span, "Shouldn't exist here"),
209217
}
210218
}
211219

src/librustc_ast_lowering/pat.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::{ImplTraitContext, LoweringContext, ParamMode};
22

3+
use rustc::{middle::limits::ensure_sufficient_stack, span_bug};
34
use rustc_ast::ast::*;
45
use rustc_ast::ptr::P;
56
use rustc_hir as hir;
@@ -9,6 +10,16 @@ use rustc_span::{source_map::Spanned, Span};
910
impl<'a, 'hir> LoweringContext<'a, 'hir> {
1011
crate fn lower_pat(&mut self, p: &Pat) -> &'hir hir::Pat<'hir> {
1112
let node = match p.kind {
13+
// FIXME: consider not using recursion to lower this.
14+
PatKind::Paren(ref inner) => return self.lower_pat(inner),
15+
_ => ensure_sufficient_stack(|| self.lower_pat_kind(p)),
16+
};
17+
18+
self.pat_with_node_id_of(p, node)
19+
}
20+
21+
fn lower_pat_kind(&mut self, p: &Pat) -> hir::PatKind<'hir> {
22+
match p.kind {
1223
PatKind::Wild => hir::PatKind::Wild,
1324
PatKind::Ident(ref binding_mode, ident, ref sub) => {
1425
let lower_sub = |this: &mut Self| sub.as_ref().map(|s| this.lower_pat(&*s));
@@ -74,11 +85,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
7485
// If we reach here the `..` pattern is not semantically allowed.
7586
self.ban_illegal_rest_pat(p.span)
7687
}
77-
PatKind::Paren(ref inner) => return self.lower_pat(inner),
78-
PatKind::MacCall(_) => panic!("Shouldn't exist here"),
79-
};
80-
81-
self.pat_with_node_id_of(p, node)
88+
PatKind::Paren(_) => span_bug!(p.span, "Should have been handled by `lower_pat`"),
89+
PatKind::MacCall(_) => span_bug!(p.span, "Shouldn't exist here"),
90+
}
8291
}
8392

8493
fn lower_pat_tuple(

src/librustc_interface/util.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,7 @@ pub fn create_session(
8383
(Lrc::new(sess), Lrc::new(codegen_backend), source_map)
8484
}
8585

86-
// Temporarily have stack size set to 32MB to deal with various crates with long method
87-
// chains or deep syntax trees, except when on Haiku.
88-
// FIXME(oli-obk): get https://github.com/rust-lang/rust/pull/55617 the finish line
89-
#[cfg(not(target_os = "haiku"))]
90-
const STACK_SIZE: usize = 32 * 1024 * 1024;
91-
92-
#[cfg(target_os = "haiku")]
93-
const STACK_SIZE: usize = 16 * 1024 * 1024;
86+
const STACK_SIZE: usize = 8 * 1024 * 1024;
9487

9588
fn get_stack_size() -> Option<usize> {
9689
// FIXME: Hacks on hacks. If the env is trying to override the stack size

src/librustc_mir/monomorphize/collector.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,9 @@ fn collect_items_rec<'tcx>(
368368
recursion_depth_reset = Some(check_recursion_limit(tcx, instance, recursion_depths));
369369
check_type_length_limit(tcx, instance);
370370

371-
collect_neighbours(tcx, instance, &mut neighbors);
371+
rustc::middle::limits::ensure_sufficient_stack(|| {
372+
collect_neighbours(tcx, instance, &mut neighbors);
373+
});
372374
}
373375
MonoItem::GlobalAsm(..) => {
374376
recursion_depth_reset = None;
@@ -1134,7 +1136,9 @@ fn collect_miri<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut Vec<Mon
11341136
Some(GlobalAlloc::Memory(alloc)) => {
11351137
trace!("collecting {:?} with {:#?}", alloc_id, alloc);
11361138
for &((), inner) in alloc.relocations().values() {
1137-
collect_miri(tcx, inner, output);
1139+
rustc::middle::limits::ensure_sufficient_stack(|| {
1140+
collect_miri(tcx, inner, output);
1141+
});
11381142
}
11391143
}
11401144
Some(GlobalAlloc::Function(fn_instance)) => {

src/librustc_mir_build/build/expr/as_temp.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::build::scope::DropKind;
44
use crate::build::{BlockAnd, BlockAndExtension, Builder};
55
use crate::hair::*;
6-
use rustc::middle::region;
6+
use rustc::middle::{region, limits::ensure_sufficient_stack};
77
use rustc::mir::*;
88
use rustc_hir as hir;
99
use rustc_span::symbol::sym;
@@ -22,7 +22,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
2222
M: Mirror<'tcx, Output = Expr<'tcx>>,
2323
{
2424
let expr = self.hir.mirror(expr);
25-
self.expr_as_temp(block, temp_lifetime, expr, mutability)
25+
//
26+
// this is the only place in mir building that we need to truly need to worry about
27+
// infinite recursion. Everything else does recurse, too, but it always gets broken up
28+
// at some point by inserting an intermediate temporary
29+
ensure_sufficient_stack(|| self.expr_as_temp(block, temp_lifetime, expr, mutability))
2630
}
2731

2832
fn expr_as_temp(

src/librustc_trait_selection/traits/project.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use super::{VtableClosureData, VtableFnPointerData, VtableGeneratorData, VtableI
1717
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
1818
use crate::infer::{InferCtxt, InferOk, LateBoundRegionConversionTime};
1919
use crate::traits::error_reporting::InferCtxtExt;
20+
use rustc::middle::limits::ensure_sufficient_stack;
2021
use rustc::ty::fold::{TypeFoldable, TypeFolder};
2122
use rustc::ty::subst::{InternalSubsts, Subst};
2223
use rustc::ty::{self, ToPolyTraitRef, ToPredicate, Ty, TyCtxt, WithConstness};
@@ -261,7 +262,7 @@ where
261262
{
262263
debug!("normalize_with_depth(depth={}, value={:?})", depth, value);
263264
let mut normalizer = AssocTypeNormalizer::new(selcx, param_env, cause, depth, obligations);
264-
let result = normalizer.fold(value);
265+
let result = ensure_sufficient_stack(|| normalizer.fold(value));
265266
debug!(
266267
"normalize_with_depth: depth={} result={:?} with {} obligations",
267268
depth,

src/librustc_trait_selection/traits/query/normalize.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::infer::canonical::OriginalQueryValues;
77
use crate::infer::{InferCtxt, InferOk};
88
use crate::traits::error_reporting::InferCtxtExt;
99
use crate::traits::{Obligation, ObligationCause, PredicateObligation, Reveal};
10+
use rustc::middle::limits::ensure_sufficient_stack;
1011
use rustc::ty::fold::{TypeFoldable, TypeFolder};
1112
use rustc::ty::subst::Subst;
1213
use rustc::ty::{self, Ty, TyCtxt};
@@ -120,7 +121,7 @@ impl<'cx, 'tcx> TypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
120121
ty
121122
);
122123
}
123-
let folded_ty = self.fold_ty(concrete_ty);
124+
let folded_ty = ensure_sufficient_stack(|| self.fold_ty(concrete_ty));
124125
self.anon_depth -= 1;
125126
folded_ty
126127
}

0 commit comments

Comments
 (0)