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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
use rustc::lint::*;
use rustc::ty::TypeVariants::{TyRawPtr, TyRef};
use rustc::ty;
use rustc::hir::*;
use utils::{match_def_path, paths, span_lint, span_lint_and_then, snippet, last_path_segment};
use utils::sugg;

/// **What it does:** Checks for transmutes that can't ever be correct on any
/// architecture.
///
/// **Why is this bad?** It's basically guaranteed to be undefined behaviour.
///
/// **Known problems:** When accessing C, users might want to store pointer
/// sized objects in `extradata` arguments to save an allocation.
///
/// **Example:**
/// ```rust
/// let ptr: *const T = core::intrinsics::transmute('x')`
/// ```
declare_lint! {
    pub WRONG_TRANSMUTE,
    Warn,
    "transmutes that are confusing at best, undefined behaviour at worst and always useless"
}

/// **What it does:** Checks for transmutes to the original type of the object
/// and transmutes that could be a cast.
///
/// **Why is this bad?** Readability. The code tricks people into thinking that
/// something complex is going on.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// core::intrinsics::transmute(t) // where the result type is the same as `t`'s
/// ```
declare_lint! {
    pub USELESS_TRANSMUTE,
    Warn,
    "transmutes that have the same to and from types or could be a cast/coercion"
}

/// **What it does:** Checks for transmutes between a type `T` and `*T`.
///
/// **Why is this bad?** It's easy to mistakenly transmute between a type and a
/// pointer to that type.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// core::intrinsics::transmute(t)` // where the result type is the same as `*t` or `&t`'s
/// ```
declare_lint! {
    pub CROSSPOINTER_TRANSMUTE,
    Warn,
    "transmutes that have to or from types that are a pointer to the other"
}

/// **What it does:** Checks for transmutes from a pointer to a reference.
///
/// **Why is this bad?** This can always be rewritten with `&` and `*`.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let _: &T = std::mem::transmute(p); // where p: *const T
/// // can be written:
/// let _: &T = &*p;
/// ```
declare_lint! {
    pub TRANSMUTE_PTR_TO_REF,
    Warn,
    "transmutes from a pointer to a reference type"
}

pub struct Transmute;

impl LintPass for Transmute {
    fn get_lints(&self) -> LintArray {
        lint_array![CROSSPOINTER_TRANSMUTE, TRANSMUTE_PTR_TO_REF, USELESS_TRANSMUTE, WRONG_TRANSMUTE]
    }
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
        if let ExprCall(ref path_expr, ref args) = e.node {
            if let ExprPath(ref qpath) = path_expr.node {
                let def_id = cx.tcx.tables().qpath_def(qpath, path_expr.id).def_id();

                if match_def_path(cx, def_id, &paths::TRANSMUTE) {
                    let from_ty = cx.tcx.tables().expr_ty(&args[0]);
                    let to_ty = cx.tcx.tables().expr_ty(e);

                    match (&from_ty.sty, &to_ty.sty) {
                        _ if from_ty == to_ty => {
                            span_lint(cx,
                                      USELESS_TRANSMUTE,
                                      e.span,
                                      &format!("transmute from a type (`{}`) to itself", from_ty))
                        },
                        (&TyRef(_, rty), &TyRawPtr(ptr_ty)) => {
                            span_lint_and_then(cx,
                                               USELESS_TRANSMUTE,
                                               e.span,
                                               "transmute from a reference to a pointer",
                                               |db| {
                                if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
                                    let sugg = if ptr_ty == rty {
                                        arg.as_ty(to_ty)
                                    } else {
                                        arg.as_ty(cx.tcx.mk_ptr(rty)).as_ty(to_ty)
                                    };

                                    db.span_suggestion(e.span, "try", sugg.to_string());
                                }
                            })
                        },
                        (&ty::TyInt(_), &TyRawPtr(_)) |
                        (&ty::TyUint(_), &TyRawPtr(_)) => {
                            span_lint_and_then(cx,
                                               USELESS_TRANSMUTE,
                                               e.span,
                                               "transmute from an integer to a pointer",
                                               |db| {
                                if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
                                    db.span_suggestion(e.span, "try", arg.as_ty(&to_ty.to_string()).to_string());
                                }
                            })
                        },
                        (&ty::TyFloat(_), &TyRef(..)) |
                        (&ty::TyFloat(_), &TyRawPtr(_)) |
                        (&ty::TyChar, &TyRef(..)) |
                        (&ty::TyChar, &TyRawPtr(_)) => {
                            span_lint(cx,
                                      WRONG_TRANSMUTE,
                                      e.span,
                                      &format!("transmute from a `{}` to a pointer", from_ty))
                        },
                        (&TyRawPtr(from_ptr), _) if from_ptr.ty == to_ty => {
                            span_lint(cx,
                                      CROSSPOINTER_TRANSMUTE,
                                      e.span,
                                      &format!("transmute from a type (`{}`) to the type that it points to (`{}`)",
                                               from_ty,
                                               to_ty))
                        },
                        (_, &TyRawPtr(to_ptr)) if to_ptr.ty == from_ty => {
                            span_lint(cx,
                                      CROSSPOINTER_TRANSMUTE,
                                      e.span,
                                      &format!("transmute from a type (`{}`) to a pointer to that type (`{}`)",
                                               from_ty,
                                               to_ty))
                        },
                        (&TyRawPtr(from_pty), &TyRef(_, to_rty)) => {
                            span_lint_and_then(cx,
                                               TRANSMUTE_PTR_TO_REF,
                                               e.span,
                                               &format!("transmute from a pointer type (`{}`) to a reference type \
                                                         (`{}`)",
                                                        from_ty,
                                                        to_ty),
                                               |db| {
                                let arg = sugg::Sugg::hir(cx, &args[0], "..");
                                let (deref, cast) = if to_rty.mutbl == Mutability::MutMutable {
                                    ("&mut *", "*mut")
                                } else {
                                    ("&*", "*const")
                                };

                                let arg = if from_pty.ty == to_rty.ty {
                                    arg
                                } else {
                                    arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_rty.ty)))
                                };

                                db.span_suggestion(e.span, "try", sugg::make_unop(deref, arg).to_string());
                            })
                        },
                        _ => return,
                    };
                }
            }
        }
    }
}

/// Get the snippet of `Bar` in `…::transmute<Foo, &Bar>`. If that snippet is not available , use
/// the type's `ToString` implementation. In weird cases it could lead to types with invalid `'_`
/// lifetime, but it should be rare.
fn get_type_snippet(cx: &LateContext, path: &QPath, to_rty: ty::Ty) -> String {
    let seg = last_path_segment(path);
    if_let_chain!{[
        let PathParameters::AngleBracketedParameters(ref ang) = seg.parameters,
        let Some(to_ty) = ang.types.get(1),
        let TyRptr(_, ref to_ty) = to_ty.node,
    ], {
        return snippet(cx, to_ty.ty.span, &to_rty.to_string()).to_string();
    }}

    to_rty.to_string()
}