Skip to content

Commit 8c84a12

Browse files
committed
make it operate on the MIR
1 parent 0e65248 commit 8c84a12

File tree

3 files changed

+196
-98
lines changed

3 files changed

+196
-98
lines changed

clippy_lints/src/ptr_to_temporary.rs

Lines changed: 163 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
use clippy_utils::consts::is_promotable;
2-
use clippy_utils::diagnostics::span_lint_and_note;
3-
use clippy_utils::{is_from_proc_macro, is_temporary, path_res};
4-
use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, OwnerNode};
2+
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_hir_and_then};
3+
use clippy_utils::mir::local_assignments;
4+
use clippy_utils::ty::has_drop;
5+
use clippy_utils::{is_from_proc_macro, is_temporary, last_path_segment, path_res};
6+
use itertools::Itertools;
7+
use rustc_hir::def_id::LocalDefId;
8+
use rustc_hir::intravisit::FnKind;
9+
use rustc_hir::{Body, BorrowKind, Expr, ExprKind, FnDecl, ItemKind, OwnerNode};
510
use rustc_lint::{LateContext, LateLintPass, LintContext};
611
use rustc_middle::lint::in_external_macro;
7-
use rustc_middle::ty::TypeVisitableExt;
12+
use rustc_middle::mir::visit::Visitor;
13+
use rustc_middle::mir::{
14+
self, AggregateKind, BasicBlock, BasicBlockData, CallSource, Operand, Place, Rvalue, StatementKind, TerminatorKind,
15+
};
16+
use rustc_middle::ty::{self, TypeVisitableExt};
817
use rustc_session::{declare_lint_pass, declare_tool_lint};
9-
use rustc_span::sym;
18+
use rustc_span::{sym, Span};
1019

1120
declare_clippy_lint! {
1221
/// ### What it does
@@ -45,11 +54,48 @@ impl<'tcx> LateLintPass<'tcx> for PtrToTemporary {
4554
return;
4655
}
4756

48-
_ = check_for_returning_raw_ptr(cx, expr) || check_for_dangling_as_ptr(cx, expr);
57+
_ = check_for_returning_raw_ptr(cx, expr)
58+
}
59+
60+
fn check_fn(
61+
&mut self,
62+
cx: &LateContext<'tcx>,
63+
kind: FnKind<'_>,
64+
decl: &FnDecl<'_>,
65+
body: &Body<'_>,
66+
span: Span,
67+
def_id: LocalDefId,
68+
) {
69+
let mir = cx.tcx.optimized_mir(def_id);
70+
let mut v = DanglingPtrVisitor {
71+
cx,
72+
body: mir,
73+
results: vec![],
74+
};
75+
v.visit_body(mir);
76+
77+
for dangling_ptr_span in v.results {
78+
// TODO: We need to lint on the call in question instead, so lint attributes work fine. I'm not sure
79+
// how to though
80+
span_lint_hir_and_then(
81+
cx,
82+
PTR_TO_TEMPORARY,
83+
cx.tcx.local_def_id_to_hir_id(def_id),
84+
dangling_ptr_span,
85+
"calling `TODO` on a temporary value",
86+
|diag| {
87+
diag.note(
88+
"usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at \
89+
the end of the statement, yet the pointer will continue pointing to it, resulting in a \
90+
dangling pointer",
91+
);
92+
},
93+
);
94+
}
4995
}
5096
}
5197

52-
/// Check for returning raw pointers to temporaries that are not promoted to a constant.
98+
/// Check for returning raw pointers to temporaries that are not promoted to a constant
5399
fn check_for_returning_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
54100
// Get the final return statement if this is a return statement, or don't lint
55101
let expr = if let ExprKind::Ret(Some(expr)) = expr.kind {
@@ -87,61 +133,120 @@ fn check_for_returning_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'t
87133
false
88134
}
89135

