Skip to content

Commit 2b10b3d

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 f90aab7 + 9f61d00 commit 2b10b3d

File tree

17 files changed

+279
-210
lines changed

17 files changed

+279
-210
lines changed

src/Cargo.lock

+18
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,11 @@ dependencies = [
824824
"termcolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
825825
]
826826

827+
[[package]]
828+
name = "gcc"
829+
version = "0.3.55"
830+
source = "registry+https://github.com/rust-lang/crates.io-index"
831+
827832
[[package]]
828833
name = "getopts"
829834
version = "0.2.17"
@@ -1913,6 +1918,7 @@ dependencies = [
19131918
"scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
19141919
"serialize 0.0.0",
19151920
"smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
1921+
"stacker 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
19161922
"syntax 0.0.0",
19171923
"syntax_pos 0.0.0",
19181924
"tempfile 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2672,6 +2678,16 @@ name = "stable_deref_trait"
26722678
version = "1.1.0"
26732679
source = "registry+https://github.com/rust-lang/crates.io-index"
26742680

2681+
[[package]]
2682+
name = "stacker"
2683+
version = "0.1.3"
2684+
source = "registry+https://github.com/rust-lang/crates.io-index"
2685+
dependencies = [
2686+
"cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
2687+
"gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)",
2688+
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
2689+
]
2690+
26752691
[[package]]
26762692
name = "std"
26772693
version = "0.0.0"
@@ -3240,6 +3256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
32403256
"checksum futf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
32413257
"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"
32423258
"checksum fwdansi 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34dd4c507af68d37ffef962063dfa1944ce0dd4d5b82043dbab1dabe088610c3"
3259+
"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
32433260
"checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05"
32443261
"checksum git2 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "591f8be1674b421644b6c030969520bc3fa12114d2eb467471982ed3e9584e71"
32453262
"checksum git2-curl 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0173e317f8ba21f3fff0f71549fead5e42e67961dbd402bf69f42775f3cc78b4"
@@ -3379,6 +3396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
33793396
"checksum smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "153ffa32fd170e9944f7e0838edf824a754ec4c1fc64746fcc9fe1f8fa602e5d"
33803397
"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7"
33813398
"checksum stable_deref_trait 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbc596e092fe5f598b12ef46cc03754085ac2f4d8c739ad61c4ae266cc3b3fa"
3399+
"checksum stacker 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "82c150485b78a81ed189dbdd1947397344bc296b86f7fcc7ca3cdae8bfe882e0"
33823400
"checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423"
33833401
"checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191"
33843402
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"

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 = "0.1.3"
3738

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

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);

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;

0 commit comments

Comments
 (0)