Skip to main content

rustc_lint/ferrocene/
diagnostics.rs

1//! ## Recommended reading
2//! - [Errors and lints](https://rustc-dev-guide.rust-lang.org/diagnostics.html)
3
4use rustc_errors::{Diag, MultiSpan};
5use rustc_hir::def_id::DefId;
6use rustc_hir::{HirId, LangItem};
7use rustc_span::{STDLIB_STABLE_CRATES, Span};
8use tracing::debug;
9
10use crate::ferrocene::post_mono::InstantiationSite;
11use crate::ferrocene::{LintState, UNVALIDATED, UnvalidatedImplCause, Use, UseKind};
12
13/// Diagnostics.
14impl<'tcx> LintState<'tcx> {
15    fn func_span(&self, def_id: DefId) -> Span {
16        match self.tcx.opt_item_ident(def_id) {
17            Some(name) => name.span,
18            None => self.tcx.def_span(def_id),
19        }
20    }
21
22    pub(super) fn lint_use(&mut self, lint_node: HirId, use_: Use<'tcx>) {
23        let Self { tcx, item: owner, .. } = *self;
24        let (callee, receiver_span) = (use_.def_id(), use_.span);
25
26        {
    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/diagnostics.rs:26",
                        "rustc_lint::ferrocene::diagnostics",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_lint/src/ferrocene/diagnostics.rs"),
                        ::tracing_core::__macro_support::Option::Some(26u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_lint::ferrocene::diagnostics"),
                        ::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!("linting node {0:?}",
                                                    lint_node) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("linting node {lint_node:?}");
27
28        tcx.emit_node_span_lint(UNVALIDATED, lint_node, receiver_span, rustc_errors::DiagDecorator(|diag| {
29            let callee_descr = tcx.def_descr(callee);
30            let owner_descr = tcx.def_descr(owner.into());
31            diag.primary_message(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("validated {1} {0} an unvalidated {2}",
                use_.present_tense(), owner_descr, callee_descr))
    })format!(
32                "validated {owner_descr} {} an unvalidated {callee_descr}",
33                use_.present_tense()
34            ));
35
36            // Need to do this lazily or `with_no_trimmed_paths` will panic :/
37            let name = match use_.opt_instance() {
38                None => tcx.def_path_str(callee),
39                Some(instance) => tcx.def_path_str_with_args(callee, instance.args),
40            };
41            diag.span_label(self.func_span(callee), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` is unvalidated", name))
    })format!("`{name}` is unvalidated"));
42
43            if let UseKind::ContainsFnPtr(_, ty) = use_.kind {
44                diag.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` contains a function pointer that might be called at runtime",
                name))
    })format!("`{name}` contains a function pointer that might be called at runtime"));
45                diag.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the Ferrocene compiler does not know if the `{0}` was verified, so it must assume it is unverified",
                ty))
    })format!("the Ferrocene compiler does not know if the `{ty}` was verified, so it must assume it is unverified"));
