rust-lang/rust

"Aborted (core dumped)" on typo in attribute

dwrensha opened this issue · 12 comments

rustc crashes ungracefully when given the following input (found by fuzz-rustc):

fn main() {
   let _ = || {
       #[cfg(any(target_arc+ = "arm"))]
       fn _foo() {}
   };
}
$ rustc -vV
rustc 1.44.0-nightly (74bd074ee 2020-04-03)
binary: rustc
commit-hash: 74bd074eefcf4915c73d1ab91bc90859664729e6
commit-date: 2020-04-03
host: x86_64-unknown-linux-gnu
release: 1.44.0-nightly
LLVM version: 9.0

$ rustc main.rs
error: expected one of `(`, `)`, `,`, `::`, or `=`, found `+`
 --> main.rs:3:28
  |
3 |        #[cfg(any(target_arc+ = "arm"))]
  |                            ^ expected one of `(`, `)`, `,`, `::`, or `=`

Aborted (core dumped)

According to gdb, the abort happens in rustc_ast::mut_visit::visit_clobber::{{closure}}, as it did with #62894.

Here's a somewhat cuter test case that hits the same failure:

fn main() {
   let _ = || {
       #[cfg(any(target_arch == "arm"))]
       let _ = ();
   };
}
$ rustc main.rs
error: expected one of `(`, `)`, `,`, `::`, or `=`, found `==`
 --> main.rs:3:30
  |
3 |        #[cfg(any(target_arch == "arm"))]
  |                              ^^ expected one of `(`, `)`, `,`, `::`, or `=`

Aborted (core dumped)

cc @Centril, I think you might have been around that code recently.

Simplified:

