#![deny(clippy::arithmetic_side_effects)]
use crate::error::Result;
use super::inputs::CoffInput;
use super::read::{Directive, ExportSpec, Source, parse_directives};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ExportRequest {
pub name: Vec<u8>,
pub internal: Option<Vec<u8>>,
pub forwarder: Option<Vec<u8>>,
pub ordinal: Option<u16>,
pub noname: bool,
pub data: bool,
pub private: bool,
}
impl ExportRequest {
#[must_use]
pub fn from_spec(spec: &ExportSpec<'_>) -> Self {
let forwarder = spec.forwarder().map(<[u8]>::to_vec);
Self {
name: spec.export_as.as_deref().unwrap_or(&spec.name).to_vec(),
internal: if forwarder.is_some() {
None
} else {
spec.internal_name.as_deref().map(<[u8]>::to_vec)
},
forwarder,
ordinal: spec.ordinal,
noname: spec.noname,
data: spec.data,
private: spec.private,
}
}
#[must_use]
pub fn symbol(&self) -> &[u8] {
self.internal.as_deref().unwrap_or(&self.name)
}
}
#[derive(Clone, Debug, Default)]
pub struct Directives {
pub exports: Vec<ExportRequest>,
pub includes: Vec<Vec<u8>>,
pub default_libs: Vec<Vec<u8>>,
pub no_default_libs: Vec<Vec<u8>>,
pub no_default_lib: bool,
pub alternate_names: Vec<(Vec<u8>, Vec<u8>)>,
pub align_comm: Vec<(Vec<u8>, u32)>,
pub exclude_symbols: Vec<Vec<u8>>,
pub entry: Option<Vec<u8>>,
pub subsystem: Option<Vec<u8>>,
pub stack: Option<(u64, Option<u64>)>,
pub heap: Option<(u64, Option<u64>)>,
pub merges: Vec<(Vec<u8>, Vec<u8>)>,
}
impl Directives {
pub fn add_from(&mut self, input: &CoffInput<'_>) -> Result<()> {
let Some(parsed) = input.object() else {
return Ok(());
};
let source = match input.file {
Some(file) => super::inputs::source_of(file),
None => Source::new(std::path::Path::new("<internal>")),
};
for data in &parsed.directives {
for directive in parse_directives(data, 0, source) {
self.apply(&directive?);
}
}
Ok(())
}
pub fn apply(&mut self, directive: &Directive<'_>) {
match directive {
Directive::Export(spec) => {
let request = ExportRequest::from_spec(spec);
if !self.exports.contains(&request) {
self.exports.push(request);
}
}
Directive::Include(name) | Directive::IncludeOptional(name) => {
push_unique(&mut self.includes, name);
}
Directive::DefaultLib(name) => push_unique(&mut self.default_libs, name),
Directive::NoDefaultLib(None) => self.no_default_lib = true,
Directive::NoDefaultLib(Some(name)) => {
push_unique(&mut self.no_default_libs, name);
}
Directive::AlternateName { alias, target } => {
let pair = (alias.to_vec(), target.to_vec());
if !self.alternate_names.contains(&pair) {
self.alternate_names.push(pair);
}
}
Directive::AlignComm {
symbol,
alignment_log2,
} => self.align_comm.push((symbol.to_vec(), *alignment_log2)),
Directive::ExcludeSymbols(list) => {
for name in super::read::directives::split_list(list) {
push_unique(&mut self.exclude_symbols, name);
}
}
Directive::Entry(name) => self.entry = Some(name.to_vec()),
Directive::Subsystem(name) => self.subsystem = Some(name.to_vec()),
Directive::Stack { reserve, commit } => self.stack = Some((*reserve, *commit)),
Directive::Heap { reserve, commit } => self.heap = Some((*reserve, *commit)),
Directive::Merge { from, to } => self.merges.push((from.to_vec(), to.to_vec())),
_ => {}
}
}
#[must_use]
pub fn wanted_libraries(&self, no_default_lib: bool) -> Vec<Vec<u8>> {
if no_default_lib || self.no_default_lib {
return Vec::new();
}
self.default_libs
.iter()
.filter(|name| {
!self
.no_default_libs
.iter()
.any(|excluded| equal_ignoring_case(excluded, name))
})
.cloned()
.collect()
}
}
fn push_unique(list: &mut Vec<Vec<u8>>, name: &[u8]) {
if !list.iter().any(|existing| existing == name) {
list.push(name.to_vec());
}
}
fn equal_ignoring_case(left: &[u8], right: &[u8]) -> bool {
let trim = |name: &[u8]| {
let lowered: Vec<u8> = name.to_ascii_lowercase();
match lowered.strip_suffix(b".lib") {
Some(base) => base.to_vec(),
None => lowered,
}
};
trim(left) == trim(right)
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use super::*;
fn directive(text: &[u8]) -> Directive<'_> {
super::super::read::directives::parse_token(text).unwrap()
}
#[test]
fn collects_what_the_linker_acts_on() {
let mut directives = Directives::default();
for text in [
&b"-export:foo,data"[..],
b"-export:foo,data",
b"-include:bar",
b"-defaultlib:LIBCMT",
b"-defaultlib:oldnames",
b"-nodefaultlib:oldnames.lib",
b"-alternatename:a=b",
b"-aligncomm:c,4",
b"-entry:go",
] {
directives.apply(&directive(text));
}
assert_eq!(directives.exports.len(), 1, "duplicates are folded");
assert!(directives.exports[0].data);
assert_eq!(directives.includes, [b"bar".to_vec()]);
assert_eq!(
directives.wanted_libraries(false),
[b"LIBCMT".to_vec()],
"-nodefaultlib:oldnames.lib removes oldnames"
);
assert!(directives.wanted_libraries(true).is_empty());
assert_eq!(directives.alternate_names, [(b"a".to_vec(), b"b".to_vec())]);
assert_eq!(directives.align_comm, [(b"c".to_vec(), 4)]);
assert_eq!(directives.entry.as_deref(), Some(&b"go"[..]));
}
#[test]
fn forwarders_and_renames() {
let spec = ExportSpec {
name: Cow::Borrowed(b"outer"),
internal_name: Some(Cow::Borrowed(b"inner")),
..ExportSpec::default()
};
let request = ExportRequest::from_spec(&spec);
assert_eq!(request.symbol(), b"inner");
assert!(request.forwarder.is_none());
let spec = ExportSpec {
name: Cow::Borrowed(b"Sleep"),
internal_name: Some(Cow::Borrowed(b"kernel32.Sleep")),
..ExportSpec::default()
};
let request = ExportRequest::from_spec(&spec);
assert_eq!(request.forwarder.as_deref(), Some(&b"kernel32.Sleep"[..]));
}
}