90-
/// Check for calls to `as_ptr` or `as_mut_ptr` that will always result in a dangling pointer, under
91-
/// the assumption of course that `as_ptr` will return a pointer to data owned by `self`, rather
92-
/// than returning a raw pointer to new memory.
93-
///
94-
/// This only lints `std` types as anything else could potentially be wrong if the above assumption
95-
/// doesn't hold (which it should for all `std` types).
96-
///
97-
/// We could perhaps extend this to some external crates as well, if we want.
98-
fn check_for_dangling_as_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
99-
if let ExprKind::MethodCall(seg, recv, [], _) = expr.kind
100-
&& (seg.ident.name == sym::as_ptr || seg.ident.name == sym!(as_mut_ptr))
101-
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
102-
&& cx.tcx.fn_sig(def_id).skip_binder().output().skip_binder().is_unsafe_ptr()
103-
&& matches!(cx.tcx.crate_name(def_id.krate), sym::core | sym::alloc | sym::std)
104-
&& is_temporary(cx, recv)
105-
&& !is_from_proc_macro(cx, expr)
106-
// The typeck table only contains `ReErased`.
107-
&& let Some(def_id) = match recv.kind {
108-
ExprKind::Call(path, _) => {
109-
path_res(cx, path).opt_def_id()
110-
},
111-
ExprKind::MethodCall(..) => {
112-
cx.typeck_results().type_dependent_def_id(recv.hir_id)
113-
},
114-
// Anything else will either always have a `'static` lifetime or won't be dropped at
115-
// the end of the statement (i.e., will have a longer lifetime), so we can skip them
116-
_ => None,
136+
struct DanglingPtrVisitor<'a, 'tcx> {
137+
cx: &'a LateContext<'tcx>,
138+
body: &'a mir::Body<'tcx>,
139+
results: Vec<Span>,
140+
}
141+
142+
impl<'tcx> Visitor<'tcx> for DanglingPtrVisitor<'_, 'tcx> {
143+
fn visit_basic_block_data(&mut self, block: BasicBlock, data: &BasicBlockData<'tcx>) {
144+
// TODO: We really need to clean this up. I hope future me has any idea what it does!
145+
if let Some(term) = &data.terminator
146+
&& let TerminatorKind::Call {
147+
// TODO: Check if this is `as_ptr`, or any of our specific functions we want to
148+
// lint. Should be simple to implement.
149+
func,
150+
args,
151+
destination,
152+
target: Some(target),
153+
call_source: CallSource::Normal,
154+
..
155+
} = &term.kind
156+
&& destination.ty(&self.body.local_decls, self.cx.tcx).ty.is_unsafe_ptr()
157+
&& let [recv] = args.as_slice()
158+
&& let Some(recv) = recv.place()
159+
&& let [assign] = &*local_assignments(self.body, recv.local)
160+
&& let assign_stmt = &self.body.basic_blocks[assign.block].statements[assign.statement_index]
161+
&& let StatementKind::Assign(box (_, rvalue)) = &assign_stmt.kind
162+
&& let Rvalue::Ref(_, _, actual_recv) = rvalue
163+
&& let [const_assign] = &*local_assignments(self.body, actual_recv.local)
164+
&& if const_assign.statement_index == self.body.basic_blocks[const_assign.block].statements.len() {
165+
if let TerminatorKind::Call {
166+
func,
167+
call_source: CallSource::Normal,
168+
..
169+
} = &self.body.basic_blocks[const_assign.block].terminator().kind
170+
&& let Some((def_id, _)) = func.const_fn_def()
171+
&& let ty = self.cx.tcx.fn_sig(def_id).subst_identity().skip_binder().output()
172+
&& (ty.has_late_bound_vars() || !ty.is_ref())
173+
&& !matches!(ty.kind(), ty::Ref(region, _, _) if region.is_static() )
174+
{
175+
true
176+
} else {
177+
false
178+
}
179+
} else {
180+
let StatementKind::Assign(box (_, rvalue)) =
181+
&self.body.basic_blocks[const_assign.block].statements[const_assign.statement_index].kind
182+
else {
183+
return;
184+
};
185+
186+
let ops = match rvalue {
187+
Rvalue::Use(op) => vec![op],
188+
Rvalue::Repeat(op, _) => vec![op],
189+
Rvalue::Cast(_, op, _) => vec![op],
190+
Rvalue::Aggregate(box AggregateKind::Array(_) | box AggregateKind::Tuple, ops) => {
191+
ops.into_iter().collect_vec()
192+
},
193+
_ => return,
194+
};
195+
196+
!ops.iter().all(|op| matches!(op, Operand::Constant(_)))
197+
}
198+
{
199+
check_for_dangling(
200+
self.cx,
201+
self.body,
202+
*target,
203+
&actual_recv,
204+
destination,
205+
term.source_info.span,
206+
&mut self.results,
207+
);
117208
}
118-
&& let ty = cx.tcx.fn_sig(def_id).subst_identity().output()
119-
// afaik, any `ReLateBound` means it isn't static. Lifetimes are confusing under the
120-
// hood tho so this is likely wrong, and we'll need a smarter heuristic
121-
&& (ty.has_late_bound_regions() || !ty.skip_binder().is_ref())
209+
}
210+
}
211+
212+
fn check_for_dangling<'tcx>(
213+
cx: &LateContext<'tcx>,
214+
body: &mir::Body<'tcx>,
215+
bb: BasicBlock,
216+
recv: &Place<'tcx>,
217+
ptr: &Place<'_>,
218+
span: Span,
219+
results: &mut Vec<Span>,
220+
) {
221+
let data = &body.basic_blocks[bb];
222+
223+
if let Some(term) = &data.terminator
224+
&& let TerminatorKind::Drop { .. } = term.kind
122225
{
123-
span_lint_and_note(
124-
cx,
125-
PTR_TO_TEMPORARY,
126-
expr.span,
127-
"calling `as_ptr` on a temporary value",
128-
None,
129-
"usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at the end of \
130-
the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer",
131-
);
226+
if !body.basic_blocks[bb].statements.iter().any(|stmt| {
227+
matches!(stmt.kind, StatementKind::StorageDead(local) if ptr.local == local)
228+
}) {
229+
results.push(span);
230+
}
132231

133-
return true;
232+
return;
134233
}
135234

136-
false
137-
}
235+
// No drop glue or it's not a temporary. Bummer. Instead let's inspect the order of dead locals
236+
// to see if the pointer is dangling for even a single statement. If it's not, the reference to
237+
// `self` should always be dead after the pointer.
138238

