Skip to content

Commit 5009042

Browse files
committed
Auto merge of #55617 - oli-obk:stacker, r=<try>
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 #55471 fixes #41884 fixes #40161 fixes #34844 fixes #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 485397e + a4ebef0 commit 5009042

File tree

18 files changed

+274
-210
lines changed

18 files changed

+274
-210
lines changed

Diff for: src/Cargo.lock

+12
Original file line numberDiff line numberDiff line change
@@ -1904,6 +1904,7 @@ dependencies = [
19041904
"scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
19051905
"serialize 0.0.0",
19061906
"smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
1907+
"stacker 0.1.4 (git+https://github.com/oli-obk/stacker.git)",
19071908
"syntax 0.0.0",
19081909
"syntax_pos 0.0.0",
19091910
"tempfile 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2662,6 +2663,16 @@ name = "stable_deref_trait"
26622663
version = "1.1.0"
26632664
source = "registry+https://github.com/rust-lang/crates.io-index"
26642665

2666+
[[package]]
2667+
name = "stacker"
2668+
version = "0.1.4"
2669+
source = "git+https://github.com/oli-obk/stacker.git#30b32ea0514bac734539eccd4157e6d8684abcb6"
2670+
dependencies = [
2671+
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
2672+
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
2673+
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
2674+
]
2675+
26652676
[[package]]
26662677
name = "std"
26672678
version = "0.0.0"
@@ -3369,6 +3380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
33693380
"checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d"
33703381
"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7"
33713382
"checksum stable_deref_trait 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbc596e092fe5f598b12ef46cc03754085ac2f4d8c739ad61c4ae266cc3b3fa"
3383+
"checksum stacker 0.1.4 (git+https://github.com/oli-obk/stacker.git)" = "<none>"
33723384
"checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423"
33733385
"checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191"
33743386
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"

Diff for: src/librustc/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ byteorder = { version = "1.1", features = ["i128"]}
3434
chalk-engine = { version = "0.8.0", default-features=false }
3535
rustc_fs_util = { path = "../librustc_fs_util" }
3636
smallvec = { version = "0.6.5", features = ["union"] }
37+
stacker = { git = "https://github.com/oli-obk/stacker.git" }
3738

3839
# Note that these dependencies are a lie, they're just here to get linkage to
3940
# work.

Diff for: src/librustc/hir/lowering.rs

+130-127
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use hir::GenericArg;
5050
use lint::builtin::{self, PARENTHESIZED_PARAMS_IN_TYPES_AND_MODULES,
5151
ELIDED_LIFETIMES_IN_PATHS};
5252
use middle::cstore::CrateStore;
53+
use middle::recursion_limit::ensure_sufficient_stack;
5354
use rustc_data_structures::fx::FxHashSet;
5455
use rustc_data_structures::indexed_vec::IndexVec;
5556
use rustc_data_structures::thin_vec::ThinVec;
@@ -3659,8 +3660,8 @@ impl<'a> LoweringContext<'a> {
36593660
})
36603661
}
36613662

