rust/compiler/rustc_lint/src/foreign_modules.rs

398 lines
17 KiB
Rust

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,
ty::TypingEnv::non_body_analysis(tcx, 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>,
typing_env: ty::TypingEnv<'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, typing_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(typing_env.as_query_input(a)).unwrap();
let b_layout = tcx.layout_of(typing_env.as_query_input(b)).unwrap();
assert_eq!(a_layout.backend_repr, b_layout.backend_repr);
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>,
typing_env: ty::TypingEnv<'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!(?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,
typing_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, typing_env, *a_ty, *b_ty, ckind,
)
}
(Slice(a_ty), Slice(b_ty)) => {
structurally_same_type_impl(seen_types, tcx, typing_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, typing_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, typing_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, typing_env, *a, *b, ckind)
})
&& structurally_same_type_impl(
seen_types,
tcx,
typing_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(..) | Pat(..), _) if is_primitive_or_pointer(b) => {
if let Some(a_inner) = types::repr_nullable_ptr(tcx, typing_env, a, ckind) {
a_inner == b
} else {
false
}
}
(_, Adt(..) | Pat(..)) if is_primitive_or_pointer(a) => {
if let Some(b_inner) = types::repr_nullable_ptr(tcx, typing_env, b, ckind) {
b_inner == a
} else {
false
}
}
_ => false,
}
})
}
}