about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-02-06 14:10:13 +0000
committerbors <bors@rust-lang.org>2017-02-06 14:10:13 +0000
commit324b175174c19b8be4592df11e65e0c4b6fee9d3 (patch)
treec898b6e95844adbf2e1df8794f1f1da3c34fce23 /src
parent4711ac314c3380f992e218879b7c94b26ba4102b (diff)
parentab3c8269f4d73d4760e99aca82411f954a11e571 (diff)
downloadrust-324b175174c19b8be4592df11e65e0c4b6fee9d3.tar.gz
rust-324b175174c19b8be4592df11e65e0c4b6fee9d3.zip
Auto merge of #39500 - michaelwoerister:fix-ich-testing, r=nikomatsakis
Let the dep-tracking test framework check that all #[rustc_dirty] attrs have been actually checked

r? @nikomatsakis
Diffstat (limited to 'src')
-rw-r--r--src/librustc/hir/intravisit.rs1
-rw-r--r--src/librustc_incremental/persist/dirty_clean.rs153
-rw-r--r--src/test/incremental/hashes/inherent_impls.rs67
-rw-r--r--src/test/incremental/hashes/trait_defs.rs4
-rw-r--r--src/test/incremental/hashes/trait_impls.rs50
-rw-r--r--src/test/incremental/unchecked_dirty_clean.rs44
-rw-r--r--src/test/incremental/unchecked_dirty_clean_metadata.rs45
7 files changed, 306 insertions, 58 deletions
diff --git a/src/librustc/hir/intravisit.rs b/src/librustc/hir/intravisit.rs
index 4b3e0d29101..1df67615069 100644
--- a/src/librustc/hir/intravisit.rs
+++ b/src/librustc/hir/intravisit.rs
@@ -914,6 +914,7 @@ pub fn walk_decl<'v, V: Visitor<'v>>(visitor: &mut V, declaration: &'v Decl) {
 
 pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
     visitor.visit_id(expression.id);
+    walk_list!(visitor, visit_attribute, expression.attrs.iter());
     match expression.node {
         ExprBox(ref subexpression) => {
             visitor.visit_expr(subexpression)
diff --git a/src/librustc_incremental/persist/dirty_clean.rs b/src/librustc_incremental/persist/dirty_clean.rs
index 798bc6e9f98..a0bcb54af32 100644
--- a/src/librustc_incremental/persist/dirty_clean.rs
+++ b/src/librustc_incremental/persist/dirty_clean.rs
@@ -46,6 +46,7 @@ use rustc::dep_graph::{DepGraphQuery, DepNode};
 use rustc::hir;
 use rustc::hir::def_id::DefId;
 use rustc::hir::itemlikevisit::ItemLikeVisitor;
+use rustc::hir::intravisit;
 use syntax::ast::{self, Attribute, NestedMetaItem};
 use rustc_data_structures::fx::{FxHashSet, FxHashMap};
 use syntax_pos::Span;
@@ -73,17 +74,32 @@ pub fn check_dirty_clean_annotations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     let query = tcx.dep_graph.query();
     debug!("query-nodes: {:?}", query.nodes());
     let krate = tcx.hir.krate();
-    krate.visit_all_item_likes(&mut DirtyCleanVisitor {
+    let mut dirty_clean_visitor = DirtyCleanVisitor {
         tcx: tcx,
         query: &query,
         dirty_inputs: dirty_inputs,
-    });
+        checked_attrs: FxHashSet(),
+    };
+    krate.visit_all_item_likes(&mut dirty_clean_visitor);
+
+    let mut all_attrs = FindAllAttrs {
+        tcx: tcx,
+        attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
+        found_attrs: vec![],
+    };
+    intravisit::walk_crate(&mut all_attrs, krate);
+
+    // Note that we cannot use the existing "unused attribute"-infrastructure
+    // here, since that is running before trans. This is also the reason why
+    // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
+    all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
 }
 
 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
     tcx: TyCtxt<'a, 'tcx, 'tcx>,
     query: &'a DepGraphQuery<DefId>,
     dirty_inputs: FxHashSet<DepNode<DefId>>,
+    checked_attrs: FxHashSet<ast::AttrId>,
 }
 
 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
@@ -109,7 +125,7 @@ impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
         dep_node.map_def(|&def_id| Some(self.tcx.item_path_str(def_id))).unwrap()
     }
 
