Skip to content

Uplift temporary-cstring-as-ptr lint from clippy into rustc #75671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Oct 28, 2020
Prev Previous commit
Next Next commit
Address review comments
  • Loading branch information
nathanwhit committed Oct 26, 2020
commit 8b65df06ce0cf78fd2298c9cd63e1f5beb40525f
84 changes: 35 additions & 49 deletions compiler/rustc_lint/src/methods.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::LateContext;
use crate::LateLintPass;
use crate::LintContext;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{Expr, ExprKind, PathSegment};
use rustc_middle::ty;
use rustc_span::{
symbol::{sym, Symbol, SymbolStr},
symbol::{sym, Symbol},
ExpnKind, Span,
};

Expand All @@ -16,34 +16,6 @@ declare_lint! {

declare_lint_pass!(TemporaryCStringAsPtr => [TEMPORARY_CSTRING_AS_PTR]);

/// Returns the method names and argument list of nested method call expressions that make up
/// `expr`. method/span lists are sorted with the most recent call first.
pub fn method_calls<'tcx>(
expr: &'tcx Expr<'tcx>,
max_depth: usize,
) -> (Vec<Symbol>, Vec<&'tcx [Expr<'tcx>]>, Vec<Span>) {
let mut method_names = Vec::with_capacity(max_depth);
let mut arg_lists = Vec::with_capacity(max_depth);
let mut spans = Vec::with_capacity(max_depth);

let mut current = expr;
for _ in 0..max_depth {
if let ExprKind::MethodCall(path, span, args, _) = &current.kind {
if args.iter().any(|e| e.span.from_expansion()) {
break;
}
method_names.push(path.ident.name);
arg_lists.push(&**args);
spans.push(*span);
current = &args[0];
} else {
break;
}
}

(method_names, arg_lists, spans)
}

fn in_macro(span: Span) -> bool {
if span.from_expansion() {
!matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
Expand All @@ -52,47 +24,61 @@ fn in_macro(span: Span) -> bool {
}
}

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
}
}

impl<'tcx> LateLintPass<'tcx> for TemporaryCStringAsPtr {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if in_macro(expr.span) {
return;
}

let (method_names, arg_lists, _) = method_calls(expr, 2);
let method_names: Vec<SymbolStr> = method_names.iter().map(|s| s.as_str()).collect();
let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect();

if let ["as_ptr", "unwrap" | "expect"] = method_names.as_slice() {
lint_cstring_as_ptr(cx, expr, &arg_lists[1][0], &arg_lists[0][0]);
match first_method_call(expr) {
Some((path, args)) if path.ident.name == sym::as_ptr => {
let unwrap_arg = &args[0];
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];
lint_cstring_as_ptr(cx, source_arg, unwrap_arg);
}
_ => return,
}
}
_ => return,
}
}
}

const CSTRING_PATH: [Symbol; 4] = [sym::std, sym::ffi, sym::c_str, sym::CString];

fn lint_cstring_as_ptr(
cx: &LateContext<'_>,
expr: &rustc_hir::Expr<'_>,
source: &rustc_hir::Expr<'_>,
unwrap: &rustc_hir::Expr<'_>,
) {
let source_type = cx.typeck_results().expr_ty(source);
if let ty::Adt(def, substs) = source_type.kind {
if cx.tcx.is_diagnostic_item(Symbol::intern("result_type"), def.did) {
if cx.tcx.is_diagnostic_item(sym::result_type, def.did) {
if let ty::Adt(adt, _) = substs.type_at(0).kind {
let path = [
sym::std,
Symbol::intern("ffi"),
Symbol::intern("c_str"),
Symbol::intern("CString"),
];
if cx.match_def_path(adt.did, &path) {
cx.struct_span_lint(TEMPORARY_CSTRING_AS_PTR, expr.span, |diag| {
if cx.match_def_path(adt.did, &CSTRING_PATH) {
cx.struct_span_lint(TEMPORARY_CSTRING_AS_PTR, source.span, |diag| {
let mut diag = diag
.build("you are getting the inner pointer of a temporary `CString`");
diag.note("that pointer will be invalid outside this expression");
.build("getting the inner pointer of a temporary `CString`");
diag.span_label(source.span, "this pointer will be invalid");
diag.span_help(
unwrap.span,
"assign the `CString` to a variable to extend its lifetime",
"this `CString` is deallocated at the end of the expression, bind it to a variable to extend its lifetime",
);
diag.note("pointers do not have a lifetime; when calling `as_ptr` the `CString` is deallocated because nothing is referencing it as far as the type system is concerned");
diag.emit();
});
}
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ symbols! {
ArgumentV1,
Arguments,
C,
CString,
Center,
Clone,
Copy,
Expand Down Expand Up @@ -261,6 +262,7 @@ symbols! {
arm_target_feature,
array,
arrays,
as_ptr,
as_str,
asm,
assert,
Expand Down Expand Up @@ -310,6 +312,7 @@ symbols! {
breakpoint,
bridge,
bswap,
c_str,
c_variadic,
call,
call_mut,
Expand Down Expand Up @@ -477,6 +480,7 @@ symbols! {
existential_type,
exp2f32,
exp2f64,
expect,
expected,
expf32,
expf64,
Expand All @@ -500,6 +504,7 @@ symbols! {
fadd_fast,
fdiv_fast,
feature,
ffi,
ffi_const,
ffi_pure,
ffi_returns_twice,
Expand Down Expand Up @@ -1167,6 +1172,7 @@ symbols! {
unused_qualifications,
unwind,
unwind_attributes,
unwrap,
unwrap_or,
use_extern_macros,
use_nested_groups,
Expand Down
3 changes: 1 addition & 2 deletions src/test/ui/lint/lint-temporary-cstring-as-ptr.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// check-fail
// ignore-tidy-linelength

use std::ffi::CString;

fn main() {
let s = CString::new("some text").unwrap().as_ptr(); //~ ERROR you are getting the inner pointer of a temporary `CString`
let s = CString::new("some text").unwrap().as_ptr(); //~ ERROR getting the inner pointer of a temporary `CString`
}
12 changes: 6 additions & 6 deletions src/test/ui/lint/lint-temporary-cstring-as-ptr.stderr
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
error: you are getting the inner pointer of a temporary `CString`
--> $DIR/lint-temporary-cstring-as-ptr.rs:7:13
error: getting the inner pointer of a temporary `CString`
--> $DIR/lint-temporary-cstring-as-ptr.rs:6:13
|
LL | let s = CString::new("some text").unwrap().as_ptr();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^^ this pointer will be invalid
|
= note: `#[deny(temporary_cstring_as_ptr)]` on by default
= note: that pointer will be invalid outside this expression
help: assign the `CString` to a variable to extend its lifetime
--> $DIR/lint-temporary-cstring-as-ptr.rs:7:13
help: this `CString` is deallocated at the end of the expression, bind it to a variable to extend its lifetime
--> $DIR/lint-temporary-cstring-as-ptr.rs:6:13
|
LL | let s = CString::new("some text").unwrap().as_ptr();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: pointers do not have a lifetime; when calling `as_ptr` the `CString` is deallocated because nothing is referencing it as far as the type system is concerned

error: aborting due to previous error