1 use crate::LateContext;
2 use crate::LateLintPass;
3 use crate::LintContext;
4 use rustc_hir::{Expr, ExprKind, PathSegment};
5 use rustc_middle::ty;
6 use rustc_span::{symbol::sym, ExpnKind, Span};
7 
8 declare_lint! {
9     /// The `temporary_cstring_as_ptr` lint detects getting the inner pointer of
10     /// a temporary `CString`.
11     ///
12     /// ### Example
13     ///
14     /// ```rust
15     /// # #![allow(unused)]
16     /// # use std::ffi::CString;
17     /// let c_str = CString::new("foo").unwrap().as_ptr();
18     /// ```
19     ///
20     /// {{produces}}
21     ///
22     /// ### Explanation
23     ///
24     /// The inner pointer of a `CString` lives only as long as the `CString` it
25     /// points to. Getting the inner pointer of a *temporary* `CString` allows the `CString`
26     /// to be dropped at the end of the statement, as it is not being referenced as far as the typesystem
27     /// is concerned. This means outside of the statement the pointer will point to freed memory, which
28     /// causes undefined behavior if the pointer is later dereferenced.
29     pub TEMPORARY_CSTRING_AS_PTR,
30     Warn,
31     "detects getting the inner pointer of a temporary `CString`"
32 }
33 
34 declare_lint_pass!(TemporaryCStringAsPtr => [TEMPORARY_CSTRING_AS_PTR]);
35 
in_macro(span: Span) -> bool36 fn in_macro(span: Span) -> bool {
37     if span.from_expansion() {
38         !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
39     } else {
40         false
41     }
42 }
43 
first_method_call<'tcx>( expr: &'tcx Expr<'tcx>, ) -> Option<(&'tcx PathSegment<'tcx>, &'tcx [Expr<'tcx>])>44 fn first_method_call<'tcx>(
45     expr: &'tcx Expr<'tcx>,
46 ) -> Option<(&'tcx PathSegment<'tcx>, &'tcx [Expr<'tcx>])> {
47     if let ExprKind::MethodCall(path, _, args, _) = &expr.kind {
48         if args.iter().any(|e| e.span.from_expansion()) { None } else { Some((path, *args)) }
49     } else {
50         None
51     }
52 }
53 
54 impl<'tcx> LateLintPass<'tcx> for TemporaryCStringAsPtr {
check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>)55     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
56         if in_macro(expr.span) {
57             return;
58         }
59 
60         match first_method_call(expr) {
61             Some((path, args)) if path.ident.name == sym::as_ptr => {
62                 let unwrap_arg = &args[0];
63                 let as_ptr_span = path.ident.span;
64                 match first_method_call(unwrap_arg) {
65                     Some((path, args))
66                         if path.ident.name == sym::unwrap || path.ident.name == sym::expect =>
67                     {
68                         let source_arg = &args[0];
69                         lint_cstring_as_ptr(cx, as_ptr_span, source_arg, unwrap_arg);
70                     }
71                     _ => return,
72                 }
73             }
74             _ => return,
75         }
76     }
77 }
78 
lint_cstring_as_ptr( cx: &LateContext<'_>, as_ptr_span: Span, source: &rustc_hir::Expr<'_>, unwrap: &rustc_hir::Expr<'_>, )79 fn lint_cstring_as_ptr(
80     cx: &LateContext<'_>,
81     as_ptr_span: Span,
82     source: &rustc_hir::Expr<'_>,
83     unwrap: &rustc_hir::Expr<'_>,
84 ) {
85     let source_type = cx.typeck_results().expr_ty(source);
86     if let ty::Adt(def, substs) = source_type.kind() {
87         if cx.tcx.is_diagnostic_item(sym::Result, def.did) {
88             if let ty::Adt(adt, _) = substs.type_at(0).kind() {
89                 if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did) {
90                     cx.struct_span_lint(TEMPORARY_CSTRING_AS_PTR, as_ptr_span, |diag| {
91                         let mut diag = diag
92                             .build("getting the inner pointer of a temporary `CString`");
93                         diag.span_label(as_ptr_span, "this pointer will be invalid");
94                         diag.span_label(
95                             unwrap.span,
96                             "this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime",
97                         );
98                         diag.note("pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned");
99                         diag.help("for more information, see https://doc.rust-lang.org/reference/destructors.html");
100                         diag.emit();
101                     });
102                 }
103             }
104         }
105     }
106 }
107