Skip to content

Commit aa2b23d

Browse files
committed
Also lint some as_ptr methods as well
1 parent 1de8b62 commit aa2b23d

File tree

4 files changed

+239
-49
lines changed

4 files changed

+239
-49
lines changed

clippy_lints/src/ptr_to_temporary.rs

+104-36
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
use clippy_utils::{consts::is_promotable, diagnostics::span_lint_and_note, is_from_proc_macro};
1+
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};
24
use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, OwnerNode};
35
use rustc_lint::{LateContext, LateLintPass, LintContext};
46
use rustc_middle::lint::in_external_macro;
7+
use rustc_middle::ty;
58
use rustc_session::{declare_lint_pass, declare_tool_lint};
9+
use rustc_span::sym;
610

711
declare_clippy_lint! {
812
/// ### What it does
@@ -11,23 +15,27 @@ declare_clippy_lint! {
1115
/// [constant promotion](https://doc.rust-lang.org/stable/reference/destructors.html#constant-promotion).
1216
///
1317
/// ### Why is this bad?
14-
/// Usage of such a pointer can result in Undefined Behavior, as the pointer will stop pointing
15-
/// to valid stack memory once the temporary is dropped.
18+
/// Usage of such a pointer will result in Undefined Behavior, as the pointer will stop
19+
/// pointing to valid stack memory once the temporary is dropped.
1620
///
1721
/// ### Example
1822
/// ```rust,ignore
19-
/// fn x() -> *const i32 {
23+
/// fn returning_temp() -> *const i32 {
2024
/// let x = 0;
2125
/// &x as *const i32
2226
/// }
2327
///
24-
/// let x = x();
25-
/// unsafe { *x }; // ⚠️
28+
/// let px = returning_temp();
29+
/// unsafe { *px }; // ⚠️
30+
/// let pv = vec![].as_ptr();
31+
/// unsafe { *pv }; // ⚠️
2632
/// ```
2733
#[clippy::version = "1.72.0"]
2834
pub PTR_TO_TEMPORARY,
29-
correctness,
30-
"disallows returning a raw pointer to a temporary value"
35+
// TODO: Let's make it warn-by-default for now, and change this to deny-by-default once we know
36+
// there are no major FPs
37+
suspicious,
38+
"disallows obtaining raw pointers to temporary values"
3139
}
3240
declare_lint_pass!(PtrToTemporary => [PTR_TO_TEMPORARY]);
3341

@@ -37,34 +45,94 @@ impl<'tcx> LateLintPass<'tcx> for PtrToTemporary {
3745
return;
3846
}
3947

40-
// Get the final return statement if this is a return statement, or don't lint
41-
let expr = if let ExprKind::Ret(Some(expr)) = expr.kind {
42-
expr
43-
} else if let OwnerNode::Item(parent) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id))
44-
&& let ItemKind::Fn(_, _, body) = parent.kind
45-
&& let block = cx.tcx.hir().body(body).value
46-
&& let ExprKind::Block(block, _) = block.kind
47-
&& let Some(final_block_expr) = block.expr
48-
&& final_block_expr.hir_id == expr.hir_id
49-
{
50-
expr
51-
} else {
52-
return;
53-
};
48+
_ = check_for_returning_raw_ptr(cx, expr) || check_for_dangling_as_ptr(cx, expr);
49+
}
50+
}
5451

