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