Add lint for unknown feature attributes

This commit is contained in:
varkor 2018-07-23 01:20:33 +01:00
parent a00ba4d71e
commit 5242dce01d
17 changed files with 266 additions and 26 deletions

View File

@ -615,6 +615,8 @@ define_dep_nodes!( <'tcx>
[input] CrateName(CrateNum),
[] ItemChildren(DefId),
[] ExternModStmtCnum(DefId),
[input] GetLibFeatures,
[] DefinedLibFeatures(CrateNum),
[input] GetLangItems,
[] DefinedLangItems(CrateNum),
[] MissingLangItems(CrateNum),

View File

@ -2138,4 +2138,5 @@ register_diagnostics! {
E0708, // `async` non-`move` closures with arguments are not currently supported
E0709, // multiple different lifetimes used in arguments of `async fn`
E0710, // an unknown tool name found in scoped lint
E0711, // a feature has been declared with conflicting stability attributes
}

View File

@ -1072,6 +1072,11 @@ impl_stable_hash_for!(struct hir::def::Export {
span
});
impl_stable_hash_for!(struct ::middle::lib_features::LibFeatures {
stable,
unstable
});
impl<'a> HashStable<StableHashingContext<'a>> for ::middle::lang_items::LangItem {
fn hash_stable<W: StableHasherResult>(&self,
_: &mut StableHashingContext<'a>,

View File

@ -141,6 +141,7 @@ pub mod middle {
pub mod exported_symbols;
pub mod free_region;
pub mod intrinsicck;
pub mod lib_features;
pub mod lang_items;
pub mod liveness;
pub mod mem_categorization;

View File

@ -102,7 +102,13 @@ declare_lint! {
declare_lint! {
pub UNUSED_FEATURES,
Warn,
"unused or unknown features found in crate-level #[feature] directives"
"unused features found in crate-level #[feature] directives"
}
declare_lint! {
pub UNKNOWN_FEATURES,
Deny,
"unknown features found in crate-level #[feature] directives"
}
declare_lint! {
@ -362,6 +368,7 @@ impl LintPass for HardwiredLints {
UNUSED_MACROS,
WARNINGS,
UNUSED_FEATURES,
UNKNOWN_FEATURES,
STABLE_FEATURES,
UNKNOWN_CRATE_TYPES,
TRIVIAL_CASTS,

View File

@ -0,0 +1,153 @@
// Copyright 2018 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.
// Detecting lib features (i.e. features that are not lang features).
//
// These are declared using stability attributes (e.g. `#[stable(..)]`
// and `#[unstable(..)]`), but are not declared in one single location
// (unlike lang features), which means we need to collect them instead.
use ty::TyCtxt;
use syntax::symbol::Symbol;
use syntax::ast::{Attribute, MetaItem, MetaItemKind};
use syntax_pos::{Span, DUMMY_SP};
use hir;
use hir::itemlikevisit::ItemLikeVisitor;
use rustc_data_structures::fx::{FxHashSet, FxHashMap};
use errors::DiagnosticId;
pub struct LibFeatures {
// A map from feature to stabilisation version.
pub stable: FxHashMap<Symbol, Symbol>,
pub unstable: FxHashSet<Symbol>,
}
impl LibFeatures {
fn new() -> LibFeatures {
LibFeatures {
stable: FxHashMap(),
unstable: FxHashSet(),
}
}
pub fn iter(&self) -> Vec<(Symbol, Option<Symbol>)> {
self.stable.iter().map(|(f, s)| (*f, Some(*s)))
.chain(self.unstable.iter().map(|f| (*f, None)))
.collect()
}
}
pub struct LibFeatureCollector<'a, 'tcx: 'a> {
tcx: TyCtxt<'a, 'tcx, 'tcx>,
lib_features: LibFeatures,
}
impl<'a, 'tcx> LibFeatureCollector<'a, 'tcx> {
fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> LibFeatureCollector<'a, 'tcx> {
LibFeatureCollector {
tcx,
lib_features: LibFeatures::new(),
}
}
fn extract(&self, attrs: &[Attribute]) -> Vec<(Symbol, Option<Symbol>, Span)> {
let stab_attrs = vec!["stable", "unstable", "rustc_const_unstable"];
let mut features = vec![];
for attr in attrs {
// FIXME(varkor): the stability attribute might be behind a `#[cfg]` attribute.
// Find a stability attribute (i.e. `#[stable(..)]`, `#[unstable(..)]`,
// `#[rustc_const_unstable(..)]`).
if stab_attrs.iter().any(|stab_attr| attr.check_name(stab_attr)) {
let meta_item = attr.meta();
if let Some(MetaItem { node: MetaItemKind::List(ref metas), .. }) = meta_item {
let mut feature = None;
let mut since = None;
for meta in metas {
if let Some(mi) = meta.meta_item() {
// Find the `feature = ".."` meta-item.
match (&*mi.name().as_str(), mi.value_str()) {
("feature", val) => feature = val,
("since", val) => since = val,
_ => {}
}
}
}
if let Some(feature) = feature {
features.push((feature, since, attr.span));
}
// We need to iterate over the other attributes, because
// `rustc_const_unstable` is not mutually exclusive with
// the other stability attributes, so we can't just `break`
// here.
}
}
}
features
}
fn collect_feature(&mut self, feature: Symbol, since: Option<Symbol>, span: Span) {
let already_in_stable = self.lib_features.stable.contains_key(&feature);
let already_in_unstable = self.lib_features.unstable.contains(&feature);
match (since, already_in_stable, already_in_unstable) {
(Some(since), _, false) => {
self.lib_features.stable.insert(feature, since);
}
(None, false, _) => {
self.lib_features.unstable.insert(feature);
}
(Some(_), _, true) | (None, true, _) => {
let msg = format!(
"feature `{}` is declared {}, but was previously declared {}",
feature,
if since.is_some() { "stable"} else { "unstable" },
if since.is_none() { "stable"} else { "unstable" },
);
self.tcx.sess.struct_span_err_with_code(span, &msg,
DiagnosticId::Error("E0711".into())).emit();
}
}
}
fn collect_from_attrs(&mut self, attrs: &[Attribute]) {
for (feature, stable, span) in self.extract(attrs) {
self.collect_feature(feature, stable, span);
}
}
}
impl<'a, 'v, 'tcx> ItemLikeVisitor<'v> for LibFeatureCollector<'a, 'tcx> {
fn visit_item(&mut self, item: &hir::Item) {
self.collect_from_attrs(&item.attrs);
}
fn visit_trait_item(&mut self, trait_item: &hir::TraitItem) {
self.collect_from_attrs(&trait_item.attrs);
}
fn visit_impl_item(&mut self, impl_item: &hir::ImplItem) {
self.collect_from_attrs(&impl_item.attrs);
}
}
pub fn collect<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> LibFeatures {
let mut collector = LibFeatureCollector::new(tcx);
for &cnum in tcx.crates().iter() {
for &(feature, since) in tcx.defined_lib_features(cnum).iter() {
collector.collect_feature(feature, since, DUMMY_SP);
}
}
collector.collect_from_attrs(&tcx.hir.krate().attrs);
tcx.hir.krate().visit_all_item_likes(&mut collector);
collector.lib_features
}

View File

@ -813,37 +813,42 @@ pub fn check_unused_or_stable_features<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
krate.visit_all_item_likes(&mut missing.as_deep_visitor());
}
let ref declared_lib_features = tcx.features().declared_lib_features;
let mut remaining_lib_features: FxHashMap<Symbol, Span>
= declared_lib_features.clone().into_iter().collect();
remaining_lib_features.remove(&Symbol::intern("proc_macro"));
for &(ref stable_lang_feature, span) in &tcx.features().declared_stable_lang_features {
let version = find_lang_feature_accepted_version(&stable_lang_feature.as_str())
let since = find_lang_feature_accepted_version(&stable_lang_feature.as_str())
.expect("unexpectedly couldn't find version feature was stabilized");
tcx.lint_node(lint::builtin::STABLE_FEATURES,
ast::CRATE_NODE_ID,
span,
&format_stable_since_msg(version));
&format_stable_since_msg(*stable_lang_feature, since));
}
// FIXME(#44232) the `used_features` table no longer exists, so we don't
// lint about unknown or unused features. We should reenable
// this one day!
//
// let index = tcx.stability();
// for (used_lib_feature, level) in &index.used_features {
// remaining_lib_features.remove(used_lib_feature);
// }
//
// for &span in remaining_lib_features.values() {
// tcx.lint_node(lint::builtin::UNUSED_FEATURES,
// ast::CRATE_NODE_ID,
// span,
// "unused or unknown feature");
// }
let ref declared_lib_features = tcx.features().declared_lib_features;
let mut remaining_lib_features = FxHashMap();
for (feature, span) in declared_lib_features.clone().into_iter() {
remaining_lib_features.insert(feature, span);
}
// FIXME(varkor): we don't properly handle lib features behind `cfg` attributes yet,
// but it happens just to affect `libc`, so we're just going to hard-code it for now.
remaining_lib_features.remove(&Symbol::intern("libc"));
for (feature, stable) in tcx.lib_features().iter() {
remaining_lib_features.remove(&feature);
}
for (feature, span) in remaining_lib_features {
tcx.lint_node(lint::builtin::UNKNOWN_FEATURES,
ast::CRATE_NODE_ID,
span,
&format!("unknown feature `{}`", feature));
}
// FIXME(#44232): the `used_features` table no longer exists, so we
// don't lint about unused features. We should reenable this one day!
}
fn format_stable_since_msg(version: &str) -> String {
format!("this feature has been stable since {}. Attribute no longer needed", version)
fn format_stable_since_msg(feature: Symbol, since: &str) -> String {
// "this feature has been stable since {}. Attribute no longer needed"
format!("the feature `{}` has been stable since {} and no longer requires \
an attribute to enable", feature, since)
}

View File

@ -1192,6 +1192,10 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
self.sess.consider_optimizing(&cname, msg)
}
pub fn lib_features(self) -> Lrc<middle::lib_features::LibFeatures> {
self.get_lib_features(LOCAL_CRATE)
}
pub fn lang_items(self) -> Lrc<middle::lang_items::LanguageItems> {
self.get_lang_items(LOCAL_CRATE)
}
@ -2840,6 +2844,11 @@ pub fn provide(providers: &mut ty::query::Providers) {
assert_eq!(id, LOCAL_CRATE);
tcx.crate_name
};
providers.get_lib_features = |tcx, id| {
assert_eq!(id, LOCAL_CRATE);
// FIXME(#42293): see comment below.
tcx.dep_graph.with_ignore(|| Lrc::new(middle::lib_features::collect(tcx)))
};
providers.get_lang_items = |tcx, id| {
assert_eq!(id, LOCAL_CRATE);
// FIXME(#42293) Right now we insert a `with_ignore` node in the dep

View File

@ -626,6 +626,18 @@ impl<'tcx> QueryDescription<'tcx> for queries::crate_name<'tcx> {
}
}
impl<'tcx> QueryDescription<'tcx> for queries::get_lib_features<'tcx> {
fn describe(_tcx: TyCtxt, _: CrateNum) -> String {
format!("calculating the lib features map")
}
}
impl<'tcx> QueryDescription<'tcx> for queries::defined_lib_features<'tcx> {
fn describe(_tcx: TyCtxt, _: CrateNum) -> String {
format!("calculating the lib features defined in a crate")
}
}
impl<'tcx> QueryDescription<'tcx> for queries::get_lang_items<'tcx> {
fn describe(_tcx: TyCtxt, _: CrateNum) -> String {
"calculating the lang items map".to_string()

View File

@ -24,6 +24,7 @@ use middle::reachable::ReachableSet;
use middle::region;
use middle::resolve_lifetime::{ResolveLifetimes, Region, ObjectLifetimeDefault};
use middle::stability::{self, DeprecationEntry};
use middle::lib_features::LibFeatures;
use middle::lang_items::{LanguageItems, LangItem};
use middle::exported_symbols::{SymbolExportLevel, ExportedSymbol};
use mir::interpret::ConstEvalResult;
@ -492,6 +493,9 @@ define_queries! { <'tcx>
[] fn item_children: ItemChildren(DefId) -> Lrc<Vec<Export>>,
[] fn extern_mod_stmt_cnum: ExternModStmtCnum(DefId) -> Option<CrateNum>,
[] fn get_lib_features: get_lib_features_node(CrateNum) -> Lrc<LibFeatures>,
[] fn defined_lib_features: DefinedLibFeatures(CrateNum)
-> Lrc<Vec<(Symbol, Option<Symbol>)>>,
[] fn get_lang_items: get_lang_items_node(CrateNum) -> Lrc<LanguageItems>,
[] fn defined_lang_items: DefinedLangItems(CrateNum) -> Lrc<Vec<(DefId, usize)>>,
[] fn missing_lang_items: MissingLangItems(CrateNum) -> Lrc<Vec<LangItem>>,
@ -800,6 +804,10 @@ fn link_args_node<'tcx>(_: CrateNum) -> DepConstructor<'tcx> {
DepConstructor::LinkArgs
}
fn get_lib_features_node<'tcx>(_: CrateNum) -> DepConstructor<'tcx> {
DepConstructor::GetLibFeatures
}
fn get_lang_items_node<'tcx>(_: CrateNum) -> DepConstructor<'tcx> {
DepConstructor::GetLangItems
}

View File

@ -1218,6 +1218,8 @@ pub fn force_from_dep_node<'a, 'gcx, 'lcx>(tcx: TyCtxt<'a, 'gcx, 'lcx>,
DepKind::CrateName => { force!(crate_name, krate!()); }
DepKind::ItemChildren => { force!(item_children, def_id!()); }
DepKind::ExternModStmtCnum => { force!(extern_mod_stmt_cnum, def_id!()); }
DepKind::GetLibFeatures => { force!(get_lib_features, LOCAL_CRATE); }
DepKind::DefinedLibFeatures => { force!(defined_lib_features, krate!()); }
DepKind::GetLangItems => { force!(get_lang_items, LOCAL_CRATE); }
DepKind::DefinedLangItems => { force!(defined_lang_items, krate!()); }
DepKind::MissingLangItems => { force!(missing_lang_items, krate!()); }

View File

@ -188,6 +188,7 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
UNUSED_DOC_COMMENTS,
UNUSED_EXTERN_CRATES,
UNUSED_FEATURES,
UNKNOWN_FEATURES,
UNUSED_LABELS,
UNUSED_PARENS);
@ -342,7 +343,6 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
store.register_renamed("bare_trait_object", "bare_trait_objects");
store.register_renamed("unstable_name_collision", "unstable_name_collisions");
store.register_renamed("unused_doc_comment", "unused_doc_comments");
store.register_renamed("unknown_features", "unused_features");
store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
store.register_removed("negate_unsigned", "cast a signed value instead");
store.register_removed("raw_pointer_derive", "using derive with raw pointers is ok");

View File

@ -240,6 +240,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
cdata.each_child_of_item(def_id.index, |child| result.push(child), tcx.sess);
Lrc::new(result)
}
defined_lib_features => { Lrc::new(cdata.get_lib_features()) }
defined_lang_items => { Lrc::new(cdata.get_lang_items()) }
missing_lang_items => { Lrc::new(cdata.get_missing_lang_items()) }

