Skip to main content

rustc_lint/ferrocene/
thir.rs

1//! Run a pre-mono THIR pass on the current crate.
2//! In THIR, all operator overloads have been resolved to a function call, but we still may have
3//! uninstantiated generic functions.
4//!
5//! This exists to give useful diagnostics without having to wait all the way until monomorphization
6//! to give any feedback at all. This matters a lot for core, which has a bunch of generic
7//! functions.
8//!
9//! This pass works an item-at-a-time, with little shared state.
10
11use std::ops::ControlFlow;
12
13use rustc_hir::def_id::{DefId, LocalDefId};
14use rustc_hir::{HirId, LangItem, OwnerId};
15use rustc_middle::thir::visit::Visitor as _;
16use rustc_middle::thir::{self, Thir};
17use rustc_middle::ty::adjustment::PointerCoercion;
18use rustc_middle::ty::{
19    self, Binder, ExistentialTraitRef, GenericArgs, Instance, Ty, TyCtxt, TypeSuperVisitable as _,
20    TypeVisitable as _, TypeVisitor, TypingEnv,
21};
22use rustc_span::Span;
23use tracing::{debug, info};
24
25use crate::ferrocene::{InstantiateResult, LintState, UnvalidatedImplCause, Use, UseKind};
26
27pub(super) struct LintThir<'thir, 'tcx> {
28    thir: &'thir Thir<'tcx>,
29    linter: LintState<'tcx>,
30    owner: OwnerId,
31}
32
33impl<'thir, 'tcx: 'thir> thir::visit::Visitor<'thir, 'tcx> for LintThir<'thir, 'tcx> {
34    fn thir(&self) -> &'thir Thir<'tcx> {
35        self.thir
36    }
37
38    fn visit_expr(&mut self, expr: &'thir thir::Expr<'tcx>) {
39        let use_ = match self.find_unvalidated_use(expr) {
40            None => return,
41            // Didn't have all the generic parameters in scope.
42            // This will be caught later by the post-mono pass.
43            Some(Use {
44                kind: UseKind::TraitObjectCast(UnvalidatedImplCause::UnresolvedGenericImpl(..), _),
45                ..
46            }) => return,
47            Some(use_) => use_,
48        };
49        let hir_id = HirId { owner: self.owner, local_id: expr.temp_scope_id };
50        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/ferrocene/thir.rs:50",
                        "rustc_lint::ferrocene::thir", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/ferrocene/thir.rs"),
                        ::tracing_core::__macro_support::Option::Some(50u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::ferrocene::thir"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("id={1:?}, kind={0:?}",
                                                    expr.kind, hir_id) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("id={hir_id:?}, kind={:?}", expr.kind);
51        self.linter.check_use(hir_id, use_);
52    }
53}
54
55impl<'thir, 'tcx: 'thir> LintThir<'thir, 'tcx> {
56    /// Entrypoint.
57    ///
58    /// We need a separate `owner` to be able to synthesize `HirId`s from expression IDs.
59    /// `item` might not be an owner if it's a closure.
60    pub(super) fn check_item(tcx: TyCtxt<'tcx>, owner: OwnerId, item: LocalDefId) -> Option<()> {
61        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/ferrocene/thir.rs:61",
                        "rustc_lint::ferrocene::thir", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/ferrocene/thir.rs"),
                        ::tracing_core::__macro_support::Option::Some(61u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::ferrocene::thir"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("checking {0:?}",
                                                    item) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};tracing::trace!("checking {item:?}");
