Skip to content

Commit ea60ab1

Browse files
committed
Add a lint against never type fallback affecting unsafe code
1 parent 114d7d1 commit ea60ab1

File tree

8 files changed

+259
-11
lines changed

8 files changed

+259
-11
lines changed

compiler/rustc_data_structures/src/graph/vec_graph/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_index::{Idx, IndexVec};
44
#[cfg(test)]
55
mod tests;
66

7+
#[derive(Debug)]
78
pub struct VecGraph<N: Idx> {
89
/// Maps from a given node to an index where the set of successors
910
/// for that node starts. The index indexes into the `edges`

compiler/rustc_hir_typeck/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ hir_typeck_method_call_on_unknown_raw_pointee =
9898
9999
hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}`
100100
101+
hir_typeck_never_type_fallback_flowing_into_unsafe =
102+
never type fallback affects this call to an `unsafe` function
103+
.help = specify the type explicitly
104+
101105
hir_typeck_no_associated_item = no {$item_kind} named `{$item_name}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method ->
102106
[true] {""}
103107
*[other] {" "}in the current scope

compiler/rustc_hir_typeck/src/errors.rs

+5
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,8 @@ pub enum SuggestBoxingForReturnImplTrait {
639639
ends: Vec<Span>,
640640
},
641641
}
642+
643+
#[derive(LintDiagnostic)]
644+
#[diag(hir_typeck_never_type_fallback_flowing_into_unsafe)]
645+
#[help]
646+
pub struct NeverTypeFallbackFlowingIntoUnsafe {}

compiler/rustc_hir_typeck/src/fallback.rs

+144-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
use crate::FnCtxt;
1+
use std::cell::OnceCell;
2+
3+
use crate::{errors, FnCtxt};
24
use rustc_data_structures::{
35
graph::{self, iterate::DepthFirstSearch, vec_graph::VecGraph},
46
unord::{UnordBag, UnordMap, UnordSet},
57
};
8+
use rustc_hir::HirId;
69
use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
7-
use rustc_middle::ty::{self, Ty};
10+
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
11+
use rustc_session::lint;
12+
use rustc_span::Span;
813