55-
if let ExprKind::Cast(cast_expr, _) = expr.kind
56-
&& let ExprKind::AddrOf(BorrowKind::Ref, _, e) = cast_expr.kind
57-
&& !is_promotable(cx, e)
58-
&& !is_from_proc_macro(cx, expr)
59-
{
60-
span_lint_and_note(
61-
cx,
62-
PTR_TO_TEMPORARY,
63-
expr.span,
64-
"returning a raw pointer to a temporary value that cannot be promoted to a constant",
65-
None,
66-
"usage of this pointer by callers will cause Undefined Behavior",
67-
);
68-
}
52+
/// Check for returning raw pointers to temporaries that are not promoted to a constant.
53+
fn check_for_returning_raw_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
54+
// Get the final return statement if this is a return statement, or don't lint
55+
let expr = if let ExprKind::Ret(Some(expr)) = expr.kind {
56+
expr
57+
} else if let OwnerNode::Item(parent) = cx.tcx.hir().owner(cx.tcx.hir().get_parent_item(expr.hir_id))
58+
&& let ItemKind::Fn(_, _, body) = parent.kind
59+
&& let block = cx.tcx.hir().body(body).value
60+
&& let ExprKind::Block(block, _) = block.kind
61+
&& let Some(final_block_expr) = block.expr
62+
&& final_block_expr.hir_id == expr.hir_id
63+
{
64+
expr
65+
} else {
66+
return false;
67+
};
68+
69+
if let ExprKind::Cast(cast_expr, _) = expr.kind
70+
&& let ExprKind::AddrOf(BorrowKind::Ref, _, e) = cast_expr.kind
71+
&& !is_promotable(cx, e)
72+
&& !is_from_proc_macro(cx, expr)
73+
{
74+
span_lint_and_note(
75+
cx,
76+
PTR_TO_TEMPORARY,
77+
expr.span,
78+
"returning a raw pointer to a temporary value that cannot be promoted to a constant",
79+
None,
80+
"usage of this pointer by callers will cause Undefined Behavior as the temporary will be deallocated at \
81+
the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer",
82+
);
83+
84+
return true;
85+
}
86+
87+
false
88+
}
89+
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+
// These will almost always be promoted yet have the `as_ptr` method. Ideally we would
105+
// check if these would be promoted but our logic considers any function call to be
106+
// non-promotable, but in this case it will be as it's `'static`, soo...
107+
&& !matches!(
108+
cx.typeck_results().expr_ty(recv).peel_refs().kind(),
109+
ty::Str | ty::Array(_, _) | ty::Slice(_)
110+
)
111+
&& is_temporary(cx, recv)
112+
&& !is_from_proc_macro(cx, expr)
113+
{
114+
span_lint_and_note(
115+
cx,
116+
PTR_TO_TEMPORARY,
117+
expr.span,
118+
"calling `as_ptr` on a temporary value",
119+
None,
120+
"usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at the end of \
121+
the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer",
122+
);
123+
124+
return true;
69125
}
126+
127+
false
128+
}
129+
130+
// TODO: Add a check here for some blocklist for methods that return a raw pointer that we should
131+
// lint. We can't bulk-deny these because we don't know whether it's returning something owned by
132+
// `self` (and will thus be dropped at the end of the statement) or is returning a pointer to newly
133+
// allocated memory, like what allocators do.
134+
/*
135+
fn check_for_denied_ptr_method<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
136+
true
70137
}
138+
*/

clippy_utils/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -2458,6 +2458,16 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
24582458
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
24592459
}
24602460

2461+
/// Returns whether `expr` is a temporary value that will be freed at the end of the statement.
2462+
pub fn is_temporary(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2463+
!expr.is_place_expr(|base| {
2464+
cx.typeck_results()
2465+
.adjustments()
2466+
.get(base.hir_id)
2467+
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
2468+
})
2469+
}
2470+
24612471
/// Walks the HIR tree from the given expression, up to the node where the value produced by the
24622472
/// expression is consumed. Calls the function for every node encountered this way until it returns
24632473
/// `Some`.

tests/ui/ptr_to_temporary.rs

+66-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
//@aux-build:proc_macros.rs
2-
#![allow(clippy::borrow_deref_ref, clippy::deref_addrof, clippy::identity_op, unused)]
1+
//@aux-build:proc_macros.rs:proc-macro
2+
#![allow(
3+
clippy::borrow_deref_ref,
4+
clippy::deref_addrof,
5+
clippy::identity_op,
6+
temporary_cstring_as_ptr,
7+
unused
8+
)]
39
#![warn(clippy::ptr_to_temporary)]
410
#![no_main]
511

612
#[macro_use]
713
extern crate proc_macros;
814

15+
use std::cell::{Cell, RefCell};
16+
use std::ffi::CString;
17+
use std::sync::atomic::AtomicBool;
18+
919
fn bad_1() -> *const i32 {
20+
// NOTE: `is_const_evaluatable` misses this. This may be a bug in that utils function, and if
21+
// possible we should switch to it.
1022
&(100 + *&0) as *const i32
1123
}
1224

@@ -35,6 +47,37 @@ fn bad_5() -> *const i32 {
3547
&a as *const i32
3648
}
3749

50+
fn bad_6() {
51+
let pv = vec![1].as_ptr();
52+
}
53+
54+
fn bad_7() {
55+
fn helper() -> [i32; 1] {
56+
[1]
57+
}
58+
59+
let pa = helper().as_ptr();
60+
}
61+
62+
fn bad_8() {
63+
let pc = Cell::new("oops ub").as_ptr();
64+
}
65+
66+
fn bad_9() {
67+
let prc = RefCell::new("oops more ub").as_ptr();
68+
}
69+
70+
fn bad_10() {
71+
// Our lint and `temporary_cstring_as_ptr` both catch this. Maybe we can deprecate that one?
72+
let pcstr = unsafe { CString::new(vec![]).unwrap().as_ptr() };
73+
}
74+
75+
fn bad_11() {
76+
let pab = unsafe { AtomicBool::new(true).as_ptr() };
77+
}
78+
79+
// TODO: We need more tests here...
80+
3881
fn fine_1() -> *const i32 {
3982
&100 as *const i32
4083
}
@@ -49,6 +92,27 @@ fn fine_3() -> *const i32 {
4992
&(*A) as *const i32
5093
}
5194

