Skip to main content

rustc_passes/
check_export.rs

1use std::iter;
2use std::ops::ControlFlow;
3
4use rustc_abi::ExternAbi;
5use rustc_attr_ir::find_attr;
6use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
7use rustc_hir as hir;
8use rustc_hir::def::DefKind;
9use rustc_hir::def_id::{DefId, LocalDefId};
10use rustc_hir::intravisit::{self, Visitor};
11use rustc_middle::hir::nested_filter;
12use rustc_middle::middle::privacy::{EffectiveVisibility, Level};
13use rustc_middle::query::{LocalCrate, Providers};
14use rustc_middle::ty::{
15    self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, Unnormalized, Visibility,
16};
17use rustc_span::Span;
18use rustc_structures::CrateType;
19
20use crate::diagnostics::UnexportableItem;
21
22struct ExportableItemCollector<'tcx> {
23    tcx: TyCtxt<'tcx>,
24    exportable_items: FxIndexSet<DefId>,
25    in_exportable_mod: bool,
26    seen_exportable_in_mod: bool,
27}
28
29impl<'tcx> ExportableItemCollector<'tcx> {
30    fn new(tcx: TyCtxt<'tcx>) -> ExportableItemCollector<'tcx> {
31        ExportableItemCollector {
32            tcx,
33            exportable_items: Default::default(),
34            in_exportable_mod: false,
35            seen_exportable_in_mod: false,
36        }
37    }
38
39    fn report_wrong_site(&self, def_id: LocalDefId) {
40        let def_descr = self.tcx.def_descr(def_id.to_def_id());
41        self.tcx.dcx().emit_err(UnexportableItem::Item {
42            descr: &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}", def_descr))
    })format!("{}", def_descr),
43            span: self.tcx.def_span(def_id),
44        });
45    }
46
47    fn item_is_exportable(&self, def_id: LocalDefId) -> bool {
48        let has_attr = {
        {
            'done:
                {
                for i in
                    ::rustc_attr_ir::HasAttrs::get_attrs(def_id, &self.tcx) {
                    #[allow(unused_imports)]
                    use ::rustc_attr_ir::AttributeKind::*;
                    let i: &::rustc_attr_ir::Attribute = i;
                    match i {
                        ::rustc_attr_ir::Attribute::Parsed(ExportStable) => {
                            break 'done Some(());
                        }
                        ::rustc_attr_ir::Attribute::Unparsed(..) =>
                            {}
                            #[deny(unreachable_patterns)]
                            _ => {}
                    }
                }
                None
            }
        }
    }.is_some()find_attr!(self.tcx, def_id, ExportStable);
49        if !self.in_exportable_mod && !has_attr {
50            return false;
51        }
52
53        let visibilities = self.tcx.effective_visibilities(());
54        let is_pub = visibilities.is_directly_public(def_id);
55
56        if has_attr && !is_pub {
57            let vis = visibilities.effective_vis(def_id).cloned().unwrap_or_else(|| {
58                EffectiveVisibility::from_vis(Visibility::Restricted(
59                    self.tcx.parent_module_from_def_id(def_id),
60                ))
61            });
62            let vis = vis.at_level(Level::Direct);
63            let span = self.tcx.def_span(def_id);
64
65            self.tcx.dcx().emit_err(UnexportableItem::PrivItem {
66                vis_note: span,
67                vis_descr: &vis.to_string(def_id, self.tcx),
68                span,
69            });
70            return false;
71        }
72
73        is_pub && (has_attr || self.in_exportable_mod)
74    }
75
76    fn add_exportable(&mut self, def_id: LocalDefId) {
77        self.seen_exportable_in_mod = true;
78        self.exportable_items.insert(def_id.to_def_id());
79    }
80
81    fn walk_item_with_mod(&mut self, item: &'tcx hir::Item<'tcx>) {
82        let def_id = item.hir_id().owner.def_id;
83        let old_exportable_mod = self.in_exportable_mod;
84        if {
        {
            'done:
                {
                for i in
                    ::rustc_attr_ir::HasAttrs::get_attrs(def_id, &self.tcx) {
                    #[allow(unused_imports)]
                    use ::rustc_attr_ir::AttributeKind::*;
                    let i: &::rustc_attr_ir::Attribute = i;
                    match i {
                        ::rustc_attr_ir::Attribute::Parsed(ExportStable) => {
                            break 'done Some(());
                        }
                        ::rustc_attr_ir::Attribute::Unparsed(..) =>
                            {}
                            #[deny(unreachable_patterns)]
                            _ => {}
                    }
                }
                None
            }
        }
    }.is_some()find_attr!(self.tcx, def_id, ExportStable) {
85            self.in_exportable_mod = true;
86        }
87        let old_seen_exportable_in_mod = std::mem::replace(&mut self.seen_exportable_in_mod, false);
88
89        intravisit::walk_item(self, item);
90
91        if self.seen_exportable_in_mod || self.in_exportable_mod {
92            self.exportable_items.insert(def_id.to_def_id());
93        }
94
95        self.seen_exportable_in_mod = old_seen_exportable_in_mod;
96        self.in_exportable_mod = old_exportable_mod;
97    }
98}
99
100impl<'tcx> Visitor<'tcx> for ExportableItemCollector<'tcx> {
101    type NestedFilter = nested_filter::All;
102
103    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
104        self.tcx
105    }
106
107    fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
108        let def_id = item.hir_id().owner.def_id;
109        // Applying #[extern] attribute to modules is simply equivalent to
110        // applying the attribute to every public item within it.
111        match item.kind {
112            hir::ItemKind::Mod(..) => {
113                self.walk_item_with_mod(item);
114                return;
115            }
116            hir::ItemKind::Impl(impl_) if impl_.of_trait.is_none() => {
117                self.walk_item_with_mod(item);
118                return;
119            }
120            _ => {}
121        }
122
123        if !self.item_is_exportable(def_id) {
124            return;
125        }
126
127        match item.kind {
128            hir::ItemKind::Fn { .. }
129            | hir::ItemKind::Struct(..)
130            | hir::ItemKind::Enum(..)
131            | hir::ItemKind::Union(..)
132            | hir::ItemKind::TyAlias(..) => {
133                self.add_exportable(def_id);
134            }
135            hir::ItemKind::Use(tree) => {
136                for res in tree.resolutions() {
137                    for res in res.present_items() {
138                        // Only local items are exportable.
139                        if let Some(res_id) = res.opt_def_id()
140                            && let Some(res_id) = res_id.as_local()
141                        {
142                            self.add_exportable(res_id);
143                        }
144                    }
145                }
146            }
147            // handled above
148            hir::ItemKind::Mod(..) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
149            hir::ItemKind::Impl(impl_) if impl_.of_trait.is_none() => {
150                ::core::panicking::panic("internal error: entered unreachable code");unreachable!();
151            }
152            _ => {
153                self.tcx.dcx().delayed_bug("Target is checked by attribute parser");
154            }
155        }
156    }
157
158    fn visit_impl_item(&mut self, item: &'tcx hir::ImplItem<'tcx>) {
159        let def_id = item.hir_id().owner.def_id;
160        if !self.item_is_exportable(def_id) {
161            return;
162        }
163        match item.kind {
164            hir::ImplItemKind::Fn(..) | hir::ImplItemKind::Type(..) => {
165                self.add_exportable(def_id);
166            }
167            _ => self.report_wrong_site(def_id),
168        }
169    }
170
171    fn visit_foreign_item(&mut self, item: &'tcx hir::ForeignItem<'tcx>) {
172        let def_id = item.hir_id().owner.def_id;
173        if !self.item_is_exportable(def_id) {
174            self.report_wrong_site(def_id);
175        }
176    }
177
178    fn visit_trait_item(&mut self, item: &'tcx hir::TraitItem<'tcx>) {
179        let def_id = item.hir_id().owner.def_id;
180        if !self.item_is_exportable(def_id) {
181            self.report_wrong_site(def_id);
182        }
183    }
184}
185
186struct ExportableItemsChecker<'tcx, 'a> {
187    tcx: TyCtxt<'tcx>,
188    exportable_items: &'a FxIndexSet<DefId>,
189    item_id: DefId,
190}
191
192impl<'tcx, 'a> ExportableItemsChecker<'tcx, 'a> {
193    fn check(&mut self) {
194        match self.tcx.def_kind(self.item_id) {
195            DefKind::Fn | DefKind::AssocFn => self.check_fn(),
196            DefKind::Enum | DefKind::Struct | DefKind::Union => self.check_ty(),
197            _ => {}
198        }
199    }
200
201    fn check_fn(&mut self) {
202        let def_id = self.item_id.expect_local();
203        let span = self.tcx.def_span(def_id);
204
205        if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
206            self.tcx.dcx().emit_err(UnexportableItem::GenericFn(span));
207            return;
208        }
209
210        let sig = self.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
211        if !#[allow(non_exhaustive_omitted_patterns)] match sig.abi() {
    ExternAbi::C { .. } => true,
    _ => false,
}matches!(sig.abi(), ExternAbi::C { .. }) {
212            self.tcx.dcx().emit_err(UnexportableItem::FnAbi(span));
213            return;
214        }
215
216        let sig = self
217            .tcx
218            .try_normalize_erasing_regions(
219                ty::TypingEnv::non_body_analysis(self.tcx, def_id),
220                Unnormalized::new_wip(sig),
221            )
222            .unwrap_or(sig);
223
224        let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
225        let decl = self.tcx.hir_fn_decl_by_hir_id(hir_id).unwrap();
226
227        for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
228            self.check_nested_types_are_exportable(*input_ty, input_hir.span);
229        }
230
231        if let hir::FnRetTy::Return(ret_hir) = decl.output {
232            self.check_nested_types_are_exportable(sig.output(), ret_hir.span);
233        }
234    }
235
236    fn check_ty(&mut self) {
237        let ty = self.tcx.type_of(self.item_id).skip_binder();
238        if let ty::Adt(adt_def, _) = ty.kind() {
239            if !adt_def.repr().inhibit_struct_field_reordering() {
240                self.tcx
241                    .dcx()
242                    .emit_err(UnexportableItem::TypeRepr(self.tcx.def_span(self.item_id)));
243            }
244
245            // FIXME: support `#[export(unsafe_stable_abi = "hash")]` syntax
246            for variant in adt_def.variants() {
247                for field in &variant.fields {
248                    if !field.vis.is_public() {
249                        self.tcx.dcx().emit_err(UnexportableItem::AdtWithPrivFields {
250                            span: self.tcx.def_span(self.item_id),
251                            vis_note: self.tcx.def_span(field.did),
252                            field_name: field.name.as_str(),
253                        });
254                    }
255                }
256            }
257        }
258    }
259
260    fn check_nested_types_are_exportable(&mut self, ty: Ty<'tcx>, ty_span: Span) {
261        let res = ty.visit_with(self);
262        if let Some(err_cause) = res.break_value() {
263            self.tcx.dcx().emit_err(UnexportableItem::TypeInInterface {
264                span: self.tcx.def_span(self.item_id),
265                desc: self.tcx.def_descr(self.item_id),
266                ty: &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}", err_cause))
    })format!("{}", err_cause),
267                ty_span,
268            });
269        }
270    }
271}
272
273impl<'tcx, 'a> TypeVisitor<TyCtxt<'tcx>> for ExportableItemsChecker<'tcx, 'a> {
274    type Result = ControlFlow<Ty<'tcx>>;
275
276    fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
277        match ty.kind() {
278            ty::Adt(adt_def, _) => {
279                let did = adt_def.did();
280                let exportable = if did.is_local() {
281                    self.exportable_items.contains(&did)
282                } else {
283                    self.tcx.is_exportable(did)
284                };
285                if !exportable {
286                    return ControlFlow::Break(ty);
287                }
288                for variant in adt_def.variants() {
289                    for field in &variant.fields {
290                        let field_ty =
291                            self.tcx.type_of(field.did).instantiate_identity().skip_norm_wip();
292                        field_ty.visit_with(self)?;
293                    }
294                }
295
296                return ty.super_visit_with(self);
297            }
298
299            ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Bool | ty::Char | ty::Error(_) => {}
300
301            ty::Array(_, _)
302            | ty::Ref(_, _, _)
303            | ty::Param(_)
304            | ty::Closure(_, _)
305            | ty::Dynamic(_, _)
306            | ty::Coroutine(_, _)
307            | ty::Foreign(_)
308            | ty::Str
309            | ty::Tuple(_)
310            | ty::Pat(..)
311            | ty::Slice(_)
312            | ty::RawPtr(_, _)
313            | ty::FnDef(_, _)
314            | ty::FnPtr(_, _)
315            | ty::CoroutineClosure(_, _)
316            | ty::CoroutineWitness(_, _)
317            | ty::Never
318            | ty::UnsafeBinder(_)
319            | ty::Alias(_, ty::AliasTy { kind: ty::AliasTyKind::Opaque { .. }, .. }) => {
320                return ControlFlow::Break(ty);
321            }
322
323            ty::Alias(..) | ty::Infer(_) | ty::Placeholder(_) | ty::Bound(..) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
324        }
325        ControlFlow::Continue(())
326    }
327}
328
329/// Exportable items:
330///
331/// 1. Structs/enums/unions with a stable representation (e.g. repr(i32) or repr(C)).
332/// 2. Primitive types.
333/// 3. Non-generic functions with a stable ABI (e.g. extern "C") for which every user
334///    defined type used in the signature is also marked as `#[export]`.
335fn exportable_items_provider_local<'tcx>(tcx: TyCtxt<'tcx>, _: LocalCrate) -> &'tcx [DefId] {
336    if !tcx.crate_types().contains(&CrateType::Sdylib) && !tcx.is_sdylib_interface_build() {
337        return &[];
338    }
339
340    let mut visitor = ExportableItemCollector::new(tcx);
341    tcx.hir_walk_toplevel_module(&mut visitor);
342    let exportable_items = visitor.exportable_items;
343    for item_id in exportable_items.iter() {
344        let mut validator =
345            ExportableItemsChecker { tcx, exportable_items: &exportable_items, item_id: *item_id };
346        validator.check();
347    }
348
349    tcx.arena.alloc_from_iter(exportable_items.into_iter())
350}
351
352struct ImplsOrderVisitor<'tcx> {
353    tcx: TyCtxt<'tcx>,
354    order: FxIndexMap<DefId, usize>,
355}
356
357impl<'tcx> ImplsOrderVisitor<'tcx> {
358    fn new(tcx: TyCtxt<'tcx>) -> ImplsOrderVisitor<'tcx> {
359        ImplsOrderVisitor { tcx, order: Default::default() }
360    }
361}
362
363impl<'tcx> Visitor<'tcx> for ImplsOrderVisitor<'tcx> {
364    type NestedFilter = nested_filter::All;
365
366    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
367        self.tcx
368    }
369
370    fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
371        if let hir::ItemKind::Impl(impl_) = item.kind
372            && impl_.of_trait.is_none()
373            && self.tcx.is_exportable(item.owner_id.def_id.to_def_id())
374        {
375            self.order.insert(item.owner_id.def_id.to_def_id(), self.order.len());
376        }
377        intravisit::walk_item(self, item);
378    }
379}
380
381/// During symbol mangling rustc uses a special index to distinguish between two impls of
382/// the same type in the same module(See `DisambiguatedDefPathData`). For exportable items
383/// we cannot use the current approach because it is dependent on the compiler's
384/// implementation.
385///
386/// In order to make disambiguation independent of the compiler version we can assign an
387/// id to each impl according to the relative order of elements in the source code.
388fn stable_order_of_exportable_impls<'tcx>(
389    tcx: TyCtxt<'tcx>,
390    _: LocalCrate,
391) -> &'tcx FxIndexMap<DefId, usize> {
392    if !tcx.crate_types().contains(&CrateType::Sdylib) && !tcx.is_sdylib_interface_build() {
393        return tcx.arena.alloc(FxIndexMap::<DefId, usize>::default());
394    }
395
396    let mut vis = ImplsOrderVisitor::new(tcx);
397    tcx.hir_walk_toplevel_module(&mut vis);
398    tcx.arena.alloc(vis.order)
399}
400
401pub(crate) fn provide(providers: &mut Providers) {
402    *providers = Providers {
403        exportable_items: exportable_items_provider_local,
404        stable_order_of_exportable_impls,
405        ..*providers
406    };
407}