chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

393
vendor/ar_archive_writer/tests/common.rs vendored Normal file
View File

@@ -0,0 +1,393 @@
#![allow(dead_code)]
use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::process::Command;
use ar_archive_writer::{ArchiveKind, NewArchiveMember};
use object::write::{self, Object};
use object::{
Architecture, BinaryFormat, Endianness, SubArchitecture, SymbolFlags, SymbolKind, SymbolScope,
};
use pretty_assertions::assert_eq;
/// Creates the temporary directory for a test.
pub fn create_tmp_dir(test_name: &str) -> PathBuf {
let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(test_name);
match fs::remove_dir_all(&tmpdir) {
Ok(_) => {}
Err(err) => {
if err.kind() != std::io::ErrorKind::NotFound {
panic!("Failed to delete directory: {tmpdir:?}");
}
}
}
fs::create_dir_all(&tmpdir).unwrap();
tmpdir
}
/// Creates a symlink to `llvm-ar` so that it acts like `llvm-lib`.
pub fn create_llvm_lib_tool(tmp_dir: &Path) -> PathBuf {
let ar_path = cargo_binutils::Tool::Ar.path().unwrap();
let lib_path = tmp_dir.join("llvm-lib");
#[cfg(unix)]
std::os::unix::fs::symlink(ar_path, &lib_path).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_file(ar_path, &lib_path).unwrap();
lib_path
}
/// Creates a symlink to `llvm-ar` so that it acts like `llvm-dlltool`.
pub fn create_llvm_dlltool_tool(tmp_dir: &Path) -> PathBuf {
let ar_path = cargo_binutils::Tool::Ar.path().unwrap();
let lib_path = tmp_dir.join("llvm-dlltool");
#[cfg(unix)]
std::os::unix::fs::symlink(ar_path, &lib_path).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_file(ar_path, &lib_path).unwrap();
lib_path
}
fn run_llvm_ar(
object_paths: &[PathBuf],
archive_path: &Path,
archive_kind: ArchiveKind,
thin: bool,
is_ec: bool,
) {
// FIXME: LLVM 19 adds support for "coff" as a format argument, so in the
// meantime, we'll instruct llvm-ar to pretend to be llvm-lib.
let output = if archive_kind == ArchiveKind::Coff {
let lib_path = create_llvm_lib_tool(archive_path.parent().unwrap());
let mut command = Command::new(lib_path);
if is_ec {
command.arg("/machine:arm64ec");
}
// llvm-lib reverses the order of the files versus llvm-ar.
let mut object_paths = Vec::from(object_paths);
object_paths.reverse();
command
.arg("/OUT:".to_string() + archive_path.to_str().unwrap())
.args(object_paths)
.output()
.unwrap()
} else {
let ar_path = cargo_binutils::Tool::Ar.path().unwrap();
let mut command = Command::new(ar_path);
let format_arg = match archive_kind {
ArchiveKind::AixBig => "bigarchive",
ArchiveKind::Darwin => "darwin",
ArchiveKind::Gnu => "gnu",
_ => panic!("unsupported archive kind: {archive_kind:?}"),
};
command.arg(format!("--format={format_arg}"));
if thin {
command.arg("--thin");
}
command
.arg("rcs")
.arg(archive_path)
.args(object_paths)
.output()
.unwrap()
};
assert_eq!(
String::from_utf8_lossy(&output.stderr),
"",
"llvm-ar failed. archive: {archive_path:?}"
);
}
/// Creates an archive with the given objects using `llvm-ar`.
/// The generated archive is written to disk as `output_llvm_ar.a`.
pub fn create_archive_with_llvm_ar<'name, 'data>(
tmpdir: &Path,
archive_kind: ArchiveKind,
input_objects: impl IntoIterator<Item = (&'name str, &'data [u8])>,
thin: bool,
is_ec: bool,
) -> Vec<u8> {
let archive_file_path = tmpdir.join("output_llvm_ar.a");
let input_file_paths = input_objects
.into_iter()
.map(|(name, bytes)| {
let input_file_path = tmpdir.join(name);
if name.contains('/') {
fs::create_dir_all(input_file_path.parent().unwrap()).unwrap();
}
fs::write(&input_file_path, bytes).unwrap();
input_file_path
})
.collect::<Vec<_>>();
run_llvm_ar(
&input_file_paths,
&archive_file_path,
archive_kind,
thin,
is_ec,
);
fs::read(archive_file_path).unwrap()
}
/// Creates an archive with the given objects using `ar_archive_writer`.
/// The generated archive is written to disk as `output_ar_archive_writer.a`.
pub fn create_archive_with_ar_archive_writer<'name, 'data>(
tmpdir: &Path,
archive_kind: ArchiveKind,
input_objects: impl IntoIterator<Item = (&'name str, &'data [u8])>,
thin: bool,
is_ec: bool,
) -> Vec<u8> {
let members = input_objects
.into_iter()
.map(|(name, bytes)| {
let member_name = if thin {
// Thin archives use the full path to the object file.
tmpdir
.join(name)
.to_string_lossy()
.replace(std::path::MAIN_SEPARATOR, "/")
} else if archive_kind == ArchiveKind::Coff {
// For COFF archives, we are running llvm-ar as lib.exe, which
// uses the full path to the object file.
tmpdir.join(name).to_string_lossy().to_string()
} else {
// Non-thin archives use the file name only.
name.rsplit_once('/')
.map_or(name, |(_, filename)| filename)
.to_string()
};
NewArchiveMember::new(
bytes,
&ar_archive_writer::DEFAULT_OBJECT_READER,
member_name,
)
})
.collect::<Vec<_>>();
let mut output_bytes = Cursor::new(Vec::new());
ar_archive_writer::write_archive_to_stream(
&mut output_bytes,
&members,
archive_kind,
thin,
Some(is_ec),
)
.unwrap();
let output_archive_bytes = output_bytes.into_inner();
let ar_archive_writer_file_path = tmpdir.join("output_ar_archive_writer.a");
fs::write(ar_archive_writer_file_path, &output_archive_bytes).unwrap();
output_archive_bytes
}
/// Helper for comparing archives generated by `llvm-ar` and `ar_archive_writer`
/// across a variety of archive kinds and their relevant object formats.
pub fn generate_archive_and_compare<F>(test_name: &str, generate_objects: F)
where
F: Fn(
Architecture,
Option<SubArchitecture>,
Endianness,
BinaryFormat,
) -> Vec<(&'static str, Vec<u8>)>,
{
for (architecture, subarch, endianness, binary_format, archive_kind, thin) in [
// Elf + GNU + non-thin
(
Architecture::X86_64,
None,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
false,
),
(
Architecture::I386,
None,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
false,
),
(
Architecture::Aarch64,
None,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
false,
),
// Elf + GNU + thin
(
Architecture::X86_64,
None,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
true,
),
(
Architecture::I386,
None,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
true,
),
(
Architecture::Aarch64,
None,
Endianness::Little,
BinaryFormat::Elf,
ArchiveKind::Gnu,
true,
),
// AIX Big
(
Architecture::PowerPc64,
None,
Endianness::Big,
BinaryFormat::Elf,
ArchiveKind::AixBig,
false,
),
// PE + GNU
(
Architecture::X86_64,
None,
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Gnu,
false,
),
(
Architecture::I386,
None,
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Gnu,
false,
),
// PE + Coff
(
Architecture::X86_64,
None,
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Coff,
false,
),
(
Architecture::I386,
None,
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Coff,
false,
),
(
Architecture::Aarch64,
None,
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Coff,
false,
),
(
Architecture::Aarch64,
Some(SubArchitecture::Arm64EC),
Endianness::Little,
BinaryFormat::Coff,
ArchiveKind::Coff,
false,
),
// MachO
(
Architecture::X86_64,
None,
Endianness::Little,
BinaryFormat::MachO,
ArchiveKind::Darwin,
false,
),
(
Architecture::Aarch64,
None,
Endianness::Little,
BinaryFormat::MachO,
ArchiveKind::Darwin,
false,
),
(
Architecture::Aarch64,
Some(SubArchitecture::Arm64E),
Endianness::Little,
BinaryFormat::MachO,
ArchiveKind::Darwin,
false,
),
] {
let is_ec = subarch == Some(SubArchitecture::Arm64EC);
let tmpdir = create_tmp_dir(test_name);
let input_objects = generate_objects(architecture, subarch, endianness, binary_format);
let llvm_ar_archive = create_archive_with_llvm_ar(
&tmpdir,
archive_kind,
input_objects
.iter()
.map(|(name, bytes)| (*name, bytes.as_slice())),
thin,
is_ec,
);
let ar_archive_writer_archive = create_archive_with_ar_archive_writer(
&tmpdir,
archive_kind,
input_objects
.iter()
.map(|(name, bytes)| (*name, bytes.as_slice())),
thin,
is_ec,
);
assert_eq!(
llvm_ar_archive, ar_archive_writer_archive,
"Archives differ for architecture: {architecture:?}, binary_format: {binary_format:?}, archive_kind: {archive_kind:?}, thin: {thin}",
);
}
}
pub fn add_file_with_functions_to_object(
object: &mut Object<'_>,
file_name: &[u8],
func_names: &[&[u8]],
) {
object.add_file_symbol(file_name.to_vec());
let text = object.section_id(write::StandardSection::Text);
object.append_section_data(text, &[1; 30], 4);
for func_name in func_names {
let offset = object.append_section_data(text, &[1; 30], 4);
object.add_symbol(write::Symbol {
name: func_name.to_vec(),
value: offset,
size: 32,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: write::SymbolSection::Section(text),
flags: SymbolFlags::None,
});
}
}

View File

@@ -0,0 +1,63 @@
; This must match get_members() in import_library.rs
LIBRARY MyLibrary
EXPORTS
NormalFunc
NormalData DATA
NormalConstant CONSTANT
PrivateFunc PRIVATE
FuncWithOrdinal @1
FuncWithNoName @2 NONAME
RenamedFunc=InternalName
ReexportedFunc=OtherModule.OtherName
ReexportedViaOrd=OtherModule.#42
FuncWithImportName==ImportName
; These are Arm64EC mangled names, since we currently don't support converting
; the non-Arm64EC format to Arm64EC. They are taken from ManglerTest.cpp
; Basic C++ name.
?foo@@$$hYAHXZ
; Regression test: https://github.com/llvm/llvm-project/issues/115231
?GetValue@?$Wrapper@UA@@@@$$hQEBAHXZ
; Symbols from:
; ```
; namespace A::B::C::D {
; struct Base {
; virtual int f() { return 0; }
; };
; }
; struct Derived : public A::B::C::D::Base {
; virtual int f() override { return 1; }
; };
; A::B::C::D::Base* MakeObj() { return new Derived(); }
; ```
; void * __cdecl operator new(unsigned __int64)
??2@$$hYAPEAX_K@Z
; public: virtual int __cdecl A::B::C::D::Base::f(void)
?f@Base@D@C@B@A@@$$hUEAAHXZ
; public: __cdecl A::B::C::D::Base::Base(void)
??0Base@D@C@B@A@@$$hQEAA@XZ
; public: virtual int __cdecl Derived::f(void)
?f@Derived@@$$hUEAAHXZ
; public: __cdecl Derived::Derived(void)
??0Derived@@$$hQEAA@XZ
; struct A::B::C::D::Base * __cdecl MakeObj(void)
?MakeObj@@$$hYAPEAUBase@D@C@B@A@@XZ
; Symbols from:
; ```
; template <typename T> struct WW { struct Z{}; };
; template <typename X> struct Wrapper {
; int GetValue(typename WW<X>::Z) const;
; };
; struct A { };
; template <typename X> int Wrapper<X>::GetValue(typename WW<X>::Z) const
; { return 3; }
; template class Wrapper<A>;
; ```
; public: int __cdecl Wrapper<struct A>::GetValue(struct WW<struct
; A>::Z)const
?GetValue@?$Wrapper@UA@@@@$$hQEBAHUZ@?$WW@UA@@@@@Z

View File

@@ -0,0 +1,387 @@
use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::process::Command;
use ar_archive_writer::{ArchiveKind, COFFShortExport, MachineTypes};
use common::{create_archive_with_ar_archive_writer, create_archive_with_llvm_ar};
use object::coff::CoffFile;
use object::pe::ImageFileHeader;
use object::read::archive::ArchiveFile;
use object::{Architecture, Object, ObjectSection, ObjectSymbol, SubArchitecture, bytes_of};
use pretty_assertions::assert_eq;
mod common;
const DEFAULT_EXPORT: COFFShortExport = COFFShortExport {
name: String::new(),
ext_name: None,
symbol_name: None,
export_as: None,
import_name: None,
ordinal: 0,
noname: false,
data: false,
private: false,
constant: false,
};
fn get_members(machine_type: MachineTypes) -> Vec<COFFShortExport> {
let prefix = match machine_type {
MachineTypes::I386 => "_",
_ => "",
};
// This must match import_library.def.
vec![
COFFShortExport {
name: format!("{prefix}NormalFunc"),
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}NormalData"),
data: true,
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}NormalConstant"),
constant: true,
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}PrivateFunc"),
private: true,
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}FuncWithOrdinal"),
ordinal: 1,
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}FuncWithNoName"),
ordinal: 2,
noname: true,
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}InternalName"),
ext_name: Some(format!("{prefix}RenamedFunc")),
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}OtherModule.OtherName"),
ext_name: Some(format!("{prefix}ReexportedFunc")),
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}OtherModule.#42"),
ext_name: Some(format!("{prefix}ReexportedViaOrd")),
..DEFAULT_EXPORT
},
COFFShortExport {
name: format!("{prefix}FuncWithImportName"),
import_name: Some("ImportName".to_string()),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "?foo@@$$hYAHXZ".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "?GetValue@?$Wrapper@UA@@@@$$hQEBAHXZ".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "??2@$$hYAPEAX_K@Z".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "?f@Base@D@C@B@A@@$$hUEAAHXZ".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "??0Base@D@C@B@A@@$$hQEAA@XZ".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "?f@Derived@@$$hUEAAHXZ".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "??0Derived@@$$hQEAA@XZ".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "?MakeObj@@$$hYAPEAUBase@D@C@B@A@@XZ".to_string(),
..DEFAULT_EXPORT
},
COFFShortExport {
name: "?GetValue@?$Wrapper@UA@@@@$$hQEBAHUZ@?$WW@UA@@@@@Z".to_string(),
..DEFAULT_EXPORT
},
]
}
fn create_import_library_with_ar_archive_writer(
temp_dir: &Path,
machine_type: MachineTypes,
mingw: bool,
comdat: bool,
) -> Vec<u8> {
let mut output_bytes = Cursor::new(Vec::new());
ar_archive_writer::write_import_library(
&mut output_bytes,
"MyLibrary.dll",
&get_members(machine_type),
machine_type,
mingw,
comdat,
&[],
)
.unwrap();
let output_archive_bytes = output_bytes.into_inner();
let ar_archive_writer_file_path = temp_dir.join("output_ar_archive_writer.a");
fs::write(ar_archive_writer_file_path, &output_archive_bytes).unwrap();
output_archive_bytes
}
#[test]
fn compare_to_lib() {
for machine_type in [
MachineTypes::I386,
MachineTypes::AMD64,
MachineTypes::ARMNT,
MachineTypes::ARM64,
MachineTypes::ARM64EC,
] {
let temp_dir = common::create_tmp_dir("import_library_compare_to_lib");
let archive_writer_bytes =
create_import_library_with_ar_archive_writer(&temp_dir, machine_type, false, false);
let llvm_lib_bytes = {
let machine_arg = match machine_type {
MachineTypes::I386 => "X86",
MachineTypes::AMD64 => "X64",
MachineTypes::ARMNT => "ARM",
MachineTypes::ARM64 => "ARM64",
MachineTypes::ARM64EC => "ARM64EC",
_ => panic!("Unsupported machine type"),
};
let llvm_lib_tool_path = common::create_llvm_lib_tool(&temp_dir);
let output_library_path = temp_dir.join("output_llvm_lib.a");
let def_path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/import_library.def");
let output = Command::new(llvm_lib_tool_path)
.arg(format!("/MACHINE:{machine_arg}"))
.arg(format!("/DEF:{}", def_path.to_string_lossy()))
.arg(format!("/OUT:{}", output_library_path.to_string_lossy()))
.output()
.unwrap();
assert_eq!(
String::from_utf8_lossy(&output.stderr),
"",
"llvm-lib failed. archive: {output_library_path:?}"
);
fs::read(output_library_path).unwrap()
};
assert_eq!(
llvm_lib_bytes, archive_writer_bytes,
"Import library differs. Machine type: {machine_type:?}",
);
compare_comdat(
&create_import_library_with_ar_archive_writer(&temp_dir, machine_type, false, true),
&llvm_lib_bytes,
);
}
}
#[test]
fn compare_to_dlltool() {
for machine_type in [
MachineTypes::I386,
MachineTypes::AMD64,
MachineTypes::ARMNT,
MachineTypes::ARM64,
] {
let temp_dir = common::create_tmp_dir("import_library_compare_to_dlltool");
let archive_writer_bytes =
create_import_library_with_ar_archive_writer(&temp_dir, machine_type, true, false);
let llvm_lib_bytes = {
let machine_arg = match machine_type {
MachineTypes::I386 => "i386",
MachineTypes::AMD64 => "i386:x86-64",
MachineTypes::ARMNT => "arm",
MachineTypes::ARM64 => "arm64",
_ => panic!("Unsupported machine type"),
};
let llvm_lib_tool_path = common::create_llvm_dlltool_tool(&temp_dir);
let output_library_path = temp_dir.join("output_llvm_lib.a");
let def_path =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/import_library.def");
let output = Command::new(llvm_lib_tool_path)
.arg("--machine")
.arg(machine_arg)
.arg("--input-def")
.arg(def_path)
.arg("--output-lib")
.arg(&output_library_path)
.output()
.unwrap();
assert_eq!(
String::from_utf8_lossy(&output.stderr),
"",
"llvm-lib failed. archive: {output_library_path:?}"
);
fs::read(output_library_path).unwrap()
};
assert_eq!(
llvm_lib_bytes, archive_writer_bytes,
"Import library differs. Machine type: {machine_type:?}",
);
compare_comdat(
&create_import_library_with_ar_archive_writer(&temp_dir, machine_type, true, true),
&llvm_lib_bytes,
);
}
}
/// Creates an import library and then wraps that in an archive.
#[test]
fn wrap_in_archive() {
for (architecture, subarch, machine_type) in [
(Architecture::I386, None, MachineTypes::I386),
(Architecture::X86_64, None, MachineTypes::AMD64),
(Architecture::Arm, None, MachineTypes::ARMNT),
(Architecture::Aarch64, None, MachineTypes::ARM64),
(
Architecture::Aarch64,
Some(SubArchitecture::Arm64EC),
MachineTypes::ARM64EC,
),
] {
let temp_dir = common::create_tmp_dir("import_library_wrap_in_archive");
let mut import_lib_bytes = Cursor::new(Vec::new());
ar_archive_writer::write_import_library(
&mut import_lib_bytes,
&temp_dir.join("MyLibrary.dll").to_string_lossy(),
&get_members(machine_type),
machine_type,
false,
false,
&[],
)
.unwrap();
let import_lib_bytes = import_lib_bytes.into_inner();
let is_ec = subarch == Some(SubArchitecture::Arm64EC);
let llvm_ar_archive = create_archive_with_llvm_ar(
&temp_dir,
ArchiveKind::Coff,
[("MyLibrary.dll.lib", import_lib_bytes.as_slice())],
false,
is_ec,
);
// When a archive is passed into lib.exe, it is opened and the individual members are included
// in the new output library. Also, for whatever reason, the members are reversed.
let archive = ArchiveFile::parse(import_lib_bytes.as_slice()).unwrap();
let mut members = archive
.members()
.map(|m| {
let member = m.unwrap();
(
String::from_utf8(member.name().to_vec()).unwrap(),
member.data(import_lib_bytes.as_slice()).unwrap(),
)
})
.collect::<Vec<_>>();
members.reverse();
let ar_archive_writer_archive = create_archive_with_ar_archive_writer(
&temp_dir,
ArchiveKind::Coff,
members.iter().map(|(name, data)| (name.as_str(), *data)),
false,
is_ec,
);
assert_eq!(
llvm_ar_archive, ar_archive_writer_archive,
"Archives differ for architecture: {architecture:?}, subarch: {subarch:?}, machine type: {machine_type:?}",
);
}
}
fn compare_comdat(archive_writer_bytes: &[u8], llvm_bytes: &[u8]) {
let archive_writer = ArchiveFile::parse(archive_writer_bytes).unwrap();
let llvm = ArchiveFile::parse(llvm_bytes).unwrap();
for (archive_member, llvm_member) in archive_writer.members().zip(llvm.members()) {
let archive_member = archive_member.unwrap();
let llvm_member = llvm_member.unwrap();
// skip the EC symbols table.
if archive_member.name() == b"/<ECSYMBOLS>/" {
continue;
}
if archive_member.size() != llvm_member.size() {
// Ensure that the member header is the same except for the file size.
let mut llvm_file_header = *llvm_member.header().unwrap();
llvm_file_header.size = archive_member.header().unwrap().size;
assert_eq!(
bytes_of(archive_member.header().unwrap()),
bytes_of(&llvm_file_header)
);
// Make sure they are both COFF files with the same sections and symbols,
// except for the different naming for the null import descriptor.
let archive_data = archive_member.data(archive_writer_bytes).unwrap();
let llvm_data = llvm_member.data(llvm_bytes).unwrap();
let archive_file = CoffFile::<_, ImageFileHeader>::parse(archive_data).unwrap();
let llvm_file = CoffFile::<_, ImageFileHeader>::parse(llvm_data).unwrap();
for (archive_section, llvm_section) in archive_file.sections().zip(llvm_file.sections())
{
assert_eq!(archive_section.data(), llvm_section.data());
}
for (archive_symbol, llvm_symbol) in archive_file.symbols().zip(llvm_file.symbols()) {
if llvm_symbol.name().unwrap() == "__NULL_IMPORT_DESCRIPTOR" {
assert!(
archive_symbol
.name()
.unwrap()
.starts_with("__NULL_IMPORT_DESCRIPTOR_")
);
} else {
assert_eq!(archive_symbol.name(), llvm_symbol.name());
}
let archive_coff_symbol = archive_symbol.coff_symbol();
let mut llvm_coff_symbol = *llvm_symbol.coff_symbol();
llvm_coff_symbol.name = archive_coff_symbol.name;
assert_eq!(bytes_of(archive_coff_symbol), bytes_of(&llvm_coff_symbol));
}
} else {
assert_eq!(
bytes_of(archive_member.header().unwrap()),
bytes_of(llvm_member.header().unwrap())
);
assert_eq!(
archive_member.data(archive_writer_bytes).unwrap(),
llvm_member.data(llvm_bytes).unwrap()
);
}
}
}