62
63        if tcx.sess.opts.test
64            && tcx.entry_fn(()).and_then(|(id, _)| id.as_local()) == Some(owner.def_id)
65        {
66            // We don't lint `main` functions if they're a shim generated by the `--test` machinery.
67            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/ferrocene/thir.rs:67",
                        "rustc_lint::ferrocene::thir", ::tracing::Level::INFO,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/ferrocene/thir.rs"),
                        ::tracing_core::__macro_support::Option::Some(67u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::ferrocene::thir"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::INFO <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("treating libtest main function as unvalidated")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};info!("treating libtest main function as unvalidated");
68            return None;
69        }
70
71        let linter = LintState::new(tcx, item)?;
72        // thir_body can return ErrorGuaranteed if this is a const block that failed evaluation.
73        let body = tcx.thir_body(item).ok();
74        if body.is_none() {
75            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/ferrocene/thir.rs:75",
                        "rustc_lint::ferrocene::thir", ::tracing::Level::INFO,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/ferrocene/thir.rs"),
                        ::tracing_core::__macro_support::Option::Some(75u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::ferrocene::thir"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::INFO <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("skipping item {0:?} without body",
                                                    item) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};info!("skipping item {item:?} without body");
76        }
77        let thir = &body?.0.borrow();
78        let mut visitor = LintThir { linter, thir, owner };
79        for expr in &*thir.exprs {
80            visitor.visit_expr(expr);
81        }
82
83        Some(())
84    }
85
86    fn find_unvalidated_use(&mut self, expr: &thir::Expr<'tcx>) -> Option<Use<'tcx>> {
87        let tcx = self.linter.tcx;
88        let mut span = expr.span;
89
90        let mut try_instantiate = |def_id, args| self.try_instantiate(def_id, args, span);
91
92        let use_kind = match expr.kind {
93            thir::ExprKind::NamedConst { def_id, .. }
94            | thir::ExprKind::StaticRef { def_id, .. } => {
95                // Statics and constants have bodies, but they are always evaluated at compile time.
96                // We argue to our assessor that means that the correct behavior is
97                // validated whenever the const/static is used in a runtime function, so the
98                // functions that generate the constant don't need to be tested separately.
99                // The constants themselves execute no code at runtime, so mentioning them is ok.
100                let unknown_fn = contains_unknown_fn(expr.ty)?;
101                // However, it's possible for runtime code to access an unknown function type from
102                // this constant. Ensure that it's marked with `prevalidated` so that its body gets
103                // checked.
104                UseKind::ContainsFnPtr(def_id, unknown_fn)
105            }
106            thir::ExprKind::Call { ty, .. } => {
107                let instance = self.instance_of_ty_ignoring_validated(ty, expr.span)?;
108                // we use a custom narrowed span here. it's the receiver that's unvalidated, not the
109                // arguments.
110                span = tcx.sess.source_map().span_until_char(expr.span, '(');
111
112                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/ferrocene/thir.rs:112",
                        "rustc_lint::ferrocene::thir", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/ferrocene/thir.rs"),
                        ::tracing_core::__macro_support::Option::Some(112u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::ferrocene::thir"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("saw call to {0:?}",
                                                    instance) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("saw call to {instance:?}");
113                UseKind::Called(instance)
114            }
115            // We assume all closure definitions in this function are also validated.
116            // However, we still need to check the closure body to make sure it doesn't call
117            // unvalidated functions.
118            thir::ExprKind::Closure(ref expr) => {
119                // Closures are never an owner, so we need to hang onto the original owner so that
120                // our synthesized HirIds are valid.
121                LintThir::check_item(tcx, self.owner, expr.closure_id);
122                return None;
123            }
124            thir::ExprKind::PointerCoercion {
125                // NOTE: we intentionally don't check closure casts.
126                cast: PointerCoercion::ReifyFnPointer(_),
127                source,
128                ..
129            } => {
130                let source_ty = self.thir[source].ty;
131                let fn_ptr_trait = tcx.require_lang_item(LangItem::FnPtrTrait, expr.span);
132                let trait_ref = Binder::dummy(ExistentialTraitRef::new_from_args(
133                    tcx,
134                    fn_ptr_trait,
135                    GenericArgs::empty(),
136                ));
137                self.linter.check_fn_ptr_coercion(
138                    source_ty,
139                    trait_ref.with_self_ty(tcx, source_ty),
140                    &mut try_instantiate,
141                    expr.span,
142                )?
143            }
144            thir::ExprKind::PointerCoercion { cast: PointerCoercion::Unsize, source, .. } => {
145                let source_ty = self.thir[source].ty;
146                self.linter.check_dyn_trait_coercion(
147                    expr.ty,
148                    source_ty,
149                    self.typing_env(),
150                    &mut try_instantiate,
151                    span,
152                )?
153            }
154            // Nothing to check.
155            _ => return None,
156        };
157
158        Some(Use { kind: use_kind, span, from_instantiation: None })
159    }
160
161    fn instance_of_ty_ignoring_validated(
162        &self,
163        ty: Ty<'tcx>,
164        span: Span,
165    ) -> Option<Instance<'tcx>> {
166        let mut try_instantiate = |def_id, args| self.try_instantiate(def_id, args, span);
167
168        self.linter.instance_of_ty(ty, None, &mut try_instantiate, span).filter(|instance| {
169            // Skip trait functions. These happen when we're calling the vtable of a `dyn` unsized
170            // object. This case is caught below in `PointerCoercion::Unsize`.
171            if #[allow(non_exhaustive_omitted_patterns)] match instance.def {
    ty::InstanceKind::Virtual(..) => true,
    _ => false,
}matches!(instance.def, ty::InstanceKind::Virtual(..)) {
172                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_lint/src/ferrocene/thir.rs:172",
                        "rustc_lint::ferrocene::thir", ::tracing::Level::INFO,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/ferrocene/thir.rs"),
                        ::tracing_core::__macro_support::Option::Some(172u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::ferrocene::thir"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::INFO <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("skipping dyn assoc item {0:?}",
                                                    instance) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};info!("skipping dyn assoc item {instance:?}");
173                false
174            } else {
175                true
176            }
177        })
178    }
179
180    fn try_instantiate(
181        &self,
182        def_id: DefId,
183        args: &'tcx GenericArgs<'tcx>,
184        span: Span,
185    ) -> InstantiateResult<'tcx> {
186        let tcx = self.linter.tcx;
187        match Instance::try_resolve(tcx, self.typing_env(), def_id, args) {
188            Err(_) => {
189                // this happens when we hit the
190                // [type length limit](https://doc.rust-lang.org/reference/attributes/limits.html#the-type_length_limit-attribute)
191                tcx.dcx().span_delayed_bug(
192                    span,
193                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("could not resolve instance ({0:?}, {1:?})",
                def_id, args))
    })format!("could not resolve instance ({def_id:?}, {args:?})"),