46            }
47
48            if STDLIB_STABLE_CRATES.contains(&tcx.crate_name(callee.krate)) {
49                diag.help_once(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("contact Ferrocene support to see if this {0} is possible to certify",
                callee_descr))
    })format!(
50                    "contact Ferrocene support to see if this {callee_descr} is possible to certify"
51                ));
52            }
53
54            // Don't show this "takes place in a validated function" label more than once per function.
55            // We really do need this as a separate bit of state from shown_lints because the lint might not be
56            // emitted. ideally we would just `cancel` the diagnostic if we don't want to emit it,
57            // but we don't get an owned `Diag` from `node_span_lint` :(
58            if !self.shown_item {
59                self.shown_item = true;
60                let mut validated_span = MultiSpan::from_span(self.func_span(owner.into()));
61                if let Some(annotation) = self.annotation {
62                    validated_span.push_span_label(annotation, "marked as validated here");
63                }
64
65                self.decorate_cast(use_, diag);
66                self.decorate_instantiation(use_, diag, Some(&mut validated_span));
67
68                diag.span_note(
69                    validated_span,
70                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` is validated",
                tcx.def_path_str(owner)))
    })format!("`{}` is validated", tcx.def_path_str(owner)),
71                );
72                if self.annotation.is_none() {
73                    diag.note("main functions are assumed to be validated");
74                }
75            } else {
76                self.decorate_cast(use_, diag);
77                self.decorate_instantiation(use_, diag, None);
78            }
79        }));
80    }
81
82    fn decorate_cast(&self, use_: Use<'tcx>, diag: &mut Diag<'_, ()>) {
83        let tcx = self.tcx;
84        if #[allow(non_exhaustive_omitted_patterns)] match use_.kind {
    UseKind::FnPtrCast(..) => true,
    _ => false,
}matches!(use_.kind, UseKind::FnPtrCast(..)) {
85            diag.note("once a function is cast to a function pointer, Ferrocene can no longer tell whether it is validated");
86            diag.note("as a precaution, it must assume you will eventually call the function");
87        } else if let UseKind::TraitObjectCast(cause, ty) = use_.kind {
88            diag.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("once `{0}` is cast to a dynamic trait object, Ferrocene can no longer tell whether it is validated",
                ty))
    })format!("once `{ty}` is cast to a dynamic trait object, Ferrocene can no longer tell whether it is validated"));
89            match cause {
90                UnvalidatedImplCause::AssocFn(assoc_fn) => {
91                    diag.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("as a precaution, it must assume you will eventually call `{0}`",
                tcx.def_path_str(assoc_fn)))
    })format!(
92                        "as a precaution, it must assume you will eventually call `{}`",
93                        tcx.def_path_str(assoc_fn)
94                    ));
95                }
96                UnvalidatedImplCause::UnresolvedGenericImpl(..) => {
97                    {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("all generics should be resolved by post-mono")));
}unreachable!("all generics should be resolved by post-mono")
98                }
99            }
100        }
101    }
102
103    fn decorate_instantiation(
104        &self,
105        use_: Use<'tcx>,
106        diag: &mut Diag<'_, ()>,
107        validated_span: Option<&mut MultiSpan>,
108    ) {
109        let tcx = self.tcx;
110        if let Some(InstantiationSite {
111            caller_span,
112            caller_instance,
113            pre_mono_callee,
114            drop_fn,
115            lint_node: _,
116        }) = use_.from_instantiation
117        {
118            let caller_descr =
119                tcx.def_path_str_with_args(caller_instance.def_id(), caller_instance.args);
120
121            let drop = tcx.require_lang_item(LangItem::Drop, caller_span);
122            let get_drop_impl = |def_id| {
123                tcx.trait_impl_of_assoc(def_id).filter(|impl_| tcx.impl_trait_id(*impl_) == drop)
124            };
125
126            let msg = if let Some(impl_) =
127                get_drop_impl(use_.def_id()).or(drop_fn.and_then(|drop| get_drop_impl(drop)))
128            {
129                let dropped_ty = tcx.type_of(impl_).skip_binder();
130                // Call to drop(), injected by the compiler.
131                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` dropped here, in `{1}`",
                dropped_ty, caller_descr))
    })format!("`{dropped_ty}` dropped here, in `{caller_descr}`")
132            } else {
133                let callee_descr = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("generic {0} `{1}`",
                tcx.def_descr(pre_mono_callee),
                {
                    let _guard = NoTrimmedGuard::new();
                    tcx.def_path_str(pre_mono_callee)
                }))
    })format!(
134                    "generic {} `{}`",
135                    tcx.def_descr(pre_mono_callee),
136                    rustc_middle::ty::print::with_no_trimmed_paths!(
137                        tcx.def_path_str(pre_mono_callee)
138                    )
139                );
140
141                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} instantiated by `{1}`",
                callee_descr, caller_descr))
    })format!("{callee_descr} instantiated by `{caller_descr}`")
142            };
143
144            if let Some(multi) = validated_span {
145                multi.push_span_label(caller_span, msg);
146            } else {
147                diag.span_note_once(
148                    caller_span,
149                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}, which is called from a validated entrypoint",
                msg))
    })format!("{msg}, which is called from a validated entrypoint"),
150                );
151            }
152        }
153    }
154}
155
156impl<'tcx> Use<'tcx> {
157    fn present_tense(self) -> &'static str {
158        match self.kind {
159            UseKind::Called(..) => "calls",
160            // originally this said "type-erases" but that's unfamiliar jargon, and it's not clear
161            // that it actually helps understanding.
162            UseKind::TraitObjectCast(..) | UseKind::FnPtrCast(..) => "possibly calls",
163            UseKind::ContainsFnPtr(..) => "uses",
164        }
165    }
166}