914
#[derive(Copy, Clone)]
1015
pub enum DivergingFallbackBehavior {
@@ -251,7 +256,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
251256

252257
// Construct a coercion graph where an edge `A -> B` indicates
253258
// a type variable is that is coerced
254-
let coercion_graph = self.create_coercion_graph();
259+
let (coercion_graph, coercion_graph2) = self.create_coercion_graph();
255260

256261
// Extract the unsolved type inference variable vids; note that some
257262
// unsolved variables are integer/float variables and are excluded.
@@ -338,6 +343,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
338343
// reach a member of N. If so, it falls back to `()`. Else
339344
// `!`.
340345
let mut diverging_fallback = UnordMap::with_capacity(diverging_vids.len());
346+
let unsafe_infer_vars = OnceCell::new();
341347
for &diverging_vid in &diverging_vids {
342348
let diverging_ty = Ty::new_var(self.tcx, diverging_vid);
343349
let root_vid = self.root_var(diverging_vid);
@@ -357,11 +363,35 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
357363
output: infer_var_infos.items().any(|info| info.output),
358364
};
359365

366+
let mut fallback_to = |ty| {
367+
let unsafe_infer_vars = unsafe_infer_vars.get_or_init(|| {
368+
let unsafe_infer_vars = compute_unsafe_infer_vars(self.root_ctxt, self.body_id);
369+
debug!(?unsafe_infer_vars);
370+
unsafe_infer_vars
371+
});
372+
373+
let affected_unsafe_infer_vars =
374+
graph::depth_first_search(&coercion_graph2, root_vid)
375+
.filter_map(|x| unsafe_infer_vars.get(&x).copied())
376+
.collect::<Vec<_>>();
377+
378+
for (hir_id, span) in affected_unsafe_infer_vars {
379+
self.tcx.emit_node_span_lint(
380+
lint::builtin::NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
381+
hir_id,
382+
span,
383+
errors::NeverTypeFallbackFlowingIntoUnsafe {},
384+
);
385+
}
386+
387+
diverging_fallback.insert(diverging_ty, ty);
388+
};
389+
360390
use DivergingFallbackBehavior::*;
361391
match behavior {
362392
FallbackToUnit => {
363393
debug!("fallback to () - legacy: {:?}", diverging_vid);
364-
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
394+
fallback_to(self.tcx.types.unit);
365395
}
366396
FallbackToNiko => {
367397
if found_infer_var_info.self_in_trait && found_infer_var_info.output {
@@ -390,21 +420,21 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
390420
// set, see the relationship finding module in
391421
// compiler/rustc_trait_selection/src/traits/relationships.rs.
392422
debug!("fallback to () - found trait and projection: {:?}", diverging_vid);
393-
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
423+
fallback_to(self.tcx.types.unit);
394424
} else if can_reach_non_diverging {
395425
debug!("fallback to () - reached non-diverging: {:?}", diverging_vid);
396-
diverging_fallback.insert(diverging_ty, self.tcx.types.unit);
426+
fallback_to(self.tcx.types.unit);
397427
} else {
398428
debug!("fallback to ! - all diverging: {:?}", diverging_vid);
399-
diverging_fallback.insert(diverging_ty, self.tcx.types.never);
429+
fallback_to(self.tcx.types.never);
400430
}
401431
}
402432
FallbackToNever => {
403433
debug!(
404434
"fallback to ! - `rustc_never_type_mode = \"fallback_to_never\")`: {:?}",
405435
diverging_vid
406436
);
407-
diverging_fallback.insert(diverging_ty, self.tcx.types.never);
437+
fallback_to(self.tcx.types.never);
408438
}
409439
NoFallback => {
410440
debug!(
@@ -420,7 +450,9 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
420450

421451
/// Returns a graph whose nodes are (unresolved) inference variables and where
422452
/// an edge `?A -> ?B` indicates that the variable `?A` is coerced to `?B`.
423-
fn create_coercion_graph(&self) -> VecGraph<ty::TyVid> {
453+
///
454+
/// The second element of the return tuple is a graph with edges in both directions.
455+
fn create_coercion_graph(&self) -> (VecGraph<ty::TyVid>, VecGraph<ty::TyVid>) {
424456
let pending_obligations = self.fulfillment_cx.borrow_mut().pending_obligations();
425457
debug!("create_coercion_graph: pending_obligations={:?}", pending_obligations);
426458
let coercion_edges: Vec<(ty::TyVid, ty::TyVid)> = pending_obligations
@@ -454,11 +486,113 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
454486
.collect();
455487
debug!("create_coercion_graph: coercion_edges={:?}", coercion_edges);
456488
let num_ty_vars = self.num_ty_vars();
457-
VecGraph::new(num_ty_vars, coercion_edges)
489+
490+
// This essentially creates a non-directed graph.
491+
// Ideally we wouldn't do it like this, but it works ig :\
492+
let doubly_connected = VecGraph::new(
493+
num_ty_vars,
494+
coercion_edges
495+
.iter()
496+
.copied()
497+
.chain(coercion_edges.iter().copied().map(|(a, b)| (b, a)))
498+
.collect(),
499+
);
500+
501+
let normal = VecGraph::new(num_ty_vars, coercion_edges.clone());
502+
503+
(normal, doubly_connected)
458504
}
459505

460506
/// If `ty` is an unresolved type variable, returns its root vid.
461507
fn root_vid(&self, ty: Ty<'tcx>) -> Option<ty::TyVid> {
462508
Some(self.root_var(self.shallow_resolve(ty).ty_vid()?))
463509
}
464510
}
511+
512+
/// Finds all type variables which are passed to an `unsafe` function.
513+
///
514+
/// For example, for this function `f`:
515+
/// ```ignore (demonstrative)
516+
/// fn f() {
517+
/// unsafe {
518+
/// let x /* ?X */ = core::mem::zeroed();
519+
/// // ^^^^^^^^^^^^^^^^^^^ -- hir_id, span
520+
///
521+
/// let y = core::mem::zeroed::<Option<_ /* ?Y */>>();
522+
/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- hir_id, span
523+
/// }
524+
/// }
525+
/// ```
526+
///
527+
/// Will return `{ id(?X) -> (hir_id, span) }`
528+
fn compute_unsafe_infer_vars<'a, 'tcx>(
529+
root_ctxt: &'a crate::TypeckRootCtxt<'tcx>,
530+
body_id: rustc_span::def_id::LocalDefId,
531+
) -> UnordMap<ty::TyVid, (HirId, Span)> {
532+
use rustc_hir as hir;
533+
534+
let tcx = root_ctxt.infcx.tcx;
535+
let body_id = tcx.hir().maybe_body_owned_by(body_id).unwrap();
536+
let body = tcx.hir().body(body_id);
537+
let mut res = <_>::default();
538+
539+
struct UnsafeInferVarsVisitor<'a, 'tcx, 'r> {
540+
root_ctxt: &'a crate::TypeckRootCtxt<'tcx>,
541+
res: &'r mut UnordMap<ty::TyVid, (HirId, Span)>,
542+
}
543+
544+
use hir::intravisit::Visitor;
545+
impl hir::intravisit::Visitor<'_> for UnsafeInferVarsVisitor<'_, '_, '_> {
546+
fn visit_expr(&mut self, ex: &'_ hir::Expr<'_>) {
547+
// FIXME: method calls
548+
if let hir::ExprKind::Call(func, ..) = ex.kind {
549+
let typeck_results = self.root_ctxt.typeck_results.borrow();
550+
551+
let func_ty = typeck_results.expr_ty(func);
552+
553+
// `is_fn` is required to ignore closures (which can't be unsafe)
554+
if func_ty.is_fn()
555+
&& let sig = func_ty.fn_sig(self.root_ctxt.infcx.tcx)
556+
&& let hir::Unsafety::Unsafe = sig.unsafety()
557+
{
558+
let mut collector =
559+
InferVarCollector { hir_id: ex.hir_id, call_span: ex.span, res: self.res };
560+
561+
// Collect generic arguments of the function which are inference variables
562+
typeck_results
563+
.node_args(ex.hir_id)
564+
.types()
565+
.for_each(|t| t.visit_with(&mut collector));
566+
567+
// Also check the return type, for cases like `(unsafe_fn::<_> as unsafe fn() -> _)()`
568+
sig.output().visit_with(&mut collector);
569+
}
570+
}
571+
572+
hir::intravisit::walk_expr(self, ex);
573+
}
574+
}
575+
576+
struct InferVarCollector<'r> {
577+
hir_id: HirId,
578+
call_span: Span,
579+
res: &'r mut UnordMap<ty::TyVid, (HirId, Span)>,
580+
}
581+
582+
impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for InferVarCollector<'_> {
583+
fn visit_ty(&mut self, t: Ty<'tcx>) {
584+
if let Some(vid) = t.ty_vid() {
585+
self.res.insert(vid, (self.hir_id, self.call_span));
586+
} else {
587+
use ty::TypeSuperVisitable as _;
588+
t.super_visit_with(self)
589+
}
590+
}
591+
}
592+
593+
UnsafeInferVarsVisitor { root_ctxt, res: &mut res }.visit_expr(&body.value);
594+
595+
debug!(?res, "collected the following unsafe vars for {body_id:?}");
596+
597+
res
598+
}

compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ mod arg_matrix;
44
mod checks;
55
mod suggestions;
66

7+
use rustc_errors::ErrorGuaranteed;
8+
79
use crate::coercion::DynamicCoerceMany;
810
use crate::fallback::DivergingFallbackBehavior;
911
use crate::fn_ctxt::checks::DivergingBlockBehavior;
1012
use crate::{CoroutineTypes, Diverges, EnclosingBreakables, TypeckRootCtxt};
1113
use hir::def_id::CRATE_DEF_ID;
12-
use rustc_errors::{DiagCtxt, ErrorGuaranteed};
14+
use rustc_errors::DiagCtxt;
1315
use rustc_hir as hir;
1416
use rustc_hir::def_id::{DefId, LocalDefId};
1517
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;

compiler/rustc_lint_defs/src/builtin.rs

+44
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ declare_lint_pass! {
6868
MISSING_FRAGMENT_SPECIFIER,
6969
MUST_NOT_SUSPEND,
7070
NAMED_ARGUMENTS_USED_POSITIONALLY,
71+
NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
7172
NON_CONTIGUOUS_RANGE_ENDPOINTS,
7273
NON_EXHAUSTIVE_OMITTED_PATTERNS,
7374
ORDER_DEPENDENT_TRAIT_OBJECTS,
@@ -4179,6 +4180,49 @@ declare_lint! {
41794180
"named arguments in format used positionally"
41804181
}
41814182

4183+
declare_lint! {
4184+
/// The `never_type_fallback_flowing_into_unsafe` lint detects cases where never type fallback
4185+
/// affects unsafe function calls.
4186+
///
4187+
/// ### Example
4188+
///
4189+
/// ```rust,compile_fail
4190+
/// #![deny(never_type_fallback_flowing_into_unsafe)]
4191+
/// fn main() {
4192+
/// if true {
4193+
/// // return has type `!` (never) which, is some cases, causes never type fallback
4194+
/// return
4195+
/// } else {
4196+
/// // `zeroed` is an unsafe function, which returns an unbounded type
4197+
/// unsafe { std::mem::zeroed() }
4198+
/// };
4199+
/// // depending on the fallback, `zeroed` may create `()` (which is completely sound),
4200+
/// // or `!` (which is instant undefined behavior)
4201+
/// }
4202+
/// ```
4203+
///
4204+
/// {{produces}}
4205+
///
4206+
/// ### Explanation
4207+
///
4208+
/// Due to historic reasons never type fallback were `()`, meaning that `!` got spontaneously
4209+
/// coerced to `()`. There are plans to change that, but they may make the code such as above
4210+
/// unsound. Instead of depending on the fallback, you should specify the type explicitly:
4211+
/// ```
4212+
/// if true {
4213+
/// return
4214+
/// } else {
4215+
/// // type is explicitly specified, fallback can't hurt us no more
4216+
/// unsafe { std::mem::zeroed::<()>() }
4217+
/// };
4218+
/// ```
4219+
///
4220+
/// See [Tracking Issue for making `!` fall back to `!`](https://github.com/rust-lang/rust/issues/123748).
4221+
pub NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
4222+
Warn,
4223+
"never type fallback affecting unsafe function calls"
4224+
}
4225+
41824226
declare_lint! {
41834227
/// The `byte_slice_in_packed_struct_with_derive` lint detects cases where a byte slice field
41844228
/// (`[u8]`) or string slice field (`str`) is used in a `packed` struct that derives one or
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//@ check-pass
2+
use std::mem;
3+
4+
fn main() {
5+
if false {
6+
unsafe { mem::zeroed() }
7+
//~^ warn: never type fallback affects this call to an `unsafe` function
8+
} else {
9+
return;
10+
};
11+
12+
// no ; -> type is inferred without fallback
13+
if true { unsafe { mem::zeroed() } } else { return }
14+
}
15+
16+
// Minimization of the famous `objc` crate issue
17+
fn _objc() {
18+
pub unsafe fn send_message<R>() -> Result<R, ()> {
19+
Ok(unsafe { core::mem::zeroed() })
20+
}
21+
22+
macro_rules! msg_send {
23+
() => {
24+
match send_message::<_ /* ?0 */>() {
25+
//~^ warn: never type fallback affects this call to an `unsafe` function
26+
Ok(x) => x,
27+
Err(_) => loop {},
28+
}
29+
};
30+
}
31+
32+
unsafe {
33+
msg_send!();
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
warning: never type fallback affects this call to an `unsafe` function
2+
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:6:18
3+
|
4+
LL | unsafe { mem::zeroed() }
5+
| ^^^^^^^^^^^^^
6+
|
7+
= help: specify the type explicitly
8+
= note: `#[warn(never_type_fallback_flowing_into_unsafe)]` on by default
9+
10+
warning: never type fallback affects this call to an `unsafe` function
11+
--> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:24:19
12+
|
13+
LL | match send_message::<_ /* ?0 */>() {
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
...
16+
LL | msg_send!();
17+
| ----------- in this macro invocation
18+
|
19+
= help: specify the type explicitly
20+
= note: this warning originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info)
21+
22+
warning: 2 warnings emitted
23+

0 commit comments

Comments
 (0)