@@ -4,24 +4,69 @@ 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 > ( 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 > {
1446 if let ExprKind :: Path ( QPath :: TypeRelative ( cstr, fn_name) ) = & func. kind
1547 && let TyKind :: Path ( QPath :: Resolved ( _, ty_path) ) = & cstr. kind
1648 && 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)
1762 && let [ arg] = args
1863 && msrv. meets ( msrvs:: C_STR_LITERALS )
1964 {
20- match fn_name. ident . name . as_str ( ) {
65+ match fn_name. as_str ( ) {
2166 name @ ( "from_bytes_with_nul" | "from_bytes_with_nul_unchecked" )
2267 if !arg. span . from_expansion ( )
2368 && 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 =>
2570 {
2671 check_from_bytes ( cx, expr, arg, name) ;
2772 } ,
@@ -33,19 +78,20 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args
3378
3479/// Checks `CStr::from_bytes_with_nul(b"foo\0")`
3580fn 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
3782 && method. ident . name == sym:: as_ptr
3883 && !lit. span . from_expansion ( )
3984 && let ExprKind :: Lit ( lit) = lit. kind
4085 && let LitKind :: ByteStr ( _, StrStyle :: Cooked ) = lit. node
86+ && let Some ( sugg) = rewrite_as_cstr ( cx, lit. span )
4187 {
4288 span_lint_and_sugg (
4389 cx,
4490 MANUAL_C_STR_LITERALS ,
4591 expr. span ,
4692 "calling `CStr::from_ptr` with a byte string literal" ,
4793 r#"use a `c""` literal"# ,
48- rewrite_as_cstr ( cx , lit . span ) ,
94+ sugg ,
4995 Applicability :: MachineApplicable ,
5096 ) ;
5197 }
@@ -66,24 +112,29 @@ fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, metho
66112 ( expr. span , Applicability :: MaybeIncorrect )
67113 } ;
68114
115+ let Some ( sugg) = rewrite_as_cstr ( cx, arg. span ) else {
116+ return ;
117+ } ;
118+
69119 span_lint_and_sugg (
70120 cx,
71121 MANUAL_C_STR_LITERALS ,
72122 span,
73123 "calling `CStr::new` with a byte string literal" ,
74124 r#"use a `c""` literal"# ,
75- rewrite_as_cstr ( cx , arg . span ) ,
125+ sugg ,
76126 applicability,
77127 ) ;
78128}
79129
80130/// Rewrites a byte string literal to a c-str literal.
81131/// `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 > {
83135 let mut sugg = String :: from ( "c" ) + snippet ( cx, span. source_callsite ( ) , ".." ) . trim_start_matches ( 'b' ) ;
84136
85137 // NUL byte should always be right before the closing quote.
86- // (Can rfind ever return `None`?)
87138 if let Some ( quote_pos) = sugg. rfind ( '"' ) {
88139 // Possible values right before the quote:
89140 // - literal NUL value
@@ -98,17 +149,44 @@ pub fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> String {
98149 else if sugg[ ..quote_pos] . ends_with ( "\\ 0" ) {
99150 sugg. replace_range ( quote_pos - 2 ..quote_pos, "" ) ;
100151 }
152+ // No known suffix, so assume it's not a C-string.
153+ else {
154+ return None ;
155+ }
101156 }
102157
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+ }
104167}
105168
106169/// `x.cast()` -> `x`
107170/// `x as *const _` -> `x`
171+ /// `x` -> `x` (returns the same expression for non-cast exprs)
108172fn 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+ }
113190 }
191+ prev
114192}
0 commit comments