Skip to content

Commit 0df43db

Browse files
committed
Add proper support for all kinds of unsafe ops to the lint
(never_type_fallback_flowing_into_unsafe)
1 parent 3c815a6 commit 0df43db

File tree

5 files changed

+321
-49
lines changed

5 files changed

+321
-49
lines changed

compiler/rustc_hir_typeck/messages.ftl

+8-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,14 @@ hir_typeck_lossy_provenance_ptr2int =
100100
hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}`
101101
102102
hir_typeck_never_type_fallback_flowing_into_unsafe =
103-
never type fallback affects this call to an `unsafe` function
103+
never type fallback affects this {$reason ->
104+
[call] call to an `unsafe` function
105+
[union_field] union access
106+
[deref] raw pointer dereference
107+
[path] `unsafe` function
108+
[method] call to an `unsafe` method
109+
*[other] THIS SHOULD NOT BE REACHABLE
110+
}
104111
.help = specify the type explicitly
105112
106113
hir_typeck_no_associated_item = no {$item_kind} named `{$item_name}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method ->

compiler/rustc_hir_typeck/src/errors.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Errors emitted by `rustc_hir_typeck`.
22
use std::borrow::Cow;
33

4-
use crate::fluent_generated as fluent;
4+
use crate::{fallback::UnsafeUseReason, fluent_generated as fluent};
55
use rustc_errors::{
66
codes::*, Applicability, Diag, DiagArgValue, EmissionGuarantee, IntoDiagArg, MultiSpan,
77
SubdiagMessageOp, Subdiagnostic,
@@ -167,7 +167,9 @@ pub struct MissingParenthesesInRange {
167167
#[derive(LintDiagnostic)]
168168
#[diag(hir_typeck_never_type_fallback_flowing_into_unsafe)]
169169
#[help]
170-
pub struct NeverTypeFallbackFlowingIntoUnsafe {}
170+
pub struct NeverTypeFallbackFlowingIntoUnsafe {
171+
pub reason: UnsafeUseReason,
172+
}
171173

172174
#[derive(Subdiagnostic)]
173175
#[multipart_suggestion(

compiler/rustc_hir_typeck/src/fallback.rs

+134-41
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use std::cell::OnceCell;
1+
use std::{borrow::Cow, cell::OnceCell};
22

33
use crate::{errors, FnCtxt, TypeckRootCtxt};
44
use rustc_data_structures::{
55
graph::{self, iterate::DepthFirstSearch, vec_graph::VecGraph},
66
unord::{UnordBag, UnordMap, UnordSet},
77
};
8+
use rustc_errors::{DiagArgValue, IntoDiagArg};
89
use rustc_hir as hir;
910
use rustc_hir::intravisit::Visitor;
1011
use rustc_hir::HirId;
@@ -374,12 +375,12 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
374375
.filter_map(|x| unsafe_infer_vars.get(&x).copied())
375376
.collect::<Vec<_>>();
376377

377-
for (hir_id, span) in affected_unsafe_infer_vars {
378+
for (hir_id, span, reason) in affected_unsafe_infer_vars {
378379
self.tcx.emit_node_span_lint(
379380
lint::builtin::NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
380381
hir_id,
381382
span,
382-
errors::NeverTypeFallbackFlowingIntoUnsafe {},
383+
errors::NeverTypeFallbackFlowingIntoUnsafe { reason },
383384
);
384385
}
385386

@@ -493,77 +494,169 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
493494
}
494495
}
495496

496-
/// Finds all type variables which are passed to an `unsafe` function.
497+
#[derive(Debug, Copy, Clone)]
498+
pub(crate) enum UnsafeUseReason {
499+
Call,
500+
Method,
501+
Path,
502+
UnionField,
503+
Deref,
504+
}
505+
506+
impl IntoDiagArg for UnsafeUseReason {
507+
fn into_diag_arg(self) -> DiagArgValue {
508+
let s = match self {
509+
UnsafeUseReason::Call => "call",
510+
UnsafeUseReason::Method => "method",
511+
UnsafeUseReason::Path => "path",
512+
UnsafeUseReason::UnionField => "union_field",
513+
UnsafeUseReason::Deref => "deref",
514+
};
515+
DiagArgValue::Str(Cow::Borrowed(s))
516+
}
517+
}
518+
519+
/// Finds all type variables which are passed to an `unsafe` operation.
497520
///
498521
/// For example, for this function `f`:
499522
/// ```ignore (demonstrative)
500523
/// fn f() {
501524
/// unsafe {
502525
/// let x /* ?X */ = core::mem::zeroed();
503-
/// // ^^^^^^^^^^^^^^^^^^^ -- hir_id, span
526+
/// // ^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
504527
///
505528
/// let y = core::mem::zeroed::<Option<_ /* ?Y */>>();
506-
/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- hir_id, span
529+
/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- hir_id, span, reason
507530
/// }
508531
/// }
509532
/// ```
510533
///
511-
/// Will return `{ id(?X) -> (hir_id, span) }`
534+
/// `compute_unsafe_infer_vars` will return `{ id(?X) -> (hir_id, span, Call) }`
512535
fn compute_unsafe_infer_vars<'a, 'tcx>(
513536
root_ctxt: &'a TypeckRootCtxt<'tcx>,
514537
body_id: LocalDefId,
515-
) -> UnordMap<ty::TyVid, (HirId, Span)> {
516-
let tcx = root_ctxt.infcx.tcx;
517-
let body_id = tcx.hir().maybe_body_owned_by(body_id).expect("body id must have an owner");
518-
let body = tcx.hir().body(body_id);
538+
) -> UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)> {
539+
let body_id =
540+
root_ctxt.tcx.hir().maybe_body_owned_by(body_id).expect("body id must have an owner");
541+
let body = root_ctxt.tcx.hir().body(body_id);
519542
let mut res = UnordMap::default();
520543

521544
struct UnsafeInferVarsVisitor<'a, 'tcx, 'r> {
522545
root_ctxt: &'a TypeckRootCtxt<'tcx>,
523-
res: &'r mut UnordMap<ty::TyVid, (HirId, Span)>,
546+
res: &'r mut UnordMap<ty::TyVid, (HirId, Span, UnsafeUseReason)>,
524547
}
525548

