use rustc_feature::Features; use rustc_hir::attrs::AttributeKind::{LinkName, LinkOrdinal, LinkSection}; use rustc_hir::attrs::*; use rustc_session::Session; use rustc_session::parse::feature_err; use rustc_span::kw; use rustc_target::spec::BinaryFormat; use super::prelude::*; use super::util::parse_single_integer; use crate::attributes::cfg::parse_cfg_entry; use crate::fluent_generated; use crate::session_diagnostics::{ AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, LinkFrameworkApple, LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkSection, RawDylibNoNul, RawDylibOnlyWindows, WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; impl SingleAttributeParser for LinkNameParser { const PATH: &[Symbol] = &[sym::link_name]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ Allow(Target::ForeignFn), Allow(Target::ForeignStatic), ]); const TEMPLATE: AttributeTemplate = template!( NameValueStr: "name", "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute" ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(nv) = args.name_value() else { cx.expected_name_value(cx.attr_span, None); return None; }; let Some(name) = nv.value_as_str() else { cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); return None; }; Some(LinkName { name, span: cx.attr_span }) } } pub(crate) struct LinkParser; impl CombineAttributeParser for LinkParser { type Item = LinkEntry; const PATH: &[Symbol] = &[sym::link]; const CONVERT: ConvertFn = AttributeKind::Link; const TEMPLATE: AttributeTemplate = template!(List: &[ r#"name = "...""#, r#"name = "...", kind = "dylib|static|...""#, r#"name = "...", wasm_import_module = "...""#, r#"name = "...", import_name_type = "decorated|noprefix|undecorated""#, r#"name = "...", kind = "dylib|static|...", wasm_import_module = "...", import_name_type = "decorated|noprefix|undecorated""#, ], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute"); const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` fn extend<'c>( cx: &'c mut AcceptContext<'_, '_, S>, args: &'c ArgParser<'_>, ) -> impl IntoIterator + 'c { let mut result = None; let Some(items) = args.list() else { cx.expected_list(cx.attr_span); return result; }; let sess = cx.sess(); let features = cx.features(); let mut name = None; let mut kind = None; let mut modifiers = None; let mut cfg = None; let mut wasm_import_module = None; let mut import_name_type = None; for item in items.mixed() { let Some(item) = item.meta_item() else { cx.unexpected_literal(item.span()); continue; }; let cont = match item.path().word().map(|ident| ident.name) { Some(sym::name) => Self::parse_link_name(item, &mut name, cx), Some(sym::kind) => Self::parse_link_kind(item, &mut kind, cx, sess, features), Some(sym::modifiers) => Self::parse_link_modifiers(item, &mut modifiers, cx), Some(sym::cfg) => Self::parse_link_cfg(item, &mut cfg, cx, sess, features), Some(sym::wasm_import_module) => { Self::parse_link_wasm_import_module(item, &mut wasm_import_module, cx) } Some(sym::import_name_type) => { Self::parse_link_import_name_type(item, &mut import_name_type, cx) } _ => { cx.expected_specific_argument_strings( item.span(), &[ sym::name, sym::kind, sym::modifiers, sym::cfg, sym::wasm_import_module, sym::import_name_type, ], ); true } }; if !cont { return result; } } // Do this outside the above loop so we don't depend on modifiers coming after kinds let mut verbatim = None; if let Some((modifiers, span)) = modifiers { for modifier in modifiers.as_str().split(',') { let (modifier, value): (Symbol, bool) = match modifier.strip_prefix(&['+', '-']) { Some(m) => (Symbol::intern(m), modifier.starts_with('+')), None => { cx.emit_err(InvalidLinkModifier { span }); continue; } }; macro report_unstable_modifier($feature: ident) { if !features.$feature() { // FIXME: make this translatable #[expect(rustc::untranslatable_diagnostic)] feature_err( sess, sym::$feature, span, format!("linking modifier `{modifier}` is unstable"), ) .emit(); } } let assign_modifier = |dst: &mut Option| { if dst.is_some() { cx.emit_err(MultipleModifiers { span, modifier }); } else { *dst = Some(value); } }; match (modifier, &mut kind) { (sym::bundle, Some(NativeLibKind::Static { bundle, .. })) => { assign_modifier(bundle) } (sym::bundle, _) => { cx.emit_err(BundleNeedsStatic { span }); } (sym::verbatim, _) => assign_modifier(&mut verbatim), ( sym::whole_dash_archive, Some(NativeLibKind::Static { whole_archive, .. }), ) => assign_modifier(whole_archive), (sym::whole_dash_archive, _) => { cx.emit_err(WholeArchiveNeedsStatic { span }); } (sym::as_dash_needed, Some(NativeLibKind::Dylib { as_needed })) | (sym::as_dash_needed, Some(NativeLibKind::Framework { as_needed })) => { report_unstable_modifier!(native_link_modifiers_as_needed); assign_modifier(as_needed) } (sym::as_dash_needed, _) => { cx.emit_err(AsNeededCompatibility { span }); } _ => { cx.expected_specific_argument_strings( span, &[ sym::bundle, sym::verbatim, sym::whole_dash_archive, sym::as_dash_needed, ], ); } } } } if let Some((_, span)) = wasm_import_module { if name.is_some() || kind.is_some() || modifiers.is_some() || cfg.is_some() { cx.emit_err(IncompatibleWasmLink { span }); } } if wasm_import_module.is_some() { (name, kind) = (wasm_import_module, Some(NativeLibKind::WasmImportModule)); } let Some((name, name_span)) = name else { cx.emit_err(LinkRequiresName { span: cx.attr_span }); return result; }; // Do this outside of the loop so that `import_name_type` can be specified before `kind`. if let Some((_, span)) = import_name_type { if kind != Some(NativeLibKind::RawDylib) { cx.emit_err(ImportNameTypeRaw { span }); } } if let Some(NativeLibKind::RawDylib) = kind && name.as_str().contains('\0') { cx.emit_err(RawDylibNoNul { span: name_span }); } result = Some(LinkEntry { span: cx.attr_span, kind: kind.unwrap_or(NativeLibKind::Unspecified), name, cfg, verbatim, import_name_type, }); result } } impl LinkParser { fn parse_link_name( item: &MetaItemParser<'_>, name: &mut Option<(Symbol, Span)>, cx: &mut AcceptContext<'_, '_, S>, ) -> bool { if name.is_some() { cx.duplicate_key(item.span(), sym::name); return true; } let Some(nv) = item.args().name_value() else { cx.expected_name_value(item.span(), Some(sym::name)); return false; }; let Some(link_name) = nv.value_as_str() else { cx.expected_name_value(item.span(), Some(sym::name)); return false; }; if link_name.is_empty() { cx.emit_err(EmptyLinkName { span: nv.value_span }); } *name = Some((link_name, nv.value_span)); true } fn parse_link_kind( item: &MetaItemParser<'_>, kind: &mut Option, cx: &mut AcceptContext<'_, '_, S>, sess: &Session, features: &Features, ) -> bool { if kind.is_some() { cx.duplicate_key(item.span(), sym::kind); return true; } let Some(nv) = item.args().name_value() else { cx.expected_name_value(item.span(), Some(sym::kind)); return true; }; let Some(link_kind) = nv.value_as_str() else { cx.expected_name_value(item.span(), Some(sym::kind)); return true; }; let link_kind = match link_kind { kw::Static => NativeLibKind::Static { bundle: None, whole_archive: None }, sym::dylib => NativeLibKind::Dylib { as_needed: None }, sym::framework => { if !sess.target.is_like_darwin { cx.emit_err(LinkFrameworkApple { span: nv.value_span }); } NativeLibKind::Framework { as_needed: None } } sym::raw_dash_dylib => { if sess.target.is_like_windows { // raw-dylib is stable and working on Windows } else if sess.target.binary_format == BinaryFormat::Elf && features.raw_dylib_elf() { // raw-dylib is unstable on ELF, but the user opted in } else if sess.target.binary_format == BinaryFormat::Elf && sess.is_nightly_build() { feature_err( sess, sym::raw_dylib_elf, nv.value_span, fluent_generated::attr_parsing_raw_dylib_elf_unstable, ) .emit(); } else { cx.emit_err(RawDylibOnlyWindows { span: nv.value_span }); } NativeLibKind::RawDylib } sym::link_dash_arg => { if !features.link_arg_attribute() { feature_err( sess, sym::link_arg_attribute, nv.value_span, fluent_generated::attr_parsing_link_arg_unstable, ) .emit(); } NativeLibKind::LinkArg } _kind => { cx.expected_specific_argument_strings( nv.value_span, &[ kw::Static, sym::dylib, sym::framework, sym::raw_dash_dylib, sym::link_dash_arg, ], ); return true; } }; *kind = Some(link_kind); true } fn parse_link_modifiers( item: &MetaItemParser<'_>, modifiers: &mut Option<(Symbol, Span)>, cx: &mut AcceptContext<'_, '_, S>, ) -> bool { if modifiers.is_some() { cx.duplicate_key(item.span(), sym::modifiers); return true; } let Some(nv) = item.args().name_value() else { cx.expected_name_value(item.span(), Some(sym::modifiers)); return true; }; let Some(link_modifiers) = nv.value_as_str() else { cx.expected_name_value(item.span(), Some(sym::modifiers)); return true; }; *modifiers = Some((link_modifiers, nv.value_span)); true } fn parse_link_cfg( item: &MetaItemParser<'_>, cfg: &mut Option, cx: &mut AcceptContext<'_, '_, S>, sess: &Session, features: &Features, ) -> bool { if cfg.is_some() { cx.duplicate_key(item.span(), sym::cfg); return true; } let Some(link_cfg) = item.args().list() else { cx.expected_list(item.span()); return true; }; let Some(link_cfg) = link_cfg.single() else { cx.expected_single_argument(item.span()); return true; }; if !features.link_cfg() { feature_err( sess, sym::link_cfg, item.span(), fluent_generated::attr_parsing_link_cfg_unstable, ) .emit(); } *cfg = parse_cfg_entry(cx, link_cfg); true } fn parse_link_wasm_import_module( item: &MetaItemParser<'_>, wasm_import_module: &mut Option<(Symbol, Span)>, cx: &mut AcceptContext<'_, '_, S>, ) -> bool { if wasm_import_module.is_some() { cx.duplicate_key(item.span(), sym::wasm_import_module); return true; } let Some(nv) = item.args().name_value() else { cx.expected_name_value(item.span(), Some(sym::wasm_import_module)); return true; }; let Some(link_wasm_import_module) = nv.value_as_str() else { cx.expected_name_value(item.span(), Some(sym::wasm_import_module)); return true; }; *wasm_import_module = Some((link_wasm_import_module, item.span())); true } fn parse_link_import_name_type( item: &MetaItemParser<'_>, import_name_type: &mut Option<(PeImportNameType, Span)>, cx: &mut AcceptContext<'_, '_, S>, ) -> bool { if import_name_type.is_some() { cx.duplicate_key(item.span(), sym::import_name_type); return true; } let Some(nv) = item.args().name_value() else { cx.expected_name_value(item.span(), Some(sym::import_name_type)); return true; }; let Some(link_import_name_type) = nv.value_as_str() else { cx.expected_name_value(item.span(), Some(sym::import_name_type)); return true; }; if cx.sess().target.arch != "x86" { cx.emit_err(ImportNameTypeX86 { span: item.span() }); return true; } let link_import_name_type = match link_import_name_type { sym::decorated => PeImportNameType::Decorated, sym::noprefix => PeImportNameType::NoPrefix, sym::undecorated => PeImportNameType::Undecorated, _ => { cx.expected_specific_argument_strings( item.span(), &[sym::decorated, sym::noprefix, sym::undecorated], ); return true; } }; *import_name_type = Some((link_import_name_type, item.span())); true } } pub(crate) struct LinkSectionParser; impl SingleAttributeParser for LinkSectionParser { const PATH: &[Symbol] = &[sym::link_section]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::WarnButFutureError; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[Allow(Target::Static), Allow(Target::Fn)]); const TEMPLATE: AttributeTemplate = template!( NameValueStr: "name", "https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute" ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(nv) = args.name_value() else { cx.expected_name_value(cx.attr_span, None); return None; }; let Some(name) = nv.value_as_str() else { cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); return None; }; if name.as_str().contains('\0') { // `#[link_section = ...]` will be converted to a null-terminated string, // so it may not contain any null characters. cx.emit_err(NullOnLinkSection { span: cx.attr_span }); return None; } Some(LinkSection { name, span: cx.attr_span }) } } pub(crate) struct ExportStableParser; impl NoArgsAttributeParser for ExportStableParser { const PATH: &[Symbol] = &[sym::export_stable]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs` const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ExportStable; } pub(crate) struct FfiConstParser; impl NoArgsAttributeParser for FfiConstParser { const PATH: &[Symbol] = &[sym::ffi_const]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiConst; } pub(crate) struct FfiPureParser; impl NoArgsAttributeParser for FfiPureParser { const PATH: &[Symbol] = &[sym::ffi_pure]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; } pub(crate) struct StdInternalSymbolParser; impl NoArgsAttributeParser for StdInternalSymbolParser { const PATH: &[Symbol] = &[sym::rustc_std_internal_symbol]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::ForeignFn), Allow(Target::Static), Allow(Target::ForeignStatic), ]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::StdInternalSymbol; } pub(crate) struct LinkOrdinalParser; impl SingleAttributeParser for LinkOrdinalParser { const PATH: &[Symbol] = &[sym::link_ordinal]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::ForeignFn), Allow(Target::ForeignStatic), Warn(Target::MacroCall), ]); const TEMPLATE: AttributeTemplate = template!( List: &["ordinal"], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute" ); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let ordinal = parse_single_integer(cx, args)?; // According to the table at // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-header, the // ordinal must fit into 16 bits. Similarly, the Ordinal field in COFFShortExport (defined // in llvm/include/llvm/Object/COFFImportFile.h), which we use to communicate import // information to LLVM for `#[link(kind = "raw-dylib"_])`, is also defined to be uint16_t. // // FIXME: should we allow an ordinal of 0? The MSVC toolchain has inconsistent support for // this: both LINK.EXE and LIB.EXE signal errors and abort when given a .DEF file that // specifies a zero ordinal. However, llvm-dlltool is perfectly happy to generate an import // library for such a .DEF file, and MSVC's LINK.EXE is also perfectly happy to consume an // import library produced by LLVM with an ordinal of 0, and it generates an .EXE. (I // don't know yet if the resulting EXE runs, as I haven't yet built the necessary DLL -- // see earlier comment about LINK.EXE failing.) let Ok(ordinal) = ordinal.try_into() else { cx.emit_err(LinkOrdinalOutOfRange { span: cx.attr_span, ordinal }); return None; }; Some(LinkOrdinal { ordinal, span: cx.attr_span }) } } pub(crate) struct LinkageParser; impl SingleAttributeParser for LinkageParser { const PATH: &[Symbol] = &[sym::linkage]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), Allow(Target::Method(MethodKind::Trait { body: false })), Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), Allow(Target::Static), Allow(Target::ForeignStatic), Allow(Target::ForeignFn), ]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: [ "available_externally", "common", "extern_weak", "external", "internal", "linkonce", "linkonce_odr", "weak", "weak_odr", ]); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option { let Some(name_value) = args.name_value() else { cx.expected_name_value(cx.attr_span, Some(sym::linkage)); return None; }; let Some(value) = name_value.value_as_str() else { cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit())); return None; }; // Use the names from src/llvm/docs/LangRef.rst here. Most types are only // applicable to variable declarations and may not really make sense for // Rust code in the first place but allow them anyway and trust that the // user knows what they're doing. Who knows, unanticipated use cases may pop // up in the future. // // ghost, dllimport, dllexport and linkonce_odr_autohide are not supported // and don't have to be, LLVM treats them as no-ops. let linkage = match value { sym::available_externally => Linkage::AvailableExternally, sym::common => Linkage::Common, sym::extern_weak => Linkage::ExternalWeak, sym::external => Linkage::External, sym::internal => Linkage::Internal, sym::linkonce => Linkage::LinkOnceAny, sym::linkonce_odr => Linkage::LinkOnceODR, sym::weak => Linkage::WeakAny, sym::weak_odr => Linkage::WeakODR, _ => { cx.expected_specific_argument( name_value.value_span, &[ sym::available_externally, sym::common, sym::extern_weak, sym::external, sym::internal, sym::linkonce, sym::linkonce_odr, sym::weak, sym::weak_odr, ], ); return None; } }; Some(AttributeKind::Linkage(linkage, cx.attr_span)) } }