Skip to content

Commit 905e092

Browse files
committed
Extend incorrect_fn_null_checks to incorrect_non_null_checks
This extends the scope of the incorrect_fn_null_checks lint to also references. In theory, we could extend the check even further, for example to NonNull, or Box, but these types can't be casted via as casts to raw pointers so it would be a bit harder from an implementation point of view.
1 parent 33a2c24 commit 905e092

File tree

8 files changed

+187
-129
lines changed

8 files changed

+187
-129
lines changed

compiler/rustc_lint/messages.ftl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,6 @@ lint_expectation = this lint expectation is unfulfilled
215215
.note = the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message
216216
.rationale = {$rationale}
217217
218-
lint_fn_null_check = function pointers are not nullable, so checking them for null will always return false
219-
.help = wrap the function pointer inside an `Option` and use `Option::is_none` to check for null pointer value
220-
221218
lint_for_loops_over_fallibles =
222219
for loop over {$article} `{$ty}`. This is more readably written as an `if let` statement
223220
.suggestion = consider using `if let` to clear intent
@@ -399,6 +396,9 @@ lint_non_fmt_panic_unused =
399396
}
400397
.add_fmt_suggestion = or add a "{"{"}{"}"}" format string to use the message literally
401398
399+
lint_non_null_check = {$ty_desc}s can never be null, so checking them for null will always return false
400+
.help = wrap the {$ty_desc} inside an `Option` and use `Option::is_none` to check for null pointer values
401+
402402
lint_non_snake_case = {$sort} `{$name}` should have a snake case name
403403
.rename_or_convert_suggestion = rename the identifier or convert it to a snake case raw identifier
404404
.cannot_convert_note = `{$sc}` cannot be used as a raw identifier

compiler/rustc_lint/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ mod early;
5858
mod enum_intrinsics_non_enums;
5959
mod errors;
6060
mod expect;
61-
mod fn_null_check;
6261
mod for_loops_over_fallibles;
6362
pub mod hidden_unicode_codepoints;
6463
mod internal;
@@ -72,6 +71,7 @@ mod methods;
7271
mod multiple_supertrait_upcastable;
7372
mod non_ascii_idents;
7473
mod non_fmt_panic;
74+
mod non_null_check;
7575
mod nonstandard_style;
7676
mod noop_method_call;
7777
mod opaque_hidden_inferred_bound;
@@ -103,7 +103,6 @@ use cast_ref_to_mut::*;
103103
use deref_into_dyn_supertrait::*;
104104
use drop_forget_useless::*;
105105
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
106-
use fn_null_check::*;
107106
use for_loops_over_fallibles::*;
108107
use hidden_unicode_codepoints::*;
109108
use internal::*;
@@ -114,6 +113,7 @@ use methods::*;
114113
use multiple_supertrait_upcastable::*;
115114
use non_ascii_idents::*;
116115
use non_fmt_panic::NonPanicFmt;
116+
use non_null_check::*;
117117
use nonstandard_style::*;
118118
use noop_method_call::*;
119119
use opaque_hidden_inferred_bound::*;
@@ -227,7 +227,7 @@ late_lint_methods!(
227227
// Depends on types used in type definitions
228228
MissingCopyImplementations: MissingCopyImplementations,
229229
// Depends on referenced function signatures in expressions
230-
IncorrectFnNullChecks: IncorrectFnNullChecks,
230+
IncorrectNonNullChecks: IncorrectNonNullChecks,
231231
MutableTransmutes: MutableTransmutes,
232232
TypeAliasBounds: TypeAliasBounds,
233233
TrivialConstraints: TrivialConstraints,

compiler/rustc_lint/src/lints.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![allow(rustc::untranslatable_diagnostic)]
22
#![allow(rustc::diagnostic_outside_of_impl)]
3+
use std::borrow::Cow;
34
use std::num::NonZeroU32;
45

56
use crate::fluent_generated as fluent;
@@ -613,11 +614,13 @@ pub struct ExpectationNote {
613614
pub rationale: Symbol,
614615
}
615616

616-
// fn_null_check.rs
617+
// non_null_check.rs
617618
#[derive(LintDiagnostic)]
618-
#[diag(lint_fn_null_check)]
619+
#[diag(lint_non_null_check)]
619620
#[help]
620-
pub struct FnNullCheckDiag;
621+
pub struct NonNullCheckDiag {
622+
pub ty_desc: Cow<'static, str>,
623+
}
621624

622625
// for_loops_over_fallibles.rs
623626
#[derive(LintDiagnostic)]

compiler/rustc_lint/src/fn_null_check.rs renamed to compiler/rustc_lint/src/non_null_check.rs

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use crate::{lints::FnNullCheckDiag, LateContext, LateLintPass, LintContext};
1+
use crate::{lints::NonNullCheckDiag, LateContext, LateLintPass, LintContext};
22
use rustc_ast::LitKind;
33
use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind};
4+
use rustc_middle::ty::Ty;
45
use rustc_session::{declare_lint, declare_lint_pass};
56
use rustc_span::sym;
67

