rustc_lint/
foreign_modules.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
use rustc_abi::FIRST_VARIANT;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_middle::query::Providers;
use rustc_middle::ty::{self, AdtDef, Instance, Ty, TyCtxt};
use rustc_session::declare_lint;
use rustc_span::{Span, Symbol, sym};
use tracing::{debug, instrument};

use crate::lints::{BuiltinClashingExtern, BuiltinClashingExternSub};
use crate::{LintVec, types};

pub(crate) fn provide(providers: &mut Providers) {
    *providers = Providers { clashing_extern_declarations, ..*providers };
}

pub(crate) fn get_lints() -> LintVec {
    vec![CLASHING_EXTERN_DECLARATIONS]
}

fn clashing_extern_declarations(tcx: TyCtxt<'_>, (): ()) {
    let mut lint = ClashingExternDeclarations::new();
    for id in tcx.hir_crate_items(()).foreign_items() {
        lint.check_foreign_item(tcx, id);
    }
}

declare_lint! {
    /// The `clashing_extern_declarations` lint detects when an `extern fn`
    /// has been declared with the same name but different types.
    ///
    /// ### Example
    ///
    /// ```rust
    /// mod m {
    ///     extern "C" {
    ///         fn foo();
    ///     }
    /// }
    ///
    /// extern "C" {
    ///     fn foo(_: u32);
    /// }
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// Because two symbols of the same name cannot be resolved to two
    /// different functions at link time, and one function cannot possibly
    /// have two types, a clashing extern declaration is almost certainly a
    /// mistake. Check to make sure that the `extern` definitions are correct
    /// and equivalent, and possibly consider unifying them in one location.
    ///
    /// This lint does not run between crates because a project may have
    /// dependencies which both rely on the same extern function, but declare
    /// it in a different (but valid) way. For example, they may both declare
    /// an opaque type for one or more of the arguments (which would end up
    /// distinct types), or use types that are valid conversions in the
    /// language the `extern fn` is defined in. In these cases, the compiler
    /// can't say that the clashing declaration is incorrect.
    pub CLASHING_EXTERN_DECLARATIONS,
    Warn,
    "detects when an extern fn has been declared with the same name but different types"
}

struct ClashingExternDeclarations {
    /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls
    /// contains an entry for key K, it means a symbol with name K has been seen by this lint and
    /// the symbol should be reported as a clashing declaration.
    // FIXME: Technically, we could just store a &'tcx str here without issue; however, the
    // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime.
    seen_decls: UnordMap<Symbol, hir::OwnerId>,
}

/// Differentiate between whether the name for an extern decl came from the link_name attribute or
/// just from declaration itself. This is important because we don't want to report clashes on
/// symbol name if they don't actually clash because one or the other links against a symbol with a
/// different name.
enum SymbolName {
    /// The name of the symbol + the span of the annotation which introduced the link name.
    Link(Symbol, Span),
    /// No link name, so just the name of the symbol.
    Normal(Symbol),
}

impl SymbolName {
    fn get_name(&self) -> Symbol {
        match self {
            SymbolName::Link(s, _) | SymbolName::Normal(s) => *s,
        }
    }
}

impl ClashingExternDeclarations {
    pub(crate) fn new() -> Self {
        ClashingExternDeclarations { seen_decls: Default::default() }
    }

    /// Insert a new foreign item into the seen set. If a symbol with the same name already exists
    /// for the item, return its HirId without updating the set.
    fn insert(&mut self, tcx: TyCtxt<'_>, fi: hir::ForeignItemId) -> Option<hir::OwnerId> {
        let did = fi.owner_id.to_def_id();
        let instance = Instance::new(did, ty::List::identity_for_item(tcx, did));
        let name = Symbol::intern(tcx.symbol_name(instance).name);
        if let Some(&existing_id) = self.seen_decls.get(&name) {
            // Avoid updating the map with the new entry when we do find a collision. We want to
            // make sure we're always pointing to the first definition as the previous declaration.
            // This lets us avoid emitting "knock-on" diagnostics.
            Some(existing_id)
        } else {
            self.seen_decls.insert(name, fi.owner_id)
        }
    }