139-
// TODO: Add a check here for some blocklist for methods that return a raw pointer that we should
140-
// lint. We can't bulk-deny these because we don't know whether it's returning something owned by
141-
// `self` (and will thus be dropped at the end of the statement) or is returning a pointer to newly
142-
// allocated memory, like what allocators do.
143-
/*
144-
fn check_for_denied_ptr_method<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
145-
true
239+
let mut recv_dead = false;
240+
241+
for stmt in &data.statements {
242+
if let StatementKind::StorageDead(local) = stmt.kind {
243+
match (local == recv.local, local == ptr.local) {
244+
(true, false) => recv_dead = true,
245+
(false, true) if recv_dead => {
246+
results.push(span);
247+
},
248+
_ => continue,
249+
}
250+
}
251+
}
146252
}
147-
*/

tests/ui/ptr_to_temporary.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@aux-build:proc_macros.rs:proc-macro
1+
//#aux-build:proc_macros.rs:proc-macro
22
#![allow(
33
clippy::borrow_deref_ref,
44
clippy::deref_addrof,
@@ -9,8 +9,8 @@
99
#![warn(clippy::ptr_to_temporary)]
1010
#![no_main]
1111

12-
#[macro_use]
13-
extern crate proc_macros;
12+
// #[macro_use]
13+
// extern crate proc_macros;
1414

1515
use std::cell::{Cell, RefCell};
1616
use std::ffi::CString;
@@ -51,6 +51,12 @@ fn bad_6() {
5151
let pv = vec![1].as_ptr();
5252
}
5353

54+
// TODO: Not linted yet. I believe this is because there's a cast in the MIR between the call to
55+
// `helper` and `as_ptr`. We likely need to be smarter with traversing assignments backwards.
56+
//
57+
// We can also probably optimize it by doing one pass over the body to get local assignments, that
58+
// way we don't need to call `local_assignments` a ton of times (and thus do a ton of completely
59+
// avoidable passes as well).
5460
fn bad_7() {
5561
fn helper() -> [i32; 1] {
5662
[1]
@@ -81,10 +87,13 @@ fn bad_12() {
8187
}
8288

8389
fn bad_13() {
84-
// TODO: Not linted. We need to normalize these
8590
let ps = <[i32]>::as_ptr(Vec::as_slice(&vec![1]));
8691
}
8792

93+
fn bad_14() {
94+
CString::new("asd".to_owned()).unwrap().as_c_str().as_ptr();
95+
}
96+
8897
// TODO: We need more tests here...
8998

9099
fn fine_1() -> *const i32 {
@@ -137,17 +146,16 @@ fn fine_8() {
137146
fn fine_9() {
138147
let pcstr = CString::new("asd".to_owned()).unwrap();
139148
// Not UB, as `pcstr` is not a temporary
140-
// TODO: Don't lint this! We need a check for whether a chain begins with a local...
141149
pcstr.as_c_str().as_ptr();
142150
}
143151

144152
fn fine_10() {
145153
let pcstr = CString::new("asd".to_owned()).unwrap();
146154
// Not UB, as `pcstr` is not a temporary
147-
// TODO: Same here, but this time it needs to be normalized
148155
CString::as_c_str(&pcstr).as_ptr();
149156
}
150157

158+
/*
151159
external! {
152160
fn fine_external() -> *const i32 {
153161
let a = 0i32;
@@ -162,3 +170,4 @@ with_span! {
162170
&a as *const i32
163171
}
164172
}
173+
*/

0 commit comments

Comments
 (0)