78
declare_lint! {
8-
/// The `incorrect_fn_null_checks` lint checks for expression that checks if a
9-
/// function pointer is null.
9+
/// The `incorrect_non_null_checks` lint checks for expressions that check if a
10+
/// non-nullable type is null.
1011
///
1112
/// ### Example
1213
///
@@ -22,85 +23,108 @@ declare_lint! {
2223
///
2324
/// ### Explanation
2425
///
25-
/// Function pointers are assumed to be non-null, checking them for null will always
26-
/// return false.
27-
INCORRECT_FN_NULL_CHECKS,
26+
/// A non-nullable type is assumed to never be null, and therefore having an actual
27+
/// non-null pointer is ub.
28+
INCORRECT_NON_NULL_CHECKS,
2829
Warn,
29-
"incorrect checking of null function pointer"
30+
"incorrect checking of non null pointers"
3031
}
3132

32-
declare_lint_pass!(IncorrectFnNullChecks => [INCORRECT_FN_NULL_CHECKS]);
33+
declare_lint_pass!(IncorrectNonNullChecks => [INCORRECT_NON_NULL_CHECKS]);
3334

34-
fn is_fn_ptr_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
35+
/// Is the cast to a nonnull type?
36+
/// If yes, return (ty, nullable_version) where former is the nonnull type while latter
37+
/// is a nullable version (e.g. (fn, Option<fn>) or (&u8, *const u8)).
38+
fn is_nonnull_cast<'a>(cx: &LateContext<'a>, expr: &Expr<'_>) -> Option<Ty<'a>> {
3539
let mut expr = expr.peel_blocks();
3640
let mut had_at_least_one_cast = false;
3741
while let ExprKind::Cast(cast_expr, cast_ty) = expr.kind
3842
&& let TyKind::Ptr(_) = cast_ty.kind {
3943
expr = cast_expr.peel_blocks();
4044
had_at_least_one_cast = true;
4145
}
42-
had_at_least_one_cast && cx.typeck_results().expr_ty_adjusted(expr).is_fn()
46+
if !had_at_least_one_cast {
47+
return None;
48+
}
49+
let ty = cx.typeck_results().expr_ty_adjusted(expr);
50+
if ty.is_fn() || ty.is_ref() {
51+
return Some(ty);
52+
}
53+
// Usually, references get coerced to pointers in a casting situation.
54+
// Therefore, we give also give a look to the original type.
55+
let ty_unadjusted = cx.typeck_results().expr_ty_opt(expr);
56+
if let Some(ty_unadjusted) = ty_unadjusted && ty_unadjusted.is_ref() {
57+
return Some(ty_unadjusted);
58+
}
59+
None
4360
}
4461

45-
impl<'tcx> LateLintPass<'tcx> for IncorrectFnNullChecks {
62+
impl<'tcx> LateLintPass<'tcx> for IncorrectNonNullChecks {
4663
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
4764
match expr.kind {
4865
// Catching:
49-
// <*<const/mut> <ty>>::is_null(fn_ptr as *<const/mut> <ty>)
66+
// <*<const/mut> <ty>>::is_null(test_ptr as *<const/mut> <ty>)
5067
ExprKind::Call(path, [arg])
5168
if let ExprKind::Path(ref qpath) = path.kind
5269
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
5370
&& matches!(
5471
cx.tcx.get_diagnostic_name(def_id),
5572
Some(sym::ptr_const_is_null | sym::ptr_is_null)
5673
)
57-
&& is_fn_ptr_cast(cx, arg) =>
74+
&& let Some(ty) = is_nonnull_cast(cx, arg) =>
5875
{
59-
cx.emit_spanned_lint(INCORRECT_FN_NULL_CHECKS, expr.span, FnNullCheckDiag)
76+
let diag = NonNullCheckDiag { ty_desc: ty.prefix_string(cx.tcx) };
77+
cx.emit_spanned_lint(INCORRECT_NON_NULL_CHECKS, expr.span, diag)
6078
}
6179

6280
// Catching:
63-
// (fn_ptr as *<const/mut> <ty>).is_null()
81+
// (test_ptr as *<const/mut> <ty>).is_null()
6482
ExprKind::MethodCall(_, receiver, _, _)
6583
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
6684
&& matches!(
6785
cx.tcx.get_diagnostic_name(def_id),
6886
Some(sym::ptr_const_is_null | sym::ptr_is_null)
6987
)
70-
&& is_fn_ptr_cast(cx, receiver) =>
88+
&& let Some(ty) = is_nonnull_cast(cx, receiver) =>
7189
{
72-
cx.emit_spanned_lint(INCORRECT_FN_NULL_CHECKS, expr.span, FnNullCheckDiag)
90+
let diag = NonNullCheckDiag { ty_desc: ty.prefix_string(cx.tcx) };
91+
cx.emit_spanned_lint(INCORRECT_NON_NULL_CHECKS, expr.span, diag)
7392
}
7493

7594
ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => {
7695
let to_check: &Expr<'_>;
77-
if is_fn_ptr_cast(cx, left) {
96+
let ty: Ty<'_>;
97+
if let Some(ty_) = is_nonnull_cast(cx, left) {
7898
to_check = right;
79-
} else if is_fn_ptr_cast(cx, right) {
99+
ty = ty_;
100+
} else if let Some(ty_) = is_nonnull_cast(cx, right) {
80101
to_check = left;
102+
ty = ty_;
81103
} else {
82104
return;
83105
}
84106

85107
match to_check.kind {
86108
// Catching:
87-
// (fn_ptr as *<const/mut> <ty>) == (0 as <ty>)
109+
// (test_ptr as *<const/mut> <ty>) == (0 as <ty>)
88110
ExprKind::Cast(cast_expr, _)
89111
if let ExprKind::Lit(spanned) = cast_expr.kind
90112
&& let LitKind::Int(v, _) = spanned.node && v == 0 =>
91113
{
92-
cx.emit_spanned_lint(INCORRECT_FN_NULL_CHECKS, expr.span, FnNullCheckDiag)
114+
let diag = NonNullCheckDiag { ty_desc: ty.prefix_string(cx.tcx) };
115+
cx.emit_spanned_lint(INCORRECT_NON_NULL_CHECKS, expr.span, diag)
93116
},
94117

95118
// Catching:
96-
// (fn_ptr as *<const/mut> <ty>) == std::ptr::null()
119+
// (test_ptr as *<const/mut> <ty>) == std::ptr::null()
97120
ExprKind::Call(path, [])
98121
if let ExprKind::Path(ref qpath) = path.kind
99122
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
100123
&& let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id)
101124
&& (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) =>
102125
{
103-
cx.emit_spanned_lint(INCORRECT_FN_NULL_CHECKS, expr.span, FnNullCheckDiag)
126+
let diag = NonNullCheckDiag { ty_desc: ty.prefix_string(cx.tcx) };
127+
cx.emit_spanned_lint(INCORRECT_NON_NULL_CHECKS, expr.span, diag)
104128
},
105129

106130
_ => {},

tests/ui/lint/fn_null_check.rs

Lines changed: 0 additions & 30 deletions
This file was deleted.

tests/ui/lint/fn_null_check.stderr

Lines changed: 0 additions & 67 deletions
This file was deleted.

tests/ui/lint/non_null_check.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// check-pass
2+
3+
fn main() {
4+
let fn_ptr = main;
5+
6+
if (fn_ptr as *mut ()).is_null() {}
7+
//~^ WARN can never be null, so checking
8+
if (fn_ptr as *const u8).is_null() {}
9+
//~^ WARN can never be null, so checking
10+
if (fn_ptr as *const ()) == std::ptr::null() {}
11+
//~^ WARN can never be null, so checking
12+
if (fn_ptr as *mut ()) == std::ptr::null_mut() {}
13+
//~^ WARN can never be null, so checking
14+
if (fn_ptr as *const ()) == (0 as *const ()) {}
15+
//~^ WARN can never be null, so checking
16+
if <*const _>::is_null(fn_ptr as *const ()) {}
17+
//~^ WARN can never be null, so checking
18+
if (fn_ptr as *mut fn() as *const fn() as *const ()).is_null() {}
19+
//~^ WARN can never be null, so checking
20+
if (fn_ptr as fn() as *const ()).is_null() {}
21+
//~^ WARN can never be null, so checking
22+
23+
const ZPTR: *const () = 0 as *const _;
24+
const NOT_ZPTR: *const () = 1 as *const _;
25+
26+
// unlike the uplifted clippy::fn_null_check lint we do
27+
// not lint on them
28+
if (fn_ptr as *const ()) == ZPTR {}
29+
if (fn_ptr as *const ()) == NOT_ZPTR {}
30+
31+
// Non fn pointers
32+
33+
let tup_ref: &_ = &(10u8, 10u8);
34+
if (tup_ref as *const (u8, u8)).is_null() {}
35+
//~^ WARN can never be null, so checking
36+
if (&mut (10u8, 10u8) as *mut (u8, u8)).is_null() {}
37+
//~^ WARN can never be null, so checking
38+
39+
// We could warn on these too, but don't:
40+
if Box::into_raw(Box::new("hi")).is_null() {}
41+
42+
let ptr = &mut () as *mut ();
43+
if core::ptr::NonNull::new(ptr).unwrap().as_ptr().is_null() {}
44+
45+
}

0 commit comments

Comments
 (0)