]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_metadata/src/native_libs.rs
Fix off by one error and add ui test.
[rust.git] / compiler / rustc_metadata / src / native_libs.rs
1 use rustc_ast::{NestedMetaItem, CRATE_NODE_ID};
2 use rustc_attr as attr;
3 use rustc_data_structures::fx::FxHashSet;
4 use rustc_errors::struct_span_err;
5 use rustc_hir as hir;
6 use rustc_hir::def::DefKind;
7 use rustc_middle::ty::{List, ParamEnv, ParamEnvAnd, Ty, TyCtxt};
8 use rustc_session::cstore::{DllCallingConvention, DllImport, NativeLib};
9 use rustc_session::parse::feature_err;
10 use rustc_session::utils::NativeLibKind;
11 use rustc_session::Session;
12 use rustc_span::symbol::{sym, Symbol};
13 use rustc_target::spec::abi::Abi;
14
15 pub(crate) fn collect(tcx: TyCtxt<'_>) -> Vec<NativeLib> {
16     let mut collector = Collector { tcx, libs: Vec::new() };
17     for id in tcx.hir().items() {
18         collector.process_item(id);
19     }
20     collector.process_command_line();
21     collector.libs
22 }
23
24 pub(crate) fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {
25     match lib.cfg {
26         Some(ref cfg) => attr::cfg_matches(cfg, &sess.parse_sess, CRATE_NODE_ID, None),
27         None => true,
28     }
29 }
30
31 struct Collector<'tcx> {
32     tcx: TyCtxt<'tcx>,
33     libs: Vec<NativeLib>,
34 }
35
36 impl<'tcx> Collector<'tcx> {
37     fn process_item(&mut self, id: rustc_hir::ItemId) {
38         if !matches!(self.tcx.def_kind(id.def_id), DefKind::ForeignMod) {
39             return;
40         }
41
42         let it = self.tcx.hir().item(id);
43         let hir::ItemKind::ForeignMod { abi, items: foreign_mod_items } = it.kind else {
44             return;
45         };
46
47         if abi == Abi::Rust || abi == Abi::RustIntrinsic || abi == Abi::PlatformIntrinsic {
48             return;
49         }
50
51         // Process all of the #[link(..)]-style arguments
52         let sess = &self.tcx.sess;
53         let features = self.tcx.features();
54         for m in self.tcx.hir().attrs(it.hir_id()).iter().filter(|a| a.has_name(sym::link)) {
55             let Some(items) = m.meta_item_list() else {
56                 continue;
57             };
58
59             let mut name = None;
60             let mut kind = None;
61             let mut modifiers = None;
62             let mut cfg = None;
63             let mut wasm_import_module = None;
64             for item in items.iter() {
65                 match item.name_or_empty() {
66                     sym::name => {
67                         if name.is_some() {
68                             let msg = "multiple `name` arguments in a single `#[link]` attribute";
69                             sess.span_err(item.span(), msg);
70                             continue;
71                         }
72                         let Some(link_name) = item.value_str() else {
73                             let msg = "link name must be of the form `name = \"string\"`";
74                             sess.span_err(item.span(), msg);
75                             continue;
76                         };
77                         let span = item.name_value_literal_span().unwrap();
78                         if link_name.is_empty() {
79                             struct_span_err!(sess, span, E0454, "link name must not be empty")
80                                 .span_label(span, "empty link name")
81                                 .emit();
82                         }
83                         name = Some((link_name, span));
84                     }
85                     sym::kind => {
86                         if kind.is_some() {
87                             let msg = "multiple `kind` arguments in a single `#[link]` attribute";
88                             sess.span_err(item.span(), msg);
89                             continue;
90                         }
91                         let Some(link_kind) = item.value_str() else {
92                             let msg = "link kind must be of the form `kind = \"string\"`";
93                             sess.span_err(item.span(), msg);
94                             continue;
95                         };
96
97                         let span = item.name_value_literal_span().unwrap();
98                         let link_kind = match link_kind.as_str() {
99                             "static" => NativeLibKind::Static { bundle: None, whole_archive: None },
100                             "dylib" => NativeLibKind::Dylib { as_needed: None },
101                             "framework" => {
102                                 if !sess.target.is_like_osx {
103                                     struct_span_err!(
104                                         sess,
105                                         span,
106                                         E0455,
107                                         "link kind `framework` is only supported on Apple targets"
108                                     )
109                                     .emit();
110                                 }
111                                 NativeLibKind::Framework { as_needed: None }
112                             }
113                             "raw-dylib" => {
114                                 if !sess.target.is_like_windows {
115                                     struct_span_err!(
116                                         sess,
117                                         span,
118                                         E0455,
119                                         "link kind `raw-dylib` is only supported on Windows targets"
120                                     )
121                                     .emit();
122                                 } else if !features.raw_dylib {
123                                     feature_err(
124                                         &sess.parse_sess,
125                                         sym::raw_dylib,
126                                         span,
127                                         "link kind `raw-dylib` is unstable",
128                                     )
129                                     .emit();
130                                 }
131                                 NativeLibKind::RawDylib
132                             }
133                             kind => {
134                                 let msg = format!(
135                                     "unknown link kind `{kind}`, expected one of: \
136                                      static, dylib, framework, raw-dylib"
137                                 );
138                                 struct_span_err!(sess, span, E0458, "{}", msg)
139                                     .span_label(span, "unknown link kind")
140                                     .emit();
141                                 continue;
142                             }
143                         };
144                         kind = Some(link_kind);
145                     }
146                     sym::modifiers => {
147                         if modifiers.is_some() {
148                             let msg =
149                                 "multiple `modifiers` arguments in a single `#[link]` attribute";
150                             sess.span_err(item.span(), msg);
151                             continue;
152                         }
153                         let Some(link_modifiers) = item.value_str() else {
154                             let msg = "link modifiers must be of the form `modifiers = \"string\"`";
155                             sess.span_err(item.span(), msg);
156                             continue;
157                         };
158                         modifiers = Some((link_modifiers, item.name_value_literal_span().unwrap()));
159                     }
160                     sym::cfg => {
161                         if cfg.is_some() {
162                             let msg = "multiple `cfg` arguments in a single `#[link]` attribute";
163                             sess.span_err(item.span(), msg);
164                             continue;
165                         }
166                         let Some(link_cfg) = item.meta_item_list() else {
167                             let msg = "link cfg must be of the form `cfg(/* predicate */)`";
168                             sess.span_err(item.span(), msg);
169                             continue;
170                         };
171                         let [NestedMetaItem::MetaItem(link_cfg)] = link_cfg else {
172                             let msg = "link cfg must have a single predicate argument";
173                             sess.span_err(item.span(), msg);
174                             continue;
175                         };
176                         if !features.link_cfg {
177                             feature_err(
178                                 &sess.parse_sess,
179                                 sym::link_cfg,
180                                 item.span(),
181                                 "link cfg is unstable",
182                             )
183                             .emit();
184                         }
185                         cfg = Some(link_cfg.clone());
186                     }
187                     sym::wasm_import_module => {
188                         if wasm_import_module.is_some() {
189                             let msg = "multiple `wasm_import_module` arguments \
190                                        in a single `#[link]` attribute";
191                             sess.span_err(item.span(), msg);
192                             continue;
193                         }
194                         let Some(link_wasm_import_module) = item.value_str() else {
195                             let msg = "wasm import module must be of the form \
196                                        `wasm_import_module = \"string\"`";
197                             sess.span_err(item.span(), msg);
198                             continue;
199                         };
200                         wasm_import_module = Some((link_wasm_import_module, item.span()));
201                     }
202                     _ => {
203                         let msg = "unexpected `#[link]` argument, expected one of: \
204                                    name, kind, modifiers, cfg, wasm_import_module";
205                         sess.span_err(item.span(), msg);
206                     }
207                 }
208             }
209
210             // Do this outside the above loop so we don't depend on modifiers coming after kinds
211             let mut verbatim = None;
212             if let Some((modifiers, span)) = modifiers {
213                 for modifier in modifiers.as_str().split(',') {
214                     let (modifier, value) = match modifier.strip_prefix(&['+', '-']) {
215                         Some(m) => (m, modifier.starts_with('+')),
216                         None => {
217                             sess.span_err(
218                                 span,
219                                 "invalid linking modifier syntax, expected '+' or '-' prefix \
220                                 before one of: bundle, verbatim, whole-archive, as-needed",
221                             );
222                             continue;
223                         }
224                     };
225
226                     macro report_unstable_modifier($feature: ident) {
227                         if !features.$feature {
228                             feature_err(
229                                 &sess.parse_sess,
230                                 sym::$feature,
231                                 span,
232                                 &format!("linking modifier `{modifier}` is unstable"),
233                             )
234                             .emit();
235                         }
236                     }
237                     let assign_modifier = |dst: &mut Option<bool>| {
238                         if dst.is_some() {
239                             let msg = format!(
240                                 "multiple `{modifier}` modifiers in a single `modifiers` argument"
241                             );
242                             sess.span_err(span, &msg);
243                         } else {
244                             *dst = Some(value);
245                         }
246                     };
247                     match (modifier, &mut kind) {
248                         ("bundle", Some(NativeLibKind::Static { bundle, .. })) => {
249                             assign_modifier(bundle)
250                         }
251                         ("bundle", _) => {
252                             sess.span_err(
253                                 span,
254                                 "linking modifier `bundle` is only compatible with \
255                                  `static` linking kind",
256                             );
257                         }
258
259                         ("verbatim", _) => {
260                             report_unstable_modifier!(native_link_modifiers_verbatim);
261                             assign_modifier(&mut verbatim)
262                         }
263
264                         ("whole-archive", Some(NativeLibKind::Static { whole_archive, .. })) => {
265                             assign_modifier(whole_archive)
266                         }
267                         ("whole-archive", _) => {
268                             sess.span_err(
269                                 span,
270                                 "linking modifier `whole-archive` is only compatible with \
271                                  `static` linking kind",
272                             );
273                         }
274
275                         ("as-needed", Some(NativeLibKind::Dylib { as_needed }))
276                         | ("as-needed", Some(NativeLibKind::Framework { as_needed })) => {
277                             report_unstable_modifier!(native_link_modifiers_as_needed);
278                             assign_modifier(as_needed)
279                         }
280                         ("as-needed", _) => {
281                             sess.span_err(
282                                 span,
283                                 "linking modifier `as-needed` is only compatible with \
284                                  `dylib` and `framework` linking kinds",
285                             );
286                         }
287
288                         _ => {
289                             sess.span_err(
290                                 span,
291                                 format!(
292                                     "unknown linking modifier `{modifier}`, expected one of: \
293                                      bundle, verbatim, whole-archive, as-needed"
294                                 ),
295                             );
296                         }
297                     }
298                 }
299             }
300
301             if let Some((_, span)) = wasm_import_module {
302                 if name.is_some() || kind.is_some() || modifiers.is_some() || cfg.is_some() {
303                     let msg = "`wasm_import_module` is incompatible with \
304                                other arguments in `#[link]` attributes";
305                     sess.span_err(span, msg);
306                 }
307             } else if name.is_none() {
308                 struct_span_err!(
309                     sess,
310                     m.span,
311                     E0459,
312                     "`#[link]` attribute requires a `name = \"string\"` argument"
313                 )
314                 .span_label(m.span, "missing `name` argument")
315                 .emit();
316             }
317
318             let dll_imports = match kind {
319                 Some(NativeLibKind::RawDylib) => {
320                     if let Some((name, span)) = name && name.as_str().contains('\0') {
321                         sess.span_err(
322                             span,
323                             "link name must not contain NUL characters if link kind is `raw-dylib`",
324                         );
325                     }
326                     foreign_mod_items
327                         .iter()
328                         .map(|child_item| self.build_dll_import(abi, child_item))
329                         .collect()
330                 }
331                 _ => Vec::new(),
332             };
333             self.libs.push(NativeLib {
334                 name: name.map(|(name, _)| name),
335                 kind: kind.unwrap_or(NativeLibKind::Unspecified),
336                 cfg,
337                 foreign_module: Some(it.def_id.to_def_id()),
338                 wasm_import_module: wasm_import_module.map(|(name, _)| name),
339                 verbatim,
340                 dll_imports,
341             });
342         }
343     }
344
345     // Process libs passed on the command line
346     fn process_command_line(&mut self) {
347         // First, check for errors
348         let mut renames = FxHashSet::default();
349         for lib in &self.tcx.sess.opts.libs {
350             if let NativeLibKind::Framework { .. } = lib.kind && !self.tcx.sess.target.is_like_osx {
351                 // Cannot check this when parsing options because the target is not yet available.
352                 self.tcx.sess.err("library kind `framework` is only supported on Apple targets");
353             }
354             if let Some(ref new_name) = lib.new_name {
355                 let any_duplicate = self
356                     .libs
357                     .iter()
358                     .filter_map(|lib| lib.name.as_ref())
359                     .any(|n| n.as_str() == lib.name);
360                 if new_name.is_empty() {
361                     self.tcx.sess.err(format!(
362                         "an empty renaming target was specified for library `{}`",
363                         lib.name
364                     ));
365                 } else if !any_duplicate {
366                     self.tcx.sess.err(format!(
367                         "renaming of the library `{}` was specified, \
368                                                 however this crate contains no `#[link(...)]` \
369                                                 attributes referencing this library",
370                         lib.name
371                     ));
372                 } else if !renames.insert(&lib.name) {
373                     self.tcx.sess.err(format!(
374                         "multiple renamings were \
375                                                 specified for library `{}`",
376                         lib.name
377                     ));
378                 }
379             }
380         }
381
382         // Update kind and, optionally, the name of all native libraries
383         // (there may be more than one) with the specified name.  If any
384         // library is mentioned more than once, keep the latest mention
385         // of it, so that any possible dependent libraries appear before
386         // it.  (This ensures that the linker is able to see symbols from
387         // all possible dependent libraries before linking in the library
388         // in question.)
389         for passed_lib in &self.tcx.sess.opts.libs {
390             // If we've already added any native libraries with the same
391             // name, they will be pulled out into `existing`, so that we
392             // can move them to the end of the list below.
393             let mut existing = self
394                 .libs
395                 .drain_filter(|lib| {
396                     if let Some(lib_name) = lib.name {
397                         if lib_name.as_str() == passed_lib.name {
398                             // FIXME: This whole logic is questionable, whether modifiers are
399                             // involved or not, library reordering and kind overriding without
400                             // explicit `:rename` in particular.
401                             if lib.has_modifiers() || passed_lib.has_modifiers() {
402                                 let msg = "overriding linking modifiers from command line is not supported";
403                                 match lib.foreign_module {
404                                     Some(def_id) => self.tcx.sess.span_err(self.tcx.def_span(def_id), msg),
405                                     None => self.tcx.sess.err(msg),
406                                 };
407                             }
408                             if passed_lib.kind != NativeLibKind::Unspecified {
409                                 lib.kind = passed_lib.kind;
410                             }
411                             if let Some(new_name) = &passed_lib.new_name {
412                                 lib.name = Some(Symbol::intern(new_name));
413                             }
414                             lib.verbatim = passed_lib.verbatim;
415                             return true;
416                         }
417                     }
418                     false
419                 })
420                 .collect::<Vec<_>>();
421             if existing.is_empty() {
422                 // Add if not found
423                 let new_name: Option<&str> = passed_lib.new_name.as_deref();
424                 self.libs.push(NativeLib {
425                     name: Some(Symbol::intern(new_name.unwrap_or(&passed_lib.name))),
426                     kind: passed_lib.kind,
427                     cfg: None,
428                     foreign_module: None,
429                     wasm_import_module: None,
430                     verbatim: passed_lib.verbatim,
431                     dll_imports: Vec::new(),
432                 });
433             } else {
434                 // Move all existing libraries with the same name to the
435                 // end of the command line.
436                 self.libs.append(&mut existing);
437             }
438         }
439     }
440
441     fn i686_arg_list_size(&self, item: &hir::ForeignItemRef) -> usize {
442         let argument_types: &List<Ty<'_>> = self.tcx.erase_late_bound_regions(
443             self.tcx
444                 .type_of(item.id.def_id)
445                 .fn_sig(self.tcx)
446                 .inputs()
447                 .map_bound(|slice| self.tcx.mk_type_list(slice.iter())),
448         );
449
450         argument_types
451             .iter()
452             .map(|ty| {
453                 let layout = self
454                     .tcx
455                     .layout_of(ParamEnvAnd { param_env: ParamEnv::empty(), value: ty })
456                     .expect("layout")
457                     .layout;
458                 // In both stdcall and fastcall, we always round up the argument size to the
459                 // nearest multiple of 4 bytes.
460                 (layout.size().bytes_usize() + 3) & !3
461             })
462             .sum()
463     }
464
465     fn build_dll_import(&self, abi: Abi, item: &hir::ForeignItemRef) -> DllImport {
466         let calling_convention = if self.tcx.sess.target.arch == "x86" {
467             match abi {
468                 Abi::C { .. } | Abi::Cdecl { .. } => DllCallingConvention::C,
469                 Abi::Stdcall { .. } | Abi::System { .. } => {
470                     DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
471                 }
472                 Abi::Fastcall { .. } => {
473                     DllCallingConvention::Fastcall(self.i686_arg_list_size(item))
474                 }
475                 // Vectorcall is intentionally not supported at this time.
476                 _ => {
477                     self.tcx.sess.span_fatal(
478                         item.span,
479                         r#"ABI not supported by `#[link(kind = "raw-dylib")]` on i686"#,
480                     );
481                 }
482             }
483         } else {
484             match abi {
485                 Abi::C { .. } | Abi::Win64 { .. } | Abi::System { .. } => DllCallingConvention::C,
486                 _ => {
487                     self.tcx.sess.span_fatal(
488                         item.span,
489                         r#"ABI not supported by `#[link(kind = "raw-dylib")]` on this architecture"#,
490                     );
491                 }
492             }
493         };
494
495         DllImport {
496             name: item.ident.name,
497             ordinal: self.tcx.codegen_fn_attrs(item.id.def_id).link_ordinal,
498             calling_convention,
499             span: item.span,
500         }
501     }
502 }