Skip to content

limit the length of types in monomorphization #37789

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/librustc/middle/recursion_limit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,31 @@
use session::Session;
use syntax::ast;

pub fn update_recursion_limit(sess: &Session, krate: &ast::Crate) {
use std::cell::Cell;

pub fn update_limits(sess: &Session, krate: &ast::Crate) {
update_limit(sess, krate, &sess.recursion_limit, "recursion_limit",
"recursion limit");
update_limit(sess, krate, &sess.type_length_limit, "type_length_limit",
"type length limit");
}

fn update_limit(sess: &Session, krate: &ast::Crate, limit: &Cell<usize>,
name: &str, description: &str) {
for attr in &krate.attrs {
if !attr.check_name("recursion_limit") {
if !attr.check_name(name) {
continue;
}

if let Some(s) = attr.value_str() {
if let Some(n) = s.as_str().parse().ok() {
sess.recursion_limit.set(n);
limit.set(n);
return;
}
}

span_err!(sess, attr.span, E0296, "malformed recursion limit attribute, \
expected #![recursion_limit=\"N\"]");
span_err!(sess, attr.span, E0296,
"malformed {} attribute, expected #![{}=\"N\"]",
description, name);
}
}
4 changes: 4 additions & 0 deletions src/librustc/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ pub struct Session {
/// operations such as auto-dereference and monomorphization.
pub recursion_limit: Cell<usize>,

/// The maximum length of types during monomorphization.
pub type_length_limit: Cell<usize>,

/// The metadata::creader module may inject an allocator/panic_runtime
/// dependency if it didn't already find one, and this tracks what was
/// injected.
Expand Down Expand Up @@ -620,6 +623,7 @@ pub fn build_session_(sopts: config::Options,
crate_disambiguator: RefCell::new(Symbol::intern("")),
features: RefCell::new(feature_gate::Features::new()),
recursion_limit: Cell::new(64),
type_length_limit: Cell::new(1048576),
next_node_id: Cell::new(NodeId::new(1)),
injected_allocator: Cell::new(None),
injected_panic_runtime: Cell::new(None),
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_driver/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ pub fn phase_2_configure_and_expand<F>(sess: &Session,
*sess.crate_disambiguator.borrow_mut() = Symbol::intern(&compute_crate_disambiguator(sess));

time(time_passes, "recursion limit", || {
middle::recursion_limit::update_recursion_limit(sess, &krate);
middle::recursion_limit::update_limits(sess, &krate);
});

krate = time(time_passes, "crate injection", || {
Expand Down
35 changes: 35 additions & 0 deletions src/librustc_trans/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ fn collect_items_rec<'a, 'tcx: 'a>(scx: &SharedCrateContext<'a, 'tcx>,
recursion_depth_reset = Some(check_recursion_limit(scx.tcx(),
instance,
recursion_depths));
check_type_length_limit(scx.tcx(), instance);

// Scan the MIR in order to find function calls, closures, and
// drop-glue
Expand Down Expand Up @@ -432,6 +433,40 @@ fn check_recursion_limit<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
(instance.def, recursion_depth)
}

fn check_type_length_limit<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems good; I'm wondering if this is the only place we have to check it. I was originally considering checking this directly in the type interner (i.e., we just couldn't create a type that is too big). What do you think about that?

Copy link
Contributor Author

@arielb1 arielb1 Nov 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking it in other places would basically require us to interncache "type sizes" in types, which would bump our memory usage for no very good reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually expected this value to be stored inside each TyS - seems cheaper that way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eddyb

That's what I meant. Looks like a bump in memory use, and there's no killer case for that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I wrote my message before seeing yours. Any chance this can be done with recursion, to avoid the memory allocations of the walker? Or is it amortized and otherwise not a problem?
Could we reuse the walker?

instance: Instance<'tcx>)
{
let type_length = instance.substs.types().flat_map(|ty| ty.walk()).count();
debug!(" => type length={}", type_length);

// Rust code can easily create exponentially-long types using only a
// polynomial recursion depth. Even with the default recursion
// depth, you can easily get cases that take >2^60 steps to run,
// which means that rustc basically hangs.
//
// Bail out in these cases to avoid that bad user experience.
let type_length_limit = tcx.sess.type_length_limit.get();
if type_length > type_length_limit {
// The instance name is already known to be too long for rustc. Use
// `{:.64}` to avoid blasting the user's terminal with thousands of
// lines of type-name.
let instance_name = instance.to_string();
let msg = format!("reached the type-length limit while instantiating `{:.64}...`",
instance_name);
let mut diag = if let Some(node_id) = tcx.map.as_local_node_id(instance.def) {
tcx.sess.struct_span_fatal(tcx.map.span(node_id), &msg)
} else {
tcx.sess.struct_fatal(&msg)
};

diag.note(&format!(
"consider adding a `#![type_length_limit=\"{}\"]` attribute to your crate",
type_length_limit*2));
diag.emit();
tcx.sess.abort_if_errors();
}
}

struct MirNeighborCollector<'a, 'tcx: 'a> {
scx: &'a SharedCrateContext<'a, 'tcx>,
mir: &'a mir::Mir<'tcx>,
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
("no_main", CrateLevel, Ungated),
("no_builtins", CrateLevel, Ungated),
("recursion_limit", CrateLevel, Ungated),
("type_length_limit", CrateLevel, Ungated),
];

// cfg(...)'s that are feature gated
Expand Down
3 changes: 2 additions & 1 deletion src/test/compile-fail/issue-22638.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

#![allow(unused)]

#![recursion_limit = "32"]
#![recursion_limit = "20"]
#![type_length_limit = "20000000"]

#[derive(Clone)]
struct A (B);
Expand Down
35 changes: 35 additions & 0 deletions src/test/compile-fail/type_length_limit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// error-pattern: reached the type-length limit while instantiating

// Test that the type length limit can be changed.

#![allow(dead_code)]
#![type_length_limit="256"]

macro_rules! link {
($id:ident, $t:ty) => {
pub type $id = ($t, $t, $t);
}
}

link! { A, B }
link! { B, C }
link! { C, D }
link! { D, E }
link! { E, F }
link! { F, G }

pub struct G;

fn main() {
drop::<Option<A>>(None);
}
30 changes: 30 additions & 0 deletions src/test/ui/issue-37311-type-length-limit/issue-37311.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

trait Mirror {
type Image;
}

impl<T> Mirror for T { type Image = T; }

trait Foo {
fn recurse(&self);
}

impl<T> Foo for T {
#[allow(unconditional_recursion)]
fn recurse(&self) {
(self, self).recurse();
}
}

fn main() {
().recurse();
}
13 changes: 13 additions & 0 deletions src/test/ui/issue-37311-type-length-limit/issue-37311.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error: reached the type-length limit while instantiating `<T as Foo><(&(&(&(&(&(&(&(&(&(&(&(&(&(&(&(&(&(&(&(), &()), &(&()...`
--> $DIR/issue-37311.rs:23:5
|
23 | fn recurse(&self) {
| _____^ starting here...
24 | | (self, self).recurse();
25 | | }
| |_____^ ...ending here
|
= note: consider adding a `#![type_length_limit="2097152"]` attribute to your crate

error: aborting due to previous error