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