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