View File

@ -645,6 +645,14 @@ impl<'a, 'tcx> CrateMetadata {
self.get_impl_data(id).trait_ref.map(|tr| tr.decode((self, tcx)))
}
/// Iterates over all the stability attributes in the given crate.
pub fn get_lib_features(&self) -> Vec<(ast::Name, Option<ast::Name>)> {
self.root
.lib_features
.decode(self)
.collect()
}
/// Iterates over the language items in the given crate.
pub fn get_lang_items(&self) -> Vec<(DefId, usize)> {
self.root

View File

@ -394,6 +394,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
());
let dep_bytes = self.position() - i;
// Encode the lib features.
i = self.position();
let lib_features = self.tracked(IsolatedEncoder::encode_lib_features, ());
let lib_feature_bytes = self.position() - i;
// Encode the language items.
i = self.position();
let lang_items = self.tracked(IsolatedEncoder::encode_lang_items, ());
@ -513,6 +518,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
crate_deps,
dylib_dependency_formats,
lib_features,
lang_items,
lang_items_missing,
native_libraries,
@ -537,6 +543,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
println!("metadata stats:");
println!(" dep bytes: {}", dep_bytes);
println!(" lib feature bytes: {}", lib_feature_bytes);
println!(" lang item bytes: {}", lang_item_bytes);
println!(" native bytes: {}", native_lib_bytes);
println!(" codemap bytes: {}", codemap_bytes);
@ -1456,6 +1463,12 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
self.lazy_seq_ref(deps.iter().map(|&(_, ref dep)| dep))
}
fn encode_lib_features(&mut self, _: ()) -> LazySeq<(ast::Name, Option<ast::Name>)> {
let tcx = self.tcx;
let lib_features = tcx.lib_features();
self.lazy_seq(lib_features.iter())
}
fn encode_lang_items(&mut self, _: ()) -> LazySeq<(DefIndex, usize)> {
let tcx = self.tcx;
let lang_items = tcx.lang_items();

View File

@ -198,6 +198,7 @@ pub struct CrateRoot {
pub crate_deps: LazySeq<CrateDep>,
pub dylib_dependency_formats: LazySeq<Option<LinkagePreference>>,
pub lib_features: LazySeq<(Symbol, Option<Symbol>)>,
pub lang_items: LazySeq<(DefIndex, usize)>,
pub lang_items_missing: LazySeq<lang_items::LangItem>,
pub native_libraries: LazySeq<NativeLibrary>,

View File

@ -374,6 +374,18 @@ and likely to change in the future.
"##,
E0635: r##"
The `#![feature]` attribute specified an unknown feature.
Erroneous code example:
```compile_fail,E0635
#![feature(nonexistent_rust_feature)] // error: unknown feature
```
"##,
}
register_diagnostics! {