194                );
195                InstantiateResult::Err
196            }
197            Ok(None) => InstantiateResult::Indeterminate,
198            Ok(Some(instance)) => InstantiateResult::Resolved(instance),
199        }
200    }
201
202    fn typing_env(&self) -> TypingEnv<'tcx> {
203        use rustc_middle::ty::TypingMode;
204        let tcx = self.linter.tcx;
205
206        let typing_mode = TypingMode::typeck_for_body(tcx, self.linter.item);
207        let param_env = tcx.param_env(self.linter.item);
208        TypingEnv::new(param_env, typing_mode)
209    }
210}
211
212/// Used to check whether a `const` or `static` has a function pointer callable at runtime.
213///
214/// c.f. Ty::contains_closure
215fn contains_unknown_fn<'tcx>(ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
216    struct ContainsUnknownFnVisitor;
217
218    impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsUnknownFnVisitor {
219        type Result = ControlFlow<Ty<'tcx>>;
220
221        fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
222            match t.kind() {
223                ty::Dynamic(..) | ty::FnPtr(_, _) => ControlFlow::Break(t),
224                _ => t.super_visit_with(self),
225            }
226        }
227    }
228
229    let cf = ty.visit_with(&mut ContainsUnknownFnVisitor);
230    cf.break_value()
231}