    #[instrument(level = "trace", skip(self, tcx))]
    fn check_foreign_item<'tcx>(&mut self, tcx: TyCtxt<'tcx>, this_fi: hir::ForeignItemId) {
        let DefKind::Fn = tcx.def_kind(this_fi.owner_id) else { return };
        let Some(existing_did) = self.insert(tcx, this_fi) else { return };

        let existing_decl_ty = tcx.type_of(existing_did).skip_binder();
        let this_decl_ty = tcx.type_of(this_fi.owner_id).instantiate_identity();
        debug!(
            "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}",
            existing_did, existing_decl_ty, this_fi.owner_id, this_decl_ty
        );

        // Check that the declarations match.
        if !structurally_same_type(
            tcx,
            tcx.param_env(this_fi.owner_id),
            existing_decl_ty,
            this_decl_ty,
            types::CItemKind::Declaration,
        ) {
            let orig = name_of_extern_decl(tcx, existing_did);

            // Finally, emit the diagnostic.
            let this = tcx.item_name(this_fi.owner_id.to_def_id());
            let orig = orig.get_name();
            let previous_decl_label = get_relevant_span(tcx, existing_did);
            let mismatch_label = get_relevant_span(tcx, this_fi.owner_id);
            let sub =
                BuiltinClashingExternSub { tcx, expected: existing_decl_ty, found: this_decl_ty };
            let decorator = if orig == this {
                BuiltinClashingExtern::SameName {
                    this,
                    orig,
                    previous_decl_label,
                    mismatch_label,
                    sub,
                }
            } else {
                BuiltinClashingExtern::DiffName {
                    this,
                    orig,
                    previous_decl_label,
                    mismatch_label,
                    sub,
                }
            };
            tcx.emit_node_span_lint(
                CLASHING_EXTERN_DECLARATIONS,
                this_fi.hir_id(),
                mismatch_label,
                decorator,
            );
        }
    }
}

/// Get the name of the symbol that's linked against for a given extern declaration. That is,
/// the name specified in a #[link_name = ...] attribute if one was specified, else, just the
/// symbol's name.
fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> SymbolName {
    if let Some((overridden_link_name, overridden_link_name_span)) =
        tcx.codegen_fn_attrs(fi).link_name.map(|overridden_link_name| {
            // FIXME: Instead of searching through the attributes again to get span
            // information, we could have codegen_fn_attrs also give span information back for
            // where the attribute was defined. However, until this is found to be a
            // bottleneck, this does just fine.
            (overridden_link_name, tcx.get_attr(fi, sym::link_name).unwrap().span)
        })
    {
        SymbolName::Link(overridden_link_name, overridden_link_name_span)
    } else {
        SymbolName::Normal(tcx.item_name(fi.to_def_id()))
    }
}

/// We want to ensure that we use spans for both decls that include where the
/// name was defined, whether that was from the link_name attribute or not.
fn get_relevant_span(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> Span {
    match name_of_extern_decl(tcx, fi) {
        SymbolName::Normal(_) => tcx.def_span(fi),
        SymbolName::Link(_, annot_span) => annot_span,
    }
}

/// Checks whether two types are structurally the same enough that the declarations shouldn't
/// clash. We need this so we don't emit a lint when two modules both declare an extern struct,
/// with the same members (as the declarations shouldn't clash).
fn structurally_same_type<'tcx>(
    tcx: TyCtxt<'tcx>,
    param_env: ty::ParamEnv<'tcx>,
    a: Ty<'tcx>,
    b: Ty<'tcx>,
    ckind: types::CItemKind,
) -> bool {
    let mut seen_types = UnordSet::default();
    let result = structurally_same_type_impl(&mut seen_types, tcx, param_env, a, b, ckind);
    if cfg!(debug_assertions) && result {
        // Sanity-check: must have same ABI, size and alignment.
        // `extern` blocks cannot be generic, so we'll always get a layout here.
        let a_layout = tcx.layout_of(param_env.and(a)).unwrap();
        let b_layout = tcx.layout_of(param_env.and(b)).unwrap();
        assert_eq!(a_layout.abi, b_layout.abi);
        assert_eq!(a_layout.size, b_layout.size);
        assert_eq!(a_layout.align, b_layout.align);
    }
    result
}

