diff --git a/.gitignore b/.gitignore index adf5e8feddf4..f1e6fd10bc2a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,11 @@ out *Cargo.lock /target /clippy_lints/target +/clippy_utils/target /clippy_workspace_tests/target /clippy_dev/target +/plugin_examples/*/target +/plugin_examples/*/tests/ui/*/target /rustc_tools_util/target # Generated by dogfood diff --git a/Cargo.toml b/Cargo.toml index e60aa472846c..f63a0996a808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ path = "src/driver.rs" # begin automatic update clippy_lints = { version = "0.1.50", path = "clippy_lints" } # end automatic update +clippy_utils = { path = "clippy_utils" } +libloading = "0.7" semver = "0.11" rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" } tempfile = { version = "3.1.0", optional = true } diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index a9516560a619..5a6611cedaf8 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -18,6 +18,7 @@ edition = "2018" [dependencies] cargo_metadata = "0.12" +clippy_utils = { path = "../clippy_utils" } if_chain = "1.0.0" itertools = "0.9" pulldown-cmark = { version = "0.8", default-features = false } @@ -38,4 +39,4 @@ syn = { version = "1", features = ["full"] } [features] deny-warnings = [] # build clippy with internal lints enabled, off by default -internal-lints = [] +internal-lints = ["clippy_utils/internal-lints"] diff --git a/clippy_lints/src/consts.rs b/clippy_lints/src/consts.rs index 640cffd24a70..7e87f53e3fba 100644 --- a/clippy_lints/src/consts.rs +++ b/clippy_lints/src/consts.rs @@ -1,574 +1 @@ -#![allow(clippy::float_cmp)] - -use crate::utils::{clip, sext, unsext}; -use if_chain::if_chain; -use rustc_ast::ast::{self, LitFloatType, LitKind}; -use rustc_data_structures::sync::Lrc; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp}; -use rustc_lint::LateContext; -use rustc_middle::mir::interpret::Scalar; -use rustc_middle::ty::subst::{Subst, SubstsRef}; -use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt}; -use rustc_middle::{bug, span_bug}; -use rustc_span::symbol::Symbol; -use std::cmp::Ordering::{self, Equal}; -use std::convert::TryInto; -use std::hash::{Hash, Hasher}; - -/// A `LitKind`-like enum to fold constant `Expr`s into. -#[derive(Debug, Clone)] -pub enum Constant { - /// A `String` (e.g., "abc"). - Str(String), - /// A binary string (e.g., `b"abc"`). - Binary(Lrc<[u8]>), - /// A single `char` (e.g., `'a'`). - Char(char), - /// An integer's bit representation. - Int(u128), - /// An `f32`. - F32(f32), - /// An `f64`. - F64(f64), - /// `true` or `false`. - Bool(bool), - /// An array of constants. - Vec(Vec), - /// Also an array, but with only one constant, repeated N times. - Repeat(Box, u64), - /// A tuple of constants. - Tuple(Vec), - /// A raw pointer. - RawPtr(u128), - /// A reference - Ref(Box), - /// A literal with syntax error. - Err(Symbol), -} - -impl PartialEq for Constant { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs, - (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r, - (&Self::Char(l), &Self::Char(r)) => l == r, - (&Self::Int(l), &Self::Int(r)) => l == r, - (&Self::F64(l), &Self::F64(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - l.to_bits() == r.to_bits() - }, - (&Self::F32(l), &Self::F32(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - f64::from(l).to_bits() == f64::from(r).to_bits() - }, - (&Self::Bool(l), &Self::Bool(r)) => l == r, - (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, - (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv, - (&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb, - // TODO: are there inter-type equalities? - _ => false, - } - } -} - -impl Hash for Constant { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - std::mem::discriminant(self).hash(state); - match *self { - Self::Str(ref s) => { - s.hash(state); - }, - Self::Binary(ref b) => { - b.hash(state); - }, - Self::Char(c) => { - c.hash(state); - }, - Self::Int(i) => { - i.hash(state); - }, - Self::F32(f) => { - f64::from(f).to_bits().hash(state); - }, - Self::F64(f) => { - f.to_bits().hash(state); - }, - Self::Bool(b) => { - b.hash(state); - }, - Self::Vec(ref v) | Self::Tuple(ref v) => { - v.hash(state); - }, - Self::Repeat(ref c, l) => { - c.hash(state); - l.hash(state); - }, - Self::RawPtr(u) => { - u.hash(state); - }, - Self::Ref(ref r) => { - r.hash(state); - }, - Self::Err(ref s) => { - s.hash(state); - }, - } - } -} - -impl Constant { - pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { - match (left, right) { - (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)), - (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)), - (&Self::Int(l), &Self::Int(r)) => { - if let ty::Int(int_ty) = *cmp_type.kind() { - Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))) - } else { - Some(l.cmp(&r)) - } - }, - (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r), - (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r), - (&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)), - (&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => l - .iter() - .zip(r.iter()) - .map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri)) - .find(|r| r.map_or(true, |o| o != Ordering::Equal)) - .unwrap_or_else(|| Some(l.len().cmp(&r.len()))), - (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => { - match Self::partial_cmp(tcx, cmp_type, lv, rv) { - Some(Equal) => Some(ls.cmp(rs)), - x => x, - } - }, - (&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb), - // TODO: are there any useful inter-type orderings? - _ => None, - } - } -} - -/// Parses a `LitKind` to a `Constant`. -pub fn lit_to_constant(lit: &LitKind, ty: Option>) -> Constant { - match *lit { - LitKind::Str(ref is, _) => Constant::Str(is.to_string()), - LitKind::Byte(b) => Constant::Int(u128::from(b)), - LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)), - LitKind::Char(c) => Constant::Char(c), - LitKind::Int(n, _) => Constant::Int(n), - LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { - ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), - ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), - }, - LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() { - ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()), - ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()), - _ => bug!(), - }, - LitKind::Bool(b) => Constant::Bool(b), - LitKind::Err(s) => Constant::Err(s), - } -} - -pub fn constant<'tcx>( - lcx: &LateContext<'tcx>, - typeck_results: &ty::TypeckResults<'tcx>, - e: &Expr<'_>, -) -> Option<(Constant, bool)> { - let mut cx = ConstEvalLateContext { - lcx, - typeck_results, - param_env: lcx.param_env, - needed_resolution: false, - substs: lcx.tcx.intern_substs(&[]), - }; - cx.expr(e).map(|cst| (cst, cx.needed_resolution)) -} - -pub fn constant_simple<'tcx>( - lcx: &LateContext<'tcx>, - typeck_results: &ty::TypeckResults<'tcx>, - e: &Expr<'_>, -) -> Option { - constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) }) -} - -/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`. -pub fn constant_context<'a, 'tcx>( - lcx: &'a LateContext<'tcx>, - typeck_results: &'a ty::TypeckResults<'tcx>, -) -> ConstEvalLateContext<'a, 'tcx> { - ConstEvalLateContext { - lcx, - typeck_results, - param_env: lcx.param_env, - needed_resolution: false, - substs: lcx.tcx.intern_substs(&[]), - } -} - -pub struct ConstEvalLateContext<'a, 'tcx> { - lcx: &'a LateContext<'tcx>, - typeck_results: &'a ty::TypeckResults<'tcx>, - param_env: ty::ParamEnv<'tcx>, - needed_resolution: bool, - substs: SubstsRef<'tcx>, -} - -impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { - /// Simple constant folding: Insert an expression, get a constant or none. - pub fn expr(&mut self, e: &Expr<'_>) -> Option { - match e.kind { - ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), - ExprKind::Block(ref block, _) => self.block(block), - ExprKind::Lit(ref lit) => Some(lit_to_constant(&lit.node, self.typeck_results.expr_ty_opt(e))), - ExprKind::Array(ref vec) => self.multi(vec).map(Constant::Vec), - ExprKind::Tup(ref tup) => self.multi(tup).map(Constant::Tuple), - ExprKind::Repeat(ref value, _) => { - let n = match self.typeck_results.expr_ty(e).kind() { - ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?, - _ => span_bug!(e.span, "typeck error"), - }; - self.expr(value).map(|v| Constant::Repeat(Box::new(v), n)) - }, - ExprKind::Unary(op, ref operand) => self.expr(operand).and_then(|o| match op { - UnOp::UnNot => self.constant_not(&o, self.typeck_results.expr_ty(e)), - UnOp::UnNeg => self.constant_negate(&o, self.typeck_results.expr_ty(e)), - UnOp::UnDeref => Some(if let Constant::Ref(r) = o { *r } else { o }), - }), - ExprKind::If(ref cond, ref then, ref otherwise) => self.ifthenelse(cond, then, *otherwise), - ExprKind::Binary(op, ref left, ref right) => self.binop(op, left, right), - ExprKind::Call(ref callee, ref args) => { - // We only handle a few const functions for now. - if_chain! { - if args.is_empty(); - if let ExprKind::Path(qpath) = &callee.kind; - let res = self.typeck_results.qpath_res(qpath, callee.hir_id); - if let Some(def_id) = res.opt_def_id(); - let def_path: Vec<_> = self.lcx.get_def_path(def_id).into_iter().map(Symbol::as_str).collect(); - let def_path: Vec<&str> = def_path.iter().take(4).map(|s| &**s).collect(); - if let ["core", "num", int_impl, "max_value"] = *def_path; - then { - let value = match int_impl { - "" => i8::MAX as u128, - "" => i16::MAX as u128, - "" => i32::MAX as u128, - "" => i64::MAX as u128, - "" => i128::MAX as u128, - _ => return None, - }; - Some(Constant::Int(value)) - } - else { - None - } - } - }, - ExprKind::Index(ref arr, ref index) => self.index(arr, index), - ExprKind::AddrOf(_, _, ref inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), - // TODO: add other expressions. - _ => None, - } - } - - #[allow(clippy::cast_possible_wrap)] - fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { - use self::Constant::{Bool, Int}; - match *o { - Bool(b) => Some(Bool(!b)), - Int(value) => { - let value = !value; - match *ty.kind() { - ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))), - ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))), - _ => None, - } - }, - _ => None, - } - } - - fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { - use self::Constant::{Int, F32, F64}; - match *o { - Int(value) => { - let ity = match *ty.kind() { - ty::Int(ity) => ity, - _ => return None, - }; - // sign extend - let value = sext(self.lcx.tcx, value, ity); - let value = value.checked_neg()?; - // clear unused bits - Some(Int(unsext(self.lcx.tcx, value, ity))) - }, - F32(f) => Some(F32(-f)), - F64(f) => Some(F64(-f)), - _ => None, - } - } - - /// Create `Some(Vec![..])` of all constants, unless there is any - /// non-constant part. - fn multi(&mut self, vec: &[Expr<'_>]) -> Option> { - vec.iter().map(|elem| self.expr(elem)).collect::>() - } - - /// Lookup a possibly constant expression from a `ExprKind::Path`. - fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option { - let res = self.typeck_results.qpath_res(qpath, id); - match res { - Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { - let substs = self.typeck_results.node_substs(id); - let substs = if self.substs.is_empty() { - substs - } else { - substs.subst(self.lcx.tcx, self.substs) - }; - - let result = self - .lcx - .tcx - .const_eval_resolve( - self.param_env, - ty::WithOptConstParam::unknown(def_id), - substs, - None, - None, - ) - .ok() - .map(|val| rustc_middle::ty::Const::from_value(self.lcx.tcx, val, ty))?; - let result = miri_to_const(&result); - if result.is_some() { - self.needed_resolution = true; - } - result - }, - // FIXME: cover all usable cases. - _ => None, - } - } - - fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { - let lhs = self.expr(lhs); - let index = self.expr(index); - - match (lhs, index) { - (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) { - Some(Constant::F32(x)) => Some(Constant::F32(*x)), - Some(Constant::F64(x)) => Some(Constant::F64(*x)), - _ => None, - }, - (Some(Constant::Vec(vec)), _) => { - if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) { - match vec.get(0) { - Some(Constant::F32(x)) => Some(Constant::F32(*x)), - Some(Constant::F64(x)) => Some(Constant::F64(*x)), - _ => None, - } - } else { - None - } - }, - _ => None, - } - } - - /// A block can only yield a constant if it only has one constant expression. - fn block(&mut self, block: &Block<'_>) -> Option { - if block.stmts.is_empty() { - block.expr.as_ref().and_then(|b| self.expr(b)) - } else { - None - } - } - - fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { - if let Some(Constant::Bool(b)) = self.expr(cond) { - if b { - self.expr(&*then) - } else { - otherwise.as_ref().and_then(|expr| self.expr(expr)) - } - } else { - None - } - } - - fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option { - let l = self.expr(left)?; - let r = self.expr(right); - match (l, r) { - (Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() { - ty::Int(ity) => { - let l = sext(self.lcx.tcx, l, ity); - let r = sext(self.lcx.tcx, r, ity); - let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity)); - match op.node { - BinOpKind::Add => l.checked_add(r).map(zext), - BinOpKind::Sub => l.checked_sub(r).map(zext), - BinOpKind::Mul => l.checked_mul(r).map(zext), - BinOpKind::Div if r != 0 => l.checked_div(r).map(zext), - BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext), - BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext), - BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext), - BinOpKind::BitXor => Some(zext(l ^ r)), - BinOpKind::BitOr => Some(zext(l | r)), - BinOpKind::BitAnd => Some(zext(l & r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - } - }, - ty::Uint(_) => match op.node { - BinOpKind::Add => l.checked_add(r).map(Constant::Int), - BinOpKind::Sub => l.checked_sub(r).map(Constant::Int), - BinOpKind::Mul => l.checked_mul(r).map(Constant::Int), - BinOpKind::Div => l.checked_div(r).map(Constant::Int), - BinOpKind::Rem => l.checked_rem(r).map(Constant::Int), - BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int), - BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int), - BinOpKind::BitXor => Some(Constant::Int(l ^ r)), - BinOpKind::BitOr => Some(Constant::Int(l | r)), - BinOpKind::BitAnd => Some(Constant::Int(l & r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - }, - _ => None, - }, - (Constant::F32(l), Some(Constant::F32(r))) => match op.node { - BinOpKind::Add => Some(Constant::F32(l + r)), - BinOpKind::Sub => Some(Constant::F32(l - r)), - BinOpKind::Mul => Some(Constant::F32(l * r)), - BinOpKind::Div => Some(Constant::F32(l / r)), - BinOpKind::Rem => Some(Constant::F32(l % r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - }, - (Constant::F64(l), Some(Constant::F64(r))) => match op.node { - BinOpKind::Add => Some(Constant::F64(l + r)), - BinOpKind::Sub => Some(Constant::F64(l - r)), - BinOpKind::Mul => Some(Constant::F64(l * r)), - BinOpKind::Div => Some(Constant::F64(l / r)), - BinOpKind::Rem => Some(Constant::F64(l % r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - }, - (l, r) => match (op.node, l, r) { - (BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)), - (BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)), - (BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => { - Some(r) - }, - (BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)), - (BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)), - (BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)), - _ => None, - }, - } - } -} - -pub fn miri_to_const(result: &ty::Const<'_>) -> Option { - use rustc_middle::mir::interpret::ConstValue; - match result.val { - ty::ConstKind::Value(ConstValue::Scalar(Scalar::Int(int))) => { - match result.ty.kind() { - ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), - ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), - ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( - int.try_into().expect("invalid f32 bit representation"), - ))), - ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( - int.try_into().expect("invalid f64 bit representation"), - ))), - ty::RawPtr(type_and_mut) => { - if let ty::Uint(_) = type_and_mut.ty.kind() { - return Some(Constant::RawPtr(int.assert_bits(int.size()))); - } - None - }, - // FIXME: implement other conversions. - _ => None, - } - }, - ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind() { - ty::Ref(_, tam, _) => match tam.kind() { - ty::Str => String::from_utf8( - data.inspect_with_uninit_and_ptr_outside_interpreter(start..end) - .to_owned(), - ) - .ok() - .map(Constant::Str), - _ => None, - }, - _ => None, - }, - ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind() { - ty::Array(sub_type, len) => match sub_type.kind() { - ty::Float(FloatTy::F32) => match miri_to_const(len) { - Some(Constant::Int(len)) => alloc - .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize)) - .to_owned() - .chunks(4) - .map(|chunk| { - Some(Constant::F32(f32::from_le_bytes( - chunk.try_into().expect("this shouldn't happen"), - ))) - }) - .collect::>>() - .map(Constant::Vec), - _ => None, - }, - ty::Float(FloatTy::F64) => match miri_to_const(len) { - Some(Constant::Int(len)) => alloc - .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize)) - .to_owned() - .chunks(8) - .map(|chunk| { - Some(Constant::F64(f64::from_le_bytes( - chunk.try_into().expect("this shouldn't happen"), - ))) - }) - .collect::>>() - .map(Constant::Vec), - _ => None, - }, - // FIXME: implement other array type conversions. - _ => None, - }, - _ => None, - }, - // FIXME: implement other conversions. - _ => None, - } -} +pub use clippy_utils::consts::*; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 5a40c00bd673..1cd5da274e97 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,13 +1,9 @@ // error-pattern:cargo-clippy -#![feature(bindings_after_at)] #![feature(box_patterns)] #![feature(box_syntax)] -#![feature(concat_idents)] -#![feature(crate_visibility_modifier)] #![feature(drain_filter)] #![feature(in_band_lifetimes)] -#![feature(once_cell)] #![feature(or_patterns)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] @@ -50,103 +46,23 @@ use rustc_data_structures::fx::FxHashSet; use rustc_lint::LintId; use rustc_session::Session; -/// Macro used to declare a Clippy lint. -/// -/// Every lint declaration consists of 4 parts: -/// -/// 1. The documentation, which is used for the website -/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions. -/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or -/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of. -/// 4. The `description` that contains a short explanation on what's wrong with code where the -/// lint is triggered. -/// -/// Currently the categories `style`, `correctness`, `complexity` and `perf` are enabled by default. -/// As said in the README.md of this repository, if the lint level mapping changes, please update -/// README.md. -/// -/// # Example -/// -/// ``` -/// #![feature(rustc_private)] -/// extern crate rustc_session; -/// use rustc_session::declare_tool_lint; -/// use clippy_lints::declare_clippy_lint; -/// -/// declare_clippy_lint! { -/// /// **What it does:** Checks for ... (describe what the lint matches). -/// /// -/// /// **Why is this bad?** Supply the reason for linting the code. -/// /// -/// /// **Known problems:** None. (Or describe where it could go wrong.) -/// /// -/// /// **Example:** -/// /// -/// /// ```rust -/// /// // Bad -/// /// Insert a short example of code that triggers the lint -/// /// -/// /// // Good -/// /// Insert a short example of improved code that doesn't trigger the lint -/// /// ``` -/// pub LINT_NAME, -/// pedantic, -/// "description" -/// } -/// ``` -/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints #[macro_export] macro_rules! declare_clippy_lint { - { $(#[$attr:meta])* pub $name:tt, style, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, correctness, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Deny, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, complexity, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, perf, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, pedantic, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, restriction, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, cargo, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, nursery, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, internal, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true - } - }; - { $(#[$attr:meta])* pub $name:tt, internal_warn, $description:tt } => { - declare_tool_lint! { - $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true - } - }; + ( $($x:tt)* ) => { clippy_utils::declare_clippy_lint!($($x)*); } +} + +#[macro_export] +macro_rules! sym { + ( $($x:tt)* ) => { clippy_utils::sym!($($x)*) } +} + +#[macro_export] +macro_rules! unwrap_cargo_metadata { + ( $($x:tt)* ) => { clippy_utils::unwrap_cargo_metadata!($($x)*) } +} + +macro_rules! extract_msrv_attr { + ( $($x:tt)* ) => { clippy_utils::extract_msrv_attr!($($x)*); } } mod consts; @@ -366,7 +282,7 @@ pub use crate::utils::conf::Conf; /// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. /// /// Used in `./src/driver.rs`. -pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) { +fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) { store.register_pre_expansion_pass(|| box write::Write::default()); store.register_pre_expansion_pass(|| box attrs::EarlyAttributes); store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro); @@ -2015,6 +1931,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&transmute::USELESS_TRANSMUTE), LintId::of(&use_self::USE_SELF), ]); + + register_pre_expansion_lints(store); + register_renamed(store); } #[rustfmt::skip] @@ -2064,7 +1983,7 @@ fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) { /// Register renamed lints. /// /// Used in `./src/driver.rs`. -pub fn register_renamed(ls: &mut rustc_lint::LintStore) { +fn register_renamed(ls: &mut rustc_lint::LintStore) { ls.register_renamed("clippy::stutter", "clippy::module_name_repetitions"); ls.register_renamed("clippy::new_without_default_derive", "clippy::new_without_default"); ls.register_renamed("clippy::cyclomatic_complexity", "clippy::cognitive_complexity"); diff --git a/clippy_lints/src/utils.rs b/clippy_lints/src/utils.rs new file mode 100644 index 000000000000..bf54e39769cb --- /dev/null +++ b/clippy_lints/src/utils.rs @@ -0,0 +1 @@ +pub use clippy_utils::*; diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml new file mode 100644 index 000000000000..d99dbbd7aa38 --- /dev/null +++ b/clippy_utils/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "clippy_utils" +version = "0.1.51" +authors = ["The Rust Clippy Developers"] +edition = "2018" + +[dependencies] +if_chain = "1.0.0" +itertools = "0.9" +regex-syntax = "0.6" +serde = { version = "1.0", features = ["derive"] } +smallvec = { version = "1", features = ["union"] } +toml = "0.5.3" +unicode-normalization = "0.1" +rustc-semver="1.1.0" + +[features] +internal-lints = [] diff --git a/clippy_lints/src/utils/ast_utils.rs b/clippy_utils/src/ast_utils.rs similarity index 99% rename from clippy_lints/src/utils/ast_utils.rs rename to clippy_utils/src/ast_utils.rs index 44eb3968ae73..7ec0e103c000 100644 --- a/clippy_lints/src/utils/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -4,7 +4,7 @@ #![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] -use crate::utils::{both, over}; +use crate::{both, over}; use rustc_ast::ptr::P; use rustc_ast::{self as ast, *}; use rustc_span::symbol::Ident; diff --git a/clippy_lints/src/utils/ast_utils/ident_iter.rs b/clippy_utils/src/ast_utils/ident_iter.rs similarity index 100% rename from clippy_lints/src/utils/ast_utils/ident_iter.rs rename to clippy_utils/src/ast_utils/ident_iter.rs diff --git a/clippy_lints/src/utils/attrs.rs b/clippy_utils/src/attrs.rs similarity index 100% rename from clippy_lints/src/utils/attrs.rs rename to clippy_utils/src/attrs.rs diff --git a/clippy_lints/src/utils/author.rs b/clippy_utils/src/author.rs similarity index 99% rename from clippy_lints/src/utils/author.rs rename to clippy_utils/src/author.rs index ca60d335262b..ba83566b5e65 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_utils/src/author.rs @@ -1,7 +1,7 @@ //! A group of attributes that can be attached to Rust code in order //! to generate a clippy lint detecting said code automatically. -use crate::utils::get_attr; +use crate::{declare_clippy_lint, get_attr}; use rustc_ast::ast::{Attribute, LitFloatType, LitKind}; use rustc_ast::walk_list; use rustc_data_structures::fx::FxHashMap; diff --git a/clippy_lints/src/utils/camel_case.rs b/clippy_utils/src/camel_case.rs similarity index 100% rename from clippy_lints/src/utils/camel_case.rs rename to clippy_utils/src/camel_case.rs diff --git a/clippy_lints/src/utils/comparisons.rs b/clippy_utils/src/comparisons.rs similarity index 100% rename from clippy_lints/src/utils/comparisons.rs rename to clippy_utils/src/comparisons.rs diff --git a/clippy_lints/src/utils/conf.rs b/clippy_utils/src/conf.rs similarity index 100% rename from clippy_lints/src/utils/conf.rs rename to clippy_utils/src/conf.rs diff --git a/clippy_lints/src/utils/constants.rs b/clippy_utils/src/constants.rs similarity index 100% rename from clippy_lints/src/utils/constants.rs rename to clippy_utils/src/constants.rs diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs new file mode 100644 index 000000000000..e8ce57ea0046 --- /dev/null +++ b/clippy_utils/src/consts.rs @@ -0,0 +1,574 @@ +#![allow(clippy::float_cmp)] + +use crate::{clip, sext, unsext}; +use if_chain::if_chain; +use rustc_ast::ast::{self, LitFloatType, LitKind}; +use rustc_data_structures::sync::Lrc; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::mir::interpret::Scalar; +use rustc_middle::ty::subst::{Subst, SubstsRef}; +use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt}; +use rustc_middle::{bug, span_bug}; +use rustc_span::symbol::Symbol; +use std::cmp::Ordering::{self, Equal}; +use std::convert::TryInto; +use std::hash::{Hash, Hasher}; + +/// A `LitKind`-like enum to fold constant `Expr`s into. +#[derive(Debug, Clone)] +pub enum Constant { + /// A `String` (e.g., "abc"). + Str(String), + /// A binary string (e.g., `b"abc"`). + Binary(Lrc<[u8]>), + /// A single `char` (e.g., `'a'`). + Char(char), + /// An integer's bit representation. + Int(u128), + /// An `f32`. + F32(f32), + /// An `f64`. + F64(f64), + /// `true` or `false`. + Bool(bool), + /// An array of constants. + Vec(Vec), + /// Also an array, but with only one constant, repeated N times. + Repeat(Box, u64), + /// A tuple of constants. + Tuple(Vec), + /// A raw pointer. + RawPtr(u128), + /// A reference + Ref(Box), + /// A literal with syntax error. + Err(Symbol), +} + +impl PartialEq for Constant { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs, + (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r, + (&Self::Char(l), &Self::Char(r)) => l == r, + (&Self::Int(l), &Self::Int(r)) => l == r, + (&Self::F64(l), &Self::F64(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + l.to_bits() == r.to_bits() + }, + (&Self::F32(l), &Self::F32(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + f64::from(l).to_bits() == f64::from(r).to_bits() + }, + (&Self::Bool(l), &Self::Bool(r)) => l == r, + (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv, + (&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb, + // TODO: are there inter-type equalities? + _ => false, + } + } +} + +impl Hash for Constant { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + std::mem::discriminant(self).hash(state); + match *self { + Self::Str(ref s) => { + s.hash(state); + }, + Self::Binary(ref b) => { + b.hash(state); + }, + Self::Char(c) => { + c.hash(state); + }, + Self::Int(i) => { + i.hash(state); + }, + Self::F32(f) => { + f64::from(f).to_bits().hash(state); + }, + Self::F64(f) => { + f.to_bits().hash(state); + }, + Self::Bool(b) => { + b.hash(state); + }, + Self::Vec(ref v) | Self::Tuple(ref v) => { + v.hash(state); + }, + Self::Repeat(ref c, l) => { + c.hash(state); + l.hash(state); + }, + Self::RawPtr(u) => { + u.hash(state); + }, + Self::Ref(ref r) => { + r.hash(state); + }, + Self::Err(ref s) => { + s.hash(state); + }, + } + } +} + +impl Constant { + pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { + match (left, right) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)), + (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)), + (&Self::Int(l), &Self::Int(r)) => { + if let ty::Int(int_ty) = *cmp_type.kind() { + Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))) + } else { + Some(l.cmp(&r)) + } + }, + (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r), + (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r), + (&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)), + (&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => l + .iter() + .zip(r.iter()) + .map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri)) + .find(|r| r.map_or(true, |o| o != Ordering::Equal)) + .unwrap_or_else(|| Some(l.len().cmp(&r.len()))), + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => { + match Self::partial_cmp(tcx, cmp_type, lv, rv) { + Some(Equal) => Some(ls.cmp(rs)), + x => x, + } + }, + (&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb), + // TODO: are there any useful inter-type orderings? + _ => None, + } + } +} + +/// Parses a `LitKind` to a `Constant`. +pub fn lit_to_constant(lit: &LitKind, ty: Option>) -> Constant { + match *lit { + LitKind::Str(ref is, _) => Constant::Str(is.to_string()), + LitKind::Byte(b) => Constant::Int(u128::from(b)), + LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)), + LitKind::Char(c) => Constant::Char(c), + LitKind::Int(n, _) => Constant::Int(n), + LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { + ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), + ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), + }, + LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() { + ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()), + ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()), + _ => bug!(), + }, + LitKind::Bool(b) => Constant::Bool(b), + LitKind::Err(s) => Constant::Err(s), + } +} + +pub fn constant<'tcx>( + lcx: &LateContext<'tcx>, + typeck_results: &ty::TypeckResults<'tcx>, + e: &Expr<'_>, +) -> Option<(Constant, bool)> { + let mut cx = ConstEvalLateContext { + lcx, + typeck_results, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + }; + cx.expr(e).map(|cst| (cst, cx.needed_resolution)) +} + +pub fn constant_simple<'tcx>( + lcx: &LateContext<'tcx>, + typeck_results: &ty::TypeckResults<'tcx>, + e: &Expr<'_>, +) -> Option { + constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) }) +} + +/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`. +pub fn constant_context<'a, 'tcx>( + lcx: &'a LateContext<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, +) -> ConstEvalLateContext<'a, 'tcx> { + ConstEvalLateContext { + lcx, + typeck_results, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + } +} + +pub struct ConstEvalLateContext<'a, 'tcx> { + lcx: &'a LateContext<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, + param_env: ty::ParamEnv<'tcx>, + needed_resolution: bool, + substs: SubstsRef<'tcx>, +} + +impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { + /// Simple constant folding: Insert an expression, get a constant or none. + pub fn expr(&mut self, e: &Expr<'_>) -> Option { + match e.kind { + ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), + ExprKind::Block(ref block, _) => self.block(block), + ExprKind::Lit(ref lit) => Some(lit_to_constant(&lit.node, self.typeck_results.expr_ty_opt(e))), + ExprKind::Array(ref vec) => self.multi(vec).map(Constant::Vec), + ExprKind::Tup(ref tup) => self.multi(tup).map(Constant::Tuple), + ExprKind::Repeat(ref value, _) => { + let n = match self.typeck_results.expr_ty(e).kind() { + ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?, + _ => span_bug!(e.span, "typeck error"), + }; + self.expr(value).map(|v| Constant::Repeat(Box::new(v), n)) + }, + ExprKind::Unary(op, ref operand) => self.expr(operand).and_then(|o| match op { + UnOp::UnNot => self.constant_not(&o, self.typeck_results.expr_ty(e)), + UnOp::UnNeg => self.constant_negate(&o, self.typeck_results.expr_ty(e)), + UnOp::UnDeref => Some(if let Constant::Ref(r) = o { *r } else { o }), + }), + ExprKind::If(ref cond, ref then, ref otherwise) => self.ifthenelse(cond, then, *otherwise), + ExprKind::Binary(op, ref left, ref right) => self.binop(op, left, right), + ExprKind::Call(ref callee, ref args) => { + // We only handle a few const functions for now. + if_chain! { + if args.is_empty(); + if let ExprKind::Path(qpath) = &callee.kind; + let res = self.typeck_results.qpath_res(qpath, callee.hir_id); + if let Some(def_id) = res.opt_def_id(); + let def_path: Vec<_> = self.lcx.get_def_path(def_id).into_iter().map(Symbol::as_str).collect(); + let def_path: Vec<&str> = def_path.iter().take(4).map(|s| &**s).collect(); + if let ["core", "num", int_impl, "max_value"] = *def_path; + then { + let value = match int_impl { + "" => i8::MAX as u128, + "" => i16::MAX as u128, + "" => i32::MAX as u128, + "" => i64::MAX as u128, + "" => i128::MAX as u128, + _ => return None, + }; + Some(Constant::Int(value)) + } + else { + None + } + } + }, + ExprKind::Index(ref arr, ref index) => self.index(arr, index), + ExprKind::AddrOf(_, _, ref inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), + // TODO: add other expressions. + _ => None, + } + } + + #[allow(clippy::cast_possible_wrap)] + fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { + use self::Constant::{Bool, Int}; + match *o { + Bool(b) => Some(Bool(!b)), + Int(value) => { + let value = !value; + match *ty.kind() { + ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))), + ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))), + _ => None, + } + }, + _ => None, + } + } + + fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { + use self::Constant::{Int, F32, F64}; + match *o { + Int(value) => { + let ity = match *ty.kind() { + ty::Int(ity) => ity, + _ => return None, + }; + // sign extend + let value = sext(self.lcx.tcx, value, ity); + let value = value.checked_neg()?; + // clear unused bits + Some(Int(unsext(self.lcx.tcx, value, ity))) + }, + F32(f) => Some(F32(-f)), + F64(f) => Some(F64(-f)), + _ => None, + } + } + + /// Create `Some(Vec![..])` of all constants, unless there is any + /// non-constant part. + fn multi(&mut self, vec: &[Expr<'_>]) -> Option> { + vec.iter().map(|elem| self.expr(elem)).collect::>() + } + + /// Lookup a possibly constant expression from a `ExprKind::Path`. + fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option { + let res = self.typeck_results.qpath_res(qpath, id); + match res { + Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { + let substs = self.typeck_results.node_substs(id); + let substs = if self.substs.is_empty() { + substs + } else { + substs.subst(self.lcx.tcx, self.substs) + }; + + let result = self + .lcx + .tcx + .const_eval_resolve( + self.param_env, + ty::WithOptConstParam::unknown(def_id), + substs, + None, + None, + ) + .ok() + .map(|val| rustc_middle::ty::Const::from_value(self.lcx.tcx, val, ty))?; + let result = miri_to_const(&result); + if result.is_some() { + self.needed_resolution = true; + } + result + }, + // FIXME: cover all usable cases. + _ => None, + } + } + + fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { + let lhs = self.expr(lhs); + let index = self.expr(index); + + match (lhs, index) { + (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + }, + (Some(Constant::Vec(vec)), _) => { + if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) { + match vec.get(0) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + } + } else { + None + } + }, + _ => None, + } + } + + /// A block can only yield a constant if it only has one constant expression. + fn block(&mut self, block: &Block<'_>) -> Option { + if block.stmts.is_empty() { + block.expr.as_ref().and_then(|b| self.expr(b)) + } else { + None + } + } + + fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { + if let Some(Constant::Bool(b)) = self.expr(cond) { + if b { + self.expr(&*then) + } else { + otherwise.as_ref().and_then(|expr| self.expr(expr)) + } + } else { + None + } + } + + fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option { + let l = self.expr(left)?; + let r = self.expr(right); + match (l, r) { + (Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() { + ty::Int(ity) => { + let l = sext(self.lcx.tcx, l, ity); + let r = sext(self.lcx.tcx, r, ity); + let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity)); + match op.node { + BinOpKind::Add => l.checked_add(r).map(zext), + BinOpKind::Sub => l.checked_sub(r).map(zext), + BinOpKind::Mul => l.checked_mul(r).map(zext), + BinOpKind::Div if r != 0 => l.checked_div(r).map(zext), + BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::BitXor => Some(zext(l ^ r)), + BinOpKind::BitOr => Some(zext(l | r)), + BinOpKind::BitAnd => Some(zext(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + } + }, + ty::Uint(_) => match op.node { + BinOpKind::Add => l.checked_add(r).map(Constant::Int), + BinOpKind::Sub => l.checked_sub(r).map(Constant::Int), + BinOpKind::Mul => l.checked_mul(r).map(Constant::Int), + BinOpKind::Div => l.checked_div(r).map(Constant::Int), + BinOpKind::Rem => l.checked_rem(r).map(Constant::Int), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::BitXor => Some(Constant::Int(l ^ r)), + BinOpKind::BitOr => Some(Constant::Int(l | r)), + BinOpKind::BitAnd => Some(Constant::Int(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + _ => None, + }, + (Constant::F32(l), Some(Constant::F32(r))) => match op.node { + BinOpKind::Add => Some(Constant::F32(l + r)), + BinOpKind::Sub => Some(Constant::F32(l - r)), + BinOpKind::Mul => Some(Constant::F32(l * r)), + BinOpKind::Div => Some(Constant::F32(l / r)), + BinOpKind::Rem => Some(Constant::F32(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (Constant::F64(l), Some(Constant::F64(r))) => match op.node { + BinOpKind::Add => Some(Constant::F64(l + r)), + BinOpKind::Sub => Some(Constant::F64(l - r)), + BinOpKind::Mul => Some(Constant::F64(l * r)), + BinOpKind::Div => Some(Constant::F64(l / r)), + BinOpKind::Rem => Some(Constant::F64(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (l, r) => match (op.node, l, r) { + (BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)), + (BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)), + (BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => { + Some(r) + }, + (BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)), + (BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)), + (BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)), + _ => None, + }, + } + } +} + +pub fn miri_to_const(result: &ty::Const<'_>) -> Option { + use rustc_middle::mir::interpret::ConstValue; + match result.val { + ty::ConstKind::Value(ConstValue::Scalar(Scalar::Int(int))) => { + match result.ty.kind() { + ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), + ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), + ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( + int.try_into().expect("invalid f32 bit representation"), + ))), + ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( + int.try_into().expect("invalid f64 bit representation"), + ))), + ty::RawPtr(type_and_mut) => { + if let ty::Uint(_) = type_and_mut.ty.kind() { + return Some(Constant::RawPtr(int.assert_bits(int.size()))); + } + None + }, + // FIXME: implement other conversions. + _ => None, + } + }, + ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind() { + ty::Ref(_, tam, _) => match tam.kind() { + ty::Str => String::from_utf8( + data.inspect_with_uninit_and_ptr_outside_interpreter(start..end) + .to_owned(), + ) + .ok() + .map(Constant::Str), + _ => None, + }, + _ => None, + }, + ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind() { + ty::Array(sub_type, len) => match sub_type.kind() { + ty::Float(FloatTy::F32) => match miri_to_const(len) { + Some(Constant::Int(len)) => alloc + .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize)) + .to_owned() + .chunks(4) + .map(|chunk| { + Some(Constant::F32(f32::from_le_bytes( + chunk.try_into().expect("this shouldn't happen"), + ))) + }) + .collect::>>() + .map(Constant::Vec), + _ => None, + }, + ty::Float(FloatTy::F64) => match miri_to_const(len) { + Some(Constant::Int(len)) => alloc + .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize)) + .to_owned() + .chunks(8) + .map(|chunk| { + Some(Constant::F64(f64::from_le_bytes( + chunk.try_into().expect("this shouldn't happen"), + ))) + }) + .collect::>>() + .map(Constant::Vec), + _ => None, + }, + // FIXME: implement other array type conversions. + _ => None, + }, + _ => None, + }, + // FIXME: implement other conversions. + _ => None, + } +} diff --git a/clippy_lints/src/utils/diagnostics.rs b/clippy_utils/src/diagnostics.rs similarity index 100% rename from clippy_lints/src/utils/diagnostics.rs rename to clippy_utils/src/diagnostics.rs diff --git a/clippy_lints/src/utils/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs similarity index 97% rename from clippy_lints/src/utils/eager_or_lazy.rs rename to clippy_utils/src/eager_or_lazy.rs index 2f157c5030f4..52a33e9b1704 100644 --- a/clippy_lints/src/utils/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -9,7 +9,7 @@ //! - or-fun-call //! - option-if-let-else -use crate::utils::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths}; +use crate::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit; diff --git a/clippy_lints/src/utils/higher.rs b/clippy_utils/src/higher.rs similarity index 99% rename from clippy_lints/src/utils/higher.rs rename to clippy_utils/src/higher.rs index 340d340d6d34..280ff721c1fc 100644 --- a/clippy_lints/src/utils/higher.rs +++ b/clippy_utils/src/higher.rs @@ -3,7 +3,7 @@ #![deny(clippy::missing_docs_in_private_items)] -use crate::utils::{is_expn_of, match_def_path, paths}; +use crate::{is_expn_of, match_def_path, paths}; use if_chain::if_chain; use rustc_ast::ast; use rustc_hir as hir; diff --git a/clippy_lints/src/utils/hir_utils.rs b/clippy_utils/src/hir_utils.rs similarity index 99% rename from clippy_lints/src/utils/hir_utils.rs rename to clippy_utils/src/hir_utils.rs index c5870dc51248..60ff1ebec732 100644 --- a/clippy_lints/src/utils/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1,5 +1,5 @@ use crate::consts::{constant_context, constant_simple}; -use crate::utils::differing_macro_contexts; +use crate::differing_macro_contexts; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_hir::{ diff --git a/clippy_lints/src/utils/inspector.rs b/clippy_utils/src/inspector.rs similarity index 99% rename from clippy_lints/src/utils/inspector.rs rename to clippy_utils/src/inspector.rs index 9bec24be9e4e..b082bb6cfda7 100644 --- a/clippy_lints/src/utils/inspector.rs +++ b/clippy_utils/src/inspector.rs @@ -1,6 +1,6 @@ //! checks for attributes -use crate::utils::get_attr; +use crate::{declare_clippy_lint, get_attr}; use rustc_ast::ast::{Attribute, InlineAsmTemplatePiece}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_utils/src/internal_lints.rs similarity index 99% rename from clippy_lints/src/utils/internal_lints.rs rename to clippy_utils/src/internal_lints.rs index cccad243e1b5..d672a4a41ea6 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_utils/src/internal_lints.rs @@ -1,7 +1,7 @@ use crate::consts::{constant_simple, Constant}; -use crate::utils::{ - is_expn_of, match_def_path, match_qpath, match_type, method_calls, path_to_res, paths, run_lints, snippet, - span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, +use crate::{ + declare_clippy_lint, is_expn_of, match_def_path, match_qpath, match_type, method_calls, path_to_res, paths, + run_lints, snippet, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, }; use if_chain::if_chain; use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, NodeId}; diff --git a/clippy_lints/src/utils/mod.rs b/clippy_utils/src/lib.rs similarity index 91% rename from clippy_lints/src/utils/mod.rs rename to clippy_utils/src/lib.rs index ef45f9fdcd5d..2076b2ebc72e 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_utils/src/lib.rs @@ -1,3 +1,29 @@ +#![feature(box_patterns)] +#![feature(in_band_lifetimes)] +#![feature(once_cell)] +#![feature(or_patterns)] +#![feature(rustc_private)] +#![recursion_limit = "512"] +#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] + +// FIXME: switch to something more ergonomic here, once available. +// (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_ast; +extern crate rustc_ast_pretty; +extern crate rustc_data_structures; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_hir_pretty; +extern crate rustc_infer; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_mir; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_target; +extern crate rustc_trait_selection; +extern crate rustc_typeck; + #[macro_use] pub mod sym_helper; @@ -9,6 +35,7 @@ pub mod camel_case; pub mod comparisons; pub mod conf; pub mod constants; +pub mod consts; mod diagnostics; pub mod eager_or_lazy; pub mod higher; @@ -37,7 +64,7 @@ use rustc_ast::ast::{self, Attribute, LitKind}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; use rustc_hir::Node; @@ -50,7 +77,7 @@ use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::exports::Export; use rustc_middle::hir::map::Map; use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; -use rustc_middle::ty::{self, layout::IntegerExt, Ty, TyCtxt, TypeFoldable}; +use rustc_middle::ty::{self, layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable}; use rustc_semver::RustcVersion; use rustc_session::Session; use rustc_span::hygiene::{ExpnKind, MacroKind}; @@ -64,6 +91,105 @@ use smallvec::SmallVec; use crate::consts::{constant, Constant}; +/// Macro used to declare a Clippy lint. +/// +/// Every lint declaration consists of 4 parts: +/// +/// 1. The documentation, which is used for the website +/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions. +/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or +/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of. +/// 4. The `description` that contains a short explanation on what's wrong with code where the +/// lint is triggered. +/// +/// Currently the categories `style`, `correctness`, `complexity` and `perf` are enabled by default. +/// As said in the README.md of this repository, if the lint level mapping changes, please update +/// README.md. +/// +/// # Example +/// +/// ``` +/// #![feature(rustc_private)] +/// extern crate rustc_session; +/// use rustc_session::declare_tool_lint; +/// use clippy_lints::declare_clippy_lint; +/// +/// declare_clippy_lint! { +/// /// **What it does:** Checks for ... (describe what the lint matches). +/// /// +/// /// **Why is this bad?** Supply the reason for linting the code. +/// /// +/// /// **Known problems:** None. (Or describe where it could go wrong.) +/// /// +/// /// **Example:** +/// /// +/// /// ```rust +/// /// // Bad +/// /// Insert a short example of code that triggers the lint +/// /// +/// /// // Good +/// /// Insert a short example of improved code that doesn't trigger the lint +/// /// ``` +/// pub LINT_NAME, +/// pedantic, +/// "description" +/// } +/// ``` +/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints +#[macro_export] +macro_rules! declare_clippy_lint { + { $(#[$attr:meta])* pub $name:tt, style, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, correctness, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Deny, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, complexity, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, perf, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, pedantic, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, restriction, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, cargo, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, nursery, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, internal, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Allow, $description, report_in_external_macro: true + } + }; + { $(#[$attr:meta])* pub $name:tt, internal_warn, $description:tt } => { + declare_tool_lint! { + $(#[$attr])* pub clippy::$name, Warn, $description, report_in_external_macro: true + } + }; +} + pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { if let Ok(version) = RustcVersion::parse(msrv) { return Some(version); @@ -79,6 +205,7 @@ pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool msrv.map_or(true, |msrv| msrv.meets(*lint_msrv)) } +#[macro_export] macro_rules! extract_msrv_attr { (LateContext) => { extract_msrv_attr!(@LateContext, ()); @@ -88,11 +215,11 @@ macro_rules! extract_msrv_attr { }; (@$context:ident$(, $call:tt)?) => { fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) { - use $crate::utils::get_unique_inner_attr; + use $crate::get_unique_inner_attr; match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") { Some(msrv_attr) => { if let Some(msrv) = msrv_attr.value_str() { - self.msrv = $crate::utils::parse_msrv( + self.msrv = $crate::parse_msrv( &msrv.to_string(), Some(cx.sess$($call)?), Some(msrv_attr.span), @@ -1703,6 +1830,30 @@ pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { } } +/// Check if the resolution of a given path is an `Ok` variant of `Result`. +pub fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool { + if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() { + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { + if let Some(variant_id) = cx.tcx.parent(id) { + return variant_id == ok_id; + } + } + } + false +} + +/// Check if the resolution of a given path is a `Some` variant of `Option`. +pub fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool { + if let Some(some_id) = cx.tcx.lang_items().option_some_variant() { + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { + if let Some(variant_id) = cx.tcx.parent(id) { + return variant_id == some_id; + } + } + } + false +} + #[cfg(test)] mod test { use super::{reindent_multiline, without_block_comments}; diff --git a/clippy_lints/src/utils/numeric_literal.rs b/clippy_utils/src/numeric_literal.rs similarity index 100% rename from clippy_lints/src/utils/numeric_literal.rs rename to clippy_utils/src/numeric_literal.rs diff --git a/clippy_lints/src/utils/paths.rs b/clippy_utils/src/paths.rs similarity index 100% rename from clippy_lints/src/utils/paths.rs rename to clippy_utils/src/paths.rs diff --git a/clippy_lints/src/utils/ptr.rs b/clippy_utils/src/ptr.rs similarity index 97% rename from clippy_lints/src/utils/ptr.rs rename to clippy_utils/src/ptr.rs index b330f3d890e9..baeff08e02cd 100644 --- a/clippy_lints/src/utils/ptr.rs +++ b/clippy_utils/src/ptr.rs @@ -1,4 +1,4 @@ -use crate::utils::{get_pat_name, match_var, snippet}; +use crate::{get_pat_name, match_var, snippet}; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{Body, BodyId, Expr, ExprKind, Param}; use rustc_lint::LateContext; diff --git a/clippy_lints/src/utils/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs similarity index 100% rename from clippy_lints/src/utils/qualify_min_const_fn.rs rename to clippy_utils/src/qualify_min_const_fn.rs diff --git a/clippy_lints/src/utils/sugg.rs b/clippy_utils/src/sugg.rs similarity index 99% rename from clippy_lints/src/utils/sugg.rs rename to clippy_utils/src/sugg.rs index 03678db575f0..d4f6f4281d36 100644 --- a/clippy_lints/src/utils/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -1,7 +1,7 @@ //! Contains utility functions to generate suggestions. #![deny(clippy::missing_docs_in_private_items)] -use crate::utils::{higher, snippet, snippet_opt, snippet_with_macro_callsite}; +use crate::{higher, snippet, snippet_opt, snippet_with_macro_callsite}; use rustc_ast::util::parser::AssocOp; use rustc_ast::{ast, token}; use rustc_ast_pretty::pprust::token_kind_to_string; diff --git a/clippy_lints/src/utils/sym_helper.rs b/clippy_utils/src/sym_helper.rs similarity index 100% rename from clippy_lints/src/utils/sym_helper.rs rename to clippy_utils/src/sym_helper.rs diff --git a/clippy_lints/src/utils/usage.rs b/clippy_utils/src/usage.rs similarity index 99% rename from clippy_lints/src/utils/usage.rs rename to clippy_utils/src/usage.rs index fc0db7f64ec9..e58968e7c1bb 100644 --- a/clippy_lints/src/utils/usage.rs +++ b/clippy_utils/src/usage.rs @@ -1,5 +1,5 @@ -use crate::utils; -use crate::utils::match_var; +use crate as utils; +use crate::match_var; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::Res; diff --git a/clippy_lints/src/utils/visitors.rs b/clippy_utils/src/visitors.rs similarity index 100% rename from clippy_lints/src/utils/visitors.rs rename to clippy_utils/src/visitors.rs diff --git a/plugin_examples/allow_clippy_lints/Cargo.toml b/plugin_examples/allow_clippy_lints/Cargo.toml new file mode 100644 index 000000000000..e60c91ddfbb0 --- /dev/null +++ b/plugin_examples/allow_clippy_lints/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "allow_clippy_lints" +version = "0.1.0" +authors = ["The Rust Clippy Developers"] +description = "A tongue-in-cheek example of a Clippy plugin" +edition = "2018" + +[lib] +name = "allow_clippy_lints" +crate-type = ["dylib"] + +[dependencies] +clippy_utils = { path = "../../clippy_utils" } +if_chain = "1.0.1" + +[dev-dependencies] +assert_cmd = "1.0.3" +lazy_static = "1.4.0" diff --git a/plugin_examples/allow_clippy_lints/src/allow_clippy_lints.rs b/plugin_examples/allow_clippy_lints/src/allow_clippy_lints.rs new file mode 100644 index 000000000000..1de548bd57f1 --- /dev/null +++ b/plugin_examples/allow_clippy_lints/src/allow_clippy_lints.rs @@ -0,0 +1,78 @@ +use clippy_utils::{declare_clippy_lint, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::{Attribute, NestedMetaItem}; +use rustc_errors::Applicability; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; +use rustc_span::symbol::SymbolStr; + +declare_clippy_lint! { + /// **What it does:** This tongue-in-cheek lint checks for `#[allow(clippy::...)]`. + /// It is based on `blanket_clippy_restriction_lints`: + /// https://rust-lang.github.io/rust-clippy/master/#blanket_clippy_restriction_lints + /// + /// **Why is this bad?** It's not really. This is just an example of a Clippy plugin. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// #![allow(clippy::assertions_on_constants)] + /// ``` + /// + /// Good: + /// ```rust + /// #[deny(clippy::restriction, clippy::style, clippy::pedantic, clippy::complexity, clippy::perf, clippy::cargo, clippy::nursery)] + /// ``` + pub ALLOW_CLIPPY_LINTS, + correctness, + "use of `#[allow(clippy::...)]`" +} + +declare_lint_pass!(AllowClippyLints => [ + ALLOW_CLIPPY_LINTS, +]); + +impl<'tcx> LateLintPass<'tcx> for AllowClippyLints { + fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) { + if let Some(items) = &attr.meta_item_list() { + if let Some(ident) = attr.ident() { + let ident = &*ident.as_str(); + if ident == "allow" { + check_clippy_lint_names(cx, &attr, items); + } + } + } + } +} + +/// Returns the lint name if it is clippy lint. +fn extract_clippy_lint(lint: &NestedMetaItem) -> Option { + if_chain! { + if let Some(meta_item) = lint.meta_item(); + if meta_item.path.segments.len() > 1; + if let tool_name = meta_item.path.segments[0].ident; + if tool_name.name == sym::clippy; + let lint_name = meta_item.path.segments.last().unwrap().ident.name; + then { + return Some(lint_name.as_str()); + } + } + None +} + +fn check_clippy_lint_names(cx: &LateContext<'_>, attr: &Attribute, items: &[NestedMetaItem]) { + if items.iter().find_map(extract_clippy_lint).is_some() { + span_lint_and_sugg( + cx, + ALLOW_CLIPPY_LINTS, + attr.span, + "allowing Clippy lints denies your project of its true potential", + "use", + "#[deny(clippy::restriction, clippy::style, clippy::pedantic, clippy::complexity, clippy::perf, clippy::cargo, clippy::nursery)]".to_string(), + Applicability::MachineApplicable, + ); + } +} diff --git a/plugin_examples/allow_clippy_lints/src/lib.rs b/plugin_examples/allow_clippy_lints/src/lib.rs new file mode 100644 index 000000000000..562ce2e61c31 --- /dev/null +++ b/plugin_examples/allow_clippy_lints/src/lib.rs @@ -0,0 +1,20 @@ +#![feature(rustc_private)] + +extern crate rustc_ast; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_session; +extern crate rustc_span; + +use clippy_utils::conf::Conf; +use rustc_session::Session; + +mod allow_clippy_lints; + +#[no_mangle] +pub extern "C" fn register_plugins(store: &mut rustc_lint::LintStore, _sess: &Session, _conf: &Conf) { + store.register_lints(&[&allow_clippy_lints::ALLOW_CLIPPY_LINTS]); + store.register_late_pass(|| Box::new(allow_clippy_lints::AllowClippyLints)); +} diff --git a/plugin_examples/allow_clippy_lints/tests/clippy-test.rs b/plugin_examples/allow_clippy_lints/tests/clippy-test.rs new file mode 100644 index 000000000000..9172efcb4a92 --- /dev/null +++ b/plugin_examples/allow_clippy_lints/tests/clippy-test.rs @@ -0,0 +1,37 @@ +use assert_cmd::prelude::*; +use lazy_static::lazy_static; +use std::fs::{canonicalize, metadata, read_dir, read_to_string}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +lazy_static! { + static ref CLIPPY: PathBuf = canonicalize("../../target/debug/cargo-clippy").unwrap(); +} + +const PLUGIN: &str = "liballow_clippy_lints.so"; + +#[test] +fn clippy_test() { + let src_base = Path::new("tests").join("ui"); + let plugin = Path::new("..") + .join("..") + .join("..") + .join("target") + .join("debug") + .join(PLUGIN); + + for entry in read_dir(src_base).unwrap() { + let path = entry.unwrap().path(); + + if !metadata(&path).unwrap().is_dir() { + continue; + } + + Command::new(&*CLIPPY) + .current_dir(&path) + .args(&["cargo-clippy", "--quiet", "--plugin", &plugin.to_string_lossy()]) + .assert() + .stdout(read_to_string(&*(path.to_string_lossy() + ".stdout")).unwrap()) + .stderr(read_to_string(&*(path.to_string_lossy() + ".stderr")).unwrap()); + } +} diff --git a/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints.stderr b/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints.stderr new file mode 100644 index 000000000000..468391c2309f --- /dev/null +++ b/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints.stderr @@ -0,0 +1,14 @@ +error: allowing Clippy lints denies your project of its true potential + --> src/main.rs:1:1 + | +1 | #[allow(clippy::assertions_on_constants)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `#[deny(clippy::restriction, clippy::style, clippy::pedantic, clippy::complexity, clippy::perf, clippy::cargo, clippy::nursery)]` + | + = note: `#[deny(clippy::allow_clippy_lints)]` on by default + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#allow_clippy_lints + +error: aborting due to previous error + +error: could not compile `allow_clippy_lints` + +To learn more, run the command again with --verbose. diff --git a/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints.stdout b/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints.stdout new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints/Cargo.toml b/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints/Cargo.toml new file mode 100644 index 000000000000..5d86d43ef0dc --- /dev/null +++ b/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "allow_clippy_lints" +version = "0.1.0" +authors = ["Samuel E. Moelius III "] +edition = "2018" + +[dependencies] diff --git a/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints/src/main.rs b/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints/src/main.rs new file mode 100644 index 000000000000..28bebd45256c --- /dev/null +++ b/plugin_examples/allow_clippy_lints/tests/ui/allow_clippy_lints/src/main.rs @@ -0,0 +1,4 @@ +#[allow(clippy::assertions_on_constants)] +fn main() { + assert!(true); +} diff --git a/plugin_examples/clippy_lints/Cargo.toml b/plugin_examples/clippy_lints/Cargo.toml new file mode 100644 index 000000000000..096cfd769d37 --- /dev/null +++ b/plugin_examples/clippy_lints/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "clippy_lints" +version = "0.1.0" +authors = ["The Rust Clippy Developers"] +description = "All of the Clippy lints as a Clippy plugin" +edition = "2018" + +[lib] +name = "clippy_lints" +crate-type = ["dylib"] + +[dependencies] +clippy_lints = { path = "../../clippy_lints" } +clippy_utils = { path = "../../clippy_utils" } diff --git a/plugin_examples/clippy_lints/src/lib.rs b/plugin_examples/clippy_lints/src/lib.rs new file mode 100644 index 000000000000..a764c9de7303 --- /dev/null +++ b/plugin_examples/clippy_lints/src/lib.rs @@ -0,0 +1,12 @@ +#![feature(rustc_private)] + +extern crate rustc_lint; +extern crate rustc_session; + +use clippy_utils::conf::Conf; +use rustc_session::Session; + +#[no_mangle] +pub extern "C" fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { + clippy_lints::register_plugins(store, sess, conf); +} diff --git a/src/driver.rs b/src/driver.rs index f5f6c09ed8e9..45fee62e95d7 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -11,12 +11,16 @@ extern crate rustc_driver; extern crate rustc_errors; extern crate rustc_interface; +extern crate rustc_lint; extern crate rustc_middle; +extern crate rustc_session; use rustc_interface::interface; use rustc_middle::ty::TyCtxt; +use rustc_session::{config::ErrorOutputType, early_error, Session}; use rustc_tools_util::VersionInfo; +use clippy_utils::conf::Conf; use std::borrow::Cow; use std::env; use std::lazy::SyncLazy; @@ -64,10 +68,62 @@ fn test_arg_value() { struct DefaultCallbacks; impl rustc_driver::Callbacks for DefaultCallbacks {} -struct ClippyCallbacks; +type RegisterPluginsFunc = unsafe fn(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf); + +/// A Clippy plugin that has been loaded but not necessarily registered. +struct LoadedPlugin { + path: PathBuf, + lib: libloading::Library, +} + +impl LoadedPlugin { + fn register_plugins(&self, store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { + if let Ok(func) = unsafe { self.lib.get::(b"register_plugins") } { + unsafe { + func(store, sess, conf); + } + } else { + sess.err(&format!( + "could not find `register_plugins` in `{}`", + self.path.to_string_lossy() + )); + } + } +} + +struct ClippyCallbacks { + no_builtins: bool, + loaded_plugins: Vec, +} + +impl ClippyCallbacks { + // smoelius: Load the libraries when ClippyCallbacks is created and not later (e.g., in `config`) + // to ensure that the libraries live long enough. + fn new(no_builtins: bool, clippy_plugins: Vec) -> ClippyCallbacks { + let mut loaded_plugins = Vec::new(); + for path in clippy_plugins { + unsafe { + let lib = libloading::Library::new(&path).unwrap_or_else(|_| { + early_error( + ErrorOutputType::default(), + &format!("could not load plugin `{}`", path.to_string_lossy()), + ); + }); + loaded_plugins.push(LoadedPlugin { path, lib }); + } + } + ClippyCallbacks { + no_builtins, + loaded_plugins, + } + } +} + impl rustc_driver::Callbacks for ClippyCallbacks { fn config(&mut self, config: &mut interface::Config) { let previous = config.register_lints.take(); + let no_builtins = self.no_builtins; + let loaded_plugins = self.loaded_plugins.split_off(0); config.register_lints = Some(Box::new(move |sess, mut lint_store| { // technically we're ~guaranteed that this is none but might as well call anything that // is there already. Certainly it can't hurt. @@ -76,9 +132,12 @@ impl rustc_driver::Callbacks for ClippyCallbacks { } let conf = clippy_lints::read_conf(&[], &sess); - clippy_lints::register_plugins(&mut lint_store, &sess, &conf); - clippy_lints::register_pre_expansion_lints(&mut lint_store); - clippy_lints::register_renamed(&mut lint_store); + if !no_builtins { + clippy_lints::register_plugins(&mut lint_store, &sess, &conf); + } + for loaded_plugin in &loaded_plugins { + loaded_plugin.register_plugins(&mut lint_store, &sess, &conf); + } })); // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be @@ -278,20 +337,35 @@ pub fn main() { args.extend(vec!["--sysroot".into(), sys_root]); }; + let mut no_builtins = false; let mut no_deps = false; - let clippy_args = env::var("CLIPPY_ARGS") - .unwrap_or_default() - .split("__CLIPPY_HACKERY__") - .filter_map(|s| match s { - "" => None, - "--no-deps" => { - no_deps = true; - None - }, - _ => Some(s.to_string()), - }) - .chain(vec!["--cfg".into(), r#"feature="cargo-clippy""#.into()]) - .collect::>(); + let mut clippy_args = Vec::new(); + let mut clippy_plugins = Vec::new(); + if let Ok(args) = env::var("CLIPPY_ARGS") { + let mut iter = args.split("__CLIPPY_HACKERY__"); + while let Some(s) = iter.next() { + match s { + "" => {}, + "--no-builtins" => { + no_builtins = true; + }, + "--no-deps" => { + no_deps = true; + }, + "--plugin" => { + // smoelius: The paths should already have bee canonicalized in `ClippyCmd::new`. + let path = iter.next().expect("missing argument to `--plugin`"); + clippy_plugins.push(PathBuf::from(path)); + }, + _ => { + clippy_args.push(s.to_string()); + }, + } + } + } + + clippy_args.push("--cfg".into()); + clippy_args.push(r#"feature="cargo-clippy""#.into()); // We enable Clippy if one of the following conditions is met // - IF Clippy is run on its test suite OR @@ -307,7 +381,7 @@ pub fn main() { args.extend(clippy_args); } - let mut clippy = ClippyCallbacks; + let mut clippy = ClippyCallbacks::new(no_builtins, clippy_plugins); let mut default = DefaultCallbacks; let callbacks: &mut (dyn rustc_driver::Callbacks + Send) = if clippy_enabled { &mut clippy } else { &mut default }; diff --git a/src/main.rs b/src/main.rs index ea06743394d1..e221148c383d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use rustc_tools_util::VersionInfo; use std::env; use std::ffi::OsString; +use std::fs::canonicalize; use std::path::PathBuf; use std::process::{self, Command}; @@ -15,6 +16,8 @@ Usage: Common options: -h, --help Print this message + --no-builtins Do not load builtin lints + --plugin PLUGIN Load lints in PLUGIN -V, --version Print version info and exit Other options are the same as `cargo check`. @@ -73,13 +76,26 @@ impl ClippyCmd { let mut cargo_subcommand = "check"; let mut unstable_options = false; let mut args = vec![]; + let mut clippy_args = vec![]; - for arg in old_args.by_ref() { + while let Some(arg) = old_args.next() { match arg.as_str() { "--fix" => { cargo_subcommand = "fix"; continue; }, + "--no-builtins" => { + clippy_args.push(arg); + continue; + }, + "--plugin" => { + let plugin = old_args.next().expect("missing argument to `--plugin`"); + // smoelius: canonicalize in case clippy-driver is run from a different directory. + let path = canonicalize(&plugin).unwrap_or_else(|_| panic!("could not find `{}`", plugin)); + clippy_args.push(arg); + clippy_args.push(path.to_string_lossy().to_string()); + continue; + }, "--" => break, // Cover -Zunstable-options and -Z unstable-options s if s.ends_with("unstable-options") => unstable_options = true, @@ -99,7 +115,7 @@ impl ClippyCmd { args.insert(0, "+nightly".to_string()); } - let mut clippy_args: Vec = old_args.collect(); + clippy_args.extend(old_args); if cargo_subcommand == "fix" && !clippy_args.iter().any(|arg| arg == "--no-deps") { clippy_args.push("--no-deps".into()); } diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 94f5e616cace..4604b6928105 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -9,6 +9,7 @@ use std::ffi::OsStr; use std::fs; use std::io; use std::path::{Path, PathBuf}; +use std::process::Command; mod cargo; @@ -101,6 +102,51 @@ fn run_mode(cfg: &mut compiletest::Config) { compiletest::run_tests(&cfg); } +fn build_clippy_lints_plugin() { + assert!(Command::new("cargo") + .args(&[ + "build", + "--manifest-path", + &Path::new("plugin_examples") + .join("clippy_lints") + .join("Cargo.toml") + .to_string_lossy(), + ]) + .status() + .unwrap() + .success()); +} + +fn encode_clippy_args(clippy_args: &[&str]) -> String { + clippy_args + .iter() + .map(|arg| format!("{}__CLIPPY_HACKERY__", arg)) + .collect() +} + +/// Like the `run_mode` ui tests but with all of the Clippy lints loaded from a plugin. +fn run_ui_plugin(cfg: &mut compiletest::Config) { + build_clippy_lints_plugin(); + cfg.mode = TestMode::Ui; + cfg.src_base = Path::new("tests").join("ui"); + set_var( + "CLIPPY_ARGS", + &encode_clippy_args(&[ + "--no-builtins", + "--plugin", + &Path::new("plugin_examples") + .join("clippy_lints") + .join("target") + .join("debug") + .join("libclippy_lints.so") + .canonicalize() + .unwrap() + .to_string_lossy(), + ]), + ); + compiletest::run_tests(&cfg); +} + fn run_internal_tests(cfg: &mut compiletest::Config) { // only run internal tests with the internal-tests feature if !RUN_INTERNAL_TESTS { @@ -265,6 +311,7 @@ fn compile_test() { prepare_env(); let mut config = default_config(); run_mode(&mut config); + run_ui_plugin(&mut config); run_ui_toml(&mut config); run_ui_cargo(&mut config); run_internal_tests(&mut config); diff --git a/tests/versioncheck.rs b/tests/versioncheck.rs index 76b6126c76c6..4b7146328b4f 100644 --- a/tests/versioncheck.rs +++ b/tests/versioncheck.rs @@ -2,21 +2,24 @@ use rustc_tools_util::VersionInfo; #[test] -fn check_that_clippy_lints_has_the_same_version_as_clippy() { +fn check_that_clippy_lints_and_clippy_utils_have_the_same_version_as_clippy() { let clippy_meta = cargo_metadata::MetadataCommand::new() .no_deps() .exec() .expect("could not obtain cargo metadata"); - std::env::set_current_dir(std::env::current_dir().unwrap().join("clippy_lints")).unwrap(); - let clippy_lints_meta = cargo_metadata::MetadataCommand::new() - .no_deps() - .exec() - .expect("could not obtain cargo metadata"); - assert_eq!(clippy_lints_meta.packages[0].version, clippy_meta.packages[0].version); - for package in &clippy_meta.packages[0].dependencies { - if package.name == "clippy_lints" { - assert!(package.req.matches(&clippy_lints_meta.packages[0].version)); - return; + + for krate in &["clippy_lints", "clippy_utils"] { + let krate_meta = cargo_metadata::MetadataCommand::new() + .current_dir(std::env::current_dir().unwrap().join(krate)) + .no_deps() + .exec() + .expect("could not obtain cargo metadata"); + assert_eq!(krate_meta.packages[0].version, clippy_meta.packages[0].version); + for package in &clippy_meta.packages[0].dependencies { + if package.name == *krate { + assert!(package.req.matches(&krate_meta.packages[0].version)); + break; + } } } }