diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_lint/src | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_lint/src')
24 files changed, 12462 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs new file mode 100644 index 000000000..121fefdc6 --- /dev/null +++ b/compiler/rustc_lint/src/array_into_iter.rs @@ -0,0 +1,155 @@ +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_errors::{fluent, Applicability}; +use rustc_hir as hir; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::{Adjust, Adjustment}; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_span::edition::Edition; +use rustc_span::symbol::sym; +use rustc_span::Span; + +declare_lint! { + /// The `array_into_iter` lint detects calling `into_iter` on arrays. + /// + /// ### Example + /// + /// ```rust,edition2018 + /// # #![allow(unused)] + /// [1, 2, 3].into_iter().for_each(|n| { *n; }); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid + /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still + /// behave as `(&array).into_iter()`, returning an iterator over + /// references, just like in Rust 1.52 and earlier. + /// This only applies to the method call syntax `array.into_iter()`, not to + /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`. + pub ARRAY_INTO_ITER, + Warn, + "detects calling `into_iter` on arrays in Rust 2015 and 2018", + @future_incompatible = FutureIncompatibleInfo { + reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>", + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021), + }; +} + +#[derive(Copy, Clone, Default)] +pub struct ArrayIntoIter { + for_expr_span: Span, +} + +impl_lint_pass!(ArrayIntoIter => [ARRAY_INTO_ITER]); + +impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + // Save the span of expressions in `for _ in expr` syntax, + // so we can give a better suggestion for those later. + if let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) = &expr.kind { + if let hir::ExprKind::Call(path, [arg]) = &arg.kind { + if let hir::ExprKind::Path(hir::QPath::LangItem( + hir::LangItem::IntoIterIntoIter, + .., + )) = &path.kind + { + self.for_expr_span = arg.span; + } + } + } + + // We only care about method call expressions. + if let hir::ExprKind::MethodCall(call, args, _) = &expr.kind { + if call.ident.name != sym::into_iter { + return; + } + + // Check if the method call actually calls the libcore + // `IntoIterator::into_iter`. + let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); + match cx.tcx.trait_of_item(def_id) { + Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {} + _ => return, + }; + + // As this is a method call expression, we have at least one argument. + let receiver_arg = &args[0]; + let receiver_ty = cx.typeck_results().expr_ty(receiver_arg); + let adjustments = cx.typeck_results().expr_adjustments(receiver_arg); + + let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else { + return + }; + + let types = + std::iter::once(receiver_ty).chain(adjustments.iter().map(|adj| adj.target)); + + let mut found_array = false; + + for ty in types { + match ty.kind() { + // If we run into a &[T; N] or &[T] first, there's nothing to warn about. + // It'll resolve to the reference version. + ty::Ref(_, inner_ty, _) if inner_ty.is_array() => return, + ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => return, + // Found an actual array type without matching a &[T; N] first. + // This is the problematic case. + ty::Array(..) => { + found_array = true; + break; + } + _ => {} + } + } + + if !found_array { + return; + } + + // Emit lint diagnostic. + let target = match *target.kind() { + ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]", + ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => "[T]", + // We know the original first argument type is an array type, + // we know that the first adjustment was an autoref coercion + // and we know that `IntoIterator` is the trait involved. The + // array cannot be coerced to something other than a reference + // to an array or to a slice. + _ => bug!("array type coerced to something other than array or slice"), + }; + cx.struct_span_lint(ARRAY_INTO_ITER, call.ident.span, |lint| { + let mut diag = lint.build(fluent::lint::array_into_iter); + diag.set_arg("target", target); + diag.span_suggestion( + call.ident.span, + fluent::lint::use_iter_suggestion, + "iter", + Applicability::MachineApplicable, + ); + if self.for_expr_span == expr.span { + diag.span_suggestion( + receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), + fluent::lint::remove_into_iter_suggestion, + "", + Applicability::MaybeIncorrect, + ); + } else if receiver_ty.is_array() { + diag.multipart_suggestion( + fluent::lint::use_explicit_into_iter_suggestion, + vec![ + (expr.span.shrink_to_lo(), "IntoIterator::into_iter(".into()), + ( + receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), + ")".into(), + ), + ], + Applicability::MaybeIncorrect, + ); + } + diag.emit(); + }) + } + } +} diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs new file mode 100644 index 000000000..bd58021f7 --- /dev/null +++ b/compiler/rustc_lint/src/builtin.rs @@ -0,0 +1,3172 @@ +//! Lints in the Rust compiler. +//! +//! This contains lints which can feasibly be implemented as their own +//! AST visitor. Also see `rustc_session::lint::builtin`, which contains the +//! definitions of lints that are emitted directly inside the main compiler. +//! +//! To add a new lint to rustc, declare it here using `declare_lint!()`. +//! Then add code to emit the new lint in the appropriate circumstances. +//! You can do that in an existing `LintPass` if it makes sense, or in a +//! new `LintPass`, or using `Session::add_lint` elsewhere in the +//! compiler. Only do the latter if the check can't be written cleanly as a +//! `LintPass` (also, note that such lints will need to be defined in +//! `rustc_session::lint::builtin`, not here). +//! +//! If you define a new `EarlyLintPass`, you will also need to add it to the +//! `add_early_builtin!` or `add_early_builtin_with_new!` invocation in +//! `lib.rs`. Use the former for unit-like structs and the latter for structs +//! with a `pub fn new()`. +//! +//! If you define a new `LateLintPass`, you will also need to add it to the +//! `late_lint_methods!` invocation in `lib.rs`. + +use crate::{ + types::{transparent_newtype_field, CItemKind}, + EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext, +}; +use rustc_ast::attr; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; +use rustc_ast::visit::{FnCtxt, FnKind}; +use rustc_ast::{self as ast, *}; +use rustc_ast_pretty::pprust::{self, expr_to_string}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_errors::{ + fluent, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString, + LintDiagnosticBuilder, MultiSpan, +}; +use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability}; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID}; +use rustc_hir::{ForeignItemKind, GenericParamKind, HirId, PatKind, PredicateOrigin}; +use rustc_index::vec::Idx; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::layout::{LayoutError, LayoutOf}; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::Instance; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason}; +use rustc_span::edition::Edition; +use rustc_span::source_map::Spanned; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::{BytePos, InnerSpan, Span}; +use rustc_target::abi::VariantIdx; +use rustc_trait_selection::traits::{self, misc::can_type_implement_copy}; + +use crate::nonstandard_style::{method_context, MethodLateContext}; + +use std::fmt::Write; +use tracing::{debug, trace}; + +// hardwired lints from librustc_middle +pub use rustc_session::lint::builtin::*; + +declare_lint! { + /// The `while_true` lint detects `while true { }`. + /// + /// ### Example + /// + /// ```rust,no_run + /// while true { + /// + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// `while true` should be replaced with `loop`. A `loop` expression is + /// the preferred way to write an infinite loop because it more directly + /// expresses the intent of the loop. + WHILE_TRUE, + Warn, + "suggest using `loop { }` instead of `while true { }`" +} + +declare_lint_pass!(WhileTrue => [WHILE_TRUE]); + +/// Traverse through any amount of parenthesis and return the first non-parens expression. +fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr { + while let ast::ExprKind::Paren(sub) = &expr.kind { + expr = sub; + } + expr +} + +impl EarlyLintPass for WhileTrue { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::While(cond, _, label) = &e.kind { + if let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind { + if let ast::LitKind::Bool(true) = lit.kind { + if !lit.span.from_expansion() { + let condition_span = e.span.with_hi(cond.span.hi()); + cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| { + lint.build(fluent::lint::builtin_while_true) + .span_suggestion_short( + condition_span, + fluent::lint::suggestion, + format!( + "{}loop", + label.map_or_else(String::new, |label| format!( + "{}: ", + label.ident, + )) + ), + Applicability::MachineApplicable, + ) + .emit(); + }) + } + } + } + } + } +} + +declare_lint! { + /// The `box_pointers` lints use of the Box type. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(box_pointers)] + /// struct Foo { + /// x: Box<isize>, + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint is mostly historical, and not particularly useful. `Box<T>` + /// used to be built into the language, and the only way to do heap + /// allocation. Today's Rust can call into other allocators, etc. + BOX_POINTERS, + Allow, + "use of owned (Box type) heap memory" +} + +declare_lint_pass!(BoxPointers => [BOX_POINTERS]); + +impl BoxPointers { + fn check_heap_type(&self, cx: &LateContext<'_>, span: Span, ty: Ty<'_>) { + for leaf in ty.walk() { + if let GenericArgKind::Type(leaf_ty) = leaf.unpack() { + if leaf_ty.is_box() { + cx.struct_span_lint(BOX_POINTERS, span, |lint| { + lint.build(fluent::lint::builtin_box_pointers).set_arg("ty", ty).emit(); + }); + } + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for BoxPointers { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Fn(..) + | hir::ItemKind::TyAlias(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::Union(..) => { + self.check_heap_type(cx, it.span, cx.tcx.type_of(it.def_id)) + } + _ => (), + } + + // If it's a struct, we also have to check the fields' types + match it.kind { + hir::ItemKind::Struct(ref struct_def, _) | hir::ItemKind::Union(ref struct_def, _) => { + for struct_field in struct_def.fields() { + let def_id = cx.tcx.hir().local_def_id(struct_field.hir_id); + self.check_heap_type(cx, struct_field.span, cx.tcx.type_of(def_id)); + } + } + _ => (), + } + } + + fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { + let ty = cx.typeck_results().node_type(e.hir_id); + self.check_heap_type(cx, e.span, ty); + } +} + +declare_lint! { + /// The `non_shorthand_field_patterns` lint detects using `Struct { x: x }` + /// instead of `Struct { x }` in a pattern. + /// + /// ### Example + /// + /// ```rust + /// struct Point { + /// x: i32, + /// y: i32, + /// } + /// + /// + /// fn main() { + /// let p = Point { + /// x: 5, + /// y: 5, + /// }; + /// + /// match p { + /// Point { x: x, y: y } => (), + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The preferred style is to avoid the repetition of specifying both the + /// field name and the binding name if both identifiers are the same. + NON_SHORTHAND_FIELD_PATTERNS, + Warn, + "using `Struct { x: x }` instead of `Struct { x }` in a pattern" +} + +declare_lint_pass!(NonShorthandFieldPatterns => [NON_SHORTHAND_FIELD_PATTERNS]); + +impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns { + fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) { + if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind { + let variant = cx + .typeck_results() + .pat_ty(pat) + .ty_adt_def() + .expect("struct pattern type is not an ADT") + .variant_of_res(cx.qpath_res(qpath, pat.hir_id)); + for fieldpat in field_pats { + if fieldpat.is_shorthand { + continue; + } + if fieldpat.span.from_expansion() { + // Don't lint if this is a macro expansion: macro authors + // shouldn't have to worry about this kind of style issue + // (Issue #49588) + continue; + } + if let PatKind::Binding(binding_annot, _, ident, None) = fieldpat.pat.kind { + if cx.tcx.find_field_index(ident, &variant) + == Some(cx.tcx.field_index(fieldpat.hir_id, cx.typeck_results())) + { + cx.struct_span_lint(NON_SHORTHAND_FIELD_PATTERNS, fieldpat.span, |lint| { + let binding = match binding_annot { + hir::BindingAnnotation::Unannotated => None, + hir::BindingAnnotation::Mutable => Some("mut"), + hir::BindingAnnotation::Ref => Some("ref"), + hir::BindingAnnotation::RefMut => Some("ref mut"), + }; + let suggested_ident = if let Some(binding) = binding { + format!("{} {}", binding, ident) + } else { + ident.to_string() + }; + lint.build(fluent::lint::builtin_non_shorthand_field_patterns) + .set_arg("ident", ident.clone()) + .span_suggestion( + fieldpat.span, + fluent::lint::suggestion, + suggested_ident, + Applicability::MachineApplicable, + ) + .emit(); + }); + } + } + } + } + } +} + +declare_lint! { + /// The `unsafe_code` lint catches usage of `unsafe` code. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unsafe_code)] + /// fn main() { + /// unsafe { + /// + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint is intended to restrict the usage of `unsafe`, which can be + /// difficult to use correctly. + UNSAFE_CODE, + Allow, + "usage of `unsafe` code" +} + +declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]); + +impl UnsafeCode { + fn report_unsafe( + &self, + cx: &EarlyContext<'_>, + span: Span, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + ) { + // This comes from a macro that has `#[allow_internal_unsafe]`. + if span.allows_unsafe() { + return; + } + + cx.struct_span_lint(UNSAFE_CODE, span, decorate); + } + + fn report_overridden_symbol_name( + &self, + cx: &EarlyContext<'_>, + span: Span, + msg: DiagnosticMessage, + ) { + self.report_unsafe(cx, span, |lint| { + lint.build(msg).note(fluent::lint::builtin_overridden_symbol_name).emit(); + }) + } + + fn report_overridden_symbol_section( + &self, + cx: &EarlyContext<'_>, + span: Span, + msg: DiagnosticMessage, + ) { + self.report_unsafe(cx, span, |lint| { + lint.build(msg).note(fluent::lint::builtin_overridden_symbol_section).emit(); + }) + } +} + +impl EarlyLintPass for UnsafeCode { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) { + if attr.has_name(sym::allow_internal_unsafe) { + self.report_unsafe(cx, attr.span, |lint| { + lint.build(fluent::lint::builtin_allow_internal_unsafe).emit(); + }); + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::Block(ref blk, _) = e.kind { + // Don't warn about generated blocks; that'll just pollute the output. + if blk.rules == ast::BlockCheckMode::Unsafe(ast::UserProvided) { + self.report_unsafe(cx, blk.span, |lint| { + lint.build(fluent::lint::builtin_unsafe_block).emit(); + }); + } + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { + match it.kind { + ast::ItemKind::Trait(box ast::Trait { unsafety: ast::Unsafe::Yes(_), .. }) => self + .report_unsafe(cx, it.span, |lint| { + lint.build(fluent::lint::builtin_unsafe_trait).emit(); + }), + + ast::ItemKind::Impl(box ast::Impl { unsafety: ast::Unsafe::Yes(_), .. }) => self + .report_unsafe(cx, it.span, |lint| { + lint.build(fluent::lint::builtin_unsafe_impl).emit(); + }), + + ast::ItemKind::Fn(..) => { + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) { + self.report_overridden_symbol_name( + cx, + attr.span, + fluent::lint::builtin_no_mangle_fn, + ); + } + + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::export_name) { + self.report_overridden_symbol_name( + cx, + attr.span, + fluent::lint::builtin_export_name_fn, + ); + } + + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::link_section) { + self.report_overridden_symbol_section( + cx, + attr.span, + fluent::lint::builtin_link_section_fn, + ); + } + } + + ast::ItemKind::Static(..) => { + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) { + self.report_overridden_symbol_name( + cx, + attr.span, + fluent::lint::builtin_no_mangle_static, + ); + } + + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::export_name) { + self.report_overridden_symbol_name( + cx, + attr.span, + fluent::lint::builtin_export_name_static, + ); + } + + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::link_section) { + self.report_overridden_symbol_section( + cx, + attr.span, + fluent::lint::builtin_link_section_static, + ); + } + } + + _ => {} + } + } + + fn check_impl_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { + if let ast::AssocItemKind::Fn(..) = it.kind { + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) { + self.report_overridden_symbol_name( + cx, + attr.span, + fluent::lint::builtin_no_mangle_method, + ); + } + if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::export_name) { + self.report_overridden_symbol_name( + cx, + attr.span, + fluent::lint::builtin_export_name_method, + ); + } + } + } + + fn check_fn(&mut self, cx: &EarlyContext<'_>, fk: FnKind<'_>, span: Span, _: ast::NodeId) { + if let FnKind::Fn( + ctxt, + _, + ast::FnSig { header: ast::FnHeader { unsafety: ast::Unsafe::Yes(_), .. }, .. }, + _, + _, + body, + ) = fk + { + let msg = match ctxt { + FnCtxt::Foreign => return, + FnCtxt::Free => fluent::lint::builtin_decl_unsafe_fn, + FnCtxt::Assoc(_) if body.is_none() => fluent::lint::builtin_decl_unsafe_method, + FnCtxt::Assoc(_) => fluent::lint::builtin_impl_unsafe_method, + }; + self.report_unsafe(cx, span, |lint| { + lint.build(msg).emit(); + }); + } + } +} + +declare_lint! { + /// The `missing_docs` lint detects missing documentation for public items. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(missing_docs)] + /// pub fn foo() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint is intended to ensure that a library is well-documented. + /// Items without documentation can be difficult for users to understand + /// how to use properly. + /// + /// This lint is "allow" by default because it can be noisy, and not all + /// projects may want to enforce everything to be documented. + pub MISSING_DOCS, + Allow, + "detects missing documentation for public members", + report_in_external_macro +} + +pub struct MissingDoc { + /// Stack of whether `#[doc(hidden)]` is set at each level which has lint attributes. + doc_hidden_stack: Vec<bool>, +} + +impl_lint_pass!(MissingDoc => [MISSING_DOCS]); + +fn has_doc(attr: &ast::Attribute) -> bool { + if attr.is_doc_comment() { + return true; + } + + if !attr.has_name(sym::doc) { + return false; + } + + if attr.value_str().is_some() { + return true; + } + + if let Some(list) = attr.meta_item_list() { + for meta in list { + if meta.has_name(sym::hidden) { + return true; + } + } + } + + false +} + +impl MissingDoc { + pub fn new() -> MissingDoc { + MissingDoc { doc_hidden_stack: vec![false] } + } + + fn doc_hidden(&self) -> bool { + *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") + } + + fn check_missing_docs_attrs( + &self, + cx: &LateContext<'_>, + def_id: LocalDefId, + article: &'static str, + desc: &'static str, + ) { + // If we're building a test harness, then warning about + // documentation is probably not really relevant right now. + if cx.sess().opts.test { + return; + } + + // `#[doc(hidden)]` disables missing_docs check. + if self.doc_hidden() { + return; + } + + // Only check publicly-visible items, using the result from the privacy pass. + // It's an option so the crate root can also use this function (it doesn't + // have a `NodeId`). + if def_id != CRATE_DEF_ID { + if !cx.access_levels.is_exported(def_id) { + return; + } + } + + let attrs = cx.tcx.hir().attrs(cx.tcx.hir().local_def_id_to_hir_id(def_id)); + let has_doc = attrs.iter().any(has_doc); + if !has_doc { + cx.struct_span_lint(MISSING_DOCS, cx.tcx.def_span(def_id), |lint| { + lint.build(fluent::lint::builtin_missing_doc) + .set_arg("article", article) + .set_arg("desc", desc) + .emit(); + }); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for MissingDoc { + fn enter_lint_attrs(&mut self, _cx: &LateContext<'_>, attrs: &[ast::Attribute]) { + let doc_hidden = self.doc_hidden() + || attrs.iter().any(|attr| { + attr.has_name(sym::doc) + && match attr.meta_item_list() { + None => false, + Some(l) => attr::list_contains_name(&l, sym::hidden), + } + }); + self.doc_hidden_stack.push(doc_hidden); + } + + fn exit_lint_attrs(&mut self, _: &LateContext<'_>, _attrs: &[ast::Attribute]) { + self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); + } + + fn check_crate(&mut self, cx: &LateContext<'_>) { + self.check_missing_docs_attrs(cx, CRATE_DEF_ID, "the", "crate"); + } + + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Trait(..) => { + // Issue #11592: traits are always considered exported, even when private. + if cx.tcx.visibility(it.def_id) + == ty::Visibility::Restricted( + cx.tcx.parent_module_from_def_id(it.def_id).to_def_id(), + ) + { + return; + } + } + hir::ItemKind::TyAlias(..) + | hir::ItemKind::Fn(..) + | hir::ItemKind::Macro(..) + | hir::ItemKind::Mod(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::Union(..) + | hir::ItemKind::Const(..) + | hir::ItemKind::Static(..) => {} + + _ => return, + }; + + let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id()); + + self.check_missing_docs_attrs(cx, it.def_id, article, desc); + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, trait_item: &hir::TraitItem<'_>) { + let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id()); + + self.check_missing_docs_attrs(cx, trait_item.def_id, article, desc); + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { + // If the method is an impl for a trait, don't doc. + if method_context(cx, impl_item.hir_id()) == MethodLateContext::TraitImpl { + return; + } + + // If the method is an impl for an item with docs_hidden, don't doc. + if method_context(cx, impl_item.hir_id()) == MethodLateContext::PlainImpl { + let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()); + let impl_ty = cx.tcx.type_of(parent); + let outerdef = match impl_ty.kind() { + ty::Adt(def, _) => Some(def.did()), + ty::Foreign(def_id) => Some(*def_id), + _ => None, + }; + let is_hidden = match outerdef { + Some(id) => cx.tcx.is_doc_hidden(id), + None => false, + }; + if is_hidden { + return; + } + } + + let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id()); + self.check_missing_docs_attrs(cx, impl_item.def_id, article, desc); + } + + fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'_>) { + let (article, desc) = cx.tcx.article_and_description(foreign_item.def_id.to_def_id()); + self.check_missing_docs_attrs(cx, foreign_item.def_id, article, desc); + } + + fn check_field_def(&mut self, cx: &LateContext<'_>, sf: &hir::FieldDef<'_>) { + if !sf.is_positional() { + let def_id = cx.tcx.hir().local_def_id(sf.hir_id); + self.check_missing_docs_attrs(cx, def_id, "a", "struct field") + } + } + + fn check_variant(&mut self, cx: &LateContext<'_>, v: &hir::Variant<'_>) { + self.check_missing_docs_attrs(cx, cx.tcx.hir().local_def_id(v.id), "a", "variant"); + } +} + +declare_lint! { + /// The `missing_copy_implementations` lint detects potentially-forgotten + /// implementations of [`Copy`]. + /// + /// [`Copy`]: https://doc.rust-lang.org/std/marker/trait.Copy.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(missing_copy_implementations)] + /// pub struct Foo { + /// pub field: i32 + /// } + /// # fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Historically (before 1.0), types were automatically marked as `Copy` + /// if possible. This was changed so that it required an explicit opt-in + /// by implementing the `Copy` trait. As part of this change, a lint was + /// added to alert if a copyable type was not marked `Copy`. + /// + /// This lint is "allow" by default because this code isn't bad; it is + /// common to write newtypes like this specifically so that a `Copy` type + /// is no longer `Copy`. `Copy` types can result in unintended copies of + /// large data which can impact performance. + pub MISSING_COPY_IMPLEMENTATIONS, + Allow, + "detects potentially-forgotten implementations of `Copy`" +} + +declare_lint_pass!(MissingCopyImplementations => [MISSING_COPY_IMPLEMENTATIONS]); + +impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + if !cx.access_levels.is_reachable(item.def_id) { + return; + } + let (def, ty) = match item.kind { + hir::ItemKind::Struct(_, ref ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(item.def_id); + (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[]))) + } + hir::ItemKind::Union(_, ref ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(item.def_id); + (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[]))) + } + hir::ItemKind::Enum(_, ref ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(item.def_id); + (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[]))) + } + _ => return, + }; + if def.has_dtor(cx.tcx) { + return; + } + let param_env = ty::ParamEnv::empty(); + if ty.is_copy_modulo_regions(cx.tcx.at(item.span), param_env) { + return; + } + if can_type_implement_copy( + cx.tcx, + param_env, + ty, + traits::ObligationCause::misc(item.span, item.hir_id()), + ) + .is_ok() + { + cx.struct_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, |lint| { + lint.build(fluent::lint::builtin_missing_copy_impl).emit(); + }) + } + } +} + +declare_lint! { + /// The `missing_debug_implementations` lint detects missing + /// implementations of [`fmt::Debug`]. + /// + /// [`fmt::Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(missing_debug_implementations)] + /// pub struct Foo; + /// # fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Having a `Debug` implementation on all types can assist with + /// debugging, as it provides a convenient way to format and display a + /// value. Using the `#[derive(Debug)]` attribute will automatically + /// generate a typical implementation, or a custom implementation can be + /// added by manually implementing the `Debug` trait. + /// + /// This lint is "allow" by default because adding `Debug` to all types can + /// have a negative impact on compile time and code size. It also requires + /// boilerplate to be added to every type, which can be an impediment. + MISSING_DEBUG_IMPLEMENTATIONS, + Allow, + "detects missing implementations of Debug" +} + +#[derive(Default)] +pub struct MissingDebugImplementations { + impling_types: Option<LocalDefIdSet>, +} + +impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]); + +impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + if !cx.access_levels.is_reachable(item.def_id) { + return; + } + + match item.kind { + hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {} + _ => return, + } + + let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { + return + }; + + if self.impling_types.is_none() { + let mut impls = LocalDefIdSet::default(); + cx.tcx.for_each_impl(debug, |d| { + if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { + if let Some(def_id) = ty_def.did().as_local() { + impls.insert(def_id); + } + } + }); + + self.impling_types = Some(impls); + debug!("{:?}", self.impling_types); + } + + if !self.impling_types.as_ref().unwrap().contains(&item.def_id) { + cx.struct_span_lint(MISSING_DEBUG_IMPLEMENTATIONS, item.span, |lint| { + lint.build(fluent::lint::builtin_missing_debug_impl) + .set_arg("debug", cx.tcx.def_path_str(debug)) + .emit(); + }); + } + } +} + +declare_lint! { + /// The `anonymous_parameters` lint detects anonymous parameters in trait + /// definitions. + /// + /// ### Example + /// + /// ```rust,edition2015,compile_fail + /// #![deny(anonymous_parameters)] + /// // edition 2015 + /// pub trait Foo { + /// fn foo(usize); + /// } + /// fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This syntax is mostly a historical accident, and can be worked around + /// quite easily by adding an `_` pattern or a descriptive identifier: + /// + /// ```rust + /// trait Foo { + /// fn foo(_: usize); + /// } + /// ``` + /// + /// This syntax is now a hard error in the 2018 edition. In the 2015 + /// edition, this lint is "warn" by default. This lint + /// enables the [`cargo fix`] tool with the `--edition` flag to + /// automatically transition old code from the 2015 edition to 2018. The + /// tool will run this lint and automatically apply the + /// suggested fix from the compiler (which is to add `_` to each + /// parameter). This provides a completely automated way to update old + /// code for a new edition. See [issue #41686] for more details. + /// + /// [issue #41686]: https://github.com/rust-lang/rust/issues/41686 + /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html + pub ANONYMOUS_PARAMETERS, + Warn, + "detects anonymous parameters", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>", + reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018), + }; +} + +declare_lint_pass!( + /// Checks for use of anonymous parameters (RFC 1685). + AnonymousParameters => [ANONYMOUS_PARAMETERS] +); + +impl EarlyLintPass for AnonymousParameters { + fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { + if cx.sess().edition() != Edition::Edition2015 { + // This is a hard error in future editions; avoid linting and erroring + return; + } + if let ast::AssocItemKind::Fn(box Fn { ref sig, .. }) = it.kind { + for arg in sig.decl.inputs.iter() { + if let ast::PatKind::Ident(_, ident, None) = arg.pat.kind { + if ident.name == kw::Empty { + cx.struct_span_lint(ANONYMOUS_PARAMETERS, arg.pat.span, |lint| { + let ty_snip = cx.sess().source_map().span_to_snippet(arg.ty.span); + + let (ty_snip, appl) = if let Ok(ref snip) = ty_snip { + (snip.as_str(), Applicability::MachineApplicable) + } else { + ("<type>", Applicability::HasPlaceholders) + }; + + lint.build(fluent::lint::builtin_anonymous_params) + .span_suggestion( + arg.pat.span, + fluent::lint::suggestion, + format!("_: {}", ty_snip), + appl, + ) + .emit(); + }) + } + } + } + } + } +} + +/// Check for use of attributes which have been deprecated. +#[derive(Clone)] +pub struct DeprecatedAttr { + // This is not free to compute, so we want to keep it around, rather than + // compute it for every attribute. + depr_attrs: Vec<&'static BuiltinAttribute>, +} + +impl_lint_pass!(DeprecatedAttr => []); + +impl DeprecatedAttr { + pub fn new() -> DeprecatedAttr { + DeprecatedAttr { depr_attrs: deprecated_attributes() } + } +} + +impl EarlyLintPass for DeprecatedAttr { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) { + for BuiltinAttribute { name, gate, .. } in &self.depr_attrs { + if attr.ident().map(|ident| ident.name) == Some(*name) { + if let &AttributeGate::Gated( + Stability::Deprecated(link, suggestion), + name, + reason, + _, + ) = gate + { + cx.struct_span_lint(DEPRECATED, attr.span, |lint| { + // FIXME(davidtwco) translatable deprecated attr + lint.build(fluent::lint::builtin_deprecated_attr_link) + .set_arg("name", name) + .set_arg("reason", reason) + .set_arg("link", link) + .span_suggestion_short( + attr.span, + suggestion.map(|s| s.into()).unwrap_or( + fluent::lint::builtin_deprecated_attr_default_suggestion, + ), + "", + Applicability::MachineApplicable, + ) + .emit(); + }); + } + return; + } + } + if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) { + cx.struct_span_lint(DEPRECATED, attr.span, |lint| { + lint.build(fluent::lint::builtin_deprecated_attr_used) + .set_arg("name", pprust::path_to_string(&attr.get_normal_item().path)) + .span_suggestion_short( + attr.span, + fluent::lint::builtin_deprecated_attr_default_suggestion, + "", + Applicability::MachineApplicable, + ) + .emit(); + }); + } + } +} + +fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &[ast::Attribute]) { + use rustc_ast::token::CommentKind; + + let mut attrs = attrs.iter().peekable(); + + // Accumulate a single span for sugared doc comments. + let mut sugared_span: Option<Span> = None; + + while let Some(attr) = attrs.next() { + let is_doc_comment = attr.is_doc_comment(); + if is_doc_comment { + sugared_span = + Some(sugared_span.map_or(attr.span, |span| span.with_hi(attr.span.hi()))); + } + + if attrs.peek().map_or(false, |next_attr| next_attr.is_doc_comment()) { + continue; + } + + let span = sugared_span.take().unwrap_or(attr.span); + + if is_doc_comment || attr.has_name(sym::doc) { + cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| { + let mut err = lint.build(fluent::lint::builtin_unused_doc_comment); + err.set_arg("kind", node_kind); + err.span_label(node_span, fluent::lint::label); + match attr.kind { + AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => { + err.help(fluent::lint::plain_help); + } + AttrKind::DocComment(CommentKind::Block, _) => { + err.help(fluent::lint::block_help); + } + } + err.emit(); + }); + } + } +} + +impl EarlyLintPass for UnusedDocComment { + fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { + let kind = match stmt.kind { + ast::StmtKind::Local(..) => "statements", + // Disabled pending discussion in #78306 + ast::StmtKind::Item(..) => return, + // expressions will be reported by `check_expr`. + ast::StmtKind::Empty + | ast::StmtKind::Semi(_) + | ast::StmtKind::Expr(_) + | ast::StmtKind::MacCall(_) => return, + }; + + warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs()); + } + + fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) { + let arm_span = arm.pat.span.with_hi(arm.body.span.hi()); + warn_if_doc(cx, arm_span, "match arms", &arm.attrs); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + warn_if_doc(cx, expr.span, "expressions", &expr.attrs); + } + + fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) { + warn_if_doc(cx, param.ident.span, "generic parameters", ¶m.attrs); + } + + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { + warn_if_doc(cx, block.span, "blocks", &block.attrs()); + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + if let ast::ItemKind::ForeignMod(_) = item.kind { + warn_if_doc(cx, item.span, "extern blocks", &item.attrs); + } + } +} + +declare_lint! { + /// The `no_mangle_const_items` lint detects any `const` items with the + /// [`no_mangle` attribute]. + /// + /// [`no_mangle` attribute]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #[no_mangle] + /// const FOO: i32 = 5; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Constants do not have their symbols exported, and therefore, this + /// probably means you meant to use a [`static`], not a [`const`]. + /// + /// [`static`]: https://doc.rust-lang.org/reference/items/static-items.html + /// [`const`]: https://doc.rust-lang.org/reference/items/constant-items.html + NO_MANGLE_CONST_ITEMS, + Deny, + "const items will not have their symbols exported" +} + +declare_lint! { + /// The `no_mangle_generic_items` lint detects generic items that must be + /// mangled. + /// + /// ### Example + /// + /// ```rust + /// #[no_mangle] + /// fn foo<T>(t: T) { + /// + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// A function with generics must have its symbol mangled to accommodate + /// the generic parameter. The [`no_mangle` attribute] has no effect in + /// this situation, and should be removed. + /// + /// [`no_mangle` attribute]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute + NO_MANGLE_GENERIC_ITEMS, + Warn, + "generic items must be mangled" +} + +declare_lint_pass!(InvalidNoMangleItems => [NO_MANGLE_CONST_ITEMS, NO_MANGLE_GENERIC_ITEMS]); + +impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + let attrs = cx.tcx.hir().attrs(it.hir_id()); + let check_no_mangle_on_generic_fn = |no_mangle_attr: &ast::Attribute, + impl_generics: Option<&hir::Generics<'_>>, + generics: &hir::Generics<'_>, + span| { + for param in + generics.params.iter().chain(impl_generics.map(|g| g.params).into_iter().flatten()) + { + match param.kind { + GenericParamKind::Lifetime { .. } => {} + GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => { + cx.struct_span_lint(NO_MANGLE_GENERIC_ITEMS, span, |lint| { + lint.build(fluent::lint::builtin_no_mangle_generic) + .span_suggestion_short( + no_mangle_attr.span, + fluent::lint::suggestion, + "", + // Use of `#[no_mangle]` suggests FFI intent; correct + // fix may be to monomorphize source by hand + Applicability::MaybeIncorrect, + ) + .emit(); + }); + break; + } + } + } + }; + match it.kind { + hir::ItemKind::Fn(.., ref generics, _) => { + if let Some(no_mangle_attr) = cx.sess().find_by_name(attrs, sym::no_mangle) { + check_no_mangle_on_generic_fn(no_mangle_attr, None, generics, it.span); + } + } + hir::ItemKind::Const(..) => { + if cx.sess().contains_name(attrs, sym::no_mangle) { + // Const items do not refer to a particular location in memory, and therefore + // don't have anything to attach a symbol to + cx.struct_span_lint(NO_MANGLE_CONST_ITEMS, it.span, |lint| { + let mut err = lint.build(fluent::lint::builtin_const_no_mangle); + + // account for "pub const" (#45562) + let start = cx + .tcx + .sess + .source_map() + .span_to_snippet(it.span) + .map(|snippet| snippet.find("const").unwrap_or(0)) + .unwrap_or(0) as u32; + // `const` is 5 chars + let const_span = it.span.with_hi(BytePos(it.span.lo().0 + start + 5)); + err.span_suggestion( + const_span, + fluent::lint::suggestion, + "pub static", + Applicability::MachineApplicable, + ); + err.emit(); + }); + } + } + hir::ItemKind::Impl(hir::Impl { generics, items, .. }) => { + for it in *items { + if let hir::AssocItemKind::Fn { .. } = it.kind { + if let Some(no_mangle_attr) = cx + .sess() + .find_by_name(cx.tcx.hir().attrs(it.id.hir_id()), sym::no_mangle) + { + check_no_mangle_on_generic_fn( + no_mangle_attr, + Some(generics), + cx.tcx.hir().get_generics(it.id.def_id).unwrap(), + it.span, + ); + } + } + } + } + _ => {} + } + } +} + +declare_lint! { + /// The `mutable_transmutes` lint catches transmuting from `&T` to `&mut + /// T` because it is [undefined behavior]. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// unsafe { + /// let y = std::mem::transmute::<&i32, &mut i32>(&5); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Certain assumptions are made about aliasing of data, and this transmute + /// violates those assumptions. Consider using [`UnsafeCell`] instead. + /// + /// [`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html + MUTABLE_TRANSMUTES, + Deny, + "transmuting &T to &mut T is undefined behavior, even if the reference is unused" +} + +declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]); + +impl<'tcx> LateLintPass<'tcx> for MutableTransmutes { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + if let Some((&ty::Ref(_, _, from_mt), &ty::Ref(_, _, to_mt))) = + get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind())) + { + if to_mt == hir::Mutability::Mut && from_mt == hir::Mutability::Not { + cx.struct_span_lint(MUTABLE_TRANSMUTES, expr.span, |lint| { + lint.build(fluent::lint::builtin_mutable_transmutes).emit(); + }); + } + } + + fn get_transmute_from_to<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + ) -> Option<(Ty<'tcx>, Ty<'tcx>)> { + let def = if let hir::ExprKind::Path(ref qpath) = expr.kind { + cx.qpath_res(qpath, expr.hir_id) + } else { + return None; + }; + if let Res::Def(DefKind::Fn, did) = def { + if !def_id_is_transmute(cx, did) { + return None; + } + let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx); + let from = sig.inputs().skip_binder()[0]; + let to = sig.output().skip_binder(); + return Some((from, to)); + } + None + } + + fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool { + cx.tcx.is_intrinsic(def_id) && cx.tcx.item_name(def_id) == sym::transmute + } + } +} + +declare_lint! { + /// The `unstable_features` is deprecated and should no longer be used. + UNSTABLE_FEATURES, + Allow, + "enabling unstable features (deprecated. do not use)" +} + +declare_lint_pass!( + /// Forbids using the `#[feature(...)]` attribute + UnstableFeatures => [UNSTABLE_FEATURES] +); + +impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) { + if attr.has_name(sym::feature) { + if let Some(items) = attr.meta_item_list() { + for item in items { + cx.struct_span_lint(UNSTABLE_FEATURES, item.span(), |lint| { + lint.build(fluent::lint::builtin_unstable_features).emit(); + }); + } + } + } + } +} + +declare_lint! { + /// The `unreachable_pub` lint triggers for `pub` items not reachable from + /// the crate root. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unreachable_pub)] + /// mod foo { + /// pub mod bar { + /// + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// A bare `pub` visibility may be misleading if the item is not actually + /// publicly exported from the crate. The `pub(crate)` visibility is + /// recommended to be used instead, which more clearly expresses the intent + /// that the item is only visible within its own crate. + /// + /// This lint is "allow" by default because it will trigger for a large + /// amount existing Rust code, and has some false-positives. Eventually it + /// is desired for this to become warn-by-default. + pub UNREACHABLE_PUB, + Allow, + "`pub` items not reachable from crate root" +} + +declare_lint_pass!( + /// Lint for items marked `pub` that aren't reachable from other crates. + UnreachablePub => [UNREACHABLE_PUB] +); + +impl UnreachablePub { + fn perform_lint( + &self, + cx: &LateContext<'_>, + what: &str, + def_id: LocalDefId, + vis_span: Span, + exportable: bool, + ) { + let mut applicability = Applicability::MachineApplicable; + if cx.tcx.visibility(def_id).is_public() && !cx.access_levels.is_reachable(def_id) { + if vis_span.from_expansion() { + applicability = Applicability::MaybeIncorrect; + } + let def_span = cx.tcx.def_span(def_id); + cx.struct_span_lint(UNREACHABLE_PUB, def_span, |lint| { + let mut err = lint.build(fluent::lint::builtin_unreachable_pub); + err.set_arg("what", what); + + err.span_suggestion( + vis_span, + fluent::lint::suggestion, + "pub(crate)", + applicability, + ); + if exportable { + err.help(fluent::lint::help); + } + err.emit(); + }); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for UnreachablePub { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + // Do not warn for fake `use` statements. + if let hir::ItemKind::Use(_, hir::UseKind::ListStem) = &item.kind { + return; + } + self.perform_lint(cx, "item", item.def_id, item.vis_span, true); + } + + fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'tcx>) { + self.perform_lint(cx, "item", foreign_item.def_id, foreign_item.vis_span, true); + } + + fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) { + let def_id = cx.tcx.hir().local_def_id(field.hir_id); + self.perform_lint(cx, "field", def_id, field.vis_span, false); + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { + // Only lint inherent impl items. + if cx.tcx.associated_item(impl_item.def_id).trait_item_def_id.is_none() { + self.perform_lint(cx, "item", impl_item.def_id, impl_item.vis_span, false); + } + } +} + +declare_lint! { + /// The `type_alias_bounds` lint detects bounds in type aliases. + /// + /// ### Example + /// + /// ```rust + /// type SendVec<T: Send> = Vec<T>; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The trait bounds in a type alias are currently ignored, and should not + /// be included to avoid confusion. This was previously allowed + /// unintentionally; this may become a hard error in the future. + TYPE_ALIAS_BOUNDS, + Warn, + "bounds in type aliases are not enforced" +} + +declare_lint_pass!( + /// Lint for trait and lifetime bounds in type aliases being mostly ignored. + /// They are relevant when using associated types, but otherwise neither checked + /// at definition site nor enforced at use site. + TypeAliasBounds => [TYPE_ALIAS_BOUNDS] +); + +impl TypeAliasBounds { + fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool { + match *qpath { + hir::QPath::TypeRelative(ref ty, _) => { + // If this is a type variable, we found a `T::Assoc`. + match ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => { + matches!(path.res, Res::Def(DefKind::TyParam, _)) + } + _ => false, + } + } + hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false, + } + } + + fn suggest_changing_assoc_types(ty: &hir::Ty<'_>, err: &mut Diagnostic) { + // Access to associates types should use `<T as Bound>::Assoc`, which does not need a + // bound. Let's see if this type does that. + + // We use a HIR visitor to walk the type. + use rustc_hir::intravisit::{self, Visitor}; + struct WalkAssocTypes<'a> { + err: &'a mut Diagnostic, + } + impl Visitor<'_> for WalkAssocTypes<'_> { + fn visit_qpath(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, span: Span) { + if TypeAliasBounds::is_type_variable_assoc(qpath) { + self.err.span_help(span, fluent::lint::builtin_type_alias_bounds_help); + } + intravisit::walk_qpath(self, qpath, id, span) + } + } + + // Let's go for a walk! + let mut visitor = WalkAssocTypes { err }; + visitor.visit_ty(ty); + } +} + +impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + let hir::ItemKind::TyAlias(ty, type_alias_generics) = &item.kind else { + return + }; + if let hir::TyKind::OpaqueDef(..) = ty.kind { + // Bounds are respected for `type X = impl Trait` + return; + } + // There must not be a where clause + if type_alias_generics.predicates.is_empty() { + return; + } + + let mut where_spans = Vec::new(); + let mut inline_spans = Vec::new(); + let mut inline_sugg = Vec::new(); + for p in type_alias_generics.predicates { + let span = p.span(); + if p.in_where_clause() { + where_spans.push(span); + } else { + for b in p.bounds() { + inline_spans.push(b.span()); + } + inline_sugg.push((span, String::new())); + } + } + + let mut suggested_changing_assoc_types = false; + if !where_spans.is_empty() { + cx.lint(TYPE_ALIAS_BOUNDS, |lint| { + let mut err = lint.build(fluent::lint::builtin_type_alias_where_clause); + err.set_span(where_spans); + err.span_suggestion( + type_alias_generics.where_clause_span, + fluent::lint::suggestion, + "", + Applicability::MachineApplicable, + ); + if !suggested_changing_assoc_types { + TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err); + suggested_changing_assoc_types = true; + } + err.emit(); + }); + } + + if !inline_spans.is_empty() { + cx.lint(TYPE_ALIAS_BOUNDS, |lint| { + let mut err = lint.build(fluent::lint::builtin_type_alias_generic_bounds); + err.set_span(inline_spans); + err.multipart_suggestion( + fluent::lint::suggestion, + inline_sugg, + Applicability::MachineApplicable, + ); + if !suggested_changing_assoc_types { + TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err); + } + err.emit(); + }); + } + } +} + +declare_lint_pass!( + /// Lint constants that are erroneous. + /// Without this lint, we might not get any diagnostic if the constant is + /// unused within this crate, even though downstream crates can't use it + /// without producing an error. + UnusedBrokenConst => [] +); + +impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + match it.kind { + hir::ItemKind::Const(_, body_id) => { + let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id(); + // trigger the query once for all constants since that will already report the errors + cx.tcx.ensure().const_eval_poly(def_id); + } + hir::ItemKind::Static(_, _, body_id) => { + let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id(); + cx.tcx.ensure().eval_static_initializer(def_id); + } + _ => {} + } + } +} + +declare_lint! { + /// The `trivial_bounds` lint detects trait bounds that don't depend on + /// any type parameters. + /// + /// ### Example + /// + /// ```rust + /// #![feature(trivial_bounds)] + /// pub struct A where i32: Copy; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Usually you would not write a trait bound that you know is always + /// true, or never true. However, when using macros, the macro may not + /// know whether or not the constraint would hold or not at the time when + /// generating the code. Currently, the compiler does not alert you if the + /// constraint is always true, and generates an error if it is never true. + /// The `trivial_bounds` feature changes this to be a warning in both + /// cases, giving macros more freedom and flexibility to generate code, + /// while still providing a signal when writing non-macro code that + /// something is amiss. + /// + /// See [RFC 2056] for more details. This feature is currently only + /// available on the nightly channel, see [tracking issue #48214]. + /// + /// [RFC 2056]: https://github.com/rust-lang/rfcs/blob/master/text/2056-allow-trivial-where-clause-constraints.md + /// [tracking issue #48214]: https://github.com/rust-lang/rust/issues/48214 + TRIVIAL_BOUNDS, + Warn, + "these bounds don't depend on an type parameters" +} + +declare_lint_pass!( + /// Lint for trait and lifetime bounds that don't depend on type parameters + /// which either do nothing, or stop the item from being used. + TrivialConstraints => [TRIVIAL_BOUNDS] +); + +impl<'tcx> LateLintPass<'tcx> for TrivialConstraints { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + use rustc_middle::ty::visit::TypeVisitable; + use rustc_middle::ty::PredicateKind::*; + + if cx.tcx.features().trivial_bounds { + let predicates = cx.tcx.predicates_of(item.def_id); + for &(predicate, span) in predicates.predicates { + let predicate_kind_name = match predicate.kind().skip_binder() { + Trait(..) => "trait", + TypeOutlives(..) | + RegionOutlives(..) => "lifetime", + + // Ignore projections, as they can only be global + // if the trait bound is global + Projection(..) | + // Ignore bounds that a user can't type + WellFormed(..) | + ObjectSafe(..) | + ClosureKind(..) | + Subtype(..) | + Coerce(..) | + ConstEvaluatable(..) | + ConstEquate(..) | + TypeWellFormedFromEnv(..) => continue, + }; + if predicate.is_global() { + cx.struct_span_lint(TRIVIAL_BOUNDS, span, |lint| { + lint.build(fluent::lint::builtin_trivial_bounds) + .set_arg("predicate_kind_name", predicate_kind_name) + .set_arg("predicate", predicate) + .emit(); + }); + } + } + } + } +} + +declare_lint_pass!( + /// Does nothing as a lint pass, but registers some `Lint`s + /// which are used by other parts of the compiler. + SoftLints => [ + WHILE_TRUE, + BOX_POINTERS, + NON_SHORTHAND_FIELD_PATTERNS, + UNSAFE_CODE, + MISSING_DOCS, + MISSING_COPY_IMPLEMENTATIONS, + MISSING_DEBUG_IMPLEMENTATIONS, + ANONYMOUS_PARAMETERS, + UNUSED_DOC_COMMENTS, + NO_MANGLE_CONST_ITEMS, + NO_MANGLE_GENERIC_ITEMS, + MUTABLE_TRANSMUTES, + UNSTABLE_FEATURES, + UNREACHABLE_PUB, + TYPE_ALIAS_BOUNDS, + TRIVIAL_BOUNDS + ] +); + +declare_lint! { + /// The `ellipsis_inclusive_range_patterns` lint detects the [`...` range + /// pattern], which is deprecated. + /// + /// [`...` range pattern]: https://doc.rust-lang.org/reference/patterns.html#range-patterns + /// + /// ### Example + /// + /// ```rust,edition2018 + /// let x = 123; + /// match x { + /// 0...100 => {} + /// _ => {} + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The `...` range pattern syntax was changed to `..=` to avoid potential + /// confusion with the [`..` range expression]. Use the new form instead. + /// + /// [`..` range expression]: https://doc.rust-lang.org/reference/expressions/range-expr.html + pub ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, + Warn, + "`...` range patterns are deprecated", + @future_incompatible = FutureIncompatibleInfo { + reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/warnings-promoted-to-error.html>", + reason: FutureIncompatibilityReason::EditionError(Edition::Edition2021), + }; +} + +#[derive(Default)] +pub struct EllipsisInclusiveRangePatterns { + /// If `Some(_)`, suppress all subsequent pattern + /// warnings for better diagnostics. + node_id: Option<ast::NodeId>, +} + +impl_lint_pass!(EllipsisInclusiveRangePatterns => [ELLIPSIS_INCLUSIVE_RANGE_PATTERNS]); + +impl EarlyLintPass for EllipsisInclusiveRangePatterns { + fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) { + if self.node_id.is_some() { + // Don't recursively warn about patterns inside range endpoints. + return; + } + + use self::ast::{PatKind, RangeSyntax::DotDotDot}; + + /// If `pat` is a `...` pattern, return the start and end of the range, as well as the span + /// corresponding to the ellipsis. + fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(Option<&Expr>, &Expr, Span)> { + match &pat.kind { + PatKind::Range( + a, + Some(b), + Spanned { span, node: RangeEnd::Included(DotDotDot) }, + ) => Some((a.as_deref(), b, *span)), + _ => None, + } + } + + let (parenthesise, endpoints) = match &pat.kind { + PatKind::Ref(subpat, _) => (true, matches_ellipsis_pat(&subpat)), + _ => (false, matches_ellipsis_pat(pat)), + }; + + if let Some((start, end, join)) = endpoints { + let msg = fluent::lint::builtin_ellipsis_inclusive_range_patterns; + let suggestion = fluent::lint::suggestion; + if parenthesise { + self.node_id = Some(pat.id); + let end = expr_to_string(&end); + let replace = match start { + Some(start) => format!("&({}..={})", expr_to_string(&start), end), + None => format!("&(..={})", end), + }; + if join.edition() >= Edition::Edition2021 { + let mut err = cx.sess().struct_span_err_with_code( + pat.span, + msg, + rustc_errors::error_code!(E0783), + ); + err.span_suggestion( + pat.span, + suggestion, + replace, + Applicability::MachineApplicable, + ) + .emit(); + } else { + cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, |lint| { + lint.build(msg) + .span_suggestion( + pat.span, + suggestion, + replace, + Applicability::MachineApplicable, + ) + .emit(); + }); + } + } else { + let replace = "..="; + if join.edition() >= Edition::Edition2021 { + let mut err = cx.sess().struct_span_err_with_code( + pat.span, + msg, + rustc_errors::error_code!(E0783), + ); + err.span_suggestion_short( + join, + suggestion, + replace, + Applicability::MachineApplicable, + ) + .emit(); + } else { + cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, |lint| { + lint.build(msg) + .span_suggestion_short( + join, + suggestion, + replace, + Applicability::MachineApplicable, + ) + .emit(); + }); + } + }; + } + } + + fn check_pat_post(&mut self, _cx: &EarlyContext<'_>, pat: &ast::Pat) { + if let Some(node_id) = self.node_id { + if pat.id == node_id { + self.node_id = None + } + } + } +} + +declare_lint! { + /// The `unnameable_test_items` lint detects [`#[test]`][test] functions + /// that are not able to be run by the test harness because they are in a + /// position where they are not nameable. + /// + /// [test]: https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute + /// + /// ### Example + /// + /// ```rust,test + /// fn main() { + /// #[test] + /// fn foo() { + /// // This test will not fail because it does not run. + /// assert_eq!(1, 2); + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In order for the test harness to run a test, the test function must be + /// located in a position where it can be accessed from the crate root. + /// This generally means it must be defined in a module, and not anywhere + /// else such as inside another function. The compiler previously allowed + /// this without an error, so a lint was added as an alert that a test is + /// not being used. Whether or not this should be allowed has not yet been + /// decided, see [RFC 2471] and [issue #36629]. + /// + /// [RFC 2471]: https://github.com/rust-lang/rfcs/pull/2471#issuecomment-397414443 + /// [issue #36629]: https://github.com/rust-lang/rust/issues/36629 + UNNAMEABLE_TEST_ITEMS, + Warn, + "detects an item that cannot be named being marked as `#[test_case]`", + report_in_external_macro +} + +pub struct UnnameableTestItems { + boundary: Option<LocalDefId>, // Id of the item under which things are not nameable + items_nameable: bool, +} + +impl_lint_pass!(UnnameableTestItems => [UNNAMEABLE_TEST_ITEMS]); + +impl UnnameableTestItems { + pub fn new() -> Self { + Self { boundary: None, items_nameable: true } + } +} + +impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + if self.items_nameable { + if let hir::ItemKind::Mod(..) = it.kind { + } else { + self.items_nameable = false; + self.boundary = Some(it.def_id); + } + return; + } + + let attrs = cx.tcx.hir().attrs(it.hir_id()); + if let Some(attr) = cx.sess().find_by_name(attrs, sym::rustc_test_marker) { + cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, |lint| { + lint.build(fluent::lint::builtin_unnameable_test_items).emit(); + }); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'_>, it: &hir::Item<'_>) { + if !self.items_nameable && self.boundary == Some(it.def_id) { + self.items_nameable = true; + } + } +} + +declare_lint! { + /// The `keyword_idents` lint detects edition keywords being used as an + /// identifier. + /// + /// ### Example + /// + /// ```rust,edition2015,compile_fail + /// #![deny(keyword_idents)] + /// // edition 2015 + /// fn dyn() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Rust [editions] allow the language to evolve without breaking + /// backwards compatibility. This lint catches code that uses new keywords + /// that are added to the language that are used as identifiers (such as a + /// variable name, function name, etc.). If you switch the compiler to a + /// new edition without updating the code, then it will fail to compile if + /// you are using a new keyword as an identifier. + /// + /// You can manually change the identifiers to a non-keyword, or use a + /// [raw identifier], for example `r#dyn`, to transition to a new edition. + /// + /// This lint solves the problem automatically. It is "allow" by default + /// because the code is perfectly valid in older editions. The [`cargo + /// fix`] tool with the `--edition` flag will switch this lint to "warn" + /// and automatically apply the suggested fix from the compiler (which is + /// to use a raw identifier). This provides a completely automated way to + /// update old code for a new edition. + /// + /// [editions]: https://doc.rust-lang.org/edition-guide/ + /// [raw identifier]: https://doc.rust-lang.org/reference/identifiers.html + /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html + pub KEYWORD_IDENTS, + Allow, + "detects edition keywords being used as an identifier", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>", + reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018), + }; +} + +declare_lint_pass!( + /// Check for uses of edition keywords used as an identifier. + KeywordIdents => [KEYWORD_IDENTS] +); + +struct UnderMacro(bool); + +impl KeywordIdents { + fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: TokenStream) { + for tt in tokens.into_trees() { + match tt { + // Only report non-raw idents. + TokenTree::Token(token, _) => { + if let Some((ident, false)) = token.ident() { + self.check_ident_token(cx, UnderMacro(true), ident); + } + } + TokenTree::Delimited(_, _, tts) => self.check_tokens(cx, tts), + } + } + } + + fn check_ident_token( + &mut self, + cx: &EarlyContext<'_>, + UnderMacro(under_macro): UnderMacro, + ident: Ident, + ) { + let next_edition = match cx.sess().edition() { + Edition::Edition2015 => { + match ident.name { + kw::Async | kw::Await | kw::Try => Edition::Edition2018, + + // rust-lang/rust#56327: Conservatively do not + // attempt to report occurrences of `dyn` within + // macro definitions or invocations, because `dyn` + // can legitimately occur as a contextual keyword + // in 2015 code denoting its 2018 meaning, and we + // do not want rustfix to inject bugs into working + // code by rewriting such occurrences. + // + // But if we see `dyn` outside of a macro, we know + // its precise role in the parsed AST and thus are + // assured this is truly an attempt to use it as + // an identifier. + kw::Dyn if !under_macro => Edition::Edition2018, + + _ => return, + } + } + + // There are no new keywords yet for the 2018 edition and beyond. + _ => return, + }; + + // Don't lint `r#foo`. + if cx.sess().parse_sess.raw_identifier_spans.borrow().contains(&ident.span) { + return; + } + + cx.struct_span_lint(KEYWORD_IDENTS, ident.span, |lint| { + lint.build(fluent::lint::builtin_keyword_idents) + .set_arg("kw", ident.clone()) + .set_arg("next", next_edition) + .span_suggestion( + ident.span, + fluent::lint::suggestion, + format!("r#{}", ident), + Applicability::MachineApplicable, + ) + .emit(); + }); + } +} + +impl EarlyLintPass for KeywordIdents { + fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef, _id: ast::NodeId) { + self.check_tokens(cx, mac_def.body.inner_tokens()); + } + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { + self.check_tokens(cx, mac.args.inner_tokens()); + } + fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) { + self.check_ident_token(cx, UnderMacro(false), ident); + } +} + +declare_lint_pass!(ExplicitOutlivesRequirements => [EXPLICIT_OUTLIVES_REQUIREMENTS]); + +impl ExplicitOutlivesRequirements { + fn lifetimes_outliving_lifetime<'tcx>( + inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)], + index: u32, + ) -> Vec<ty::Region<'tcx>> { + inferred_outlives + .iter() + .filter_map(|(pred, _)| match pred.kind().skip_binder() { + ty::PredicateKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a { + ty::ReEarlyBound(ebr) if ebr.index == index => Some(b), + _ => None, + }, + _ => None, + }) + .collect() + } + + fn lifetimes_outliving_type<'tcx>( + inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)], + index: u32, + ) -> Vec<ty::Region<'tcx>> { + inferred_outlives + .iter() + .filter_map(|(pred, _)| match pred.kind().skip_binder() { + ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(a, b)) => { + a.is_param(index).then_some(b) + } + _ => None, + }) + .collect() + } + + fn collect_outlives_bound_spans<'tcx>( + &self, + tcx: TyCtxt<'tcx>, + bounds: &hir::GenericBounds<'_>, + inferred_outlives: &[ty::Region<'tcx>], + ) -> Vec<(usize, Span)> { + use rustc_middle::middle::resolve_lifetime::Region; + + bounds + .iter() + .enumerate() + .filter_map(|(i, bound)| { + if let hir::GenericBound::Outlives(lifetime) = bound { + let is_inferred = match tcx.named_region(lifetime.hir_id) { + Some(Region::EarlyBound(index, ..)) => inferred_outlives.iter().any(|r| { + if let ty::ReEarlyBound(ebr) = **r { ebr.index == index } else { false } + }), + _ => false, + }; + is_inferred.then_some((i, bound.span())) + } else { + None + } + }) + .filter(|(_, span)| !in_external_macro(tcx.sess, *span)) + .collect() + } + + fn consolidate_outlives_bound_spans( + &self, + lo: Span, + bounds: &hir::GenericBounds<'_>, + bound_spans: Vec<(usize, Span)>, + ) -> Vec<Span> { + if bounds.is_empty() { + return Vec::new(); + } + if bound_spans.len() == bounds.len() { + let (_, last_bound_span) = bound_spans[bound_spans.len() - 1]; + // If all bounds are inferable, we want to delete the colon, so + // start from just after the parameter (span passed as argument) + vec![lo.to(last_bound_span)] + } else { + let mut merged = Vec::new(); + let mut last_merged_i = None; + + let mut from_start = true; + for (i, bound_span) in bound_spans { + match last_merged_i { + // If the first bound is inferable, our span should also eat the leading `+`. + None if i == 0 => { + merged.push(bound_span.to(bounds[1].span().shrink_to_lo())); + last_merged_i = Some(0); + } + // If consecutive bounds are inferable, merge their spans + Some(h) if i == h + 1 => { + if let Some(tail) = merged.last_mut() { + // Also eat the trailing `+` if the first + // more-than-one bound is inferable + let to_span = if from_start && i < bounds.len() { + bounds[i + 1].span().shrink_to_lo() + } else { + bound_span + }; + *tail = tail.to(to_span); + last_merged_i = Some(i); + } else { + bug!("another bound-span visited earlier"); + } + } + _ => { + // When we find a non-inferable bound, subsequent inferable bounds + // won't be consecutive from the start (and we'll eat the leading + // `+` rather than the trailing one) + from_start = false; + merged.push(bounds[i - 1].span().shrink_to_hi().to(bound_span)); + last_merged_i = Some(i); + } + } + } + merged + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { + use rustc_middle::middle::resolve_lifetime::Region; + + let def_id = item.def_id; + if let hir::ItemKind::Struct(_, ref hir_generics) + | hir::ItemKind::Enum(_, ref hir_generics) + | hir::ItemKind::Union(_, ref hir_generics) = item.kind + { + let inferred_outlives = cx.tcx.inferred_outlives_of(def_id); + if inferred_outlives.is_empty() { + return; + } + + let ty_generics = cx.tcx.generics_of(def_id); + + let mut bound_count = 0; + let mut lint_spans = Vec::new(); + let mut where_lint_spans = Vec::new(); + let mut dropped_predicate_count = 0; + let num_predicates = hir_generics.predicates.len(); + for (i, where_predicate) in hir_generics.predicates.iter().enumerate() { + let (relevant_lifetimes, bounds, span, in_where_clause) = match where_predicate { + hir::WherePredicate::RegionPredicate(predicate) => { + if let Some(Region::EarlyBound(index, ..)) = + cx.tcx.named_region(predicate.lifetime.hir_id) + { + ( + Self::lifetimes_outliving_lifetime(inferred_outlives, index), + &predicate.bounds, + predicate.span, + predicate.in_where_clause, + ) + } else { + continue; + } + } + hir::WherePredicate::BoundPredicate(predicate) => { + // FIXME we can also infer bounds on associated types, + // and should check for them here. + match predicate.bounded_ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => { + let Res::Def(DefKind::TyParam, def_id) = path.res else { + continue + }; + let index = ty_generics.param_def_id_to_index[&def_id]; + ( + Self::lifetimes_outliving_type(inferred_outlives, index), + &predicate.bounds, + predicate.span, + predicate.origin == PredicateOrigin::WhereClause, + ) + } + _ => { + continue; + } + } + } + _ => continue, + }; + if relevant_lifetimes.is_empty() { + continue; + } + + let bound_spans = + self.collect_outlives_bound_spans(cx.tcx, bounds, &relevant_lifetimes); + bound_count += bound_spans.len(); + + let drop_predicate = bound_spans.len() == bounds.len(); + if drop_predicate { + dropped_predicate_count += 1; + } + + if drop_predicate && !in_where_clause { + lint_spans.push(span); + } else if drop_predicate && i + 1 < num_predicates { + // If all the bounds on a predicate were inferable and there are + // further predicates, we want to eat the trailing comma. + let next_predicate_span = hir_generics.predicates[i + 1].span(); + where_lint_spans.push(span.to(next_predicate_span.shrink_to_lo())); + } else { + where_lint_spans.extend(self.consolidate_outlives_bound_spans( + span.shrink_to_lo(), + bounds, + bound_spans, + )); + } + } + + // If all predicates are inferable, drop the entire clause + // (including the `where`) + if hir_generics.has_where_clause_predicates && dropped_predicate_count == num_predicates + { + let where_span = hir_generics.where_clause_span; + // Extend the where clause back to the closing `>` of the + // generics, except for tuple struct, which have the `where` + // after the fields of the struct. + let full_where_span = + if let hir::ItemKind::Struct(hir::VariantData::Tuple(..), _) = item.kind { + where_span + } else { + hir_generics.span.shrink_to_hi().to(where_span) + }; + lint_spans.push(full_where_span); + } else { + lint_spans.extend(where_lint_spans); + } + + if !lint_spans.is_empty() { + cx.struct_span_lint(EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), |lint| { + lint.build(fluent::lint::builtin_explicit_outlives) + .set_arg("count", bound_count) + .multipart_suggestion( + fluent::lint::suggestion, + lint_spans + .into_iter() + .map(|span| (span, String::new())) + .collect::<Vec<_>>(), + Applicability::MachineApplicable, + ) + .emit(); + }); + } + } + } +} + +declare_lint! { + /// The `incomplete_features` lint detects unstable features enabled with + /// the [`feature` attribute] that may function improperly in some or all + /// cases. + /// + /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/ + /// + /// ### Example + /// + /// ```rust + /// #![feature(generic_const_exprs)] + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Although it is encouraged for people to experiment with unstable + /// features, some of them are known to be incomplete or faulty. This lint + /// is a signal that the feature has not yet been finished, and you may + /// experience problems with it. + pub INCOMPLETE_FEATURES, + Warn, + "incomplete features that may function improperly in some or all cases" +} + +declare_lint_pass!( + /// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/active.rs`. + IncompleteFeatures => [INCOMPLETE_FEATURES] +); + +impl EarlyLintPass for IncompleteFeatures { + fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { + let features = cx.sess().features_untracked(); + features + .declared_lang_features + .iter() + .map(|(name, span, _)| (name, span)) + .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span))) + .filter(|(&name, _)| features.incomplete(name)) + .for_each(|(&name, &span)| { + cx.struct_span_lint(INCOMPLETE_FEATURES, span, |lint| { + let mut builder = lint.build(fluent::lint::builtin_incomplete_features); + builder.set_arg("name", name); + if let Some(n) = rustc_feature::find_feature_issue(name, GateIssue::Language) { + builder.set_arg("n", n); + builder.note(fluent::lint::note); + } + if HAS_MIN_FEATURES.contains(&name) { + builder.help(fluent::lint::help); + } + builder.emit(); + }) + }); + } +} + +const HAS_MIN_FEATURES: &[Symbol] = &[sym::specialization]; + +declare_lint! { + /// The `invalid_value` lint detects creating a value that is not valid, + /// such as a null reference. + /// + /// ### Example + /// + /// ```rust,no_run + /// # #![allow(unused)] + /// unsafe { + /// let x: &'static i32 = std::mem::zeroed(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In some situations the compiler can detect that the code is creating + /// an invalid value, which should be avoided. + /// + /// In particular, this lint will check for improper use of + /// [`mem::zeroed`], [`mem::uninitialized`], [`mem::transmute`], and + /// [`MaybeUninit::assume_init`] that can cause [undefined behavior]. The + /// lint should provide extra information to indicate what the problem is + /// and a possible solution. + /// + /// [`mem::zeroed`]: https://doc.rust-lang.org/std/mem/fn.zeroed.html + /// [`mem::uninitialized`]: https://doc.rust-lang.org/std/mem/fn.uninitialized.html + /// [`mem::transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html + /// [`MaybeUninit::assume_init`]: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.assume_init + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + pub INVALID_VALUE, + Warn, + "an invalid value is being created (such as a null reference)" +} + +declare_lint_pass!(InvalidValue => [INVALID_VALUE]); + +impl<'tcx> LateLintPass<'tcx> for InvalidValue { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { + #[derive(Debug, Copy, Clone, PartialEq)] + enum InitKind { + Zeroed, + Uninit, + } + + /// Information about why a type cannot be initialized this way. + /// Contains an error message and optionally a span to point at. + type InitError = (String, Option<Span>); + + /// Test if this constant is all-0. + fn is_zero(expr: &hir::Expr<'_>) -> bool { + use hir::ExprKind::*; + use rustc_ast::LitKind::*; + match &expr.kind { + Lit(lit) => { + if let Int(i, _) = lit.node { + i == 0 + } else { + false + } + } + Tup(tup) => tup.iter().all(is_zero), + _ => false, + } + } + + /// Determine if this expression is a "dangerous initialization". + fn is_dangerous_init(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<InitKind> { + if let hir::ExprKind::Call(ref path_expr, ref args) = expr.kind { + // Find calls to `mem::{uninitialized,zeroed}` methods. + if let hir::ExprKind::Path(ref qpath) = path_expr.kind { + let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?; + match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::mem_zeroed) => return Some(InitKind::Zeroed), + Some(sym::mem_uninitialized) => return Some(InitKind::Uninit), + Some(sym::transmute) if is_zero(&args[0]) => return Some(InitKind::Zeroed), + _ => {} + } + } + } else if let hir::ExprKind::MethodCall(_, ref args, _) = expr.kind { + // Find problematic calls to `MaybeUninit::assume_init`. + let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; + if cx.tcx.is_diagnostic_item(sym::assume_init, def_id) { + // This is a call to *some* method named `assume_init`. + // See if the `self` parameter is one of the dangerous constructors. + if let hir::ExprKind::Call(ref path_expr, _) = args[0].kind { + if let hir::ExprKind::Path(ref qpath) = path_expr.kind { + let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?; + match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::maybe_uninit_zeroed) => return Some(InitKind::Zeroed), + Some(sym::maybe_uninit_uninit) => return Some(InitKind::Uninit), + _ => {} + } + } + } + } + } + + None + } + + /// Test if this enum has several actually "existing" variants. + /// Zero-sized uninhabited variants do not always have a tag assigned and thus do not "exist". + fn is_multi_variant<'tcx>(adt: ty::AdtDef<'tcx>) -> bool { + // As an approximation, we only count dataless variants. Those are definitely inhabited. + let existing_variants = adt.variants().iter().filter(|v| v.fields.is_empty()).count(); + existing_variants > 1 + } + + /// Return `Some` only if we are sure this type does *not* + /// allow zero initialization. + fn ty_find_init_error<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + init: InitKind, + ) -> Option<InitError> { + use rustc_type_ir::sty::TyKind::*; + match ty.kind() { + // Primitive types that don't like 0 as a value. + Ref(..) => Some(("references must be non-null".to_string(), None)), + Adt(..) if ty.is_box() => Some(("`Box` must be non-null".to_string(), None)), + FnPtr(..) => Some(("function pointers must be non-null".to_string(), None)), + Never => Some(("the `!` type has no valid value".to_string(), None)), + RawPtr(tm) if matches!(tm.ty.kind(), Dynamic(..)) => + // raw ptr to dyn Trait + { + Some(("the vtable of a wide raw pointer must be non-null".to_string(), None)) + } + // Primitive types with other constraints. + Bool if init == InitKind::Uninit => { + Some(("booleans must be either `true` or `false`".to_string(), None)) + } + Char if init == InitKind::Uninit => { + Some(("characters must be a valid Unicode codepoint".to_string(), None)) + } + // Recurse and checks for some compound types. + Adt(adt_def, substs) if !adt_def.is_union() => { + // First check if this ADT has a layout attribute (like `NonNull` and friends). + use std::ops::Bound; + match cx.tcx.layout_scalar_valid_range(adt_def.did()) { + // We exploit here that `layout_scalar_valid_range` will never + // return `Bound::Excluded`. (And we have tests checking that we + // handle the attribute correctly.) + (Bound::Included(lo), _) if lo > 0 => { + return Some((format!("`{}` must be non-null", ty), None)); + } + (Bound::Included(_), _) | (_, Bound::Included(_)) + if init == InitKind::Uninit => + { + return Some(( + format!( + "`{}` must be initialized inside its custom valid range", + ty, + ), + None, + )); + } + _ => {} + } + // Now, recurse. + match adt_def.variants().len() { + 0 => Some(("enums with no variants have no valid value".to_string(), None)), + 1 => { + // Struct, or enum with exactly one variant. + // Proceed recursively, check all fields. + let variant = &adt_def.variant(VariantIdx::from_u32(0)); + variant.fields.iter().find_map(|field| { + ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map( + |(mut msg, span)| { + if span.is_none() { + // Point to this field, should be helpful for figuring + // out where the source of the error is. + let span = cx.tcx.def_span(field.did); + write!( + &mut msg, + " (in this {} field)", + adt_def.descr() + ) + .unwrap(); + (msg, Some(span)) + } else { + // Just forward. + (msg, span) + } + }, + ) + }) + } + // Multi-variant enum. + _ => { + if init == InitKind::Uninit && is_multi_variant(*adt_def) { + let span = cx.tcx.def_span(adt_def.did()); + Some(( + "enums have to be initialized to a variant".to_string(), + Some(span), + )) + } else { + // In principle, for zero-initialization we could figure out which variant corresponds + // to tag 0, and check that... but for now we just accept all zero-initializations. + None + } + } + } + } + Tuple(..) => { + // Proceed recursively, check all fields. + ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field, init)) + } + Array(ty, len) => { + if matches!(len.try_eval_usize(cx.tcx, cx.param_env), Some(v) if v > 0) { + // Array length known at array non-empty -- recurse. + ty_find_init_error(cx, *ty, init) + } else { + // Empty array or size unknown. + None + } + } + // Conservative fallback. + _ => None, + } + } + + if let Some(init) = is_dangerous_init(cx, expr) { + // This conjures an instance of a type out of nothing, + // using zeroed or uninitialized memory. + // We are extremely conservative with what we warn about. + let conjured_ty = cx.typeck_results().expr_ty(expr); + if let Some((msg, span)) = + with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init)) + { + // FIXME(davidtwco): make translatable + cx.struct_span_lint(INVALID_VALUE, expr.span, |lint| { + let mut err = lint.build(&format!( + "the type `{}` does not permit {}", + conjured_ty, + match init { + InitKind::Zeroed => "zero-initialization", + InitKind::Uninit => "being left uninitialized", + }, + )); + err.span_label(expr.span, "this code causes undefined behavior when executed"); + err.span_label( + expr.span, + "help: use `MaybeUninit<T>` instead, \ + and only call `assume_init` after initialization is done", + ); + if let Some(span) = span { + err.span_note(span, &msg); + } else { + err.note(&msg); + } + err.emit(); + }); + } + } + } +} + +declare_lint! { + /// The `clashing_extern_declarations` lint detects when an `extern fn` + /// has been declared with the same name but different types. + /// + /// ### Example + /// + /// ```rust + /// mod m { + /// extern "C" { + /// fn foo(); + /// } + /// } + /// + /// extern "C" { + /// fn foo(_: u32); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Because two symbols of the same name cannot be resolved to two + /// different functions at link time, and one function cannot possibly + /// have two types, a clashing extern declaration is almost certainly a + /// mistake. Check to make sure that the `extern` definitions are correct + /// and equivalent, and possibly consider unifying them in one location. + /// + /// This lint does not run between crates because a project may have + /// dependencies which both rely on the same extern function, but declare + /// it in a different (but valid) way. For example, they may both declare + /// an opaque type for one or more of the arguments (which would end up + /// distinct types), or use types that are valid conversions in the + /// language the `extern fn` is defined in. In these cases, the compiler + /// can't say that the clashing declaration is incorrect. + pub CLASHING_EXTERN_DECLARATIONS, + Warn, + "detects when an extern fn has been declared with the same name but different types" +} + +pub struct ClashingExternDeclarations { + /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls + /// contains an entry for key K, it means a symbol with name K has been seen by this lint and + /// the symbol should be reported as a clashing declaration. + // FIXME: Technically, we could just store a &'tcx str here without issue; however, the + // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime. + seen_decls: FxHashMap<Symbol, HirId>, +} + +/// Differentiate between whether the name for an extern decl came from the link_name attribute or +/// just from declaration itself. This is important because we don't want to report clashes on +/// symbol name if they don't actually clash because one or the other links against a symbol with a +/// different name. +enum SymbolName { + /// The name of the symbol + the span of the annotation which introduced the link name. + Link(Symbol, Span), + /// No link name, so just the name of the symbol. + Normal(Symbol), +} + +impl SymbolName { + fn get_name(&self) -> Symbol { + match self { + SymbolName::Link(s, _) | SymbolName::Normal(s) => *s, + } + } +} + +impl ClashingExternDeclarations { + pub(crate) fn new() -> Self { + ClashingExternDeclarations { seen_decls: FxHashMap::default() } + } + /// Insert a new foreign item into the seen set. If a symbol with the same name already exists + /// for the item, return its HirId without updating the set. + fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option<HirId> { + let did = fi.def_id.to_def_id(); + let instance = Instance::new(did, ty::List::identity_for_item(tcx, did)); + let name = Symbol::intern(tcx.symbol_name(instance).name); + if let Some(&hir_id) = self.seen_decls.get(&name) { + // Avoid updating the map with the new entry when we do find a collision. We want to + // make sure we're always pointing to the first definition as the previous declaration. + // This lets us avoid emitting "knock-on" diagnostics. + Some(hir_id) + } else { + self.seen_decls.insert(name, fi.hir_id()) + } + } + + /// Get the name of the symbol that's linked against for a given extern declaration. That is, + /// the name specified in a #[link_name = ...] attribute if one was specified, else, just the + /// symbol's name. + fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName { + if let Some((overridden_link_name, overridden_link_name_span)) = + tcx.codegen_fn_attrs(fi.def_id).link_name.map(|overridden_link_name| { + // FIXME: Instead of searching through the attributes again to get span + // information, we could have codegen_fn_attrs also give span information back for + // where the attribute was defined. However, until this is found to be a + // bottleneck, this does just fine. + ( + overridden_link_name, + tcx.get_attr(fi.def_id.to_def_id(), sym::link_name).unwrap().span, + ) + }) + { + SymbolName::Link(overridden_link_name, overridden_link_name_span) + } else { + SymbolName::Normal(fi.ident.name) + } + } + + /// Checks whether two types are structurally the same enough that the declarations shouldn't + /// clash. We need this so we don't emit a lint when two modules both declare an extern struct, + /// with the same members (as the declarations shouldn't clash). + fn structurally_same_type<'tcx>( + cx: &LateContext<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: CItemKind, + ) -> bool { + fn structurally_same_type_impl<'tcx>( + seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>, + cx: &LateContext<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: CItemKind, + ) -> bool { + debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b); + let tcx = cx.tcx; + + // Given a transparent newtype, reach through and grab the inner + // type unless the newtype makes the type non-null. + let non_transparent_ty = |ty: Ty<'tcx>| -> Ty<'tcx> { + let mut ty = ty; + loop { + if let ty::Adt(def, substs) = *ty.kind() { + let is_transparent = def.repr().transparent(); + let is_non_null = crate::types::nonnull_optimization_guaranteed(tcx, def); + debug!( + "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}", + ty, is_transparent, is_non_null + ); + if is_transparent && !is_non_null { + debug_assert!(def.variants().len() == 1); + let v = &def.variant(VariantIdx::new(0)); + ty = transparent_newtype_field(tcx, v) + .expect( + "single-variant transparent structure with zero-sized field", + ) + .ty(tcx, substs); + continue; + } + } + debug!("non_transparent_ty -> {:?}", ty); + return ty; + } + }; + + let a = non_transparent_ty(a); + let b = non_transparent_ty(b); + + if !seen_types.insert((a, b)) { + // We've encountered a cycle. There's no point going any further -- the types are + // structurally the same. + return true; + } + let tcx = cx.tcx; + if a == b { + // All nominally-same types are structurally same, too. + true + } else { + // Do a full, depth-first comparison between the two. + use rustc_type_ir::sty::TyKind::*; + let a_kind = a.kind(); + let b_kind = b.kind(); + + let compare_layouts = |a, b| -> Result<bool, LayoutError<'tcx>> { + debug!("compare_layouts({:?}, {:?})", a, b); + let a_layout = &cx.layout_of(a)?.layout.abi(); + let b_layout = &cx.layout_of(b)?.layout.abi(); + debug!( + "comparing layouts: {:?} == {:?} = {}", + a_layout, + b_layout, + a_layout == b_layout + ); + Ok(a_layout == b_layout) + }; + + #[allow(rustc::usage_of_ty_tykind)] + let is_primitive_or_pointer = |kind: &ty::TyKind<'_>| { + kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..)) + }; + + ensure_sufficient_stack(|| { + match (a_kind, b_kind) { + (Adt(a_def, _), Adt(b_def, _)) => { + // We can immediately rule out these types as structurally same if + // their layouts differ. + match compare_layouts(a, b) { + Ok(false) => return false, + _ => (), // otherwise, continue onto the full, fields comparison + } + + // Grab a flattened representation of all fields. + let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter()); + let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter()); + + // Perform a structural comparison for each field. + a_fields.eq_by( + b_fields, + |&ty::FieldDef { did: a_did, .. }, + &ty::FieldDef { did: b_did, .. }| { + structurally_same_type_impl( + seen_types, + cx, + tcx.type_of(a_did), + tcx.type_of(b_did), + ckind, + ) + }, + ) + } + (Array(a_ty, a_const), Array(b_ty, b_const)) => { + // For arrays, we also check the constness of the type. + a_const.kind() == b_const.kind() + && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) + } + (Slice(a_ty), Slice(b_ty)) => { + structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) + } + (RawPtr(a_tymut), RawPtr(b_tymut)) => { + a_tymut.mutbl == b_tymut.mutbl + && structurally_same_type_impl( + seen_types, cx, a_tymut.ty, b_tymut.ty, ckind, + ) + } + (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => { + // For structural sameness, we don't need the region to be same. + a_mut == b_mut + && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) + } + (FnDef(..), FnDef(..)) => { + let a_poly_sig = a.fn_sig(tcx); + let b_poly_sig = b.fn_sig(tcx); + + // We don't compare regions, but leaving bound regions around ICEs, so + // we erase them. + let a_sig = tcx.erase_late_bound_regions(a_poly_sig); + let b_sig = tcx.erase_late_bound_regions(b_poly_sig); + + (a_sig.abi, a_sig.unsafety, a_sig.c_variadic) + == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic) + && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { + structurally_same_type_impl(seen_types, cx, *a, *b, ckind) + }) + && structurally_same_type_impl( + seen_types, + cx, + a_sig.output(), + b_sig.output(), + ckind, + ) + } + (Tuple(a_substs), Tuple(b_substs)) => { + a_substs.iter().eq_by(b_substs.iter(), |a_ty, b_ty| { + structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind) + }) + } + // For these, it's not quite as easy to define structural-sameness quite so easily. + // For the purposes of this lint, take the conservative approach and mark them as + // not structurally same. + (Dynamic(..), Dynamic(..)) + | (Error(..), Error(..)) + | (Closure(..), Closure(..)) + | (Generator(..), Generator(..)) + | (GeneratorWitness(..), GeneratorWitness(..)) + | (Projection(..), Projection(..)) + | (Opaque(..), Opaque(..)) => false, + + // These definitely should have been caught above. + (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(), + + // An Adt and a primitive or pointer type. This can be FFI-safe if non-null + // enum layout optimisation is being applied. + (Adt(..), other_kind) | (other_kind, Adt(..)) + if is_primitive_or_pointer(other_kind) => + { + let (primitive, adt) = + if is_primitive_or_pointer(a.kind()) { (a, b) } else { (b, a) }; + if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) { + ty == primitive + } else { + compare_layouts(a, b).unwrap_or(false) + } + } + // Otherwise, just compare the layouts. This may fail to lint for some + // incompatible types, but at the very least, will stop reads into + // uninitialised memory. + _ => compare_layouts(a, b).unwrap_or(false), + } + }) + } + } + let mut seen_types = FxHashSet::default(); + structurally_same_type_impl(&mut seen_types, cx, a, b, ckind) + } +} + +impl_lint_pass!(ClashingExternDeclarations => [CLASHING_EXTERN_DECLARATIONS]); + +impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations { + fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, this_fi: &hir::ForeignItem<'_>) { + trace!("ClashingExternDeclarations: check_foreign_item: {:?}", this_fi); + if let ForeignItemKind::Fn(..) = this_fi.kind { + let tcx = cx.tcx; + if let Some(existing_hid) = self.insert(tcx, this_fi) { + let existing_decl_ty = tcx.type_of(tcx.hir().local_def_id(existing_hid)); + let this_decl_ty = tcx.type_of(this_fi.def_id); + debug!( + "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}", + existing_hid, existing_decl_ty, this_fi.def_id, this_decl_ty + ); + // Check that the declarations match. + if !Self::structurally_same_type( + cx, + existing_decl_ty, + this_decl_ty, + CItemKind::Declaration, + ) { + let orig_fi = tcx.hir().expect_foreign_item(existing_hid.expect_owner()); + let orig = Self::name_of_extern_decl(tcx, orig_fi); + + // We want to ensure that we use spans for both decls that include where the + // name was defined, whether that was from the link_name attribute or not. + let get_relevant_span = + |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) { + SymbolName::Normal(_) => fi.span, + SymbolName::Link(_, annot_span) => fi.span.to(annot_span), + }; + // Finally, emit the diagnostic. + tcx.struct_span_lint_hir( + CLASHING_EXTERN_DECLARATIONS, + this_fi.hir_id(), + get_relevant_span(this_fi), + |lint| { + let mut expected_str = DiagnosticStyledString::new(); + expected_str.push(existing_decl_ty.fn_sig(tcx).to_string(), false); + let mut found_str = DiagnosticStyledString::new(); + found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true); + + lint.build(if orig.get_name() == this_fi.ident.name { + fluent::lint::builtin_clashing_extern_same_name + } else { + fluent::lint::builtin_clashing_extern_diff_name + }) + .set_arg("this_fi", this_fi.ident.name) + .set_arg("orig", orig.get_name()) + .span_label( + get_relevant_span(orig_fi), + fluent::lint::previous_decl_label, + ) + .span_label(get_relevant_span(this_fi), fluent::lint::mismatch_label) + // FIXME(davidtwco): translatable expected/found + .note_expected_found(&"", expected_str, &"", found_str) + .emit(); + }, + ); + } + } + } + } +} + +declare_lint! { + /// The `deref_nullptr` lint detects when an null pointer is dereferenced, + /// which causes [undefined behavior]. + /// + /// ### Example + /// + /// ```rust,no_run + /// # #![allow(unused)] + /// use std::ptr; + /// unsafe { + /// let x = &*ptr::null::<i32>(); + /// let x = ptr::addr_of!(*ptr::null::<i32>()); + /// let x = *(0 as *const i32); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Dereferencing a null pointer causes [undefined behavior] even as a place expression, + /// like `&*(0 as *const i32)` or `addr_of!(*(0 as *const i32))`. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + pub DEREF_NULLPTR, + Warn, + "detects when an null pointer is dereferenced" +} + +declare_lint_pass!(DerefNullPtr => [DEREF_NULLPTR]); + +impl<'tcx> LateLintPass<'tcx> for DerefNullPtr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { + /// test if expression is a null ptr + fn is_null_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + match &expr.kind { + rustc_hir::ExprKind::Cast(ref expr, ref ty) => { + if let rustc_hir::TyKind::Ptr(_) = ty.kind { + return is_zero(expr) || is_null_ptr(cx, expr); + } + } + // check for call to `core::ptr::null` or `core::ptr::null_mut` + rustc_hir::ExprKind::Call(ref path, _) => { + if let rustc_hir::ExprKind::Path(ref qpath) = path.kind { + if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() { + return matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_null | sym::ptr_null_mut) + ); + } + } + } + _ => {} + } + false + } + + /// test if expression is the literal `0` + fn is_zero(expr: &hir::Expr<'_>) -> bool { + match &expr.kind { + rustc_hir::ExprKind::Lit(ref lit) => { + if let LitKind::Int(a, _) = lit.node { + return a == 0; + } + } + _ => {} + } + false + } + + if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind { + if is_null_ptr(cx, expr_deref) { + cx.struct_span_lint(DEREF_NULLPTR, expr.span, |lint| { + let mut err = lint.build(fluent::lint::builtin_deref_nullptr); + err.span_label(expr.span, fluent::lint::label); + err.emit(); + }); + } + } + } +} + +declare_lint! { + /// The `named_asm_labels` lint detects the use of named labels in the + /// inline `asm!` macro. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// use std::arch::asm; + /// + /// fn main() { + /// unsafe { + /// asm!("foo: bar"); + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// LLVM is allowed to duplicate inline assembly blocks for any + /// reason, for example when it is in a function that gets inlined. Because + /// of this, GNU assembler [local labels] *must* be used instead of labels + /// with a name. Using named labels might cause assembler or linker errors. + /// + /// See the explanation in [Rust By Example] for more details. + /// + /// [local labels]: https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels + /// [Rust By Example]: https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels + pub NAMED_ASM_LABELS, + Deny, + "named labels in inline assembly", +} + +declare_lint_pass!(NamedAsmLabels => [NAMED_ASM_LABELS]); + +impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + if let hir::Expr { + kind: hir::ExprKind::InlineAsm(hir::InlineAsm { template_strs, .. }), + .. + } = expr + { + for (template_sym, template_snippet, template_span) in template_strs.iter() { + let template_str = template_sym.as_str(); + let find_label_span = |needle: &str| -> Option<Span> { + if let Some(template_snippet) = template_snippet { + let snippet = template_snippet.as_str(); + if let Some(pos) = snippet.find(needle) { + let end = pos + + snippet[pos..] + .find(|c| c == ':') + .unwrap_or(snippet[pos..].len() - 1); + let inner = InnerSpan::new(pos, end); + return Some(template_span.from_inner(inner)); + } + } + + None + }; + + let mut found_labels = Vec::new(); + + // A semicolon might not actually be specified as a separator for all targets, but it seems like LLVM accepts it always + let statements = template_str.split(|c| matches!(c, '\n' | ';')); + for statement in statements { + // If there's a comment, trim it from the statement + let statement = statement.find("//").map_or(statement, |idx| &statement[..idx]); + let mut start_idx = 0; + for (idx, _) in statement.match_indices(':') { + let possible_label = statement[start_idx..idx].trim(); + let mut chars = possible_label.chars(); + let Some(c) = chars.next() else { + // Empty string means a leading ':' in this section, which is not a label + break + }; + // A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $ + if (c.is_alphabetic() || matches!(c, '.' | '_')) + && chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '$')) + { + found_labels.push(possible_label); + } else { + // If we encounter a non-label, there cannot be any further labels, so stop checking + break; + } + + start_idx = idx + 1; + } + } + + debug!("NamedAsmLabels::check_expr(): found_labels: {:#?}", &found_labels); + + if found_labels.len() > 0 { + let spans = found_labels + .into_iter() + .filter_map(|label| find_label_span(label)) + .collect::<Vec<Span>>(); + // If there were labels but we couldn't find a span, combine the warnings and use the template span + let target_spans: MultiSpan = + if spans.len() > 0 { spans.into() } else { (*template_span).into() }; + + cx.lookup_with_diagnostics( + NAMED_ASM_LABELS, + Some(target_spans), + |diag| { + diag.build(fluent::lint::builtin_asm_labels).emit(); + }, + BuiltinLintDiagnostics::NamedAsmLabel( + "only local labels of the form `<number>:` should be used in inline asm" + .to_string(), + ), + ); + } + } + } + } +} diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs new file mode 100644 index 000000000..b95fc341d --- /dev/null +++ b/compiler/rustc_lint/src/context.rs @@ -0,0 +1,1259 @@ +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs +//! after all other analyses. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an ID of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used. +//! A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use self::TargetLint::*; + +use crate::levels::LintLevelsBuilder; +use crate::passes::{EarlyLintPassObject, LateLintPassObject}; +use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sync; +use rustc_errors::{add_elided_lifetime_in_path_suggestion, struct_span_err}; +use rustc_errors::{ + Applicability, DecorateLint, LintDiagnosticBuilder, MultiSpan, SuggestionStyle, +}; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::def_id::{CrateNum, DefId}; +use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; +use rustc_middle::middle::privacy::AccessLevels; +use rustc_middle::middle::stability; +use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, Ty, TyCtxt}; +use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId}; +use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; +use rustc_session::Session; +use rustc_span::lev_distance::find_best_match_for_name; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::{BytePos, Span, DUMMY_SP}; +use rustc_target::abi; +use tracing::debug; + +use std::cell::Cell; +use std::iter; +use std::slice; + +/// Information about the registered lints. +/// +/// This is basically the subset of `Context` that we can +/// build early in the compile pipeline. +pub struct LintStore { + /// Registered lints. + lints: Vec<&'static Lint>, + + /// Constructor functions for each variety of lint pass. + /// + /// These should only be called once, but since we want to avoid locks or + /// interior mutability, we don't enforce this (and lints should, in theory, + /// be compatible with being constructed more than once, though not + /// necessarily in a sane manner. This is safe though.) + pub pre_expansion_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>, + pub early_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>, + pub late_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>, + /// This is unique in that we construct them per-module, so not once. + pub late_module_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>, + + /// Lints indexed by name. + by_name: FxHashMap<String, TargetLint>, + + /// Map of registered lint groups to what lints they expand to. + lint_groups: FxHashMap<&'static str, LintGroup>, +} + +/// The target of the `by_name` map, which accounts for renaming/deprecation. +#[derive(Debug)] +enum TargetLint { + /// A direct lint target + Id(LintId), + + /// Temporary renaming, used for easing migration pain; see #16545 + Renamed(String, LintId), + + /// Lint with this name existed previously, but has been removed/deprecated. + /// The string argument is the reason for removal. + Removed(String), + + /// A lint name that should give no warnings and have no effect. + /// + /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers them as tool lints. + Ignored, +} + +pub enum FindLintError { + NotFound, + Removed, +} + +struct LintAlias { + name: &'static str, + /// Whether deprecation warnings should be suppressed for this alias. + silent: bool, +} + +struct LintGroup { + lint_ids: Vec<LintId>, + from_plugin: bool, + depr: Option<LintAlias>, +} + +#[derive(Debug)] +pub enum CheckLintNameResult<'a> { + Ok(&'a [LintId]), + /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. + NoLint(Option<Symbol>), + /// The lint refers to a tool that has not been registered. + NoTool, + /// The lint is either renamed or removed. This is the warning + /// message, and an optional new name (`None` if removed). + Warning(String, Option<String>), + /// The lint is from a tool. If the Option is None, then either + /// the lint does not exist in the tool or the code was not + /// compiled with the tool and therefore the lint was never + /// added to the `LintStore`. Otherwise the `LintId` will be + /// returned as if it where a rustc lint. + Tool(Result<&'a [LintId], (Option<&'a [LintId]>, String)>), +} + +impl LintStore { + pub fn new() -> LintStore { + LintStore { + lints: vec![], + pre_expansion_passes: vec![], + early_passes: vec![], + late_passes: vec![], + late_module_passes: vec![], + by_name: Default::default(), + lint_groups: Default::default(), + } + } + + pub fn get_lints<'t>(&'t self) -> &'t [&'static Lint] { + &self.lints + } + + pub fn get_lint_groups<'t>( + &'t self, + ) -> impl Iterator<Item = (&'static str, Vec<LintId>, bool)> + 't { + // This function is not used in a way which observes the order of lints. + #[allow(rustc::potential_query_instability)] + self.lint_groups + .iter() + .filter(|(_, LintGroup { depr, .. })| { + // Don't display deprecated lint groups. + depr.is_none() + }) + .map(|(k, LintGroup { lint_ids, from_plugin, .. })| { + (*k, lint_ids.clone(), *from_plugin) + }) + } + + pub fn register_early_pass( + &mut self, + pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.early_passes.push(Box::new(pass)); + } + + /// This lint pass is softly deprecated. It misses expanded code and has caused a few + /// errors in the past. Currently, it is only used in Clippy. New implementations + /// should avoid using this interface, as it might be removed in the future. + /// + /// * See [rust#69838](https://github.com/rust-lang/rust/pull/69838) + /// * See [rust-clippy#5518](https://github.com/rust-lang/rust-clippy/pull/5518) + pub fn register_pre_expansion_pass( + &mut self, + pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.pre_expansion_passes.push(Box::new(pass)); + } + + pub fn register_late_pass( + &mut self, + pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.late_passes.push(Box::new(pass)); + } + + pub fn register_late_mod_pass( + &mut self, + pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync, + ) { + self.late_module_passes.push(Box::new(pass)); + } + + // Helper method for register_early/late_pass + pub fn register_lints(&mut self, lints: &[&'static Lint]) { + for lint in lints { + self.lints.push(lint); + + let id = LintId::of(lint); + if self.by_name.insert(lint.name_lower(), Id(id)).is_some() { + bug!("duplicate specification of lint {}", lint.name_lower()) + } + + if let Some(FutureIncompatibleInfo { reason, .. }) = lint.future_incompatible { + if let Some(edition) = reason.edition() { + self.lint_groups + .entry(edition.lint_name()) + .or_insert(LintGroup { + lint_ids: vec![], + from_plugin: lint.is_plugin, + depr: None, + }) + .lint_ids + .push(id); + } else { + // Lints belonging to the `future_incompatible` lint group are lints where a + // future version of rustc will cause existing code to stop compiling. + // Lints tied to an edition don't count because they are opt-in. + self.lint_groups + .entry("future_incompatible") + .or_insert(LintGroup { + lint_ids: vec![], + from_plugin: lint.is_plugin, + depr: None, + }) + .lint_ids + .push(id); + } + } + } + } + + pub fn register_group_alias(&mut self, lint_name: &'static str, alias: &'static str) { + self.lint_groups.insert( + alias, + LintGroup { + lint_ids: vec![], + from_plugin: false, + depr: Some(LintAlias { name: lint_name, silent: true }), + }, + ); + } + + pub fn register_group( + &mut self, + from_plugin: bool, + name: &'static str, + deprecated_name: Option<&'static str>, + to: Vec<LintId>, + ) { + let new = self + .lint_groups + .insert(name, LintGroup { lint_ids: to, from_plugin, depr: None }) + .is_none(); + if let Some(deprecated) = deprecated_name { + self.lint_groups.insert( + deprecated, + LintGroup { + lint_ids: vec![], + from_plugin, + depr: Some(LintAlias { name, silent: false }), + }, + ); + } + + if !new { + bug!("duplicate specification of lint group {}", name); + } + } + + /// This lint should give no warning and have no effect. + /// + /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers them as tool lints. + #[track_caller] + pub fn register_ignored(&mut self, name: &str) { + if self.by_name.insert(name.to_string(), Ignored).is_some() { + bug!("duplicate specification of lint {}", name); + } + } + + /// This lint has been renamed; warn about using the new name and apply the lint. + #[track_caller] + pub fn register_renamed(&mut self, old_name: &str, new_name: &str) { + let Some(&Id(target)) = self.by_name.get(new_name) else { + bug!("invalid lint renaming of {} to {}", old_name, new_name); + }; + self.by_name.insert(old_name.to_string(), Renamed(new_name.to_string(), target)); + } + + pub fn register_removed(&mut self, name: &str, reason: &str) { + self.by_name.insert(name.into(), Removed(reason.into())); + } + + pub fn find_lints(&self, mut lint_name: &str) -> Result<Vec<LintId>, FindLintError> { + match self.by_name.get(lint_name) { + Some(&Id(lint_id)) => Ok(vec![lint_id]), + Some(&Renamed(_, lint_id)) => Ok(vec![lint_id]), + Some(&Removed(_)) => Err(FindLintError::Removed), + Some(&Ignored) => Ok(vec![]), + None => loop { + return match self.lint_groups.get(lint_name) { + Some(LintGroup { lint_ids, depr, .. }) => { + if let Some(LintAlias { name, .. }) = depr { + lint_name = name; + continue; + } + Ok(lint_ids.clone()) + } + None => Err(FindLintError::Removed), + }; + }, + } + } + + /// Checks the validity of lint names derived from the command line. + pub fn check_lint_name_cmdline( + &self, + sess: &Session, + lint_name: &str, + level: Level, + registered_tools: &RegisteredTools, + ) { + let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name); + if lint_name_only == crate::WARNINGS.name_lower() && matches!(level, Level::ForceWarn(_)) { + struct_span_err!( + sess, + DUMMY_SP, + E0602, + "`{}` lint group is not supported with ´--force-warn´", + crate::WARNINGS.name_lower() + ) + .emit(); + return; + } + let db = match self.check_lint_name(lint_name_only, tool_name, registered_tools) { + CheckLintNameResult::Ok(_) => None, + CheckLintNameResult::Warning(ref msg, _) => Some(sess.struct_warn(msg)), + CheckLintNameResult::NoLint(suggestion) => { + let mut err = + struct_span_err!(sess, DUMMY_SP, E0602, "unknown lint: `{}`", lint_name); + + if let Some(suggestion) = suggestion { + err.help(&format!("did you mean: `{}`", suggestion)); + } + + Some(err.forget_guarantee()) + } + CheckLintNameResult::Tool(result) => match result { + Err((Some(_), new_name)) => Some(sess.struct_warn(&format!( + "lint name `{}` is deprecated \ + and does not have an effect anymore. \ + Use: {}", + lint_name, new_name + ))), + _ => None, + }, + CheckLintNameResult::NoTool => Some( + struct_span_err!( + sess, + DUMMY_SP, + E0602, + "unknown lint tool: `{}`", + tool_name.unwrap() + ) + .forget_guarantee(), + ), + }; + + if let Some(mut db) = db { + let msg = format!( + "requested on the command line with `{} {}`", + match level { + Level::Allow => "-A", + Level::Warn => "-W", + Level::ForceWarn(_) => "--force-warn", + Level::Deny => "-D", + Level::Forbid => "-F", + Level::Expect(_) => { + unreachable!("lints with the level of `expect` should not run this code"); + } + }, + lint_name + ); + db.note(&msg); + db.emit(); + } + } + + /// True if this symbol represents a lint group name. + pub fn is_lint_group(&self, lint_name: Symbol) -> bool { + debug!( + "is_lint_group(lint_name={:?}, lint_groups={:?})", + lint_name, + self.lint_groups.keys().collect::<Vec<_>>() + ); + let lint_name_str = lint_name.as_str(); + self.lint_groups.contains_key(lint_name_str) || { + let warnings_name_str = crate::WARNINGS.name_lower(); + lint_name_str == warnings_name_str + } + } + + /// Checks the name of a lint for its existence, and whether it was + /// renamed or removed. Generates a DiagnosticBuilder containing a + /// warning for renamed and removed lints. This is over both lint + /// names from attributes and those passed on the command line. Since + /// it emits non-fatal warnings and there are *two* lint passes that + /// inspect attributes, this is only run from the late pass to avoid + /// printing duplicate warnings. + pub fn check_lint_name( + &self, + lint_name: &str, + tool_name: Option<Symbol>, + registered_tools: &RegisteredTools, + ) -> CheckLintNameResult<'_> { + if let Some(tool_name) = tool_name { + // FIXME: rustc and rustdoc are considered tools for lints, but not for attributes. + if tool_name != sym::rustc + && tool_name != sym::rustdoc + && !registered_tools.contains(&Ident::with_dummy_span(tool_name)) + { + return CheckLintNameResult::NoTool; + } + } + + let complete_name = if let Some(tool_name) = tool_name { + format!("{}::{}", tool_name, lint_name) + } else { + lint_name.to_string() + }; + // If the lint was scoped with `tool::` check if the tool lint exists + if let Some(tool_name) = tool_name { + match self.by_name.get(&complete_name) { + None => match self.lint_groups.get(&*complete_name) { + // If the lint isn't registered, there are two possibilities: + None => { + // 1. The tool is currently running, so this lint really doesn't exist. + // FIXME: should this handle tools that never register a lint, like rustfmt? + tracing::debug!("lints={:?}", self.by_name.keys().collect::<Vec<_>>()); + let tool_prefix = format!("{}::", tool_name); + return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) { + self.no_lint_suggestion(&complete_name) + } else { + // 2. The tool isn't currently running, so no lints will be registered. + // To avoid giving a false positive, ignore all unknown lints. + CheckLintNameResult::Tool(Err((None, String::new()))) + }; + } + Some(LintGroup { lint_ids, .. }) => { + return CheckLintNameResult::Tool(Ok(&lint_ids)); + } + }, + Some(&Id(ref id)) => return CheckLintNameResult::Tool(Ok(slice::from_ref(id))), + // If the lint was registered as removed or renamed by the lint tool, we don't need + // to treat tool_lints and rustc lints different and can use the code below. + _ => {} + } + } + match self.by_name.get(&complete_name) { + Some(&Renamed(ref new_name, _)) => CheckLintNameResult::Warning( + format!("lint `{}` has been renamed to `{}`", complete_name, new_name), + Some(new_name.to_owned()), + ), + Some(&Removed(ref reason)) => CheckLintNameResult::Warning( + format!("lint `{}` has been removed: {}", complete_name, reason), + None, + ), + None => match self.lint_groups.get(&*complete_name) { + // If neither the lint, nor the lint group exists check if there is a `clippy::` + // variant of this lint + None => self.check_tool_name_for_backwards_compat(&complete_name, "clippy"), + Some(LintGroup { lint_ids, depr, .. }) => { + // Check if the lint group name is deprecated + if let Some(LintAlias { name, silent }) = depr { + let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); + return if *silent { + CheckLintNameResult::Ok(&lint_ids) + } else { + CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string()))) + }; + } + CheckLintNameResult::Ok(&lint_ids) + } + }, + Some(&Id(ref id)) => CheckLintNameResult::Ok(slice::from_ref(id)), + Some(&Ignored) => CheckLintNameResult::Ok(&[]), + } + } + + fn no_lint_suggestion(&self, lint_name: &str) -> CheckLintNameResult<'_> { + let name_lower = lint_name.to_lowercase(); + + if lint_name.chars().any(char::is_uppercase) && self.find_lints(&name_lower).is_ok() { + // First check if the lint name is (partly) in upper case instead of lower case... + return CheckLintNameResult::NoLint(Some(Symbol::intern(&name_lower))); + } + // ...if not, search for lints with a similar name + let groups = self.lint_groups.keys().copied().map(Symbol::intern); + let lints = self.lints.iter().map(|l| Symbol::intern(&l.name_lower())); + let names: Vec<Symbol> = groups.chain(lints).collect(); + let suggestion = find_best_match_for_name(&names, Symbol::intern(&name_lower), None); + CheckLintNameResult::NoLint(suggestion) + } + + fn check_tool_name_for_backwards_compat( + &self, + lint_name: &str, + tool_name: &str, + ) -> CheckLintNameResult<'_> { + let complete_name = format!("{}::{}", tool_name, lint_name); + match self.by_name.get(&complete_name) { + None => match self.lint_groups.get(&*complete_name) { + // Now we are sure, that this lint exists nowhere + None => self.no_lint_suggestion(lint_name), + Some(LintGroup { lint_ids, depr, .. }) => { + // Reaching this would be weird, but let's cover this case anyway + if let Some(LintAlias { name, silent }) = depr { + let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); + return if *silent { + CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name))) + } else { + CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string()))) + }; + } + CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name))) + } + }, + Some(&Id(ref id)) => { + CheckLintNameResult::Tool(Err((Some(slice::from_ref(id)), complete_name))) + } + Some(other) => { + tracing::debug!("got renamed lint {:?}", other); + CheckLintNameResult::NoLint(None) + } + } + } +} + +/// Context for lint checking outside of type inference. +pub struct LateContext<'tcx> { + /// Type context we're checking in. + pub tcx: TyCtxt<'tcx>, + + /// Current body, or `None` if outside a body. + pub enclosing_body: Option<hir::BodyId>, + + /// Type-checking results for the current body. Access using the `typeck_results` + /// and `maybe_typeck_results` methods, which handle querying the typeck results on demand. + // FIXME(eddyb) move all the code accessing internal fields like this, + // to this module, to avoid exposing it to lint logic. + pub(super) cached_typeck_results: Cell<Option<&'tcx ty::TypeckResults<'tcx>>>, + + /// Parameter environment for the item we are in. + pub param_env: ty::ParamEnv<'tcx>, + + /// Items accessible from the crate being checked. + pub access_levels: &'tcx AccessLevels, + + /// The store of registered lints and the lint levels. + pub lint_store: &'tcx LintStore, + + pub last_node_with_lint_attrs: hir::HirId, + + /// Generic type parameters in scope for the item we are in. + pub generics: Option<&'tcx hir::Generics<'tcx>>, + + /// We are only looking at one module + pub only_module: bool, +} + +/// Context for lint checking of the AST, after expansion, before lowering to HIR. +pub struct EarlyContext<'a> { + pub builder: LintLevelsBuilder<'a>, + pub buffered: LintBuffer, +} + +pub trait LintPassObject: Sized {} + +impl LintPassObject for EarlyLintPassObject {} + +impl LintPassObject for LateLintPassObject {} + +pub trait LintContext: Sized { + type PassObject: LintPassObject; + + fn sess(&self) -> &Session; + fn lints(&self) -> &LintStore; + + fn lookup_with_diagnostics( + &self, + lint: &'static Lint, + span: Option<impl Into<MultiSpan>>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + diagnostic: BuiltinLintDiagnostics, + ) { + self.lookup(lint, span, |lint| { + // We first generate a blank diagnostic. + let mut db = lint.build(""); + + // Now, set up surrounding context. + let sess = self.sess(); + match diagnostic { + BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => { + let spans: Vec<_> = content + .char_indices() + .filter_map(|(i, c)| { + TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { + let lo = span.lo() + BytePos(2 + i as u32); + (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) + }) + }) + .collect(); + let (an, s) = match spans.len() { + 1 => ("an ", ""), + _ => ("", "s"), + }; + db.span_label(span, &format!( + "this comment contains {}invisible unicode text flow control codepoint{}", + an, + s, + )); + for (c, span) in &spans { + db.span_label(*span, format!("{:?}", c)); + } + db.note( + "these kind of unicode codepoints change the way text flows on \ + applications that support them, but can cause confusion because they \ + change the order of characters on the screen", + ); + if !spans.is_empty() { + db.multipart_suggestion_with_style( + "if their presence wasn't intentional, you can remove them", + spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(), + Applicability::MachineApplicable, + SuggestionStyle::HideCodeAlways, + ); + } + }, + BuiltinLintDiagnostics::Normal => (), + BuiltinLintDiagnostics::AbsPathWithModule(span) => { + let (sugg, app) = match sess.source_map().span_to_snippet(span) { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = + if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{}{}", opt_colon, s), Applicability::MachineApplicable) + } + Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders), + }; + db.span_suggestion(span, "use `crate`", sugg, app); + } + BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => { + db.span_label( + span, + "names from parent modules are not accessible without an explicit import", + ); + } + BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths( + span_def, + ) => { + db.span_note(span_def, "the macro is defined here"); + } + BuiltinLintDiagnostics::ElidedLifetimesInPaths( + n, + path_span, + incl_angl_brckt, + insertion_span, + ) => { + add_elided_lifetime_in_path_suggestion( + sess.source_map(), + &mut db, + n, + path_span, + incl_angl_brckt, + insertion_span, + ); + } + BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => { + db.span_suggestion(span, ¬e, sugg, Applicability::MaybeIncorrect); + } + BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => { + if !replaces.is_empty() { + db.tool_only_multipart_suggestion( + &message, + replaces, + Applicability::MachineApplicable, + ); + } + + if let Some(span) = in_test_module { + db.span_help( + self.sess().source_map().guess_head_span(span), + "consider adding a `#[cfg(test)]` to the containing module", + ); + } + } + BuiltinLintDiagnostics::RedundantImport(spans, ident) => { + for (span, is_imported) in spans { + let introduced = if is_imported { "imported" } else { "defined" }; + db.span_label( + span, + format!("the item `{}` is already {} here", ident, introduced), + ); + } + } + BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => { + stability::deprecation_suggestion(&mut db, "macro", suggestion, span) + } + BuiltinLintDiagnostics::UnusedDocComment(span) => { + db.span_label(span, "rustdoc does not generate documentation for macro invocations"); + db.help("to document an item produced by a macro, \ + the macro must produce the documentation as part of its expansion"); + } + BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => { + db.span_suggestion(span, "remove `mut` from the parameter", ident, Applicability::MachineApplicable); + } + BuiltinLintDiagnostics::MissingAbi(span, default_abi) => { + db.span_label(span, "ABI should be specified here"); + db.help(&format!("the default ABI is {}", default_abi.name())); + } + BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => { + db.span_label(span, "the attribute is introduced here"); + } + BuiltinLintDiagnostics::ProcMacroBackCompat(note) => { + db.note(¬e); + } + BuiltinLintDiagnostics::OrPatternsBackCompat(span,suggestion) => { + db.span_suggestion(span, "use pat_param to preserve semantics", suggestion, Applicability::MachineApplicable); + } + BuiltinLintDiagnostics::ReservedPrefix(span) => { + db.span_label(span, "unknown prefix"); + db.span_suggestion_verbose( + span.shrink_to_hi(), + "insert whitespace here to avoid this being parsed as a prefix in Rust 2021", + " ", + Applicability::MachineApplicable, + ); + } + BuiltinLintDiagnostics::UnusedBuiltinAttribute { + attr_name, + macro_name, + invoc_span + } => { + db.span_note( + invoc_span, + &format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`") + ); + } + BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => { + if is_trailing { + db.note("macro invocations at the end of a block are treated as expressions"); + db.note(&format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`")); + } + } + BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => { + db.multipart_suggestion( + "wrap this expression in parentheses", + vec![(span.shrink_to_lo(), "(".to_string()), + (span.shrink_to_hi(), ")".to_string())], + Applicability::MachineApplicable + ); + } + BuiltinLintDiagnostics::NamedAsmLabel(help) => { + db.help(&help); + db.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information"); + }, + BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), None) => { + let Some(names_valid) = &sess.parse_sess.check_config.names_valid else { + bug!("it shouldn't be possible to have a diagnostic on a name if name checking is not enabled"); + }; + let possibilities: Vec<Symbol> = names_valid.iter().map(|s| *s).collect(); + + // Suggest the most probable if we found one + if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) { + db.span_suggestion(name_span, "did you mean", best_match, Applicability::MaybeIncorrect); + } + }, + BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), Some((value, value_span))) => { + let Some(values) = &sess.parse_sess.check_config.values_valid.get(&name) else { + bug!("it shouldn't be possible to have a diagnostic on a value whose name is not in values"); + }; + let possibilities: Vec<Symbol> = values.iter().map(|&s| s).collect(); + + // Show the full list if all possible values for a given name, but don't do it + // for names as the possibilities could be very long + if !possibilities.is_empty() { + { + let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>(); + possibilities.sort(); + + let possibilities = possibilities.join(", "); + db.note(&format!("expected values for `{name}` are: {possibilities}")); + } + + // Suggest the most probable if we found one + if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) { + db.span_suggestion(value_span, "did you mean", format!("\"{best_match}\""), Applicability::MaybeIncorrect); + } + } else { + db.note(&format!("no expected value for `{name}`")); + if name != sym::feature { + db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect); + } + } + }, + BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => { + db.multipart_suggestion( + "move it to the end of the type declaration", + vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)], + Applicability::MachineApplicable, + ); + db.note( + "see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information", + ); + }, + BuiltinLintDiagnostics::SingleUseLifetime { + param_span, + use_span: Some((use_span, elide)), + deletion_span, + } => { + debug!(?param_span, ?use_span, ?deletion_span); + db.span_label(param_span, "this lifetime..."); + db.span_label(use_span, "...is used only here"); + let msg = "elide the single-use lifetime"; + let (use_span, replace_lt) = if elide { + let use_span = sess.source_map().span_extend_while( + use_span, + char::is_whitespace, + ).unwrap_or(use_span); + (use_span, String::new()) + } else { + (use_span, "'_".to_owned()) + }; + db.multipart_suggestion( + msg, + vec![(deletion_span, String::new()), (use_span, replace_lt)], + Applicability::MachineApplicable, + ); + }, + BuiltinLintDiagnostics::SingleUseLifetime { + param_span: _, + use_span: None, + deletion_span, + } => { + debug!(?deletion_span); + db.span_suggestion( + deletion_span, + "elide the unused lifetime", + "", + Applicability::MachineApplicable, + ); + }, + BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => { + db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string"); + if let Some(positional_arg_for_msg) = position_sp_for_msg { + let msg = format!("this formatting argument uses named argument `{}` by position", named_arg_name); + db.span_label(positional_arg_for_msg, msg); + } + + if let Some(positional_arg_to_replace) = position_sp_to_replace { + let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name }; + + db.span_suggestion_verbose( + positional_arg_to_replace, + "use the named argument by name to avoid ambiguity", + name, + Applicability::MaybeIncorrect, + ); + } + } + } + // Rewrap `db`, and pass control to the user. + decorate(LintDiagnosticBuilder::new(db)); + }); + } + + // FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to + // set the span in their `decorate` function (preferably using set_span). + fn lookup<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + ); + + /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`, + /// typically generated by `#[derive(LintDiagnostic)]`). + fn emit_spanned_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: S, + decorator: impl for<'a> DecorateLint<'a, ()>, + ) { + self.lookup(lint, Some(span), |diag| decorator.decorate_lint(diag)); + } + + fn struct_span_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: S, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + ) { + self.lookup(lint, Some(span), decorate); + } + + /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically + /// generated by `#[derive(LintDiagnostic)]`). + fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> DecorateLint<'a, ()>) { + self.lookup(lint, None as Option<Span>, |diag| decorator.decorate_lint(diag)); + } + + /// Emit a lint at the appropriate level, with no associated span. + fn lint( + &self, + lint: &'static Lint, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + ) { + self.lookup(lint, None as Option<Span>, decorate); + } + + /// This returns the lint level for the given lint at the current location. + fn get_lint_level(&self, lint: &'static Lint) -> Level; + + /// This function can be used to manually fulfill an expectation. This can + /// be used for lints which contain several spans, and should be suppressed, + /// if either location was marked with an expectation. + /// + /// Note that this function should only be called for [`LintExpectationId`]s + /// retrieved from the current lint pass. Buffered or manually created ids can + /// cause ICEs. + fn fulfill_expectation(&self, expectation: LintExpectationId) { + // We need to make sure that submitted expectation ids are correctly fulfilled suppressed + // and stored between compilation sessions. To not manually do these steps, we simply create + // a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal + // expected lint diagnostic. + self.sess() + .struct_expect( + "this is a dummy diagnostic, to submit and store an expectation", + expectation, + ) + .emit(); + } +} + +impl<'a> EarlyContext<'a> { + pub(crate) fn new( + sess: &'a Session, + warn_about_weird_lints: bool, + lint_store: &'a LintStore, + registered_tools: &'a RegisteredTools, + buffered: LintBuffer, + ) -> EarlyContext<'a> { + EarlyContext { + builder: LintLevelsBuilder::new( + sess, + warn_about_weird_lints, + lint_store, + registered_tools, + ), + buffered, + } + } +} + +impl LintContext for LateContext<'_> { + type PassObject = LateLintPassObject; + + /// Gets the overall compiler `Session` object. + fn sess(&self) -> &Session { + &self.tcx.sess + } + + fn lints(&self) -> &LintStore { + &*self.lint_store + } + + fn lookup<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + ) { + let hir_id = self.last_node_with_lint_attrs; + + match span { + Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, decorate), + None => self.tcx.struct_lint_node(lint, hir_id, decorate), + } + } + + fn get_lint_level(&self, lint: &'static Lint) -> Level { + self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs).0 + } +} + +impl LintContext for EarlyContext<'_> { + type PassObject = EarlyLintPassObject; + + /// Gets the overall compiler `Session` object. + fn sess(&self) -> &Session { + &self.builder.sess() + } + + fn lints(&self) -> &LintStore { + self.builder.lint_store() + } + + fn lookup<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + ) { + self.builder.struct_lint(lint, span.map(|s| s.into()), decorate) + } + + fn get_lint_level(&self, lint: &'static Lint) -> Level { + self.builder.lint_level(lint).0 + } +} + +impl<'tcx> LateContext<'tcx> { + /// Gets the type-checking results for the current body, + /// or `None` if outside a body. + pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> { + self.cached_typeck_results.get().or_else(|| { + self.enclosing_body.map(|body| { + let typeck_results = self.tcx.typeck_body(body); + self.cached_typeck_results.set(Some(typeck_results)); + typeck_results + }) + }) + } + + /// Gets the type-checking results for the current body. + /// As this will ICE if called outside bodies, only call when working with + /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies). + #[track_caller] + pub fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> { + self.maybe_typeck_results().expect("`LateContext::typeck_results` called outside of body") + } + + /// Returns the final resolution of a `QPath`, or `Res::Err` if unavailable. + /// Unlike `.typeck_results().qpath_res(qpath, id)`, this can be used even outside + /// bodies (e.g. for paths in `hir::Ty`), without any risk of ICE-ing. + pub fn qpath_res(&self, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res { + match *qpath { + hir::QPath::Resolved(_, ref path) => path.res, + hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self + .maybe_typeck_results() + .filter(|typeck_results| typeck_results.hir_owner == id.owner) + .or_else(|| { + if self.tcx.has_typeck_results(id.owner.to_def_id()) { + Some(self.tcx.typeck(id.owner)) + } else { + None + } + }) + .and_then(|typeck_results| typeck_results.type_dependent_def(id)) + .map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)), + } + } + + /// Check if a `DefId`'s path matches the given absolute type path usage. + /// + /// Anonymous scopes such as `extern` imports are matched with `kw::Empty`; + /// inherent `impl` blocks are matched with the name of the type. + /// + /// Instead of using this method, it is often preferable to instead use + /// `rustc_diagnostic_item` or a `lang_item`. This is less prone to errors + /// as paths get invalidated if the target definition moves. + /// + /// # Examples + /// + /// ```rust,ignore (no context or def id available) + /// if cx.match_def_path(def_id, &[sym::core, sym::option, sym::Option]) { + /// // The given `def_id` is that of an `Option` type + /// } + /// ``` + /// + /// Used by clippy, but should be replaced by diagnostic items eventually. + pub fn match_def_path(&self, def_id: DefId, path: &[Symbol]) -> bool { + let names = self.get_def_path(def_id); + + names.len() == path.len() && iter::zip(names, path).all(|(a, &b)| a == b) + } + + /// Gets the absolute path of `def_id` as a vector of `Symbol`. + /// + /// # Examples + /// + /// ```rust,ignore (no context or def id available) + /// let def_path = cx.get_def_path(def_id); + /// if let &[sym::core, sym::option, sym::Option] = &def_path[..] { + /// // The given `def_id` is that of an `Option` type + /// } + /// ``` + pub fn get_def_path(&self, def_id: DefId) -> Vec<Symbol> { + pub struct AbsolutePathPrinter<'tcx> { + pub tcx: TyCtxt<'tcx>, + } + + impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { + type Error = !; + + type Path = Vec<Symbol>; + type Region = (); + type Type = (); + type DynExistential = (); + type Const = (); + + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn print_region(self, _region: ty::Region<'_>) -> Result<Self::Region, Self::Error> { + Ok(()) + } + + fn print_type(self, _ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> { + Ok(()) + } + + fn print_dyn_existential( + self, + _predicates: &'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>, + ) -> Result<Self::DynExistential, Self::Error> { + Ok(()) + } + + fn print_const(self, _ct: ty::Const<'tcx>) -> Result<Self::Const, Self::Error> { + Ok(()) + } + + fn path_crate(self, cnum: CrateNum) -> Result<Self::Path, Self::Error> { + Ok(vec![self.tcx.crate_name(cnum)]) + } + + fn path_qualified( + self, + self_ty: Ty<'tcx>, + trait_ref: Option<ty::TraitRef<'tcx>>, + ) -> Result<Self::Path, Self::Error> { + if trait_ref.is_none() { + if let ty::Adt(def, substs) = self_ty.kind() { + return self.print_def_path(def.did(), substs); + } + } + + // This shouldn't ever be needed, but just in case: + with_no_trimmed_paths!({ + Ok(vec![match trait_ref { + Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)), + None => Symbol::intern(&format!("<{}>", self_ty)), + }]) + }) + } + + fn path_append_impl( + self, + print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, + _disambiguated_data: &DisambiguatedDefPathData, + self_ty: Ty<'tcx>, + trait_ref: Option<ty::TraitRef<'tcx>>, + ) -> Result<Self::Path, Self::Error> { + let mut path = print_prefix(self)?; + + // This shouldn't ever be needed, but just in case: + path.push(match trait_ref { + Some(trait_ref) => { + with_no_trimmed_paths!(Symbol::intern(&format!( + "<impl {} for {}>", + trait_ref.print_only_trait_path(), + self_ty + ))) + } + None => { + with_no_trimmed_paths!(Symbol::intern(&format!("<impl {}>", self_ty))) + } + }); + + Ok(path) + } + + fn path_append( + self, + print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, + disambiguated_data: &DisambiguatedDefPathData, + ) -> Result<Self::Path, Self::Error> { + let mut path = print_prefix(self)?; + + // Skip `::{{extern}}` blocks and `::{{constructor}}` on tuple/unit structs. + if let DefPathData::ForeignMod | DefPathData::Ctor = disambiguated_data.data { + return Ok(path); + } + + path.push(Symbol::intern(&disambiguated_data.data.to_string())); + Ok(path) + } + + fn path_generic_args( + self, + print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>, + _args: &[GenericArg<'tcx>], + ) -> Result<Self::Path, Self::Error> { + print_prefix(self) + } + } + + AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]).unwrap() + } +} + +impl<'tcx> abi::HasDataLayout for LateContext<'tcx> { + #[inline] + fn data_layout(&self) -> &abi::TargetDataLayout { + &self.tcx.data_layout + } +} + +impl<'tcx> ty::layout::HasTyCtxt<'tcx> for LateContext<'tcx> { + #[inline] + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } +} + +impl<'tcx> ty::layout::HasParamEnv<'tcx> for LateContext<'tcx> { + #[inline] + fn param_env(&self) -> ty::ParamEnv<'tcx> { + self.param_env + } +} + +impl<'tcx> LayoutOfHelpers<'tcx> for LateContext<'tcx> { + type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>; + + #[inline] + fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> { + err + } +} + +pub fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) { + match lint_name.split_once("::") { + Some((tool_name, lint_name)) => { + let tool_name = Symbol::intern(tool_name); + + (Some(tool_name), lint_name) + } + None => (None, lint_name), + } +} diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs new file mode 100644 index 000000000..d13711c3a --- /dev/null +++ b/compiler/rustc_lint/src/early.rs @@ -0,0 +1,456 @@ +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs +//! after all other analyses. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an ID of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used. +//! A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use crate::context::{EarlyContext, LintContext, LintStore}; +use crate::passes::{EarlyLintPass, EarlyLintPassObject}; +use rustc_ast::ptr::P; +use rustc_ast::visit::{self as ast_visit, Visitor}; +use rustc_ast::{self as ast, walk_list, HasAttrs}; +use rustc_middle::ty::RegisteredTools; +use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass}; +use rustc_session::Session; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +use std::slice; +use tracing::debug; + +macro_rules! run_early_pass { ($cx:expr, $f:ident, $($args:expr),*) => ({ + $cx.pass.$f(&$cx.context, $($args),*); +}) } + +pub struct EarlyContextAndPass<'a, T: EarlyLintPass> { + context: EarlyContext<'a>, + pass: T, +} + +impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> { + fn check_id(&mut self, id: ast::NodeId) { + for early_lint in self.context.buffered.take(id) { + let BufferedEarlyLint { span, msg, node_id: _, lint_id, diagnostic } = early_lint; + self.context.lookup_with_diagnostics( + lint_id.lint, + Some(span), + |lint| { + lint.build(&msg).emit(); + }, + diagnostic, + ); + } + } + + /// Merge the lints specified by any lint attributes into the + /// current lint context, call the provided function, then reset the + /// lints in effect to their previous state. + fn with_lint_attrs<F>(&mut self, id: ast::NodeId, attrs: &'a [ast::Attribute], f: F) + where + F: FnOnce(&mut Self), + { + let is_crate_node = id == ast::CRATE_NODE_ID; + let push = self.context.builder.push(attrs, is_crate_node, None); + + self.check_id(id); + debug!("early context: enter_attrs({:?})", attrs); + run_early_pass!(self, enter_lint_attrs, attrs); + f(self); + debug!("early context: exit_attrs({:?})", attrs); + run_early_pass!(self, exit_lint_attrs, attrs); + self.context.builder.pop(push); + } +} + +impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> { + fn visit_param(&mut self, param: &'a ast::Param) { + self.with_lint_attrs(param.id, ¶m.attrs, |cx| { + run_early_pass!(cx, check_param, param); + ast_visit::walk_param(cx, param); + }); + } + + fn visit_item(&mut self, it: &'a ast::Item) { + self.with_lint_attrs(it.id, &it.attrs, |cx| { + run_early_pass!(cx, check_item, it); + ast_visit::walk_item(cx, it); + run_early_pass!(cx, check_item_post, it); + }) + } + + fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) { + self.with_lint_attrs(it.id, &it.attrs, |cx| { + ast_visit::walk_foreign_item(cx, it); + }) + } + + fn visit_pat(&mut self, p: &'a ast::Pat) { + run_early_pass!(self, check_pat, p); + self.check_id(p.id); + ast_visit::walk_pat(self, p); + run_early_pass!(self, check_pat_post, p); + } + + fn visit_anon_const(&mut self, c: &'a ast::AnonConst) { + self.check_id(c.id); + ast_visit::walk_anon_const(self, c); + } + + fn visit_expr(&mut self, e: &'a ast::Expr) { + self.with_lint_attrs(e.id, &e.attrs, |cx| { + run_early_pass!(cx, check_expr, e); + ast_visit::walk_expr(cx, e); + }) + } + + fn visit_expr_field(&mut self, f: &'a ast::ExprField) { + self.with_lint_attrs(f.id, &f.attrs, |cx| { + ast_visit::walk_expr_field(cx, f); + }) + } + + fn visit_stmt(&mut self, s: &'a ast::Stmt) { + // Add the statement's lint attributes to our + // current state when checking the statement itself. + // This allows us to handle attributes like + // `#[allow(unused_doc_comments)]`, which apply to + // sibling attributes on the same target + // + // Note that statements get their attributes from + // the AST struct that they wrap (e.g. an item) + self.with_lint_attrs(s.id, s.attrs(), |cx| { + run_early_pass!(cx, check_stmt, s); + cx.check_id(s.id); + }); + // The visitor for the AST struct wrapped + // by the statement (e.g. `Item`) will call + // `with_lint_attrs`, so do this walk + // outside of the above `with_lint_attrs` call + ast_visit::walk_stmt(self, s); + } + + fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) { + run_early_pass!(self, check_fn, fk, span, id); + self.check_id(id); + ast_visit::walk_fn(self, fk, span); + + // Explicitly check for lints associated with 'closure_id', since + // it does not have a corresponding AST node + if let ast_visit::FnKind::Fn(_, _, sig, _, _, _) = fk { + if let ast::Async::Yes { closure_id, .. } = sig.header.asyncness { + self.check_id(closure_id); + } + } + } + + fn visit_variant_data(&mut self, s: &'a ast::VariantData) { + if let Some(ctor_hir_id) = s.ctor_id() { + self.check_id(ctor_hir_id); + } + ast_visit::walk_struct_def(self, s); + } + + fn visit_field_def(&mut self, s: &'a ast::FieldDef) { + self.with_lint_attrs(s.id, &s.attrs, |cx| { + ast_visit::walk_field_def(cx, s); + }) + } + + fn visit_variant(&mut self, v: &'a ast::Variant) { + self.with_lint_attrs(v.id, &v.attrs, |cx| { + run_early_pass!(cx, check_variant, v); + ast_visit::walk_variant(cx, v); + }) + } + + fn visit_ty(&mut self, t: &'a ast::Ty) { + run_early_pass!(self, check_ty, t); + self.check_id(t.id); + ast_visit::walk_ty(self, t); + } + + fn visit_ident(&mut self, ident: Ident) { + run_early_pass!(self, check_ident, ident); + } + + fn visit_local(&mut self, l: &'a ast::Local) { + self.with_lint_attrs(l.id, &l.attrs, |cx| { + run_early_pass!(cx, check_local, l); + ast_visit::walk_local(cx, l); + }) + } + + fn visit_block(&mut self, b: &'a ast::Block) { + run_early_pass!(self, check_block, b); + self.check_id(b.id); + ast_visit::walk_block(self, b); + } + + fn visit_arm(&mut self, a: &'a ast::Arm) { + self.with_lint_attrs(a.id, &a.attrs, |cx| { + run_early_pass!(cx, check_arm, a); + ast_visit::walk_arm(cx, a); + }) + } + + fn visit_expr_post(&mut self, e: &'a ast::Expr) { + // Explicitly check for lints associated with 'closure_id', since + // it does not have a corresponding AST node + match e.kind { + ast::ExprKind::Closure(_, _, ast::Async::Yes { closure_id, .. }, ..) + | ast::ExprKind::Async(_, closure_id, ..) => self.check_id(closure_id), + _ => {} + } + } + + fn visit_generic_arg(&mut self, arg: &'a ast::GenericArg) { + run_early_pass!(self, check_generic_arg, arg); + ast_visit::walk_generic_arg(self, arg); + } + + fn visit_generic_param(&mut self, param: &'a ast::GenericParam) { + run_early_pass!(self, check_generic_param, param); + self.check_id(param.id); + ast_visit::walk_generic_param(self, param); + } + + fn visit_generics(&mut self, g: &'a ast::Generics) { + run_early_pass!(self, check_generics, g); + ast_visit::walk_generics(self, g); + } + + fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) { + ast_visit::walk_where_predicate(self, p); + } + + fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef, m: &'a ast::TraitBoundModifier) { + run_early_pass!(self, check_poly_trait_ref, t, m); + ast_visit::walk_poly_trait_ref(self, t, m); + } + + fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) { + self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt { + ast_visit::AssocCtxt::Trait => { + run_early_pass!(cx, check_trait_item, item); + ast_visit::walk_assoc_item(cx, item, ctxt); + } + ast_visit::AssocCtxt::Impl => { + run_early_pass!(cx, check_impl_item, item); + ast_visit::walk_assoc_item(cx, item, ctxt); + } + }); + } + + fn visit_lifetime(&mut self, lt: &'a ast::Lifetime, _: ast_visit::LifetimeCtxt) { + self.check_id(lt.id); + } + + fn visit_path(&mut self, p: &'a ast::Path, id: ast::NodeId) { + self.check_id(id); + ast_visit::walk_path(self, p); + } + + fn visit_path_segment(&mut self, path_span: Span, s: &'a ast::PathSegment) { + self.check_id(s.id); + ast_visit::walk_path_segment(self, path_span, s); + } + + fn visit_attribute(&mut self, attr: &'a ast::Attribute) { + run_early_pass!(self, check_attribute, attr); + } + + fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) { + run_early_pass!(self, check_mac_def, mac, id); + self.check_id(id); + } + + fn visit_mac_call(&mut self, mac: &'a ast::MacCall) { + run_early_pass!(self, check_mac, mac); + ast_visit::walk_mac(self, mac); + } +} + +struct EarlyLintPassObjects<'a> { + lints: &'a mut [EarlyLintPassObject], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for EarlyLintPassObjects<'_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! expand_early_lint_pass_impl_methods { + ([$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { + for obj in self.lints.iter_mut() { + obj.$name(context, $($param),*); + } + })* + ) +} + +macro_rules! early_lint_pass_impl { + ([], [$($methods:tt)*]) => ( + impl EarlyLintPass for EarlyLintPassObjects<'_> { + expand_early_lint_pass_impl_methods!([$($methods)*]); + } + ) +} + +crate::early_lint_methods!(early_lint_pass_impl, []); + +/// Early lints work on different nodes - either on the crate root, or on freshly loaded modules. +/// This trait generalizes over those nodes. +pub trait EarlyCheckNode<'a>: Copy { + fn id(self) -> ast::NodeId; + fn attrs<'b>(self) -> &'b [ast::Attribute] + where + 'a: 'b; + fn check<'b>(self, cx: &mut EarlyContextAndPass<'b, impl EarlyLintPass>) + where + 'a: 'b; +} + +impl<'a> EarlyCheckNode<'a> for &'a ast::Crate { + fn id(self) -> ast::NodeId { + ast::CRATE_NODE_ID + } + fn attrs<'b>(self) -> &'b [ast::Attribute] + where + 'a: 'b, + { + &self.attrs + } + fn check<'b>(self, cx: &mut EarlyContextAndPass<'b, impl EarlyLintPass>) + where + 'a: 'b, + { + run_early_pass!(cx, check_crate, self); + ast_visit::walk_crate(cx, self); + run_early_pass!(cx, check_crate_post, self); + } +} + +impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [ast::Attribute], &'a [P<ast::Item>]) { + fn id(self) -> ast::NodeId { + self.0 + } + fn attrs<'b>(self) -> &'b [ast::Attribute] + where + 'a: 'b, + { + self.1 + } + fn check<'b>(self, cx: &mut EarlyContextAndPass<'b, impl EarlyLintPass>) + where + 'a: 'b, + { + walk_list!(cx, visit_attribute, self.1); + walk_list!(cx, visit_item, self.2); + } +} + +fn early_lint_node<'a>( + sess: &Session, + warn_about_weird_lints: bool, + lint_store: &LintStore, + registered_tools: &RegisteredTools, + buffered: LintBuffer, + pass: impl EarlyLintPass, + check_node: impl EarlyCheckNode<'a>, +) -> LintBuffer { + let mut cx = EarlyContextAndPass { + context: EarlyContext::new( + sess, + warn_about_weird_lints, + lint_store, + registered_tools, + buffered, + ), + pass, + }; + + cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx)); + cx.context.buffered +} + +pub fn check_ast_node<'a>( + sess: &Session, + pre_expansion: bool, + lint_store: &LintStore, + registered_tools: &RegisteredTools, + lint_buffer: Option<LintBuffer>, + builtin_lints: impl EarlyLintPass, + check_node: impl EarlyCheckNode<'a>, +) { + let passes = + if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes }; + let mut passes: Vec<_> = passes.iter().map(|p| (p)()).collect(); + let mut buffered = lint_buffer.unwrap_or_default(); + + if sess.opts.unstable_opts.no_interleave_lints { + for (i, pass) in passes.iter_mut().enumerate() { + buffered = + sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| { + early_lint_node( + sess, + !pre_expansion && i == 0, + lint_store, + registered_tools, + buffered, + EarlyLintPassObjects { lints: slice::from_mut(pass) }, + check_node, + ) + }); + } + } else { + buffered = early_lint_node( + sess, + !pre_expansion, + lint_store, + registered_tools, + buffered, + builtin_lints, + check_node, + ); + + if !passes.is_empty() { + buffered = early_lint_node( + sess, + false, + lint_store, + registered_tools, + buffered, + EarlyLintPassObjects { lints: &mut passes[..] }, + check_node, + ); + } + } + + // All of the buffered lints should have been emitted at this point. + // If not, that means that we somehow buffered a lint for a node id + // that was not lint-checked (perhaps it doesn't exist?). This is a bug. + for (id, lints) in buffered.map { + for early_lint in lints { + sess.delay_span_bug( + early_lint.span, + &format!( + "failed to process buffered lint here (dummy = {})", + id == ast::DUMMY_NODE_ID + ), + ); + } + } +} diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs new file mode 100644 index 000000000..f41ee6404 --- /dev/null +++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs @@ -0,0 +1,88 @@ +use crate::{context::LintContext, LateContext, LateLintPass}; +use rustc_errors::fluent; +use rustc_hir as hir; +use rustc_middle::ty::{visit::TypeVisitable, Ty}; +use rustc_span::{symbol::sym, Span}; + +declare_lint! { + /// The `enum_intrinsics_non_enums` lint detects calls to + /// intrinsic functions that require an enum ([`core::mem::discriminant`], + /// [`core::mem::variant_count`]), but are called with a non-enum type. + /// + /// [`core::mem::discriminant`]: https://doc.rust-lang.org/core/mem/fn.discriminant.html + /// [`core::mem::variant_count`]: https://doc.rust-lang.org/core/mem/fn.variant_count.html + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(enum_intrinsics_non_enums)] + /// core::mem::discriminant::<i32>(&123); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In order to accept any enum, the `mem::discriminant` and + /// `mem::variant_count` functions are generic over a type `T`. + /// This makes it technically possible for `T` to be a non-enum, + /// in which case the return value is unspecified. + /// + /// This lint prevents such incorrect usage of these functions. + ENUM_INTRINSICS_NON_ENUMS, + Deny, + "detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types" +} + +declare_lint_pass!(EnumIntrinsicsNonEnums => [ENUM_INTRINSICS_NON_ENUMS]); + +/// Returns `true` if we know for sure that the given type is not an enum. Note that for cases where +/// the type is generic, we can't be certain if it will be an enum so we have to assume that it is. +fn is_non_enum(t: Ty<'_>) -> bool { + !t.is_enum() && !t.needs_subst() +} + +fn enforce_mem_discriminant( + cx: &LateContext<'_>, + func_expr: &hir::Expr<'_>, + expr_span: Span, + args_span: Span, +) { + let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + if is_non_enum(ty_param) { + cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| { + builder + .build(fluent::lint::enum_intrinsics_mem_discriminant) + .set_arg("ty_param", ty_param) + .span_note(args_span, fluent::lint::note) + .emit(); + }); + } +} + +fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) { + let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + if is_non_enum(ty_param) { + cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| { + builder + .build(fluent::lint::enum_intrinsics_mem_variant) + .set_arg("ty_param", ty_param) + .note(fluent::lint::note) + .emit(); + }); + } +} + +impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + let hir::ExprKind::Call(func, args) = &expr.kind else { return }; + let hir::ExprKind::Path(qpath) = &func.kind else { return }; + let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() else { return }; + let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return }; + match name { + sym::mem_discriminant => enforce_mem_discriminant(cx, func, expr.span, args[0].span), + sym::mem_variant_count => enforce_mem_variant_count(cx, func, expr.span), + _ => {} + } + } +} diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs new file mode 100644 index 000000000..699e81543 --- /dev/null +++ b/compiler/rustc_lint/src/expect.rs @@ -0,0 +1,59 @@ +use crate::builtin; +use rustc_errors::fluent; +use rustc_hir::HirId; +use rustc_middle::ty::query::Providers; +use rustc_middle::{lint::LintExpectation, ty::TyCtxt}; +use rustc_session::lint::LintExpectationId; +use rustc_span::symbol::sym; +use rustc_span::Symbol; + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { check_expectations, ..*providers }; +} + +fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) { + if !tcx.sess.features_untracked().enabled(sym::lint_reasons) { + return; + } + + let fulfilled_expectations = tcx.sess.diagnostic().steal_fulfilled_expectation_ids(); + let lint_expectations = &tcx.lint_levels(()).lint_expectations; + + for (id, expectation) in lint_expectations { + // This check will always be true, since `lint_expectations` only + // holds stable ids + if let LintExpectationId::Stable { hir_id, .. } = id { + if !fulfilled_expectations.contains(&id) + && tool_filter.map_or(true, |filter| expectation.lint_tool == Some(filter)) + { + emit_unfulfilled_expectation_lint(tcx, *hir_id, expectation); + } + } else { + unreachable!("at this stage all `LintExpectationId`s are stable"); + } + } +} + +fn emit_unfulfilled_expectation_lint( + tcx: TyCtxt<'_>, + hir_id: HirId, + expectation: &LintExpectation, +) { + tcx.struct_span_lint_hir( + builtin::UNFULFILLED_LINT_EXPECTATIONS, + hir_id, + expectation.emission_span, + |diag| { + let mut diag = diag.build(fluent::lint::expectation); + if let Some(rationale) = expectation.reason { + diag.note(rationale.as_str()); + } + + if expectation.is_unfulfilled_lint_expectations { + diag.note(fluent::lint::note); + } + + diag.emit(); + }, + ); +} diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs new file mode 100644 index 000000000..fe2712525 --- /dev/null +++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs @@ -0,0 +1,141 @@ +use crate::{EarlyContext, EarlyLintPass, LintContext}; +use ast::util::unicode::{contains_text_flow_control_chars, TEXT_FLOW_CONTROL_CHARS}; +use rustc_ast as ast; +use rustc_errors::{fluent, Applicability, SuggestionStyle}; +use rustc_span::{BytePos, Span, Symbol}; + +declare_lint! { + /// The `text_direction_codepoint_in_literal` lint detects Unicode codepoints that change the + /// visual representation of text on screen in a way that does not correspond to their on + /// memory representation. + /// + /// ### Explanation + /// + /// The unicode characters `\u{202A}`, `\u{202B}`, `\u{202D}`, `\u{202E}`, `\u{2066}`, + /// `\u{2067}`, `\u{2068}`, `\u{202C}` and `\u{2069}` make the flow of text on screen change + /// its direction on software that supports these codepoints. This makes the text "abc" display + /// as "cba" on screen. By leveraging software that supports these, people can write specially + /// crafted literals that make the surrounding code seem like it's performing one action, when + /// in reality it is performing another. Because of this, we proactively lint against their + /// presence to avoid surprises. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(text_direction_codepoint_in_literal)] + /// fn main() { + /// println!("{:?}", '‮'); + /// } + /// ``` + /// + /// {{produces}} + /// + pub TEXT_DIRECTION_CODEPOINT_IN_LITERAL, + Deny, + "detect special Unicode codepoints that affect the visual representation of text on screen, \ + changing the direction in which text flows", +} + +declare_lint_pass!(HiddenUnicodeCodepoints => [TEXT_DIRECTION_CODEPOINT_IN_LITERAL]); + +impl HiddenUnicodeCodepoints { + fn lint_text_direction_codepoint( + &self, + cx: &EarlyContext<'_>, + text: Symbol, + span: Span, + padding: u32, + point_at_inner_spans: bool, + label: &str, + ) { + // Obtain the `Span`s for each of the forbidden chars. + let spans: Vec<_> = text + .as_str() + .char_indices() + .filter_map(|(i, c)| { + TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { + let lo = span.lo() + BytePos(i as u32 + padding); + (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) + }) + }) + .collect(); + + cx.struct_span_lint(TEXT_DIRECTION_CODEPOINT_IN_LITERAL, span, |lint| { + let mut err = lint.build(fluent::lint::hidden_unicode_codepoints); + err.set_arg("label", label); + err.set_arg("count", spans.len()); + err.span_label(span, fluent::lint::label); + err.note(fluent::lint::note); + if point_at_inner_spans { + for (c, span) in &spans { + err.span_label(*span, format!("{:?}", c)); + } + } + if point_at_inner_spans && !spans.is_empty() { + err.multipart_suggestion_with_style( + fluent::lint::suggestion_remove, + spans.iter().map(|(_, span)| (*span, "".to_string())).collect(), + Applicability::MachineApplicable, + SuggestionStyle::HideCodeAlways, + ); + err.multipart_suggestion( + fluent::lint::suggestion_escape, + spans + .into_iter() + .map(|(c, span)| { + let c = format!("{:?}", c); + (span, c[1..c.len() - 1].to_string()) + }) + .collect(), + Applicability::MachineApplicable, + ); + } else { + // FIXME: in other suggestions we've reversed the inner spans of doc comments. We + // should do the same here to provide the same good suggestions as we do for + // literals above. + err.set_arg( + "escaped", + spans + .into_iter() + .map(|(c, _)| format!("{:?}", c)) + .collect::<Vec<String>>() + .join(", "), + ); + err.note(fluent::lint::suggestion_remove); + err.note(fluent::lint::no_suggestion_note_escape); + } + err.emit(); + }); + } +} +impl EarlyLintPass for HiddenUnicodeCodepoints { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) { + if let ast::AttrKind::DocComment(_, comment) = attr.kind { + if contains_text_flow_control_chars(comment.as_str()) { + self.lint_text_direction_codepoint(cx, comment, attr.span, 0, false, "doc comment"); + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + // byte strings are already handled well enough by `EscapeError::NonAsciiCharInByteString` + let (text, span, padding) = match &expr.kind { + ast::ExprKind::Lit(ast::Lit { token, kind, span }) => { + let text = token.symbol; + if !contains_text_flow_control_chars(text.as_str()) { + return; + } + let padding = match kind { + // account for `"` or `'` + ast::LitKind::Str(_, ast::StrStyle::Cooked) | ast::LitKind::Char(_) => 1, + // account for `r###"` + ast::LitKind::Str(_, ast::StrStyle::Raw(val)) => *val as u32 + 2, + _ => return, + }; + (text, span, padding) + } + _ => return, + }; + self.lint_text_direction_codepoint(cx, text, *span, padding, true, "literal"); + } +} diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs new file mode 100644 index 000000000..c26d78247 --- /dev/null +++ b/compiler/rustc_lint/src/internal.rs @@ -0,0 +1,469 @@ +//! Some lints that are only useful in the compiler or crates that use compiler internals, such as +//! Clippy. + +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_errors::{fluent, Applicability}; +use rustc_hir::def::Res; +use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath}; +use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::Span; +use tracing::debug; + +declare_tool_lint! { + pub rustc::DEFAULT_HASH_TYPES, + Allow, + "forbid HashMap and HashSet and suggest the FxHash* variants", + report_in_external_macro: true +} + +declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]); + +impl LateLintPass<'_> for DefaultHashTypes { + fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) { + let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return }; + if matches!(cx.tcx.hir().get(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) { + // don't lint imports, only actual usages + return; + } + let replace = match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::HashMap) => "FxHashMap", + Some(sym::HashSet) => "FxHashSet", + _ => return, + }; + cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| { + lint.build(fluent::lint::default_hash_types) + .set_arg("preferred", replace) + .set_arg("used", cx.tcx.item_name(def_id)) + .note(fluent::lint::note) + .emit(); + }); + } +} + +/// Helper function for lints that check for expressions with calls and use typeck results to +/// get the `DefId` and `SubstsRef` of the function. +fn typeck_results_of_method_fn<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'_>, +) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> { + match expr.kind { + ExprKind::MethodCall(segment, _, _) + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => + { + Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id))) + }, + _ => { + match cx.typeck_results().node_type(expr.hir_id).kind() { + &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)), + _ => None, + } + } + } +} + +declare_tool_lint! { + pub rustc::POTENTIAL_QUERY_INSTABILITY, + Allow, + "require explicit opt-in when using potentially unstable methods or functions", + report_in_external_macro: true +} + +declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]); + +impl LateLintPass<'_> for QueryStability { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; + if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) { + let def_id = instance.def_id(); + if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) { + cx.struct_span_lint(POTENTIAL_QUERY_INSTABILITY, span, |lint| { + lint.build(fluent::lint::query_instability) + .set_arg("query", cx.tcx.item_name(def_id)) + .note(fluent::lint::note) + .emit(); + }) + } + } + } +} + +declare_tool_lint! { + pub rustc::USAGE_OF_TY_TYKIND, + Allow, + "usage of `ty::TyKind` outside of the `ty::sty` module", + report_in_external_macro: true +} + +declare_tool_lint! { + pub rustc::USAGE_OF_QUALIFIED_TY, + Allow, + "using `ty::{Ty,TyCtxt}` instead of importing it", + report_in_external_macro: true +} + +declare_lint_pass!(TyTyKind => [ + USAGE_OF_TY_TYKIND, + USAGE_OF_QUALIFIED_TY, +]); + +impl<'tcx> LateLintPass<'tcx> for TyTyKind { + fn check_path( + &mut self, + cx: &LateContext<'tcx>, + path: &'tcx rustc_hir::Path<'tcx>, + _: rustc_hir::HirId, + ) { + if let Some(segment) = path.segments.iter().nth_back(1) + && let Some(res) = &segment.res + && lint_ty_kind_usage(cx, res) + { + let span = path.span.with_hi( + segment.args.map_or(segment.ident.span, |a| a.span_ext).hi() + ); + cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| { + lint.build(fluent::lint::tykind_kind) + .span_suggestion( + span, + fluent::lint::suggestion, + "ty", + Applicability::MaybeIncorrect, // ty maybe needs an import + ) + .emit(); + }); + } + } + + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) { + match &ty.kind { + TyKind::Path(QPath::Resolved(_, path)) => { + if lint_ty_kind_usage(cx, &path.res) { + cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| { + let hir = cx.tcx.hir(); + match hir.find(hir.get_parent_node(ty.hir_id)) { + Some(Node::Pat(Pat { + kind: + PatKind::Path(qpath) + | PatKind::TupleStruct(qpath, ..) + | PatKind::Struct(qpath, ..), + .. + })) => { + if let QPath::TypeRelative(qpath_ty, ..) = qpath + && qpath_ty.hir_id == ty.hir_id + { + lint.build(fluent::lint::tykind_kind) + .span_suggestion( + path.span, + fluent::lint::suggestion, + "ty", + Applicability::MaybeIncorrect, // ty maybe needs an import + ) + .emit(); + return; + } + } + Some(Node::Expr(Expr { + kind: ExprKind::Path(qpath), + .. + })) => { + if let QPath::TypeRelative(qpath_ty, ..) = qpath + && qpath_ty.hir_id == ty.hir_id + { + lint.build(fluent::lint::tykind_kind) + .span_suggestion( + path.span, + fluent::lint::suggestion, + "ty", + Applicability::MaybeIncorrect, // ty maybe needs an import + ) + .emit(); + return; + } + } + // Can't unify these two branches because qpath below is `&&` and above is `&` + // and `A | B` paths don't play well together with adjustments, apparently. + Some(Node::Expr(Expr { + kind: ExprKind::Struct(qpath, ..), + .. + })) => { + if let QPath::TypeRelative(qpath_ty, ..) = qpath + && qpath_ty.hir_id == ty.hir_id + { + lint.build(fluent::lint::tykind_kind) + .span_suggestion( + path.span, + fluent::lint::suggestion, + "ty", + Applicability::MaybeIncorrect, // ty maybe needs an import + ) + .emit(); + return; + } + } + _ => {} + } + lint.build(fluent::lint::tykind).help(fluent::lint::help).emit(); + }) + } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) { + if path.segments.len() > 1 { + cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| { + lint.build(fluent::lint::ty_qualified) + .set_arg("ty", t.clone()) + .span_suggestion( + path.span, + fluent::lint::suggestion, + t, + // The import probably needs to be changed + Applicability::MaybeIncorrect, + ) + .emit(); + }) + } + } + } + _ => {} + } + } +} + +fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool { + if let Some(did) = res.opt_def_id() { + cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did) + } else { + false + } +} + +fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> { + match &path.res { + Res::Def(_, def_id) => { + if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) { + return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap()))); + } + } + // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait. + Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => { + if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() { + if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did()) + { + // NOTE: This path is currently unreachable as `Ty<'tcx>` is + // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>` + // is not actually allowed. + // + // I(@lcnr) still kept this branch in so we don't miss this + // if we ever change it in the future. + return Some(format!("{}<{}>", name, substs[0])); + } + } + } + _ => (), + } + + None +} + +fn gen_args(segment: &PathSegment<'_>) -> String { + if let Some(args) = &segment.args { + let lifetimes = args + .args + .iter() + .filter_map(|arg| { + if let GenericArg::Lifetime(lt) = arg { + Some(lt.name.ident().to_string()) + } else { + None + } + }) + .collect::<Vec<_>>(); + + if !lifetimes.is_empty() { + return format!("<{}>", lifetimes.join(", ")); + } + } + + String::new() +} + +declare_tool_lint! { + pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO, + Allow, + "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros" +} + +declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]); + +impl EarlyLintPass for LintPassImpl { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind { + if let Some(last) = lint_pass.path.segments.last() { + if last.ident.name == sym::LintPass { + let expn_data = lint_pass.path.span.ctxt().outer_expn_data(); + let call_site = expn_data.call_site; + if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass) + && call_site.ctxt().outer_expn_data().kind + != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass) + { + cx.struct_span_lint( + LINT_PASS_IMPL_WITHOUT_MACRO, + lint_pass.path.span, + |lint| { + lint.build(fluent::lint::lintpass_by_hand) + .help(fluent::lint::help) + .emit(); + }, + ) + } + } + } + } + } +} + +declare_tool_lint! { + pub rustc::EXISTING_DOC_KEYWORD, + Allow, + "Check that documented keywords in std and core actually exist", + report_in_external_macro: true +} + +declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]); + +fn is_doc_keyword(s: Symbol) -> bool { + s <= kw::Union +} + +impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword { + fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) { + for attr in cx.tcx.hir().attrs(item.hir_id()) { + if !attr.has_name(sym::doc) { + continue; + } + if let Some(list) = attr.meta_item_list() { + for nested in list { + if nested.has_name(sym::keyword) { + let v = nested + .value_str() + .expect("#[doc(keyword = \"...\")] expected a value!"); + if is_doc_keyword(v) { + return; + } + cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| { + lint.build(fluent::lint::non_existant_doc_keyword) + .set_arg("keyword", v) + .help(fluent::lint::help) + .emit(); + }); + } + } + } + } + } +} + +declare_tool_lint! { + pub rustc::UNTRANSLATABLE_DIAGNOSTIC, + Allow, + "prevent creation of diagnostics which cannot be translated", + report_in_external_macro: true +} + +declare_tool_lint! { + pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL, + Allow, + "prevent creation of diagnostics outside of `SessionDiagnostic`/`AddSubdiagnostic` impls", + report_in_external_macro: true +} + +declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]); + +impl LateLintPass<'_> for Diagnostics { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; + debug!(?span, ?def_id, ?substs); + let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) + .ok() + .and_then(|inst| inst) + .map(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics)) + .unwrap_or(false); + if !has_attr { + return; + } + + let mut found_impl = false; + for (_, parent) in cx.tcx.hir().parent_iter(expr.hir_id) { + debug!(?parent); + if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent && + let Impl { of_trait: Some(of_trait), .. } = impl_ && + let Some(def_id) = of_trait.trait_def_id() && + let Some(name) = cx.tcx.get_diagnostic_name(def_id) && + matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic | sym::DecorateLint) + { + found_impl = true; + break; + } + } + debug!(?found_impl); + if !found_impl { + cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| { + lint.build(fluent::lint::diag_out_of_impl).emit(); + }) + } + + let mut found_diagnostic_message = false; + for ty in substs.types() { + debug!(?ty); + if let Some(adt_def) = ty.ty_adt_def() && + let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) && + matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage) + { + found_diagnostic_message = true; + break; + } + } + debug!(?found_diagnostic_message); + if !found_diagnostic_message { + cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| { + lint.build(fluent::lint::untranslatable_diag).emit(); + }) + } + } +} + +declare_tool_lint! { + pub rustc::BAD_OPT_ACCESS, + Deny, + "prevent using options by field access when there is a wrapper function", + report_in_external_macro: true +} + +declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]); + +impl LateLintPass<'_> for BadOptAccess { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + let ExprKind::Field(base, target) = expr.kind else { return }; + let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return }; + // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be + // avoided. + if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) { + return; + } + + for field in adt_def.all_fields() { + if field.name == target.name && + let Some(attr) = cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access) && + let Some(items) = attr.meta_item_list() && + let Some(item) = items.first() && + let Some(literal) = item.literal() && + let ast::LitKind::Str(val, _) = literal.kind + { + cx.struct_span_lint(BAD_OPT_ACCESS, expr.span, |lint| { + lint.build(val.as_str()).emit(); } + ); + } + } + } +} diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs new file mode 100644 index 000000000..a329b3751 --- /dev/null +++ b/compiler/rustc_lint/src/late.rs @@ -0,0 +1,482 @@ +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs +//! after all other analyses. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an ID of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used. +//! A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore}; +use rustc_ast as ast; +use rustc_data_structures::sync::join; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit as hir_visit; +use rustc_hir::intravisit::Visitor; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_session::lint::LintPass; +use rustc_span::symbol::Symbol; +use rustc_span::Span; + +use std::any::Any; +use std::cell::Cell; +use std::slice; +use tracing::debug; + +/// Extract the `LintStore` from the query context. +/// This function exists because we've erased `LintStore` as `dyn Any` in the context. +pub fn unerased_lint_store(tcx: TyCtxt<'_>) -> &LintStore { + let store: &dyn Any = &*tcx.lint_store; + store.downcast_ref().unwrap() +} + +macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ + $cx.pass.$f(&$cx.context, $($args),*); +}) } + +struct LateContextAndPass<'tcx, T: LateLintPass<'tcx>> { + context: LateContext<'tcx>, + pass: T, +} + +impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> { + /// Merge the lints specified by any lint attributes into the + /// current lint context, call the provided function, then reset the + /// lints in effect to their previous state. + fn with_lint_attrs<F>(&mut self, id: hir::HirId, f: F) + where + F: FnOnce(&mut Self), + { + let attrs = self.context.tcx.hir().attrs(id); + let prev = self.context.last_node_with_lint_attrs; + self.context.last_node_with_lint_attrs = id; + debug!("late context: enter_attrs({:?})", attrs); + lint_callback!(self, enter_lint_attrs, attrs); + f(self); + debug!("late context: exit_attrs({:?})", attrs); + lint_callback!(self, exit_lint_attrs, attrs); + self.context.last_node_with_lint_attrs = prev; + } + + fn with_param_env<F>(&mut self, id: hir::HirId, f: F) + where + F: FnOnce(&mut Self), + { + let old_param_env = self.context.param_env; + self.context.param_env = + self.context.tcx.param_env(self.context.tcx.hir().local_def_id(id)); + f(self); + self.context.param_env = old_param_env; + } + + fn process_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) { + lint_callback!(self, check_mod, m, s, n); + hir_visit::walk_mod(self, m, n); + } +} + +impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPass<'tcx, T> { + type NestedFilter = nested_filter::All; + + /// Because lints are scoped lexically, we want to walk nested + /// items in the context of the outer item, so enable + /// deep-walking. + fn nested_visit_map(&mut self) -> Self::Map { + self.context.tcx.hir() + } + + fn visit_nested_body(&mut self, body_id: hir::BodyId) { + let old_enclosing_body = self.context.enclosing_body.replace(body_id); + let old_cached_typeck_results = self.context.cached_typeck_results.get(); + + // HACK(eddyb) avoid trashing `cached_typeck_results` when we're + // nested in `visit_fn`, which may have already resulted in them + // being queried. + if old_enclosing_body != Some(body_id) { + self.context.cached_typeck_results.set(None); + } + + let body = self.context.tcx.hir().body(body_id); + self.visit_body(body); + self.context.enclosing_body = old_enclosing_body; + + // See HACK comment above. + if old_enclosing_body != Some(body_id) { + self.context.cached_typeck_results.set(old_cached_typeck_results); + } + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + self.with_lint_attrs(param.hir_id, |cx| { + hir_visit::walk_param(cx, param); + }); + } + + fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { + lint_callback!(self, check_body, body); + hir_visit::walk_body(self, body); + lint_callback!(self, check_body_post, body); + } + + fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { + let generics = self.context.generics.take(); + self.context.generics = it.kind.generics(); + let old_cached_typeck_results = self.context.cached_typeck_results.take(); + let old_enclosing_body = self.context.enclosing_body.take(); + self.with_lint_attrs(it.hir_id(), |cx| { + cx.with_param_env(it.hir_id(), |cx| { + lint_callback!(cx, check_item, it); + hir_visit::walk_item(cx, it); + lint_callback!(cx, check_item_post, it); + }); + }); + self.context.enclosing_body = old_enclosing_body; + self.context.cached_typeck_results.set(old_cached_typeck_results); + self.context.generics = generics; + } + + fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { + self.with_lint_attrs(it.hir_id(), |cx| { + cx.with_param_env(it.hir_id(), |cx| { + lint_callback!(cx, check_foreign_item, it); + hir_visit::walk_foreign_item(cx, it); + }); + }) + } + + fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) { + lint_callback!(self, check_pat, p); + hir_visit::walk_pat(self, p); + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + self.with_lint_attrs(e.hir_id, |cx| { + lint_callback!(cx, check_expr, e); + hir_visit::walk_expr(cx, e); + lint_callback!(cx, check_expr_post, e); + }) + } + + fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) { + // See `EarlyContextAndPass::visit_stmt` for an explanation + // of why we call `walk_stmt` outside of `with_lint_attrs` + self.with_lint_attrs(s.hir_id, |cx| { + lint_callback!(cx, check_stmt, s); + }); + hir_visit::walk_stmt(self, s); + } + + fn visit_fn( + &mut self, + fk: hir_visit::FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'tcx>, + body_id: hir::BodyId, + span: Span, + id: hir::HirId, + ) { + // Wrap in typeck results here, not just in visit_nested_body, + // in order for `check_fn` to be able to use them. + let old_enclosing_body = self.context.enclosing_body.replace(body_id); + let old_cached_typeck_results = self.context.cached_typeck_results.take(); + let body = self.context.tcx.hir().body(body_id); + lint_callback!(self, check_fn, fk, decl, body, span, id); + hir_visit::walk_fn(self, fk, decl, body_id, span, id); + self.context.enclosing_body = old_enclosing_body; + self.context.cached_typeck_results.set(old_cached_typeck_results); + } + + fn visit_variant_data( + &mut self, + s: &'tcx hir::VariantData<'tcx>, + _: Symbol, + _: &'tcx hir::Generics<'tcx>, + _: hir::HirId, + _: Span, + ) { + lint_callback!(self, check_struct_def, s); + hir_visit::walk_struct_def(self, s); + } + + fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) { + self.with_lint_attrs(s.hir_id, |cx| { + lint_callback!(cx, check_field_def, s); + hir_visit::walk_field_def(cx, s); + }) + } + + fn visit_variant( + &mut self, + v: &'tcx hir::Variant<'tcx>, + g: &'tcx hir::Generics<'tcx>, + item_id: hir::HirId, + ) { + self.with_lint_attrs(v.id, |cx| { + lint_callback!(cx, check_variant, v); + hir_visit::walk_variant(cx, v, g, item_id); + }) + } + + fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) { + lint_callback!(self, check_ty, t); + hir_visit::walk_ty(self, t); + } + + fn visit_infer(&mut self, inf: &'tcx hir::InferArg) { + hir_visit::walk_inf(self, inf); + } + + fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) { + if !self.context.only_module { + self.process_mod(m, s, n); + } + } + + fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) { + self.with_lint_attrs(l.hir_id, |cx| { + lint_callback!(cx, check_local, l); + hir_visit::walk_local(cx, l); + }) + } + + fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) { + lint_callback!(self, check_block, b); + hir_visit::walk_block(self, b); + lint_callback!(self, check_block_post, b); + } + + fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { + lint_callback!(self, check_arm, a); + hir_visit::walk_arm(self, a); + } + + fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) { + lint_callback!(self, check_generic_param, p); + hir_visit::walk_generic_param(self, p); + } + + fn visit_generics(&mut self, g: &'tcx hir::Generics<'tcx>) { + lint_callback!(self, check_generics, g); + hir_visit::walk_generics(self, g); + } + + fn visit_where_predicate(&mut self, p: &'tcx hir::WherePredicate<'tcx>) { + hir_visit::walk_where_predicate(self, p); + } + + fn visit_poly_trait_ref( + &mut self, + t: &'tcx hir::PolyTraitRef<'tcx>, + m: hir::TraitBoundModifier, + ) { + lint_callback!(self, check_poly_trait_ref, t, m); + hir_visit::walk_poly_trait_ref(self, t, m); + } + + fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { + let generics = self.context.generics.take(); + self.context.generics = Some(&trait_item.generics); + self.with_lint_attrs(trait_item.hir_id(), |cx| { + cx.with_param_env(trait_item.hir_id(), |cx| { + lint_callback!(cx, check_trait_item, trait_item); + hir_visit::walk_trait_item(cx, trait_item); + }); + }); + self.context.generics = generics; + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + let generics = self.context.generics.take(); + self.context.generics = Some(&impl_item.generics); + self.with_lint_attrs(impl_item.hir_id(), |cx| { + cx.with_param_env(impl_item.hir_id(), |cx| { + lint_callback!(cx, check_impl_item, impl_item); + hir_visit::walk_impl_item(cx, impl_item); + lint_callback!(cx, check_impl_item_post, impl_item); + }); + }); + self.context.generics = generics; + } + + fn visit_lifetime(&mut self, lt: &'tcx hir::Lifetime) { + hir_visit::walk_lifetime(self, lt); + } + + fn visit_path(&mut self, p: &'tcx hir::Path<'tcx>, id: hir::HirId) { + lint_callback!(self, check_path, p, id); + hir_visit::walk_path(self, p); + } + + fn visit_attribute(&mut self, attr: &'tcx ast::Attribute) { + lint_callback!(self, check_attribute, attr); + } +} + +struct LateLintPassObjects<'a> { + lints: &'a mut [LateLintPassObject], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for LateLintPassObjects<'_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! expand_late_lint_pass_impl_methods { + ([$hir:tt], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &LateContext<$hir>, $($param: $arg),*) { + for obj in self.lints.iter_mut() { + obj.$name(context, $($param),*); + } + })* + ) +} + +macro_rules! late_lint_pass_impl { + ([], [$hir:tt], $methods:tt) => { + impl<$hir> LateLintPass<$hir> for LateLintPassObjects<'_> { + expand_late_lint_pass_impl_methods!([$hir], $methods); + } + }; +} + +crate::late_lint_methods!(late_lint_pass_impl, [], ['tcx]); + +fn late_lint_mod_pass<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + module_def_id: LocalDefId, + pass: T, +) { + let access_levels = &tcx.privacy_access_levels(()); + + let context = LateContext { + tcx, + enclosing_body: None, + cached_typeck_results: Cell::new(None), + param_env: ty::ParamEnv::empty(), + access_levels, + lint_store: unerased_lint_store(tcx), + last_node_with_lint_attrs: tcx.hir().local_def_id_to_hir_id(module_def_id), + generics: None, + only_module: true, + }; + + let mut cx = LateContextAndPass { context, pass }; + + let (module, span, hir_id) = tcx.hir().get_module(module_def_id); + cx.process_mod(module, span, hir_id); + + // Visit the crate attributes + if hir_id == hir::CRATE_HIR_ID { + for attr in tcx.hir().attrs(hir::CRATE_HIR_ID).iter() { + cx.visit_attribute(attr) + } + } +} + +pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + module_def_id: LocalDefId, + builtin_lints: T, +) { + if tcx.sess.opts.unstable_opts.no_interleave_lints { + // These passes runs in late_lint_crate with -Z no_interleave_lints + return; + } + + late_lint_mod_pass(tcx, module_def_id, builtin_lints); + + let mut passes: Vec<_> = + unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect(); + + if !passes.is_empty() { + late_lint_mod_pass(tcx, module_def_id, LateLintPassObjects { lints: &mut passes[..] }); + } +} + +fn late_lint_pass_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, pass: T) { + let access_levels = &tcx.privacy_access_levels(()); + + let context = LateContext { + tcx, + enclosing_body: None, + cached_typeck_results: Cell::new(None), + param_env: ty::ParamEnv::empty(), + access_levels, + lint_store: unerased_lint_store(tcx), + last_node_with_lint_attrs: hir::CRATE_HIR_ID, + generics: None, + only_module: false, + }; + + let mut cx = LateContextAndPass { context, pass }; + + // Visit the whole crate. + cx.with_lint_attrs(hir::CRATE_HIR_ID, |cx| { + // since the root module isn't visited as an item (because it isn't an + // item), warn for it here. + lint_callback!(cx, check_crate,); + tcx.hir().walk_toplevel_module(cx); + tcx.hir().walk_attributes(cx); + lint_callback!(cx, check_crate_post,); + }) +} + +fn late_lint_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, builtin_lints: T) { + let mut passes = unerased_lint_store(tcx).late_passes.iter().map(|p| (p)()).collect::<Vec<_>>(); + + if !tcx.sess.opts.unstable_opts.no_interleave_lints { + if !passes.is_empty() { + late_lint_pass_crate(tcx, LateLintPassObjects { lints: &mut passes[..] }); + } + + late_lint_pass_crate(tcx, builtin_lints); + } else { + for pass in &mut passes { + tcx.sess.prof.extra_verbose_generic_activity("run_late_lint", pass.name()).run(|| { + late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) }); + }); + } + + let mut passes: Vec<_> = + unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect(); + + for pass in &mut passes { + tcx.sess.prof.extra_verbose_generic_activity("run_late_module_lint", pass.name()).run( + || { + late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) }); + }, + ); + } + } +} + +/// Performs lint checking on a crate. +pub fn check_crate<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + builtin_lints: impl FnOnce() -> T + Send, +) { + join( + || { + tcx.sess.time("crate_lints", || { + // Run whole crate non-incremental lints + late_lint_crate(tcx, builtin_lints()); + }); + }, + || { + tcx.sess.time("module_lints", || { + // Run per-module lints + tcx.hir().par_for_each_module(|module| tcx.ensure().lint_mod(module)); + }); + }, + ); +} diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs new file mode 100644 index 000000000..00e96f20d --- /dev/null +++ b/compiler/rustc_lint/src/levels.rs @@ -0,0 +1,813 @@ +use crate::context::{CheckLintNameResult, LintStore}; +use crate::late::unerased_lint_store; +use rustc_ast as ast; +use rustc_ast_pretty::pprust; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::{struct_span_err, Applicability, Diagnostic, LintDiagnosticBuilder, MultiSpan}; +use rustc_hir as hir; +use rustc_hir::{intravisit, HirId}; +use rustc_middle::hir::nested_filter; +use rustc_middle::lint::{ + struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets, + LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE, +}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{RegisteredTools, TyCtxt}; +use rustc_session::lint::{ + builtin::{self, FORBIDDEN_LINT_GROUPS, SINGLE_USE_LIFETIMES, UNFULFILLED_LINT_EXPECTATIONS}, + Level, Lint, LintExpectationId, LintId, +}; +use rustc_session::parse::{add_feature_diagnostics, feature_err}; +use rustc_session::Session; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use tracing::debug; + +fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap { + let store = unerased_lint_store(tcx); + let levels = + LintLevelsBuilder::new(tcx.sess, false, &store, &tcx.resolutions(()).registered_tools); + let mut builder = LintLevelMapBuilder { levels, tcx }; + let krate = tcx.hir().krate(); + + builder.levels.id_to_set.reserve(krate.owners.len() + 1); + + let push = + builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID)); + + builder.levels.register_id(hir::CRATE_HIR_ID); + tcx.hir().walk_toplevel_module(&mut builder); + builder.levels.pop(push); + + builder.levels.update_unstable_expectation_ids(); + builder.levels.build_map() +} + +pub struct LintLevelsBuilder<'s> { + sess: &'s Session, + lint_expectations: Vec<(LintExpectationId, LintExpectation)>, + /// Each expectation has a stable and an unstable identifier. This map + /// is used to map from unstable to stable [`LintExpectationId`]s. + expectation_id_map: FxHashMap<LintExpectationId, LintExpectationId>, + sets: LintLevelSets, + id_to_set: FxHashMap<HirId, LintStackIndex>, + cur: LintStackIndex, + warn_about_weird_lints: bool, + store: &'s LintStore, + registered_tools: &'s RegisteredTools, +} + +pub struct BuilderPush { + prev: LintStackIndex, + pub changed: bool, +} + +impl<'s> LintLevelsBuilder<'s> { + pub fn new( + sess: &'s Session, + warn_about_weird_lints: bool, + store: &'s LintStore, + registered_tools: &'s RegisteredTools, + ) -> Self { + let mut builder = LintLevelsBuilder { + sess, + lint_expectations: Default::default(), + expectation_id_map: Default::default(), + sets: LintLevelSets::new(), + cur: COMMAND_LINE, + id_to_set: Default::default(), + warn_about_weird_lints, + store, + registered_tools, + }; + builder.process_command_line(sess, store); + assert_eq!(builder.sets.list.len(), 1); + builder + } + + pub(crate) fn sess(&self) -> &Session { + self.sess + } + + pub(crate) fn lint_store(&self) -> &LintStore { + self.store + } + + fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> { + &self.sets.list[self.cur].specs + } + + fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> { + &mut self.sets.list[self.cur].specs + } + + fn process_command_line(&mut self, sess: &Session, store: &LintStore) { + self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid); + + self.cur = + self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE }); + for &(ref lint_name, level) in &sess.opts.lint_opts { + store.check_lint_name_cmdline(sess, &lint_name, level, self.registered_tools); + let orig_level = level; + let lint_flag_val = Symbol::intern(lint_name); + + let Ok(ids) = store.find_lints(&lint_name) else { + // errors handled in check_lint_name_cmdline above + continue + }; + for id in ids { + // ForceWarn and Forbid cannot be overridden + if let Some((Level::ForceWarn(_) | Level::Forbid, _)) = + self.current_specs().get(&id) + { + continue; + } + + if self.check_gated_lint(id, DUMMY_SP) { + let src = LintLevelSource::CommandLine(lint_flag_val, orig_level); + self.current_specs_mut().insert(id, (level, src)); + } + } + } + } + + /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful + /// (e.g. if a forbid was already inserted on the same scope), then emits a + /// diagnostic with no change to `specs`. + fn insert_spec(&mut self, id: LintId, (level, src): LevelAndSource) { + let (old_level, old_src) = + self.sets.get_lint_level(id.lint, self.cur, Some(self.current_specs()), &self.sess); + // Setting to a non-forbid level is an error if the lint previously had + // a forbid level. Note that this is not necessarily true even with a + // `#[forbid(..)]` attribute present, as that is overridden by `--cap-lints`. + // + // This means that this only errors if we're truly lowering the lint + // level from forbid. + if level != Level::Forbid { + if let Level::Forbid = old_level { + // Backwards compatibility check: + // + // We used to not consider `forbid(lint_group)` + // as preventing `allow(lint)` for some lint `lint` in + // `lint_group`. For now, issue a future-compatibility + // warning for this case. + let id_name = id.lint.name_lower(); + let fcw_warning = match old_src { + LintLevelSource::Default => false, + LintLevelSource::Node(symbol, _, _) => self.store.is_lint_group(symbol), + LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol), + }; + debug!( + "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}", + fcw_warning, + self.current_specs(), + old_src, + id_name + ); + + let decorate_diag = |diag: &mut Diagnostic| { + diag.span_label(src.span(), "overruled by previous forbid"); + match old_src { + LintLevelSource::Default => { + diag.note(&format!( + "`forbid` lint level is the default for {}", + id.to_string() + )); + } + LintLevelSource::Node(_, forbid_source_span, reason) => { + diag.span_label(forbid_source_span, "`forbid` level set here"); + if let Some(rationale) = reason { + diag.note(rationale.as_str()); + } + } + LintLevelSource::CommandLine(_, _) => { + diag.note("`forbid` lint level was set on command line"); + } + } + }; + if !fcw_warning { + let mut diag_builder = struct_span_err!( + self.sess, + src.span(), + E0453, + "{}({}) incompatible with previous forbid", + level.as_str(), + src.name(), + ); + decorate_diag(&mut diag_builder); + diag_builder.emit(); + } else { + self.struct_lint( + FORBIDDEN_LINT_GROUPS, + Some(src.span().into()), + |diag_builder| { + let mut diag_builder = diag_builder.build(&format!( + "{}({}) incompatible with previous forbid", + level.as_str(), + src.name(), + )); + decorate_diag(&mut diag_builder); + diag_builder.emit(); + }, + ); + } + + // Retain the forbid lint level, unless we are + // issuing a FCW. In the FCW case, we want to + // respect the new setting. + if !fcw_warning { + return; + } + } + } + + // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself. + // Handling expectations of this lint would add additional complexity with little to no + // benefit. The expect level for this lint will therefore be ignored. + if let Level::Expect(_) = level && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS) { + return; + } + + match (old_level, level) { + // If the new level is an expectation store it in `ForceWarn` + (Level::ForceWarn(_), Level::Expect(expectation_id)) => self + .current_specs_mut() + .insert(id, (Level::ForceWarn(Some(expectation_id)), old_src)), + // Keep `ForceWarn` level but drop the expectation + (Level::ForceWarn(_), _) => { + self.current_specs_mut().insert(id, (Level::ForceWarn(None), old_src)) + } + // Set the lint level as normal + _ => self.current_specs_mut().insert(id, (level, src)), + }; + } + + /// Pushes a list of AST lint attributes onto this context. + /// + /// This function will return a `BuilderPush` object which should be passed + /// to `pop` when this scope for the attributes provided is exited. + /// + /// This function will perform a number of tasks: + /// + /// * It'll validate all lint-related attributes in `attrs` + /// * It'll mark all lint-related attributes as used + /// * Lint levels will be updated based on the attributes provided + /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to + /// `#[allow]` + /// + /// Don't forget to call `pop`! + pub(crate) fn push( + &mut self, + attrs: &[ast::Attribute], + is_crate_node: bool, + source_hir_id: Option<HirId>, + ) -> BuilderPush { + let prev = self.cur; + self.cur = self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev }); + + let sess = self.sess; + let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input"); + for (attr_index, attr) in attrs.iter().enumerate() { + if attr.has_name(sym::automatically_derived) { + self.current_specs_mut().insert( + LintId::of(SINGLE_USE_LIFETIMES), + (Level::Allow, LintLevelSource::Default), + ); + continue; + } + + let level = match Level::from_attr(attr) { + None => continue, + // This is the only lint level with a `LintExpectationId` that can be created from an attribute + Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => { + let stable_id = self.create_stable_id(unstable_id, hir_id, attr_index); + + Level::Expect(stable_id) + } + Some(lvl) => lvl, + }; + + let Some(mut metas) = attr.meta_item_list() else { + continue + }; + + if metas.is_empty() { + // This emits the unused_attributes lint for `#[level()]` + continue; + } + + // Before processing the lint names, look for a reason (RFC 2383) + // at the end. + let mut reason = None; + let tail_li = &metas[metas.len() - 1]; + if let Some(item) = tail_li.meta_item() { + match item.kind { + ast::MetaItemKind::Word => {} // actual lint names handled later + ast::MetaItemKind::NameValue(ref name_value) => { + if item.path == sym::reason { + if let ast::LitKind::Str(rationale, _) = name_value.kind { + if !self.sess.features_untracked().lint_reasons { + feature_err( + &self.sess.parse_sess, + sym::lint_reasons, + item.span, + "lint reasons are experimental", + ) + .emit(); + } + reason = Some(rationale); + } else { + bad_attr(name_value.span) + .span_label(name_value.span, "reason must be a string literal") + .emit(); + } + // found reason, reslice meta list to exclude it + metas.pop().unwrap(); + } else { + bad_attr(item.span) + .span_label(item.span, "bad attribute argument") + .emit(); + } + } + ast::MetaItemKind::List(_) => { + bad_attr(item.span).span_label(item.span, "bad attribute argument").emit(); + } + } + } + + for (lint_index, li) in metas.iter_mut().enumerate() { + let level = match level { + Level::Expect(mut id) => { + id.set_lint_index(Some(lint_index as u16)); + Level::Expect(id) + } + level => level, + }; + + let sp = li.span(); + let meta_item = match li { + ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item, + _ => { + let mut err = bad_attr(sp); + let mut add_label = true; + if let Some(item) = li.meta_item() { + if let ast::MetaItemKind::NameValue(_) = item.kind { + if item.path == sym::reason { + err.span_label(sp, "reason in lint attribute must come last"); + add_label = false; + } + } + } + if add_label { + err.span_label(sp, "bad attribute argument"); + } + err.emit(); + continue; + } + }; + let tool_ident = if meta_item.path.segments.len() > 1 { + Some(meta_item.path.segments.remove(0).ident) + } else { + None + }; + let tool_name = tool_ident.map(|ident| ident.name); + let name = pprust::path_to_string(&meta_item.path); + let lint_result = + self.store.check_lint_name(&name, tool_name, self.registered_tools); + match &lint_result { + CheckLintNameResult::Ok(ids) => { + // This checks for instances where the user writes `#[expect(unfulfilled_lint_expectations)]` + // in that case we want to avoid overriding the lint level but instead add an expectation that + // can't be fulfilled. The lint message will include an explanation, that the + // `unfulfilled_lint_expectations` lint can't be expected. + if let Level::Expect(expect_id) = level { + // The `unfulfilled_lint_expectations` lint is not part of any lint groups. Therefore. we + // only need to check the slice if it contains a single lint. + let is_unfulfilled_lint_expectations = match ids { + [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS), + _ => false, + }; + self.lint_expectations.push(( + expect_id, + LintExpectation::new( + reason, + sp, + is_unfulfilled_lint_expectations, + tool_name, + ), + )); + } + let src = LintLevelSource::Node( + meta_item.path.segments.last().expect("empty lint name").ident.name, + sp, + reason, + ); + for &id in *ids { + if self.check_gated_lint(id, attr.span) { + self.insert_spec(id, (level, src)); + } + } + } + + CheckLintNameResult::Tool(result) => { + match *result { + Ok(ids) => { + let complete_name = + &format!("{}::{}", tool_ident.unwrap().name, name); + let src = LintLevelSource::Node( + Symbol::intern(complete_name), + sp, + reason, + ); + for id in ids { + self.insert_spec(*id, (level, src)); + } + if let Level::Expect(expect_id) = level { + self.lint_expectations.push(( + expect_id, + LintExpectation::new(reason, sp, false, tool_name), + )); + } + } + Err((Some(ids), ref new_lint_name)) => { + let lint = builtin::RENAMED_AND_REMOVED_LINTS; + let (lvl, src) = self.sets.get_lint_level( + lint, + self.cur, + Some(self.current_specs()), + &sess, + ); + struct_lint_level( + self.sess, + lint, + lvl, + src, + Some(sp.into()), + |lint| { + let msg = format!( + "lint name `{}` is deprecated \ + and may not have an effect in the future.", + name + ); + lint.build(&msg) + .span_suggestion( + sp, + "change it to", + new_lint_name, + Applicability::MachineApplicable, + ) + .emit(); + }, + ); + + let src = LintLevelSource::Node( + Symbol::intern(&new_lint_name), + sp, + reason, + ); + for id in ids { + self.insert_spec(*id, (level, src)); + } + if let Level::Expect(expect_id) = level { + self.lint_expectations.push(( + expect_id, + LintExpectation::new(reason, sp, false, tool_name), + )); + } + } + Err((None, _)) => { + // If Tool(Err(None, _)) is returned, then either the lint does not + // exist in the tool or the code was not compiled with the tool and + // therefore the lint was never added to the `LintStore`. To detect + // this is the responsibility of the lint tool. + } + } + } + + &CheckLintNameResult::NoTool => { + let mut err = struct_span_err!( + sess, + tool_ident.map_or(DUMMY_SP, |ident| ident.span), + E0710, + "unknown tool name `{}` found in scoped lint: `{}::{}`", + tool_name.unwrap(), + tool_name.unwrap(), + pprust::path_to_string(&meta_item.path), + ); + if sess.is_nightly_build() { + err.help(&format!( + "add `#![register_tool({})]` to the crate root", + tool_name.unwrap() + )); + } + err.emit(); + continue; + } + + _ if !self.warn_about_weird_lints => {} + + CheckLintNameResult::Warning(msg, renamed) => { + let lint = builtin::RENAMED_AND_REMOVED_LINTS; + let (renamed_lint_level, src) = self.sets.get_lint_level( + lint, + self.cur, + Some(self.current_specs()), + &sess, + ); + struct_lint_level( + self.sess, + lint, + renamed_lint_level, + src, + Some(sp.into()), + |lint| { + let mut err = lint.build(msg); + if let Some(new_name) = &renamed { + err.span_suggestion( + sp, + "use the new name", + new_name, + Applicability::MachineApplicable, + ); + } + err.emit(); + }, + ); + } + CheckLintNameResult::NoLint(suggestion) => { + let lint = builtin::UNKNOWN_LINTS; + let (level, src) = self.sets.get_lint_level( + lint, + self.cur, + Some(self.current_specs()), + self.sess, + ); + struct_lint_level(self.sess, lint, level, src, Some(sp.into()), |lint| { + let name = if let Some(tool_ident) = tool_ident { + format!("{}::{}", tool_ident.name, name) + } else { + name.to_string() + }; + let mut db = lint.build(format!("unknown lint: `{}`", name)); + if let Some(suggestion) = suggestion { + db.span_suggestion( + sp, + "did you mean", + suggestion, + Applicability::MachineApplicable, + ); + } + db.emit(); + }); + } + } + // If this lint was renamed, apply the new lint instead of ignoring the attribute. + // This happens outside of the match because the new lint should be applied even if + // we don't warn about the name change. + if let CheckLintNameResult::Warning(_, Some(new_name)) = lint_result { + // Ignore any errors or warnings that happen because the new name is inaccurate + // NOTE: `new_name` already includes the tool name, so we don't have to add it again. + if let CheckLintNameResult::Ok(ids) = + self.store.check_lint_name(&new_name, None, self.registered_tools) + { + let src = LintLevelSource::Node(Symbol::intern(&new_name), sp, reason); + for &id in ids { + if self.check_gated_lint(id, attr.span) { + self.insert_spec(id, (level, src)); + } + } + if let Level::Expect(expect_id) = level { + self.lint_expectations.push(( + expect_id, + LintExpectation::new(reason, sp, false, tool_name), + )); + } + } else { + panic!("renamed lint does not exist: {}", new_name); + } + } + } + } + + if !is_crate_node { + for (id, &(level, ref src)) in self.current_specs().iter() { + if !id.lint.crate_level_only { + continue; + } + + let LintLevelSource::Node(lint_attr_name, lint_attr_span, _) = *src else { + continue + }; + + let lint = builtin::UNUSED_ATTRIBUTES; + let (lint_level, lint_src) = + self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), self.sess); + struct_lint_level( + self.sess, + lint, + lint_level, + lint_src, + Some(lint_attr_span.into()), + |lint| { + let mut db = lint.build(&format!( + "{}({}) is ignored unless specified at crate level", + level.as_str(), + lint_attr_name + )); + db.emit(); + }, + ); + // don't set a separate error for every lint in the group + break; + } + } + + if self.current_specs().is_empty() { + self.sets.list.pop(); + self.cur = prev; + } + + BuilderPush { prev, changed: prev != self.cur } + } + + fn create_stable_id( + &mut self, + unstable_id: LintExpectationId, + hir_id: HirId, + attr_index: usize, + ) -> LintExpectationId { + let stable_id = + LintExpectationId::Stable { hir_id, attr_index: attr_index as u16, lint_index: None }; + + self.expectation_id_map.insert(unstable_id, stable_id); + + stable_id + } + + /// Checks if the lint is gated on a feature that is not enabled. + /// + /// Returns `true` if the lint's feature is enabled. + fn check_gated_lint(&self, lint_id: LintId, span: Span) -> bool { + if let Some(feature) = lint_id.lint.feature_gate { + if !self.sess.features_untracked().enabled(feature) { + let lint = builtin::UNKNOWN_LINTS; + let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS); + struct_lint_level(self.sess, lint, level, src, Some(span.into()), |lint_db| { + let mut db = + lint_db.build(&format!("unknown lint: `{}`", lint_id.lint.name_lower())); + db.note(&format!("the `{}` lint is unstable", lint_id.lint.name_lower(),)); + add_feature_diagnostics(&mut db, &self.sess.parse_sess, feature); + db.emit(); + }); + return false; + } + } + true + } + + /// Called after `push` when the scope of a set of attributes are exited. + pub fn pop(&mut self, push: BuilderPush) { + self.cur = push.prev; + } + + /// Find the lint level for a lint. + pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintLevelSource) { + self.sets.get_lint_level(lint, self.cur, None, self.sess) + } + + /// Used to emit a lint-related diagnostic based on the current state of + /// this lint context. + pub fn struct_lint( + &self, + lint: &'static Lint, + span: Option<MultiSpan>, + decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>), + ) { + let (level, src) = self.lint_level(lint); + struct_lint_level(self.sess, lint, level, src, span, decorate) + } + + /// Registers the ID provided with the current set of lints stored in + /// this context. + pub fn register_id(&mut self, id: HirId) { + self.id_to_set.insert(id, self.cur); + } + + fn update_unstable_expectation_ids(&self) { + self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map); + } + + pub fn build_map(self) -> LintLevelMap { + LintLevelMap { + sets: self.sets, + id_to_set: self.id_to_set, + lint_expectations: self.lint_expectations, + } + } +} + +struct LintLevelMapBuilder<'tcx> { + levels: LintLevelsBuilder<'tcx>, + tcx: TyCtxt<'tcx>, +} + +impl LintLevelMapBuilder<'_> { + fn with_lint_attrs<F>(&mut self, id: hir::HirId, f: F) + where + F: FnOnce(&mut Self), + { + let is_crate_hir = id == hir::CRATE_HIR_ID; + let attrs = self.tcx.hir().attrs(id); + let push = self.levels.push(attrs, is_crate_hir, Some(id)); + + if push.changed { + self.levels.register_id(id); + } + f(self); + self.levels.pop(push); + } +} + +impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'tcx> { + type NestedFilter = nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + self.with_lint_attrs(param.hir_id, |builder| { + intravisit::walk_param(builder, param); + }); + } + + fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { + self.with_lint_attrs(it.hir_id(), |builder| { + intravisit::walk_item(builder, it); + }); + } + + fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { + self.with_lint_attrs(it.hir_id(), |builder| { + intravisit::walk_foreign_item(builder, it); + }) + } + + fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) { + // We will call `with_lint_attrs` when we walk + // the `StmtKind`. The outer statement itself doesn't + // define the lint levels. + intravisit::walk_stmt(self, e); + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + self.with_lint_attrs(e.hir_id, |builder| { + intravisit::walk_expr(builder, e); + }) + } + + fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) { + self.with_lint_attrs(s.hir_id, |builder| { + intravisit::walk_field_def(builder, s); + }) + } + + fn visit_variant( + &mut self, + v: &'tcx hir::Variant<'tcx>, + g: &'tcx hir::Generics<'tcx>, + item_id: hir::HirId, + ) { + self.with_lint_attrs(v.id, |builder| { + intravisit::walk_variant(builder, v, g, item_id); + }) + } + + fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) { + self.with_lint_attrs(l.hir_id, |builder| { + intravisit::walk_local(builder, l); + }) + } + + fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { + self.with_lint_attrs(a.hir_id, |builder| { + intravisit::walk_arm(builder, a); + }) + } + + fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { + self.with_lint_attrs(trait_item.hir_id(), |builder| { + intravisit::walk_trait_item(builder, trait_item); + }); + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + self.with_lint_attrs(impl_item.hir_id(), |builder| { + intravisit::walk_impl_item(builder, impl_item); + }); + } +} + +pub fn provide(providers: &mut Providers) { + providers.lint_levels = lint_levels; +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs new file mode 100644 index 000000000..389a0b5d1 --- /dev/null +++ b/compiler/rustc_lint/src/lib.rs @@ -0,0 +1,538 @@ +//! Lints, aka compiler warnings. +//! +//! A 'lint' check is a kind of miscellaneous constraint that a user _might_ +//! want to enforce, but might reasonably want to permit as well, on a +//! module-by-module basis. They contrast with static constraints enforced by +//! other phases of the compiler, which are generally required to hold in order +//! to compile the program at all. +//! +//! Most lints can be written as [LintPass] instances. These run after +//! all other analyses. The `LintPass`es built into rustc are defined +//! within [rustc_session::lint::builtin], +//! which has further comments on how to add such a lint. +//! rustc can also load user-defined lint plugins via the plugin mechanism. +//! +//! Some of rustc's lints are defined elsewhere in the compiler and work by +//! calling `add_lint()` on the overall `Session` object. This works when +//! it happens before the main lint pass, which emits the lints stored by +//! `add_lint()`. To emit lints after the main lint pass (from codegen, for +//! example) requires more effort. See `emit_lint` and `GatherNodeLevels` +//! in `context.rs`. +//! +//! Some code also exists in [rustc_session::lint], [rustc_middle::lint]. +//! +//! ## Note +//! +//! This API is completely unstable and subject to change. + +#![allow(rustc::potential_query_instability)] +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] +#![feature(array_windows)] +#![feature(box_patterns)] +#![feature(control_flow_enum)] +#![feature(if_let_guard)] +#![feature(iter_intersperse)] +#![feature(iter_order_by)] +#![feature(let_chains)] +#![feature(let_else)] +#![feature(never_type)] +#![recursion_limit = "256"] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate rustc_session; + +mod array_into_iter; +pub mod builtin; +mod context; +mod early; +mod enum_intrinsics_non_enums; +mod expect; +pub mod hidden_unicode_codepoints; +mod internal; +mod late; +mod levels; +mod methods; +mod non_ascii_idents; +mod non_fmt_panic; +mod nonstandard_style; +mod noop_method_call; +mod pass_by_value; +mod passes; +mod redundant_semicolon; +mod traits; +mod types; +mod unused; + +pub use array_into_iter::ARRAY_INTO_ITER; + +use rustc_ast as ast; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::builtin::{ + BARE_TRAIT_OBJECTS, ELIDED_LIFETIMES_IN_PATHS, EXPLICIT_OUTLIVES_REQUIREMENTS, +}; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +use array_into_iter::ArrayIntoIter; +use builtin::*; +use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; +use hidden_unicode_codepoints::*; +use internal::*; +use methods::*; +use non_ascii_idents::*; +use non_fmt_panic::NonPanicFmt; +use nonstandard_style::*; +use noop_method_call::*; +use pass_by_value::*; +use redundant_semicolon::*; +use traits::*; +use types::*; +use unused::*; + +/// Useful for other parts of the compiler / Clippy. +pub use builtin::SoftLints; +pub use context::{CheckLintNameResult, FindLintError, LintStore}; +pub use context::{EarlyContext, LateContext, LintContext}; +pub use early::{check_ast_node, EarlyCheckNode}; +pub use late::{check_crate, unerased_lint_store}; +pub use passes::{EarlyLintPass, LateLintPass}; +pub use rustc_session::lint::Level::{self, *}; +pub use rustc_session::lint::{BufferedEarlyLint, FutureIncompatibleInfo, Lint, LintId}; +pub use rustc_session::lint::{LintArray, LintPass}; + +pub fn provide(providers: &mut Providers) { + levels::provide(providers); + expect::provide(providers); + *providers = Providers { lint_mod, ..*providers }; +} + +fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); +} + +macro_rules! pre_expansion_lint_passes { + ($macro:path, $args:tt) => { + $macro!($args, [KeywordIdents: KeywordIdents,]); + }; +} + +macro_rules! early_lint_passes { + ($macro:path, $args:tt) => { + $macro!( + $args, + [ + UnusedParens: UnusedParens, + UnusedBraces: UnusedBraces, + UnusedImportBraces: UnusedImportBraces, + UnsafeCode: UnsafeCode, + AnonymousParameters: AnonymousParameters, + EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), + NonCamelCaseTypes: NonCamelCaseTypes, + DeprecatedAttr: DeprecatedAttr::new(), + WhileTrue: WhileTrue, + NonAsciiIdents: NonAsciiIdents, + HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, + IncompleteFeatures: IncompleteFeatures, + RedundantSemicolons: RedundantSemicolons, + UnusedDocComment: UnusedDocComment, + ] + ); + }; +} + +macro_rules! declare_combined_early_pass { + ([$name:ident], $passes:tt) => ( + early_lint_methods!(declare_combined_early_lint_pass, [pub $name, $passes]); + ) +} + +pre_expansion_lint_passes!(declare_combined_early_pass, [BuiltinCombinedPreExpansionLintPass]); +early_lint_passes!(declare_combined_early_pass, [BuiltinCombinedEarlyLintPass]); + +macro_rules! late_lint_passes { + ($macro:path, $args:tt) => { + $macro!( + $args, + [ + // Tracks state across modules + UnnameableTestItems: UnnameableTestItems::new(), + // Tracks attributes of parents + MissingDoc: MissingDoc::new(), + // Builds a global list of all impls of `Debug`. + // FIXME: Turn the computation of types which implement Debug into a query + // and change this to a module lint pass + MissingDebugImplementations: MissingDebugImplementations::default(), + // Keeps a global list of foreign declarations. + ClashingExternDeclarations: ClashingExternDeclarations::new(), + ] + ); + }; +} + +macro_rules! late_lint_mod_passes { + ($macro:path, $args:tt) => { + $macro!( + $args, + [ + HardwiredLints: HardwiredLints, + ImproperCTypesDeclarations: ImproperCTypesDeclarations, + ImproperCTypesDefinitions: ImproperCTypesDefinitions, + VariantSizeDifferences: VariantSizeDifferences, + BoxPointers: BoxPointers, + PathStatements: PathStatements, + // Depends on referenced function signatures in expressions + UnusedResults: UnusedResults, + NonUpperCaseGlobals: NonUpperCaseGlobals, + NonShorthandFieldPatterns: NonShorthandFieldPatterns, + UnusedAllocation: UnusedAllocation, + // Depends on types used in type definitions + MissingCopyImplementations: MissingCopyImplementations, + // Depends on referenced function signatures in expressions + MutableTransmutes: MutableTransmutes, + TypeAliasBounds: TypeAliasBounds, + TrivialConstraints: TrivialConstraints, + TypeLimits: TypeLimits::new(), + NonSnakeCase: NonSnakeCase, + InvalidNoMangleItems: InvalidNoMangleItems, + // Depends on access levels + UnreachablePub: UnreachablePub, + ExplicitOutlivesRequirements: ExplicitOutlivesRequirements, + InvalidValue: InvalidValue, + DerefNullPtr: DerefNullPtr, + // May Depend on constants elsewhere + UnusedBrokenConst: UnusedBrokenConst, + UnstableFeatures: UnstableFeatures, + ArrayIntoIter: ArrayIntoIter::default(), + DropTraitConstraints: DropTraitConstraints, + TemporaryCStringAsPtr: TemporaryCStringAsPtr, + NonPanicFmt: NonPanicFmt, + NoopMethodCall: NoopMethodCall, + EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums, + InvalidAtomicOrdering: InvalidAtomicOrdering, + NamedAsmLabels: NamedAsmLabels, + ] + ); + }; +} + +macro_rules! declare_combined_late_pass { + ([$v:vis $name:ident], $passes:tt) => ( + late_lint_methods!(declare_combined_late_lint_pass, [$v $name, $passes], ['tcx]); + ) +} + +// FIXME: Make a separate lint type which do not require typeck tables +late_lint_passes!(declare_combined_late_pass, [pub BuiltinCombinedLateLintPass]); + +late_lint_mod_passes!(declare_combined_late_pass, [BuiltinCombinedModuleLateLintPass]); + +pub fn new_lint_store(no_interleave_lints: bool, internal_lints: bool) -> LintStore { + let mut lint_store = LintStore::new(); + + register_builtins(&mut lint_store, no_interleave_lints); + if internal_lints { + register_internals(&mut lint_store); + } + + lint_store +} + +/// Tell the `LintStore` about all the built-in lints (the ones +/// defined in this crate and the ones defined in +/// `rustc_session::lint::builtin`). +fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) { + macro_rules! add_lint_group { + ($name:expr, $($lint:ident),*) => ( + store.register_group(false, $name, None, vec![$(LintId::of($lint)),*]); + ) + } + + macro_rules! register_pass { + ($method:ident, $ty:ident, $constructor:expr) => { + store.register_lints(&$ty::get_lints()); + store.$method(|| Box::new($constructor)); + }; + } + + macro_rules! register_passes { + ($method:ident, [$($passes:ident: $constructor:expr,)*]) => ( + $( + register_pass!($method, $passes, $constructor); + )* + ) + } + + if no_interleave_lints { + pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass); + early_lint_passes!(register_passes, register_early_pass); + late_lint_passes!(register_passes, register_late_pass); + late_lint_mod_passes!(register_passes, register_late_mod_pass); + } else { + store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); + store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); + store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); + store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); + } + + add_lint_group!( + "nonstandard_style", + NON_CAMEL_CASE_TYPES, + NON_SNAKE_CASE, + NON_UPPER_CASE_GLOBALS + ); + + add_lint_group!( + "unused", + UNUSED_IMPORTS, + UNUSED_VARIABLES, + UNUSED_ASSIGNMENTS, + DEAD_CODE, + UNUSED_MUT, + UNREACHABLE_CODE, + UNREACHABLE_PATTERNS, + UNUSED_MUST_USE, + UNUSED_UNSAFE, + PATH_STATEMENTS, + UNUSED_ATTRIBUTES, + UNUSED_MACROS, + UNUSED_MACRO_RULES, + UNUSED_ALLOCATION, + UNUSED_DOC_COMMENTS, + UNUSED_EXTERN_CRATES, + UNUSED_FEATURES, + UNUSED_LABELS, + UNUSED_PARENS, + UNUSED_BRACES, + REDUNDANT_SEMICOLONS + ); + + add_lint_group!( + "rust_2018_idioms", + BARE_TRAIT_OBJECTS, + UNUSED_EXTERN_CRATES, + ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, + ELIDED_LIFETIMES_IN_PATHS, + EXPLICIT_OUTLIVES_REQUIREMENTS // FIXME(#52665, #47816) not always applicable and not all + // macros are ready for this yet. + // UNREACHABLE_PUB, + + // FIXME macro crates are not up for this yet, too much + // breakage is seen if we try to encourage this lint. + // MACRO_USE_EXTERN_CRATE + ); + + // Register renamed and removed lints. + store.register_renamed("single_use_lifetime", "single_use_lifetimes"); + store.register_renamed("elided_lifetime_in_path", "elided_lifetimes_in_paths"); + store.register_renamed("bare_trait_object", "bare_trait_objects"); + store.register_renamed("unstable_name_collision", "unstable_name_collisions"); + store.register_renamed("unused_doc_comment", "unused_doc_comments"); + store.register_renamed("async_idents", "keyword_idents"); + store.register_renamed("exceeding_bitshifts", "arithmetic_overflow"); + store.register_renamed("redundant_semicolon", "redundant_semicolons"); + store.register_renamed("overlapping_patterns", "overlapping_range_endpoints"); + store.register_renamed("safe_packed_borrows", "unaligned_references"); + store.register_renamed("disjoint_capture_migration", "rust_2021_incompatible_closure_captures"); + store.register_renamed("or_patterns_back_compat", "rust_2021_incompatible_or_patterns"); + store.register_renamed("non_fmt_panic", "non_fmt_panics"); + + // These were moved to tool lints, but rustc still sees them when compiling normally, before + // tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use + // `register_removed` explicitly. + const RUSTDOC_LINTS: &[&str] = &[ + "broken_intra_doc_links", + "private_intra_doc_links", + "missing_crate_level_docs", + "missing_doc_code_examples", + "private_doc_tests", + "invalid_codeblock_attributes", + "invalid_html_tags", + "non_autolinks", + ]; + for rustdoc_lint in RUSTDOC_LINTS { + store.register_ignored(rustdoc_lint); + } + store.register_removed( + "intra_doc_link_resolution_failure", + "use `rustdoc::broken_intra_doc_links` instead", + ); + store.register_removed("rustdoc", "use `rustdoc::all` instead"); + + store.register_removed("unknown_features", "replaced by an error"); + store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate"); + store.register_removed("negate_unsigned", "cast a signed value instead"); + store.register_removed("raw_pointer_derive", "using derive with raw pointers is ok"); + // Register lint group aliases. + store.register_group_alias("nonstandard_style", "bad_style"); + // This was renamed to `raw_pointer_derive`, which was then removed, + // so it is also considered removed. + store.register_removed("raw_pointer_deriving", "using derive with raw pointers is ok"); + store.register_removed("drop_with_repr_extern", "drop flags have been removed"); + store.register_removed("fat_ptr_transmutes", "was accidentally removed back in 2014"); + store.register_removed("deprecated_attr", "use `deprecated` instead"); + store.register_removed( + "transmute_from_fn_item_types", + "always cast functions before transmuting them", + ); + store.register_removed( + "hr_lifetime_in_assoc_type", + "converted into hard error, see issue #33685 \ + <https://github.com/rust-lang/rust/issues/33685> for more information", + ); + store.register_removed( + "inaccessible_extern_crate", + "converted into hard error, see issue #36886 \ + <https://github.com/rust-lang/rust/issues/36886> for more information", + ); + store.register_removed( + "super_or_self_in_global_path", + "converted into hard error, see issue #36888 \ + <https://github.com/rust-lang/rust/issues/36888> for more information", + ); + store.register_removed( + "overlapping_inherent_impls", + "converted into hard error, see issue #36889 \ + <https://github.com/rust-lang/rust/issues/36889> for more information", + ); + store.register_removed( + "illegal_floating_point_constant_pattern", + "converted into hard error, see issue #36890 \ + <https://github.com/rust-lang/rust/issues/36890> for more information", + ); + store.register_removed( + "illegal_struct_or_enum_constant_pattern", + "converted into hard error, see issue #36891 \ + <https://github.com/rust-lang/rust/issues/36891> for more information", + ); + store.register_removed( + "lifetime_underscore", + "converted into hard error, see issue #36892 \ + <https://github.com/rust-lang/rust/issues/36892> for more information", + ); + store.register_removed( + "extra_requirement_in_impl", + "converted into hard error, see issue #37166 \ + <https://github.com/rust-lang/rust/issues/37166> for more information", + ); + store.register_removed( + "legacy_imports", + "converted into hard error, see issue #38260 \ + <https://github.com/rust-lang/rust/issues/38260> for more information", + ); + store.register_removed( + "coerce_never", + "converted into hard error, see issue #48950 \ + <https://github.com/rust-lang/rust/issues/48950> for more information", + ); + store.register_removed( + "resolve_trait_on_defaulted_unit", + "converted into hard error, see issue #48950 \ + <https://github.com/rust-lang/rust/issues/48950> for more information", + ); + store.register_removed( + "private_no_mangle_fns", + "no longer a warning, `#[no_mangle]` functions always exported", + ); + store.register_removed( + "private_no_mangle_statics", + "no longer a warning, `#[no_mangle]` statics always exported", + ); + store.register_removed("bad_repr", "replaced with a generic attribute input check"); + store.register_removed( + "duplicate_matcher_binding_name", + "converted into hard error, see issue #57742 \ + <https://github.com/rust-lang/rust/issues/57742> for more information", + ); + store.register_removed( + "incoherent_fundamental_impls", + "converted into hard error, see issue #46205 \ + <https://github.com/rust-lang/rust/issues/46205> for more information", + ); + store.register_removed( + "legacy_constructor_visibility", + "converted into hard error, see issue #39207 \ + <https://github.com/rust-lang/rust/issues/39207> for more information", + ); + store.register_removed( + "legacy_directory_ownership", + "converted into hard error, see issue #37872 \ + <https://github.com/rust-lang/rust/issues/37872> for more information", + ); + store.register_removed( + "safe_extern_statics", + "converted into hard error, see issue #36247 \ + <https://github.com/rust-lang/rust/issues/36247> for more information", + ); + store.register_removed( + "parenthesized_params_in_types_and_modules", + "converted into hard error, see issue #42238 \ + <https://github.com/rust-lang/rust/issues/42238> for more information", + ); + store.register_removed( + "duplicate_macro_exports", + "converted into hard error, see issue #35896 \ + <https://github.com/rust-lang/rust/issues/35896> for more information", + ); + store.register_removed( + "nested_impl_trait", + "converted into hard error, see issue #59014 \ + <https://github.com/rust-lang/rust/issues/59014> for more information", + ); + store.register_removed("plugin_as_library", "plugins have been deprecated and retired"); + store.register_removed( + "unsupported_naked_functions", + "converted into hard error, see RFC 2972 \ + <https://github.com/rust-lang/rfcs/blob/master/text/2972-constrained-naked.md> for more information", + ); + store.register_removed( + "mutable_borrow_reservation_conflict", + "now allowed, see issue #59159 \ + <https://github.com/rust-lang/rust/issues/59159> for more information", + ); +} + +fn register_internals(store: &mut LintStore) { + store.register_lints(&LintPassImpl::get_lints()); + store.register_early_pass(|| Box::new(LintPassImpl)); + store.register_lints(&DefaultHashTypes::get_lints()); + store.register_late_pass(|| Box::new(DefaultHashTypes)); + store.register_lints(&QueryStability::get_lints()); + store.register_late_pass(|| Box::new(QueryStability)); + store.register_lints(&ExistingDocKeyword::get_lints()); + store.register_late_pass(|| Box::new(ExistingDocKeyword)); + store.register_lints(&TyTyKind::get_lints()); + store.register_late_pass(|| Box::new(TyTyKind)); + store.register_lints(&Diagnostics::get_lints()); + store.register_late_pass(|| Box::new(Diagnostics)); + store.register_lints(&BadOptAccess::get_lints()); + store.register_late_pass(|| Box::new(BadOptAccess)); + store.register_lints(&PassByValue::get_lints()); + store.register_late_pass(|| Box::new(PassByValue)); + // FIXME(davidtwco): deliberately do not include `UNTRANSLATABLE_DIAGNOSTIC` and + // `DIAGNOSTIC_OUTSIDE_OF_IMPL` here because `-Wrustc::internal` is provided to every crate and + // these lints will trigger all of the time - change this once migration to diagnostic structs + // and translation is completed + store.register_group( + false, + "rustc::internal", + None, + vec![ + LintId::of(DEFAULT_HASH_TYPES), + LintId::of(POTENTIAL_QUERY_INSTABILITY), + LintId::of(USAGE_OF_TY_TYKIND), + LintId::of(PASS_BY_VALUE), + LintId::of(LINT_PASS_IMPL_WITHOUT_MACRO), + LintId::of(USAGE_OF_QUALIFIED_TY), + LintId::of(EXISTING_DOC_KEYWORD), + LintId::of(BAD_OPT_ACCESS), + ], + ); +} + +#[cfg(test)] +mod tests; diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs new file mode 100644 index 000000000..ff5a01749 --- /dev/null +++ b/compiler/rustc_lint/src/methods.rs @@ -0,0 +1,103 @@ +use crate::LateContext; +use crate::LateLintPass; +use crate::LintContext; +use rustc_errors::fluent; +use rustc_hir::{Expr, ExprKind, PathSegment}; +use rustc_middle::ty; +use rustc_span::{symbol::sym, ExpnKind, Span}; + +declare_lint! { + /// The `temporary_cstring_as_ptr` lint detects getting the inner pointer of + /// a temporary `CString`. + /// + /// ### Example + /// + /// ```rust + /// # #![allow(unused)] + /// # use std::ffi::CString; + /// let c_str = CString::new("foo").unwrap().as_ptr(); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The inner pointer of a `CString` lives only as long as the `CString` it + /// points to. Getting the inner pointer of a *temporary* `CString` allows the `CString` + /// to be dropped at the end of the statement, as it is not being referenced as far as the typesystem + /// is concerned. This means outside of the statement the pointer will point to freed memory, which + /// causes undefined behavior if the pointer is later dereferenced. + pub TEMPORARY_CSTRING_AS_PTR, + Warn, + "detects getting the inner pointer of a temporary `CString`" +} + +declare_lint_pass!(TemporaryCStringAsPtr => [TEMPORARY_CSTRING_AS_PTR]); + +fn in_macro(span: Span) -> bool { + if span.from_expansion() { + !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..)) + } else { + false + } +} + +fn first_method_call<'tcx>( + expr: &'tcx Expr<'tcx>, +) -> Option<(&'tcx PathSegment<'tcx>, &'tcx [Expr<'tcx>])> { + if let ExprKind::MethodCall(path, args, _) = &expr.kind { + if args.iter().any(|e| e.span.from_expansion()) { None } else { Some((path, *args)) } + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for TemporaryCStringAsPtr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_macro(expr.span) { + return; + } + + match first_method_call(expr) { + Some((path, args)) if path.ident.name == sym::as_ptr => { + let unwrap_arg = &args[0]; + let as_ptr_span = path.ident.span; + match first_method_call(unwrap_arg) { + Some((path, args)) + if path.ident.name == sym::unwrap || path.ident.name == sym::expect => + { + let source_arg = &args[0]; + lint_cstring_as_ptr(cx, as_ptr_span, source_arg, unwrap_arg); + } + _ => return, + } + } + _ => return, + } + } +} + +fn lint_cstring_as_ptr( + cx: &LateContext<'_>, + as_ptr_span: Span, + source: &rustc_hir::Expr<'_>, + unwrap: &rustc_hir::Expr<'_>, +) { + let source_type = cx.typeck_results().expr_ty(source); + if let ty::Adt(def, substs) = source_type.kind() { + if cx.tcx.is_diagnostic_item(sym::Result, def.did()) { + if let ty::Adt(adt, _) = substs.type_at(0).kind() { + if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) { + cx.struct_span_lint(TEMPORARY_CSTRING_AS_PTR, as_ptr_span, |diag| { + diag.build(fluent::lint::cstring_ptr) + .span_label(as_ptr_span, fluent::lint::as_ptr_label) + .span_label(unwrap.span, fluent::lint::unwrap_label) + .note(fluent::lint::note) + .help(fluent::lint::help) + .emit(); + }); + } + } + } + } +} diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs new file mode 100644 index 000000000..764003e61 --- /dev/null +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -0,0 +1,345 @@ +use crate::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_ast as ast; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::fluent; +use rustc_span::symbol::Symbol; + +declare_lint! { + /// The `non_ascii_idents` lint detects non-ASCII identifiers. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![allow(unused)] + /// #![deny(non_ascii_idents)] + /// fn main() { + /// let föö = 1; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint allows projects that wish to retain the limit of only using + /// ASCII characters to switch this lint to "forbid" (for example to ease + /// collaboration or for security reasons). + /// See [RFC 2457] for more details. + /// + /// [RFC 2457]: https://github.com/rust-lang/rfcs/blob/master/text/2457-non-ascii-idents.md + pub NON_ASCII_IDENTS, + Allow, + "detects non-ASCII identifiers", + crate_level_only +} + +declare_lint! { + /// The `uncommon_codepoints` lint detects uncommon Unicode codepoints in + /// identifiers. + /// + /// ### Example + /// + /// ```rust + /// # #![allow(unused)] + /// const µ: f64 = 0.000001; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint warns about using characters which are not commonly used, and may + /// cause visual confusion. + /// + /// This lint is triggered by identifiers that contain a codepoint that is + /// not part of the set of "Allowed" codepoints as described by [Unicode® + /// Technical Standard #39 Unicode Security Mechanisms Section 3.1 General + /// Security Profile for Identifiers][TR39Allowed]. + /// + /// Note that the set of uncommon codepoints may change over time. Beware + /// that if you "forbid" this lint that existing code may fail in the + /// future. + /// + /// [TR39Allowed]: https://www.unicode.org/reports/tr39/#General_Security_Profile + pub UNCOMMON_CODEPOINTS, + Warn, + "detects uncommon Unicode codepoints in identifiers", + crate_level_only +} + +declare_lint! { + /// The `confusable_idents` lint detects visually confusable pairs between + /// identifiers. + /// + /// ### Example + /// + /// ```rust + /// // Latin Capital Letter E With Caron + /// pub const Äš: i32 = 1; + /// // Latin Capital Letter E With Breve + /// pub const Ä”: i32 = 2; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint warns when different identifiers may appear visually similar, + /// which can cause confusion. + /// + /// The confusable detection algorithm is based on [Unicode® Technical + /// Standard #39 Unicode Security Mechanisms Section 4 Confusable + /// Detection][TR39Confusable]. For every distinct identifier X execute + /// the function `skeleton(X)`. If there exist two distinct identifiers X + /// and Y in the same crate where `skeleton(X) = skeleton(Y)` report it. + /// The compiler uses the same mechanism to check if an identifier is too + /// similar to a keyword. + /// + /// Note that the set of confusable characters may change over time. + /// Beware that if you "forbid" this lint that existing code may fail in + /// the future. + /// + /// [TR39Confusable]: https://www.unicode.org/reports/tr39/#Confusable_Detection + pub CONFUSABLE_IDENTS, + Warn, + "detects visually confusable pairs between identifiers", + crate_level_only +} + +declare_lint! { + /// The `mixed_script_confusables` lint detects visually confusable + /// characters in identifiers between different [scripts]. + /// + /// [scripts]: https://en.wikipedia.org/wiki/Script_(Unicode) + /// + /// ### Example + /// + /// ```rust + /// // The Japanese katakana character エ can be confused with the Han character å·¥. + /// const エ: &'static str = "アイウ"; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint warns when characters between different scripts may appear + /// visually similar, which can cause confusion. + /// + /// If the crate contains other identifiers in the same script that have + /// non-confusable characters, then this lint will *not* be issued. For + /// example, if the example given above has another identifier with + /// katakana characters (such as `let カタカナ = 123;`), then this indicates + /// that you are intentionally using katakana, and it will not warn about + /// it. + /// + /// Note that the set of confusable characters may change over time. + /// Beware that if you "forbid" this lint that existing code may fail in + /// the future. + pub MIXED_SCRIPT_CONFUSABLES, + Warn, + "detects Unicode scripts whose mixed script confusables codepoints are solely used", + crate_level_only +} + +declare_lint_pass!(NonAsciiIdents => [NON_ASCII_IDENTS, UNCOMMON_CODEPOINTS, CONFUSABLE_IDENTS, MIXED_SCRIPT_CONFUSABLES]); + +impl EarlyLintPass for NonAsciiIdents { + fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { + use rustc_session::lint::Level; + use rustc_span::Span; + use std::collections::BTreeMap; + use unicode_security::GeneralSecurityProfile; + + let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).0 != Level::Allow; + let check_uncommon_codepoints = + cx.builder.lint_level(UNCOMMON_CODEPOINTS).0 != Level::Allow; + let check_confusable_idents = cx.builder.lint_level(CONFUSABLE_IDENTS).0 != Level::Allow; + let check_mixed_script_confusables = + cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).0 != Level::Allow; + + if !check_non_ascii_idents + && !check_uncommon_codepoints + && !check_confusable_idents + && !check_mixed_script_confusables + { + return; + } + + let mut has_non_ascii_idents = false; + let symbols = cx.sess().parse_sess.symbol_gallery.symbols.lock(); + + // Sort by `Span` so that error messages make sense with respect to the + // order of identifier locations in the code. + let mut symbols: Vec<_> = symbols.iter().collect(); + symbols.sort_by_key(|k| k.1); + + for (symbol, &sp) in symbols.iter() { + let symbol_str = symbol.as_str(); + if symbol_str.is_ascii() { + continue; + } + has_non_ascii_idents = true; + cx.struct_span_lint(NON_ASCII_IDENTS, sp, |lint| { + lint.build(fluent::lint::identifier_non_ascii_char).emit(); + }); + if check_uncommon_codepoints + && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed) + { + cx.struct_span_lint(UNCOMMON_CODEPOINTS, sp, |lint| { + lint.build(fluent::lint::identifier_uncommon_codepoints).emit(); + }) + } + } + + if has_non_ascii_idents && check_confusable_idents { + let mut skeleton_map: FxHashMap<Symbol, (Symbol, Span, bool)> = + FxHashMap::with_capacity_and_hasher(symbols.len(), Default::default()); + let mut skeleton_buf = String::new(); + + for (&symbol, &sp) in symbols.iter() { + use unicode_security::confusable_detection::skeleton; + + let symbol_str = symbol.as_str(); + let is_ascii = symbol_str.is_ascii(); + + // Get the skeleton as a `Symbol`. + skeleton_buf.clear(); + skeleton_buf.extend(skeleton(&symbol_str)); + let skeleton_sym = if *symbol_str == *skeleton_buf { + symbol + } else { + Symbol::intern(&skeleton_buf) + }; + + skeleton_map + .entry(skeleton_sym) + .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| { + if !*existing_is_ascii || !is_ascii { + cx.struct_span_lint(CONFUSABLE_IDENTS, sp, |lint| { + lint.build(fluent::lint::confusable_identifier_pair) + .set_arg("existing_sym", *existing_symbol) + .set_arg("sym", symbol) + .span_label(*existing_span, fluent::lint::label) + .emit(); + }); + } + if *existing_is_ascii && !is_ascii { + *existing_symbol = symbol; + *existing_span = sp; + *existing_is_ascii = is_ascii; + } + }) + .or_insert((symbol, sp, is_ascii)); + } + } + + if has_non_ascii_idents && check_mixed_script_confusables { + use unicode_security::is_potential_mixed_script_confusable_char; + use unicode_security::mixed_script::AugmentedScriptSet; + + #[derive(Clone)] + enum ScriptSetUsage { + Suspicious(Vec<char>, Span), + Verified, + } + + let mut script_states: FxHashMap<AugmentedScriptSet, ScriptSetUsage> = + FxHashMap::default(); + let latin_augmented_script_set = AugmentedScriptSet::for_char('A'); + script_states.insert(latin_augmented_script_set, ScriptSetUsage::Verified); + + let mut has_suspicous = false; + for (symbol, &sp) in symbols.iter() { + let symbol_str = symbol.as_str(); + for ch in symbol_str.chars() { + if ch.is_ascii() { + // all ascii characters are covered by exception. + continue; + } + if !GeneralSecurityProfile::identifier_allowed(ch) { + // this character is covered by `uncommon_codepoints` lint. + continue; + } + let augmented_script_set = AugmentedScriptSet::for_char(ch); + script_states + .entry(augmented_script_set) + .and_modify(|existing_state| { + if let ScriptSetUsage::Suspicious(ch_list, _) = existing_state { + if is_potential_mixed_script_confusable_char(ch) { + ch_list.push(ch); + } else { + *existing_state = ScriptSetUsage::Verified; + } + } + }) + .or_insert_with(|| { + if !is_potential_mixed_script_confusable_char(ch) { + ScriptSetUsage::Verified + } else { + has_suspicous = true; + ScriptSetUsage::Suspicious(vec![ch], sp) + } + }); + } + } + + if has_suspicous { + let verified_augmented_script_sets = script_states + .iter() + .flat_map(|(k, v)| match v { + ScriptSetUsage::Verified => Some(*k), + _ => None, + }) + .collect::<Vec<_>>(); + + // we're sorting the output here. + let mut lint_reports: BTreeMap<(Span, Vec<char>), AugmentedScriptSet> = + BTreeMap::new(); + + 'outerloop: for (augment_script_set, usage) in script_states { + let ScriptSetUsage::Suspicious(mut ch_list, sp) = usage else { continue }; + + if augment_script_set.is_all() { + continue; + } + + for existing in verified_augmented_script_sets.iter() { + if existing.is_all() { + continue; + } + let mut intersect = *existing; + intersect.intersect_with(augment_script_set); + if !intersect.is_empty() && !intersect.is_all() { + continue 'outerloop; + } + } + + // We sort primitive chars here and can use unstable sort + ch_list.sort_unstable(); + ch_list.dedup(); + lint_reports.insert((sp, ch_list), augment_script_set); + } + + for ((sp, ch_list), script_set) in lint_reports { + cx.struct_span_lint(MIXED_SCRIPT_CONFUSABLES, sp, |lint| { + let mut includes = String::new(); + for (idx, ch) in ch_list.into_iter().enumerate() { + if idx != 0 { + includes += ", "; + } + let char_info = format!("'{}' (U+{:04X})", ch, ch as u32); + includes += &char_info; + } + lint.build(fluent::lint::mixed_script_confusables) + .set_arg("set", script_set.to_string()) + .set_arg("includes", includes) + .note(fluent::lint::includes_note) + .note(fluent::lint::note) + .emit(); + }); + } + } + } + } +} diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs new file mode 100644 index 000000000..cdad2d2e8 --- /dev/null +++ b/compiler/rustc_lint/src/non_fmt_panic.rs @@ -0,0 +1,357 @@ +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_errors::{fluent, Applicability}; +use rustc_hir as hir; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_middle::ty::subst::InternalSubsts; +use rustc_parse_format::{ParseMode, Parser, Piece}; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_span::edition::Edition; +use rustc_span::{hygiene, sym, symbol::kw, InnerSpan, Span, Symbol}; +use rustc_trait_selection::infer::InferCtxtExt; + +declare_lint! { + /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first + /// argument is not a formatting string. + /// + /// ### Example + /// + /// ```rust,no_run,edition2018 + /// panic!("{}"); + /// panic!(123); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message. + /// That means that `panic!("{}")` panics with the message `"{}"` instead + /// of using it as a formatting string, and `panic!(123)` will panic with + /// an `i32` as message. + /// + /// Rust 2021 always interprets the first argument as format string. + NON_FMT_PANICS, + Warn, + "detect single-argument panic!() invocations in which the argument is not a format string", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021), + explain_reason: false, + }; + report_in_external_macro +} + +declare_lint_pass!(NonPanicFmt => [NON_FMT_PANICS]); + +impl<'tcx> LateLintPass<'tcx> for NonPanicFmt { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Call(f, [arg]) = &expr.kind { + if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() { + let f_diagnostic_name = cx.tcx.get_diagnostic_name(def_id); + + if Some(def_id) == cx.tcx.lang_items().begin_panic_fn() + || Some(def_id) == cx.tcx.lang_items().panic_fn() + || f_diagnostic_name == Some(sym::panic_str) + { + if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id { + if matches!( + cx.tcx.get_diagnostic_name(id), + Some(sym::core_panic_2015_macro | sym::std_panic_2015_macro) + ) { + check_panic(cx, f, arg); + } + } + } else if f_diagnostic_name == Some(sym::unreachable_display) { + if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id { + if cx.tcx.is_diagnostic_item(sym::unreachable_2015_macro, id) { + check_panic( + cx, + f, + // This is safe because we checked above that the callee is indeed + // unreachable_display + match &arg.kind { + // Get the borrowed arg not the borrow + hir::ExprKind::AddrOf(ast::BorrowKind::Ref, _, arg) => arg, + _ => bug!("call to unreachable_display without borrow"), + }, + ); + } + } + } + } + } + } +} + +fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Lit(lit) = &arg.kind { + if let ast::LitKind::Str(sym, _) = lit.node { + // The argument is a string literal. + check_panic_str(cx, f, arg, sym.as_str()); + return; + } + } + + // The argument is *not* a string literal. + + let (span, panic, symbol) = panic_call(cx, f); + + if in_external_macro(cx.sess(), span) { + // Nothing that can be done about it in the current crate. + return; + } + + // Find the span of the argument to `panic!()` or `unreachable!`, before expansion in the + // case of `panic!(some_macro!())` or `unreachable!(some_macro!())`. + // We don't use source_callsite(), because this `panic!(..)` might itself + // be expanded from another macro, in which case we want to stop at that + // expansion. + let mut arg_span = arg.span; + let mut arg_macro = None; + while !span.contains(arg_span) { + let expn = arg_span.ctxt().outer_expn_data(); + if expn.is_root() { + break; + } + arg_macro = expn.macro_def_id; + arg_span = expn.call_site; + } + + cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| { + let mut l = lint.build(fluent::lint::non_fmt_panic); + l.set_arg("name", symbol); + l.note(fluent::lint::note); + l.note(fluent::lint::more_info_note); + if !is_arg_inside_call(arg_span, span) { + // No clue where this argument is coming from. + l.emit(); + return; + } + if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) { + // A case of `panic!(format!(..))`. + l.note(fluent::lint::supports_fmt_note); + if let Some((open, close, _)) = find_delimiters(cx, arg_span) { + l.multipart_suggestion( + fluent::lint::supports_fmt_suggestion, + vec![ + (arg_span.until(open.shrink_to_hi()), "".into()), + (close.until(arg_span.shrink_to_hi()), "".into()), + ], + Applicability::MachineApplicable, + ); + } + } else { + let ty = cx.typeck_results().expr_ty(arg); + // If this is a &str or String, we can confidently give the `"{}", ` suggestion. + let is_str = matches!( + ty.kind(), + ty::Ref(_, r, _) if *r.kind() == ty::Str, + ) || matches!( + ty.ty_adt_def(), + Some(ty_def) if cx.tcx.is_diagnostic_item(sym::String, ty_def.did()), + ); + + let (suggest_display, suggest_debug) = cx.tcx.infer_ctxt().enter(|infcx| { + let display = is_str + || cx.tcx.get_diagnostic_item(sym::Display).map(|t| { + infcx + .type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env) + .may_apply() + }) == Some(true); + let debug = !display + && cx.tcx.get_diagnostic_item(sym::Debug).map(|t| { + infcx + .type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env) + .may_apply() + }) == Some(true); + (display, debug) + }); + + let suggest_panic_any = !is_str && panic == sym::std_panic_macro; + + let fmt_applicability = if suggest_panic_any { + // If we can use panic_any, use that as the MachineApplicable suggestion. + Applicability::MaybeIncorrect + } else { + // If we don't suggest panic_any, using a format string is our best bet. + Applicability::MachineApplicable + }; + + if suggest_display { + l.span_suggestion_verbose( + arg_span.shrink_to_lo(), + fluent::lint::display_suggestion, + "\"{}\", ", + fmt_applicability, + ); + } else if suggest_debug { + l.set_arg("ty", ty); + l.span_suggestion_verbose( + arg_span.shrink_to_lo(), + fluent::lint::debug_suggestion, + "\"{:?}\", ", + fmt_applicability, + ); + } + + if suggest_panic_any { + if let Some((open, close, del)) = find_delimiters(cx, span) { + l.set_arg("already_suggested", suggest_display || suggest_debug); + l.multipart_suggestion( + fluent::lint::panic_suggestion, + if del == '(' { + vec![(span.until(open), "std::panic::panic_any".into())] + } else { + vec![ + (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()), + (close, ")".into()), + ] + }, + Applicability::MachineApplicable, + ); + } + } + } + l.emit(); + }); +} + +fn check_panic_str<'tcx>( + cx: &LateContext<'tcx>, + f: &'tcx hir::Expr<'tcx>, + arg: &'tcx hir::Expr<'tcx>, + fmt: &str, +) { + if !fmt.contains(&['{', '}']) { + // No brace, no problem. + return; + } + + let (span, _, _) = panic_call(cx, f); + + if in_external_macro(cx.sess(), span) && in_external_macro(cx.sess(), arg.span) { + // Nothing that can be done about it in the current crate. + return; + } + + let fmt_span = arg.span.source_callsite(); + + let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) { + Ok(snippet) => { + // Count the number of `#`s between the `r` and `"`. + let style = snippet.strip_prefix('r').and_then(|s| s.find('"')); + (Some(snippet), style) + } + Err(_) => (None, None), + }; + + let mut fmt_parser = Parser::new(fmt, style, snippet.clone(), false, ParseMode::Format); + let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count(); + + if n_arguments > 0 && fmt_parser.errors.is_empty() { + let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] { + [] => vec![fmt_span], + v => v + .iter() + .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end))) + .collect(), + }; + cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| { + let mut l = lint.build(fluent::lint::non_fmt_panic_unused); + l.set_arg("count", n_arguments); + l.note(fluent::lint::note); + if is_arg_inside_call(arg.span, span) { + l.span_suggestion( + arg.span.shrink_to_hi(), + fluent::lint::add_args_suggestion, + ", ...", + Applicability::HasPlaceholders, + ); + l.span_suggestion( + arg.span.shrink_to_lo(), + fluent::lint::add_fmt_suggestion, + "\"{}\", ", + Applicability::MachineApplicable, + ); + } + l.emit(); + }); + } else { + let brace_spans: Option<Vec<_>> = + snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| { + s.char_indices() + .filter(|&(_, c)| c == '{' || c == '}') + .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 })) + .collect() + }); + let count = brace_spans.as_ref().map(|v| v.len()).unwrap_or(/* any number >1 */ 2); + cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| { + let mut l = lint.build(fluent::lint::non_fmt_panic_braces); + l.set_arg("count", count); + l.note(fluent::lint::note); + if is_arg_inside_call(arg.span, span) { + l.span_suggestion( + arg.span.shrink_to_lo(), + fluent::lint::suggestion, + "\"{}\", ", + Applicability::MachineApplicable, + ); + } + l.emit(); + }); + } +} + +/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`, +/// and the type of (opening) delimiter used. +fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> { + let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?; + let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?; + let close = snippet.rfind(|c| ")]}".contains(c))?; + Some(( + span.from_inner(InnerSpan { start: open, end: open + 1 }), + span.from_inner(InnerSpan { start: close, end: close + 1 }), + open_ch, + )) +} + +fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, Symbol) { + let mut expn = f.span.ctxt().outer_expn_data(); + + let mut panic_macro = kw::Empty; + + // Unwrap more levels of macro expansion, as panic_2015!() + // was likely expanded from panic!() and possibly from + // [debug_]assert!(). + loop { + let parent = expn.call_site.ctxt().outer_expn_data(); + let Some(id) = parent.macro_def_id else { break }; + let Some(name) = cx.tcx.get_diagnostic_name(id) else { break }; + if !matches!( + name, + sym::core_panic_macro + | sym::std_panic_macro + | sym::assert_macro + | sym::debug_assert_macro + | sym::unreachable_macro + ) { + break; + } + expn = parent; + panic_macro = name; + } + + let macro_symbol = + if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic }; + (expn.call_site, panic_macro, macro_symbol) +} + +fn is_arg_inside_call(arg: Span, call: Span) -> bool { + // We only add suggestions if the argument we're looking at appears inside the + // panic call in the source file, to avoid invalid suggestions when macros are involved. + // We specifically check for the spans to not be identical, as that happens sometimes when + // proc_macros lie about spans and apply the same span to all the tokens they produce. + call.contains(arg) && !call.source_equal(arg) +} diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs new file mode 100644 index 000000000..8d04d68bf --- /dev/null +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -0,0 +1,565 @@ +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_attr as attr; +use rustc_errors::{fluent, Applicability}; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{GenericParamKind, PatKind}; +use rustc_middle::ty; +use rustc_span::symbol::sym; +use rustc_span::{symbol::Ident, BytePos, Span}; +use rustc_target::spec::abi::Abi; + +#[derive(PartialEq)] +pub enum MethodLateContext { + TraitAutoImpl, + TraitImpl, + PlainImpl, +} + +pub fn method_context(cx: &LateContext<'_>, id: hir::HirId) -> MethodLateContext { + let def_id = cx.tcx.hir().local_def_id(id); + let item = cx.tcx.associated_item(def_id); + match item.container { + ty::TraitContainer => MethodLateContext::TraitAutoImpl, + ty::ImplContainer => match cx.tcx.impl_trait_ref(item.container_id(cx.tcx)) { + Some(_) => MethodLateContext::TraitImpl, + None => MethodLateContext::PlainImpl, + }, + } +} + +declare_lint! { + /// The `non_camel_case_types` lint detects types, variants, traits and + /// type parameters that don't have camel case names. + /// + /// ### Example + /// + /// ```rust + /// struct my_struct; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The preferred style for these identifiers is to use "camel case", such + /// as `MyStruct`, where the first letter should not be lowercase, and + /// should not use underscores between letters. Underscores are allowed at + /// the beginning and end of the identifier, as well as between + /// non-letters (such as `X86_64`). + pub NON_CAMEL_CASE_TYPES, + Warn, + "types, variants, traits and type parameters should have camel case names" +} + +declare_lint_pass!(NonCamelCaseTypes => [NON_CAMEL_CASE_TYPES]); + +/// Some unicode characters *have* case, are considered upper case or lower case, but they *can't* +/// be upper cased or lower cased. For the purposes of the lint suggestion, we care about being able +/// to change the char's case. +fn char_has_case(c: char) -> bool { + let mut l = c.to_lowercase(); + let mut u = c.to_uppercase(); + while let Some(l) = l.next() { + match u.next() { + Some(u) if l != u => return true, + _ => {} + } + } + u.next().is_some() +} + +fn is_camel_case(name: &str) -> bool { + let name = name.trim_matches('_'); + if name.is_empty() { + return true; + } + + // start with a non-lowercase letter rather than non-uppercase + // ones (some scripts don't have a concept of upper/lowercase) + !name.chars().next().unwrap().is_lowercase() + && !name.contains("__") + && !name.chars().collect::<Vec<_>>().array_windows().any(|&[fst, snd]| { + // contains a capitalisable character followed by, or preceded by, an underscore + char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_' + }) +} + +fn to_camel_case(s: &str) -> String { + s.trim_matches('_') + .split('_') + .filter(|component| !component.is_empty()) + .map(|component| { + let mut camel_cased_component = String::new(); + + let mut new_word = true; + let mut prev_is_lower_case = true; + + for c in component.chars() { + // Preserve the case if an uppercase letter follows a lowercase letter, so that + // `camelCase` is converted to `CamelCase`. + if prev_is_lower_case && c.is_uppercase() { + new_word = true; + } + + if new_word { + camel_cased_component.extend(c.to_uppercase()); + } else { + camel_cased_component.extend(c.to_lowercase()); + } + + prev_is_lower_case = c.is_lowercase(); + new_word = false; + } + + camel_cased_component + }) + .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| { + // separate two components with an underscore if their boundary cannot + // be distinguished using an uppercase/lowercase case distinction + let join = if let Some(prev) = prev { + let l = prev.chars().last().unwrap(); + let f = next.chars().next().unwrap(); + !char_has_case(l) && !char_has_case(f) + } else { + false + }; + (acc + if join { "_" } else { "" } + &next, Some(next)) + }) + .0 +} + +impl NonCamelCaseTypes { + fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) { + let name = ident.name.as_str(); + + if !is_camel_case(name) { + cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, |lint| { + let mut err = lint.build(fluent::lint::non_camel_case_type); + let cc = to_camel_case(name); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Lowercase Letter". + if *name != cc { + err.span_suggestion( + ident.span, + fluent::lint::suggestion, + to_camel_case(name), + Applicability::MaybeIncorrect, + ); + } else { + err.span_label(ident.span, fluent::lint::label); + } + + err.set_arg("sort", sort); + err.set_arg("name", name); + err.emit(); + }) + } + } +} + +impl EarlyLintPass for NonCamelCaseTypes { + fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { + let has_repr_c = it + .attrs + .iter() + .any(|attr| attr::find_repr_attrs(cx.sess(), attr).contains(&attr::ReprC)); + + if has_repr_c { + return; + } + + match it.kind { + ast::ItemKind::TyAlias(..) + | ast::ItemKind::Enum(..) + | ast::ItemKind::Struct(..) + | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident), + ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident), + ast::ItemKind::TraitAlias(..) => self.check_case(cx, "trait alias", &it.ident), + _ => (), + } + } + + fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { + if let ast::AssocItemKind::TyAlias(..) = it.kind { + self.check_case(cx, "associated type", &it.ident); + } + } + + fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) { + self.check_case(cx, "variant", &v.ident); + } + + fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) { + if let ast::GenericParamKind::Type { .. } = param.kind { + self.check_case(cx, "type parameter", ¶m.ident); + } + } +} + +declare_lint! { + /// The `non_snake_case` lint detects variables, methods, functions, + /// lifetime parameters and modules that don't have snake case names. + /// + /// ### Example + /// + /// ```rust + /// let MY_VALUE = 5; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The preferred style for these identifiers is to use "snake case", + /// where all the characters are in lowercase, with words separated with a + /// single underscore, such as `my_value`. + pub NON_SNAKE_CASE, + Warn, + "variables, methods, functions, lifetime parameters and modules should have snake case names" +} + +declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]); + +impl NonSnakeCase { + fn to_snake_case(mut str: &str) -> String { + let mut words = vec![]; + // Preserve leading underscores + str = str.trim_start_matches(|c: char| { + if c == '_' { + words.push(String::new()); + true + } else { + false + } + }); + for s in str.split('_') { + let mut last_upper = false; + let mut buf = String::new(); + if s.is_empty() { + continue; + } + for ch in s.chars() { + if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper { + words.push(buf); + buf = String::new(); + } + last_upper = ch.is_uppercase(); + buf.extend(ch.to_lowercase()); + } + words.push(buf); + } + words.join("_") + } + + /// Checks if a given identifier is snake case, and reports a diagnostic if not. + fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) { + fn is_snake_case(ident: &str) -> bool { + if ident.is_empty() { + return true; + } + let ident = ident.trim_start_matches('\''); + let ident = ident.trim_matches('_'); + + let mut allow_underscore = true; + ident.chars().all(|c| { + allow_underscore = match c { + '_' if !allow_underscore => return false, + '_' => false, + // It would be more obvious to use `c.is_lowercase()`, + // but some characters do not have a lowercase form + c if !c.is_uppercase() => true, + _ => return false, + }; + true + }) + } + + let name = ident.name.as_str(); + + if !is_snake_case(name) { + cx.struct_span_lint(NON_SNAKE_CASE, ident.span, |lint| { + let sc = NonSnakeCase::to_snake_case(name); + let mut err = lint.build(fluent::lint::non_snake_case); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Uppercase Letter". + if name != sc { + // We have a valid span in almost all cases, but we don't have one when linting a crate + // name provided via the command line. + if !ident.span.is_dummy() { + let sc_ident = Ident::from_str_and_span(&sc, ident.span); + let (message, suggestion) = if sc_ident.is_reserved() { + // We shouldn't suggest a reserved identifier to fix non-snake-case identifiers. + // Instead, recommend renaming the identifier entirely or, if permitted, + // escaping it to create a raw identifier. + if sc_ident.name.can_be_raw() { + (fluent::lint::rename_or_convert_suggestion, sc_ident.to_string()) + } else { + err.note(fluent::lint::cannot_convert_note); + (fluent::lint::rename_suggestion, String::new()) + } + } else { + (fluent::lint::convert_suggestion, sc.clone()) + }; + + err.span_suggestion( + ident.span, + message, + suggestion, + Applicability::MaybeIncorrect, + ); + } else { + err.help(fluent::lint::help); + } + } else { + err.span_label(ident.span, fluent::lint::label); + } + + err.set_arg("sort", sort); + err.set_arg("name", name); + err.set_arg("sc", sc); + err.emit(); + }); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { + fn check_mod( + &mut self, + cx: &LateContext<'_>, + _: &'tcx hir::Mod<'tcx>, + _: Span, + id: hir::HirId, + ) { + if id != hir::CRATE_HIR_ID { + return; + } + + let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name { + Some(Ident::from_str(name)) + } else { + cx.sess() + .find_by_name(&cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name) + .and_then(|attr| attr.meta()) + .and_then(|meta| { + meta.name_value_literal().and_then(|lit| { + if let ast::LitKind::Str(name, ..) = lit.kind { + // Discard the double quotes surrounding the literal. + let sp = cx + .sess() + .source_map() + .span_to_snippet(lit.span) + .ok() + .and_then(|snippet| { + let left = snippet.find('"')?; + let right = + snippet.rfind('"').map(|pos| snippet.len() - pos)?; + + Some( + lit.span + .with_lo(lit.span.lo() + BytePos(left as u32 + 1)) + .with_hi(lit.span.hi() - BytePos(right as u32)), + ) + }) + .unwrap_or(lit.span); + + Some(Ident::new(name, sp)) + } else { + None + } + }) + }) + }; + + if let Some(ident) = &crate_ident { + self.check_snake_case(cx, "crate", ident); + } + } + + fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) { + if let GenericParamKind::Lifetime { .. } = param.kind { + self.check_snake_case(cx, "lifetime", ¶m.name.ident()); + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'_>, + fk: FnKind<'_>, + _: &hir::FnDecl<'_>, + _: &hir::Body<'_>, + _: Span, + id: hir::HirId, + ) { + let attrs = cx.tcx.hir().attrs(id); + match &fk { + FnKind::Method(ident, sig, ..) => match method_context(cx, id) { + MethodLateContext::PlainImpl => { + if sig.header.abi != Abi::Rust && cx.sess().contains_name(attrs, sym::no_mangle) + { + return; + } + self.check_snake_case(cx, "method", ident); + } + MethodLateContext::TraitAutoImpl => { + self.check_snake_case(cx, "trait method", ident); + } + _ => (), + }, + FnKind::ItemFn(ident, _, header) => { + // Skip foreign-ABI #[no_mangle] functions (Issue #31924) + if header.abi != Abi::Rust && cx.sess().contains_name(attrs, sym::no_mangle) { + return; + } + self.check_snake_case(cx, "function", ident); + } + FnKind::Closure => (), + } + } + + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + if let hir::ItemKind::Mod(_) = it.kind { + self.check_snake_case(cx, "module", &it.ident); + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) { + if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind { + self.check_snake_case(cx, "trait method", &item.ident); + for param_name in pnames { + self.check_snake_case(cx, "variable", param_name); + } + } + } + + fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) { + if let PatKind::Binding(_, hid, ident, _) = p.kind { + if let hir::Node::Pat(parent_pat) = cx.tcx.hir().get(cx.tcx.hir().get_parent_node(hid)) + { + if let PatKind::Struct(_, field_pats, _) = &parent_pat.kind { + if field_pats + .iter() + .any(|field| !field.is_shorthand && field.pat.hir_id == p.hir_id) + { + // Only check if a new name has been introduced, to avoid warning + // on both the struct definition and this pattern. + self.check_snake_case(cx, "variable", &ident); + } + return; + } + } + self.check_snake_case(cx, "variable", &ident); + } + } + + fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) { + for sf in s.fields() { + self.check_snake_case(cx, "structure field", &sf.ident); + } + } +} + +declare_lint! { + /// The `non_upper_case_globals` lint detects static items that don't have + /// uppercase identifiers. + /// + /// ### Example + /// + /// ```rust + /// static max_points: i32 = 5; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The preferred style is for static item names to use all uppercase + /// letters such as `MAX_POINTS`. + pub NON_UPPER_CASE_GLOBALS, + Warn, + "static constants should have uppercase identifiers" +} + +declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]); + +impl NonUpperCaseGlobals { + fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) { + let name = ident.name.as_str(); + if name.chars().any(|c| c.is_lowercase()) { + cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, |lint| { + let uc = NonSnakeCase::to_snake_case(&name).to_uppercase(); + let mut err = lint.build(fluent::lint::non_upper_case_global); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Lowercase Letter". + if *name != uc { + err.span_suggestion( + ident.span, + fluent::lint::suggestion, + uc, + Applicability::MaybeIncorrect, + ); + } else { + err.span_label(ident.span, fluent::lint::label); + } + + err.set_arg("sort", sort); + err.set_arg("name", name); + err.emit(); + }) + } + } +} + +impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + let attrs = cx.tcx.hir().attrs(it.hir_id()); + match it.kind { + hir::ItemKind::Static(..) if !cx.sess().contains_name(attrs, sym::no_mangle) => { + NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident); + } + hir::ItemKind::Const(..) => { + NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident); + } + _ => {} + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) { + if let hir::TraitItemKind::Const(..) = ti.kind { + NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) { + if let hir::ImplItemKind::Const(..) = ii.kind { + NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident); + } + } + + fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) { + // Lint for constants that look like binding identifiers (#7526) + if let PatKind::Path(hir::QPath::Resolved(None, ref path)) = p.kind { + if let Res::Def(DefKind::Const, _) = path.res { + if path.segments.len() == 1 { + NonUpperCaseGlobals::check_upper_case( + cx, + "constant in pattern", + &path.segments[0].ident, + ); + } + } + } + } + + fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) { + if let GenericParamKind::Const { .. } = param.kind { + NonUpperCaseGlobals::check_upper_case(cx, "const parameter", ¶m.name.ident()); + } + } +} + +#[cfg(test)] +mod tests; diff --git a/compiler/rustc_lint/src/nonstandard_style/tests.rs b/compiler/rustc_lint/src/nonstandard_style/tests.rs new file mode 100644 index 000000000..39c525b86 --- /dev/null +++ b/compiler/rustc_lint/src/nonstandard_style/tests.rs @@ -0,0 +1,21 @@ +use super::{is_camel_case, to_camel_case}; + +#[test] +fn camel_case() { + assert!(!is_camel_case("userData")); + assert_eq!(to_camel_case("userData"), "UserData"); + + assert!(is_camel_case("X86_64")); + + assert!(!is_camel_case("X86__64")); + assert_eq!(to_camel_case("X86__64"), "X86_64"); + + assert!(!is_camel_case("Abc_123")); + assert_eq!(to_camel_case("Abc_123"), "Abc123"); + + assert!(!is_camel_case("A1_b2_c3")); + assert_eq!(to_camel_case("A1_b2_c3"), "A1B2C3"); + + assert!(!is_camel_case("ONE_TWO_THREE")); + assert_eq!(to_camel_case("ONE_TWO_THREE"), "OneTwoThree"); +} diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs new file mode 100644 index 000000000..11a752ff0 --- /dev/null +++ b/compiler/rustc_lint/src/noop_method_call.rs @@ -0,0 +1,103 @@ +use crate::context::LintContext; +use crate::rustc_middle::ty::TypeVisitable; +use crate::LateContext; +use crate::LateLintPass; +use rustc_errors::fluent; +use rustc_hir::def::DefKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +declare_lint! { + /// The `noop_method_call` lint detects specific calls to noop methods + /// such as a calling `<&T as Clone>::clone` where `T: !Clone`. + /// + /// ### Example + /// + /// ```rust + /// # #![allow(unused)] + /// #![warn(noop_method_call)] + /// struct Foo; + /// let foo = &Foo; + /// let clone: &Foo = foo.clone(); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Some method calls are noops meaning that they do nothing. Usually such methods + /// are the result of blanket implementations that happen to create some method invocations + /// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but + /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything + /// as references are copy. This lint detects these calls and warns the user about them. + pub NOOP_METHOD_CALL, + Allow, + "detects the use of well-known noop methods" +} + +declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL]); + +impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // We only care about method calls. + let ExprKind::MethodCall(call, elements, _) = &expr.kind else { + return + }; + // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow` + // traits and ignore any other method call. + let (trait_id, did) = match cx.typeck_results().type_dependent_def(expr.hir_id) { + // Verify we are dealing with a method/associated function. + Some((DefKind::AssocFn, did)) => match cx.tcx.trait_of_item(did) { + // Check that we're dealing with a trait method for one of the traits we care about. + Some(trait_id) + if matches!( + cx.tcx.get_diagnostic_name(trait_id), + Some(sym::Borrow | sym::Clone | sym::Deref) + ) => + { + (trait_id, did) + } + _ => return, + }, + _ => return, + }; + let substs = cx.typeck_results().node_substs(expr.hir_id); + if substs.needs_subst() { + // We can't resolve on types that require monomorphization, so we don't handle them if + // we need to perform substitution. + return; + } + let param_env = cx.tcx.param_env(trait_id); + // Resolve the trait method instance. + let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, param_env, did, substs) else { + return + }; + // (Re)check that it implements the noop diagnostic. + let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return }; + if !matches!( + name, + sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref + ) { + return; + } + let receiver = &elements[0]; + let receiver_ty = cx.typeck_results().expr_ty(receiver); + let expr_ty = cx.typeck_results().expr_ty_adjusted(expr); + if receiver_ty != expr_ty { + // This lint will only trigger if the receiver type and resulting expression \ + // type are the same, implying that the method call is unnecessary. + return; + } + let expr_span = expr.span; + let span = expr_span.with_lo(receiver.span.hi()); + cx.struct_span_lint(NOOP_METHOD_CALL, span, |lint| { + lint.build(fluent::lint::noop_method_call) + .set_arg("method", call.ident.name) + .set_arg("receiver_ty", receiver_ty) + .span_label(span, fluent::lint::label) + .note(fluent::lint::note) + .emit(); + }); + } +} diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs new file mode 100644 index 000000000..af5e5faf1 --- /dev/null +++ b/compiler/rustc_lint/src/pass_by_value.rs @@ -0,0 +1,96 @@ +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_errors::{fluent, Applicability}; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::{GenericArg, PathSegment, QPath, TyKind}; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +declare_tool_lint! { + /// The `rustc_pass_by_value` lint marks a type with `#[rustc_pass_by_value]` requiring it to + /// always be passed by value. This is usually used for types that are thin wrappers around + /// references, so there is no benefit to an extra layer of indirection. (Example: `Ty` which + /// is a reference to an `Interned<TyS>`) + pub rustc::PASS_BY_VALUE, + Warn, + "pass by reference of a type flagged as `#[rustc_pass_by_value]`", + report_in_external_macro: true +} + +declare_lint_pass!(PassByValue => [PASS_BY_VALUE]); + +impl<'tcx> LateLintPass<'tcx> for PassByValue { + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) { + match &ty.kind { + TyKind::Rptr(_, hir::MutTy { ty: inner_ty, mutbl: hir::Mutability::Not }) => { + if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) { + if cx.tcx.impl_trait_ref(impl_did).is_some() { + return; + } + } + if let Some(t) = path_for_pass_by_value(cx, &inner_ty) { + cx.struct_span_lint(PASS_BY_VALUE, ty.span, |lint| { + lint.build(fluent::lint::pass_by_value) + .set_arg("ty", t.clone()) + .span_suggestion( + ty.span, + fluent::lint::suggestion, + t, + // Changing type of function argument + Applicability::MaybeIncorrect, + ) + .emit(); + }) + } + } + _ => {} + } + } +} + +fn path_for_pass_by_value(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Option<String> { + if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind { + match path.res { + Res::Def(_, def_id) if cx.tcx.has_attr(def_id, sym::rustc_pass_by_value) => { + let name = cx.tcx.item_name(def_id).to_ident_string(); + let path_segment = path.segments.last().unwrap(); + return Some(format!("{}{}", name, gen_args(cx, path_segment))); + } + Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => { + if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() { + if cx.tcx.has_attr(adt.did(), sym::rustc_pass_by_value) { + return Some(cx.tcx.def_path_str_with_substs(adt.did(), substs)); + } + } + } + _ => (), + } + } + + None +} + +fn gen_args(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> String { + if let Some(args) = &segment.args { + let params = args + .args + .iter() + .map(|arg| match arg { + GenericArg::Lifetime(lt) => lt.name.ident().to_string(), + GenericArg::Type(ty) => { + cx.tcx.sess.source_map().span_to_snippet(ty.span).unwrap_or_else(|_| "_".into()) + } + GenericArg::Const(c) => { + cx.tcx.sess.source_map().span_to_snippet(c.span).unwrap_or_else(|_| "_".into()) + } + GenericArg::Infer(_) => String::from("_"), + }) + .collect::<Vec<_>>(); + + if !params.is_empty() { + return format!("<{}>", params.join(", ")); + } + } + + String::new() +} diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs new file mode 100644 index 000000000..cb7bd407e --- /dev/null +++ b/compiler/rustc_lint/src/passes.rs @@ -0,0 +1,249 @@ +use crate::context::{EarlyContext, LateContext}; + +use rustc_ast as ast; +use rustc_data_structures::sync; +use rustc_hir as hir; +use rustc_session::lint::builtin::HardwiredLints; +use rustc_session::lint::LintPass; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +#[macro_export] +macro_rules! late_lint_methods { + ($macro:path, $args:tt, [$hir:tt]) => ( + $macro!($args, [$hir], [ + fn check_body(a: &$hir hir::Body<$hir>); + fn check_body_post(a: &$hir hir::Body<$hir>); + fn check_crate(); + fn check_crate_post(); + fn check_mod(a: &$hir hir::Mod<$hir>, b: Span, c: hir::HirId); + fn check_foreign_item(a: &$hir hir::ForeignItem<$hir>); + fn check_item(a: &$hir hir::Item<$hir>); + fn check_item_post(a: &$hir hir::Item<$hir>); + fn check_local(a: &$hir hir::Local<$hir>); + fn check_block(a: &$hir hir::Block<$hir>); + fn check_block_post(a: &$hir hir::Block<$hir>); + fn check_stmt(a: &$hir hir::Stmt<$hir>); + fn check_arm(a: &$hir hir::Arm<$hir>); + fn check_pat(a: &$hir hir::Pat<$hir>); + fn check_expr(a: &$hir hir::Expr<$hir>); + fn check_expr_post(a: &$hir hir::Expr<$hir>); + fn check_ty(a: &$hir hir::Ty<$hir>); + fn check_generic_param(a: &$hir hir::GenericParam<$hir>); + fn check_generics(a: &$hir hir::Generics<$hir>); + fn check_poly_trait_ref(a: &$hir hir::PolyTraitRef<$hir>, b: hir::TraitBoundModifier); + fn check_fn( + a: rustc_hir::intravisit::FnKind<$hir>, + b: &$hir hir::FnDecl<$hir>, + c: &$hir hir::Body<$hir>, + d: Span, + e: hir::HirId); + fn check_trait_item(a: &$hir hir::TraitItem<$hir>); + fn check_impl_item(a: &$hir hir::ImplItem<$hir>); + fn check_impl_item_post(a: &$hir hir::ImplItem<$hir>); + fn check_struct_def(a: &$hir hir::VariantData<$hir>); + fn check_field_def(a: &$hir hir::FieldDef<$hir>); + fn check_variant(a: &$hir hir::Variant<$hir>); + fn check_path(a: &$hir hir::Path<$hir>, b: hir::HirId); + fn check_attribute(a: &$hir ast::Attribute); + + /// Called when entering a syntax node that can have lint attributes such + /// as `#[allow(...)]`. Called with *all* the attributes of that node. + fn enter_lint_attrs(a: &$hir [ast::Attribute]); + + /// Counterpart to `enter_lint_attrs`. + fn exit_lint_attrs(a: &$hir [ast::Attribute]); + ]); + ) +} + +/// Trait for types providing lint checks. +/// +/// Each `check` method checks a single syntax node, and should not +/// invoke methods recursively (unlike `Visitor`). By default they +/// do nothing. +// +// FIXME: eliminate the duplication with `Visitor`. But this also +// contains a few lint-specific methods with no equivalent in `Visitor`. + +macro_rules! expand_lint_pass_methods { + ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})* + ) +} + +macro_rules! declare_late_lint_pass { + ([], [$hir:tt], [$($methods:tt)*]) => ( + pub trait LateLintPass<$hir>: LintPass { + expand_lint_pass_methods!(&LateContext<$hir>, [$($methods)*]); + } + ) +} + +late_lint_methods!(declare_late_lint_pass, [], ['tcx]); + +impl LateLintPass<'_> for HardwiredLints {} + +#[macro_export] +macro_rules! expand_combined_late_lint_pass_method { + ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({ + $($self.$passes.$name $params;)* + }) +} + +#[macro_export] +macro_rules! expand_combined_late_lint_pass_methods { + ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &LateContext<'tcx>, $($param: $arg),*) { + expand_combined_late_lint_pass_method!($passes, self, $name, (context, $($param),*)); + })* + ) +} + +#[macro_export] +macro_rules! declare_combined_late_lint_pass { + ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], [$hir:tt], $methods:tt) => ( + #[allow(non_snake_case)] + $v struct $name { + $($passes: $passes,)* + } + + impl $name { + $v fn new() -> Self { + Self { + $($passes: $constructor,)* + } + } + + $v fn get_lints() -> LintArray { + let mut lints = Vec::new(); + $(lints.extend_from_slice(&$passes::get_lints());)* + lints + } + } + + impl<'tcx> LateLintPass<'tcx> for $name { + expand_combined_late_lint_pass_methods!([$($passes),*], $methods); + } + + #[allow(rustc::lint_pass_impl_without_macro)] + impl LintPass for $name { + fn name(&self) -> &'static str { + panic!() + } + } + ) +} + +#[macro_export] +macro_rules! early_lint_methods { + ($macro:path, $args:tt) => ( + $macro!($args, [ + fn check_param(a: &ast::Param); + fn check_ident(a: Ident); + fn check_crate(a: &ast::Crate); + fn check_crate_post(a: &ast::Crate); + fn check_item(a: &ast::Item); + fn check_item_post(a: &ast::Item); + fn check_local(a: &ast::Local); + fn check_block(a: &ast::Block); + fn check_stmt(a: &ast::Stmt); + fn check_arm(a: &ast::Arm); + fn check_pat(a: &ast::Pat); + fn check_pat_post(a: &ast::Pat); + fn check_expr(a: &ast::Expr); + fn check_ty(a: &ast::Ty); + fn check_generic_arg(a: &ast::GenericArg); + fn check_generic_param(a: &ast::GenericParam); + fn check_generics(a: &ast::Generics); + fn check_poly_trait_ref(a: &ast::PolyTraitRef, + b: &ast::TraitBoundModifier); + fn check_fn(a: rustc_ast::visit::FnKind<'_>, c: Span, d_: ast::NodeId); + fn check_trait_item(a: &ast::AssocItem); + fn check_impl_item(a: &ast::AssocItem); + fn check_variant(a: &ast::Variant); + fn check_attribute(a: &ast::Attribute); + fn check_mac_def(a: &ast::MacroDef, b: ast::NodeId); + fn check_mac(a: &ast::MacCall); + + /// Called when entering a syntax node that can have lint attributes such + /// as `#[allow(...)]`. Called with *all* the attributes of that node. + fn enter_lint_attrs(a: &[ast::Attribute]); + + /// Counterpart to `enter_lint_attrs`. + fn exit_lint_attrs(a: &[ast::Attribute]); + ]); + ) +} + +macro_rules! expand_early_lint_pass_methods { + ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})* + ) +} + +macro_rules! declare_early_lint_pass { + ([], [$($methods:tt)*]) => ( + pub trait EarlyLintPass: LintPass { + expand_early_lint_pass_methods!(&EarlyContext<'_>, [$($methods)*]); + } + ) +} + +early_lint_methods!(declare_early_lint_pass, []); + +#[macro_export] +macro_rules! expand_combined_early_lint_pass_method { + ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({ + $($self.$passes.$name $params;)* + }) +} + +#[macro_export] +macro_rules! expand_combined_early_lint_pass_methods { + ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { + expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); + })* + ) +} + +#[macro_export] +macro_rules! declare_combined_early_lint_pass { + ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], $methods:tt) => ( + #[allow(non_snake_case)] + $v struct $name { + $($passes: $passes,)* + } + + impl $name { + $v fn new() -> Self { + Self { + $($passes: $constructor,)* + } + } + + $v fn get_lints() -> LintArray { + let mut lints = Vec::new(); + $(lints.extend_from_slice(&$passes::get_lints());)* + lints + } + } + + impl EarlyLintPass for $name { + expand_combined_early_lint_pass_methods!([$($passes),*], $methods); + } + + #[allow(rustc::lint_pass_impl_without_macro)] + impl LintPass for $name { + fn name(&self) -> &'static str { + panic!() + } + } + ) +} + +/// A lint pass boxed up as a trait object. +pub type EarlyLintPassObject = Box<dyn EarlyLintPass + sync::Send + sync::Sync + 'static>; +pub type LateLintPassObject = + Box<dyn for<'tcx> LateLintPass<'tcx> + sync::Send + sync::Sync + 'static>; diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs new file mode 100644 index 000000000..26f413453 --- /dev/null +++ b/compiler/rustc_lint/src/redundant_semicolon.rs @@ -0,0 +1,58 @@ +use crate::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_ast::{Block, StmtKind}; +use rustc_errors::{fluent, Applicability}; +use rustc_span::Span; + +declare_lint! { + /// The `redundant_semicolons` lint detects unnecessary trailing + /// semicolons. + /// + /// ### Example + /// + /// ```rust + /// let _ = 123;; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Extra semicolons are not needed, and may be removed to avoid confusion + /// and visual clutter. + pub REDUNDANT_SEMICOLONS, + Warn, + "detects unnecessary trailing semicolons" +} + +declare_lint_pass!(RedundantSemicolons => [REDUNDANT_SEMICOLONS]); + +impl EarlyLintPass for RedundantSemicolons { + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) { + let mut seq = None; + for stmt in block.stmts.iter() { + match (&stmt.kind, &mut seq) { + (StmtKind::Empty, None) => seq = Some((stmt.span, false)), + (StmtKind::Empty, Some(seq)) => *seq = (seq.0.to(stmt.span), true), + (_, seq) => maybe_lint_redundant_semis(cx, seq), + } + } + maybe_lint_redundant_semis(cx, &mut seq); + } +} + +fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) { + if let Some((span, multiple)) = seq.take() { + // FIXME: Find a better way of ignoring the trailing + // semicolon from macro expansion + if span == rustc_span::DUMMY_SP { + return; + } + + cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| { + lint.build(fluent::lint::redundant_semicolons) + .set_arg("multiple", multiple) + .span_suggestion(span, fluent::lint::suggestion, "", Applicability::MaybeIncorrect) + .emit(); + }); + } +} diff --git a/compiler/rustc_lint/src/tests.rs b/compiler/rustc_lint/src/tests.rs new file mode 100644 index 000000000..fc9d6f636 --- /dev/null +++ b/compiler/rustc_lint/src/tests.rs @@ -0,0 +1,26 @@ +use crate::context::parse_lint_and_tool_name; +use rustc_span::{create_default_session_globals_then, Symbol}; + +#[test] +fn parse_lint_no_tool() { + create_default_session_globals_then(|| { + assert_eq!(parse_lint_and_tool_name("foo"), (None, "foo")) + }); +} + +#[test] +fn parse_lint_with_tool() { + create_default_session_globals_then(|| { + assert_eq!(parse_lint_and_tool_name("clippy::foo"), (Some(Symbol::intern("clippy")), "foo")) + }); +} + +#[test] +fn parse_lint_multiple_path() { + create_default_session_globals_then(|| { + assert_eq!( + parse_lint_and_tool_name("clippy::foo::bar"), + (Some(Symbol::intern("clippy")), "foo::bar") + ) + }); +} diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs new file mode 100644 index 000000000..df1587c59 --- /dev/null +++ b/compiler/rustc_lint/src/traits.rs @@ -0,0 +1,134 @@ +use crate::LateContext; +use crate::LateLintPass; +use crate::LintContext; +use rustc_errors::fluent; +use rustc_hir as hir; +use rustc_span::symbol::sym; + +declare_lint! { + /// The `drop_bounds` lint checks for generics with `std::ops::Drop` as + /// bounds. + /// + /// ### Example + /// + /// ```rust + /// fn foo<T: Drop>() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// A generic trait bound of the form `T: Drop` is most likely misleading + /// and not what the programmer intended (they probably should have used + /// `std::mem::needs_drop` instead). + /// + /// `Drop` bounds do not actually indicate whether a type can be trivially + /// dropped or not, because a composite type containing `Drop` types does + /// not necessarily implement `Drop` itself. Naïvely, one might be tempted + /// to write an implementation that assumes that a type can be trivially + /// dropped while also supplying a specialization for `T: Drop` that + /// actually calls the destructor. However, this breaks down e.g. when `T` + /// is `String`, which does not implement `Drop` itself but contains a + /// `Vec`, which does implement `Drop`, so assuming `T` can be trivially + /// dropped would lead to a memory leak here. + /// + /// Furthermore, the `Drop` trait only contains one method, `Drop::drop`, + /// which may not be called explicitly in user code (`E0040`), so there is + /// really no use case for using `Drop` in trait bounds, save perhaps for + /// some obscure corner cases, which can use `#[allow(drop_bounds)]`. + pub DROP_BOUNDS, + Warn, + "bounds of the form `T: Drop` are most likely incorrect" +} + +declare_lint! { + /// The `dyn_drop` lint checks for trait objects with `std::ops::Drop`. + /// + /// ### Example + /// + /// ```rust + /// fn foo(_x: Box<dyn Drop>) {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// A trait object bound of the form `dyn Drop` is most likely misleading + /// and not what the programmer intended. + /// + /// `Drop` bounds do not actually indicate whether a type can be trivially + /// dropped or not, because a composite type containing `Drop` types does + /// not necessarily implement `Drop` itself. Naïvely, one might be tempted + /// to write a deferred drop system, to pull cleaning up memory out of a + /// latency-sensitive code path, using `dyn Drop` trait objects. However, + /// this breaks down e.g. when `T` is `String`, which does not implement + /// `Drop`, but should probably be accepted. + /// + /// To write a trait object bound that accepts anything, use a placeholder + /// trait with a blanket implementation. + /// + /// ```rust + /// trait Placeholder {} + /// impl<T> Placeholder for T {} + /// fn foo(_x: Box<dyn Placeholder>) {} + /// ``` + pub DYN_DROP, + Warn, + "trait objects of the form `dyn Drop` are useless" +} + +declare_lint_pass!( + /// Lint for bounds of the form `T: Drop`, which usually + /// indicate an attempt to emulate `std::mem::needs_drop`. + DropTraitConstraints => [DROP_BOUNDS, DYN_DROP] +); + +impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + use rustc_middle::ty::PredicateKind::*; + + let predicates = cx.tcx.explicit_predicates_of(item.def_id); + for &(predicate, span) in predicates.predicates { + let Trait(trait_predicate) = predicate.kind().skip_binder() else { + continue + }; + let def_id = trait_predicate.trait_ref.def_id; + if cx.tcx.lang_items().drop_trait() == Some(def_id) { + // Explicitly allow `impl Drop`, a drop-guards-as-Voldemort-type pattern. + if trait_predicate.trait_ref.self_ty().is_impl_trait() { + continue; + } + cx.struct_span_lint(DROP_BOUNDS, span, |lint| { + let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { + return + }; + lint.build(fluent::lint::drop_trait_constraints) + .set_arg("predicate", predicate) + .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop)) + .emit(); + }); + } + } + } + + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) { + let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else { + return + }; + for bound in &bounds[..] { + let def_id = bound.trait_ref.trait_def_id(); + if cx.tcx.lang_items().drop_trait() == def_id { + cx.struct_span_lint(DYN_DROP, bound.span, |lint| { + let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { + return + }; + lint.build(fluent::lint::drop_glue) + .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop)) + .emit(); + }); + } + } + } +} diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs new file mode 100644 index 000000000..5c07afeb7 --- /dev/null +++ b/compiler/rustc_lint/src/types.rs @@ -0,0 +1,1576 @@ +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_attr as attr; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{fluent, Applicability, DiagnosticMessage}; +use rustc_hir as hir; +use rustc_hir::{is_range_literal, Expr, ExprKind, Node}; +use rustc_macros::LintDiagnostic; +use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton}; +use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable}; +use rustc_span::source_map; +use rustc_span::symbol::sym; +use rustc_span::{Span, Symbol, DUMMY_SP}; +use rustc_target::abi::{Abi, WrappingRange}; +use rustc_target::abi::{Integer, TagEncoding, Variants}; +use rustc_target::spec::abi::Abi as SpecAbi; + +use std::cmp; +use std::iter; +use std::ops::ControlFlow; +use tracing::debug; + +declare_lint! { + /// The `unused_comparisons` lint detects comparisons made useless by + /// limits of the types involved. + /// + /// ### Example + /// + /// ```rust + /// fn foo(x: u8) { + /// x >= 0; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// A useless comparison may indicate a mistake, and should be fixed or + /// removed. + UNUSED_COMPARISONS, + Warn, + "comparisons made useless by limits of the types involved" +} + +declare_lint! { + /// The `overflowing_literals` lint detects literal out of range for its + /// type. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// let x: u8 = 1000; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// It is usually a mistake to use a literal that overflows the type where + /// it is used. Either use a literal that is within range, or change the + /// type to be within the range of the literal. + OVERFLOWING_LITERALS, + Deny, + "literal out of range for its type" +} + +declare_lint! { + /// The `variant_size_differences` lint detects enums with widely varying + /// variant sizes. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(variant_size_differences)] + /// enum En { + /// V0(u8), + /// VBig([u8; 1024]), + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// It can be a mistake to add a variant to an enum that is much larger + /// than the other variants, bloating the overall size required for all + /// variants. This can impact performance and memory usage. This is + /// triggered if one variant is more than 3 times larger than the + /// second-largest variant. + /// + /// Consider placing the large variant's contents on the heap (for example + /// via [`Box`]) to keep the overall size of the enum itself down. + /// + /// This lint is "allow" by default because it can be noisy, and may not be + /// an actual problem. Decisions about this should be guided with + /// profiling and benchmarking. + /// + /// [`Box`]: https://doc.rust-lang.org/std/boxed/index.html + VARIANT_SIZE_DIFFERENCES, + Allow, + "detects enums with widely varying variant sizes" +} + +#[derive(Copy, Clone)] +pub struct TypeLimits { + /// Id of the last visited negated expression + negated_expr_id: Option<hir::HirId>, +} + +impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS]); + +impl TypeLimits { + pub fn new() -> TypeLimits { + TypeLimits { negated_expr_id: None } + } +} + +/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint. +/// Returns `true` iff the lint was overridden. +fn lint_overflowing_range_endpoint<'tcx>( + cx: &LateContext<'tcx>, + lit: &hir::Lit, + lit_val: u128, + max: u128, + expr: &'tcx hir::Expr<'tcx>, + parent_expr: &'tcx hir::Expr<'tcx>, + ty: &str, +) -> bool { + // We only want to handle exclusive (`..`) ranges, + // which are represented as `ExprKind::Struct`. + let mut overwritten = false; + if let ExprKind::Struct(_, eps, _) = &parent_expr.kind { + if eps.len() != 2 { + return false; + } + // We can suggest using an inclusive range + // (`..=`) instead only if it is the `end` that is + // overflowing and only by 1. + if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max { + cx.struct_span_lint(OVERFLOWING_LITERALS, parent_expr.span, |lint| { + let mut err = lint.build(fluent::lint::range_endpoint_out_of_range); + err.set_arg("ty", ty); + if let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) { + use ast::{LitIntType, LitKind}; + // We need to preserve the literal's suffix, + // as it may determine typing information. + let suffix = match lit.node { + LitKind::Int(_, LitIntType::Signed(s)) => s.name_str(), + LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str(), + LitKind::Int(_, LitIntType::Unsuffixed) => "", + _ => bug!(), + }; + let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix); + err.span_suggestion( + parent_expr.span, + fluent::lint::suggestion, + suggestion, + Applicability::MachineApplicable, + ); + err.emit(); + overwritten = true; + } + }); + } + } + overwritten +} + +// For `isize` & `usize`, be conservative with the warnings, so that the +// warnings are consistent between 32- and 64-bit platforms. +fn int_ty_range(int_ty: ty::IntTy) -> (i128, i128) { + match int_ty { + ty::IntTy::Isize => (i64::MIN.into(), i64::MAX.into()), + ty::IntTy::I8 => (i8::MIN.into(), i8::MAX.into()), + ty::IntTy::I16 => (i16::MIN.into(), i16::MAX.into()), + ty::IntTy::I32 => (i32::MIN.into(), i32::MAX.into()), + ty::IntTy::I64 => (i64::MIN.into(), i64::MAX.into()), + ty::IntTy::I128 => (i128::MIN, i128::MAX), + } +} + +fn uint_ty_range(uint_ty: ty::UintTy) -> (u128, u128) { + let max = match uint_ty { + ty::UintTy::Usize => u64::MAX.into(), + ty::UintTy::U8 => u8::MAX.into(), + ty::UintTy::U16 => u16::MAX.into(), + ty::UintTy::U32 => u32::MAX.into(), + ty::UintTy::U64 => u64::MAX.into(), + ty::UintTy::U128 => u128::MAX, + }; + (0, max) +} + +fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> { + let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?; + let firstch = src.chars().next()?; + + if firstch == '0' { + match src.chars().nth(1) { + Some('x' | 'b') => return Some(src), + _ => return None, + } + } + + None +} + +fn report_bin_hex_error( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + ty: attr::IntType, + repr_str: String, + val: u128, + negative: bool, +) { + let size = Integer::from_attr(&cx.tcx, ty).size(); + cx.struct_span_lint(OVERFLOWING_LITERALS, expr.span, |lint| { + let (t, actually) = match ty { + attr::IntType::SignedInt(t) => { + let actually = if negative { + -(size.sign_extend(val) as i128) + } else { + size.sign_extend(val) as i128 + }; + (t.name_str(), actually.to_string()) + } + attr::IntType::UnsignedInt(t) => { + let actually = size.truncate(val); + (t.name_str(), actually.to_string()) + } + }; + let mut err = lint.build(fluent::lint::overflowing_bin_hex); + if negative { + // If the value is negative, + // emits a note about the value itself, apart from the literal. + err.note(fluent::lint::negative_note); + err.note(fluent::lint::negative_becomes_note); + } else { + err.note(fluent::lint::positive_note); + } + if let Some(sugg_ty) = + get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative) + { + err.set_arg("suggestion_ty", sugg_ty); + if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { + let (sans_suffix, _) = repr_str.split_at(pos); + err.span_suggestion( + expr.span, + fluent::lint::suggestion, + format!("{}{}", sans_suffix, sugg_ty), + Applicability::MachineApplicable, + ); + } else { + err.help(fluent::lint::help); + } + } + err.set_arg("ty", t); + err.set_arg("lit", repr_str); + err.set_arg("dec", val); + err.set_arg("actually", actually); + err.emit(); + }); +} + +// This function finds the next fitting type and generates a suggestion string. +// It searches for fitting types in the following way (`X < Y`): +// - `iX`: if literal fits in `uX` => `uX`, else => `iY` +// - `-iX` => `iY` +// - `uX` => `uY` +// +// No suggestion for: `isize`, `usize`. +fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> { + use ty::IntTy::*; + use ty::UintTy::*; + macro_rules! find_fit { + ($ty:expr, $val:expr, $negative:expr, + $($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => { + { + let _neg = if negative { 1 } else { 0 }; + match $ty { + $($type => { + $(if !negative && val <= uint_ty_range($utypes).1 { + return Some($utypes.name_str()) + })* + $(if val <= int_ty_range($itypes).1 as u128 + _neg { + return Some($itypes.name_str()) + })* + None + },)+ + _ => None + } + } + } + } + match t.kind() { + ty::Int(i) => find_fit!(i, val, negative, + I8 => [U8] => [I16, I32, I64, I128], + I16 => [U16] => [I32, I64, I128], + I32 => [U32] => [I64, I128], + I64 => [U64] => [I128], + I128 => [U128] => []), + ty::Uint(u) => find_fit!(u, val, negative, + U8 => [U8, U16, U32, U64, U128] => [], + U16 => [U16, U32, U64, U128] => [], + U32 => [U32, U64, U128] => [], + U64 => [U64, U128] => [], + U128 => [U128] => []), + _ => None, + } +} + +fn lint_int_literal<'tcx>( + cx: &LateContext<'tcx>, + type_limits: &TypeLimits, + e: &'tcx hir::Expr<'tcx>, + lit: &hir::Lit, + t: ty::IntTy, + v: u128, +) { + let int_type = t.normalize(cx.sess().target.pointer_width); + let (min, max) = int_ty_range(int_type); + let max = max as u128; + let negative = type_limits.negated_expr_id == Some(e.hir_id); + + // Detect literal value out of range [min, max] inclusive + // avoiding use of -min to prevent overflow/panic + if (negative && v > max + 1) || (!negative && v > max) { + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + report_bin_hex_error( + cx, + e, + attr::IntType::SignedInt(ty::ast_int_ty(t)), + repr_str, + v, + negative, + ); + return; + } + + let par_id = cx.tcx.hir().get_parent_node(e.hir_id); + if let Node::Expr(par_e) = cx.tcx.hir().get(par_id) { + if let hir::ExprKind::Struct(..) = par_e.kind { + if is_range_literal(par_e) + && lint_overflowing_range_endpoint(cx, lit, v, max, e, par_e, t.name_str()) + { + // The overflowing literal lint was overridden. + return; + } + } + } + + cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { + let mut err = lint.build(fluent::lint::overflowing_int); + err.set_arg("ty", t.name_str()); + err.set_arg( + "lit", + cx.sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + ); + err.set_arg("min", min); + err.set_arg("max", max); + err.note(fluent::lint::note); + if let Some(sugg_ty) = + get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative) + { + err.set_arg("suggestion_ty", sugg_ty); + err.help(fluent::lint::help); + } + err.emit(); + }); + } +} + +fn lint_uint_literal<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx hir::Expr<'tcx>, + lit: &hir::Lit, + t: ty::UintTy, +) { + let uint_type = t.normalize(cx.sess().target.pointer_width); + let (min, max) = uint_ty_range(uint_type); + let lit_val: u128 = match lit.node { + // _v is u8, within range by definition + ast::LitKind::Byte(_v) => return, + ast::LitKind::Int(v, _) => v, + _ => bug!(), + }; + if lit_val < min || lit_val > max { + let parent_id = cx.tcx.hir().get_parent_node(e.hir_id); + if let Node::Expr(par_e) = cx.tcx.hir().get(parent_id) { + match par_e.kind { + hir::ExprKind::Cast(..) => { + if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() { + cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| { + lint.build(fluent::lint::only_cast_u8_to_char) + .span_suggestion( + par_e.span, + fluent::lint::suggestion, + format!("'\\u{{{:X}}}'", lit_val), + Applicability::MachineApplicable, + ) + .emit(); + }); + return; + } + } + hir::ExprKind::Struct(..) if is_range_literal(par_e) => { + let t = t.name_str(); + if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, par_e, t) { + // The overflowing literal lint was overridden. + return; + } + } + _ => {} + } + } + if let Some(repr_str) = get_bin_hex_repr(cx, lit) { + report_bin_hex_error( + cx, + e, + attr::IntType::UnsignedInt(ty::ast_uint_ty(t)), + repr_str, + lit_val, + false, + ); + return; + } + cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { + lint.build(fluent::lint::overflowing_uint) + .set_arg("ty", t.name_str()) + .set_arg( + "lit", + cx.sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + ) + .set_arg("min", min) + .set_arg("max", max) + .note(fluent::lint::note) + .emit(); + }); + } +} + +fn lint_literal<'tcx>( + cx: &LateContext<'tcx>, + type_limits: &TypeLimits, + e: &'tcx hir::Expr<'tcx>, + lit: &hir::Lit, +) { + match *cx.typeck_results().node_type(e.hir_id).kind() { + ty::Int(t) => { + match lit.node { + ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => { + lint_int_literal(cx, type_limits, e, lit, t, v) + } + _ => bug!(), + }; + } + ty::Uint(t) => lint_uint_literal(cx, e, lit, t), + ty::Float(t) => { + let is_infinite = match lit.node { + ast::LitKind::Float(v, _) => match t { + ty::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite), + ty::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite), + }, + _ => bug!(), + }; + if is_infinite == Ok(true) { + cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { + lint.build(fluent::lint::overflowing_literal) + .set_arg("ty", t.name_str()) + .set_arg( + "lit", + cx.sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + ) + .note(fluent::lint::note) + .emit(); + }); + } + } + _ => {} + } +} + +impl<'tcx> LateLintPass<'tcx> for TypeLimits { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) { + match e.kind { + hir::ExprKind::Unary(hir::UnOp::Neg, ref expr) => { + // propagate negation, if the negation itself isn't negated + if self.negated_expr_id != Some(e.hir_id) { + self.negated_expr_id = Some(expr.hir_id); + } + } + hir::ExprKind::Binary(binop, ref l, ref r) => { + if is_comparison(binop) && !check_limits(cx, binop, &l, &r) { + cx.struct_span_lint(UNUSED_COMPARISONS, e.span, |lint| { + lint.build(fluent::lint::unused_comparisons).emit(); + }); + } + } + hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit), + _ => {} + }; + + fn is_valid<T: cmp::PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool { + match binop.node { + hir::BinOpKind::Lt => v > min && v <= max, + hir::BinOpKind::Le => v >= min && v < max, + hir::BinOpKind::Gt => v >= min && v < max, + hir::BinOpKind::Ge => v > min && v <= max, + hir::BinOpKind::Eq | hir::BinOpKind::Ne => v >= min && v <= max, + _ => bug!(), + } + } + + fn rev_binop(binop: hir::BinOp) -> hir::BinOp { + source_map::respan( + binop.span, + match binop.node { + hir::BinOpKind::Lt => hir::BinOpKind::Gt, + hir::BinOpKind::Le => hir::BinOpKind::Ge, + hir::BinOpKind::Gt => hir::BinOpKind::Lt, + hir::BinOpKind::Ge => hir::BinOpKind::Le, + _ => return binop, + }, + ) + } + + fn check_limits( + cx: &LateContext<'_>, + binop: hir::BinOp, + l: &hir::Expr<'_>, + r: &hir::Expr<'_>, + ) -> bool { + let (lit, expr, swap) = match (&l.kind, &r.kind) { + (&hir::ExprKind::Lit(_), _) => (l, r, true), + (_, &hir::ExprKind::Lit(_)) => (r, l, false), + _ => return true, + }; + // Normalize the binop so that the literal is always on the RHS in + // the comparison + let norm_binop = if swap { rev_binop(binop) } else { binop }; + match *cx.typeck_results().node_type(expr.hir_id).kind() { + ty::Int(int_ty) => { + let (min, max) = int_ty_range(int_ty); + let lit_val: i128 = match lit.kind { + hir::ExprKind::Lit(ref li) => match li.node { + ast::LitKind::Int( + v, + ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed, + ) => v as i128, + _ => return true, + }, + _ => bug!(), + }; + is_valid(norm_binop, lit_val, min, max) + } + ty::Uint(uint_ty) => { + let (min, max): (u128, u128) = uint_ty_range(uint_ty); + let lit_val: u128 = match lit.kind { + hir::ExprKind::Lit(ref li) => match li.node { + ast::LitKind::Int(v, _) => v, + _ => return true, + }, + _ => bug!(), + }; + is_valid(norm_binop, lit_val, min, max) + } + _ => true, + } + } + + fn is_comparison(binop: hir::BinOp) -> bool { + matches!( + binop.node, + hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt + ) + } + } +} + +declare_lint! { + /// The `improper_ctypes` lint detects incorrect use of types in foreign + /// modules. + /// + /// ### Example + /// + /// ```rust + /// extern "C" { + /// static STATIC: String; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The compiler has several checks to verify that types used in `extern` + /// blocks are safe and follow certain rules to ensure proper + /// compatibility with the foreign interfaces. This lint is issued when it + /// detects a probable mistake in a definition. The lint usually should + /// provide a description of the issue, along with possibly a hint on how + /// to resolve it. + IMPROPER_CTYPES, + Warn, + "proper use of libc types in foreign modules" +} + +declare_lint_pass!(ImproperCTypesDeclarations => [IMPROPER_CTYPES]); + +declare_lint! { + /// The `improper_ctypes_definitions` lint detects incorrect use of + /// [`extern` function] definitions. + /// + /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier + /// + /// ### Example + /// + /// ```rust + /// # #![allow(unused)] + /// pub extern "C" fn str_type(p: &str) { } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// There are many parameter and return types that may be specified in an + /// `extern` function that are not compatible with the given ABI. This + /// lint is an alert that these types should not be used. The lint usually + /// should provide a description of the issue, along with possibly a hint + /// on how to resolve it. + IMPROPER_CTYPES_DEFINITIONS, + Warn, + "proper use of libc types in foreign item definitions" +} + +declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]); + +#[derive(Clone, Copy)] +pub(crate) enum CItemKind { + Declaration, + Definition, +} + +struct ImproperCTypesVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + mode: CItemKind, +} + +enum FfiResult<'tcx> { + FfiSafe, + FfiPhantom(Ty<'tcx>), + FfiUnsafe { ty: Ty<'tcx>, reason: DiagnosticMessage, help: Option<DiagnosticMessage> }, +} + +pub(crate) fn nonnull_optimization_guaranteed<'tcx>( + tcx: TyCtxt<'tcx>, + def: ty::AdtDef<'tcx>, +) -> bool { + tcx.has_attr(def.did(), sym::rustc_nonnull_optimization_guaranteed) +} + +/// `repr(transparent)` structs can have a single non-ZST field, this function returns that +/// field. +pub fn transparent_newtype_field<'a, 'tcx>( + tcx: TyCtxt<'tcx>, + variant: &'a ty::VariantDef, +) -> Option<&'a ty::FieldDef> { + let param_env = tcx.param_env(variant.def_id); + variant.fields.iter().find(|field| { + let field_ty = tcx.type_of(field.did); + let is_zst = tcx.layout_of(param_env.and(field_ty)).map_or(false, |layout| layout.is_zst()); + !is_zst + }) +} + +/// Is type known to be non-null? +fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { + let tcx = cx.tcx; + match ty.kind() { + ty::FnPtr(_) => true, + ty::Ref(..) => true, + ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, + ty::Adt(def, substs) if def.repr().transparent() && !def.is_union() => { + let marked_non_null = nonnull_optimization_guaranteed(tcx, *def); + + if marked_non_null { + return true; + } + + // `UnsafeCell` has its niche hidden. + if def.is_unsafe_cell() { + return false; + } + + def.variants() + .iter() + .filter_map(|variant| transparent_newtype_field(cx.tcx, variant)) + .any(|field| ty_is_known_nonnull(cx, field.ty(tcx, substs), mode)) + } + _ => false, + } +} + +/// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type. +/// If the type passed in was not scalar, returns None. +fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { + let tcx = cx.tcx; + Some(match *ty.kind() { + ty::Adt(field_def, field_substs) => { + let inner_field_ty = { + let first_non_zst_ty = field_def + .variants() + .iter() + .filter_map(|v| transparent_newtype_field(cx.tcx, v)); + debug_assert_eq!( + first_non_zst_ty.clone().count(), + 1, + "Wrong number of fields for transparent type" + ); + first_non_zst_ty + .last() + .expect("No non-zst fields in transparent type.") + .ty(tcx, field_substs) + }; + return get_nullable_type(cx, inner_field_ty); + } + ty::Int(ty) => tcx.mk_mach_int(ty), + ty::Uint(ty) => tcx.mk_mach_uint(ty), + ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut), + // As these types are always non-null, the nullable equivalent of + // Option<T> of these types are their raw pointer counterparts. + ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }), + ty::FnPtr(..) => { + // There is no nullable equivalent for Rust's function pointers -- you + // must use an Option<fn(..) -> _> to represent it. + ty + } + + // We should only ever reach this case if ty_is_known_nonnull is extended + // to other types. + ref unhandled => { + debug!( + "get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}", + unhandled, ty + ); + return None; + } + }) +} + +/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it +/// can, return the type that `ty` can be safely converted to, otherwise return `None`. +/// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`, +/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. +/// FIXME: This duplicates code in codegen. +pub(crate) fn repr_nullable_ptr<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + ckind: CItemKind, +) -> Option<Ty<'tcx>> { + debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty); + if let ty::Adt(ty_def, substs) = ty.kind() { + let field_ty = match &ty_def.variants().raw[..] { + [var_one, var_two] => match (&var_one.fields[..], &var_two.fields[..]) { + ([], [field]) | ([field], []) => field.ty(cx.tcx, substs), + _ => return None, + }, + _ => return None, + }; + + if !ty_is_known_nonnull(cx, field_ty, ckind) { + return None; + } + + // At this point, the field's type is known to be nonnull and the parent enum is Option-like. + // If the computed size for the field and the enum are different, the nonnull optimization isn't + // being applied (and we've got a problem somewhere). + let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap(); + if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { + bug!("improper_ctypes: Option nonnull optimization not applied?"); + } + + // Return the nullable type this Option-like enum can be safely represented with. + let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi; + if let Abi::Scalar(field_ty_scalar) = field_ty_abi { + match field_ty_scalar.valid_range(cx) { + WrappingRange { start: 0, end } + if end == field_ty_scalar.size(&cx.tcx).unsigned_int_max() - 1 => + { + return Some(get_nullable_type(cx, field_ty).unwrap()); + } + WrappingRange { start: 1, .. } => { + return Some(get_nullable_type(cx, field_ty).unwrap()); + } + WrappingRange { start, end } => { + unreachable!("Unhandled start and end range: ({}, {})", start, end) + } + }; + } + } + None +} + +impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { + /// Check if the type is array and emit an unsafe type lint. + fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + if let ty::Array(..) = ty.kind() { + self.emit_ffi_unsafe_type_lint( + ty, + sp, + fluent::lint::improper_ctypes_array_reason, + Some(fluent::lint::improper_ctypes_array_help), + ); + true + } else { + false + } + } + + /// Checks if the given field's type is "ffi-safe". + fn check_field_type_for_ffi( + &self, + cache: &mut FxHashSet<Ty<'tcx>>, + field: &ty::FieldDef, + substs: SubstsRef<'tcx>, + ) -> FfiResult<'tcx> { + let field_ty = field.ty(self.cx.tcx, substs); + if field_ty.has_opaque_types() { + self.check_type_for_ffi(cache, field_ty) + } else { + let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty); + self.check_type_for_ffi(cache, field_ty) + } + } + + /// Checks if the given `VariantDef`'s field types are "ffi-safe". + fn check_variant_for_ffi( + &self, + cache: &mut FxHashSet<Ty<'tcx>>, + ty: Ty<'tcx>, + def: ty::AdtDef<'tcx>, + variant: &ty::VariantDef, + substs: SubstsRef<'tcx>, + ) -> FfiResult<'tcx> { + use FfiResult::*; + + if def.repr().transparent() { + // Can assume that at most one field is not a ZST, so only check + // that field's type for FFI-safety. + if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) { + self.check_field_type_for_ffi(cache, field, substs) + } else { + // All fields are ZSTs; this means that the type should behave + // like (), which is FFI-unsafe + FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_struct_zst, help: None } + } + } else { + // We can't completely trust repr(C) markings; make sure the fields are + // actually safe. + let mut all_phantom = !variant.fields.is_empty(); + for field in &variant.fields { + match self.check_field_type_for_ffi(cache, &field, substs) { + FfiSafe => { + all_phantom = false; + } + FfiPhantom(..) if def.is_enum() => { + return FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_enum_phantomdata, + help: None, + }; + } + FfiPhantom(..) => {} + r => return r, + } + } + + if all_phantom { FfiPhantom(ty) } else { FfiSafe } + } + } + + /// Checks if the given type is "ffi-safe" (has a stable, well-defined + /// representation which can be exported to C code). + fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> { + use FfiResult::*; + + let tcx = self.cx.tcx; + + // Protect against infinite recursion, for example + // `struct S(*mut S);`. + // FIXME: A recursion limit is necessary as well, for irregular + // recursive types. + if !cache.insert(ty) { + return FfiSafe; + } + + match *ty.kind() { + ty::Adt(def, substs) => { + if def.is_box() && matches!(self.mode, CItemKind::Definition) { + if ty.boxed_ty().is_sized(tcx.at(DUMMY_SP), self.cx.param_env) { + return FfiSafe; + } else { + return FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_box, + help: None, + }; + } + } + if def.is_phantom_data() { + return FfiPhantom(ty); + } + match def.adt_kind() { + AdtKind::Struct | AdtKind::Union => { + if !def.repr().c() && !def.repr().transparent() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + fluent::lint::improper_ctypes_struct_layout_reason + } else { + fluent::lint::improper_ctypes_union_layout_reason + }, + help: if def.is_struct() { + Some(fluent::lint::improper_ctypes_struct_layout_help) + } else { + Some(fluent::lint::improper_ctypes_union_layout_help) + }, + }; + } + + let is_non_exhaustive = + def.non_enum_variant().is_field_list_non_exhaustive(); + if is_non_exhaustive && !def.did().is_local() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + fluent::lint::improper_ctypes_struct_non_exhaustive + } else { + fluent::lint::improper_ctypes_union_non_exhaustive + }, + help: None, + }; + } + + if def.non_enum_variant().fields.is_empty() { + return FfiUnsafe { + ty, + reason: if def.is_struct() { + fluent::lint::improper_ctypes_struct_fieldless_reason + } else { + fluent::lint::improper_ctypes_union_fieldless_reason + }, + help: if def.is_struct() { + Some(fluent::lint::improper_ctypes_struct_fieldless_help) + } else { + Some(fluent::lint::improper_ctypes_union_fieldless_help) + }, + }; + } + + self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs) + } + AdtKind::Enum => { + if def.variants().is_empty() { + // Empty enums are okay... although sort of useless. + return FfiSafe; + } + + // Check for a repr() attribute to specify the size of the + // discriminant. + if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() + { + // Special-case types like `Option<extern fn()>`. + if repr_nullable_ptr(self.cx, ty, self.mode).is_none() { + return FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_enum_repr_reason, + help: Some(fluent::lint::improper_ctypes_enum_repr_help), + }; + } + } + + if def.is_variant_list_non_exhaustive() && !def.did().is_local() { + return FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_non_exhaustive, + help: None, + }; + } + + // Check the contained variants. + for variant in def.variants() { + let is_non_exhaustive = variant.is_field_list_non_exhaustive(); + if is_non_exhaustive && !variant.def_id.is_local() { + return FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_non_exhaustive_variant, + help: None, + }; + } + + match self.check_variant_for_ffi(cache, ty, def, variant, substs) { + FfiSafe => (), + r => return r, + } + } + + FfiSafe + } + } + } + + ty::Char => FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_char_reason, + help: Some(fluent::lint::improper_ctypes_char_help), + }, + + ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => { + FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_128bit, help: None } + } + + // Primitive types with a stable representation. + ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, + + ty::Slice(_) => FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_slice_reason, + help: Some(fluent::lint::improper_ctypes_slice_help), + }, + + ty::Dynamic(..) => { + FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_dyn, help: None } + } + + ty::Str => FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_str_reason, + help: Some(fluent::lint::improper_ctypes_str_help), + }, + + ty::Tuple(..) => FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_tuple_reason, + help: Some(fluent::lint::improper_ctypes_tuple_help), + }, + + ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) + if { + matches!(self.mode, CItemKind::Definition) + && ty.is_sized(self.cx.tcx.at(DUMMY_SP), self.cx.param_env) + } => + { + FfiSafe + } + + ty::RawPtr(ty::TypeAndMut { ty, .. }) + if match ty.kind() { + ty::Tuple(tuple) => tuple.is_empty(), + _ => false, + } => + { + FfiSafe + } + + ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => { + self.check_type_for_ffi(cache, ty) + } + + ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty), + + ty::FnPtr(sig) => { + if self.is_internal_abi(sig.abi()) { + return FfiUnsafe { + ty, + reason: fluent::lint::improper_ctypes_fnptr_reason, + help: Some(fluent::lint::improper_ctypes_fnptr_help), + }; + } + + let sig = tcx.erase_late_bound_regions(sig); + if !sig.output().is_unit() { + let r = self.check_type_for_ffi(cache, sig.output()); + match r { + FfiSafe => {} + _ => { + return r; + } + } + } + for arg in sig.inputs() { + let r = self.check_type_for_ffi(cache, *arg); + match r { + FfiSafe => {} + _ => { + return r; + } + } + } + FfiSafe + } + + ty::Foreign(..) => FfiSafe, + + // While opaque types are checked for earlier, if a projection in a struct field + // normalizes to an opaque type, then it will reach this branch. + ty::Opaque(..) => { + FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_opaque, help: None } + } + + // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, + // so they are currently ignored for the purposes of this lint. + ty::Param(..) | ty::Projection(..) if matches!(self.mode, CItemKind::Definition) => { + FfiSafe + } + + ty::Param(..) + | ty::Projection(..) + | ty::Infer(..) + | ty::Bound(..) + | ty::Error(_) + | ty::Closure(..) + | ty::Generator(..) + | ty::GeneratorWitness(..) + | ty::Placeholder(..) + | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), + } + } + + fn emit_ffi_unsafe_type_lint( + &mut self, + ty: Ty<'tcx>, + sp: Span, + note: DiagnosticMessage, + help: Option<DiagnosticMessage>, + ) { + let lint = match self.mode { + CItemKind::Declaration => IMPROPER_CTYPES, + CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, + }; + + self.cx.struct_span_lint(lint, sp, |lint| { + let item_description = match self.mode { + CItemKind::Declaration => "block", + CItemKind::Definition => "fn", + }; + let mut diag = lint.build(fluent::lint::improper_ctypes); + diag.set_arg("ty", ty); + diag.set_arg("desc", item_description); + diag.span_label(sp, fluent::lint::label); + if let Some(help) = help { + diag.help(help); + } + diag.note(note); + if let ty::Adt(def, _) = ty.kind() { + if let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) { + diag.span_note(sp, fluent::lint::note); + } + } + diag.emit(); + }); + } + + fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + struct ProhibitOpaqueTypes<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + } + + impl<'a, 'tcx> ty::visit::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> { + type BreakTy = Ty<'tcx>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { + match ty.kind() { + ty::Opaque(..) => ControlFlow::Break(ty), + // Consider opaque types within projections FFI-safe if they do not normalize + // to more opaque types. + ty::Projection(..) => { + let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty); + + // If `ty` is an opaque type directly then `super_visit_with` won't invoke + // this function again. + if ty.has_opaque_types() { + self.visit_ty(ty) + } else { + ControlFlow::CONTINUE + } + } + _ => ty.super_visit_with(self), + } + } + } + + if let Some(ty) = ty.visit_with(&mut ProhibitOpaqueTypes { cx: self.cx }).break_value() { + self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint::improper_ctypes_opaque, None); + true + } else { + false + } + } + + fn check_type_for_ffi_and_report_errors( + &mut self, + sp: Span, + ty: Ty<'tcx>, + is_static: bool, + is_return_type: bool, + ) { + // We have to check for opaque types before `normalize_erasing_regions`, + // which will replace opaque types with their underlying concrete type. + if self.check_for_opaque_ty(sp, ty) { + // We've already emitted an error due to an opaque type. + return; + } + + // it is only OK to use this function because extern fns cannot have + // any generic types right now: + let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty); + + // C doesn't really support passing arrays by value - the only way to pass an array by value + // is through a struct. So, first test that the top level isn't an array, and then + // recursively check the types inside. + if !is_static && self.check_for_array_ty(sp, ty) { + return; + } + + // Don't report FFI errors for unit return types. This check exists here, and not in + // `check_foreign_fn` (where it would make more sense) so that normalization has definitely + // happened. + if is_return_type && ty.is_unit() { + return; + } + + match self.check_type_for_ffi(&mut FxHashSet::default(), ty) { + FfiResult::FfiSafe => {} + FfiResult::FfiPhantom(ty) => { + self.emit_ffi_unsafe_type_lint( + ty, + sp, + fluent::lint::improper_ctypes_only_phantomdata, + None, + ); + } + // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic + // argument, which after substitution, is `()`, then this branch can be hit. + FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {} + FfiResult::FfiUnsafe { ty, reason, help } => { + self.emit_ffi_unsafe_type_lint(ty, sp, reason, help); + } + } + } + + fn check_foreign_fn(&mut self, id: hir::HirId, decl: &hir::FnDecl<'_>) { + let def_id = self.cx.tcx.hir().local_def_id(id); + let sig = self.cx.tcx.fn_sig(def_id); + let sig = self.cx.tcx.erase_late_bound_regions(sig); + + for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { + self.check_type_for_ffi_and_report_errors(input_hir.span, *input_ty, false, false); + } + + if let hir::FnRetTy::Return(ref ret_hir) = decl.output { + let ret_ty = sig.output(); + self.check_type_for_ffi_and_report_errors(ret_hir.span, ret_ty, false, true); + } + } + + fn check_foreign_static(&mut self, id: hir::HirId, span: Span) { + let def_id = self.cx.tcx.hir().local_def_id(id); + let ty = self.cx.tcx.type_of(def_id); + self.check_type_for_ffi_and_report_errors(span, ty, true, false); + } + + fn is_internal_abi(&self, abi: SpecAbi) -> bool { + matches!( + abi, + SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic | SpecAbi::PlatformIntrinsic + ) + } +} + +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { + fn check_foreign_item(&mut self, cx: &LateContext<'_>, it: &hir::ForeignItem<'_>) { + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; + let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); + + if !vis.is_internal_abi(abi) { + match it.kind { + hir::ForeignItemKind::Fn(ref decl, _, _) => { + vis.check_foreign_fn(it.hir_id(), decl); + } + hir::ForeignItemKind::Static(ref ty, _) => { + vis.check_foreign_static(it.hir_id(), ty.span); + } + hir::ForeignItemKind::Type => (), + } + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: hir::intravisit::FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'_>, + _: &'tcx hir::Body<'_>, + _: Span, + hir_id: hir::HirId, + ) { + use hir::intravisit::FnKind; + + let abi = match kind { + FnKind::ItemFn(_, _, header, ..) => header.abi, + FnKind::Method(_, sig, ..) => sig.header.abi, + _ => return, + }; + + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; + if !vis.is_internal_abi(abi) { + vis.check_foreign_fn(hir_id, decl); + } + } +} + +declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); + +impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { + fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { + if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind { + let t = cx.tcx.type_of(it.def_id); + let ty = cx.tcx.erase_regions(t); + let Ok(layout) = cx.layout_of(ty) else { return }; + let Variants::Multiple { + tag_encoding: TagEncoding::Direct, tag, ref variants, .. + } = &layout.variants else { + return + }; + + let tag_size = tag.size(&cx.tcx).bytes(); + + debug!( + "enum `{}` is {} bytes large with layout:\n{:#?}", + t, + layout.size.bytes(), + layout + ); + + let (largest, slargest, largest_index) = iter::zip(enum_definition.variants, variants) + .map(|(variant, variant_layout)| { + // Subtract the size of the enum tag. + let bytes = variant_layout.size().bytes().saturating_sub(tag_size); + + debug!("- variant `{}` is {} bytes large", variant.ident, bytes); + bytes + }) + .enumerate() + .fold((0, 0, 0), |(l, s, li), (idx, size)| { + if size > l { + (size, l, idx) + } else if size > s { + (l, size, li) + } else { + (l, s, li) + } + }); + + // We only warn if the largest variant is at least thrice as large as + // the second-largest. + if largest > slargest * 3 && slargest > 0 { + cx.struct_span_lint( + VARIANT_SIZE_DIFFERENCES, + enum_definition.variants[largest_index].span, + |lint| { + lint.build(fluent::lint::variant_size_differences) + .set_arg("largest", largest) + .emit(); + }, + ); + } + } + } +} + +declare_lint! { + /// The `invalid_atomic_ordering` lint detects passing an `Ordering` + /// to an atomic operation that does not support that ordering. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # use core::sync::atomic::{AtomicU8, Ordering}; + /// let atom = AtomicU8::new(0); + /// let value = atom.load(Ordering::Release); + /// # let _ = value; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Some atomic operations are only supported for a subset of the + /// `atomic::Ordering` variants. Passing an unsupported variant will cause + /// an unconditional panic at runtime, which is detected by this lint. + /// + /// This lint will trigger in the following cases: (where `AtomicType` is an + /// atomic type from `core::sync::atomic`, such as `AtomicBool`, + /// `AtomicPtr`, `AtomicUsize`, or any of the other integer atomics). + /// + /// - Passing `Ordering::Acquire` or `Ordering::AcqRel` to + /// `AtomicType::store`. + /// + /// - Passing `Ordering::Release` or `Ordering::AcqRel` to + /// `AtomicType::load`. + /// + /// - Passing `Ordering::Relaxed` to `core::sync::atomic::fence` or + /// `core::sync::atomic::compiler_fence`. + /// + /// - Passing `Ordering::Release` or `Ordering::AcqRel` as the failure + /// ordering for any of `AtomicType::compare_exchange`, + /// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`. + INVALID_ATOMIC_ORDERING, + Deny, + "usage of invalid atomic ordering in atomic operations and memory fences" +} + +declare_lint_pass!(InvalidAtomicOrdering => [INVALID_ATOMIC_ORDERING]); + +impl InvalidAtomicOrdering { + fn inherent_atomic_method_call<'hir>( + cx: &LateContext<'_>, + expr: &Expr<'hir>, + recognized_names: &[Symbol], // used for fast path calculation + ) -> Option<(Symbol, &'hir [Expr<'hir>])> { + const ATOMIC_TYPES: &[Symbol] = &[ + sym::AtomicBool, + sym::AtomicPtr, + sym::AtomicUsize, + sym::AtomicU8, + sym::AtomicU16, + sym::AtomicU32, + sym::AtomicU64, + sym::AtomicU128, + sym::AtomicIsize, + sym::AtomicI8, + sym::AtomicI16, + sym::AtomicI32, + sym::AtomicI64, + sym::AtomicI128, + ]; + if let ExprKind::MethodCall(ref method_path, args, _) = &expr.kind + && recognized_names.contains(&method_path.ident.name) + && let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && let Some(impl_did) = cx.tcx.impl_of_method(m_def_id) + && let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() + // skip extension traits, only lint functions from the standard library + && cx.tcx.trait_id_of_impl(impl_did).is_none() + && let parent = cx.tcx.parent(adt.did()) + && cx.tcx.is_diagnostic_item(sym::atomic_mod, parent) + && ATOMIC_TYPES.contains(&cx.tcx.item_name(adt.did())) + { + return Some((method_path.ident.name, args)); + } + None + } + + fn match_ordering(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<Symbol> { + let ExprKind::Path(ref ord_qpath) = ord_arg.kind else { return None }; + let did = cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id()?; + let tcx = cx.tcx; + let atomic_ordering = tcx.get_diagnostic_item(sym::Ordering); + let name = tcx.item_name(did); + let parent = tcx.parent(did); + [sym::Relaxed, sym::Release, sym::Acquire, sym::AcqRel, sym::SeqCst].into_iter().find( + |&ordering| { + name == ordering + && (Some(parent) == atomic_ordering + // needed in case this is a ctor, not a variant + || tcx.opt_parent(parent) == atomic_ordering) + }, + ) + } + + fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::load, sym::store]) + && let Some((ordering_arg, invalid_ordering)) = match method { + sym::load => Some((&args[1], sym::Release)), + sym::store => Some((&args[2], sym::Acquire)), + _ => None, + } + && let Some(ordering) = Self::match_ordering(cx, ordering_arg) + && (ordering == invalid_ordering || ordering == sym::AcqRel) + { + cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| { + if method == sym::load { + diag.build(fluent::lint::atomic_ordering_load) + .help(fluent::lint::help) + .emit() + } else { + debug_assert_eq!(method, sym::store); + diag.build(fluent::lint::atomic_ordering_store) + .help(fluent::lint::help) + .emit(); + } + }); + } + } + + fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Call(ref func, ref args) = expr.kind + && let ExprKind::Path(ref func_qpath) = func.kind + && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id() + && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::fence | sym::compiler_fence)) + && Self::match_ordering(cx, &args[0]) == Some(sym::Relaxed) + { + cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, |diag| { + diag.build(fluent::lint::atomic_ordering_fence) + .help(fluent::lint::help) + .emit(); + }); + } + } + + fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) { + let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak]) + else {return }; + + let fail_order_arg = match method { + sym::fetch_update => &args[2], + sym::compare_exchange | sym::compare_exchange_weak => &args[4], + _ => return, + }; + + let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return }; + + if matches!(fail_ordering, sym::Release | sym::AcqRel) { + #[derive(LintDiagnostic)] + #[lint(lint::atomic_ordering_invalid)] + #[help] + struct InvalidAtomicOrderingDiag { + method: Symbol, + #[label] + fail_order_arg_span: Span, + } + + cx.emit_spanned_lint( + INVALID_ATOMIC_ORDERING, + fail_order_arg.span, + InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span }, + ); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for InvalidAtomicOrdering { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + Self::check_atomic_load_store(cx, expr); + Self::check_memory_fence(cx, expr); + Self::check_atomic_compare_exchange(cx, expr); + } +} diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs new file mode 100644 index 000000000..b6cf18291 --- /dev/null +++ b/compiler/rustc_lint/src/unused.rs @@ -0,0 +1,1197 @@ +use crate::Lint; +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_ast::util::{classify, parser}; +use rustc_ast::{ExprKind, StmtKind}; +use rustc_errors::{fluent, pluralize, Applicability, MultiSpan}; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_middle::ty::adjustment; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::Symbol; +use rustc_span::symbol::{kw, sym}; +use rustc_span::{BytePos, Span, DUMMY_SP}; + +declare_lint! { + /// The `unused_must_use` lint detects unused result of a type flagged as + /// `#[must_use]`. + /// + /// ### Example + /// + /// ```rust + /// fn returns_result() -> Result<(), ()> { + /// Ok(()) + /// } + /// + /// fn main() { + /// returns_result(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The `#[must_use]` attribute is an indicator that it is a mistake to + /// ignore the value. See [the reference] for more details. + /// + /// [the reference]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + pub UNUSED_MUST_USE, + Warn, + "unused result of a type flagged as `#[must_use]`", + report_in_external_macro +} + +declare_lint! { + /// The `unused_results` lint checks for the unused result of an + /// expression in a statement. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unused_results)] + /// fn foo<T>() -> T { panic!() } + /// + /// fn main() { + /// foo::<usize>(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Ignoring the return value of a function may indicate a mistake. In + /// cases were it is almost certain that the result should be used, it is + /// recommended to annotate the function with the [`must_use` attribute]. + /// Failure to use such a return value will trigger the [`unused_must_use` + /// lint] which is warn-by-default. The `unused_results` lint is + /// essentially the same, but triggers for *all* return values. + /// + /// This lint is "allow" by default because it can be noisy, and may not be + /// an actual problem. For example, calling the `remove` method of a `Vec` + /// or `HashMap` returns the previous value, which you may not care about. + /// Using this lint would require explicitly ignoring or discarding such + /// values. + /// + /// [`must_use` attribute]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// [`unused_must_use` lint]: warn-by-default.html#unused-must-use + pub UNUSED_RESULTS, + Allow, + "unused result of an expression in a statement" +} + +declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); + +impl<'tcx> LateLintPass<'tcx> for UnusedResults { + fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { + let expr = match s.kind { + hir::StmtKind::Semi(ref expr) => &**expr, + _ => return, + }; + + if let hir::ExprKind::Ret(..) = expr.kind { + return; + } + + let ty = cx.typeck_results().expr_ty(&expr); + let type_permits_lack_of_use = check_must_use_ty(cx, ty, &expr, s.span, "", "", 1); + + let mut fn_warned = false; + let mut op_warned = false; + let maybe_def_id = match expr.kind { + hir::ExprKind::Call(ref callee, _) => { + match callee.kind { + hir::ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, callee.hir_id) { + Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), + // `Res::Local` if it was a closure, for which we + // do not currently support must-use linting + _ => None, + } + } + _ => None, + } + } + hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }; + if let Some(def_id) = maybe_def_id { + fn_warned = check_must_use_def(cx, def_id, s.span, "return value of ", ""); + } else if type_permits_lack_of_use { + // We don't warn about unused unit or uninhabited types. + // (See https://github.com/rust-lang/rust/issues/43806 for details.) + return; + } + + let must_use_op = match expr.kind { + // Hardcoding operators here seemed more expedient than the + // refactoring that would be needed to look up the `#[must_use]` + // attribute which does exist on the comparison trait methods + hir::ExprKind::Binary(bin_op, ..) => match bin_op.node { + hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => Some("comparison"), + hir::BinOpKind::Add + | hir::BinOpKind::Sub + | hir::BinOpKind::Div + | hir::BinOpKind::Mul + | hir::BinOpKind::Rem => Some("arithmetic operation"), + hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"), + hir::BinOpKind::BitXor + | hir::BinOpKind::BitAnd + | hir::BinOpKind::BitOr + | hir::BinOpKind::Shl + | hir::BinOpKind::Shr => Some("bitwise operation"), + }, + hir::ExprKind::AddrOf(..) => Some("borrow"), + hir::ExprKind::Unary(..) => Some("unary operation"), + _ => None, + }; + + if let Some(must_use_op) = must_use_op { + cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| { + lint.build(fluent::lint::unused_op) + .set_arg("op", must_use_op) + .span_label(expr.span, fluent::lint::label) + .span_suggestion_verbose( + expr.span.shrink_to_lo(), + fluent::lint::suggestion, + "let _ = ", + Applicability::MachineApplicable, + ) + .emit(); + }); + op_warned = true; + } + + if !(type_permits_lack_of_use || fn_warned || op_warned) { + cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| { + lint.build(fluent::lint::unused_result).set_arg("ty", ty).emit(); + }); + } + + // Returns whether an error has been emitted (and thus another does not need to be later). + fn check_must_use_ty<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + span: Span, + descr_pre: &str, + descr_post: &str, + plural_len: usize, + ) -> bool { + if ty.is_unit() + || cx.tcx.is_ty_uninhabited_from( + cx.tcx.parent_module(expr.hir_id).to_def_id(), + ty, + cx.param_env, + ) + { + return true; + } + + let plural_suffix = pluralize!(plural_len); + + match *ty.kind() { + ty::Adt(..) if ty.is_box() => { + let boxed_ty = ty.boxed_ty(); + let descr_pre = &format!("{}boxed ", descr_pre); + check_must_use_ty(cx, boxed_ty, expr, span, descr_pre, descr_post, plural_len) + } + ty::Adt(def, _) => check_must_use_def(cx, def.did(), span, descr_pre, descr_post), + ty::Opaque(def, _) => { + let mut has_emitted = false; + for &(predicate, _) in cx.tcx.explicit_item_bounds(def) { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::PredicateKind::Trait(ref poly_trait_predicate) = + predicate.kind().skip_binder() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + let descr_pre = + &format!("{}implementer{} of ", descr_pre, plural_suffix,); + if check_must_use_def(cx, def_id, span, descr_pre, descr_post) { + has_emitted = true; + break; + } + } + } + has_emitted + } + ty::Dynamic(binder, _) => { + let mut has_emitted = false; + for predicate in binder.iter() { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = + predicate.skip_binder() + { + let def_id = trait_ref.def_id; + let descr_post = + &format!(" trait object{}{}", plural_suffix, descr_post,); + if check_must_use_def(cx, def_id, span, descr_pre, descr_post) { + has_emitted = true; + break; + } + } + } + has_emitted + } + ty::Tuple(ref tys) => { + let mut has_emitted = false; + let comps = if let hir::ExprKind::Tup(comps) = expr.kind { + debug_assert_eq!(comps.len(), tys.len()); + comps + } else { + &[] + }; + for (i, ty) in tys.iter().enumerate() { + let descr_post = &format!(" in tuple element {}", i); + let e = comps.get(i).unwrap_or(expr); + let span = e.span; + if check_must_use_ty(cx, ty, e, span, descr_pre, descr_post, plural_len) { + has_emitted = true; + } + } + has_emitted + } + ty::Array(ty, len) => match len.try_eval_usize(cx.tcx, cx.param_env) { + // If the array is empty we don't lint, to avoid false positives + Some(0) | None => false, + // If the array is definitely non-empty, we can do `#[must_use]` checking. + Some(n) => { + let descr_pre = &format!("{}array{} of ", descr_pre, plural_suffix,); + check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, n as usize + 1) + } + }, + ty::Closure(..) => { + cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| { + // FIXME(davidtwco): this isn't properly translatable becauses of the + // pre/post strings + lint.build(fluent::lint::unused_closure) + .set_arg("count", plural_len) + .set_arg("pre", descr_pre) + .set_arg("post", descr_post) + .note(fluent::lint::note) + .emit(); + }); + true + } + ty::Generator(..) => { + cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| { + // FIXME(davidtwco): this isn't properly translatable becauses of the + // pre/post strings + lint.build(fluent::lint::unused_generator) + .set_arg("count", plural_len) + .set_arg("pre", descr_pre) + .set_arg("post", descr_post) + .note(fluent::lint::note) + .emit(); + }); + true + } + _ => false, + } + } + + // Returns whether an error has been emitted (and thus another does not need to be later). + // FIXME: Args desc_{pre,post}_path could be made lazy by taking Fn() -> &str, but this + // would make calling it a big awkward. Could also take String (so args are moved), but + // this would still require a copy into the format string, which would only be executed + // when needed. + fn check_must_use_def( + cx: &LateContext<'_>, + def_id: DefId, + span: Span, + descr_pre_path: &str, + descr_post_path: &str, + ) -> bool { + if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) { + cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| { + // FIXME(davidtwco): this isn't properly translatable becauses of the pre/post + // strings + let mut err = lint.build(fluent::lint::unused_def); + err.set_arg("pre", descr_pre_path); + err.set_arg("post", descr_post_path); + err.set_arg("def", cx.tcx.def_path_str(def_id)); + // check for #[must_use = "..."] + if let Some(note) = attr.value_str() { + err.note(note.as_str()); + } + err.emit(); + }); + true + } else { + false + } + } + } +} + +declare_lint! { + /// The `path_statements` lint detects path statements with no effect. + /// + /// ### Example + /// + /// ```rust + /// let x = 42; + /// + /// x; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// It is usually a mistake to have a statement that has no effect. + pub PATH_STATEMENTS, + Warn, + "path statements with no effect" +} + +declare_lint_pass!(PathStatements => [PATH_STATEMENTS]); + +impl<'tcx> LateLintPass<'tcx> for PathStatements { + fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { + if let hir::StmtKind::Semi(expr) = s.kind { + if let hir::ExprKind::Path(_) = expr.kind { + cx.struct_span_lint(PATH_STATEMENTS, s.span, |lint| { + let ty = cx.typeck_results().expr_ty(expr); + if ty.needs_drop(cx.tcx, cx.param_env) { + let mut lint = lint.build(fluent::lint::path_statement_drop); + if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) { + lint.span_suggestion( + s.span, + fluent::lint::suggestion, + format!("drop({});", snippet), + Applicability::MachineApplicable, + ); + } else { + lint.span_help(s.span, fluent::lint::suggestion); + } + lint.emit(); + } else { + lint.build(fluent::lint::path_statement_no_effect).emit(); + } + }); + } + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum UnusedDelimsCtx { + FunctionArg, + MethodArg, + AssignedValue, + AssignedValueLetElse, + IfCond, + WhileCond, + ForIterExpr, + MatchScrutineeExpr, + ReturnValue, + BlockRetValue, + LetScrutineeExpr, + ArrayLenExpr, + AnonConst, + MatchArmExpr, +} + +impl From<UnusedDelimsCtx> for &'static str { + fn from(ctx: UnusedDelimsCtx) -> &'static str { + match ctx { + UnusedDelimsCtx::FunctionArg => "function argument", + UnusedDelimsCtx::MethodArg => "method argument", + UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => { + "assigned value" + } + UnusedDelimsCtx::IfCond => "`if` condition", + UnusedDelimsCtx::WhileCond => "`while` condition", + UnusedDelimsCtx::ForIterExpr => "`for` iterator expression", + UnusedDelimsCtx::MatchScrutineeExpr => "`match` scrutinee expression", + UnusedDelimsCtx::ReturnValue => "`return` value", + UnusedDelimsCtx::BlockRetValue => "block return value", + UnusedDelimsCtx::LetScrutineeExpr => "`let` scrutinee expression", + UnusedDelimsCtx::ArrayLenExpr | UnusedDelimsCtx::AnonConst => "const expression", + UnusedDelimsCtx::MatchArmExpr => "match arm expression", + } + } +} + +/// Used by both `UnusedParens` and `UnusedBraces` to prevent code duplication. +trait UnusedDelimLint { + const DELIM_STR: &'static str; + + /// Due to `ref` pattern, there can be a difference between using + /// `{ expr }` and `expr` in pattern-matching contexts. This means + /// that we should only lint `unused_parens` and not `unused_braces` + /// in this case. + /// + /// ```rust + /// let mut a = 7; + /// let ref b = { a }; // We actually borrow a copy of `a` here. + /// a += 1; // By mutating `a` we invalidate any borrows of `a`. + /// assert_eq!(b + 1, a); // `b` does not borrow `a`, so we can still use it here. + /// ``` + const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool; + + // this cannot be a constant is it refers to a static. + fn lint(&self) -> &'static Lint; + + fn check_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + followed_by_block: bool, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ); + + fn is_expr_delims_necessary( + inner: &ast::Expr, + followed_by_block: bool, + followed_by_else: bool, + ) -> bool { + if followed_by_else { + match inner.kind { + ast::ExprKind::Binary(op, ..) if op.node.lazy() => return true, + _ if classify::expr_trailing_brace(inner).is_some() => return true, + _ => {} + } + } + + // Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }` + let lhs_needs_parens = { + let mut innermost = inner; + loop { + innermost = match &innermost.kind { + ExprKind::Binary(_, lhs, _rhs) => lhs, + ExprKind::Call(fn_, _params) => fn_, + ExprKind::Cast(expr, _ty) => expr, + ExprKind::Type(expr, _ty) => expr, + ExprKind::Index(base, _subscript) => base, + _ => break false, + }; + if !classify::expr_requires_semi_to_be_stmt(innermost) { + break true; + } + } + }; + + lhs_needs_parens + || (followed_by_block + && match &inner.kind { + ExprKind::Ret(_) | ExprKind::Break(..) | ExprKind::Yield(..) => true, + ExprKind::Range(_lhs, Some(rhs), _limits) => { + matches!(rhs.kind, ExprKind::Block(..)) + } + _ => parser::contains_exterior_struct_lit(&inner), + }) + } + + fn emit_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ) { + let spans = match value.kind { + ast::ExprKind::Block(ref block, None) if block.stmts.len() > 0 => { + let start = block.stmts[0].span; + let end = block.stmts[block.stmts.len() - 1].span; + if value.span.from_expansion() || start.from_expansion() || end.from_expansion() { + ( + value.span.with_hi(value.span.lo() + BytePos(1)), + value.span.with_lo(value.span.hi() - BytePos(1)), + ) + } else { + (value.span.with_hi(start.lo()), value.span.with_lo(end.hi())) + } + } + ast::ExprKind::Paren(ref expr) => { + if value.span.from_expansion() || expr.span.from_expansion() { + ( + value.span.with_hi(value.span.lo() + BytePos(1)), + value.span.with_lo(value.span.hi() - BytePos(1)), + ) + } else { + (value.span.with_hi(expr.span.lo()), value.span.with_lo(expr.span.hi())) + } + } + _ => return, + }; + let keep_space = ( + left_pos.map_or(false, |s| s >= value.span.lo()), + right_pos.map_or(false, |s| s <= value.span.hi()), + ); + self.emit_unused_delims(cx, spans, ctx.into(), keep_space); + } + + fn emit_unused_delims( + &self, + cx: &EarlyContext<'_>, + spans: (Span, Span), + msg: &str, + keep_space: (bool, bool), + ) { + // FIXME(flip1995): Quick and dirty fix for #70814. This should be fixed in rustdoc + // properly. + if spans.0 == DUMMY_SP || spans.1 == DUMMY_SP { + return; + } + + cx.struct_span_lint(self.lint(), MultiSpan::from(vec![spans.0, spans.1]), |lint| { + let replacement = vec![ + (spans.0, if keep_space.0 { " ".into() } else { "".into() }), + (spans.1, if keep_space.1 { " ".into() } else { "".into() }), + ]; + lint.build(fluent::lint::unused_delim) + .set_arg("delim", Self::DELIM_STR) + .set_arg("item", msg) + .multipart_suggestion( + fluent::lint::suggestion, + replacement, + Applicability::MachineApplicable, + ) + .emit(); + }); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + use rustc_ast::ExprKind::*; + let (value, ctx, followed_by_block, left_pos, right_pos) = match e.kind { + // Do not lint `unused_braces` in `if let` expressions. + If(ref cond, ref block, _) + if !matches!(cond.kind, Let(_, _, _)) + || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => + { + let left = e.span.lo() + rustc_span::BytePos(2); + let right = block.span.lo(); + (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right)) + } + + // Do not lint `unused_braces` in `while let` expressions. + While(ref cond, ref block, ..) + if !matches!(cond.kind, Let(_, _, _)) + || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => + { + let left = e.span.lo() + rustc_span::BytePos(5); + let right = block.span.lo(); + (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right)) + } + + ForLoop(_, ref cond, ref block, ..) => { + (cond, UnusedDelimsCtx::ForIterExpr, true, None, Some(block.span.lo())) + } + + Match(ref head, _) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { + let left = e.span.lo() + rustc_span::BytePos(5); + (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None) + } + + Ret(Some(ref value)) => { + let left = e.span.lo() + rustc_span::BytePos(3); + (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None) + } + + Assign(_, ref value, _) | AssignOp(.., ref value) => { + (value, UnusedDelimsCtx::AssignedValue, false, None, None) + } + // either function/method call, or something this lint doesn't care about + ref call_or_other => { + let (args_to_check, ctx) = match *call_or_other { + Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg), + // first "argument" is self (which sometimes needs delims) + MethodCall(_, ref args, _) => (&args[1..], UnusedDelimsCtx::MethodArg), + // actual catch-all arm + _ => { + return; + } + }; + // Don't lint if this is a nested macro expansion: otherwise, the lint could + // trigger in situations that macro authors shouldn't have to care about, e.g., + // when a parenthesized token tree matched in one macro expansion is matched as + // an expression in another and used as a fn/method argument (Issue #47775) + if e.span.ctxt().outer_expn_data().call_site.from_expansion() { + return; + } + for arg in args_to_check { + self.check_unused_delims_expr(cx, arg, ctx, false, None, None); + } + return; + } + }; + self.check_unused_delims_expr(cx, &value, ctx, followed_by_block, left_pos, right_pos); + } + + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + match s.kind { + StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { + if let Some((init, els)) = local.kind.init_else_opt() { + let ctx = match els { + None => UnusedDelimsCtx::AssignedValue, + Some(_) => UnusedDelimsCtx::AssignedValueLetElse, + }; + self.check_unused_delims_expr(cx, init, ctx, false, None, None); + } + } + StmtKind::Expr(ref expr) => { + self.check_unused_delims_expr( + cx, + &expr, + UnusedDelimsCtx::BlockRetValue, + false, + None, + None, + ); + } + _ => {} + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + use ast::ItemKind::*; + + if let Const(.., Some(expr)) | Static(.., Some(expr)) = &item.kind { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::AssignedValue, + false, + None, + None, + ); + } + } +} + +declare_lint! { + /// The `unused_parens` lint detects `if`, `match`, `while` and `return` + /// with parentheses; they do not need them. + /// + /// ### Examples + /// + /// ```rust + /// if(true) {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The parentheses are not needed, and should be removed. This is the + /// preferred style for writing these expressions. + pub(super) UNUSED_PARENS, + Warn, + "`if`, `match`, `while` and `return` do not need parentheses" +} + +declare_lint_pass!(UnusedParens => [UNUSED_PARENS]); + +impl UnusedDelimLint for UnusedParens { + const DELIM_STR: &'static str = "parentheses"; + + const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = true; + + fn lint(&self) -> &'static Lint { + UNUSED_PARENS + } + + fn check_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + followed_by_block: bool, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ) { + match value.kind { + ast::ExprKind::Paren(ref inner) => { + let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse; + if !Self::is_expr_delims_necessary(inner, followed_by_block, followed_by_else) + && value.attrs.is_empty() + && !value.span.from_expansion() + && (ctx != UnusedDelimsCtx::LetScrutineeExpr + || !matches!(inner.kind, ast::ExprKind::Binary( + rustc_span::source_map::Spanned { node, .. }, + _, + _, + ) if node.lazy())) + { + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos) + } + } + ast::ExprKind::Let(_, ref expr, _) => { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::LetScrutineeExpr, + followed_by_block, + None, + None, + ); + } + _ => {} + } + } +} + +impl UnusedParens { + fn check_unused_parens_pat( + &self, + cx: &EarlyContext<'_>, + value: &ast::Pat, + avoid_or: bool, + avoid_mut: bool, + ) { + use ast::{BindingMode, Mutability, PatKind}; + + if let PatKind::Paren(inner) = &value.kind { + match inner.kind { + // The lint visitor will visit each subpattern of `p`. We do not want to lint + // any range pattern no matter where it occurs in the pattern. For something like + // `&(a..=b)`, there is a recursive `check_pat` on `a` and `b`, but we will assume + // that if there are unnecessary parens they serve a purpose of readability. + PatKind::Range(..) => return, + // Avoid `p0 | .. | pn` if we should. + PatKind::Or(..) if avoid_or => return, + // Avoid `mut x` and `mut x @ p` if we should: + PatKind::Ident(BindingMode::ByValue(Mutability::Mut), ..) if avoid_mut => return, + // Otherwise proceed with linting. + _ => {} + } + let spans = if value.span.from_expansion() || inner.span.from_expansion() { + ( + value.span.with_hi(value.span.lo() + BytePos(1)), + value.span.with_lo(value.span.hi() - BytePos(1)), + ) + } else { + (value.span.with_hi(inner.span.lo()), value.span.with_lo(inner.span.hi())) + }; + self.emit_unused_delims(cx, spans, "pattern", (false, false)); + } + } +} + +impl EarlyLintPass for UnusedParens { + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + match e.kind { + ExprKind::Let(ref pat, _, _) | ExprKind::ForLoop(ref pat, ..) => { + self.check_unused_parens_pat(cx, pat, false, false); + } + // We ignore parens in cases like `if (((let Some(0) = Some(1))))` because we already + // handle a hard error for them during AST lowering in `lower_expr_mut`, but we still + // want to complain about things like `if let 42 = (42)`. + ExprKind::If(ref cond, ref block, ref else_) + if matches!(cond.peel_parens().kind, ExprKind::Let(..)) => + { + self.check_unused_delims_expr( + cx, + cond.peel_parens(), + UnusedDelimsCtx::LetScrutineeExpr, + true, + None, + None, + ); + for stmt in &block.stmts { + <Self as UnusedDelimLint>::check_stmt(self, cx, stmt); + } + if let Some(e) = else_ { + <Self as UnusedDelimLint>::check_expr(self, cx, e); + } + return; + } + ExprKind::Match(ref _expr, ref arm) => { + for a in arm { + self.check_unused_delims_expr( + cx, + &a.body, + UnusedDelimsCtx::MatchArmExpr, + false, + None, + None, + ); + } + } + _ => {} + } + + <Self as UnusedDelimLint>::check_expr(self, cx, e) + } + + fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) { + use ast::{Mutability, PatKind::*}; + match &p.kind { + // Do not lint on `(..)` as that will result in the other arms being useless. + Paren(_) + // The other cases do not contain sub-patterns. + | Wild | Rest | Lit(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) => {}, + // These are list-like patterns; parens can always be removed. + TupleStruct(_, _, ps) | Tuple(ps) | Slice(ps) | Or(ps) => for p in ps { + self.check_unused_parens_pat(cx, p, false, false); + }, + Struct(_, _, fps, _) => for f in fps { + self.check_unused_parens_pat(cx, &f.pat, false, false); + }, + // Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106. + Ident(.., Some(p)) | Box(p) => self.check_unused_parens_pat(cx, p, true, false), + // Avoid linting on `&(mut x)` as `&mut x` has a different meaning, #55342. + // Also avoid linting on `& mut? (p0 | .. | pn)`, #64106. + Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not), + } + } + + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + if let StmtKind::Local(ref local) = s.kind { + self.check_unused_parens_pat(cx, &local.pat, true, false); + } + + <Self as UnusedDelimLint>::check_stmt(self, cx, s) + } + + fn check_param(&mut self, cx: &EarlyContext<'_>, param: &ast::Param) { + self.check_unused_parens_pat(cx, ¶m.pat, true, false); + } + + fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) { + self.check_unused_parens_pat(cx, &arm.pat, false, false); + } + + fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) { + if let ast::TyKind::Paren(r) = &ty.kind { + match &r.kind { + ast::TyKind::TraitObject(..) => {} + ast::TyKind::ImplTrait(_, bounds) if bounds.len() > 1 => {} + ast::TyKind::Array(_, len) => { + self.check_unused_delims_expr( + cx, + &len.value, + UnusedDelimsCtx::ArrayLenExpr, + false, + None, + None, + ); + } + _ => { + let spans = if ty.span.from_expansion() || r.span.from_expansion() { + ( + ty.span.with_hi(ty.span.lo() + BytePos(1)), + ty.span.with_lo(ty.span.hi() - BytePos(1)), + ) + } else { + (ty.span.with_hi(r.span.lo()), ty.span.with_lo(r.span.hi())) + }; + self.emit_unused_delims(cx, spans, "type", (false, false)); + } + } + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + <Self as UnusedDelimLint>::check_item(self, cx, item) + } +} + +declare_lint! { + /// The `unused_braces` lint detects unnecessary braces around an + /// expression. + /// + /// ### Example + /// + /// ```rust + /// if { true } { + /// // ... + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The braces are not needed, and should be removed. This is the + /// preferred style for writing these expressions. + pub(super) UNUSED_BRACES, + Warn, + "unnecessary braces around an expression" +} + +declare_lint_pass!(UnusedBraces => [UNUSED_BRACES]); + +impl UnusedDelimLint for UnusedBraces { + const DELIM_STR: &'static str = "braces"; + + const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = false; + + fn lint(&self) -> &'static Lint { + UNUSED_BRACES + } + + fn check_unused_delims_expr( + &self, + cx: &EarlyContext<'_>, + value: &ast::Expr, + ctx: UnusedDelimsCtx, + followed_by_block: bool, + left_pos: Option<BytePos>, + right_pos: Option<BytePos>, + ) { + match value.kind { + ast::ExprKind::Block(ref inner, None) + if inner.rules == ast::BlockCheckMode::Default => + { + // emit a warning under the following conditions: + // + // - the block does not have a label + // - the block is not `unsafe` + // - the block contains exactly one expression (do not lint `{ expr; }`) + // - `followed_by_block` is true and the internal expr may contain a `{` + // - the block is not multiline (do not lint multiline match arms) + // ``` + // match expr { + // Pattern => { + // somewhat_long_expression + // } + // // ... + // } + // ``` + // - the block has no attribute and was not created inside a macro + // - if the block is an `anon_const`, the inner expr must be a literal + // (do not lint `struct A<const N: usize>; let _: A<{ 2 + 3 }>;`) + // + // FIXME(const_generics): handle paths when #67075 is fixed. + if let [stmt] = inner.stmts.as_slice() { + if let ast::StmtKind::Expr(ref expr) = stmt.kind { + if !Self::is_expr_delims_necessary(expr, followed_by_block, false) + && (ctx != UnusedDelimsCtx::AnonConst + || matches!(expr.kind, ast::ExprKind::Lit(_))) + && !cx.sess().source_map().is_multiline(value.span) + && value.attrs.is_empty() + && !value.span.from_expansion() + { + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos) + } + } + } + } + ast::ExprKind::Let(_, ref expr, _) => { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::LetScrutineeExpr, + followed_by_block, + None, + None, + ); + } + _ => {} + } + } +} + +impl EarlyLintPass for UnusedBraces { + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + <Self as UnusedDelimLint>::check_stmt(self, cx, s) + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + <Self as UnusedDelimLint>::check_expr(self, cx, e); + + if let ExprKind::Repeat(_, ref anon_const) = e.kind { + self.check_unused_delims_expr( + cx, + &anon_const.value, + UnusedDelimsCtx::AnonConst, + false, + None, + None, + ); + } + } + + fn check_generic_arg(&mut self, cx: &EarlyContext<'_>, arg: &ast::GenericArg) { + if let ast::GenericArg::Const(ct) = arg { + self.check_unused_delims_expr( + cx, + &ct.value, + UnusedDelimsCtx::AnonConst, + false, + None, + None, + ); + } + } + + fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) { + if let Some(anon_const) = &v.disr_expr { + self.check_unused_delims_expr( + cx, + &anon_const.value, + UnusedDelimsCtx::AnonConst, + false, + None, + None, + ); + } + } + + fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) { + match ty.kind { + ast::TyKind::Array(_, ref len) => { + self.check_unused_delims_expr( + cx, + &len.value, + UnusedDelimsCtx::ArrayLenExpr, + false, + None, + None, + ); + } + + ast::TyKind::Typeof(ref anon_const) => { + self.check_unused_delims_expr( + cx, + &anon_const.value, + UnusedDelimsCtx::AnonConst, + false, + None, + None, + ); + } + + _ => {} + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + <Self as UnusedDelimLint>::check_item(self, cx, item) + } +} + +declare_lint! { + /// The `unused_import_braces` lint catches unnecessary braces around an + /// imported item. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unused_import_braces)] + /// use test::{A}; + /// + /// pub mod test { + /// pub struct A; + /// } + /// # fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// If there is only a single item, then remove the braces (`use test::A;` + /// for example). + /// + /// This lint is "allow" by default because it is only enforcing a + /// stylistic choice. + UNUSED_IMPORT_BRACES, + Allow, + "unnecessary braces around an imported item" +} + +declare_lint_pass!(UnusedImportBraces => [UNUSED_IMPORT_BRACES]); + +impl UnusedImportBraces { + fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) { + if let ast::UseTreeKind::Nested(ref items) = use_tree.kind { + // Recursively check nested UseTrees + for &(ref tree, _) in items { + self.check_use_tree(cx, tree, item); + } + + // Trigger the lint only if there is one nested item + if items.len() != 1 { + return; + } + + // Trigger the lint if the nested item is a non-self single item + let node_name = match items[0].0.kind { + ast::UseTreeKind::Simple(rename, ..) => { + let orig_ident = items[0].0.prefix.segments.last().unwrap().ident; + if orig_ident.name == kw::SelfLower { + return; + } + rename.unwrap_or(orig_ident).name + } + ast::UseTreeKind::Glob => Symbol::intern("*"), + ast::UseTreeKind::Nested(_) => return, + }; + + cx.struct_span_lint(UNUSED_IMPORT_BRACES, item.span, |lint| { + lint.build(fluent::lint::unused_import_braces).set_arg("node", node_name).emit(); + }); + } + } +} + +impl EarlyLintPass for UnusedImportBraces { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + if let ast::ItemKind::Use(ref use_tree) = item.kind { + self.check_use_tree(cx, use_tree, item); + } + } +} + +declare_lint! { + /// The `unused_allocation` lint detects unnecessary allocations that can + /// be eliminated. + /// + /// ### Example + /// + /// ```rust + /// #![feature(box_syntax)] + /// fn main() { + /// let a = (box [1, 2, 3]).len(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// When a `box` expression is immediately coerced to a reference, then + /// the allocation is unnecessary, and a reference (using `&` or `&mut`) + /// should be used instead to avoid the allocation. + pub(super) UNUSED_ALLOCATION, + Warn, + "detects unnecessary allocations that can be eliminated" +} + +declare_lint_pass!(UnusedAllocation => [UNUSED_ALLOCATION]); + +impl<'tcx> LateLintPass<'tcx> for UnusedAllocation { + fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { + match e.kind { + hir::ExprKind::Box(_) => {} + _ => return, + } + + for adj in cx.typeck_results().expr_adjustments(e) { + if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind { + cx.struct_span_lint(UNUSED_ALLOCATION, e.span, |lint| { + lint.build(match m { + adjustment::AutoBorrowMutability::Not => fluent::lint::unused_allocation, + adjustment::AutoBorrowMutability::Mut { .. } => { + fluent::lint::unused_allocation_mut + } + }) + .emit(); + }); + } + } + } +} |