diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 3f16bb9f442ee..f19572550ebd0 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1,3 +1,5 @@ +// ignore-tidy-filelength + //! Lints in the Rust compiler. //! //! This contains lints which can feasibly be implemented as their own @@ -2961,3 +2963,88 @@ impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations { } } } + +declare_lint! { + /// The `deref_nullptr` lint detects when an null pointer is dereferenced, + /// which causes [undefined behavior]. + /// + /// ### Example + /// + /// ```rust,no_run + /// # #![allow(unused)] + /// use std::ptr; + /// unsafe { + /// let x = &*ptr::null::(); + /// let x = ptr::addr_of!(*ptr::null::()); + /// let x = *(0 as *const i32); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Dereferencing a null pointer causes [undefined behavior] even as a place expression, + /// like `&*(0 as *const i32)` or `addr_of!(*(0 as *const i32))`. + /// + /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html + pub DEREF_NULLPTR, + Warn, + "detects when an null pointer is dereferenced" +} + +declare_lint_pass!(DerefNullPtr => [DEREF_NULLPTR]); + +impl<'tcx> LateLintPass<'tcx> for DerefNullPtr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { + /// test if expression is a null ptr + fn is_null_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + match &expr.kind { + rustc_hir::ExprKind::Cast(ref expr, ref ty) => { + if let rustc_hir::TyKind::Ptr(_) = ty.kind { + return is_zero(expr) || is_null_ptr(cx, expr); + } + } + // check for call to `core::ptr::null` or `core::ptr::null_mut` + rustc_hir::ExprKind::Call(ref path, _) => { + if let rustc_hir::ExprKind::Path(ref qpath) = path.kind { + if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() { + return cx.tcx.is_diagnostic_item(sym::ptr_null, def_id) + || cx.tcx.is_diagnostic_item(sym::ptr_null_mut, def_id); + } + } + } + _ => {} + } + false + } + + /// test if experssion is the literal `0` + fn is_zero(expr: &hir::Expr<'_>) -> bool { + match &expr.kind { + rustc_hir::ExprKind::Lit(ref lit) => { + if let LitKind::Int(a, _) = lit.node { + return a == 0; + } + } + _ => {} + } + false + } + + if let rustc_hir::ExprKind::Unary(ref un_op, ref expr_deref) = expr.kind { + if let rustc_hir::UnOp::Deref = un_op { + if is_null_ptr(cx, expr_deref) { + cx.struct_span_lint(DEREF_NULLPTR, expr.span, |lint| { + let mut err = lint.build("dereferencing a null pointer"); + err.span_label( + expr.span, + "this code causes undefined behavior when executed", + ); + err.emit(); + }); + } + } + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index e2724b52453a5..2f46969b021e6 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -206,6 +206,7 @@ macro_rules! late_lint_mod_passes { UnreachablePub: UnreachablePub, ExplicitOutlivesRequirements: ExplicitOutlivesRequirements, InvalidValue: InvalidValue, + DerefNullPtr: DerefNullPtr, ] ); }; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 54fea5515946f..95a8bda94222c 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -900,6 +900,8 @@ symbols! { profiler_runtime, ptr_guaranteed_eq, ptr_guaranteed_ne, + ptr_null, + ptr_null_mut, ptr_offset_from, pub_macro_rules, pub_restricted, diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 6e207156b55a3..ad8696ab9272d 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -211,6 +211,7 @@ pub unsafe fn drop_in_place(to_drop: *mut T) { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_promotable] #[rustc_const_stable(feature = "const_ptr_null", since = "1.24.0")] +#[rustc_diagnostic_item = "ptr_null"] pub const fn null() -> *const T { 0 as *const T } @@ -229,6 +230,7 @@ pub const fn null() -> *const T { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_promotable] #[rustc_const_stable(feature = "const_ptr_null", since = "1.24.0")] +#[rustc_diagnostic_item = "ptr_null_mut"] pub const fn null_mut() -> *mut T { 0 as *mut T } diff --git a/src/test/ui/cleanup-shortcircuit.rs b/src/test/ui/cleanup-shortcircuit.rs index 4f5197a5ba9b8..fe867ce1fbd57 100644 --- a/src/test/ui/cleanup-shortcircuit.rs +++ b/src/test/ui/cleanup-shortcircuit.rs @@ -3,6 +3,9 @@ // pretty-expanded FIXME #23616 +#![allow(deref_nullptr)] + + use std::env; pub fn main() { diff --git a/src/test/ui/lint/lint-deref-nullptr.rs b/src/test/ui/lint/lint-deref-nullptr.rs new file mode 100644 index 0000000000000..d052dbd9b647c --- /dev/null +++ b/src/test/ui/lint/lint-deref-nullptr.rs @@ -0,0 +1,38 @@ +// test the deref_nullptr lint + +#![deny(deref_nullptr)] + +use std::ptr; + +struct Struct { + field: u8, +} + +fn f() { + unsafe { + let a = 1; + let ub = *(a as *const i32); + let ub = *(0 as *const i32); + //~^ ERROR dereferencing a null pointer + let ub = *ptr::null::(); + //~^ ERROR dereferencing a null pointer + let ub = *ptr::null_mut::(); + //~^ ERROR dereferencing a null pointer + let ub = *(ptr::null::() as *const i32); + //~^ ERROR dereferencing a null pointer + let ub = *(ptr::null::() as *mut i32 as *mut usize as *const u8); + //~^ ERROR dereferencing a null pointer + let ub = &*ptr::null::(); + //~^ ERROR dereferencing a null pointer + let ub = &*ptr::null_mut::(); + //~^ ERROR dereferencing a null pointer + ptr::addr_of!(*ptr::null::()); + //~^ ERROR dereferencing a null pointer + ptr::addr_of_mut!(*ptr::null_mut::()); + //~^ ERROR dereferencing a null pointer + let offset = ptr::addr_of!((*ptr::null::()).field); + //~^ ERROR dereferencing a null pointer + } +} + +fn main() {} diff --git a/src/test/ui/lint/lint-deref-nullptr.stderr b/src/test/ui/lint/lint-deref-nullptr.stderr new file mode 100644 index 0000000000000..c6f432e4e4207 --- /dev/null +++ b/src/test/ui/lint/lint-deref-nullptr.stderr @@ -0,0 +1,68 @@ +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:15:18 + | +LL | let ub = *(0 as *const i32); + | ^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + | +note: the lint level is defined here + --> $DIR/lint-deref-nullptr.rs:3:9 + | +LL | #![deny(deref_nullptr)] + | ^^^^^^^^^^^^^ + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:17:18 + | +LL | let ub = *ptr::null::(); + | ^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:19:18 + | +LL | let ub = *ptr::null_mut::(); + | ^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:21:18 + | +LL | let ub = *(ptr::null::() as *const i32); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:23:18 + | +LL | let ub = *(ptr::null::() as *mut i32 as *mut usize as *const u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:25:19 + | +LL | let ub = &*ptr::null::(); + | ^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:27:19 + | +LL | let ub = &*ptr::null_mut::(); + | ^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:29:23 + | +LL | ptr::addr_of!(*ptr::null::()); + | ^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:31:27 + | +LL | ptr::addr_of_mut!(*ptr::null_mut::()); + | ^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: dereferencing a null pointer + --> $DIR/lint-deref-nullptr.rs:33:36 + | +LL | let offset = ptr::addr_of!((*ptr::null::()).field); + | ^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed + +error: aborting due to 10 previous errors +