summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/casts/cast_slice_different_sizes.rs
blob: d31d10d22b92b8997a77631d71d86ddc21dcdd38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source};
use if_chain::if_chain;
use rustc_ast::Mutability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
use rustc_semver::RustcVersion;

use super::CAST_SLICE_DIFFERENT_SIZES;

pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Option<RustcVersion>) {
    // suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
    if !meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS) {
        return;
    }

    // if this cast is the child of another cast expression then don't emit something for it, the full
    // chain will be analyzed
    if is_child_of_cast(cx, expr) {
        return;
    }

    if let Some(CastChainInfo {
        left_cast,
        start_ty,
        end_ty,
    }) = expr_cast_chain_tys(cx, expr)
    {
        if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) {
            let from_size = from_layout.size.bytes();
            let to_size = to_layout.size.bytes();
            if from_size != to_size && from_size != 0 && to_size != 0 {
                span_lint_and_then(
                    cx,
                    CAST_SLICE_DIFFERENT_SIZES,
                    expr.span,
                    &format!(
                        "casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count",
                        start_ty.ty, end_ty.ty,
                    ),
                    |diag| {
                        let ptr_snippet = source::snippet(cx, left_cast.span, "..");

                        let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl {
                            Mutability::Mut => ("_mut", "mut"),
                            Mutability::Not => ("", "const"),
                        };
                        let sugg = format!(
                            "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)",
                            // get just the ty from the TypeAndMut so that the printed type isn't something like `mut
                            // T`, extract just the `T`
                            end_ty.ty
                        );

                        diag.span_suggestion(
                            expr.span,
                            &format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
                            sugg,
                            rustc_errors::Applicability::HasPlaceholders,
                        );
                    },
                );
            }
        }
    }
}

fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
    let map = cx.tcx.hir();
    if_chain! {
        if let Some(parent_id) = map.find_parent_node(expr.hir_id);
        if let Some(parent) = map.find(parent_id);
        then {
            let expr = match parent {
                Node::Block(block) => {
                    if let Some(parent_expr) = block.expr {
                        parent_expr
                    } else {
                        return false;
                    }
                },
                Node::Expr(expr) => expr,
                _ => return false,
            };

            matches!(expr.kind, ExprKind::Cast(..))
        } else {
            false
        }
    }
}

/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
/// the type is one of those slices
fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
    match ty.kind() {
        ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
            ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
            _ => None,
        },
        _ => None,
    }
}

struct CastChainInfo<'tcx> {
    /// The left most part of the cast chain, or in other words, the first cast in the chain
    /// Used for diagnostics
    left_cast: &'tcx Expr<'tcx>,
    /// The starting type of the cast chain
    start_ty: TypeAndMut<'tcx>,
    /// The final type of the cast chain
    end_ty: TypeAndMut<'tcx>,
}

/// Returns a `CastChainInfo` with the left-most cast in the chain and the original ptr T and final
/// ptr U if the expression is composed of casts.
/// Returns None if the expr is not a Cast
fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<CastChainInfo<'tcx>> {
    if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
        let cast_to = cx.typeck_results().expr_ty(expr);
        let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;

        // If the expression that makes up the source of this cast is itself a cast, recursively
        // call `expr_cast_chain_tys` and update the end type with the final target type.
        // Otherwise, this cast is not immediately nested, just construct the info for this cast
        if let Some(prev_info) = expr_cast_chain_tys(cx, cast_expr) {
            Some(CastChainInfo {
                end_ty: to_slice_ty,
                ..prev_info
            })
        } else {
            let cast_from = cx.typeck_results().expr_ty(cast_expr);
            let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
            Some(CastChainInfo {
                left_cast: cast_expr,
                start_ty: from_slice_ty,
                end_ty: to_slice_ty,
            })
        }
    } else {
        None
    }
}