blob: b5d2add65cf19bdad594722dc74c23ebb78facee [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::SpanRangeExt;
use clippy_utils::sym;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Lit, UnOp};
use rustc_lint::LateContext;
use std::cmp::Ordering;
use std::fmt;
use super::PTR_OFFSET_BY_LITERAL;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Msrv) {
// `pointer::add` and `pointer::wrapping_add` are only stable since 1.26.0. These functions
// became const-stable in 1.61.0, the same version that `pointer::offset` became const-stable.
if !msrv.meets(cx, msrvs::POINTER_ADD_SUB_METHODS) {
return;
}
let ExprKind::MethodCall(method_name, recv, [arg_expr], _) = expr.kind else {
return;
};
let method = match method_name.ident.name {
sym::offset => Method::Offset,
sym::wrapping_offset => Method::WrappingOffset,
_ => return,
};
if !cx.typeck_results().expr_ty_adjusted(recv).is_raw_ptr() {
return;
}
// Check if the argument to the method call is a (negated) literal.
let Some((literal, literal_text)) = expr_as_literal(cx, arg_expr) else {
return;
};
match method.suggestion(literal) {
None => {
let msg = format!("use of `{method}` with zero");
span_lint_and_then(cx, PTR_OFFSET_BY_LITERAL, expr.span, msg, |diag| {
diag.span_suggestion(
expr.span.with_lo(recv.span.hi()),
format!("remove the call to `{method}`"),
String::new(),
Applicability::MachineApplicable,
);
});
},
Some(method_suggestion) => {
let msg = format!("use of `{method}` with a literal");
span_lint_and_then(cx, PTR_OFFSET_BY_LITERAL, expr.span, msg, |diag| {
diag.multipart_suggestion(
format!("use `{method_suggestion}` instead"),
vec![
(method_name.ident.span, method_suggestion.to_string()),
(arg_expr.span, literal_text),
],
Applicability::MachineApplicable,
);
});
},
}
}
fn get_literal_bits<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<u128> {
match expr.kind {
ExprKind::Lit(Lit {
node: LitKind::Int(packed_u128, _),
..
}) => Some(packed_u128.get()),
_ => None,
}
}
// If the given expression is a (negated) literal, return its value.
fn expr_as_literal<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<(i128, String)> {
if let Some(literal_bits) = get_literal_bits(expr) {
// The value must fit in a isize, so we can't have overflow here.
return Some((literal_bits.cast_signed(), format_isize_literal(cx, expr)?));
}
if let ExprKind::Unary(UnOp::Neg, inner) = expr.kind
&& let Some(literal_bits) = get_literal_bits(inner)
{
return Some((-(literal_bits.cast_signed()), format_isize_literal(cx, inner)?));
}
None
}
fn format_isize_literal<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<String> {
let text = expr.span.get_source_text(cx)?;
let text = peel_parens_str(&text);
Some(text.trim_end_matches("isize").trim_end_matches('_').to_string())
}
fn peel_parens_str(snippet: &str) -> &str {
let mut s = snippet.trim();
while let Some(next) = s.strip_prefix("(").and_then(|suf| suf.strip_suffix(")")) {
s = next.trim();
}
s
}
#[derive(Copy, Clone)]
enum Method {
Offset,
WrappingOffset,
}
impl Method {
fn suggestion(self, literal: i128) -> Option<&'static str> {
match Ord::cmp(&literal, &0) {
Ordering::Greater => match self {
Method::Offset => Some("add"),
Method::WrappingOffset => Some("wrapping_add"),
},
// `ptr.offset(0)` is equivalent to `ptr`, so no adjustment is needed
Ordering::Equal => None,
Ordering::Less => match self {
Method::Offset => Some("sub"),
Method::WrappingOffset => Some("wrapping_sub"),
},
}
}
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Offset => write!(f, "offset"),
Self::WrappingOffset => write!(f, "wrapping_offset"),
}
}
}