rustc_passes/
check_export.rs

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