526549
impl Visitor<'_> for UnsafeInferVarsVisitor<'_, '_, '_> {
527550
fn visit_expr(&mut self, ex: &'_ hir::Expr<'_>) {
528-
// FIXME: method calls
529-
if let hir::ExprKind::Call(func, ..) = ex.kind {
530-
let typeck_results = self.root_ctxt.typeck_results.borrow();
531-
532-
let func_ty = typeck_results.expr_ty(func);
533-
534-
// `is_fn` is required to ignore closures (which can't be unsafe)
535-
if func_ty.is_fn()
536-
&& let sig = func_ty.fn_sig(self.root_ctxt.infcx.tcx)
537-
&& let hir::Unsafety::Unsafe = sig.unsafety()
538-
{
539-
let mut collector =
540-
InferVarCollector { hir_id: ex.hir_id, call_span: ex.span, res: self.res };
541-
542-
// Collect generic arguments of the function which are inference variables
543-
typeck_results
544-
.node_args(ex.hir_id)
545-
.types()
546-
.for_each(|t| t.visit_with(&mut collector));
547-
548-
// Also check the return type, for cases like `(unsafe_fn::<_> as unsafe fn() -> _)()`
549-
sig.output().visit_with(&mut collector);
551+
let typeck_results = self.root_ctxt.typeck_results.borrow();
552+
553+
match ex.kind {
554+
hir::ExprKind::MethodCall(..) => {
555+
if let Some(def_id) = typeck_results.type_dependent_def_id(ex.hir_id)
556+
&& let method_ty = self.root_ctxt.tcx.type_of(def_id).instantiate_identity()
557+
&& let sig = method_ty.fn_sig(self.root_ctxt.tcx)
558+
&& let hir::Unsafety::Unsafe = sig.unsafety()
559+
{
560+
let mut collector = InferVarCollector {
561+
value: (ex.hir_id, ex.span, UnsafeUseReason::Method),
562+
res: self.res,
563+
};
564+
565+
// Collect generic arguments (incl. `Self`) of the method
566+
typeck_results
567+
.node_args(ex.hir_id)
568+
.types()
569+
.for_each(|t| t.visit_with(&mut collector));
570+
}
550571
}
551-
}
572+
573+
hir::ExprKind::Call(func, ..) => {
574+
let func_ty = typeck_results.expr_ty(func);
575+
576+
if func_ty.is_fn()
577+
&& let sig = func_ty.fn_sig(self.root_ctxt.tcx)
578+
&& let hir::Unsafety::Unsafe = sig.unsafety()
579+
{
580+
let mut collector = InferVarCollector {
581+
value: (ex.hir_id, ex.span, UnsafeUseReason::Call),
582+
res: self.res,
583+
};
584+
585+
// Try collecting generic arguments of the function.
586+
// Note that we do this below for any paths (that don't have to be called),
587+
// but there we do it with a different span/reason.
588+
// This takes priority.
589+
typeck_results
590+
.node_args(func.hir_id)
591+
.types()
592+
.for_each(|t| t.visit_with(&mut collector));
593+
594+
// Also check the return type, for cases like `returns_unsafe_fn_ptr()()`
595+
sig.output().visit_with(&mut collector);
596+
}
597+
}
598+
599+
// Check paths which refer to functions.
600+
// We do this, instead of only checking `Call` to make sure the lint can't be
601+
// avoided by storing unsafe function in a variable.
602+
hir::ExprKind::Path(_) => {
603+
let ty = typeck_results.expr_ty(ex);
604+
605+
// If this path refers to an unsafe function, collect inference variables which may affect it.
606+
// `is_fn` excludes closures, but those can't be unsafe.
607+
if ty.is_fn()
608+
&& let sig = ty.fn_sig(self.root_ctxt.tcx)
609+
&& let hir::Unsafety::Unsafe = sig.unsafety()
610+
{
611+
let mut collector = InferVarCollector {
612+
value: (ex.hir_id, ex.span, UnsafeUseReason::Path),
613+
res: self.res,
614+
};
615+
616+
// Collect generic arguments of the function
617+
typeck_results
618+
.node_args(ex.hir_id)
619+
.types()
620+
.for_each(|t| t.visit_with(&mut collector));
621+
}
622+
}
623+
624+
hir::ExprKind::Unary(hir::UnOp::Deref, pointer) => {
625+
if let ty::RawPtr(pointee, _) = typeck_results.expr_ty(pointer).kind() {
626+
pointee.visit_with(&mut InferVarCollector {
627+
value: (ex.hir_id, ex.span, UnsafeUseReason::Deref),
628+
res: self.res,
629+
});
630+
}
631+
}
632+
633+
hir::ExprKind::Field(base, _) => {
634+
let base_ty = typeck_results.expr_ty(base);
635+
636+
if base_ty.is_union() {
637+
typeck_results.expr_ty(ex).visit_with(&mut InferVarCollector {
638+
value: (ex.hir_id, ex.span, UnsafeUseReason::UnionField),
639+
res: self.res,
640+
});
641+
}
642+
}
643+
644+
_ => (),
645+
};
552646

553647
hir::intravisit::walk_expr(self, ex);
554648
}
555649
}
556650

557-
struct InferVarCollector<'r> {
558-
hir_id: HirId,
559-
call_span: Span,
560-
res: &'r mut UnordMap<ty::TyVid, (HirId, Span)>,
651+
struct InferVarCollector<'r, V> {
652+
value: V,
653+
res: &'r mut UnordMap<ty::TyVid, V>,
561654
}
562655