-    fn assert_dirty(&self, item: &hir::Item, dep_node: DepNode<DefId>) {
+    fn assert_dirty(&self, item_span: Span, dep_node: DepNode<DefId>) {
         debug!("assert_dirty({:?})", dep_node);
 
         match dep_node {
@@ -121,7 +137,7 @@ impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
                 if !self.dirty_inputs.contains(&dep_node) {
                     let dep_node_str = self.dep_node_str(&dep_node);
                     self.tcx.sess.span_err(
-                        item.span,
+                        item_span,
                         &format!("`{:?}` not found in dirty set, but should be dirty",
                                  dep_node_str));
                 }
@@ -132,14 +148,14 @@ impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
                 if self.query.contains_node(&dep_node) {
                     let dep_node_str = self.dep_node_str(&dep_node);
                     self.tcx.sess.span_err(
-                        item.span,
+                        item_span,
                         &format!("`{:?}` found in dep graph, but should be dirty", dep_node_str));
                 }
             }
         }
     }
 
-    fn assert_clean(&self, item: &hir::Item, dep_node: DepNode<DefId>) {
+    fn assert_clean(&self, item_span: Span, dep_node: DepNode<DefId>) {
         debug!("assert_clean({:?})", dep_node);
 
         match dep_node {
@@ -150,7 +166,7 @@ impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
                 if self.dirty_inputs.contains(&dep_node) {
                     let dep_node_str = self.dep_node_str(&dep_node);
                     self.tcx.sess.span_err(
-                        item.span,
+                        item_span,
                         &format!("`{:?}` found in dirty-node set, but should be clean",
                                  dep_node_str));
                 }
@@ -160,35 +176,43 @@ impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
                 if !self.query.contains_node(&dep_node) {
                     let dep_node_str = self.dep_node_str(&dep_node);
                     self.tcx.sess.span_err(
-                        item.span,
+                        item_span,
                         &format!("`{:?}` not found in dep graph, but should be clean",
                                  dep_node_str));
                 }
             }
         }
     }
-}
 
-impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
-    fn visit_item(&mut self, item: &'tcx hir::Item) {
-        let def_id = self.tcx.hir.local_def_id(item.id);
+    fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
+        let def_id = self.tcx.hir.local_def_id(item_id);
         for attr in self.tcx.get_attrs(def_id).iter() {
             if attr.check_name(ATTR_DIRTY) {
                 if check_config(self.tcx, attr) {
-                    self.assert_dirty(item, self.dep_node(attr, def_id));
+                    self.checked_attrs.insert(attr.id);
+                    self.assert_dirty(item_span, self.dep_node(attr, def_id));
                 }
             } else if attr.check_name(ATTR_CLEAN) {
                 if check_config(self.tcx, attr) {
-                    self.assert_clean(item, self.dep_node(attr, def_id));
+                    self.checked_attrs.insert(attr.id);
+                    self.assert_clean(item_span, self.dep_node(attr, def_id));
                 }
             }
         }
     }
+}
+
+impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
+    fn visit_item(&mut self, item: &'tcx hir::Item) {
+        self.check_item(item.id, item.span);
+    }
 
-    fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem) {
+    fn visit_trait_item(&mut self, item: &hir::TraitItem) {
+        self.check_item(item.id, item.span);
     }
 
-    fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem) {
+    fn visit_impl_item(&mut self, item: &hir::ImplItem) {
+        self.check_item(item.id, item.span);
     }
 }
 
@@ -201,11 +225,25 @@ pub fn check_dirty_clean_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
 
     tcx.dep_graph.with_ignore(||{
         let krate = tcx.hir.krate();
-        krate.visit_all_item_likes(&mut DirtyCleanMetadataVisitor {
+        let mut dirty_clean_visitor = DirtyCleanMetadataVisitor {
             tcx: tcx,
             prev_metadata_hashes: prev_metadata_hashes,
             current_metadata_hashes: current_metadata_hashes,
-        });
+            checked_attrs: FxHashSet(),
+        };
+        krate.visit_all_item_likes(&mut dirty_clean_visitor);
+
+        let mut all_attrs = FindAllAttrs {
+            tcx: tcx,
+            attr_names: vec![ATTR_DIRTY_METADATA, ATTR_CLEAN_METADATA],
+            found_attrs: vec![],
+        };
+        intravisit::walk_crate(&mut all_attrs, krate);
+
+        // Note that we cannot use the existing "unused attribute"-infrastructure
+        // here, since that is running before trans. This is also the reason why
+        // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
+        all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
     });
 }
 
