about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVadim Petrochenkov <vadim.petrochenkov@gmail.com>2019-07-19 00:24:58 +0300
committerVadim Petrochenkov <vadim.petrochenkov@gmail.com>2019-07-24 12:29:44 +0300
commit433024147ae1e9795fe7e94cb1810a17fd37fa51 (patch)
tree3afa866aa7a338c7f04dc6da7ac4798d9584e2c0
parenta93fdfedf36dcb909d90cbf963b087c5873bec1d (diff)
downloadrust-433024147ae1e9795fe7e94cb1810a17fd37fa51.tar.gz
rust-433024147ae1e9795fe7e94cb1810a17fd37fa51.zip
syntax_ext: Turn `#[global_allocator]` into a regular attribute macro
-rw-r--r--src/libcore/macros.rs7
-rw-r--r--src/librustc/middle/dead.rs5
-rw-r--r--src/librustc_interface/passes.rs15
-rw-r--r--src/librustc_metadata/creader.rs29
-rw-r--r--src/librustc_metadata/lib.rs5
-rw-r--r--src/librustc_passes/ast_validation.rs10
-rw-r--r--src/libsyntax/ext/allocator.rs22
-rw-r--r--src/libsyntax/feature_gate.rs1
-rw-r--r--src/libsyntax_ext/global_allocator.rs223
-rw-r--r--src/libsyntax_ext/lib.rs8
-rw-r--r--src/test/run-pass/allocator/custom-in-block.rs21
-rw-r--r--src/test/run-pass/allocator/custom-in-submodule.rs25
-rw-r--r--src/test/ui/allocator-submodule.rs28
-rw-r--r--src/test/ui/allocator-submodule.stderr8
-rw-r--r--src/test/ui/allocator/allocator-args.rs13
-rw-r--r--src/test/ui/allocator/allocator-args.stderr8
-rw-r--r--src/test/ui/allocator/two-allocators.rs2
-rw-r--r--src/test/ui/allocator/two-allocators.stderr8
-rw-r--r--src/tools/tidy/src/features.rs2
19 files changed, 205 insertions, 235 deletions
diff --git a/src/libcore/macros.rs b/src/libcore/macros.rs
index 296bb43f9fa..17781798946 100644
--- a/src/libcore/macros.rs
+++ b/src/libcore/macros.rs
@@ -1281,6 +1281,13 @@ mod builtin {
     #[rustc_macro_transparency = "semitransparent"]
     pub macro test_case($item:item) { /* compiler built-in */ }
 
+    /// Attribute macro applied to a static to register it as a global allocator.
+    #[stable(feature = "global_allocator", since = "1.28.0")]
+    #[allow_internal_unstable(rustc_attrs)]
+    #[rustc_builtin_macro]
+    #[rustc_macro_transparency = "semitransparent"]
+    pub macro global_allocator($item:item) { /* compiler built-in */ }
+
     /// Derive macro generating an impl of the trait `Clone`.
     #[rustc_builtin_macro]
     #[rustc_macro_transparency = "semitransparent"]
diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs
index 4c27318c3e1..88de77829a6 100644
--- a/src/librustc/middle/dead.rs
+++ b/src/librustc/middle/dead.rs
@@ -320,11 +320,6 @@ fn has_allow_dead_code_or_lang_attr(
         return true;
     }
 
-    // Don't lint about global allocators
-    if attr::contains_name(attrs, sym::global_allocator) {
-        return true;
-    }
-
     let def_id = tcx.hir().local_def_id(id);
     let cg_attrs = tcx.codegen_fn_attrs(def_id);
 
diff --git a/src/librustc_interface/passes.rs b/src/librustc_interface/passes.rs
index d4a922ddd49..b460a908f1b 100644
--- a/src/librustc_interface/passes.rs
+++ b/src/librustc_interface/passes.rs
@@ -468,7 +468,7 @@ fn configure_and_expand_inner<'a>(
         util::ReplaceBodyWithLoop::new(sess).visit_crate(&mut krate);
     }
 
