mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-28 02:57:37 +00:00
Auto merge of #123939 - WaffleLapkin:never-fallback-unsafe-lint, r=compiler-errors
Add a lint against never type fallback affecting unsafe code ~~I'm not very happy with the code quality... `VecGraph` not allowing you to get predecessors is very annoying. This should work though, so there is that.~~ (ended up updating `VecGraph` to support getting predecessors) ~~First few commits are from https://github.com/rust-lang/rust/pull/123934 https://github.com/rust-lang/rust/pull/123980~~
This commit is contained in:
commit
fcc06c894b
@ -25,6 +25,7 @@
|
||||
#![feature(lazy_cell)]
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(macro_metavar_expr)]
|
||||
#![feature(map_try_insert)]
|
||||
#![feature(maybe_uninit_uninit_array)]
|
||||
#![feature(min_specialization)]
|
||||
#![feature(negative_impls)]
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustc_macros::{Decodable_Generic, Encodable_Generic};
|
||||
use std::collections::hash_map::OccupiedError;
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
collections::hash_map::Entry,
|
||||
@ -469,6 +470,11 @@ impl<K: Eq + Hash, V> UnordMap<K, V> {
|
||||
self.inner.insert(k, v)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_insert(&mut self, k: K, v: V) -> Result<&mut V, OccupiedError<'_, K, V>> {
|
||||
self.inner.try_insert(k, v)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
|
||||
where
|
||||
|
@ -99,6 +99,17 @@ hir_typeck_lossy_provenance_ptr2int =
|
||||
|
||||
hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}`
|
||||
|
||||
hir_typeck_never_type_fallback_flowing_into_unsafe_call = never type fallback affects this call to an `unsafe` function
|
||||
.help = specify the type explicitly
|
||||
hir_typeck_never_type_fallback_flowing_into_unsafe_deref = never type fallback affects this raw pointer dereference
|
||||
.help = specify the type explicitly
|
||||
hir_typeck_never_type_fallback_flowing_into_unsafe_method = never type fallback affects this call to an `unsafe` method
|
||||
.help = specify the type explicitly
|
||||
hir_typeck_never_type_fallback_flowing_into_unsafe_path = never type fallback affects this `unsafe` function
|
||||
.help = specify the type explicitly
|
||||
hir_typeck_never_type_fallback_flowing_into_unsafe_union_field = never type fallback affects this union access
|
||||
.help = specify the type explicitly
|
||||
|
||||
hir_typeck_no_associated_item = no {$item_kind} named `{$item_name}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method ->
|
||||
[true] {""}
|
||||
*[other] {" "}in the current scope
|
||||
|
@ -164,6 +164,25 @@ pub struct MissingParenthesesInRange {
|
||||
pub add_missing_parentheses: Option<AddMissingParenthesesInRange>,
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
pub enum NeverTypeFallbackFlowingIntoUnsafe {
|
||||
#[help]
|
||||
#[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_call)]
|
||||
Call,
|
||||
#[help]
|
||||
#[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_method)]
|
||||
Method,
|
||||
#[help]
|
||||
#[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_path)]
|
||||
Path,
|
||||
#[help]
|
||||
#[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_union_field)]
|
||||
UnionField,
|
||||
#[help]
|
||||
#[diag(hir_typeck_never_type_fallback_flowing_into_unsafe_deref)]
|
||||
Deref,
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
#[multipart_suggestion(
|
||||
hir_typeck_add_missing_parentheses_in_range,
|
||||
@ -632,7 +651,6 @@ pub enum SuggestBoxingForReturnImplTrait {
|
||||
ends: Vec<Span>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(LintDiagnostic)]
|
||||
#[diag(hir_typeck_dereferencing_mut_binding)]
|
||||
pub struct DereferencingMutBinding {
|
||||
|
@ -1,11 +1,18 @@
|
||||
use crate::FnCtxt;
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use crate::{errors, FnCtxt, TypeckRootCtxt};
|
||||
use rustc_data_structures::{
|
||||
graph::{self, iterate::DepthFirstSearch, vec_graph::VecGraph},
|
||||
unord::{UnordBag, UnordMap, UnordSet},
|
||||
};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::Visitor;
|
||||
use rustc_hir::HirId;
|
||||
use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
|
||||
use rustc_session::lint;
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_span::{def_id::LocalDefId, Span};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum DivergingFallbackBehavior {
|
||||
@ -335,6 +342,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
// reach a member of N. If so, it falls back to `()`. Else
|
||||
// `!`.
|
||||
let mut diverging_fallback = UnordMap::with_capacity(diverging_vids.len());
|
||||
let unsafe_infer_vars = OnceCell::new();
|
||||
for &diverging_vid in &diverging_vids {
|
||||
let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
|
||||
let root_vid = self.root_var(diverging_vid);
|
||||
@ -354,11 +362,51 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
output: infer_var_infos.items().any(|info| info.output),
|
||||
};
|
||||
|
||||
let mut fallback_to = |ty| {
|
||||
let unsafe_infer_vars = unsafe_infer_vars.get_or_init(|| {
|
||||
let unsafe_infer_vars = compute_unsafe_infer_vars(self.root_ctxt, self.body_id);
|
||||
debug!(?unsafe_infer_vars);
|
||||
unsafe_infer_vars
|
||||
});
|
||||
|
||||
let affected_unsafe_infer_vars =
|
||||
graph::depth_first_search_as_undirected(&coercion_graph, root_vid)
|
||||
.filter_map(|x| unsafe_infer_vars.get(&x).copied())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (hir_id, span, reason) in affected_unsafe_infer_vars {
|
||||
self.tcx.emit_node_span_lint(
|
||||
lint::builtin::NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
|
||||
hir_id,
|
||||
span,
|
||||
match reason {
|
||||
UnsafeUseReason::Call => {
|
||||
errors::NeverTypeFallbackFlowingIntoUnsafe::Call
|
||||
}
|
||||
UnsafeUseReason::Method => {
|
||||
errors::NeverTypeFallbackFlowingIntoUnsafe::Method
|
||||
}
|
||||
UnsafeUseReason::Path => {
|
||||
errors::NeverTypeFallbackFlowingIntoUnsafe::Path
|
||||
}
|
||||
UnsafeUseReason::UnionField => {
|
||||
errors::NeverTypeFallbackFlowingIntoUnsafe::UnionField
|
||||
}
|
||||
UnsafeUseReason::Deref => {
|
||||
errors::NeverTypeFallbackFlowingIntoUnsafe::Deref
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
diverging_fallback.insert(diverging_ty, ty);
|
||||
};
|
||||
|
||||
use DivergingFallbackBehavior::*;
|
||||
match behavior {
|
||||
FallbackToUnit => {
|
||||
debug!("fallback to () - legacy: {:?}", diverging_vid);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
|
||||
fallback_to(self.tcx.types.unit);
|
||||
}
|
||||
FallbackToNiko => {
|
||||
if found_infer_var_info.self_in_trait && found_infer_var_info.output {
|
||||
@ -387,13 +435,13 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
// set, see the relationship finding module in
|
||||
// compiler/rustc_trait_selection/src/traits/relationships.rs.
|
||||
debug!("fallback to () - found trait and projection: {:?}", diverging_vid);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
|
||||
fallback_to(self.tcx.types.unit);
|
||||
} else if can_reach_non_diverging {
|
||||
debug!("fallback to () - reached non-diverging: {:?}", diverging_vid);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
|
||||
fallback_to(self.tcx.types.unit);
|
||||
} else {
|
||||
debug!("fallback to ! - all diverging: {:?}", diverging_vid);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.types.never);
|
||||
fallback_to(self.tcx.types.never);
|
||||
}
|
||||
}
|
||||
FallbackToNever => {
|
||||
@ -401,7 +449,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
"fallback to ! - `rustc_never_type_mode = \"fallback_to_never\")`: {:?}",
|
||||
diverging_vid
|
||||
);
|
||||
diverging_fallback.insert(diverging_ty, self.tcx.types.never);
|
||||
fallback_to(self.tcx.types.never);
|
||||
}
|
||||
NoFallback => {
|
||||
debug!(
|
||||
@ -417,7 +465,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
|
||||
/// Returns a graph whose nodes are (unresolved) inference variables and where
|
||||
/// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
|
||||
fn create_coercion_graph(&self) -> VecGraph<ty::TyVid> {
|
||||
fn create_coercion_graph(&self) -> VecGraph<ty::TyVid, true> {
|
||||
let pending_obligations = self.fulfillment_cx.borrow_mut().pending_obligations();
|
||||
debug!("create_coercion_graph: pending_obligations={:?}", pending_obligations);
|
||||
let coercion_edges: Vec<(ty::TyVid, ty::TyVid)> = pending_obligations
|
||||
@ -436,17 +484,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
//
|
||||
// In practice currently the two ways that this happens is
|
||||
// coercion and subtyping.
|
||||
let (a, b) = if let ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) = atom {
|
||||
(a, b)
|
||||
} else if let ty::PredicateKind::Subtype(ty::SubtypePredicate {
|
||||
a_is_expected: _,
|
||||
a,
|
||||
b,
|
||||
}) = atom
|
||||
{
|
||||
(a, b)
|
||||
} else {
|
||||
return None;
|
||||
let (a, b) = match atom {
|
||||
ty::PredicateKind::Coerce(ty::CoercePredicate { a, b }) => (a, b),
|
||||
ty::PredicateKind::Subtype(ty::SubtypePredicate { a_is_expected: _, a, b }) => {
|
||||
(a, b)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let a_vid = self.root_vid(a)?;
|
||||
@ -456,6 +499,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
.collect();
|
||||
debug!("create_coercion_graph: coercion_edges={:?}", coercion_edges);
|
||||
let num_ty_vars = self.num_ty_vars();
|
||||
|
||||
VecGraph::new(num_ty_vars, coercion_edges)
|
||||
}
|
||||
|
||||
@ -464,3 +508,166 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
|
||||
Some(self.root_var(self.shallow_resolve(ty).ty_vid()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum UnsafeUseReason {
|
||||
Call,
|
||||
Method,
|
||||
Path,
|
||||
UnionField,
|
||||
Deref,
|
||||
}
|
||||
|
||||
/// Finds all type variables which are passed to an `unsafe` operation.
|
||||
///
|
||||
/// For example, for this function `f`:
|
||||
/// ```ignore (demonstrative)
|
||||
/// fn f() {
|
||||
/// unsafe {
|
||||
/// let x /* ?X */ = core::mem::zeroed();
|
||||
/// // ^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
|
||||
///
|
||||
/// let y = core::mem::zeroed::<Option<_ /* ?Y */>>();
|
||||
/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// `compute_unsafe_infer_vars` will return `{ id(?X) -> (hir_id, span, Call) }`
|
||||
fn compute_unsafe_infer_vars<'a, 'tcx>(
|
||||
root_ctxt: &'a TypeckRootCtxt<'tcx>,
|
||||
body_id: LocalDefId,
|
||||
) -> UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)> {
|
||||
let body_id =
|
||||
root_ctxt.tcx.hir().maybe_body_owned_by(body_id).expect("body id must have an owner");
|
||||
let body = root_ctxt.tcx.hir().body(body_id);
|
||||
let mut res = UnordMap::default();
|
||||
|
||||
struct UnsafeInferVarsVisitor<'a, 'tcx, 'r> {
|
||||
root_ctxt: &'a TypeckRootCtxt<'tcx>,
|
||||
res: &'r mut UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)>,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for UnsafeInferVarsVisitor<'_, '_, '_> {
|
||||
fn visit_expr(&mut self, ex: &'_ hir::Expr<'_>) {
|
||||
let typeck_results = self.root_ctxt.typeck_results.borrow();
|
||||
|
||||
match ex.kind {
|
||||
hir::ExprKind::MethodCall(..) => {
|
||||
if let Some(def_id) = typeck_results.type_dependent_def_id(ex.hir_id)
|
||||
&& let method_ty = self.root_ctxt.tcx.type_of(def_id).instantiate_identity()
|
||||
&& let sig = method_ty.fn_sig(self.root_ctxt.tcx)
|
||||
&& let hir::Unsafety::Unsafe = sig.unsafety()
|
||||
{
|
||||
let mut collector = InferVarCollector {
|
||||
value: (ex.hir_id, ex.span, UnsafeUseReason::Method),
|
||||
res: self.res,
|
||||
};
|
||||
|
||||
// Collect generic arguments (incl. `Self`) of the method
|
||||
typeck_results
|
||||
.node_args(ex.hir_id)
|
||||
.types()
|
||||
.for_each(|t| t.visit_with(&mut collector));
|
||||
}
|
||||
}
|
||||
|
||||
hir::ExprKind::Call(func, ..) => {
|
||||
let func_ty = typeck_results.expr_ty(func);
|
||||
|
||||
if func_ty.is_fn()
|
||||
&& let sig = func_ty.fn_sig(self.root_ctxt.tcx)
|
||||
&& let hir::Unsafety::Unsafe = sig.unsafety()
|
||||
{
|
||||
let mut collector = InferVarCollector {
|
||||
value: (ex.hir_id, ex.span, UnsafeUseReason::Call),
|
||||
res: self.res,
|
||||
};
|
||||
|
||||
// Try collecting generic arguments of the function.
|
||||
// Note that we do this below for any paths (that don't have to be called),
|
||||
// but there we do it with a different span/reason.
|
||||
// This takes priority.
|
||||
typeck_results
|
||||
.node_args(func.hir_id)
|
||||
.types()
|
||||
.for_each(|t| t.visit_with(&mut collector));
|
||||
|
||||
// Also check the return type, for cases like `returns_unsafe_fn_ptr()()`
|
||||
sig.output().visit_with(&mut collector);
|
||||
}
|
||||
}
|
||||
|
||||
// Check paths which refer to functions.
|
||||
// We do this, instead of only checking `Call` to make sure the lint can't be
|
||||
// avoided by storing unsafe function in a variable.
|
||||
hir::ExprKind::Path(_) => {
|
||||
let ty = typeck_results.expr_ty(ex);
|
||||
|
||||
// If this path refers to an unsafe function, collect inference variables which may affect it.
|
||||
// `is_fn` excludes closures, but those can't be unsafe.
|
||||
if ty.is_fn()
|
||||
&& let sig = ty.fn_sig(self.root_ctxt.tcx)
|
||||
&& let hir::Unsafety::Unsafe = sig.unsafety()
|
||||
{
|
||||
let mut collector = InferVarCollector {
|
||||
value: (ex.hir_id, ex.span, UnsafeUseReason::Path),
|
||||
res: self.res,
|
||||
};
|
||||
|
||||
// Collect generic arguments of the function
|
||||
typeck_results
|
||||
.node_args(ex.hir_id)
|
||||
.types()
|
||||
.for_each(|t| t.visit_with(&mut collector));
|
||||
}
|
||||
}
|
||||
|
||||
hir::ExprKind::Unary(hir::UnOp::Deref, pointer) => {
|
||||
if let ty::RawPtr(pointee, _) = typeck_results.expr_ty(pointer).kind() {
|
||||
pointee.visit_with(&mut InferVarCollector {
|
||||
value: (ex.hir_id, ex.span, UnsafeUseReason::Deref),
|
||||
res: self.res,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
hir::ExprKind::Field(base, _) => {
|
||||
let base_ty = typeck_results.expr_ty(base);
|
||||
|
||||
if base_ty.is_union() {
|
||||
typeck_results.expr_ty(ex).visit_with(&mut InferVarCollector {
|
||||
value: (ex.hir_id, ex.span, UnsafeUseReason::UnionField),
|
||||
res: self.res,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_ => (),
|
||||
};
|
||||
|
||||
hir::intravisit::walk_expr(self, ex);
|
||||
}
|
||||
}
|
||||
|
||||
struct InferVarCollector<'r, V> {
|
||||
value: V,
|
||||
res: &'r mut UnordMap<ty::TyVid, V>,
|
||||
}
|
||||
|
||||
impl<'tcx, V: Copy> ty::TypeVisitor<TyCtxt<'tcx>> for InferVarCollector<'_, V> {
|
||||
fn visit_ty(&mut self, t: Ty<'tcx>) {
|
||||
if let Some(vid) = t.ty_vid() {
|
||||
_ = self.res.try_insert(vid, self.value);
|
||||
} else {
|
||||
t.super_visit_with(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnsafeInferVarsVisitor { root_ctxt, res: &mut res }.visit_expr(&body.value);
|
||||
|
||||
debug!(?res, "collected the following unsafe vars for {body_id:?}");
|
||||
|
||||
res
|
||||
}
|
||||
|
@ -5,12 +5,14 @@ mod checks;
|
||||
mod inspect_obligations;
|
||||
mod suggestions;
|
||||
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
|
||||
use crate::coercion::DynamicCoerceMany;
|
||||
use crate::fallback::DivergingFallbackBehavior;
|
||||
use crate::fn_ctxt::checks::DivergingBlockBehavior;
|
||||
use crate::{CoroutineTypes, Diverges, EnclosingBreakables, TypeckRootCtxt};
|
||||
use hir::def_id::CRATE_DEF_ID;
|
||||
use rustc_errors::{DiagCtxt, ErrorGuaranteed};
|
||||
use rustc_errors::DiagCtxt;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
|
||||
|
@ -69,6 +69,7 @@ declare_lint_pass! {
|
||||
MISSING_FRAGMENT_SPECIFIER,
|
||||
MUST_NOT_SUSPEND,
|
||||
NAMED_ARGUMENTS_USED_POSITIONALLY,
|
||||
NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
|
||||
NON_CONTIGUOUS_RANGE_ENDPOINTS,
|
||||
NON_EXHAUSTIVE_OMITTED_PATTERNS,
|
||||
ORDER_DEPENDENT_TRAIT_OBJECTS,
|
||||
@ -4245,6 +4246,85 @@ declare_lint! {
|
||||
"named arguments in format used positionally"
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `never_type_fallback_flowing_into_unsafe` lint detects cases where never type fallback
|
||||
/// affects unsafe function calls.
|
||||
///
|
||||
/// ### Never type fallback
|
||||
///
|
||||
/// When the compiler sees a value of type [`!`] it implicitly inserts a coercion (if possible),
|
||||
/// to allow type check to infer any type:
|
||||
///
|
||||
/// ```ignore (illustrative-and-has-placeholders)
|
||||
/// // this
|
||||
/// let x: u8 = panic!();
|
||||
///
|
||||
/// // is (essentially) turned by the compiler into
|
||||
/// let x: u8 = absurd(panic!());
|
||||
///
|
||||
/// // where absurd is a function with the following signature
|
||||
/// // (it's sound, because `!` always marks unreachable code):
|
||||
/// fn absurd<T>(_: !) -> T { ... }
|
||||
// FIXME: use `core::convert::absurd` here instead, once it's merged
|
||||
/// ```
|
||||
///
|
||||
/// While it's convenient to be able to use non-diverging code in one of the branches (like
|
||||
/// `if a { b } else { return }`) this could lead to compilation errors:
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// // this
|
||||
/// { panic!() };
|
||||
///
|
||||
/// // gets turned into this
|
||||
/// { absurd(panic!()) }; // error: can't infer the type of `absurd`
|
||||
/// ```
|
||||
///
|
||||
/// To prevent such errors, compiler remembers where it inserted `absurd` calls, and if it
|
||||
/// can't infer their type, it sets the type to fallback. `{ absurd::<Fallback>(panic!()) };`.
|
||||
/// This is what is known as "never type fallback".
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust,compile_fail
|
||||
/// #![deny(never_type_fallback_flowing_into_unsafe)]
|
||||
/// fn main() {
|
||||
/// if true {
|
||||
/// // return has type `!` which, is some cases, causes never type fallback
|
||||
/// return
|
||||
/// } else {
|
||||
/// // `zeroed` is an unsafe function, which returns an unbounded type
|
||||
/// unsafe { std::mem::zeroed() }
|
||||
/// };
|
||||
/// // depending on the fallback, `zeroed` may create `()` (which is completely sound),
|
||||
/// // or `!` (which is instant undefined behavior)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// {{produces}}
|
||||
///
|
||||
/// ### Explanation
|
||||
///
|
||||
/// Due to historic reasons never type fallback was `()`, meaning that `!` got spontaneously
|
||||
/// coerced to `()`. There are plans to change that, but they may make the code such as above
|
||||
/// unsound. Instead of depending on the fallback, you should specify the type explicitly:
|
||||
/// ```
|
||||
/// if true {
|
||||
/// return
|
||||
/// } else {
|
||||
/// // type is explicitly specified, fallback can't hurt us no more
|
||||
/// unsafe { std::mem::zeroed::<()>() }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// See [Tracking Issue for making `!` fall back to `!`](https://github.com/rust-lang/rust/issues/123748).
|
||||
///
|
||||
/// [`!`]: https://doc.rust-lang.org/core/primitive.never.html
|
||||
/// [`()`]: https://doc.rust-lang.org/core/primitive.unit.html
|
||||
pub NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
|
||||
Warn,
|
||||
"never type fallback affecting unsafe function calls"
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// The `byte_slice_in_packed_struct_with_derive` lint detects cases where a byte slice field
|
||||
/// (`[u8]`) or string slice field (`str`) is used in a `packed` struct that derives one or
|
||||
|
@ -0,0 +1,141 @@
|
||||
//@ check-pass
|
||||
use std::{marker, mem, ptr};
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn _zero() {
|
||||
if false {
|
||||
unsafe { mem::zeroed() }
|
||||
//~^ warn: never type fallback affects this call to an `unsafe` function
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// no ; -> type is inferred without fallback
|
||||
if true { unsafe { mem::zeroed() } } else { return }
|
||||
}
|
||||
|
||||
fn _trans() {
|
||||
if false {
|
||||
unsafe {
|
||||
struct Zst;
|
||||
core::mem::transmute(Zst)
|
||||
//~^ warn: never type fallback affects this call to an `unsafe` function
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn _union() {
|
||||
if false {
|
||||
union Union<T: Copy> {
|
||||
a: (),
|
||||
b: T,
|
||||
}
|
||||
|
||||
unsafe { Union { a: () }.b }
|
||||
//~^ warn: never type fallback affects this union access
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn _deref() {
|
||||
if false {
|
||||
unsafe { *ptr::from_ref(&()).cast() }
|
||||
//~^ warn: never type fallback affects this raw pointer dereference
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn _only_generics() {
|
||||
if false {
|
||||
unsafe fn internally_create<T>(_: Option<T>) {
|
||||
let _ = mem::zeroed::<T>();
|
||||
}
|
||||
|
||||
// We need the option (and unwrap later) to call a function in a way,
|
||||
// which makes it affected by the fallback, but without having it return anything
|
||||
let x = None;
|
||||
|
||||
unsafe { internally_create(x) }
|
||||
//~^ warn: never type fallback affects this call to an `unsafe` function
|
||||
|
||||
x.unwrap()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn _stored_function() {
|
||||
if false {
|
||||
let zeroed = mem::zeroed;
|
||||
//~^ warn: never type fallback affects this `unsafe` function
|
||||
|
||||
unsafe { zeroed() }
|
||||
//~^ warn: never type fallback affects this call to an `unsafe` function
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn _only_generics_stored_function() {
|
||||
if false {
|
||||
unsafe fn internally_create<T>(_: Option<T>) {
|
||||
let _ = mem::zeroed::<T>();
|
||||
}
|
||||
|
||||
let x = None;
|
||||
let f = internally_create;
|
||||
//~^ warn: never type fallback affects this `unsafe` function
|
||||
|
||||
unsafe { f(x) }
|
||||
|
||||
x.unwrap()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
fn _method() {
|
||||
struct S<T>(marker::PhantomData<T>);
|
||||
|
||||
impl<T> S<T> {
|
||||
#[allow(unused)] // FIXME: the unused lint is probably incorrect here
|
||||
unsafe fn create_out_of_thin_air(&self) -> T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
if false {
|
||||
unsafe {
|
||||
S(marker::PhantomData).create_out_of_thin_air()
|
||||
//~^ warn: never type fallback affects this call to an `unsafe` method
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
// Minimization of the famous `objc` crate issue
|
||||
fn _objc() {
|
||||
pub unsafe fn send_message<R>() -> Result<R, ()> {
|
||||
Ok(unsafe { core::mem::zeroed() })
|
||||
}
|
||||
|
||||
macro_rules! msg_send {
|
||||
() => {
|
||||
match send_message::<_ /* ?0 */>() {
|
||||
//~^ warn: never type fallback affects this call to an `unsafe` function
|
||||
Ok(x) => x,
|
||||
Err(_) => loop {},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unsafe {
|
||||
msg_send!();
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
warning: never type fallback affects this call to an `unsafe` function
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:8:18
|
||||
|
|
||||
LL | unsafe { mem::zeroed() }
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
= note: `#[warn(never_type_fallback_flowing_into_unsafe)]` on by default
|
||||
|
||||
warning: never type fallback affects this call to an `unsafe` function
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:22:13
|
||||
|
|
||||
LL | core::mem::transmute(Zst)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this union access
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:37:18
|
||||
|
|
||||
LL | unsafe { Union { a: () }.b }
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this raw pointer dereference
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:46:18
|
||||
|
|
||||
LL | unsafe { *ptr::from_ref(&()).cast() }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this call to an `unsafe` function
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:63:18
|
||||
|
|
||||
LL | unsafe { internally_create(x) }
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this call to an `unsafe` function
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:77:18
|
||||
|
|
||||
LL | unsafe { zeroed() }
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this `unsafe` function
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:74:22
|
||||
|
|
||||
LL | let zeroed = mem::zeroed;
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this `unsafe` function
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:91:17
|
||||
|
|
||||
LL | let f = internally_create;
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this call to an `unsafe` method
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:114:13
|
||||
|
|
||||
LL | S(marker::PhantomData).create_out_of_thin_air()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
|
||||
warning: never type fallback affects this call to an `unsafe` function
|
||||
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:130:19
|
||||
|
|
||||
LL | match send_message::<_ /* ?0 */>() {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
LL | msg_send!();
|
||||
| ----------- in this macro invocation
|
||||
|
|
||||
= help: specify the type explicitly
|
||||
= note: this warning originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
warning: 10 warnings emitted
|
||||
|
Loading…
Reference in New Issue
Block a user