@@ -213,34 +251,43 @@ pub struct DirtyCleanMetadataVisitor<'a, 'tcx:'a, 'm> {
     tcx: TyCtxt<'a, 'tcx, 'tcx>,
     prev_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
     current_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
+    checked_attrs: FxHashSet<ast::AttrId>,
 }
 
 impl<'a, 'tcx, 'm> ItemLikeVisitor<'tcx> for DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
     fn visit_item(&mut self, item: &'tcx hir::Item) {
-        let def_id = self.tcx.hir.local_def_id(item.id);
+        self.check_item(item.id, item.span);
+    }
+
+    fn visit_trait_item(&mut self, item: &hir::TraitItem) {
+        self.check_item(item.id, item.span);
+    }
+
+    fn visit_impl_item(&mut self, item: &hir::ImplItem) {
+        self.check_item(item.id, item.span);
+    }
+}
+
+impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
+
+    fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
+        let def_id = self.tcx.hir.local_def_id(item_id);
 
         for attr in self.tcx.get_attrs(def_id).iter() {
             if attr.check_name(ATTR_DIRTY_METADATA) {
                 if check_config(self.tcx, attr) {
-                    self.assert_state(false, def_id, item.span);
+                    self.checked_attrs.insert(attr.id);
+                    self.assert_state(false, def_id, item_span);
                 }
             } else if attr.check_name(ATTR_CLEAN_METADATA) {
                 if check_config(self.tcx, attr) {
-                    self.assert_state(true, def_id, item.span);
+                    self.checked_attrs.insert(attr.id);
+                    self.assert_state(true, def_id, item_span);
                 }
             }
         }
     }
 
-    fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem) {
-    }
-
-    fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem) {
-    }
-}
-
-impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
-
     fn assert_state(&self, should_be_clean: bool, def_id: DefId, span: Span) {
         let item_path = self.tcx.item_path_str(def_id);
         debug!("assert_state({})", item_path);
@@ -274,7 +321,7 @@ impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
 /// for a `cfg="foo"` attribute and check whether we have a cfg
 /// flag called `foo`.
-fn check_config(tcx: TyCtxt, attr: &ast::Attribute) -> bool {
+fn check_config(tcx: TyCtxt, attr: &Attribute) -> bool {
     debug!("check_config(attr={:?})", attr);
     let config = &tcx.sess.parse_sess.config;
     debug!("check_config: config={:?}", config);
@@ -304,3 +351,47 @@ fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> ast::Name {
         tcx.sess.span_fatal(item.span, &msg);
     }
 }
+
+
+// A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
+// the HIR. It is used to verfiy that we really ran checks for all annotated
+// nodes.
+pub struct FindAllAttrs<'a, 'tcx:'a> {
+    tcx: TyCtxt<'a, 'tcx, 'tcx>,
+    attr_names: Vec<&'static str>,
+    found_attrs: Vec<&'tcx Attribute>,
+}
+
+impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
+
+    fn is_active_attr(&mut self, attr: &Attribute) -> bool {
+        for attr_name in &self.attr_names {
+            if attr.check_name(attr_name) && check_config(self.tcx, attr) {
+                return true;
+            }
+        }
+
+        false
+    }
+
+    fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
+        for attr in &self.found_attrs {
+            if !checked_attrs.contains(&attr.id) {
+                self.tcx.sess.span_err(attr.span, &format!("found unchecked \
+                    #[rustc_dirty]/#[rustc_clean] attribute"));
+            }
+        }
+    }
+}
+
+impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
+    fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
+        intravisit::NestedVisitorMap::All(&self.tcx.hir)
+    }
+
+    fn visit_attribute(&mut self, attr: &'tcx Attribute) {
+        if self.is_active_attr(attr) {
+            self.found_attrs.push(attr);
+        }
+    }
+}
diff --git a/src/test/incremental/hashes/inherent_impls.rs b/src/test/incremental/hashes/inherent_impls.rs
index 5c37bc13359..fd9ac61046e 100644
--- a/src/test/incremental/hashes/inherent_impls.rs
+++ b/src/test/incremental/hashes/inherent_impls.rs
@@ -39,9 +39,7 @@ impl Foo {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl Foo {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     pub fn method_name2() { }
 }
@@ -60,16 +58,47 @@ impl Foo {
 #[rustc_metadata_clean(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl Foo {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_dirty(cfg="cfail2")]
+    #[rustc_dirty(label="HirBody", cfg="cfail2")]
+    #[rustc_clean(label="HirBody", cfg="cfail3")]
+    #[rustc_metadata_clean(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     pub fn method_body() {
         println!("Hello, world!");
     }
 }
 
