Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add print_in_format_impl lint #8253

Merged
merged 2 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3370,6 +3370,7 @@ Released 2018-09-13
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl
[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn};
use clippy_utils::{is_diag_trait_item, path_to_local, peel_ref_operators};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, Impl, Item, ItemKind, QPath};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, symbol::kw, Symbol};

#[derive(Clone, Copy)]
enum FormatTrait {
Debug,
Display,
}

impl FormatTrait {
fn name(self) -> Symbol {
match self {
FormatTrait::Debug => sym::Debug,
FormatTrait::Display => sym::Display,
}
}
}

declare_clippy_lint! {
/// ### What it does
/// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
Expand Down Expand Up @@ -61,47 +47,92 @@ declare_clippy_lint! {
"Format trait method called while implementing the same Format trait"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for use of `println`, `print`, `eprintln` or `eprint` in an
/// implementation of a formatting trait.
///
/// ### Why is this bad?
/// Using a print macro is likely unintentional since formatting traits
/// should write to the `Formatter`, not stdout/stderr.
///
/// ### Example
/// ```rust
/// use std::fmt::{Display, Error, Formatter};
///
/// struct S;
/// impl Display for S {
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
/// println!("S");
///
/// Ok(())
/// }
/// }
/// ```
/// Use instead:
/// ```rust
/// use std::fmt::{Display, Error, Formatter};
///
/// struct S;
/// impl Display for S {
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
/// writeln!(f, "S");
///
/// Ok(())
/// }
/// }
/// ```
#[clippy::version = "1.61.0"]
pub PRINT_IN_FORMAT_IMPL,
suspicious,
"use of a print macro in a formatting trait impl"
}

#[derive(Clone, Copy)]
struct FormatTrait {
/// e.g. `sym::Display`
name: Symbol,
/// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
formatter_name: Option<Symbol>,
}

#[derive(Default)]
pub struct RecursiveFormatImpl {
pub struct FormatImpl {
// Whether we are inside Display or Debug trait impl - None for neither
format_trait_impl: Option<FormatTrait>,
}

impl RecursiveFormatImpl {
impl FormatImpl {
pub fn new() -> Self {
Self {
format_trait_impl: None,
}
}
}

impl_lint_pass!(RecursiveFormatImpl => [RECURSIVE_FORMAT_IMPL]);
impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]);

impl<'tcx> LateLintPass<'tcx> for RecursiveFormatImpl {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if let Some(format_trait_impl) = is_format_trait_impl(cx, item) {
self.format_trait_impl = Some(format_trait_impl);
}
impl<'tcx> LateLintPass<'tcx> for FormatImpl {
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
self.format_trait_impl = is_format_trait_impl(cx, impl_item);
}

fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
// Assume no nested Impl of Debug and Display within eachother
if is_format_trait_impl(cx, item).is_some() {
if is_format_trait_impl(cx, impl_item).is_some() {
self.format_trait_impl = None;
}
}

fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
match self.format_trait_impl {
Some(FormatTrait::Display) => {
check_to_string_in_display(cx, expr);
check_self_in_format_args(cx, expr, FormatTrait::Display);
},
Some(FormatTrait::Debug) => {
check_self_in_format_args(cx, expr, FormatTrait::Debug);
},
None => {},
let Some(format_trait_impl) = self.format_trait_impl else { return };

if format_trait_impl.name == sym::Display {
check_to_string_in_display(cx, expr);
}

check_self_in_format_args(cx, expr, format_trait_impl);
check_print_in_format_impl(cx, expr, format_trait_impl);
}
}

Expand Down Expand Up @@ -140,7 +171,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
if let Some(args) = format_args.args();
then {
for arg in args {
if arg.format_trait != impl_trait.name() {
if arg.format_trait != impl_trait.name {
continue;
}
check_format_arg_self(cx, expr, &arg, impl_trait);
Expand All @@ -156,33 +187,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
let reference = peel_ref_operators(cx, arg.value);
let map = cx.tcx.hir();
// Is the reference self?
let symbol_ident = impl_trait.name().to_ident_string();
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
let FormatTrait { name, .. } = impl_trait;
span_lint(
cx,
RECURSIVE_FORMAT_IMPL,
expr.span,
&format!(
"using `self` as `{}` in `impl {}` will cause infinite recursion",
&symbol_ident, &symbol_ident
),
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
);
}
}

fn is_format_trait_impl(cx: &LateContext<'_>, item: &Item<'_>) -> Option<FormatTrait> {
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
if_chain! {
// Are we at an Impl?
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind;
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
then {
let replacement = match name {
sym::print_macro | sym::eprint_macro => "write",
sym::println_macro | sym::eprintln_macro => "writeln",
_ => return,
};

let name = name.as_str().strip_suffix("_macro").unwrap();

span_lint_and_sugg(
cx,
PRINT_IN_FORMAT_IMPL,
macro_call.span,
&format!("use of `{}!` in `{}` impl", name, impl_trait.name),
"replace with",
if let Some(formatter_name) = impl_trait.formatter_name {
format!("{}!({}, ..)", replacement, formatter_name)
} else {
format!("{}!(..)", replacement)
},
Applicability::HasPlaceholders,
);
}
}
}

fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
if_chain! {
if impl_item.ident.name == sym::fmt;
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
if let Some(did) = trait_ref.trait_def_id();
if let Some(name) = cx.tcx.get_diagnostic_name(did);
if matches!(name, sym::Debug | sym::Display);
then {
// Is Impl for Debug or Display?
match name {
sym::Debug => Some(FormatTrait::Debug),
sym::Display => Some(FormatTrait::Display),
_ => None,
}
let body = cx.tcx.hir().body(body_id);
let formatter_name = body.params.get(1)
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);

Some(FormatTrait {
name,
formatter_name,
})
} else {
None
}
Expand Down
3 changes: 2 additions & 1 deletion clippy_lints/src/lib.register_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(format::USELESS_FORMAT),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
Expand Down Expand Up @@ -244,7 +246,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(ranges::MANUAL_RANGE_CONTAINS),
LintId::of(ranges::RANGE_ZIP_WITH_LEN),
LintId::of(ranges::REVERSED_EMPTY_RANGES),
LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(redundant_clone::REDUNDANT_CLONE),
LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/lib.register_correctness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
LintId::of(eq_op::EQ_OP),
LintId::of(erasing_op::ERASING_OP),
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
LintId::of(if_let_mutex::IF_LET_MUTEX),
Expand Down Expand Up @@ -52,7 +53,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF),
LintId::of(ranges::REVERSED_EMPTY_RANGES),
LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(regex::INVALID_REGEX),
LintId::of(self_assignment::SELF_ASSIGNMENT),
LintId::of(serde_api::SERDE_API_MISUSE),
Expand Down
3 changes: 2 additions & 1 deletion clippy_lints/src/lib.register_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ store.register_lints(&[
format::USELESS_FORMAT,
format_args::FORMAT_IN_FORMAT_ARGS,
format_args::TO_STRING_IN_FORMAT_ARGS,
format_impl::PRINT_IN_FORMAT_IMPL,
format_impl::RECURSIVE_FORMAT_IMPL,
formatting::POSSIBLE_MISSING_COMMA,
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
formatting::SUSPICIOUS_ELSE_FORMATTING,
Expand Down Expand Up @@ -417,7 +419,6 @@ store.register_lints(&[
ranges::RANGE_PLUS_ONE,
ranges::RANGE_ZIP_WITH_LEN,
ranges::REVERSED_EMPTY_RANGES,
recursive_format_impl::RECURSIVE_FORMAT_IMPL,
redundant_clone::REDUNDANT_CLONE,
redundant_closure_call::REDUNDANT_CLOSURE_CALL,
redundant_else::REDUNDANT_ELSE,
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_suspicious.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
LintId::of(casts::CAST_ENUM_TRUNCATION),
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
Expand Down
4 changes: 2 additions & 2 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ mod float_literal;
mod floating_point_arithmetic;
mod format;
mod format_args;
mod format_impl;
mod formatting;
mod from_over_into;
mod from_str_radix_10;
Expand Down Expand Up @@ -333,7 +334,6 @@ mod ptr_eq;
mod ptr_offset_with_cast;
mod question_mark;
mod ranges;
mod recursive_format_impl;
mod redundant_clone;
mod redundant_closure_call;
mod redundant_else;
Expand Down Expand Up @@ -705,7 +705,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic));
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
store.register_late_pass(|| Box::new(recursive_format_impl::RecursiveFormatImpl::new()));
store.register_late_pass(|| Box::new(format_impl::FormatImpl::new()));
store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval));
store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));
Expand Down
58 changes: 58 additions & 0 deletions tests/ui/print_in_format_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#![allow(unused, clippy::print_literal, clippy::write_literal)]
#![warn(clippy::print_in_format_impl)]
use std::fmt::{Debug, Display, Error, Formatter};

macro_rules! indirect {
() => {{ println!() }};
}

macro_rules! nested {
($($tt:tt)*) => {
$($tt)*
};
}

struct Foo;
impl Debug for Foo {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
static WORKS_WITH_NESTED_ITEMS: bool = true;

print!("{}", 1);
println!("{}", 2);
eprint!("{}", 3);
eprintln!("{}", 4);
nested! {
println!("nested");
};

write!(f, "{}", 5);
writeln!(f, "{}", 6);
indirect!();

Ok(())
}
}

impl Display for Foo {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
print!("Display");
write!(f, "Display");

Ok(())
}
}

struct UnnamedFormatter;
impl Debug for UnnamedFormatter {
fn fmt(&self, _: &mut Formatter) -> Result<(), Error> {
println!("UnnamedFormatter");
Ok(())
}
}

fn main() {
print!("outside fmt");
println!("outside fmt");
eprint!("outside fmt");
eprintln!("outside fmt");
}
Loading