View File

@@ -0,0 +1,59 @@
use object::write;
mod common;
/// Tests creating an archive with multiple objects.
/// Note that `func_overlapping` exists in both objects - this is to test
/// deduplication of symbols in the symbol table (where supported).
#[test]
fn basic_multiple_objects() {
common::generate_archive_and_compare(
"basic_multiple_objects",
|architecture, subarch, endianness, binary_format| {
let mut object1 = write::Object::new(binary_format, architecture, endianness);
object1.set_sub_architecture(subarch);
common::add_file_with_functions_to_object(
&mut object1,
b"file1.c",
&[b"func1", b"func2", b"func_overlapping"],
);
let mut object2 = write::Object::new(binary_format, architecture, endianness);
object2.set_sub_architecture(subarch);
common::add_file_with_functions_to_object(
&mut object2,
b"file2.c",
&[b"func3", b"func4", b"func_overlapping"],
);
vec![
("file1.o", object1.write().unwrap()),
("file2.o", object2.write().unwrap()),
]
},
);
}
/// Tests creating an archive with multiple objects with the same name.
/// This is important for Mach), which uses the timestamp when in deterministic
/// mode to differentiate the two objects.
#[test]
fn multiple_objects_same_name() {
common::generate_archive_and_compare(
"multiple_objects_same_name",
|architecture, subarch, endianness, binary_format| {
let mut object1 = write::Object::new(binary_format, architecture, endianness);
object1.set_sub_architecture(subarch);
common::add_file_with_functions_to_object(&mut object1, b"file1.c", &[b"func1"]);
let mut object2 = write::Object::new(binary_format, architecture, endianness);
object2.set_sub_architecture(subarch);
common::add_file_with_functions_to_object(&mut object2, b"file2.c", &[b"func2"]);
vec![
("1/file.o", object1.write().unwrap()),
("2/file.o", object2.write().unwrap()),
]
},
);
}

