2020-08-18 10:25:21 -05:00
use crate ::LateContext ;
use crate ::LateLintPass ;
use crate ::LintContext ;
2020-08-18 16:02:23 -05:00
use rustc_hir ::{ Expr , ExprKind , PathSegment } ;
2020-08-18 10:25:21 -05:00
use rustc_middle ::ty ;
2020-09-21 15:32:28 -05:00
use rustc_span ::{ symbol ::sym , ExpnKind , Span } ;
2020-08-18 10:25:21 -05:00
declare_lint! {
2020-09-22 10:20:06 -05:00
/// The `temporary_cstring_as_ptr` lint detects getting the inner pointer of
/// a temporary `CString`.
///
/// ### Example
///
/// ```rust
/// # #![allow(unused)]
2020-09-22 11:38:50 -05:00
/// # use std::ffi::CString;
2020-09-22 10:20:06 -05:00
/// let c_str = CString::new("foo").unwrap().as_ptr();
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// The inner pointer of a `CString` lives only as long as the `CString` it
/// points to. Getting the inner pointer of a *temporary* `CString` allows the `CString`
/// to be dropped at the end of the statement, as it is not being referenced as far as the typesystem
/// is concerned. This means outside of the statement the pointer will point to freed memory, which
/// causes undefined behavior if the pointer is later dereferenced.
2020-08-18 10:25:21 -05:00
pub TEMPORARY_CSTRING_AS_PTR ,
2020-08-23 13:21:58 -05:00
Warn ,
2020-08-18 10:25:21 -05:00
" detects getting the inner pointer of a temporary `CString` "
}
declare_lint_pass! ( TemporaryCStringAsPtr = > [ TEMPORARY_CSTRING_AS_PTR ] ) ;
fn in_macro ( span : Span ) -> bool {
if span . from_expansion ( ) {
! matches! ( span . ctxt ( ) . outer_expn_data ( ) . kind , ExpnKind ::Desugaring ( .. ) )
} else {
false
}
}
2020-08-18 16:02:23 -05:00
fn first_method_call < ' tcx > (
expr : & ' tcx Expr < ' tcx > ,
) -> Option < ( & ' tcx PathSegment < ' tcx > , & ' tcx [ Expr < ' tcx > ] ) > {
if let ExprKind ::MethodCall ( path , _ , args , _ ) = & expr . kind {
if args . iter ( ) . any ( | e | e . span . from_expansion ( ) ) { None } else { Some ( ( path , * args ) ) }
} else {
None
}
}
2020-08-18 10:25:21 -05:00
impl < ' tcx > LateLintPass < ' tcx > for TemporaryCStringAsPtr {
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < '_ > ) {
if in_macro ( expr . span ) {
return ;
}
2020-08-18 16:02:23 -05:00
match first_method_call ( expr ) {
Some ( ( path , args ) ) if path . ident . name = = sym ::as_ptr = > {
let unwrap_arg = & args [ 0 ] ;
2020-08-18 18:37:50 -05:00
let as_ptr_span = path . ident . span ;
2020-08-18 16:02:23 -05:00
match first_method_call ( unwrap_arg ) {
Some ( ( path , args ) )
if path . ident . name = = sym ::unwrap | | path . ident . name = = sym ::expect = >
{
let source_arg = & args [ 0 ] ;
2020-08-18 18:37:50 -05:00
lint_cstring_as_ptr ( cx , as_ptr_span , source_arg , unwrap_arg ) ;
2020-08-18 16:02:23 -05:00
}
_ = > return ,
}
}
_ = > return ,
2020-08-18 10:25:21 -05:00
}
}
}
fn lint_cstring_as_ptr (
cx : & LateContext < '_ > ,
2020-08-18 18:37:50 -05:00
as_ptr_span : Span ,
2020-08-18 10:25:21 -05:00
source : & rustc_hir ::Expr < '_ > ,
unwrap : & rustc_hir ::Expr < '_ > ,
) {
let source_type = cx . typeck_results ( ) . expr_ty ( source ) ;
2020-09-21 15:32:28 -05:00
if let ty ::Adt ( def , substs ) = source_type . kind ( ) {
2020-08-18 16:02:23 -05:00
if cx . tcx . is_diagnostic_item ( sym ::result_type , def . did ) {
2020-09-21 15:32:28 -05:00
if let ty ::Adt ( adt , _ ) = substs . type_at ( 0 ) . kind ( ) {
if cx . tcx . is_diagnostic_item ( sym ::cstring_type , adt . did ) {
2020-08-18 18:37:50 -05:00
cx . struct_span_lint ( TEMPORARY_CSTRING_AS_PTR , as_ptr_span , | diag | {
2020-08-18 10:25:21 -05:00
let mut diag = diag
2020-08-18 16:02:23 -05:00
. build ( " getting the inner pointer of a temporary `CString` " ) ;
2020-08-18 18:37:50 -05:00
diag . span_label ( as_ptr_span , " this pointer will be invalid " ) ;
diag . span_label (
2020-08-18 10:25:21 -05:00
unwrap . span ,
2020-09-21 15:32:28 -05:00
" this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime " ,
2020-08-18 10:25:21 -05:00
) ;
2020-09-22 10:20:06 -05:00
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 " ) ;
2020-09-21 15:32:28 -05:00
diag . help ( " for more information, see https://doc.rust-lang.org/reference/destructors.html " ) ;
2020-08-18 10:25:21 -05:00
diag . emit ( ) ;
} ) ;
}
}
}
}
}