diff --git a/CHANGELOG.md b/CHANGELOG.md index 30d3108e0be4..f21174c0bcef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1059,6 +1059,7 @@ Released 2018-09-13 [`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits [`large_digit_groups`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_digit_groups [`large_enum_variant`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant +[`large_stack_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays [`len_without_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty [`len_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#len_zero [`let_and_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return @@ -1176,6 +1177,7 @@ Released 2018-09-13 [`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used [`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop +[`same_functions_in_if_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#same_functions_in_if_condition [`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some [`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse [`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse @@ -1201,6 +1203,7 @@ Released 2018-09-13 [`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map [`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl [`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting +[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments [`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment [`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr [`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some @@ -1273,4 +1276,5 @@ Released 2018-09-13 [`zero_prefixed_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_prefixed_literal [`zero_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_ptr [`zero_width_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#zero_width_space +[`zst_offset`]: https://rust-lang.github.io/rust-clippy/master/index.html#zst_offset diff --git a/README.md b/README.md index 922dbcd11380..d467e05257b7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are 333 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are 337 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you: diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index f514068fb10e..9cbff066f464 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -40,6 +40,53 @@ declare_clippy_lint! { "consecutive `ifs` with the same condition" } +declare_clippy_lint! { + /// **What it does:** Checks for consecutive `if`s with the same function call. + /// + /// **Why is this bad?** This is probably a copy & paste error. + /// Despite the fact that function can have side effects and `if` works as + /// intended, such an approach is implicit and can be considered a "code smell". + /// + /// **Known problems:** Hopefully none. + /// + /// **Example:** + /// ```ignore + /// if foo() == bar { + /// … + /// } else if foo() == bar { + /// … + /// } + /// ``` + /// + /// This probably should be: + /// ```ignore + /// if foo() == bar { + /// … + /// } else if foo() == baz { + /// … + /// } + /// ``` + /// + /// or if the original code was not a typo and called function mutates a state, + /// consider move the mutation out of the `if` condition to avoid similarity to + /// a copy & paste error: + /// + /// ```ignore + /// let first = foo(); + /// if first == bar { + /// … + /// } else { + /// let second = foo(); + /// if second == bar { + /// … + /// } + /// } + /// ``` + pub SAME_FUNCTIONS_IN_IF_CONDITION, + pedantic, + "consecutive `ifs` with the same function call" +} + declare_clippy_lint! { /// **What it does:** Checks for `if/else` with the same body as the *then* part /// and the *else* part. @@ -102,7 +149,7 @@ declare_clippy_lint! { "`match` with identical arm bodies" } -declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]); +declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]); impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste { fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) { @@ -119,6 +166,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CopyAndPaste { let (conds, blocks) = if_sequence(expr); lint_same_then_else(cx, &blocks); lint_same_cond(cx, &conds); + lint_same_fns_in_if_cond(cx, &conds); lint_match_arms(cx, expr); } } @@ -163,6 +211,34 @@ fn lint_same_cond(cx: &LateContext<'_, '_>, conds: &[&Expr]) { } } +/// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. +fn lint_same_fns_in_if_cond(cx: &LateContext<'_, '_>, conds: &[&Expr]) { + let hash: &dyn Fn(&&Expr) -> u64 = &|expr| -> u64 { + let mut h = SpanlessHash::new(cx, cx.tables); + h.hash_expr(expr); + h.finish() + }; + + let eq: &dyn Fn(&&Expr, &&Expr) -> bool = &|&lhs, &rhs| -> bool { + // Do not spawn warning if `IFS_SAME_COND` already produced it. + if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) { + return false; + } + SpanlessEq::new(cx).eq_expr(lhs, rhs) + }; + + for (i, j) in search_same(conds, hash, eq) { + span_note_and_lint( + cx, + SAME_FUNCTIONS_IN_IF_CONDITION, + j.span, + "this `if` has the same function call as a previous if", + i.span, + "same as this", + ); + } +} + /// Implementation of `MATCH_SAME_ARMS`. fn lint_match_arms<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &Expr) { fn same_bindings<'tcx>( diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs new file mode 100644 index 000000000000..975a96c9a825 --- /dev/null +++ b/clippy_lints/src/large_stack_arrays.rs @@ -0,0 +1,68 @@ +use rustc::hir::*; +use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass}; +use rustc::mir::interpret::ConstValue; +use rustc::ty::{self, ConstKind}; +use rustc::{declare_tool_lint, impl_lint_pass}; + +use if_chain::if_chain; + +use crate::rustc_target::abi::LayoutOf; +use crate::utils::{snippet, span_help_and_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for local arrays that may be too large. + /// + /// **Why is this bad?** Large local arrays may cause stack overflow. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// let a = [0u32; 1_000_000]; + /// ``` + pub LARGE_STACK_ARRAYS, + pedantic, + "allocating large arrays on stack may cause stack overflow" +} + +pub struct LargeStackArrays { + maximum_allowed_size: u64, +} + +impl LargeStackArrays { + #[must_use] + pub fn new(maximum_allowed_size: u64) -> Self { + Self { maximum_allowed_size } + } +} + +impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LargeStackArrays { + fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr) { + if_chain! { + if let ExprKind::Repeat(_, _) = expr.kind; + if let ty::Array(element_type, cst) = cx.tables.expr_ty(expr).kind; + if let ConstKind::Value(val) = cst.val; + if let ConstValue::Scalar(element_count) = val; + if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); + if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if self.maximum_allowed_size < element_count * element_size; + then { + span_help_and_lint( + cx, + LARGE_STACK_ARRAYS, + expr.span, + &format!( + "allocating a local array larger than {} bytes", + self.maximum_allowed_size + ), + &format!( + "consider allocating on the heap with vec!{}.into_boxed_slice()", + snippet(cx, expr.span, "[...]") + ), + ); + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 52aca5000c1c..c8954aef2ba8 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -212,6 +212,7 @@ pub mod int_plus_one; pub mod integer_division; pub mod items_after_statements; pub mod large_enum_variant; +pub mod large_stack_arrays; pub mod len_zero; pub mod let_if_seq; pub mod lifetimes; @@ -274,6 +275,7 @@ pub mod slow_vector_initialization; pub mod strings; pub mod suspicious_trait_impl; pub mod swap; +pub mod tabs_in_doc_comments; pub mod temporary_assignment; pub mod to_digit_is_some; pub mod trait_bounds; @@ -472,6 +474,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf &copies::IFS_SAME_COND, &copies::IF_SAME_THEN_ELSE, &copies::MATCH_SAME_ARMS, + &copies::SAME_FUNCTIONS_IN_IF_CONDITION, ©_iterator::COPY_ITERATOR, &dbg_macro::DBG_MACRO, &default_trait_access::DEFAULT_TRAIT_ACCESS, @@ -538,6 +541,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf &integer_division::INTEGER_DIVISION, &items_after_statements::ITEMS_AFTER_STATEMENTS, &large_enum_variant::LARGE_ENUM_VARIANT, + &large_stack_arrays::LARGE_STACK_ARRAYS, &len_zero::LEN_WITHOUT_IS_EMPTY, &len_zero::LEN_ZERO, &let_if_seq::USELESS_LET_IF_SEQ, @@ -624,6 +628,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf &methods::USELESS_ASREF, &methods::WRONG_PUB_SELF_CONVENTION, &methods::WRONG_SELF_CONVENTION, + &methods::ZST_OFFSET, &minmax::MIN_MAX, &misc::CMP_NAN, &misc::CMP_OWNED, @@ -716,6 +721,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf &suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL, &swap::ALMOST_SWAPPED, &swap::MANUAL_SWAP, + &tabs_in_doc_comments::TABS_IN_DOC_COMMENTS, &temporary_assignment::TEMPORARY_ASSIGNMENT, &to_digit_is_some::TO_DIGIT_IS_SOME, &trait_bounds::TYPE_REPETITION_IN_BOUNDS, @@ -946,10 +952,13 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf store.register_early_pass(|| box utils::internal_lints::ClippyLintsInternal); let enum_variant_name_threshold = conf.enum_variant_name_threshold; store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold)); + store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments); store.register_late_pass(|| box unused_self::UnusedSelf); store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall); store.register_late_pass(|| box exit::Exit); store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome); + let array_size_threshold = conf.array_size_threshold; + store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold)); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -989,6 +998,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf LintId::of(&attrs::INLINE_ALWAYS), LintId::of(&checked_conversions::CHECKED_CONVERSIONS), LintId::of(&copies::MATCH_SAME_ARMS), + LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION), LintId::of(©_iterator::COPY_ITERATOR), LintId::of(&default_trait_access::DEFAULT_TRAIT_ACCESS), LintId::of(&derive::EXPL_IMPL_CLONE_ON_COPY), @@ -1003,6 +1013,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf LintId::of(&if_not_else::IF_NOT_ELSE), LintId::of(&infinite_iter::MAYBE_INFINITE_ITER), LintId::of(&items_after_statements::ITEMS_AFTER_STATEMENTS), + LintId::of(&large_stack_arrays::LARGE_STACK_ARRAYS), LintId::of(&literal_representation::LARGE_DIGIT_GROUPS), LintId::of(&loops::EXPLICIT_INTO_ITER_LOOP), LintId::of(&loops::EXPLICIT_ITER_LOOP), @@ -1176,6 +1187,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf LintId::of(&methods::UNNECESSARY_FOLD), LintId::of(&methods::USELESS_ASREF), LintId::of(&methods::WRONG_SELF_CONVENTION), + LintId::of(&methods::ZST_OFFSET), LintId::of(&minmax::MIN_MAX), LintId::of(&misc::CMP_NAN), LintId::of(&misc::CMP_OWNED), @@ -1243,6 +1255,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), LintId::of(&swap::ALMOST_SWAPPED), LintId::of(&swap::MANUAL_SWAP), + LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME), LintId::of(&transmute::CROSSPOINTER_TRANSMUTE), @@ -1370,6 +1383,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&returns::UNUSED_UNIT), LintId::of(&strings::STRING_LIT_AS_BYTES), + LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME), LintId::of(&try_err::TRY_ERR), LintId::of(&types::FN_TO_NUMERIC_CAST), @@ -1497,6 +1511,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf LintId::of(&methods::CLONE_DOUBLE_REF), LintId::of(&methods::TEMPORARY_CSTRING_AS_PTR), LintId::of(&methods::UNINIT_ASSUMED_INIT), + LintId::of(&methods::ZST_OFFSET), LintId::of(&minmax::MIN_MAX), LintId::of(&misc::CMP_NAN), LintId::of(&misc::FLOAT_CMP), diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 9fcffc770050..f20abeff0658 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1065,6 +1065,23 @@ declare_clippy_lint! { "`.chcked_add/sub(x).unwrap_or(MAX/MIN)`" } +declare_clippy_lint! { + /// **What it does:** Checks for `offset(_)`, `wrapping_`{`add`, `sub`}, etc. on raw pointers to + /// zero-sized types + /// + /// **Why is this bad?** This is a no-op, and likely unintended + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```ignore + /// unsafe { (&() as *const ()).offest(1) }; + /// ``` + pub ZST_OFFSET, + correctness, + "Check for offset calculations on raw pointers to zero-sized types" +} + declare_lint_pass!(Methods => [ OPTION_UNWRAP_USED, RESULT_UNWRAP_USED, @@ -1109,6 +1126,7 @@ declare_lint_pass!(Methods => [ SUSPICIOUS_MAP, UNINIT_ASSUMED_INIT, MANUAL_SATURATING_ARITHMETIC, + ZST_OFFSET, ]); impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { @@ -1167,6 +1185,9 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods { | ["unwrap_or", arith @ "checked_mul"] => { manual_saturating_arithmetic::lint(cx, expr, &arg_lists, &arith["checked_".len()..]) }, + ["add"] | ["offset"] | ["sub"] | ["wrapping_offset"] | ["wrapping_add"] | ["wrapping_sub"] => { + check_pointer_offset(cx, expr, arg_lists[0]) + }, _ => {}, } @@ -3063,3 +3084,15 @@ fn contains_return(expr: &hir::Expr) -> bool { visitor.visit_expr(expr); visitor.found } + +fn check_pointer_offset(cx: &LateContext<'_, '_>, expr: &hir::Expr, args: &[hir::Expr]) { + if_chain! { + if args.len() == 2; + if let ty::RawPtr(ty::TypeAndMut { ref ty, .. }) = cx.tables.expr_ty(&args[0]).kind; + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); + if layout.is_zst(); + then { + span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); + } + } +} diff --git a/clippy_lints/src/tabs_in_doc_comments.rs b/clippy_lints/src/tabs_in_doc_comments.rs new file mode 100644 index 000000000000..ccea700f3538 --- /dev/null +++ b/clippy_lints/src/tabs_in_doc_comments.rs @@ -0,0 +1,219 @@ +use crate::utils::span_lint_and_sugg; +use rustc::lint::{EarlyContext, EarlyLintPass, LintArray, LintPass}; +use rustc::{declare_lint_pass, declare_tool_lint}; +use rustc_errors::Applicability; +use std::convert::TryFrom; +use syntax::ast; +use syntax::source_map::{BytePos, Span}; + +declare_clippy_lint! { + /// **What it does:** Checks doc comments for usage of tab characters. + /// + /// **Why is this bad?** The rust style-guide promotes spaces instead of tabs for indentation. + /// To keep a consistent view on the source, also doc comments should not have tabs. + /// Also, explaining ascii-diagrams containing tabs can get displayed incorrectly when the + /// display settings of the author and reader differ. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// /// + /// /// Struct to hold two strings: + /// /// - first one + /// /// - second one + /// pub struct DoubleString { + /// /// + /// /// - First String: + /// /// - needs to be inside here + /// first_string: String, + /// /// + /// /// - Second String: + /// /// - needs to be inside here + /// second_string: String, + ///} + /// ``` + /// + /// Will be converted to: + /// ```rust + /// /// + /// /// Struct to hold two strings: + /// /// - first one + /// /// - second one + /// pub struct DoubleString { + /// /// + /// /// - First String: + /// /// - needs to be inside here + /// first_string: String, + /// /// + /// /// - Second String: + /// /// - needs to be inside here + /// second_string: String, + ///} + /// ``` + pub TABS_IN_DOC_COMMENTS, + style, + "using tabs in doc comments is not recommended" +} + +declare_lint_pass!(TabsInDocComments => [TABS_IN_DOC_COMMENTS]); + +impl TabsInDocComments { + fn warn_if_tabs_in_doc(cx: &EarlyContext<'_>, attr: &ast::Attribute) { + if let ast::AttrKind::DocComment(comment) = attr.kind { + let comment = comment.as_str(); + + for (lo, hi) in get_chunks_of_tabs(&comment) { + let new_span = Span::new( + attr.span.lo() + BytePos(lo), + attr.span.lo() + BytePos(hi), + attr.span.ctxt(), + ); + span_lint_and_sugg( + cx, + TABS_IN_DOC_COMMENTS, + new_span, + "using tabs in doc comments is not recommended", + "consider using four spaces per tab", + " ".repeat((hi - lo) as usize), + Applicability::MaybeIncorrect, + ); + } + } + } +} + +impl EarlyLintPass for TabsInDocComments { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attribute: &ast::Attribute) { + Self::warn_if_tabs_in_doc(cx, &attribute); + } +} + +/// +/// scans the string for groups of tabs and returns the start(inclusive) and end positions +/// (exclusive) of all groups +/// e.g. "sd\tasd\t\taa" will be converted to [(2, 3), (6, 8)] as +/// 012 3456 7 89 +/// ^-^ ^---^ +fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> { + let line_length_way_to_long = "doc comment longer than 2^32 chars"; + let mut spans: Vec<(u32, u32)> = vec![]; + let mut current_start: u32 = 0; + + // tracker to decide if the last group of tabs is not closed by a non-tab character + let mut is_active = false; + + let chars_array: Vec<_> = the_str.chars().collect(); + + if chars_array == vec!['\t'] { + return vec![(0, 1)]; + } + + for (index, arr) in chars_array.windows(2).enumerate() { + let index = u32::try_from(index).expect(line_length_way_to_long); + match arr { + ['\t', '\t'] => { + // either string starts with double tab, then we have to set it active, + // otherwise is_active is true anyway + is_active = true; + }, + [_, '\t'] => { + // as ['\t', '\t'] is excluded, this has to be a start of a tab group, + // set indices accordingly + is_active = true; + current_start = index + 1; + }, + ['\t', _] => { + // this now has to be an end of the group, hence we have to push a new tuple + is_active = false; + spans.push((current_start, index + 1)); + }, + _ => {}, + } + } + + // only possible when tabs are at the end, insert last group + if is_active { + spans.push(( + current_start, + u32::try_from(the_str.chars().count()).expect(line_length_way_to_long), + )); + } + + spans +} + +#[cfg(test)] +mod tests_for_get_chunks_of_tabs { + use super::get_chunks_of_tabs; + + #[test] + fn test_empty_string() { + let res = get_chunks_of_tabs(""); + + assert_eq!(res, vec![]); + } + + #[test] + fn test_simple() { + let res = get_chunks_of_tabs("sd\t\t\taa"); + + assert_eq!(res, vec![(2, 5)]); + } + + #[test] + fn test_only_t() { + let res = get_chunks_of_tabs("\t\t"); + + assert_eq!(res, vec![(0, 2)]); + } + + #[test] + fn test_only_one_t() { + let res = get_chunks_of_tabs("\t"); + + assert_eq!(res, vec![(0, 1)]); + } + + #[test] + fn test_double() { + let res = get_chunks_of_tabs("sd\tasd\t\taa"); + + assert_eq!(res, vec![(2, 3), (6, 8)]); + } + + #[test] + fn test_start() { + let res = get_chunks_of_tabs("\t\taa"); + + assert_eq!(res, vec![(0, 2)]); + } + + #[test] + fn test_end() { + let res = get_chunks_of_tabs("aa\t\t"); + + assert_eq!(res, vec![(2, 4)]); + } + + #[test] + fn test_start_single() { + let res = get_chunks_of_tabs("\taa"); + + assert_eq!(res, vec![(0, 1)]); + } + + #[test] + fn test_end_single() { + let res = get_chunks_of_tabs("aa\t"); + + assert_eq!(res, vec![(2, 3)]); + } + + #[test] + fn test_no_tabs() { + let res = get_chunks_of_tabs("dsfs"); + + assert_eq!(res, vec![]); + } +} diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 734b689ab1a6..a3407d1e9902 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -151,6 +151,8 @@ define_Conf! { (trivial_copy_size_limit, "trivial_copy_size_limit", None => Option), /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have (too_many_lines_threshold, "too_many_lines_threshold", 100 => u64), + /// Lint: LARGE_STACK_ARRAYS. The maximum allowed size for arrays on the stack + (array_size_threshold, "array_size_threshold", 512_000 => u64), } impl Default for Conf { diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 0b301b6be963..9b258ffb610a 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -6,7 +6,7 @@ pub use lint::Lint; pub use lint::LINT_LEVELS; // begin lint list, do not remove this comment, it’s used in `update_lints` -pub const ALL_LINTS: [Lint; 333] = [ +pub const ALL_LINTS: [Lint; 337] = [ Lint { name: "absurd_extreme_comparisons", group: "correctness", @@ -903,6 +903,13 @@ pub const ALL_LINTS: [Lint; 333] = [ deprecation: None, module: "large_enum_variant", }, + Lint { + name: "large_stack_arrays", + group: "pedantic", + desc: "allocating large arrays on stack may cause stack overflow", + deprecation: None, + module: "large_stack_arrays", + }, Lint { name: "len_without_is_empty", group: "style", @@ -1708,6 +1715,13 @@ pub const ALL_LINTS: [Lint; 333] = [ deprecation: None, module: "loops", }, + Lint { + name: "same_functions_in_if_condition", + group: "pedantic", + desc: "consecutive `ifs` with the same function call", + deprecation: None, + module: "copies", + }, Lint { name: "search_is_some", group: "complexity", @@ -1862,6 +1876,13 @@ pub const ALL_LINTS: [Lint; 333] = [ deprecation: None, module: "formatting", }, + Lint { + name: "tabs_in_doc_comments", + group: "style", + desc: "using tabs in doc comments is not recommended", + deprecation: None, + module: "tabs_in_doc_comments", + }, Lint { name: "temporary_assignment", group: "complexity", @@ -2338,5 +2359,12 @@ pub const ALL_LINTS: [Lint; 333] = [ deprecation: None, module: "unicode", }, + Lint { + name: "zst_offset", + group: "correctness", + desc: "Check for offset calculations on raw pointers to zero-sized types", + deprecation: None, + module: "methods", + }, ]; // end lint list, do not remove this comment, it’s used in `update_lints` diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index c9446cda77cf..cbb4126a8668 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -1,4 +1,4 @@ -error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `too-many-lines-threshold`, `third-party` at line 5 column 1 +error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `third-party` at line 5 column 1 error: aborting due to previous error diff --git a/tests/ui/large_stack_arrays.rs b/tests/ui/large_stack_arrays.rs new file mode 100644 index 000000000000..d9161bfcf154 --- /dev/null +++ b/tests/ui/large_stack_arrays.rs @@ -0,0 +1,30 @@ +#![warn(clippy::large_stack_arrays)] +#![allow(clippy::large_enum_variant)] + +#[derive(Clone, Copy)] +struct S { + pub data: [u64; 32], +} + +#[derive(Clone, Copy)] +enum E { + S(S), + T(u32), +} + +fn main() { + let bad = ( + [0u32; 20_000_000], + [S { data: [0; 32] }; 5000], + [Some(""); 20_000_000], + [E::T(0); 5000], + ); + + let good = ( + [0u32; 1000], + [S { data: [0; 32] }; 1000], + [Some(""); 1000], + [E::T(0); 1000], + [(); 20_000_000], + ); +} diff --git a/tests/ui/large_stack_arrays.stderr b/tests/ui/large_stack_arrays.stderr new file mode 100644 index 000000000000..98d8262372df --- /dev/null +++ b/tests/ui/large_stack_arrays.stderr @@ -0,0 +1,35 @@ +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:17:9 + | +LL | [0u32; 20_000_000], + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::large-stack-arrays` implied by `-D warnings` + = help: consider allocating on the heap with vec![0u32; 20_000_000].into_boxed_slice() + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:18:9 + | +LL | [S { data: [0; 32] }; 5000], + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with vec![S { data: [0; 32] }; 5000].into_boxed_slice() + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:19:9 + | +LL | [Some(""); 20_000_000], + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with vec![Some(""); 20_000_000].into_boxed_slice() + +error: allocating a local array larger than 512000 bytes + --> $DIR/large_stack_arrays.rs:20:9 + | +LL | [E::T(0); 5000], + | ^^^^^^^^^^^^^^^ + | + = help: consider allocating on the heap with vec![E::T(0); 5000].into_boxed_slice() + +error: aborting due to 4 previous errors + diff --git a/tests/ui/same_functions_in_if_condition.rs b/tests/ui/same_functions_in_if_condition.rs new file mode 100644 index 000000000000..686867cf5c6f --- /dev/null +++ b/tests/ui/same_functions_in_if_condition.rs @@ -0,0 +1,80 @@ +#![warn(clippy::same_functions_in_if_condition)] +#![allow(clippy::ifs_same_cond)] // This warning is different from `ifs_same_cond`. +#![allow(clippy::if_same_then_else, clippy::comparison_chain)] // all empty blocks + +fn function() -> bool { + true +} + +fn fn_arg(_arg: u8) -> bool { + true +} + +struct Struct; + +impl Struct { + fn method(&self) -> bool { + true + } + fn method_arg(&self, _arg: u8) -> bool { + true + } +} + +fn ifs_same_cond_fn() { + let a = 0; + let obj = Struct; + + if function() { + } else if function() { + //~ ERROR ifs same condition + } + + if fn_arg(a) { + } else if fn_arg(a) { + //~ ERROR ifs same condition + } + + if obj.method() { + } else if obj.method() { + //~ ERROR ifs same condition + } + + if obj.method_arg(a) { + } else if obj.method_arg(a) { + //~ ERROR ifs same condition + } + + let mut v = vec![1]; + if v.pop() == None { + //~ ERROR ifs same condition + } else if v.pop() == None { + } + + if v.len() == 42 { + //~ ERROR ifs same condition + } else if v.len() == 42 { + } + + if v.len() == 1 { + // ok, different conditions + } else if v.len() == 2 { + } + + if fn_arg(0) { + // ok, different arguments. + } else if fn_arg(1) { + } + + if obj.method_arg(0) { + // ok, different arguments. + } else if obj.method_arg(1) { + } + + if a == 1 { + // ok, warning is on `ifs_same_cond` behalf. + } else if a == 1 { + } +} + +fn main() {} diff --git a/tests/ui/same_functions_in_if_condition.stderr b/tests/ui/same_functions_in_if_condition.stderr new file mode 100644 index 000000000000..214f1a9e7c8f --- /dev/null +++ b/tests/ui/same_functions_in_if_condition.stderr @@ -0,0 +1,75 @@ +error: this `if` has the same function call as a previous if + --> $DIR/same_functions_in_if_condition.rs:29:15 + | +LL | } else if function() { + | ^^^^^^^^^^ + | + = note: `-D clippy::same-functions-in-if-condition` implied by `-D warnings` +note: same as this + --> $DIR/same_functions_in_if_condition.rs:28:8 + | +LL | if function() { + | ^^^^^^^^^^ + +error: this `if` has the same function call as a previous if + --> $DIR/same_functions_in_if_condition.rs:34:15 + | +LL | } else if fn_arg(a) { + | ^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:33:8 + | +LL | if fn_arg(a) { + | ^^^^^^^^^ + +error: this `if` has the same function call as a previous if + --> $DIR/same_functions_in_if_condition.rs:39:15 + | +LL | } else if obj.method() { + | ^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:38:8 + | +LL | if obj.method() { + | ^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous if + --> $DIR/same_functions_in_if_condition.rs:44:15 + | +LL | } else if obj.method_arg(a) { + | ^^^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:43:8 + | +LL | if obj.method_arg(a) { + | ^^^^^^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous if + --> $DIR/same_functions_in_if_condition.rs:51:15 + | +LL | } else if v.pop() == None { + | ^^^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:49:8 + | +LL | if v.pop() == None { + | ^^^^^^^^^^^^^^^ + +error: this `if` has the same function call as a previous if + --> $DIR/same_functions_in_if_condition.rs:56:15 + | +LL | } else if v.len() == 42 { + | ^^^^^^^^^^^^^ + | +note: same as this + --> $DIR/same_functions_in_if_condition.rs:54:8 + | +LL | if v.len() == 42 { + | ^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/tests/ui/tabs_in_doc_comments.fixed b/tests/ui/tabs_in_doc_comments.fixed new file mode 100644 index 000000000000..4bc4bc86c76c --- /dev/null +++ b/tests/ui/tabs_in_doc_comments.fixed @@ -0,0 +1,22 @@ +// run-rustfix + +#![warn(clippy::tabs_in_doc_comments)] +#[allow(dead_code)] + +/// +/// Struct to hold two strings: +/// - first one +/// - second one +pub struct DoubleString { + /// + /// - First String: + /// - needs to be inside here + first_string: String, + /// + /// - Second String: + /// - needs to be inside here + second_string: String, +} + +/// This is main +fn main() {} diff --git a/tests/ui/tabs_in_doc_comments.rs b/tests/ui/tabs_in_doc_comments.rs new file mode 100644 index 000000000000..9db3416e6596 --- /dev/null +++ b/tests/ui/tabs_in_doc_comments.rs @@ -0,0 +1,22 @@ +// run-rustfix + +#![warn(clippy::tabs_in_doc_comments)] +#[allow(dead_code)] + +/// +/// Struct to hold two strings: +/// - first one +/// - second one +pub struct DoubleString { + /// + /// - First String: + /// - needs to be inside here + first_string: String, + /// + /// - Second String: + /// - needs to be inside here + second_string: String, +} + +/// This is main +fn main() {} diff --git a/tests/ui/tabs_in_doc_comments.stderr b/tests/ui/tabs_in_doc_comments.stderr new file mode 100644 index 000000000000..355f2e805796 --- /dev/null +++ b/tests/ui/tabs_in_doc_comments.stderr @@ -0,0 +1,52 @@ +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:12:9 + | +LL | /// - First String: + | ^^^^ help: consider using four spaces per tab + | + = note: `-D clippy::tabs-in-doc-comments` implied by `-D warnings` + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:13:9 + | +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:16:9 + | +LL | /// - Second String: + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:17:9 + | +LL | /// - needs to be inside here + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:8:5 + | +LL | /// - first one + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:8:13 + | +LL | /// - first one + | ^^^^^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:9:5 + | +LL | /// - second one + | ^^^^ help: consider using four spaces per tab + +error: using tabs in doc comments is not recommended + --> $DIR/tabs_in_doc_comments.rs:9:14 + | +LL | /// - second one + | ^^^^ help: consider using four spaces per tab + +error: aborting due to 8 previous errors + diff --git a/tests/ui/zero_offset.rs b/tests/ui/zero_offset.rs new file mode 100644 index 000000000000..2de904376ad4 --- /dev/null +++ b/tests/ui/zero_offset.rs @@ -0,0 +1,12 @@ +fn main() { + unsafe { + let x = &() as *const (); + x.offset(0); + x.wrapping_add(0); + x.sub(0); + x.wrapping_sub(0); + + let y = &1 as *const u8; + y.offset(0); + } +} diff --git a/tests/ui/zero_offset.stderr b/tests/ui/zero_offset.stderr new file mode 100644 index 000000000000..cfcd7de2b3d2 --- /dev/null +++ b/tests/ui/zero_offset.stderr @@ -0,0 +1,9 @@ +error[E0606]: casting `&i32` as `*const u8` is invalid + --> $DIR/zero_offset.rs:9:17 + | +LL | let y = &1 as *const u8; + | ^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0606`.