3662-
fn lower_expr(&mut self, e: &Expr) -> hir::Expr {
3663-
let kind = match e.node {
3663+
fn lower_expr_kind(&mut self, e: &Expr) -> hir::ExprKind {
3664+
match e.node {
36643665
ExprKind::Box(ref inner) => hir::ExprKind::Box(P(self.lower_expr(inner))),
36653666
ExprKind::ObsoleteInPlace(..) => {
36663667
self.sess.abort_if_errors();
@@ -3959,19 +3960,11 @@ impl<'a> LoweringContext<'a> {
39593960
let struct_path = self.std_path(e.span, &struct_path, None, is_unit);
39603961
let struct_path = hir::QPath::Resolved(None, P(struct_path));
39613962

3962-
let LoweredNodeId { node_id, hir_id } = self.lower_node_id(e.id);
3963-
3964-
return hir::Expr {
3965-
id: node_id,
3966-
hir_id,
3967-
node: if is_unit {
3963+
if is_unit {
39683964
hir::ExprKind::Path(struct_path)
39693965
} else {
39703966
hir::ExprKind::Struct(struct_path, fields, None)
3971-
},
3972-
span: e.span,
3973-
attrs: e.attrs.clone(),
3974-
};
3967+
}
39753968
}
39763969
ExprKind::Path(ref qself, ref path) => {
39773970
let qpath = self.lower_qpath(
@@ -4050,18 +4043,7 @@ impl<'a> LoweringContext<'a> {
40504043
fields.iter().map(|x| self.lower_field(x)).collect(),
40514044
maybe_expr.as_ref().map(|x| P(self.lower_expr(x))),
40524045
),
4053-
ExprKind::Paren(ref ex) => {
4054-
let mut ex = self.lower_expr(ex);
4055-
// include parens in span, but only if it is a super-span.
4056-
if e.span.contains(ex.span) {
4057-
ex.span = e.span;
4058-
}
4059-
// merge attributes into the inner expression.
4060-
let mut attrs = e.attrs.clone();
4061-
attrs.extend::<Vec<_>>(ex.attrs.into());
4062-
ex.attrs = attrs;
4063-
return ex;
4064-
}
4046+
ExprKind::Paren(_) => bug!("parens are handled in `lower_expr`"),
40654047

40664048
ExprKind::Yield(ref opt_expr) => {
40674049
self.is_generator = true;
@@ -4173,6 +4155,128 @@ impl<'a> LoweringContext<'a> {
41734155
loop_expr
41744156
}
41754157

4158+
ExprKind::ForLoop(..) => bug!(),
4159+
4160+
// Desugar ExprKind::Try
4161+
// From: `<expr>?`
4162+
ExprKind::Try(ref sub_expr) => {
4163+
// to:
4164+
//
4165+
// match Try::into_result(<expr>) {
4166+
// Ok(val) => #[allow(unreachable_code)] val,
4167+
// Err(err) => #[allow(unreachable_code)]
4168+
// // If there is an enclosing `catch {...}`
4169+
// break 'catch_target Try::from_error(From::from(err)),
4170+
// // Otherwise
4171+
// return Try::from_error(From::from(err)),
4172+
// }
4173+
4174+
let unstable_span =
4175+
self.allow_internal_unstable(CompilerDesugaringKind::QuestionMark, e.span);
4176+
4177+
// Try::into_result(<expr>)
4178+
let discr = {
4179+
// expand <expr>
4180+
let sub_expr = self.lower_expr(sub_expr);
4181+
4182+
let path = &["ops", "Try", "into_result"];
4183+
let path = P(self.expr_std_path(
4184+
unstable_span, path, None, ThinVec::new()));
4185+
P(self.expr_call(e.span, path, hir_vec![sub_expr]))
4186+
};
4187+
4188+
// #[allow(unreachable_code)]
4189+
let attr = {
4190+
// allow(unreachable_code)
4191+
let allow = {
4192+
let allow_ident = Ident::from_str("allow").with_span_pos(e.span);
4193+
let uc_ident = Ident::from_str("unreachable_code").with_span_pos(e.span);
4194+
let uc_nested = attr::mk_nested_word_item(uc_ident);
4195+
attr::mk_list_item(e.span, allow_ident, vec![uc_nested])
4196+
};
4197+
attr::mk_spanned_attr_outer(e.span, attr::mk_attr_id(), allow)
4198+
};
4199+
let attrs = vec![attr];
4200+
4201+
// Ok(val) => #[allow(unreachable_code)] val,
4202+
let ok_arm = {
4203+
let val_ident = self.str_to_ident("val");
4204+
let val_pat = self.pat_ident(e.span, val_ident);
4205+
let val_expr = P(self.expr_ident_with_attrs(
4206+
e.span,
4207+
val_ident,
4208+
val_pat.id,
4209+
ThinVec::from(attrs.clone()),
4210+
));
4211+
let ok_pat = self.pat_ok(e.span, val_pat);
4212+
4213+
self.arm(hir_vec![ok_pat], val_expr)
4214+
};
4215+
4216+
// Err(err) => #[allow(unreachable_code)]
4217+
// return Try::from_error(From::from(err)),
4218+
let err_arm = {
4219+
let err_ident = self.str_to_ident("err");
4220+
let err_local = self.pat_ident(e.span, err_ident);
4221+
let from_expr = {
4222+
let path = &["convert", "From", "from"];
4223+
let from = P(self.expr_std_path(
4224+
e.span, path, None, ThinVec::new()));
4225+
let err_expr = self.expr_ident(e.span, err_ident, err_local.id);
4226+
4227+
self.expr_call(e.span, from, hir_vec![err_expr])
4228+
};
4229+
let from_err_expr =
4230+
self.wrap_in_try_constructor("from_error", from_expr, unstable_span);
4231+
let thin_attrs = ThinVec::from(attrs);
4232+
let catch_scope = self.catch_scopes.last().map(|x| *x);
4233+
let ret_expr = if let Some(catch_node) = catch_scope {
4234+
P(self.expr(
4235+
e.span,
4236+
hir::ExprKind::Break(
4237+
hir::Destination {
4238+
label: None,
4239+
target_id: Ok(catch_node),
4240+
},
4241+
Some(from_err_expr),
4242+
),
4243+
thin_attrs,
4244+
))
4245+
} else {
4246+
P(self.expr(e.span, hir::ExprKind::Ret(Some(from_err_expr)), thin_attrs))
4247+
};
4248+
4249+
let err_pat = self.pat_err(e.span, err_local);
4250+
self.arm(hir_vec![err_pat], ret_expr)
4251+
};
4252+
4253+
hir::ExprKind::Match(
4254+
discr,
4255+
hir_vec![err_arm, ok_arm],
4256+
hir::MatchSource::TryDesugar,
4257+
)
4258+
}
4259+
4260+
ExprKind::Mac(_) => panic!("Shouldn't exist here"),
4261+
}
4262+
}
4263+
4264+
4265+
fn lower_expr(&mut self, e: &Expr) -> hir::Expr {
4266+
// parens and for loops have some custom desugaring going on where the node ids aren't
4267+
// just lowered from the expression
4268+
let kind = match e.node {
4269+
ExprKind::Paren(ref ex) => {
4270+
let mut ex = ensure_sufficient_stack(|| self.lower_expr(ex));
4271+
// include parens in span, but only if it is a super-span.
4272+
if e.span.contains(ex.span) {
4273+
ex.span = e.span;
4274+
}
4275+
// merge attributes into the inner expression.
4276+
ex.attrs.extend(e.attrs.iter().cloned());
4277+
return ex;
4278+
},
4279+
41764280
// Desugar ExprForLoop
41774281
// From: `[opt_ident]: for <pat> in <head> <body>`
41784282
ExprKind::ForLoop(ref pat, ref head, ref body, opt_label) => {
@@ -4341,109 +4445,8 @@ impl<'a> LoweringContext<'a> {
43414445
let block = P(self.block_all(e.span, hir_vec![let_stmt], Some(result)));
43424446
// add the attributes to the outer returned expr node
43434447
return self.expr_block(block, e.attrs.clone());
4344-
}
4345-
4346-
// Desugar ExprKind::Try
4347-
// From: `<expr>?`
4348-
ExprKind::Try(ref sub_expr) => {
4349-
// to:
4350-
//
4351-
// match Try::into_result(<expr>) {
4352-
// Ok(val) => #[allow(unreachable_code)] val,
4353-
// Err(err) => #[allow(unreachable_code)]
4354-
// // If there is an enclosing `catch {...}`
4355-
// break 'catch_target Try::from_error(From::from(err)),
4356-
// // Otherwise
4357-
// return Try::from_error(From::from(err)),
4358-
// }
4359-
4360-
let unstable_span =
4361-
self.allow_internal_unstable(CompilerDesugaringKind::QuestionMark, e.span);
4362-
4363-
// Try::into_result(<expr>)
4364-
let discr = {
4365-
// expand <expr>
4366-
let sub_expr = self.lower_expr(sub_expr);
4367-
4368-
let path = &["ops", "Try", "into_result"];
4369-
let path = P(self.expr_std_path(
4370-
unstable_span, path, None, ThinVec::new()));
4371-
P(self.expr_call(e.span, path, hir_vec![sub_expr]))
4372-
};
4373-
4374-
// #[allow(unreachable_code)]
4375-
let attr = {
4376-
// allow(unreachable_code)
4377-
let allow = {
4378-
let allow_ident = Ident::from_str("allow").with_span_pos(e.span);
4379-
let uc_ident = Ident::from_str("unreachable_code").with_span_pos(e.span);
4380-
let uc_nested = attr::mk_nested_word_item(uc_ident);
4381-
attr::mk_list_item(e.span, allow_ident, vec![uc_nested])
4382-
};
4383-
attr::mk_spanned_attr_outer(e.span, attr::mk_attr_id(), allow)
4384-
};
4385-
let attrs = vec![attr];
4386-
4387-
// Ok(val) => #[allow(unreachable_code)] val,
4388-
let ok_arm = {
4389-
let val_ident = self.str_to_ident("val");
4390-
let val_pat = self.pat_ident(e.span, val_ident);
4391-
let val_expr = P(self.expr_ident_with_attrs(
4392-
e.span,
4393-
val_ident,
4394-
val_pat.id,
4395-
ThinVec::from(attrs.clone()),
4396-
));
4397-
let ok_pat = self.pat_ok(e.span, val_pat);
4398-
4399-
self.arm(hir_vec![ok_pat], val_expr)
4400-
};
4401-
4402-
// Err(err) => #[allow(unreachable_code)]
4403-
// return Try::from_error(From::from(err)),
4404-
let err_arm = {
4405-
let err_ident = self.str_to_ident("err");
4406-
let err_local = self.pat_ident(e.span, err_ident);
4407-
let from_expr = {
4408-
let path = &["convert", "From", "from"];
4409-
let from = P(self.expr_std_path(
4410-
e.span, path, None, ThinVec::new()));
4411-
let err_expr = self.expr_ident(e.span, err_ident, err_local.id);
4412-
4413-
self.expr_call(e.span, from, hir_vec![err_expr])
4414-
};
4415-
let from_err_expr =
4416-
self.wrap_in_try_constructor("from_error", from_expr, unstable_span);
4417-
let thin_attrs = ThinVec::from(attrs);
4418-
let catch_scope = self.catch_scopes.last().map(|x| *x);
4419-
let ret_expr = if let Some(catch_node) = catch_scope {
4420-
P(self.expr(
4421-
e.span,
4422-
hir::ExprKind::Break(
4423-
hir::Destination {
4424-
label: None,
4425-
target_id: Ok(catch_node),
4426-
},
4427-
Some(from_err_expr),
4428-
),
4429-
thin_attrs,
4430-
))
4431-
} else {
4432-
P(self.expr(e.span, hir::ExprKind::Ret(Some(from_err_expr)), thin_attrs))
4433-
};
4434-
4435-
let err_pat = self.pat_err(e.span, err_local);
4436-
self.arm(hir_vec![err_pat], ret_expr)
4437-
};
4438-
4439-
hir::ExprKind::Match(
4440-
discr,
4441-
hir_vec![err_arm, ok_arm],
4442-
hir::MatchSource::TryDesugar,
4443-
)
4444-
}
4445-
4446-
ExprKind::Mac(_) => panic!("Shouldn't exist here"),
4448+
},
4449+
_ => ensure_sufficient_stack(|| self.lower_expr_kind(e)),
44474450
};
44484451

44494452
let LoweredNodeId { node_id, hir_id } = self.lower_node_id(e.id);

Diff for: src/librustc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ extern crate serialize as rustc_serialize; // used by deriving
106106
extern crate rustc_apfloat;
107107
extern crate byteorder;
108108
extern crate backtrace;
109+
extern crate stacker;
109110

110111
#[macro_use]
111112
extern crate smallvec;

Diff for: src/librustc/middle/recursion_limit.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,32 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// Recursion limit.
12-
//
13-
// There are various parts of the compiler that must impose arbitrary limits
14-
// on how deeply they recurse to prevent stack overflow. Users can override
15-
// this via an attribute on the crate like `#![recursion_limit="22"]`. This pass
16-
// just peeks and looks for that attribute.
11+
//! Recursion limit.
12+
//!
13+
//! There are various parts of the compiler that must impose arbitrary limits
14+
//! on how deeply they recurse to prevent stack overflow. Users can override
15+
//! this via an attribute on the crate like `#![recursion_limit="22"]`. This pass
16+
//! just peeks and looks for that attribute.
1717
1818
use session::Session;
1919
use syntax::ast;
2020

2121
use rustc_data_structures::sync::Once;
2222

23+
const RED_ZONE: usize = 100*1024; // 100k
24+
const STACK_PER_RECURSION: usize = 1 * 1024 * 1024; // 1MB
25+
26+
/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations
27+
/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit
28+
/// from this.
29+
///
30+
/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.
31+
pub fn ensure_sufficient_stack<R, F: FnOnce() -> R>(
32+
f: F
33+
) -> R {
34+
stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)
35+
}
36+
2337
pub fn update_limits(sess: &Session, krate: &ast::Crate) {
2438
update_limit(sess, krate, &sess.recursion_limit, "recursion_limit",
2539
"recursion limit", 64);

0 commit comments

Comments
 (0)