const _: _ = [#[cfg(ident(ident ==))] 0];

Backtrace:

centril@centrilnas2:~/programming/rust/rust-gamma$ rustc +gamma-stage1 foo.rs
error: expected one of `(`, `)`, `,`, `::`, or `=`, found `==`
 --> foo.rs:1:33
  |
1 | const _: _ = [#[cfg(ident(ident == "arm"))] 0];
  |                                 ^^ expected one of `(`, `)`, `,`, `::`, or `=`

   0: rustc_ast::mut_visit::visit_clobber::{{closure}}
             at ./src/librustc_ast/mut_visit.rs:316
   1: core::result::Result<T,E>::unwrap_or_else
             at ./src/libcore/result.rs:851
      rustc_ast::mut_visit::visit_clobber
             at ./src/librustc_ast/mut_visit.rs:315
   2: <rustc_expand::expand::InvocationCollector as rustc_ast::mut_visit::MutVisitor>::visit_expr
             at src/librustc_expand/expand.rs:1171
      rustc_ast::mut_visit::noop_visit_item_kind::{{closure}}
             at ./src/librustc_ast/mut_visit.rs:887
      rustc_ast::mut_visit::visit_opt
             at ./src/librustc_ast/mut_visit.rs:341
      rustc_ast::mut_visit::noop_visit_item_kind
             at ./src/librustc_ast/mut_visit.rs:887
   3: <rustc_expand::expand::InvocationCollector as rustc_ast::mut_visit::MutVisitor>::visit_item_kind     
             at src/librustc_expand/expand.rs:1642
      rustc_ast::mut_visit::noop_flat_map_item
             at ./src/librustc_ast/mut_visit.rs:1017
   4: <rustc_expand::expand::InvocationCollector as rustc_ast::mut_visit::MutVisitor>::flat_map_item       
             at src/librustc_expand/expand.rs:1521
   5: rustc_ast::mut_visit::noop_visit_mod::{{closure}}
             at ./src/librustc_ast/mut_visit.rs:976
      <alloc::vec::Vec<T> as rustc_ast::util::map_in_place::MapInPlace<T>>::flat_map_in_place
             at ./src/librustc_ast/util/map_in_place.rs:36
   6: rustc_ast::mut_visit::noop_visit_mod
             at ./src/librustc_ast/mut_visit.rs:976
      rustc_ast::mut_visit::MutVisitor::visit_mod
             at ./src/librustc_ast/mut_visit.rs:166
      rustc_ast::mut_visit::noop_visit_item_kind
             at ./src/librustc_ast/mut_visit.rs:894
   7: <rustc_expand::expand::InvocationCollector as rustc_ast::mut_visit::MutVisitor>::visit_item_kind     
             at src/librustc_expand/expand.rs:1642
      rustc_ast::mut_visit::noop_flat_map_item
             at ./src/librustc_ast/mut_visit.rs:1017
   8: <rustc_expand::expand::InvocationCollector as rustc_ast::mut_visit::MutVisitor>::flat_map_item       
             at src/librustc_expand/expand.rs:1521
   9: rustc_expand::expand::AstFragment::mut_visit_with::{{closure}}
             at src/librustc_expand/expand.rs:121
      <smallvec::SmallVec<A> as rustc_ast::util::map_in_place::MapInPlace<T>>::flat_map_in_place
             at ./src/librustc_ast/util/map_in_place.rs:82
  10: rustc_expand::expand::AstFragment::mut_visit_with
             at src/librustc_expand/expand.rs:121
      rustc_expand::expand::MacroExpander::collect_invocations
             at src/librustc_expand/expand.rs:577
  11: rustc_expand::expand::MacroExpander::fully_expand_fragment
             at src/librustc_expand/expand.rs:396
  12: rustc_expand::expand::MacroExpander::expand_crate
             at src/librustc_expand/expand.rs:363
  13: rustc_interface::passes::configure_and_expand_inner::{{closure}}::{{closure}}
             at src/librustc_interface/passes.rs:302
      rustc_data_structures::profiling::VerboseTimingGuard::run
             at ./src/librustc_data_structures/profiling.rs:569
      rustc_session::utils::<impl rustc_session::session::Session>::time
             at ./src/librustc_session/utils.rs:9
      rustc_interface::passes::configure_and_expand_inner::{{closure}}
             at src/librustc_interface/passes.rs:302
      rustc_data_structures::profiling::VerboseTimingGuard::run
             at ./src/librustc_data_structures/profiling.rs:569
      rustc_session::utils::<impl rustc_session::session::Session>::time
             at ./src/librustc_session/utils.rs:9
  14: rustc_interface::passes::configure_and_expand_inner
             at src/librustc_interface/passes.rs:256
  15: rustc_interface::passes::configure_and_expand::{{closure}}
             at src/librustc_interface/passes.rs:115
  16: alloc::boxed::<impl core::ops::generator::Generator<R> for core::pin::Pin<alloc::boxed::Box<G>>>::resume
             at ./src/liballoc/boxed.rs:1115
      rustc_data_structures::box_region::PinnedGenerator<I,A,R>::new
             at ./src/librustc_data_structures/box_region.rs:34
  17: rustc_interface::passes::BoxedResolver::new
             at ./<::rustc_data_structures::box_region::declare_box_region_type macros>:12
      rustc_interface::passes::configure_and_expand
             at src/librustc_interface/passes.rs:112
  18: rustc_interface::queries::Queries::expansion::{{closure}}
             at src/librustc_interface/queries.rs:176
      rustc_interface::queries::Query<T>::compute
             at src/librustc_interface/queries.rs:34
      rustc_interface::queries::Queries::expansion
             at src/librustc_interface/queries.rs:172
  19: rustc_driver::run_compiler::{{closure}}::{{closure}}
             at src/librustc_driver/lib.rs:337
      rustc_interface::queries::<impl rustc_interface::interface::Compiler>::enter
             at ./src/librustc_interface/queries.rs:385
      rustc_driver::run_compiler::{{closure}}
             at src/librustc_driver/lib.rs:286
      rustc_interface::interface::run_compiler_in_existing_thread_pool
             at ./src/librustc_interface/interface.rs:199
  20: rustc_interface::interface::run_compiler::{{closure}}
             at ./src/librustc_interface/interface.rs:213
      rustc_interface::util::spawn_thread_pool::{{closure}}::{{closure}}::{{closure}}
             at ./src/librustc_interface/util.rs:154
      scoped_tls::ScopedKey<T>::set
             at /home/centril/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped-tls-1.0.0/src/lib.rs:137
      rustc_interface::util::spawn_thread_pool::{{closure}}::{{closure}}
             at ./src/librustc_interface/util.rs:150
      scoped_tls::ScopedKey<T>::set
             at /home/centril/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped-tls-1.0.0/src/lib.rs:137
      rustc_ast::attr::with_globals::{{closure}}
             at ./src/librustc_ast/attr/mod.rs:44
      scoped_tls::ScopedKey<T>::set
             at /home/centril/.cargo/registry/src/github.com-1ecc6299db9ec823/scoped-tls-1.0.0/src/lib.rs:137
      rustc_ast::attr::with_globals
             at ./src/librustc_ast/attr/mod.rs:44
  21: rustc_interface::util::spawn_thread_pool::{{closure}}
             at ./src/librustc_interface/util.rs:149
      rustc_interface::util::scoped_thread::{{closure}}
             at ./src/librustc_interface/util.rs:124
      std::sys_common::backtrace::__rust_begin_short_backtrace
             at ./src/libstd/sys_common/backtrace.rs:130
  22: std::thread::Builder::spawn_unchecked::{{closure}}::{{closure}}
             at ./src/libstd/thread/mod.rs:475
      <std::panic::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
             at ./src/libstd/panic.rs:318
      std::panicking::try::do_call
             at ./src/libstd/panicking.rs:331
  23: std::panicking::try::do_try
             at src/libstd/panicking.rs:298
  24: std::panicking::try
             at ./src/libstd/panicking.rs:274
      std::panic::catch_unwind
             at ./src/libstd/panic.rs:394
      std::thread::Builder::spawn_unchecked::{{closure}}
             at ./src/libstd/thread/mod.rs:474
      core::ops::function::FnOnce::call_once{{vtable.shim}}
             at ./src/libcore/ops/function.rs:232
  25: <alloc::boxed::Box<F> as core::ops::function::FnOnce<A>>::call_once
             at ./src/liballoc/boxed.rs:1008
  26: <alloc::boxed::Box<F> as core::ops::function::FnOnce<A>>::call_once
             at ./src/liballoc/boxed.rs:1008
      std::sys_common::thread::start_thread
             at src/libstd/sys_common/thread.rs:13
  27: std::sys::unix::thread::Thread::new::thread_start
             at src/libstd/sys/unix/thread.rs:80
  28: start_thread
             at /build/glibc-KRRWSm/glibc-2.29/nptl/pthread_create.c:486
  29: __clone

Aborted (core dumped)

Something in the closure here is causing a panic:

impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
    fn visit_expr(&mut self, expr: &mut P<ast::Expr>) {
        self.cfg.configure_expr(expr);
        visit_clobber(expr.deref_mut(), |mut expr| {
            self.cfg.configure_expr_kind(&mut expr.kind);

            // ignore derives so they remain unused
            let (attr, after_derive) = self.classify_nonitem(&mut expr);

            if attr.is_some() {
                // Collect the invoc regardless of whether or not attributes are permitted here
                // expansion will eat the attribute so it won't error later.
                attr.as_ref().map(|a| self.cfg.maybe_emit_expr_attr_err(a));

                // AstFragmentKind::Expr requires the macro to emit an expression.
                return self
                    .collect_attr(
                        attr,
                        vec![],
                        Annotatable::Expr(P(expr)),
                        AstFragmentKind::Expr,
                        after_derive,
                    )
                    .make_expr()
                    .into_inner();
            }

            if let ast::ExprKind::MacCall(mac) = expr.kind {
                self.check_attributes(&expr.attrs);
                self.collect_bang(mac, expr.span, AstFragmentKind::Expr).make_expr().into_inner()
            } else {
                noop_visit_expr(&mut expr, self);
                expr
            }
        });
    }

With the definition:

/// Use a map-style function (`FnOnce(T) -> T`) to overwrite a `&mut T`. Useful
/// when using a `flat_map_*` or `filter_map_*` method within a `visit_`
/// method. Abort the program if the closure panics.
//
// No `noop_` prefix because there isn't a corresponding method in `MutVisitor`.
pub fn visit_clobber<T, F>(t: &mut T, f: F)
where
    F: FnOnce(T) -> T,
{
    unsafe {
        // Safe because `t` is used in a read-only fashion by `read()` before
        // being overwritten by `write()`.
        let old_t = ptr::read(t);
        let new_t =
            panic::catch_unwind(panic::AssertUnwindSafe(|| f(old_t))).unwrap_or_else(|_| {
                println!("{}", std::backtrace::Backtrace::force_capture());
                process::abort()
            });
        ptr::write(t, new_t);
    }
}

The problem seems to occur in the call to noop_visit_expr(&mut expr, self);, which is a fairly useless observation.

Digging further, unsurprisingly, the panic happens in the configure! in:

    fn filter_map_expr(&mut self, expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
        let expr = configure!(self, expr);
        expr.filter_map(|mut expr| {

Further down the rabbit hole, we have:

pub fn in_cfg(&self, attrs: &[Attribute]) -> bool {

which calls validate_attr::parse_meta(self.sess, attr) which in turn has:

let nmis = parse_in(sess, t.clone(), "meta list", |p| p.parse_meta_seq_top())?;

which calls:

fn parse_meta_item_inner(&mut self) -> PResult<'a, ast::NestedMetaItem> {

which has:

match self.parse_meta_item() {

so we will end up in:

    crate fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> {
        Ok(if false && self.eat(&token::Eq) {
            ast::MetaItemKind::NameValue(self.parse_unsuffixed_lit()?)
        } else if self.check(&token::OpenDelim(token::Paren)) {
            // Matches `meta_seq = ( COMMASEP(meta_item_inner) )`.
            let (list, _) = self.parse_paren_comma_seq(|p| p.parse_meta_item_inner())?;
            ast::MetaItemKind::List(list)
        } else {
            ast::MetaItemKind::Word
        })
    }

and reach the else {.

However, this was nested, so we are in the self.parse_paren_comma_seq( call here, and so we end up in fn parse_seq_to_before_tokens<T>( which has match self.expect(t) { so we end up calling self.expect_one_of(slice::from_ref(t), &[]) which ends up in:

        } else if self.last_unexpected_token_span == Some(self.token.span) {
            FatalError.raise();
        }

and there we have our panic. Making that line not trigger removes the abort here.

Unfortunately, removing the FatalError.raise() in favor of Err(...) causes a bunch of knock-on errors all over the place, and to remove with_clobber we'd need to expr.clone().

According to a git bisect I just ran:
The bug was introduced in #66994, in particular at commit 9b53c5b.

Assigning P-low and removing nomination. This was discussed as part of pre-triage meeting in Zulip.

Latest stable does not crash any more, errors gracefully.

It looks to me like the inputs above still cause rustc to abort ungracefully (using the latest rustc releases).

Also, I found another example input triggering the same crash, reported in #87790.