about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-04-14 18:04:22 +0000
committerbors <bors@rust-lang.org>2021-04-14 18:04:22 +0000
commit7537b20626100e7e7fc8c4ad3079d38c05338121 (patch)
tree6a89a9ed2076f0e1530b422024dc4651ccb6dfa7
parent07ef25984549bc33adbdc402e977655d8c9093a7 (diff)
parent7f0f83a26fdec4caa2a8512f9fc611d504b7aad2 (diff)
downloadrust-7537b20626100e7e7fc8c4ad3079d38c05338121.tar.gz
rust-7537b20626100e7e7fc8c4ad3079d38c05338121.zip
Auto merge of #83948 - ABouttefeux:lint-nullprt-deref, r=RalfJung
add lint deref_nullptr detecting when a null ptr is dereferenced

fixes #83856
changelog: add lint that detect code like
```rust
unsafe {
      &*core::ptr::null::<i32>()
 };
unsafe {
     addr_of!(std::ptr::null::<i32>())
};
let x: i32 = unsafe {*core::ptr::null()};
let x: i32 = unsafe {*core::ptr::null_mut()};
unsafe {*(0 as *const i32)};
unsafe {*(core::ptr::null() as *const i32)};
```
```
warning: Dereferencing a null pointer causes undefined behavior
 --> src\main.rs:5:26
  |
5 |     let x: i32 = unsafe {*core::ptr::null()};
  |                          ^^^^^^^^^^^^^^^^^^
  |                          |
  |                          a null pointer is dereferenced
  |                          this code causes undefined behavior when executed
  |
  = note: `#[warn(deref_nullptr)]` on by default
```

Limitation:
It does not detect code like
```rust
const ZERO: usize = 0;
unsafe {*(ZERO as *const i32)};
```
or code where `0` is not directly a literal
-rw-r--r--compiler/rustc_lint/src/builtin.rs87
-rw-r--r--compiler/rustc_lint/src/lib.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs2
-rw-r--r--library/core/src/ptr/mod.rs2
-rw-r--r--src/test/ui/cleanup-shortcircuit.rs3
-rw-r--r--src/test/ui/lint/lint-deref-nullptr.rs38
-rw-r--r--src/test/ui/lint/lint-deref-nullptr.stderr68
7 files changed, 201 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
index c4acaef21dd..7f9e459635a 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
@@ -2964,3 +2966,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::<i32>();
+    ///     let x = ptr::addr_of!(*ptr::null::<i32>());
+    ///     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 e2724b52453..2f46969b021 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 ee1d206095e..42e521a20a3 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 6e207156b55..ad8696ab927 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<T: ?Sized>(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<T>() -> *const T {
     0 as *const T
 }
@@ -229,6 +230,7 @@ pub const fn null<T>() -> *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<T>() -> *mut T {
     0 as *mut T
 }
diff --git a/src/test/ui/cleanup-shortcircuit.rs b/src/test/ui/cleanup-shortcircuit.rs
index 4f5197a5ba9..fe867ce1fbd 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 00000000000..d052dbd9b64
--- /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::<i32>();
+        //~^ ERROR dereferencing a null pointer
+        let ub = *ptr::null_mut::<i32>();
+        //~^ ERROR dereferencing a null pointer
+        let ub = *(ptr::null::<i16>() as *const i32);
+        //~^ ERROR dereferencing a null pointer
+        let ub = *(ptr::null::<i16>() as *mut i32 as *mut usize as *const u8);
+        //~^ ERROR dereferencing a null pointer
+        let ub = &*ptr::null::<i32>();
+        //~^ ERROR dereferencing a null pointer
+        let ub = &*ptr::null_mut::<i32>();
+        //~^ ERROR dereferencing a null pointer
+        ptr::addr_of!(*ptr::null::<i32>());
+        //~^ ERROR dereferencing a null pointer
+        ptr::addr_of_mut!(*ptr::null_mut::<i32>());
+        //~^ ERROR dereferencing a null pointer
+        let offset = ptr::addr_of!((*ptr::null::<Struct>()).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 00000000000..c6f432e4e42
--- /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::<i32>();
+   |                  ^^^^^^^^^^^^^^^^^^^ 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::<i32>();
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed
+
+error: dereferencing a null pointer
+  --> $DIR/lint-deref-nullptr.rs:21:18
+   |
+LL |         let ub = *(ptr::null::<i16>() 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::<i16>() 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::<i32>();
+   |                   ^^^^^^^^^^^^^^^^^^^ 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::<i32>();
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^ 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::<i32>());
+   |                       ^^^^^^^^^^^^^^^^^^^ 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::<i32>());
+   |                           ^^^^^^^^^^^^^^^^^^^^^^^ 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::<Struct>()).field);
+   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed
+
+error: aborting due to 10 previous errors
+