View File

@@ -0,0 +1,467 @@
// Derived from object's round_trip.rs:
// https://github.com/gimli-rs/object/blob/0.35.0/tests/round_trip/mod.rs
use ar_archive_writer::ArchiveKind;
use object::{
Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SymbolFlags,
SymbolKind, SymbolScope,
};
use object::{RelocationFlags, SubArchitecture, read, write};
use pretty_assertions::assert_eq;
mod common;
fn round_trip_and_diff(
test_name: &str,
object: write::Object<'_>,
archive_kind: ArchiveKind,
trim_output_bytes: usize,
) {
let tmpdir = common::create_tmp_dir(test_name);
let input_bytes = object.write().unwrap();
let is_ec = object.sub_architecture() == Some(SubArchitecture::Arm64EC);
// Create a new archive using ar_archive_writer.
let output_archive_bytes = common::create_archive_with_ar_archive_writer(
&tmpdir,
archive_kind,
[("input.o", input_bytes.as_slice())],
false,
is_ec,
);
// Use llvm-ar to create the archive and diff with ar_archive_writer.
let output_llvm_ar_bytes = common::create_archive_with_llvm_ar(
&tmpdir,
archive_kind,
[("input.o", input_bytes.as_slice())],
false,
is_ec,
);
assert_eq!(
output_archive_bytes, output_llvm_ar_bytes,
"Comparing ar_archive_writer to llvm-ar. Test case: build {:?} for {:?}",
archive_kind, object
);
// Read the archive and member using object and diff with original data.
{
let output_archive =
read::archive::ArchiveFile::parse(output_archive_bytes.as_slice()).unwrap();
let mut members = output_archive.members();
let output_member = members.next().unwrap().unwrap();
if archive_kind != ArchiveKind::Coff {
assert_eq!(output_member.name(), b"input.o");
}
let output_bytes = output_member.data(output_archive_bytes.as_slice()).unwrap();
// Apply fixup if required.
let output_bytes = &output_bytes[..output_bytes.len() - trim_output_bytes];
assert_eq!(
&input_bytes, output_bytes,
"Comparing object after round-trip. Test case: build {:?} for {:?}",
archive_kind, object
);
}
}
#[test]
fn coff_any() {
for (arch, sub_arch) in [
(Architecture::Aarch64, None),
(Architecture::Aarch64, Some(SubArchitecture::Arm64EC)),
(Architecture::Arm, None),
(Architecture::I386, None),
(Architecture::X86_64, None),
]
.iter()
.copied()
{
for archive_kind in [ArchiveKind::Gnu, ArchiveKind::Coff] {
let mut object = write::Object::new(BinaryFormat::Coff, arch, Endianness::Little);
object.set_sub_architecture(sub_arch);
object.add_file_symbol(b"file.c".to_vec());
let text = object.section_id(write::StandardSection::Text);
object.append_section_data(text, &[1; 30], 4);
let func1_offset = object.append_section_data(text, &[1; 30], 4);
assert_eq!(func1_offset, 32);
let func1_symbol = object.add_symbol(write::Symbol {
name: b"func1".to_vec(),
value: func1_offset,
size: 32,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: write::SymbolSection::Section(text),
flags: SymbolFlags::None,
});
let func2_offset = object.append_section_data(text, &[1; 30], 4);
assert_eq!(func2_offset, 64);
object.add_symbol(write::Symbol {
name: b"func2_long".to_vec(),
value: func2_offset,
size: 32,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: write::SymbolSection::Section(text),
flags: SymbolFlags::None,
});
object
.add_relocation(
text,
write::Relocation {
offset: 8,
symbol: func1_symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: arch.address_size().unwrap().bytes() * 8,
},
},
)
.unwrap();
round_trip_and_diff("coff_any", object, archive_kind, 0);
}
}
}
#[test]
fn elf_x86_64() {
let mut object =
write::Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
object.add_file_symbol(b"file.c".to_vec());
let text = object.section_id(write::StandardSection::Text);
object.append_section_data(text, &[1; 30], 4);
let func1_offset = object.append_section_data(text, &[1; 30], 4);
assert_eq!(func1_offset, 32);
let func1_symbol = object.add_symbol(write::Symbol {
name: b"func1".to_vec(),
value: func1_offset,
size: 32,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: write::SymbolSection::Section(text),
flags: SymbolFlags::None,
});
object
.add_relocation(
text,
write::Relocation {
offset: 8,
symbol: func1_symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: 64,
},
},
)
.unwrap();
round_trip_and_diff("elf_x86_64", object, ArchiveKind::Gnu, 0);
}
#[test]
fn elf_any() {
for (arch, endian, archive_kinds) in [
(
Architecture::Aarch64,
Endianness::Little,
&[ArchiveKind::Gnu][..],
),
(
Architecture::Aarch64_Ilp32,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(Architecture::Arm, Endianness::Little, &[ArchiveKind::Gnu]),
(Architecture::Avr, Endianness::Little, &[ArchiveKind::Gnu]),
(Architecture::Bpf, Endianness::Little, &[ArchiveKind::Gnu]),
(Architecture::Csky, Endianness::Little, &[ArchiveKind::Gnu]),
(Architecture::I386, Endianness::Little, &[ArchiveKind::Gnu]),
(
Architecture::X86_64,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(
Architecture::X86_64_X32,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(
Architecture::Hexagon,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(
Architecture::LoongArch64,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(Architecture::Mips, Endianness::Little, &[ArchiveKind::Gnu]),
(
Architecture::Mips64,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(
Architecture::Msp430,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(Architecture::PowerPc, Endianness::Big, &[ArchiveKind::Gnu]),
(
Architecture::PowerPc64,
Endianness::Big,
&[ArchiveKind::Gnu, ArchiveKind::AixBig],
),
(
Architecture::Riscv32,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(
Architecture::Riscv64,
Endianness::Little,
&[ArchiveKind::Gnu],
),
(Architecture::S390x, Endianness::Big, &[ArchiveKind::Gnu]),
(Architecture::Sbf, Endianness::Little, &[ArchiveKind::Gnu]),
(Architecture::Sparc64, Endianness::Big, &[ArchiveKind::Gnu]),
(
Architecture::Xtensa,
Endianness::Little,
&[ArchiveKind::Gnu],
),
]
.iter()
.copied()
{
for archive_kind in archive_kinds {
let mut object = write::Object::new(BinaryFormat::Elf, arch, endian);
let section = object.section_id(write::StandardSection::Data);
object.append_section_data(section, &[1; 30], 4);
let symbol = object.section_symbol(section);
object
.add_relocation(
section,
write::Relocation {
offset: 8,
symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: 32,
},
},
)
.unwrap();
if arch.address_size().unwrap().bytes() >= 8 {
object
.add_relocation(
section,
write::Relocation {
offset: 16,
symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: 64,
},
},
)
.unwrap();
}
round_trip_and_diff("elf_any", object, *archive_kind, 0);
}
}
}
#[test]
fn macho_x86_64() {
let mut object = write::Object::new(
BinaryFormat::MachO,
Architecture::X86_64,
Endianness::Little,
);
object.add_file_symbol(b"file.c".to_vec());
let text = object.section_id(write::StandardSection::Text);
object.append_section_data(text, &[1; 30], 4);
let func1_offset = object.append_section_data(text, &[1; 30], 4);
assert_eq!(func1_offset, 32);
let func1_symbol = object.add_symbol(write::Symbol {
name: b"func1".to_vec(),
value: func1_offset,
size: 32,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: write::SymbolSection::Section(text),
flags: SymbolFlags::None,
});
object
.add_relocation(
text,
write::Relocation {
offset: 8,
symbol: func1_symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: 64,
},
},
)
.unwrap();
object
.add_relocation(
text,
write::Relocation {
offset: 16,
symbol: func1_symbol,
addend: -4,
flags: RelocationFlags::Generic {
kind: RelocationKind::Relative,
encoding: RelocationEncoding::Generic,
size: 32,
},
},
)
.unwrap();
round_trip_and_diff("macho_x86_64", object, ArchiveKind::Darwin, 0);
}
#[test]
fn macho_any() {
// 32-bit object files get additional padding after the round-trip:
// https://github.com/llvm/llvm-project/blob/3d3ef9d073e1e27ea57480b371b7f5a9f5642ed2/llvm/lib/Object/ArchiveWriter.cpp#L560-L565
for (arch, subarch, endian, trim_output_bytes) in [
(Architecture::Aarch64, None, Endianness::Little, 0),
(
Architecture::Aarch64,
Some(SubArchitecture::Arm64E),
Endianness::Little,
0,
),
(Architecture::Aarch64_Ilp32, None, Endianness::Little, 4),
/* TODO:
(Architecture::Arm, None, Endianness::Little),
*/
(Architecture::I386, None, Endianness::Little, 4),
(Architecture::X86_64, None, Endianness::Little, 0),
/* TODO:
(Architecture::PowerPc, None, Endianness::Big),
(Architecture::PowerPc64, None, Endianness::Big),
*/
]
.iter()
.copied()
{
let mut object = write::Object::new(BinaryFormat::MachO, arch, endian);
object.set_sub_architecture(subarch);
let section = object.section_id(write::StandardSection::Data);
object.append_section_data(section, &[1; 30], 4);
let symbol = object.section_symbol(section);
object
.add_relocation(
section,
write::Relocation {
offset: 8,
symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: 32,
},
},
)
.unwrap();
if arch.address_size().unwrap().bytes() >= 8 {
object
.add_relocation(
section,
write::Relocation {
offset: 16,
symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: 64,
},
},
)
.unwrap();
}
round_trip_and_diff("macho_any", object, ArchiveKind::Darwin, trim_output_bytes);
}
}
#[test]
fn xcoff_powerpc() {
for arch in [Architecture::PowerPc, Architecture::PowerPc64] {
let mut object = write::Object::new(BinaryFormat::Xcoff, arch, Endianness::Big);
object.add_file_symbol(b"file.c".to_vec());
let text = object.section_id(write::StandardSection::Text);
object.append_section_data(text, &[1; 30], 4);
let func1_offset = object.append_section_data(text, &[1; 30], 4);
assert_eq!(func1_offset, 32);
let func1_symbol = object.add_symbol(write::Symbol {
name: b"func1".to_vec(),
value: func1_offset,
size: 32,
kind: SymbolKind::Text,
scope: SymbolScope::Linkage,
weak: false,
section: write::SymbolSection::Section(text),
flags: SymbolFlags::None,
});
object
.add_relocation(
text,
write::Relocation {
offset: 8,
symbol: func1_symbol,
addend: 0,
flags: RelocationFlags::Generic {
kind: RelocationKind::Absolute,
encoding: RelocationEncoding::Generic,
size: 64,
},
},
)
.unwrap();
round_trip_and_diff("xcoff_powerpc", object, ArchiveKind::Gnu, 0);
}
}