|
23 | 23 |
|
24 | 24 | use rustc::hir::def::{Res, DefKind};
|
25 | 25 | use rustc::hir::def_id::{DefId, LOCAL_CRATE};
|
26 |
| -use rustc::ty::{self, Ty, TyCtxt}; |
| 26 | +use rustc::ty::{self, Ty, TyCtxt, layout::VariantIdx}; |
27 | 27 | use rustc::{lint, util};
|
28 | 28 | use hir::Node;
|
29 | 29 | use util::nodemap::HirIdSet;
|
@@ -1862,3 +1862,92 @@ impl EarlyLintPass for IncompleteFeatures {
|
1862 | 1862 | });
|
1863 | 1863 | }
|
1864 | 1864 | }
|
| 1865 | + |
| 1866 | +declare_lint! { |
| 1867 | + pub INVALID_VALUE, |
| 1868 | + Warn, |
| 1869 | + "an invalid value is being created (such as a NULL reference)" |
| 1870 | +} |
| 1871 | + |
| 1872 | +declare_lint_pass!(InvalidValue => [INVALID_VALUE]); |
| 1873 | + |
| 1874 | +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue { |
| 1875 | + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &hir::Expr) { |
| 1876 | + |
| 1877 | + const ZEROED_PATH: &[Symbol] = &[sym::core, sym::mem, sym::zeroed]; |
| 1878 | + const UININIT_PATH: &[Symbol] = &[sym::core, sym::mem, sym::uninitialized]; |
| 1879 | + |
| 1880 | + /// Return `false` only if we are sure this type does *not* |
| 1881 | + /// allow zero initialization. |
| 1882 | + fn ty_maybe_allows_zero_init<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { |
| 1883 | + use rustc::ty::TyKind::*; |
| 1884 | + match ty.sty { |
| 1885 | + // Primitive types that don't like 0 as a value. |
| 1886 | + Ref(..) | FnPtr(..) | Never => false, |
| 1887 | + Adt(..) if ty.is_box() => false, |
| 1888 | + // Recurse for some compound types. |
| 1889 | + Adt(adt_def, substs) if !adt_def.is_union() => { |
| 1890 | + match adt_def.variants.len() { |
| 1891 | + 0 => false, // Uninhabited enum! |
| 1892 | + 1 => { |
| 1893 | + // Struct, or enum with exactly one variant. |
| 1894 | + // Proceed recursively, check all fields. |
| 1895 | + let variant = &adt_def.variants[VariantIdx::from_u32(0)]; |
| 1896 | + variant.fields.iter().all(|field| { |
| 1897 | + ty_maybe_allows_zero_init( |
| 1898 | + tcx, |
| 1899 | + field.ty(tcx, substs), |
| 1900 | + ) |
| 1901 | + }) |
| 1902 | + } |
| 1903 | + _ => true, // Conservative fallback for multi-variant enum. |
| 1904 | + } |
| 1905 | + } |
| 1906 | + Tuple(..) => { |
| 1907 | + // Proceed recursively, check all fields. |
| 1908 | + ty.tuple_fields().all(|field| ty_maybe_allows_zero_init(tcx, field)) |
| 1909 | + } |
| 1910 | + // FIXME: Would be nice to also warn for `NonNull`/`NonZero*`. |
| 1911 | + // FIXME: *Only for `mem::uninitialized`*, we could also warn for `bool`, |
| 1912 | + // `char`, and any multivariant enum. |
| 1913 | + // Conservative fallback. |
| 1914 | + _ => true, |
| 1915 | + } |
| 1916 | + } |
| 1917 | + |
| 1918 | + if let hir::ExprKind::Call(ref path_expr, ref _args) = expr.node { |
| 1919 | + if let hir::ExprKind::Path(ref qpath) = path_expr.node { |
| 1920 | + if let Some(def_id) = cx.tables.qpath_res(qpath, path_expr.hir_id).opt_def_id() { |
| 1921 | + if cx.match_def_path(def_id, &ZEROED_PATH) || |
| 1922 | + cx.match_def_path(def_id, &UININIT_PATH) |
| 1923 | + { |
| 1924 | + // This conjures an instance of a type out of nothing, |
| 1925 | + // using zeroed or uninitialized memory. |
| 1926 | + // We are extremely conservative with what we warn about. |
| 1927 | + let conjured_ty = cx.tables.expr_ty(expr); |
| 1928 | + |
| 1929 | + if !ty_maybe_allows_zero_init(cx.tcx, conjured_ty) { |
| 1930 | + cx.struct_span_lint( |
| 1931 | + INVALID_VALUE, |
| 1932 | + expr.span, |
| 1933 | + &format!( |
| 1934 | + "the type `{}` does not permit {}", |
| 1935 | + conjured_ty, |
| 1936 | + if cx.match_def_path(def_id, &ZEROED_PATH) { |
| 1937 | + "zero-initialization" |
| 1938 | + } else { |
| 1939 | + "being left uninitialized" |
| 1940 | + } |
| 1941 | + ), |
| 1942 | + ) |
| 1943 | + .note("this means that this code causes undefined behavior \ |
| 1944 | + when executed") |
| 1945 | + .help("use `MaybeUninit` instead") |
| 1946 | + .emit(); |
| 1947 | + } |
| 1948 | + } |
| 1949 | + } |
| 1950 | + } |
| 1951 | + } |
| 1952 | + } |
| 1953 | +} |
0 commit comments