95+
fn fine_4() {
96+
let pa = ([1],).0.as_ptr();
97+
}
98+
99+
fn fine_5() {
100+
fn helper() -> &'static str {
101+
"i'm not ub"
102+
}
103+
104+
let ps = helper().as_ptr();
105+
}
106+
107+
fn fine_6() {
108+
fn helper() -> &'static [i32; 1] {
109+
&[1]
110+
}
111+
112+
let pa = helper().as_ptr();
113+
unsafe { *pa };
114+
}
115+
52116
external! {
53117
fn fine_external() -> *const i32 {
54118
let a = 0i32;

tests/ui/ptr_to_temporary.stderr

+59-11
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,91 @@
1+
error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr`
2+
--> $DIR/ptr_to_temporary.rs:6:5
3+
|
4+
LL | clippy::temporary_cstring_as_ptr,
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr`
6+
|
7+
= note: `-D renamed-and-removed-lints` implied by `-D warnings`
8+
19
error: returning a raw pointer to a temporary value that cannot be promoted to a constant
2-
--> $DIR/ptr_to_temporary.rs:10:5
10+
--> $DIR/ptr_to_temporary.rs:22:5
311
|
412
LL | &(100 + *&0) as *const i32
513
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
614
|
7-
= note: usage of this pointer by callers will cause Undefined Behavior
15+
= note: usage of this pointer by callers will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
816
= note: `-D clippy::ptr-to-temporary` implied by `-D warnings`
917

1018
error: returning a raw pointer to a temporary value that cannot be promoted to a constant
11-
--> $DIR/ptr_to_temporary.rs:15:5
19+
--> $DIR/ptr_to_temporary.rs:27:5
1220
|
1321
LL | &(*&a) as *const i32
1422
| ^^^^^^^^^^^^^^^^^^^^
1523
|
16-
= note: usage of this pointer by callers will cause Undefined Behavior
24+
= note: usage of this pointer by callers will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
1725

1826
error: returning a raw pointer to a temporary value that cannot be promoted to a constant
19-
--> $DIR/ptr_to_temporary.rs:20:5
27+
--> $DIR/ptr_to_temporary.rs:32:5
2028
|
2129
LL | &a as *const i32
2230
| ^^^^^^^^^^^^^^^^
2331
|
24-
= note: usage of this pointer by callers will cause Undefined Behavior
32+
= note: usage of this pointer by callers will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
2533

2634
error: returning a raw pointer to a temporary value that cannot be promoted to a constant
27-
--> $DIR/ptr_to_temporary.rs:25:5
35+
--> $DIR/ptr_to_temporary.rs:37:5
2836
|
2937
LL | &a as *const i32
3038
| ^^^^^^^^^^^^^^^^
3139
|
32-
= note: usage of this pointer by callers will cause Undefined Behavior
40+
= note: usage of this pointer by callers will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
3341

3442
error: returning a raw pointer to a temporary value that cannot be promoted to a constant
35-
--> $DIR/ptr_to_temporary.rs:35:5
43+
--> $DIR/ptr_to_temporary.rs:47:5
3644
|
3745
LL | &a as *const i32
3846
| ^^^^^^^^^^^^^^^^
3947
|
40-
= note: usage of this pointer by callers will cause Undefined Behavior
48+
= note: usage of this pointer by callers will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
49+
50+
error: calling `as_ptr` on a temporary value
51+
--> $DIR/ptr_to_temporary.rs:51:14
52+
|
53+
LL | let pv = vec![1].as_ptr();
54+
| ^^^^^^^^^^^^^^^^
55+
|
56+
= note: usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
57+
58+
error: calling `as_ptr` on a temporary value
59+
--> $DIR/ptr_to_temporary.rs:63:14
60+
|
61+
LL | let pc = Cell::new("oops ub").as_ptr();
62+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
63+
|
64+
= note: usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
65+
66+
error: calling `as_ptr` on a temporary value
67+
--> $DIR/ptr_to_temporary.rs:67:15
68+
|
69+
LL | let prc = RefCell::new("oops more ub").as_ptr();
70+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
71+
|
72+
= note: usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
73+
74+
error: calling `as_ptr` on a temporary value
75+
--> $DIR/ptr_to_temporary.rs:71:26
76+
|
77+
LL | let pcstr = unsafe { CString::new(vec![]).unwrap().as_ptr() };
78+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
79+
|
80+
= note: usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
81+
82+
error: calling `as_ptr` on a temporary value
83+
--> $DIR/ptr_to_temporary.rs:75:24
84+
|
85+
LL | let pab = unsafe { AtomicBool::new(true).as_ptr() };
86+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
87+
|
88+
= note: usage of this pointer will cause Undefined Behavior as the temporary will be deallocated at the end of the statement, yet the pointer will continue pointing to it, resulting in a dangling pointer
4189

42-
error: aborting due to 5 previous errors
90+
error: aborting due to 11 previous errors
4391

0 commit comments

Comments
 (0)