-    let (has_proc_macro_decls, has_global_allocator) = time(sess, "AST validation", || {
+    let has_proc_macro_decls = time(sess, "AST validation", || {
         ast_validation::check_crate(sess, &krate)
     });
 
@@ -494,19 +494,6 @@ fn configure_and_expand_inner<'a>(
         });
     }
 
-    if has_global_allocator {
-        // Expand global allocators, which are treated as an in-tree proc macro
-        time(sess, "creating allocators", || {
-            syntax_ext::global_allocator::modify(
-                &sess.parse_sess,
-                &mut resolver,
-                &mut krate,
-                crate_name.to_string(),
-                sess.diagnostic(),
-            )
-        });
-    }
-
     // Done with macro expansion!
 
     if sess.opts.debugging_opts.input_stats {
diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs
index 31f70155b4e..3404ec5e173 100644
--- a/src/librustc_metadata/creader.rs
+++ b/src/librustc_metadata/creader.rs
@@ -25,10 +25,9 @@ use std::{cmp, fs};
 
 use syntax::ast;
 use syntax::attr;
-use syntax::ext::allocator::AllocatorKind;
+use syntax::ext::allocator::{global_allocator_spans, AllocatorKind};
 use syntax::ext::base::{SyntaxExtension, SyntaxExtensionKind};
 use syntax::symbol::{Symbol, sym};
-use syntax::visit;
 use syntax::{span_err, span_fatal};
 use syntax_pos::{Span, DUMMY_SP};
 use log::{debug, info, log_enabled};
@@ -888,7 +887,14 @@ impl<'a> CrateLoader<'a> {
     }
 
     fn inject_allocator_crate(&mut self, krate: &ast::Crate) {
-        let has_global_allocator = has_global_allocator(krate);
+        let has_global_allocator = match &*global_allocator_spans(krate) {
+            [span1, span2, ..] => {
+                self.sess.struct_span_err(*span2, "cannot define multiple global allocators")
+                         .span_note(*span1, "the previous global allocator is defined here").emit();
+                true
+            }
+            spans => !spans.is_empty()
+        };
         self.sess.has_global_allocator.set(has_global_allocator);
 
         // Check to see if we actually need an allocator. This desire comes
@@ -975,25 +981,8 @@ impl<'a> CrateLoader<'a> {
                            that implements the GlobalAlloc trait.");
         }
         self.sess.allocator_kind.set(Some(AllocatorKind::DefaultLib));
-
-        fn has_global_allocator(krate: &ast::Crate) -> bool {
-            struct Finder(bool);
-            let mut f = Finder(false);
-            visit::walk_crate(&mut f, krate);
-            return f.0;
-
-            impl<'ast> visit::Visitor<'ast> for Finder {
-                fn visit_item(&mut self, i: &'ast ast::Item) {
-                    if attr::contains_name(&i.attrs, sym::global_allocator) {
-                        self.0 = true;
-                    }
-                    visit::walk_item(self, i)
-                }
-            }
-        }
     }
 
-
     fn inject_dependency_if(&self,
                             krate: CrateNum,
                             what: &str,
diff --git a/src/librustc_metadata/lib.rs b/src/librustc_metadata/lib.rs
index 826349362db..30bb37e4680 100644
--- a/src/librustc_metadata/lib.rs
+++ b/src/librustc_metadata/lib.rs
@@ -1,6 +1,7 @@
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
 
 #![feature(box_patterns)]
+#![feature(crate_visibility_modifier)]
 #![feature(drain_filter)]
 #![feature(in_band_lifetimes)]
 #![feature(libc)]
@@ -8,9 +9,9 @@
 #![feature(proc_macro_internals)]
 #![feature(proc_macro_quote)]
 #![feature(rustc_diagnostic_macros)]
-#![feature(crate_visibility_modifier)]
-#![feature(specialization)]
 #![feature(rustc_private)]
+#![feature(slice_patterns)]
+#![feature(specialization)]
 
 #![recursion_limit="256"]
 
diff --git a/src/librustc_passes/ast_validation.rs b/src/librustc_passes/ast_validation.rs
index 56063596299..b550029d978 100644
--- a/src/librustc_passes/ast_validation.rs
+++ b/src/librustc_passes/ast_validation.rs
@@ -51,7 +51,6 @@ impl OuterImplTrait {
 struct AstValidator<'a> {
     session: &'a Session,
     has_proc_macro_decls: bool,
-    has_global_allocator: bool,
 
     /// Used to ban nested `impl Trait`, e.g., `impl Into<impl Debug>`.
     /// Nested `impl Trait` _is_ allowed in associated type position,
@@ -539,10 +538,6 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
             self.has_proc_macro_decls = true;
         }
 
-        if attr::contains_name(&item.attrs, sym::global_allocator) {
-            self.has_global_allocator = true;
-        }
-
         match item.node {
             ItemKind::Impl(unsafety, polarity, _, _, Some(..), ref ty, ref impl_items) => {
                 self.invalid_visibility(&item.vis, None);
@@ -848,11 +843,10 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
     }
 }
 
-pub fn check_crate(session: &Session, krate: &Crate) -> (bool, bool) {
+pub fn check_crate(session: &Session, krate: &Crate) -> bool {
     let mut validator = AstValidator {
         session,
         has_proc_macro_decls: false,
-        has_global_allocator: false,
         outer_impl_trait: None,
         is_impl_trait_banned: false,
         is_assoc_ty_bound_banned: false,
@@ -861,5 +855,5 @@ pub fn check_crate(session: &Session, krate: &Crate) -> (bool, bool) {
     };
     visit::walk_crate(&mut validator, krate);
 
-    (validator.has_proc_macro_decls, validator.has_global_allocator)
+    validator.has_proc_macro_decls
 }
diff --git a/src/libsyntax/ext/allocator.rs b/src/libsyntax/ext/allocator.rs
index f2c6bf27cee..99aeb5414c5 100644
--- a/src/libsyntax/ext/allocator.rs
+++ b/src/libsyntax/ext/allocator.rs
@@ -1,3 +1,7 @@
+use crate::{ast, attr, visit};
+use crate::symbol::{sym, Symbol};
+use syntax_pos::Span;
+
 #[derive(Clone, Copy)]
 pub enum AllocatorKind {
     Global,
@@ -51,3 +55,21 @@ pub static ALLOCATOR_METHODS: &[AllocatorMethod] = &[
         output: AllocatorTy::ResultPtr,
     },
 ];
+
+pub fn global_allocator_spans(krate: &ast::Crate) -> Vec<Span> {
+    struct Finder { name: Symbol, spans: Vec<Span> }
+    impl<'ast> visit::Visitor<'ast> for Finder {
+        fn visit_item(&mut self, item: &'ast ast::Item) {
+            if item.ident.name == self.name &&
+               attr::contains_name(&item.attrs, sym::rustc_std_internal_symbol) {
+                self.spans.push(item.span);
+            }
+            visit::walk_item(self, item)
+        }
+    }
+
+    let name = Symbol::intern(&AllocatorKind::Global.fn_name("alloc"));
+    let mut f = Finder { name, spans: Vec::new() };
+    visit::walk_crate(&mut f, krate);
+    f.spans
+}
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index 43f0eaae7c9..214160f1079 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -1117,7 +1117,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
                                             "the `#[rustc_const_unstable]` attribute \
                                             is an internal feature",
                                             cfg_fn!(rustc_const_unstable))),
-    (sym::global_allocator, Normal, template!(Word), Ungated),
     (sym::default_lib_allocator, Whitelisted, template!(Word), Gated(Stability::Unstable,
                                             sym::allocator_internals,
                                             "the `#[default_lib_allocator]` \
diff --git a/src/libsyntax_ext/global_allocator.rs b/src/libsyntax_ext/global_allocator.rs
index e8f94bff144..785636abb12 100644
--- a/src/libsyntax_ext/global_allocator.rs
+++ b/src/libsyntax_ext/global_allocator.rs
@@ -1,162 +1,95 @@
-use log::debug;
-use smallvec::{smallvec, SmallVec};
-use syntax::{
-    ast::{
-        self, Arg, Attribute, Crate, Expr, FnHeader, Generics, Ident, Item, ItemKind,
-        Mac, Mod, Mutability, Ty, TyKind, Unsafety, VisibilityKind,
-    },
-    attr,
-    source_map::{
-        respan, ExpnInfo, ExpnKind,
-    },
-    ext::{
-        allocator::{AllocatorKind, AllocatorMethod, AllocatorTy, ALLOCATOR_METHODS},
-        base::{ExtCtxt, MacroKind, Resolver},
-        build::AstBuilder,
-        expand::ExpansionConfig,
-        hygiene::ExpnId,
-    },
-    mut_visit::{self, MutVisitor},
-    parse::ParseSess,
-    ptr::P,
-    symbol::{kw, sym}
-};
+use errors::Applicability;
+use syntax::ast::{self, Arg, Attribute, Expr, FnHeader, Generics, Ident, Item};
+use syntax::ast::{ItemKind, Mutability, Ty, TyKind, Unsafety, VisibilityKind};
+use syntax::source_map::respan;
+use syntax::ext::allocator::{AllocatorKind, AllocatorMethod, AllocatorTy, ALLOCATOR_METHODS};
+use syntax::ext::base::{Annotatable, ExtCtxt};
+use syntax::ext::build::AstBuilder;
+use syntax::ext::hygiene::SyntaxContext;
+use syntax::ptr::P;
+use syntax::symbol::{kw, sym};
 use syntax_pos::Span;
 
-pub fn modify(
-    sess: &ParseSess,
-    resolver: &mut dyn Resolver,
-    krate: &mut Crate,
-    crate_name: String,
-    handler: &errors::Handler,
-) {
-    ExpandAllocatorDirectives {
-        handler,
-        sess,
-        resolver,
-        found: false,
-        crate_name: Some(crate_name),
-        in_submod: -1, // -1 to account for the "root" module
-    }.visit_crate(krate);
-}
-
-struct ExpandAllocatorDirectives<'a> {
-    found: bool,
-    handler: &'a errors::Handler,
-    sess: &'a ParseSess,
-    resolver: &'a mut dyn Resolver,
-    crate_name: Option<String>,
-
-    // For now, we disallow `global_allocator` in submodules because hygiene is hard. Keep track of
-    // whether we are in a submodule or not. If `in_submod > 0` we are in a submodule.
-    in_submod: isize,
-}
-
-impl MutVisitor for ExpandAllocatorDirectives<'_> {
-    fn flat_map_item(&mut self, item: P<Item>) -> SmallVec<[P<Item>; 1]> {
-        debug!("in submodule {}", self.in_submod);
-
-        if !attr::contains_name(&item.attrs, sym::global_allocator) {
-            return mut_visit::noop_flat_map_item(item, self);
-        }
-
-        match item.node {
-            ItemKind::Static(..) => {}
-            _ => {
-                self.handler
-                    .span_err(item.span, "allocators must be statics");
-                return smallvec![item];
-            }
-        }
-
-        if self.in_submod > 0 {
-            self.handler
-                .span_err(item.span, "`global_allocator` cannot be used in submodules");
-            return smallvec![item];
-        }
+pub fn expand(
+    ecx: &mut ExtCtxt<'_>,
+    span: Span,
+    meta_item: &ast::MetaItem,
+    item: Annotatable,
+) -> Vec<Annotatable> {
+    if !meta_item.is_word() {
+        let msg = format!("malformed `{}` attribute input", meta_item.path);
+        ecx.parse_sess.span_diagnostic.struct_span_err(span, &msg)
+            .span_suggestion(
+                span,
+                "must be of the form",
+                format!("`#[{}]`", meta_item.path),
+                Applicability::MachineApplicable
+            ).emit();
+    }
 
-        if self.found {
-            self.handler
-                .span_err(item.span, "cannot define more than one `#[global_allocator]`");
-            return smallvec![item];
+    let not_static = |item: Annotatable| {
+        ecx.parse_sess.span_diagnostic.span_err(item.span(), "allocators must be statics");
+        vec![item]
+    };
+    let item = match item {
+        Annotatable::Item(item) => match item.node {
+            ItemKind::Static(..) => item,
+            _ => return not_static(Annotatable::Item(item)),
         }
-        self.found = true;
-
-        // Create a new expansion for the generated allocator code.
-        let span = item.span.fresh_expansion(ExpnId::root(), ExpnInfo::allow_unstable(
-            ExpnKind::Macro(MacroKind::Attr, sym::global_allocator), item.span, self.sess.edition,
-            [sym::rustc_attrs][..].into(),
-        ));
-
-        // Create an expansion config
-        let ecfg = ExpansionConfig::default(self.crate_name.take().unwrap());
-
-        // Generate a bunch of new items using the AllocFnFactory
-        let mut f = AllocFnFactory {
-            span,
-            kind: AllocatorKind::Global,
-            global: item.ident,
-            core: Ident::with_empty_ctxt(sym::core),
-            cx: ExtCtxt::new(self.sess, ecfg, self.resolver),
-        };
-
-        // We will generate a new submodule. To `use` the static from that module, we need to get
-        // the `super::...` path.
-        let super_path = f.cx.path(f.span, vec![Ident::with_empty_ctxt(kw::Super), f.global]);
-
-        // Generate the items in the submodule
-        let mut items = vec![
-            // import `core` to use allocators
-            f.cx.item_extern_crate(f.span, f.core),
-            // `use` the `global_allocator` in `super`
-            f.cx.item_use_simple(
-                f.span,
-                respan(f.span.shrink_to_lo(), VisibilityKind::Inherited),
-                super_path,
-            ),
-        ];
-
-        // Add the allocator methods to the submodule
-        items.extend(
-            ALLOCATOR_METHODS
-                .iter()
-                .map(|method| f.allocator_fn(method)),
-        );
-
-        // Generate the submodule itself
-        let name = f.kind.fn_name("allocator_abi");
-        let allocator_abi = Ident::from_str(&name).gensym();
-        let module = f.cx.item_mod(span, span, allocator_abi, Vec::new(), items);
-        let module = f.cx.monotonic_expander().flat_map_item(module).pop().unwrap();
-
-        // Return the item and new submodule
-        smallvec![item, module]
-    }
+        _ => return not_static(item),
+    };
+
+    // Generate a bunch of new items using the AllocFnFactory
+    let span = item.span.with_ctxt(SyntaxContext::empty().apply_mark(ecx.current_expansion.mark));
+    let f = AllocFnFactory {
+        span,
+        kind: AllocatorKind::Global,
+        global: item.ident,
+        core: Ident::with_empty_ctxt(sym::core),
+        cx: ecx,
+    };
+
+    // We will generate a new submodule. To `use` the static from that module, we need to get
+    // the `super::...` path.
+    let super_path = f.cx.path(f.span, vec![Ident::with_empty_ctxt(kw::Super), f.global]);
+
+    // Generate the items in the submodule
+    let mut items = vec![
+        // import `core` to use allocators
+        f.cx.item_extern_crate(f.span, f.core),
+        // `use` the `global_allocator` in `super`
+        f.cx.item_use_simple(
+            f.span,
+            respan(f.span.shrink_to_lo(), VisibilityKind::Inherited),
+            super_path,
+        ),
+    ];
+
+    // Add the allocator methods to the submodule
+    items.extend(
+        ALLOCATOR_METHODS
+            .iter()
+            .map(|method| f.allocator_fn(method)),
+    );
 
-    // If we enter a submodule, take note.
-    fn visit_mod(&mut self, m: &mut Mod) {
-        debug!("enter submodule");
-        self.in_submod += 1;
-        mut_visit::noop_visit_mod(m, self);
-        self.in_submod -= 1;
-        debug!("exit submodule");
-    }
+    // Generate the submodule itself
+    let name = f.kind.fn_name("allocator_abi");
+    let allocator_abi = Ident::from_str(&name).gensym();
+    let module = f.cx.item_mod(span, span, allocator_abi, Vec::new(), items);
 
-    // `visit_mac` is disabled by default. Enable it here.
-    fn visit_mac(&mut self, mac: &mut Mac) {
-        mut_visit::noop_visit_mac(mac, self)
-    }
+    // Return the item and new submodule
+    vec![Annotatable::Item(item), Annotatable::Item(module)]
 }
 
-struct AllocFnFactory<'a> {
+struct AllocFnFactory<'a, 'b> {
     span: Span,
     kind: AllocatorKind,
     global: Ident,
     core: Ident,
-    cx: ExtCtxt<'a>,
+    cx: &'b ExtCtxt<'a>,
 }
 
-impl AllocFnFactory<'_> {
+impl AllocFnFactory<'_, '_> {
     fn allocator_fn(&self, method: &AllocatorMethod) -> P<Item> {
         let mut abi_args = Vec::new();
         let mut i = 0;
diff --git a/src/libsyntax_ext/lib.rs b/src/libsyntax_ext/lib.rs
index cd7ac5fe2c6..400bfe796bb 100644
--- a/src/libsyntax_ext/lib.rs
+++ b/src/libsyntax_ext/lib.rs
@@ -29,6 +29,7 @@ mod concat_idents;
 mod env;
 mod format;
 mod format_foreign;
+mod global_allocator;
 mod global_asm;
 mod log_syntax;
 mod proc_macro_server;
@@ -37,7 +38,6 @@ mod test_case;
 mod trace_macros;
 
 pub mod deriving;
-pub mod global_allocator;
 pub mod proc_macro_decls;
 pub mod proc_macro_impl;
 
@@ -152,6 +152,12 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver,
             SyntaxExtensionKind::LegacyAttr(Box::new(test::expand_bench)), edition
         )
     });
+    register(sym::global_allocator, SyntaxExtension {
+        allow_internal_unstable: Some([sym::rustc_attrs][..].into()),
+        ..SyntaxExtension::default(
+            SyntaxExtensionKind::LegacyAttr(Box::new(global_allocator::expand)), edition
+        )
+    });
 
     let allow_internal_unstable = Some([sym::fmt_internals][..].into());
     register(sym::format_args, SyntaxExtension {
diff --git a/src/test/run-pass/allocator/custom-in-block.rs b/src/test/run-pass/allocator/custom-in-block.rs
new file mode 100644
index 00000000000..506154ec601
--- /dev/null
+++ b/src/test/run-pass/allocator/custom-in-block.rs
@@ -0,0 +1,21 @@
+// run-pass
+// no-prefer-dynamic
+// aux-build:custom.rs
+// aux-build:helper.rs
+
+extern crate custom;
+extern crate helper;
+
+use custom::A;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+fn main() {
+    #[global_allocator]
+    pub static GLOBAL: A = A(AtomicUsize::new(0));
+
+    let s = Box::new(0);
+    helper::work_with(&s);
+    assert_eq!(GLOBAL.0.load(Ordering::SeqCst), 1);
+    drop(s);
+    assert_eq!(GLOBAL.0.load(Ordering::SeqCst), 2);
+}
diff --git a/src/test/run-pass/allocator/custom-in-submodule.rs b/src/test/run-pass/allocator/custom-in-submodule.rs
new file mode 100644
index 00000000000..f9c4ad7668b
--- /dev/null
+++ b/src/test/run-pass/allocator/custom-in-submodule.rs
@@ -0,0 +1,25 @@
+// run-pass
+// no-prefer-dynamic
+// aux-build:custom.rs
+// aux-build:helper.rs
+
+extern crate custom;
+extern crate helper;
+
+use custom::A;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+mod submodule {
+    use super::*;
+
+    #[global_allocator]
+    pub static GLOBAL: A = A(AtomicUsize::new(0));
+}
+
+fn main() {
+    let s = Box::new(0);
+    helper::work_with(&s);
+    assert_eq!(submodule::GLOBAL.0.load(Ordering::SeqCst), 1);
+    drop(s);
+    assert_eq!(submodule::GLOBAL.0.load(Ordering::SeqCst), 2);
+}
diff --git a/src/test/ui/allocator-submodule.rs b/src/test/ui/allocator-submodule.rs
deleted file mode 100644
index 7a8d86b8da1..00000000000
--- a/src/test/ui/allocator-submodule.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Tests that it is possible to create a global allocator in a submodule, rather than in the crate
-// root.
-
-extern crate alloc;
-
-use std::{
-    alloc::{GlobalAlloc, Layout},
-    ptr,
-};
-
-struct MyAlloc;
-
-unsafe impl GlobalAlloc for MyAlloc {
-    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
-        ptr::null_mut()
-    }
-
-    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {}
-}
-
-mod submod {
-    use super::MyAlloc;
-
-    #[global_allocator]
-    static MY_HEAP: MyAlloc = MyAlloc; //~ ERROR global_allocator
-}
-
-fn main() {}
diff --git a/src/test/ui/allocator-submodule.stderr b/src/test/ui/allocator-submodule.stderr
deleted file mode 100644
index 91c7c0f6b8e..00000000000
--- a/src/test/ui/allocator-submodule.stderr
+++ /dev/null
@@ -1,8 +0,0 @@
-error: `global_allocator` cannot be used in submodules
-  --> $DIR/allocator-submodule.rs:25:5
-   |
-LL |     static MY_HEAP: MyAlloc = MyAlloc;
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to previous error
-
diff --git a/src/test/ui/allocator/allocator-args.rs b/src/test/ui/allocator/allocator-args.rs
new file mode 100644
index 00000000000..1033f947c5f
--- /dev/null
+++ b/src/test/ui/allocator/allocator-args.rs
@@ -0,0 +1,13 @@
+use std::alloc::{GlobalAlloc, Layout};
+
+struct A;
+
+unsafe impl GlobalAlloc for A {
+    unsafe fn alloc(&self, _: Layout) -> *mut u8 { panic!() }
+    unsafe fn dealloc(&self, _: *mut u8, _: Layout) { panic!() }
+}
+
+#[global_allocator(malloc)] //~ ERROR malformed `global_allocator` attribute input
+static S: A = A;
+
+fn main() {}
diff --git a/src/test/ui/allocator/allocator-args.stderr b/src/test/ui/allocator/allocator-args.stderr
new file mode 100644
index 00000000000..d8ae7130e5d
--- /dev/null
+++ b/src/test/ui/allocator/allocator-args.stderr
@@ -0,0 +1,8 @@
+error: malformed `global_allocator` attribute input
+  --> $DIR/allocator-args.rs:10:1
+   |
+LL | #[global_allocator(malloc)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: ``#[global_allocator]``
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/allocator/two-allocators.rs b/src/test/ui/allocator/two-allocators.rs
index 0f81fc41823..aa1291e77ae 100644
--- a/src/test/ui/allocator/two-allocators.rs
+++ b/src/test/ui/allocator/two-allocators.rs
@@ -4,6 +4,6 @@ use std::alloc::System;
 static A: System = System;
 #[global_allocator]
 static B: System = System;
-//~^ ERROR: cannot define more than one `#[global_allocator]`
+//~^ ERROR: cannot define multiple global allocators
 
 fn main() {}
diff --git a/src/test/ui/allocator/two-allocators.stderr b/src/test/ui/allocator/two-allocators.stderr
index 6b0c2b2a25d..ed0aa13eb80 100644
--- a/src/test/ui/allocator/two-allocators.stderr
+++ b/src/test/ui/allocator/two-allocators.stderr
@@ -1,8 +1,14 @@
-error: cannot define more than one `#[global_allocator]`
+error: cannot define multiple global allocators
   --> $DIR/two-allocators.rs:6:1
    |
 LL | static B: System = System;
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the previous global allocator is defined here
+  --> $DIR/two-allocators.rs:4:1
+   |
+LL | static A: System = System;
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to previous error
 
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index 1841beb1fd1..ade61abb748 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -344,7 +344,7 @@ fn get_and_check_lib_features(base_src_path: &Path,
                 Ok((name, f)) => {
                     let mut check_features = |f: &Feature, list: &Features, display: &str| {
                         if let Some(ref s) = list.get(name) {
-                            if f.tracking_issue != s.tracking_issue {
+                            if f.tracking_issue != s.tracking_issue && f.level != Status::Stable {
                                 tidy_error!(bad,
                                             "{}:{}: mismatches the `issue` in {}",
                                             file.display(),