563-
impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for InferVarCollector<'_> {
656+
impl<'tcx, V: Copy> ty::TypeVisitor<TyCtxt<'tcx>> for InferVarCollector<'_, V> {
564657
fn visit_ty(&mut self, t: Ty<'tcx>) {
565658
if let Some(vid) = t.ty_vid() {
566-
self.res.insert(vid, (self.hir_id, self.call_span));
659+
_ = self.res.try_insert(vid, self.value);
567660
} else {
568661
t.super_visit_with(self)
569662
}

tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs

+108-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//@ check-pass
2-
use std::mem;
2+
use std::{marker, mem, ptr};
33

4-
fn main() {
4+
fn main() {}
5+
6+
fn _zero() {
57
if false {
68
unsafe { mem::zeroed() }
79
//~^ warn: never type fallback affects this call to an `unsafe` function
@@ -13,6 +15,110 @@ fn main() {
1315
if true { unsafe { mem::zeroed() } } else { return }
1416
}
1517

18+
fn _trans() {
19+
if false {
20+
unsafe {
21+
struct Zst;
22+
core::mem::transmute(Zst)
23+
//~^ warn: never type fallback affects this call to an `unsafe` function
24+
}
25+
} else {
26+
return;
27+
};
28+
}
29+
30+
fn _union() {
31+
if false {
32+
union Union<T: Copy> {
33+
a: (),
34+
b: T,
35+
}
36+
37+
unsafe { Union { a: () }.b }
38+
//~^ warn: never type fallback affects this union access
39+
} else {
40+
return;
41+
};
42+
}
43+
44+
fn _deref() {
45+
if false {
46+
unsafe { *ptr::from_ref(&()).cast() }
47+
//~^ warn: never type fallback affects this raw pointer dereference
48+
} else {
49+
return;
50+
};
51+
}
52+
53+
fn _only_generics() {
54+
if false {
55+
unsafe fn internally_create<T>(_: Option<T>) {
56+
let _ = mem::zeroed::<T>();
57+
}
58+
59+
// We need the option (and unwrap later) to call a function in a way,
60+
// which makes it affected by the fallback, but without having it return anything
61+
let x = None;
62+
63+
unsafe { internally_create(x) }
64+
//~^ warn: never type fallback affects this call to an `unsafe` function
65+
66+
x.unwrap()
67+
} else {
68+
return;
69+
};
70+
}
71+
72+
fn _stored_function() {
73+
if false {
74+
let zeroed = mem::zeroed;
75+
//~^ warn: never type fallback affects this `unsafe` function
76+
77+
unsafe { zeroed() }
78+
//~^ warn: never type fallback affects this call to an `unsafe` function
79+
} else {
80+
return;
81+
};
82+
}
83+
84+
fn _only_generics_stored_function() {
85+
if false {
86+
unsafe fn internally_create<T>(_: Option<T>) {
87+
let _ = mem::zeroed::<T>();
88+
}
89+
90+
let x = None;
91+
let f = internally_create;
92+
//~^ warn: never type fallback affects this `unsafe` function
93+
94+
unsafe { f(x) }
95+
96+
x.unwrap()
97+
} else {
98+
return;
99+
};
100+
}
101+
102+
fn _method() {
103+
struct S<T>(marker::PhantomData<T>);
104+
105+
impl<T> S<T> {
106+
#[allow(unused)] // FIXME: the unused lint is probably incorrect here
107+
unsafe fn create_out_of_thin_air(&self) -> T {
108+
todo!()
109+
}
110+
}
111+
112+
if false {
113+
unsafe {
114+
S(marker::PhantomData).create_out_of_thin_air()
115+
//~^ warn: never type fallback affects this call to an `unsafe` method
116+
}
117+
} else {
118+
return;
119+
};
120+
}
121+
16122
// Minimization of the famous `objc` crate issue
17123
fn _objc() {
18124
pub unsafe fn send_message<R>() -> Result<R, ()> {

0 commit comments

Comments
 (0)