aboutsummaryrefslogtreecommitdiffstats
path: root/components/script_plugins
diff options
context:
space:
mode:
authorAnthony Ramine <n.oxyde@gmail.com>2017-02-16 09:18:02 +0100
committerAnthony Ramine <n.oxyde@gmail.com>2017-02-16 18:37:14 +0100
commit3eed8a91a1183e22b69b1be48ad97a253bc1dc0a (patch)
treeba6b0b4740560d40445e2a817397a674ec1f8637 /components/script_plugins
parent84a44a401424852bc44ef5e751e84544ed892e70 (diff)
downloadservo-3eed8a91a1183e22b69b1be48ad97a253bc1dc0a.tar.gz
servo-3eed8a91a1183e22b69b1be48ad97a253bc1dc0a.zip
Move script lints to script_plugins
The plugins crate now just allows to hook into clippy from a single crate.
Diffstat (limited to 'components/script_plugins')
-rw-r--r--components/script_plugins/Cargo.toml10
-rw-r--r--components/script_plugins/ban.rs53
-rw-r--r--components/script_plugins/jstraceable.rs24
-rw-r--r--components/script_plugins/lib.rs48
-rw-r--r--components/script_plugins/unrooted_must_root.rs231
-rw-r--r--components/script_plugins/utils.rs73
6 files changed, 439 insertions, 0 deletions
diff --git a/components/script_plugins/Cargo.toml b/components/script_plugins/Cargo.toml
new file mode 100644
index 00000000000..6b200e3ef0f
--- /dev/null
+++ b/components/script_plugins/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "script_plugins"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+license = "MPL-2.0"
+publish = false
+
+[lib]
+path = "lib.rs"
+plugin = true
diff --git a/components/script_plugins/ban.rs b/components/script_plugins/ban.rs
new file mode 100644
index 00000000000..4a4e71c08cc
--- /dev/null
+++ b/components/script_plugins/ban.rs
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rustc::lint::{EarlyContext, LintPass, LintArray, EarlyLintPass, LintContext};
+use syntax::ast::Ty;
+use utils::match_ty_unwrap;
+
+declare_lint!(BANNED_TYPE, Deny,
+ "Ban various unsafe type combinations");
+
+/// Lint for banning various unsafe types
+///
+/// Banned types:
+///
+/// - `Cell<JSVal>`
+/// - `Cell<JS<T>>`
+pub struct BanPass;
+
+impl LintPass for BanPass {
+ fn get_lints(&self) -> LintArray {
+ lint_array!(BANNED_TYPE)
+ }
+}
+
+impl EarlyLintPass for BanPass {
+ fn check_ty(&mut self, cx: &EarlyContext, ty: &Ty) {
+ if match_ty_unwrap(ty, &["std", "cell", "Cell"])
+ .and_then(|t| t.get(0))
+ .and_then(|t| match_ty_unwrap(&**t, &["dom", "bindings", "js", "JS"]))
+ .is_some() {
+ cx.span_lint(BANNED_TYPE, ty.span, "Banned type Cell<JS<T>> detected. Use MutJS<JS<T>> instead")
+ }
+ if match_ty_unwrap(ty, &["std", "cell", "Cell"])
+ .and_then(|t| t.get(0))
+ .and_then(|t| match_ty_unwrap(&**t, &["js", "jsval", "JSVal"]))
+ .is_some() {
+ cx.span_lint(BANNED_TYPE, ty.span, "Banned type Cell<JSVal> detected. Use MutJS<JSVal> instead")
+ }
+ if match_ty_unwrap(ty, &["dom", "bindings", "cell", "DOMRefCell"])
+ .and_then(|t| t.get(0))
+ .and_then(|t| match_ty_unwrap(&**t, &["dom", "bindings", "js", "JS"]))
+ .is_some() {
+ cx.span_lint(BANNED_TYPE, ty.span, "Banned type DOMRefCell<JS<T>> detected. Use MutJS<JS<T>> instead")
+ }
+ if match_ty_unwrap(ty, &["dom", "bindings", "cell", "DOMRefCell"])
+ .and_then(|t| t.get(0))
+ .and_then(|t| match_ty_unwrap(&**t, &["js", "jsapi", "Heap"]))
+ .is_some() {
+ cx.span_lint(BANNED_TYPE, ty.span, "Banned type DOMRefCell<Heap<T>> detected. Use Heap<T> directly instead")
+ }
+ }
+}
diff --git a/components/script_plugins/jstraceable.rs b/components/script_plugins/jstraceable.rs
new file mode 100644
index 00000000000..071a0b54fdc
--- /dev/null
+++ b/components/script_plugins/jstraceable.rs
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use syntax::ast::MetaItem;
+use syntax::codemap::Span;
+use syntax::ext::base::{Annotatable, ExtCtxt};
+use syntax::ptr::P;
+
+pub fn expand_dom_struct(cx: &mut ExtCtxt, sp: Span, _: &MetaItem, anno: Annotatable) -> Annotatable {
+ if let Annotatable::Item(item) = anno {
+ let mut item2 = (*item).clone();
+ item2.attrs.push(quote_attr!(cx, #[must_root]));
+ item2.attrs.push(quote_attr!(cx, #[repr(C)]));
+ item2.attrs.push(quote_attr!(cx, #[derive(JSTraceable)]));
+ item2.attrs.push(quote_attr!(cx, #[derive(HeapSizeOf)]));
+ item2.attrs.push(quote_attr!(cx, #[derive(DenyPublicFields)]));
+ item2.attrs.push(quote_attr!(cx, #[derive(DomObject)]));
+ Annotatable::Item(P(item2))
+ } else {
+ cx.span_err(sp, "#[dom_struct] applied to something other than a struct");
+ anno
+ }
+}
diff --git a/components/script_plugins/lib.rs b/components/script_plugins/lib.rs
new file mode 100644
index 00000000000..79aa5a4752d
--- /dev/null
+++ b/components/script_plugins/lib.rs
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Servo's compiler plugin/macro crate
+//!
+//! Attributes this crate provides:
+//!
+//! - `#[derive(DenyPublicFields)]` : Forces all fields in a struct/enum to be private
+//! - `#[derive(JSTraceable)]` : Auto-derives an implementation of `JSTraceable` for a struct in the script crate
+//! - `#[must_root]` : Prevents data of the marked type from being used on the stack.
+//! See the lints module for more details
+//! - `#[dom_struct]` : Implies #[derive(JSTraceable, DenyPublicFields)]`, and `#[must_root]`.
+//! Use this for structs that correspond to a DOM type
+
+
+#![deny(unsafe_code)]
+#![feature(box_syntax, plugin, plugin_registrar, quote, rustc_private, slice_patterns)]
+
+#[macro_use]
+extern crate rustc;
+extern crate rustc_plugin;
+extern crate syntax;
+
+use rustc_plugin::Registry;
+use syntax::ext::base::*;
+use syntax::feature_gate::AttributeType::Whitelisted;
+use syntax::symbol::Symbol;
+
+mod ban;
+// Public for documentation to show up
+/// Handles the auto-deriving for `#[derive(JSTraceable)]`
+pub mod jstraceable;
+mod unrooted_must_root;
+/// Utilities for writing plugins
+mod utils;
+
+#[plugin_registrar]
+pub fn plugin_registrar(reg: &mut Registry) {
+ reg.register_syntax_extension(
+ Symbol::intern("dom_struct"),
+ MultiModifier(box jstraceable::expand_dom_struct));
+
+ reg.register_late_lint_pass(box unrooted_must_root::UnrootedPass::new());
+ reg.register_early_lint_pass(box ban::BanPass);
+ reg.register_attribute("allow_unrooted_interior".to_string(), Whitelisted);
+ reg.register_attribute("must_root".to_string(), Whitelisted);
+}
diff --git a/components/script_plugins/unrooted_must_root.rs b/components/script_plugins/unrooted_must_root.rs
new file mode 100644
index 00000000000..660a0c58781
--- /dev/null
+++ b/components/script_plugins/unrooted_must_root.rs
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rustc::hir;
+use rustc::hir::intravisit as visit;
+use rustc::hir::map as ast_map;
+use rustc::lint::{LateContext, LintPass, LintArray, LateLintPass, LintContext};
+use rustc::ty;
+use syntax::{ast, codemap};
+use utils::{match_def_path, in_derive_expn};
+
+declare_lint!(UNROOTED_MUST_ROOT, Deny,
+ "Warn and report usage of unrooted jsmanaged objects");
+
+/// Lint for ensuring safe usage of unrooted pointers
+///
+/// This lint (disable with `-A unrooted-must-root`/`#[allow(unrooted_must_root)]`) ensures that `#[must_root]`
+/// values are used correctly.
+///
+/// "Incorrect" usage includes:
+///
+/// - Not being used in a struct/enum field which is not `#[must_root]` itself
+/// - Not being used as an argument to a function (Except onces named `new` and `new_inherited`)
+/// - Not being bound locally in a `let` statement, assignment, `for` loop, or `match` statement.
+///
+/// This helps catch most situations where pointers like `JS<T>` are used in a way that they can be invalidated by a
+/// GC pass.
+///
+/// Structs which have their own mechanism of rooting their unrooted contents (e.g. `ScriptThread`)
+/// can be marked as `#[allow(unrooted_must_root)]`. Smart pointers which root their interior type
+/// can be marked as `#[allow_unrooted_interior]`
+pub struct UnrootedPass;
+
+impl UnrootedPass {
+ pub fn new() -> UnrootedPass {
+ UnrootedPass
+ }
+}
+
+/// Checks if a type is unrooted or contains any owned unrooted types
+fn is_unrooted_ty(cx: &LateContext, ty: &ty::TyS, in_new_function: bool) -> bool {
+ let mut ret = false;
+ ty.maybe_walk(|t| {
+ match t.sty {
+ ty::TyAdt(did, _) => {
+ if cx.tcx.has_attr(did.did, "must_root") {
+ ret = true;
+ false
+ } else if cx.tcx.has_attr(did.did, "allow_unrooted_interior") {
+ false
+ } else if match_def_path(cx, did.did, &["core", "cell", "Ref"])
+ || match_def_path(cx, did.did, &["core", "cell", "RefMut"])
+ || match_def_path(cx, did.did, &["core", "slice", "Iter"])
+ || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "OccupiedEntry"])
+ || match_def_path(cx, did.did, &["std", "collections", "hash", "map", "VacantEntry"]) {
+ // Structures which are semantically similar to an &ptr.
+ false
+ } else if did.is_box() && in_new_function {
+ // box in new() is okay
+ false
+ } else {
+ true
+ }
+ },
+ ty::TyRef(..) => false, // don't recurse down &ptrs
+ ty::TyRawPtr(..) => false, // don't recurse down *ptrs
+ ty::TyFnDef(..) | ty::TyFnPtr(_) => false,
+ _ => true
+ }
+ });
+ ret
+}
+
+impl LintPass for UnrootedPass {
+ fn get_lints(&self) -> LintArray {
+ lint_array!(UNROOTED_MUST_ROOT)
+ }
+}
+
+impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnrootedPass {
+ /// All structs containing #[must_root] types must be #[must_root] themselves
+ fn check_struct_def(&mut self,
+ cx: &LateContext,
+ def: &hir::VariantData,
+ _n: ast::Name,
+ _gen: &hir::Generics,
+ id: ast::NodeId) {
+ let item = match cx.tcx.hir.get(id) {
+ ast_map::Node::NodeItem(item) => item,
+ _ => cx.tcx.hir.expect_item(cx.tcx.hir.get_parent(id)),
+ };
+ if item.attrs.iter().all(|a| !a.check_name("must_root")) {
+ for ref field in def.fields() {
+ let def_id = cx.tcx.hir.local_def_id(field.id);
+ if is_unrooted_ty(cx, cx.tcx.item_type(def_id), false) {
+ cx.span_lint(UNROOTED_MUST_ROOT, field.span,
+ "Type must be rooted, use #[must_root] on the struct definition to propagate")
+ }
+ }
+ }
+ }
+
+ /// All enums containing #[must_root] types must be #[must_root] themselves
+ fn check_variant(&mut self, cx: &LateContext, var: &hir::Variant, _gen: &hir::Generics) {
+ let ref map = cx.tcx.hir;
+ if map.expect_item(map.get_parent(var.node.data.id())).attrs.iter().all(|a| !a.check_name("must_root")) {
+ match var.node.data {
+ hir::VariantData::Tuple(ref fields, _) => {
+ for ref field in fields {
+ let def_id = cx.tcx.hir.local_def_id(field.id);
+ if is_unrooted_ty(cx, cx.tcx.item_type(def_id), false) {
+ cx.span_lint(UNROOTED_MUST_ROOT, field.ty.span,
+ "Type must be rooted, use #[must_root] on \
+ the enum definition to propagate")
+ }
+ }
+ }
+ _ => () // Struct variants already caught by check_struct_def
+ }
+ }
+ }
+ /// Function arguments that are #[must_root] types are not allowed
+ fn check_fn(&mut self,
+ cx: &LateContext<'a, 'tcx>,
+ kind: visit::FnKind,
+ decl: &'tcx hir::FnDecl,
+ body: &'tcx hir::Body,
+ span: codemap::Span,
+ id: ast::NodeId) {
+ let in_new_function = match kind {
+ visit::FnKind::ItemFn(n, _, _, _, _, _, _) |
+ visit::FnKind::Method(n, _, _, _) => {
+ &*n.as_str() == "new" || n.as_str().starts_with("new_")
+ }
+ visit::FnKind::Closure(_) => return,
+ };
+
+ if !in_derive_expn(cx, span) {
+ let def_id = cx.tcx.hir.local_def_id(id);
+ let ty = cx.tcx.item_type(def_id);
+
+ for (arg, ty) in decl.inputs.iter().zip(ty.fn_args().0.iter()) {
+ if is_unrooted_ty(cx, ty, false) {
+ cx.span_lint(UNROOTED_MUST_ROOT, arg.span, "Type must be rooted")
+ }
+ }
+
+ if !in_new_function {
+ if is_unrooted_ty(cx, ty.fn_ret().0, false) {
+ cx.span_lint(UNROOTED_MUST_ROOT, decl.output.span(), "Type must be rooted")
+ }
+ }
+ }
+
+ let mut visitor = FnDefVisitor {
+ cx: cx,
+ in_new_function: in_new_function,
+ };
+ visit::walk_expr(&mut visitor, &body.value);
+ }
+}
+
+struct FnDefVisitor<'a, 'b: 'a, 'tcx: 'a+'b> {
+ cx: &'a LateContext<'b, 'tcx>,
+ in_new_function: bool,
+}
+
+impl<'a, 'b, 'tcx> visit::Visitor<'tcx> for FnDefVisitor<'a, 'b, 'tcx> {
+ fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
+ let cx = self.cx;
+
+ fn require_rooted(cx: &LateContext, in_new_function: bool, subexpr: &hir::Expr) {
+ let ty = cx.tables.expr_ty(&subexpr);
+ if is_unrooted_ty(cx, ty, in_new_function) {
+ cx.span_lint(UNROOTED_MUST_ROOT,
+ subexpr.span,
+ &format!("Expression of type {:?} must be rooted", ty))
+ }
+ }
+
+ match expr.node {
+ /// Trait casts from #[must_root] types are not allowed
+ hir::ExprCast(ref subexpr, _) => require_rooted(cx, self.in_new_function, &*subexpr),
+ // This catches assignments... the main point of this would be to catch mutable
+ // references to `JS<T>`.
+ // FIXME: Enable this? Triggers on certain kinds of uses of DOMRefCell.
+ // hir::ExprAssign(_, ref rhs) => require_rooted(cx, self.in_new_function, &*rhs),
+ // This catches calls; basically, this enforces the constraint that only constructors
+ // can call other constructors.
+ // FIXME: Enable this? Currently triggers with constructs involving DOMRefCell, and
+ // constructs like Vec<JS<T>> and RootedVec<JS<T>>.
+ // hir::ExprCall(..) if !self.in_new_function => {
+ // require_rooted(cx, self.in_new_function, expr);
+ // }
+ _ => {
+ // TODO(pcwalton): Check generics with a whitelist of allowed generics.
+ }
+ }
+
+ visit::walk_expr(self, expr);
+ }
+
+ fn visit_pat(&mut self, pat: &'tcx hir::Pat) {
+ let cx = self.cx;
+
+ if let hir::PatKind::Binding(hir::BindingMode::BindByValue(_), _, _, _) = pat.node {
+ let ty = cx.tables.pat_ty(pat);
+ if is_unrooted_ty(cx, ty, self.in_new_function) {
+ cx.span_lint(UNROOTED_MUST_ROOT,
+ pat.span,
+ &format!("Expression of type {:?} must be rooted", ty))
+ }
+ }
+
+ visit::walk_pat(self, pat);
+ }
+
+ fn visit_fn(&mut self, kind: visit::FnKind<'tcx>, decl: &'tcx hir::FnDecl,
+ body: hir::BodyId, span: codemap::Span, id: ast::NodeId) {
+ if let visit::FnKind::Closure(_) = kind {
+ visit::walk_fn(self, kind, decl, body, span, id);
+ }
+ }
+
+ fn visit_foreign_item(&mut self, _: &'tcx hir::ForeignItem) {}
+ fn visit_ty(&mut self, _: &'tcx hir::Ty) { }
+ fn nested_visit_map<'this>(&'this mut self) -> hir::intravisit::NestedVisitorMap<'this, 'tcx> {
+ hir::intravisit::NestedVisitorMap::OnlyBodies(&self.cx.tcx.hir)
+ }
+}
diff --git a/components/script_plugins/utils.rs b/components/script_plugins/utils.rs
new file mode 100644
index 00000000000..50ff2a959a9
--- /dev/null
+++ b/components/script_plugins/utils.rs
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rustc::hir::def_id::DefId;
+use rustc::lint::{LateContext, LintContext};
+use syntax::ast;
+use syntax::codemap::{ExpnFormat, Span};
+use syntax::ptr::P;
+
+/// Matches a type with a provided string, and returns its type parameters if successful
+pub fn match_ty_unwrap<'a>(ty: &'a ast::Ty, segments: &[&str]) -> Option<&'a [P<ast::Ty>]> {
+ match ty.node {
+ ast::TyKind::Path(_, ast::Path { segments: ref seg, .. }) => {
+ // So hir::Path isn't the full path, just the tokens that were provided.
+ // I could muck around with the maps and find the full path
+ // however the more efficient way is to simply reverse the iterators and zip them
+ // which will compare them in reverse until one of them runs out of segments
+ if seg.iter().rev().zip(segments.iter().rev()).all(|(a, b)| &*a.identifier.name.as_str() == *b) {
+ match seg.last() {
+ Some(&ast::PathSegment { parameters: Some(ref params), .. }) => {
+ match **params {
+ ast::PathParameters::AngleBracketed(ref a) => Some(&a.types),
+
+ // `Foo(A,B) -> C`
+ ast::PathParameters::Parenthesized(_) => None,
+ }
+ }
+ Some(&ast::PathSegment { parameters: None, .. }) => Some(&[]),
+ None => None,
+ }
+ } else {
+ None
+ }
+ },
+ _ => None
+ }
+}
+
+/// check if a DefId's path matches the given absolute type path
+/// usage e.g. with
+/// `match_def_path(cx, id, &["core", "option", "Option"])`
+pub fn match_def_path(cx: &LateContext, def_id: DefId, path: &[&str]) -> bool {
+ let krate = &cx.tcx.crate_name(def_id.krate);
+ if krate != &path[0] {
+ return false;
+ }
+
+ let path = &path[1..];
+ let other = cx.tcx.def_path(def_id).data;
+
+ if other.len() != path.len() {
+ return false;
+ }
+
+ other.into_iter()
+ .map(|e| e.data)
+ .zip(path)
+ .all(|(nm, p)| &*nm.as_interned_str() == *p)
+}
+
+pub fn in_derive_expn(cx: &LateContext, span: Span) -> bool {
+ cx.sess().codemap().with_expn_info(span.expn_id,
+ |info| {
+ if let Some(i) = info {
+ if let ExpnFormat::MacroAttribute(n) = i.callee.format {
+ if n.as_str().contains("derive") {
+ true
+ } else { false }
+ } else { false }
+ } else { false }
+ })
+}