fn structurally_same_type_impl<'tcx>(
    seen_types: &mut UnordSet<(Ty<'tcx>, Ty<'tcx>)>,
    tcx: TyCtxt<'tcx>,
    param_env: ty::ParamEnv<'tcx>,
    a: Ty<'tcx>,
    b: Ty<'tcx>,
    ckind: types::CItemKind,
) -> bool {
    debug!("structurally_same_type_impl(tcx, a = {:?}, b = {:?})", a, b);

    // Given a transparent newtype, reach through and grab the inner
    // type unless the newtype makes the type non-null.
    let non_transparent_ty = |mut ty: Ty<'tcx>| -> Ty<'tcx> {
        loop {
            if let ty::Adt(def, args) = *ty.kind() {
                let is_transparent = def.repr().transparent();
                let is_non_null = types::nonnull_optimization_guaranteed(tcx, def);
                debug!(
                    "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}",
                    ty, is_transparent, is_non_null
                );
                if is_transparent && !is_non_null {
                    debug_assert_eq!(def.variants().len(), 1);
                    let v = &def.variant(FIRST_VARIANT);
                    // continue with `ty`'s non-ZST field,
                    // otherwise `ty` is a ZST and we can return
                    if let Some(field) = types::transparent_newtype_field(tcx, v) {
                        ty = field.ty(tcx, args);
                        continue;
                    }
                }
            }
            debug!("non_transparent_ty -> {:?}", ty);
            return ty;
        }
    };

    let a = non_transparent_ty(a);
    let b = non_transparent_ty(b);

    if !seen_types.insert((a, b)) {
        // We've encountered a cycle. There's no point going any further -- the types are
        // structurally the same.
        true
    } else if a == b {
        // All nominally-same types are structurally same, too.
        true
    } else {
        // Do a full, depth-first comparison between the two.
        use rustc_type_ir::TyKind::*;

        let is_primitive_or_pointer =
            |ty: Ty<'tcx>| ty.is_primitive() || matches!(ty.kind(), RawPtr(..) | Ref(..));

        ensure_sufficient_stack(|| {
            match (a.kind(), b.kind()) {
                (&Adt(a_def, a_gen_args), &Adt(b_def, b_gen_args)) => {
                    // Only `repr(C)` types can be compared structurally.
                    if !(a_def.repr().c() && b_def.repr().c()) {
                        return false;
                    }
                    // If the types differ in their packed-ness, align, or simd-ness they conflict.
                    let repr_characteristica =
                        |def: AdtDef<'tcx>| (def.repr().pack, def.repr().align, def.repr().simd());
                    if repr_characteristica(a_def) != repr_characteristica(b_def) {
                        return false;
                    }

                    // Grab a flattened representation of all fields.
                    let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter());
                    let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter());

                    // Perform a structural comparison for each field.
                    a_fields.eq_by(
                        b_fields,
                        |&ty::FieldDef { did: a_did, .. }, &ty::FieldDef { did: b_did, .. }| {
                            structurally_same_type_impl(
                                seen_types,
                                tcx,
                                param_env,
                                tcx.type_of(a_did).instantiate(tcx, a_gen_args),
                                tcx.type_of(b_did).instantiate(tcx, b_gen_args),
                                ckind,
                            )
                        },
                    )
                }
                (Array(a_ty, a_len), Array(b_ty, b_len)) => {
                    // For arrays, we also check the length.
                    a_len == b_len
                        && structurally_same_type_impl(
                            seen_types, tcx, param_env, *a_ty, *b_ty, ckind,
                        )
                }
                (Slice(a_ty), Slice(b_ty)) => {
                    structurally_same_type_impl(seen_types, tcx, param_env, *a_ty, *b_ty, ckind)
                }
                (RawPtr(a_ty, a_mutbl), RawPtr(b_ty, b_mutbl)) => {
                    a_mutbl == b_mutbl
                        && structurally_same_type_impl(
                            seen_types, tcx, param_env, *a_ty, *b_ty, ckind,
                        )
                }
                (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
                    // For structural sameness, we don't need the region to be same.
                    a_mut == b_mut
                        && structurally_same_type_impl(
                            seen_types, tcx, param_env, *a_ty, *b_ty, ckind,
                        )
                }
                (FnDef(..), FnDef(..)) => {
                    let a_poly_sig = a.fn_sig(tcx);
                    let b_poly_sig = b.fn_sig(tcx);

                    // We don't compare regions, but leaving bound regions around ICEs, so
                    // we erase them.
                    let a_sig = tcx.instantiate_bound_regions_with_erased(a_poly_sig);
                    let b_sig = tcx.instantiate_bound_regions_with_erased(b_poly_sig);

                    (a_sig.abi, a_sig.safety, a_sig.c_variadic)
                        == (b_sig.abi, b_sig.safety, b_sig.c_variadic)
                        && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
                            structurally_same_type_impl(seen_types, tcx, param_env, *a, *b, ckind)
                        })
                        && structurally_same_type_impl(
                            seen_types,
                            tcx,
                            param_env,
                            a_sig.output(),
                            b_sig.output(),
                            ckind,
                        )
                }
                (Tuple(..), Tuple(..)) => {
                    // Tuples are not `repr(C)` so these cannot be compared structurally.
                    false
                }
                // For these, it's not quite as easy to define structural-sameness quite so easily.
                // For the purposes of this lint, take the conservative approach and mark them as
                // not structurally same.
                (Dynamic(..), Dynamic(..))
                | (Error(..), Error(..))
                | (Closure(..), Closure(..))
                | (Coroutine(..), Coroutine(..))
                | (CoroutineWitness(..), CoroutineWitness(..))
                | (Alias(ty::Projection, ..), Alias(ty::Projection, ..))
                | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..))
                | (Alias(ty::Opaque, ..), Alias(ty::Opaque, ..)) => false,

                // These definitely should have been caught above.
                (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),

                // An Adt and a primitive or pointer type. This can be FFI-safe if non-null
                // enum layout optimisation is being applied.
                (Adt(..), _) if is_primitive_or_pointer(b) => {
                    if let Some(a_inner) = types::repr_nullable_ptr(tcx, param_env, a, ckind) {
                        a_inner == b
                    } else {
                        false
                    }
                }
                (_, Adt(..)) if is_primitive_or_pointer(a) => {
                    if let Some(b_inner) = types::repr_nullable_ptr(tcx, param_env, b, ckind) {
                        b_inner == a
                    } else {
                        false
                    }
                }

                _ => false,
            }
        })
    }
}