@@ -4,24 +4,75 @@ use clippy_utils::get_parent_expr;
4
4
use clippy_utils:: source:: snippet;
5
5
use rustc_ast:: { LitKind , StrStyle } ;
6
6
use rustc_errors:: Applicability ;
7
- use rustc_hir:: { Expr , ExprKind , QPath , TyKind } ;
7
+ use rustc_hir:: { Expr , ExprKind , Node , QPath , TyKind } ;
8
8
use rustc_lint:: LateContext ;
9
- use rustc_span:: { sym, Span } ;
9
+ use rustc_span:: { sym, Span , Symbol } ;
10
10
11
11
use super :: MANUAL_C_STR_LITERALS ;
12
12
13
- pub ( super ) fn check ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , func : & Expr < ' _ > , args : & [ Expr < ' _ > ] , msrv : & Msrv ) {
13
+ /// Checks:
14
+ /// - `b"...".as_ptr()`
15
+ /// - `b"...".as_ptr().cast()`
16
+ /// - `"...".as_ptr()`
17
+ /// - `"...".as_ptr().cast()`
18
+ ///
19
+ /// Iff the parent call of `.cast()` isn't `CStr::from_ptr`, to avoid linting twice.
20
+ pub ( super ) fn check_as_ptr < ' tcx > (
21
+ cx : & LateContext < ' tcx > ,
22
+ expr : & ' tcx Expr < ' tcx > ,
23
+ receiver : & ' tcx Expr < ' tcx > ,
24
+ msrv : & Msrv ,
25
+ ) {
26
+ if let ExprKind :: Lit ( lit) = receiver. kind
27
+ && let LitKind :: ByteStr ( _, StrStyle :: Cooked ) | LitKind :: Str ( _, StrStyle :: Cooked ) = lit. node
28
+ && let casts_removed = peel_ptr_cast_ancestors ( cx, expr)
29
+ && !get_parent_expr ( cx, casts_removed) . is_some_and (
30
+ |parent| matches ! ( parent. kind, ExprKind :: Call ( func, _) if is_c_str_function( cx, func) . is_some( ) ) ,
31
+ )
32
+ && let Some ( sugg) = rewrite_as_cstr ( cx, lit. span )
33
+ && msrv. meets ( msrvs:: C_STR_LITERALS )
34
+ {
35
+ span_lint_and_sugg (
36
+ cx,
37
+ MANUAL_C_STR_LITERALS ,
38
+ receiver. span ,
39
+ "manually constructing a nul-terminated string" ,
40
+ r#"use a `c""` literal"# ,
41
+ sugg,
42
+ // an additional cast may be needed, since the type of `CStr::as_ptr` and
43
+ // `"".as_ptr()` can differ and is platform dependent
44
+ Applicability :: HasPlaceholders ,
45
+ ) ;
46
+ }
47
+ }
48
+
49
+ /// Checks if the callee is a "relevant" `CStr` function considered by this lint.
50
+ /// Returns the function name.
51
+ fn is_c_str_function ( cx : & LateContext < ' _ > , func : & Expr < ' _ > ) -> Option < Symbol > {
14
52
if let ExprKind :: Path ( QPath :: TypeRelative ( cstr, fn_name) ) = & func. kind
15
53
&& let TyKind :: Path ( QPath :: Resolved ( _, ty_path) ) = & cstr. kind
16
54
&& cx. tcx . lang_items ( ) . c_str ( ) == ty_path. res . opt_def_id ( )
55
+ {
56
+ Some ( fn_name. ident . name )
57
+ } else {
58
+ None
59
+ }
60
+ }
61
+
62
+ /// Checks calls to the `CStr` constructor functions:
63
+ /// - `CStr::from_bytes_with_nul(..)`
64
+ /// - `CStr::from_bytes_with_nul_unchecked(..)`
65
+ /// - `CStr::from_ptr(..)`
66
+ pub ( super ) fn check ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , func : & Expr < ' _ > , args : & [ Expr < ' _ > ] , msrv : & Msrv ) {
67
+ if let Some ( fn_name) = is_c_str_function ( cx, func)
17
68
&& let [ arg] = args
18
69
&& msrv. meets ( msrvs:: C_STR_LITERALS )
19
70
{
20
- match fn_name. ident . name . as_str ( ) {
71
+ match fn_name. as_str ( ) {
21
72
name @ ( "from_bytes_with_nul" | "from_bytes_with_nul_unchecked" )
22
73
if !arg. span . from_expansion ( )
23
74
&& let ExprKind :: Lit ( lit) = arg. kind
24
- && let LitKind :: ByteStr ( _, StrStyle :: Cooked ) = lit. node =>
75
+ && let LitKind :: ByteStr ( _, StrStyle :: Cooked ) | LitKind :: Str ( _ , StrStyle :: Cooked ) = lit. node =>
25
76
{
26
77
check_from_bytes ( cx, expr, arg, name) ;
27
78
} ,
@@ -31,27 +82,27 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
31
82
}
32
83
}
33
84
34
- /// Checks `CStr::from_bytes_with_nul (b"foo\0")`
85
+ /// Checks `CStr::from_ptr (b"foo\0".as_ptr().cast() )`
35
86
fn check_from_ptr ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , arg : & Expr < ' _ > ) {
36
- if let ExprKind :: MethodCall ( method, lit, [ ] , _ ) = peel_ptr_cast ( arg) . kind
87
+ if let ExprKind :: MethodCall ( method, lit, .. ) = peel_ptr_cast ( arg) . kind
37
88
&& method. ident . name == sym:: as_ptr
38
89
&& !lit. span . from_expansion ( )
39
90
&& let ExprKind :: Lit ( lit) = lit. kind
40
91
&& let LitKind :: ByteStr ( _, StrStyle :: Cooked ) = lit. node
92
+ && let Some ( sugg) = rewrite_as_cstr ( cx, lit. span )
41
93
{
42
94
span_lint_and_sugg (
43
95
cx,
44
96
MANUAL_C_STR_LITERALS ,
45
97
expr. span ,
46
98
"calling `CStr::from_ptr` with a byte string literal" ,
47
99
r#"use a `c""` literal"# ,
48
- rewrite_as_cstr ( cx , lit . span ) ,
100
+ sugg ,
49
101
Applicability :: MachineApplicable ,
50
102
) ;
51
103
}
52
104
}
53
-
54
- /// Checks `CStr::from_ptr(b"foo\0".as_ptr().cast())`
105
+ /// Checks `CStr::from_bytes_with_nul(b"foo\0")`
55
106
fn check_from_bytes ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , arg : & Expr < ' _ > , method : & str ) {
56
107
let ( span, applicability) = if let Some ( parent) = get_parent_expr ( cx, expr)
57
108
&& let ExprKind :: MethodCall ( method, ..) = parent. kind
@@ -63,7 +114,11 @@ fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, metho
63
114
( expr. span , Applicability :: MachineApplicable )
64
115
} else {
65
116
// User needs to remove error handling, can't be machine applicable
66
- ( expr. span , Applicability :: MaybeIncorrect )
117
+ ( expr. span , Applicability :: HasPlaceholders )
118
+ } ;
119
+
120
+ let Some ( sugg) = rewrite_as_cstr ( cx, arg. span ) else {
121
+ return ;
67
122
} ;
68
123
69
124
span_lint_and_sugg (
@@ -72,18 +127,19 @@ fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, metho
72
127
span,
73
128
"calling `CStr::new` with a byte string literal" ,
74
129
r#"use a `c""` literal"# ,
75
- rewrite_as_cstr ( cx , arg . span ) ,
130
+ sugg ,
76
131
applicability,
77
132
) ;
78
133
}
79
134
80
135
/// Rewrites a byte string literal to a c-str literal.
81
136
/// `b"foo\0"` -> `c"foo"`
82
- pub fn rewrite_as_cstr ( cx : & LateContext < ' _ > , span : Span ) -> String {
137
+ ///
138
+ /// Returns `None` if it doesn't end in a NUL byte.
139
+ fn rewrite_as_cstr ( cx : & LateContext < ' _ > , span : Span ) -> Option < String > {
83
140
let mut sugg = String :: from ( "c" ) + snippet ( cx, span. source_callsite ( ) , ".." ) . trim_start_matches ( 'b' ) ;
84
141
85
142
// NUL byte should always be right before the closing quote.
86
- // (Can rfind ever return `None`?)
87
143
if let Some ( quote_pos) = sugg. rfind ( '"' ) {
88
144
// Possible values right before the quote:
89
145
// - literal NUL value
@@ -98,17 +154,44 @@ pub fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> String {
98
154
else if sugg[ ..quote_pos] . ends_with ( "\\ 0" ) {
99
155
sugg. replace_range ( quote_pos - 2 ..quote_pos, "" ) ;
100
156
}
157
+ // No known suffix, so assume it's not a C-string.
158
+ else {
159
+ return None ;
160
+ }
101
161
}
102
162
103
- sugg
163
+ Some ( sugg)
164
+ }
165
+
166
+ fn get_cast_target < ' tcx > ( e : & ' tcx Expr < ' tcx > ) -> Option < & ' tcx Expr < ' tcx > > {
167
+ match & e. kind {
168
+ ExprKind :: MethodCall ( method, receiver, [ ] , _) if method. ident . as_str ( ) == "cast" => Some ( receiver) ,
169
+ ExprKind :: Cast ( expr, _) => Some ( expr) ,
170
+ _ => None ,
171
+ }
104
172
}
105
173
106
174
/// `x.cast()` -> `x`
107
175
/// `x as *const _` -> `x`
176
+ /// `x` -> `x` (returns the same expression for non-cast exprs)
108
177
fn peel_ptr_cast < ' tcx > ( e : & ' tcx Expr < ' tcx > ) -> & ' tcx Expr < ' tcx > {
109
- match & e. kind {
110
- ExprKind :: MethodCall ( method, receiver, [ ] , _) if method. ident . as_str ( ) == "cast" => peel_ptr_cast ( receiver) ,
111
- ExprKind :: Cast ( expr, _) => peel_ptr_cast ( expr) ,
112
- _ => e,
178
+ get_cast_target ( e) . map_or ( e, peel_ptr_cast)
179
+ }
180
+
181
+ /// Same as `peel_ptr_cast`, but the other way around, by walking up the ancestor cast expressions:
182
+ ///
183
+ /// `foo(x.cast() as *const _)`
184
+ /// ^ given this `x` expression, returns the `foo(...)` expression
185
+ fn peel_ptr_cast_ancestors < ' tcx > ( cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' tcx > ) -> & ' tcx Expr < ' tcx > {
186
+ let mut prev = e;
187
+ for ( _, node) in cx. tcx . hir ( ) . parent_iter ( e. hir_id ) {
188
+ if let Node :: Expr ( e) = node
189
+ && get_cast_target ( e) . is_some ( )
190
+ {
191
+ prev = e;
192
+ } else {
193
+ break ;
194
+ }
113
195
}
196
+ prev
114
197
}
0 commit comments