@@ -4,24 +4,75 @@ use clippy_utils::get_parent_expr;
44use clippy_utils:: source:: snippet;
55use rustc_ast:: { LitKind , StrStyle } ;
66use rustc_errors:: Applicability ;
7- use rustc_hir:: { Expr , ExprKind , QPath , TyKind } ;
7+ use rustc_hir:: { Expr , ExprKind , Node , QPath , TyKind } ;
88use rustc_lint:: LateContext ;
9- use rustc_span:: { sym, Span } ;
9+ use rustc_span:: { sym, Span , Symbol } ;
1010
1111use super :: MANUAL_C_STR_LITERALS ;
1212
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 > {
1452 if let ExprKind :: Path ( QPath :: TypeRelative ( cstr, fn_name) ) = & func. kind
1553 && let TyKind :: Path ( QPath :: Resolved ( _, ty_path) ) = & cstr. kind
1654 && 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)
1768 && let [ arg] = args
1869 && msrv. meets ( msrvs:: C_STR_LITERALS )
1970 {
20- match fn_name. ident . name . as_str ( ) {
71+ match fn_name. as_str ( ) {
2172 name @ ( "from_bytes_with_nul" | "from_bytes_with_nul_unchecked" )
2273 if !arg. span . from_expansion ( )
2374 && 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 =>
2576 {
2677 check_from_bytes ( cx, expr, arg, name) ;
2778 } ,
@@ -31,27 +82,27 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
3182 }
3283}
3384
34- /// Checks `CStr::from_bytes_with_nul (b"foo\0")`
85+ /// Checks `CStr::from_ptr (b"foo\0".as_ptr().cast() )`
3586fn 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
3788 && method. ident . name == sym:: as_ptr
3889 && !lit. span . from_expansion ( )
3990 && let ExprKind :: Lit ( lit) = lit. kind
4091 && let LitKind :: ByteStr ( _, StrStyle :: Cooked ) = lit. node
92+ && let Some ( sugg) = rewrite_as_cstr ( cx, lit. span )
4193 {
4294 span_lint_and_sugg (
4395 cx,
4496 MANUAL_C_STR_LITERALS ,
4597 expr. span ,
4698 "calling `CStr::from_ptr` with a byte string literal" ,
4799 r#"use a `c""` literal"# ,
48- rewrite_as_cstr ( cx , lit . span ) ,
100+ sugg ,
49101 Applicability :: MachineApplicable ,
50102 ) ;
51103 }
52104}
53-
54- /// Checks `CStr::from_ptr(b"foo\0".as_ptr().cast())`
105+ /// Checks `CStr::from_bytes_with_nul(b"foo\0")`
55106fn check_from_bytes ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > , arg : & Expr < ' _ > , method : & str ) {
56107 let ( span, applicability) = if let Some ( parent) = get_parent_expr ( cx, expr)
57108 && let ExprKind :: MethodCall ( method, ..) = parent. kind
@@ -63,7 +114,11 @@ fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, metho
63114 ( expr. span , Applicability :: MachineApplicable )
64115 } else {
65116 // 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 ;
67122 } ;
68123
69124 span_lint_and_sugg (
@@ -72,18 +127,19 @@ fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, metho
72127 span,
73128 "calling `CStr::new` with a byte string literal" ,
74129 r#"use a `c""` literal"# ,
75- rewrite_as_cstr ( cx , arg . span ) ,
130+ sugg ,
76131 applicability,
77132 ) ;
78133}
79134
80135/// Rewrites a byte string literal to a c-str literal.
81136/// `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 > {
83140 let mut sugg = String :: from ( "c" ) + snippet ( cx, span. source_callsite ( ) , ".." ) . trim_start_matches ( 'b' ) ;
84141
85142 // NUL byte should always be right before the closing quote.
86- // (Can rfind ever return `None`?)
87143 if let Some ( quote_pos) = sugg. rfind ( '"' ) {
88144 // Possible values right before the quote:
89145 // - literal NUL value
@@ -98,17 +154,44 @@ pub fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> String {
98154 else if sugg[ ..quote_pos] . ends_with ( "\\ 0" ) {
99155 sugg. replace_range ( quote_pos - 2 ..quote_pos, "" ) ;
100156 }
157+ // No known suffix, so assume it's not a C-string.
158+ else {
159+ return None ;
160+ }
101161 }
102162
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+ }
104172}
105173
106174/// `x.cast()` -> `x`
107175/// `x as *const _` -> `x`
176+ /// `x` -> `x` (returns the same expression for non-cast exprs)
108177fn 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+ }
113195 }
196+ prev
114197}
0 commit comments