-// Change Method Privacy -----------------------------------------------------------
+
+// Change Method Body (inlined) ------------------------------------------------
+//
+// This should affect the method itself, but not the impl.
+#[cfg(cfail1)]
+impl Foo {
+    #[inline]
+    pub fn method_body_inlined() { }
+}
+
+#[cfg(not(cfail1))]
+#[rustc_clean(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_clean(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+impl Foo {
+    #[rustc_clean(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail3")]
+    #[rustc_dirty(label="HirBody", cfg="cfail2")]
+    #[rustc_clean(label="HirBody", cfg="cfail3")]
+    #[rustc_metadata_dirty(cfg="cfail2")]
+    #[rustc_metadata_clean(cfg="cfail3")]
+    #[inline]
+    pub fn method_body_inlined() {
+        println!("Hello, world!");
+    }
+}
+
+
+// Change Method Privacy -------------------------------------------------------
 #[cfg(cfail1)]
 impl Foo {
     pub fn method_privacy() { }
@@ -142,13 +171,11 @@ impl Foo {
 impl Foo {
     #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_clean(cfg="cfail2")]
+    #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     pub fn add_method_to_impl1(&self) { }
 
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     pub fn add_method_to_impl2(&self) { }
 }
@@ -188,9 +215,13 @@ impl Foo {
 #[rustc_metadata_clean(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl Foo {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_dirty(cfg="cfail2")]
+    #[rustc_dirty(label="HirBody", cfg="cfail2")]
+    #[rustc_clean(label="HirBody", cfg="cfail3")]
+    // At the moment we explicitly ignore argument names in metadata, since they
+    // are not used in downstream crates (except in rustdoc)
+    #[rustc_metadata_clean(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     pub fn change_method_parameter_name(&self, b: i64) { }
 }
@@ -252,9 +283,13 @@ impl Foo {
 #[rustc_metadata_clean(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl Foo {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_dirty(cfg="cfail2")]
+    #[rustc_dirty(label="HirBody", cfg="cfail2")]
+    #[rustc_clean(label="HirBody", cfg="cfail3")]
+    // At the moment we explicitly ignore argument names in metadata, since they
+    // are not used in downstream crates (except in rustdoc)
+    #[rustc_metadata_clean(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     pub fn change_method_parameter_order(&self, b: i64, a: i64) { }
 }
@@ -465,7 +500,7 @@ impl Bar<u32> {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl<T> Bar<T> {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
@@ -486,7 +521,7 @@ impl Bar<u32> {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl Bar<u64> {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
@@ -507,7 +542,7 @@ impl<T> Bar<T> {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl<T: 'static> Bar<T> {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
@@ -528,7 +563,7 @@ impl<T> Bar<T> {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl<T: Clone> Bar<T> {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
diff --git a/src/test/incremental/hashes/trait_defs.rs b/src/test/incremental/hashes/trait_defs.rs
index bc401ae9340..94698506ec5 100644
--- a/src/test/incremental/hashes/trait_defs.rs
+++ b/src/test/incremental/hashes/trait_defs.rs
@@ -316,8 +316,10 @@ trait TraitChangeModeSelfOwnToMut: Sized {
 #[rustc_metadata_clean(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 trait TraitChangeModeSelfOwnToMut: Sized {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
+    #[rustc_dirty(label="HirBody", cfg="cfail2")]
+    #[rustc_clean(label="HirBody", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     fn method(mut self) {}
diff --git a/src/test/incremental/hashes/trait_impls.rs b/src/test/incremental/hashes/trait_impls.rs
index 15094492248..30e376f04fb 100644
--- a/src/test/incremental/hashes/trait_impls.rs
+++ b/src/test/incremental/hashes/trait_impls.rs
@@ -46,9 +46,7 @@ impl ChangeMethodNameTrait for Foo {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 pub trait ChangeMethodNameTrait {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     fn method_name2();
 }
@@ -59,16 +57,14 @@ pub trait ChangeMethodNameTrait {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl ChangeMethodNameTrait for Foo {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
-    #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
     fn method_name2() { }
 }
 
 // Change Method Body -----------------------------------------------------------
 //
-// This should affect the method itself, but not the trait.
+// This should affect the method itself, but not the impl.
 
 pub trait ChangeMethodBodyTrait {
     fn method_name();
@@ -85,16 +81,50 @@ impl ChangeMethodBodyTrait for Foo {
 #[rustc_metadata_clean(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl ChangeMethodBodyTrait for Foo {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail3")]
+    #[rustc_dirty(label="HirBody", cfg="cfail2")]
+    #[rustc_clean(label="HirBody", cfg="cfail3")]
+    #[rustc_metadata_clean(cfg="cfail2")]
+    #[rustc_metadata_clean(cfg="cfail3")]
+    fn method_name() {
+        ()
+    }
+}
+
+// Change Method Body (inlined fn) ---------------------------------------------
+//
+// This should affect the method itself, but not the impl.
+
+pub trait ChangeMethodBodyTraitInlined {
+    fn method_name();
+}
+
+#[cfg(cfail1)]
+impl ChangeMethodBodyTraitInlined for Foo {
+    #[inline]
+    fn method_name() { }
+}
+
+#[cfg(not(cfail1))]
+#[rustc_clean(label="Hir", cfg="cfail2")]
+#[rustc_clean(label="Hir", cfg="cfail3")]
+#[rustc_metadata_clean(cfg="cfail2")]
+#[rustc_metadata_clean(cfg="cfail3")]
+impl ChangeMethodBodyTraitInlined for Foo {
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
+    #[rustc_dirty(label="HirBody", cfg="cfail2")]
+    #[rustc_clean(label="HirBody", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
+    #[inline]
     fn method_name() {
         ()
     }
 }
 
-// Change Method Selfness -----------------------------------------------------------
+// Change Method Selfness ------------------------------------------------------
 
 #[cfg(cfail1)]
 pub trait ChangeMethodSelfnessTrait {
@@ -447,7 +477,7 @@ impl ChangeSelfTypeOfImpl for u32 {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl ChangeSelfTypeOfImpl for u64 {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
@@ -472,7 +502,7 @@ impl<T> AddLifetimeBoundToImplParameter for T {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl<T: 'static> AddLifetimeBoundToImplParameter for T {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
@@ -497,7 +527,7 @@ impl<T> AddTraitBoundToImplParameter for T {
 #[rustc_metadata_dirty(cfg="cfail2")]
 #[rustc_metadata_clean(cfg="cfail3")]
 impl<T: Clone> AddTraitBoundToImplParameter for T {
-    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    #[rustc_clean(label="Hir", cfg="cfail2")]
     #[rustc_clean(label="Hir", cfg="cfail3")]
     #[rustc_metadata_dirty(cfg="cfail2")]
     #[rustc_metadata_clean(cfg="cfail3")]
diff --git a/src/test/incremental/unchecked_dirty_clean.rs b/src/test/incremental/unchecked_dirty_clean.rs
new file mode 100644
index 00000000000..a81e884f39e
--- /dev/null
+++ b/src/test/incremental/unchecked_dirty_clean.rs
@@ -0,0 +1,44 @@
+// Copyright 2017 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.
+
+// revisions: rpass1 cfail2
+// compile-flags: -Z query-dep-graph
+
+#![allow(warnings)]
+#![feature(rustc_attrs)]
+
+// Sanity check for the dirty-clean system. We add #[rustc_dirty]/#[rustc_clean]
+// attributes in places that are not checked and make sure that this causes an
+// error.
+
+fn main() {
+
+    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    {
+        // empty block
+    }
+
+    #[rustc_clean(label="Hir", cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    {
+        // empty block
+    }
+}
+
+struct _Struct {
+    #[rustc_dirty(label="Hir", cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    _field1: i32,
+
+    #[rustc_clean(label="Hir", cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    _field2: i32,
+}
diff --git a/src/test/incremental/unchecked_dirty_clean_metadata.rs b/src/test/incremental/unchecked_dirty_clean_metadata.rs
new file mode 100644
index 00000000000..4017b4d4ba9
--- /dev/null
+++ b/src/test/incremental/unchecked_dirty_clean_metadata.rs
@@ -0,0 +1,45 @@
+// Copyright 2017 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.
+
+// revisions: rpass1 cfail2
+// compile-flags: -Z query-dep-graph
+
+#![allow(warnings)]
+#![feature(rustc_attrs)]
+
+// Sanity check for the dirty-clean system. We add
+// #[rustc_metadata_dirty]/#[rustc_metadata_clean] attributes in places that
+// are not checked and make sure that this causes an error.
+
+fn main() {
+
+    #[rustc_metadata_dirty(cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    {
+        // empty block
+    }
+
+    #[rustc_metadata_clean(cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    {
+        // empty block
+    }
+}
+
+struct _Struct {
+    #[rustc_metadata_dirty(cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    _field1: i32,
+
+    #[rustc_metadata_clean(cfg="cfail2")]
+    //[cfail2]~^ ERROR found unchecked #[rustc_dirty]/#[rustc_clean] attribute
+    _field2: i32,
+}
+