use std::{
collections::HashMap,
env, fs,
path::{Path, PathBuf},
process, str,
sync::atomic::{AtomicU64, Ordering},
};
use object::{
BinaryFormat, File, Object, ObjectComdat, ObjectKind, ObjectSection, ObjectSymbol,
RelocationTarget, SectionFlags, SectionKind, SymbolFlags, SymbolSection, write,
};
use crate::{
config::try_rllvm_config,
constants::{
COFF_SECTION_NAME, DARWIN_SECTION_NAME, DARWIN_SEGMENT_NAME, ELF_SECTION_NAME,
WASM_SECTION_NAME,
},
error::Error,
utils::execute_command_for_status,
};
pub(crate) fn is_plain_file<P>(file: P) -> bool
where
P: AsRef<Path>,
{
let file = file.as_ref();
file.exists() && !file.is_dir()
}
pub(crate) fn is_object_file<P>(file: P) -> Result<bool, Error>
where
P: AsRef<Path>,
{
let file = file.as_ref();
if !is_plain_file(file) {
return Ok(false);
}
let data = fs::read(file)?;
match object::File::parse(&*data) {
Ok(object_file) => Ok(object_file.kind() == ObjectKind::Relocatable),
Err(err) => {
tracing::debug!("Not an object file: file={:?}, err={}", file, err);
Ok(false)
}
}
}
fn resolve_bitcode_filepath(bitcode_filepath: &Path) -> Result<String, Error> {
let absolute_filepath = if bitcode_filepath.is_absolute() {
bitcode_filepath.to_path_buf()
} else {
bitcode_filepath.canonicalize()?
};
let recorded = try_rllvm_config()
.ok()
.and_then(|config| config.bitcode_root())
.and_then(|root| {
absolute_filepath
.strip_prefix(&root)
.ok()
.map(|relative| relative.to_string_lossy().into_owned())
})
.unwrap_or_else(|| absolute_filepath.to_string_lossy().into_owned());
Ok(format!("{recorded}\n"))
}
fn encode_leb128(mut value: usize) -> Vec<u8> {
let mut result = vec![];
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
result.push(byte);
if value == 0 {
break;
}
}
result
}
fn append_wasm_custom_section(wasm_data: &[u8], section_name: &str, payload: &[u8]) -> Vec<u8> {
let name_bytes = section_name.as_bytes();
let name_len_encoded = encode_leb128(name_bytes.len());
let content_size = name_len_encoded.len() + name_bytes.len() + payload.len();
let section_size_encoded = encode_leb128(content_size);
let mut result = wasm_data.to_vec();
result.push(0x00); result.extend_from_slice(§ion_size_encoded);
result.extend_from_slice(&name_len_encoded);
result.extend_from_slice(name_bytes);
result.extend_from_slice(payload);
result
}
pub fn embed_bitcode_filepath_to_object_file<P>(
bitcode_filepath: P,
object_filepath: P,
output_object_filepath: Option<P>,
) -> Result<(), Error>
where
P: AsRef<Path>,
{
let bitcode_filepath = bitcode_filepath.as_ref();
let object_filepath = object_filepath.as_ref();
let data = fs::read(object_filepath)?;
let object_file = object::File::parse(&*data)?;
let object_binary_format = object_file.format();
let bitcode_filepath_string = resolve_bitcode_filepath(bitcode_filepath)?;
if !matches!(object_binary_format, BinaryFormat::Wasm)
&& let Some(objcopy_filepath) = try_rllvm_config()?.llvm_objcopy_filepath()
&& objcopy_filepath.exists()
{
return embed_with_objcopy(
objcopy_filepath,
object_binary_format,
&bitcode_filepath_string,
object_filepath,
output_object_filepath.as_ref().map(|p| p.as_ref()),
);
}
if object_binary_format == BinaryFormat::MachO
&& let Some(output_data) = embed_with_macho_builder(&data, &bitcode_filepath_string)
{
return write_object_output(output_data, object_filepath, output_object_filepath);
}
let output_data = match object_binary_format {
BinaryFormat::Wasm => {
append_wasm_custom_section(&data, WASM_SECTION_NAME, bitcode_filepath_string.as_bytes())
}
_ => {
let (segment_name, section_name, flags) = match object_binary_format {
BinaryFormat::Elf => (
vec![],
ELF_SECTION_NAME.as_bytes().to_vec(),
SectionFlags::Elf {
sh_type: object::elf::SHT_PROGBITS,
sh_flags: object::elf::SectionFlags(0),
},
),
BinaryFormat::MachO => (
DARWIN_SEGMENT_NAME.as_bytes().to_vec(),
DARWIN_SECTION_NAME.as_bytes().to_vec(),
SectionFlags::MachO {
flags: object::macho::SectionFlags(0),
reserved2: 0,
},
),
BinaryFormat::Coff => (
vec![],
COFF_SECTION_NAME.as_bytes().to_vec(),
SectionFlags::Coff {
characteristics: object::pe::SectionFlags(0),
},
),
_ => {
return Err(Error::UnsupportedBinaryFormat(format!(
"{:?}",
object_binary_format
)));
}
};
let mut new_object_file = copy_object_file(object_file)?;
let section_id =
new_object_file.add_section(segment_name, section_name, SectionKind::Unknown);
let new_section = new_object_file.section_mut(section_id);
new_section.set_data(bitcode_filepath_string.as_bytes(), 1);
new_section.flags = flags;
new_object_file.write()?
}
};
write_object_output(output_data, object_filepath, output_object_filepath)
}
fn write_object_output<P>(
output_data: Vec<u8>,
object_filepath: &Path,
output_object_filepath: Option<P>,
) -> Result<(), Error>
where
P: AsRef<Path>,
{
match output_object_filepath {
Some(output_object_filepath) => fs::write(output_object_filepath, output_data)?,
None => fs::write(object_filepath, output_data)?,
}
Ok(())
}
fn macho_name_field(name: &str) -> Option<[u8; 16]> {
let bytes = name.as_bytes();
if bytes.len() > 16 {
return None;
}
let mut field = [0u8; 16];
field[..bytes.len()].copy_from_slice(bytes);
Some(field)
}
fn embed_with_macho_builder(data: &[u8], bitcode_filepath_string: &str) -> Option<Vec<u8>> {
use object::build::macho::{Builder, SectionData};
let mut builder = Builder::read(data)
.inspect_err(|err| {
tracing::debug!("Mach-O builder cannot handle this object: {}", err);
})
.ok()?;
let sectname = macho_name_field(DARWIN_SECTION_NAME)?;
let segname = macho_name_field(DARWIN_SEGMENT_NAME)?;
let section_id = {
let section = builder.sections.add();
section.sectname = sectname;
section.segname = segname;
section.align = 0;
section.data = SectionData::Data(bitcode_filepath_string.as_bytes().to_vec().into());
section.id()
};
let segment = builder.segments.iter_mut().next()?;
segment.sections.push(section_id);
let mut output_data = Vec::new();
builder
.write(&mut output_data)
.inspect_err(|err| {
tracing::debug!("Mach-O builder failed to write: {}", err);
})
.ok()?;
Some(output_data)
}
fn objcopy_section_specifier(format: BinaryFormat) -> Result<String, Error> {
match format {
BinaryFormat::Elf => Ok(ELF_SECTION_NAME.to_string()),
BinaryFormat::MachO => Ok(format!("{DARWIN_SEGMENT_NAME},{DARWIN_SECTION_NAME}")),
BinaryFormat::Coff => Ok(COFF_SECTION_NAME.to_string()),
_ => Err(Error::UnsupportedBinaryFormat(format!("{format:?}"))),
}
}
fn embed_with_objcopy(
objcopy_filepath: &Path,
format: BinaryFormat,
bitcode_filepath_string: &str,
object_filepath: &Path,
output_object_filepath: Option<&Path>,
) -> Result<(), Error> {
static PAYLOAD_COUNTER: AtomicU64 = AtomicU64::new(0);
let section_specifier = objcopy_section_specifier(format)?;
let payload_filepath = env::temp_dir().join(format!(
"rllvm-bcpath-{}-{}",
process::id(),
PAYLOAD_COUNTER.fetch_add(1, Ordering::Relaxed)
));
fs::write(&payload_filepath, bitcode_filepath_string)?;
let mut args = vec![
format!(
"--add-section={section_specifier}={}",
payload_filepath.display()
),
object_filepath.to_string_lossy().into_owned(),
];
if let Some(output_object_filepath) = output_object_filepath {
args.push(output_object_filepath.to_string_lossy().into_owned());
}
let status = execute_command_for_status(objcopy_filepath, &args);
let _ = fs::remove_file(&payload_filepath);
let status = status?;
if !status.success() {
return Err(Error::ExecutionFailure(format!(
"Failed to embed the bitcode path with {objcopy_filepath:?}: exit_status={status}"
)));
}
Ok(())
}
fn copy_macho_build_version(in_object: &File, out_object: &mut write::Object) -> Result<(), Error> {
let build_version = match in_object {
File::MachO32(macho) => macho.build_version()?,
File::MachO64(macho) => macho.build_version()?,
_ => return Ok(()),
};
if let Some(build_version) = build_version {
let endian = in_object.endianness();
let mut version = write::MachOBuildVersion::default();
let (build_version, _tools) = build_version;
version.platform = build_version.platform.get(endian);
version.minos = build_version.minos.get(endian);
version.sdk = build_version.sdk.get(endian);
out_object.set_macho_build_version(version);
}
Ok(())
}
fn copy_object_file(in_object: File) -> Result<write::Object, Error> {
if in_object.kind() != ObjectKind::Relocatable {
return Err(Error::InvalidArguments(format!(
"Unsupported object kind: {:?}",
in_object.kind()
)));
}
let mut out_object = write::Object::new(
in_object.format(),
in_object.architecture(),
in_object.endianness(),
);
out_object.mangling = write::Mangling::None;
out_object.flags = in_object.flags();
copy_macho_build_version(&in_object, &mut out_object)?;
let mut out_sections = HashMap::new();
for in_section in in_object.sections() {
if in_section.kind() == SectionKind::Metadata {
continue;
}
let section_id = out_object.add_section(
in_section.segment_name()?.unwrap_or("").as_bytes().to_vec(),
in_section.name()?.as_bytes().to_vec(),
in_section.kind(),
);
let out_section = out_object.section_mut(section_id);
if out_section.is_bss() {
out_section.append_bss(in_section.size(), in_section.align());
} else {
out_section.set_data(in_section.data()?, in_section.align());
}
out_section.flags = in_section.flags();
out_sections.insert(in_section.index(), section_id);
}
let mut out_symbols = HashMap::new();
for in_symbol in in_object.symbols() {
let (section, value) = match in_symbol.section() {
SymbolSection::None => (write::SymbolSection::None, in_symbol.address()),
SymbolSection::Undefined => (write::SymbolSection::Undefined, in_symbol.address()),
SymbolSection::Absolute => (write::SymbolSection::Absolute, in_symbol.address()),
SymbolSection::Common => (write::SymbolSection::Common, in_symbol.address()),
SymbolSection::Section(index) => {
if let Some(out_section) = out_sections.get(&index) {
(
write::SymbolSection::Section(*out_section),
in_symbol.address() - in_object.section_by_index(index)?.address(),
)
} else {
continue;
}
}
_ => {
return Err(Error::InvalidArguments(format!(
"Unknown symbol section: {:?}",
in_symbol
)));
}
};
let flags = match in_symbol.flags() {
SymbolFlags::None => SymbolFlags::None,
SymbolFlags::Elf { st_info, st_other } => SymbolFlags::Elf { st_info, st_other },
SymbolFlags::MachO { n_type, n_desc } => SymbolFlags::MachO { n_type, n_desc },
SymbolFlags::CoffSection {
typ,
storage_class,
selection,
associative_section,
} => {
let associative_section =
associative_section.map(|index| *out_sections.get(&index).unwrap());
SymbolFlags::CoffSection {
typ,
storage_class,
selection,
associative_section,
}
}
SymbolFlags::Xcoff {
n_type,
n_sclass,
x_smtyp,
x_smclas,
containing_csect,
} => {
let containing_csect =
containing_csect.map(|index| *out_symbols.get(&index).unwrap());
SymbolFlags::Xcoff {
n_type,
n_sclass,
x_smtyp,
x_smclas,
containing_csect,
}
}
_ => {
return Err(Error::InvalidArguments(format!(
"Unknown symbol flags: {:?}",
in_symbol
)));
}
};
let out_symbol = write::Symbol {
name: in_symbol.name().unwrap_or("").as_bytes().to_vec(),
value,
size: in_symbol.size(),
kind: in_symbol.kind(),
scope: in_symbol.scope(),
weak: in_symbol.is_weak(),
section,
flags,
};
let symbol_id = out_object.add_symbol(out_symbol);
out_symbols.insert(in_symbol.index(), symbol_id);
}
for in_section in in_object.sections() {
if in_section.kind() == SectionKind::Metadata {
continue;
}
let out_section = *out_sections.get(&in_section.index()).unwrap();
for (offset, in_relocation) in in_section.relocations() {
let symbol = match in_relocation.target() {
RelocationTarget::Symbol(symbol) => *out_symbols.get(&symbol).unwrap(),
RelocationTarget::Section(section) => {
out_object.section_symbol(*out_sections.get(§ion).unwrap())
}
_ => {
return Err(Error::InvalidArguments(format!(
"Unknown relocation target: {:?}",
in_relocation
)));
}
};
let out_relocation = write::Relocation {
offset,
symbol,
addend: in_relocation.addend(),
flags: in_relocation.flags(),
};
out_object.add_relocation(out_section, out_relocation)?;
}
}
for in_comdat in in_object.comdats() {
let mut sections = vec![];
for in_section in in_comdat.sections() {
sections.push(*out_sections.get(&in_section).unwrap());
}
let out_comdat = write::Comdat {
kind: in_comdat.kind(),
symbol: *out_symbols.get(&in_comdat.symbol()).unwrap(),
sections,
};
out_object.add_comdat(out_comdat);
}
Ok(out_object)
}
pub fn extract_bitcode_filepaths_from_parsed_object(
object_file: &object::File,
) -> Result<Vec<PathBuf>, Error> {
let object_binary_format = object_file.format();
let section_name = match object_binary_format {
BinaryFormat::Elf => ELF_SECTION_NAME.as_bytes(),
BinaryFormat::MachO => DARWIN_SECTION_NAME.as_bytes(),
BinaryFormat::Coff => COFF_SECTION_NAME.as_bytes(),
BinaryFormat::Wasm => WASM_SECTION_NAME.as_bytes(),
_ => {
return Err(Error::UnsupportedBinaryFormat(format!(
"{:?}",
object_binary_format
)));
}
};
match object_file.section_by_name_bytes(section_name) {
Some(section) => {
let section_data = section.data()?;
let embedded_filepath_string = str::from_utf8(section_data)?.trim();
let mut embedded_filepaths: Vec<_> = embedded_filepath_string
.split('\n')
.map(PathBuf::from)
.collect();
embedded_filepaths.sort();
embedded_filepaths.dedup();
Ok(embedded_filepaths)
}
None => Ok(vec![]),
}
}
pub fn extract_bitcode_filepaths_from_object_file<P>(
object_filepath: P,
) -> Result<Vec<PathBuf>, Error>
where
P: AsRef<Path>,
{
let object_filepath = object_filepath.as_ref();
let data = fs::read(object_filepath)?;
let object_file = object::File::parse(&*data)?;
extract_bitcode_filepaths_from_parsed_object(&object_file)
}
pub fn extract_bitcode_filepaths_from_parsed_objects(
object_files: &[object::File],
) -> Result<Vec<PathBuf>, Error> {
let mut bitcode_filepaths = vec![];
for object_file in object_files {
bitcode_filepaths.extend(extract_bitcode_filepaths_from_parsed_object(object_file)?);
}
bitcode_filepaths.sort();
bitcode_filepaths.dedup();
Ok(bitcode_filepaths)
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
fs,
path::{Path, PathBuf},
};
fn tmp_bitcode(name: &str) -> PathBuf {
std::env::temp_dir().join(name)
}
fn macho_build_version(object_file: &File) -> Option<(u32, u32, u32)> {
let endian = object_file.endianness();
let build_version = match object_file {
File::MachO32(macho) => macho.build_version().ok()?,
File::MachO64(macho) => macho.build_version().ok()?,
_ => return None,
}?;
let (build_version, _tools) = build_version;
Some((
build_version.platform.get(endian).0,
build_version.minos.get(endian).0,
build_version.sdk.get(endian).0,
))
}
#[test]
fn test_macho_builder_embeds_without_padding() {
let data = create_minimal_macho_object();
let before = object::File::parse(&*data).expect("Failed to parse the object");
let expected_version = macho_build_version(&before);
let payload = "/tmp/one.bc\n";
let output = embed_with_macho_builder(&data, payload)
.expect("the builder should handle a plain object");
let after = object::File::parse(&*output).expect("Failed to parse the output");
assert_eq!(
macho_build_version(&after),
expected_version,
"the builder must round-trip LC_BUILD_VERSION"
);
let section = after
.section_by_name_bytes(DARWIN_SECTION_NAME.as_bytes())
.expect("bitcode section missing");
assert_eq!(
section.data().expect("Failed to read the section"),
payload.as_bytes()
);
assert_eq!(section.align(), 1, "bitcode section must be byte-aligned");
let extracted = extract_bitcode_filepaths_from_parsed_object(&after)
.expect("Failed to extract embedded filepaths");
assert_eq!(extracted, vec![PathBuf::from("/tmp/one.bc")]);
}
#[test]
fn test_macho_build_version_survives_rebuild() {
let data = create_minimal_macho_object();
let in_object = object::File::parse(&*data).expect("Failed to parse the object");
assert_eq!(in_object.format(), BinaryFormat::MachO);
let expected = macho_build_version(&in_object);
assert!(
expected.is_some(),
"the object carries no LC_BUILD_VERSION, so this test would prove nothing"
);
let rebuilt = copy_object_file(in_object).expect("Failed to rebuild the object");
let rebuilt_data = rebuilt.write().expect("Failed to serialize the object");
let rebuilt_object =
object::File::parse(&*rebuilt_data).expect("Failed to parse the rebuilt object");
assert_eq!(
macho_build_version(&rebuilt_object),
expected,
"LC_BUILD_VERSION was not preserved across the rebuild"
);
}
#[test]
fn test_is_object_file_on_non_object() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let source_path = dir.path().join("hello.m");
fs::write(&source_path, "int main(void) { return 0; }\n").expect("Failed to write");
assert!(
!is_object_file(&source_path).expect("a non-object must not be an error"),
"a source file is not an object file"
);
let object_path = dir.path().join("real.obj");
create_minimal_coff_object(&object_path);
assert!(is_object_file(&object_path).expect("Failed to classify"));
}
#[test]
fn test_objcopy_section_specifier() {
assert_eq!(
objcopy_section_specifier(BinaryFormat::MachO).unwrap(),
format!("{DARWIN_SEGMENT_NAME},{DARWIN_SECTION_NAME}")
);
assert_eq!(
objcopy_section_specifier(BinaryFormat::Elf).unwrap(),
ELF_SECTION_NAME
);
assert_eq!(
objcopy_section_specifier(BinaryFormat::Coff).unwrap(),
COFF_SECTION_NAME
);
assert!(objcopy_section_specifier(BinaryFormat::Wasm).is_err());
}
#[test]
fn test_path_injection_and_extraction() {
let bitcode_pathbuf = tmp_bitcode("hello.bc");
let bitcode_filepath = bitcode_pathbuf.as_path();
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let object_pathbuf = dir.path().join("hello.o");
fs::write(&object_pathbuf, create_minimal_macho_object()).expect("Failed to write");
let object_filepath = object_pathbuf.as_path();
let output_pathbuf = dir.path().join("hello.new.o");
let output_object_filepath = output_pathbuf.as_path();
let ret = embed_bitcode_filepath_to_object_file(
bitcode_filepath,
object_filepath,
Some(output_object_filepath),
);
assert!(ret.is_ok());
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(output_object_filepath)
.expect("Failed to extract embedded filepaths");
assert!(!embedded_filepaths.is_empty());
let expected_filepath = PathBuf::from(bitcode_filepath);
println!("{:?}", embedded_filepaths[0]);
assert_eq!(embedded_filepaths[0], expected_filepath);
}
#[test]
fn test_paths_extraction() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let object_filepath = dir.path().join("merged.obj");
create_object_with_bitcode_paths(
&object_filepath,
&["/tmp/foo.bc", "/tmp/bar.bc", "/tmp/baz.bc", "/tmp/bar.bc"],
);
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(&object_filepath)
.expect("Failed to extract embedded filepaths");
assert_eq!(embedded_filepaths.len(), 3);
let expected_filepaths = vec![
PathBuf::from("/tmp/bar.bc"),
PathBuf::from("/tmp/baz.bc"),
PathBuf::from("/tmp/foo.bc"),
];
println!("{:?}", embedded_filepaths);
assert_eq!(embedded_filepaths, expected_filepaths)
}
fn create_minimal_macho_object() -> Vec<u8> {
use object::Architecture;
let mut obj = write::Object::new(
BinaryFormat::MachO,
Architecture::Aarch64,
object::Endianness::Little,
);
let mut build_version = write::MachOBuildVersion::default();
build_version.platform = object::macho::PLATFORM_MACOS;
build_version.minos = object::macho::Version(0x000f_0000);
build_version.sdk = object::macho::Version(0x000f_0000);
obj.set_macho_build_version(build_version);
let section_id = obj.add_section(b"__TEXT".to_vec(), b"__text".to_vec(), SectionKind::Text);
obj.section_mut(section_id)
.set_data(&[0xc0, 0x03, 0x5f, 0xd6], 4);
obj.write().expect("Failed to write Mach-O object")
}
fn create_object_with_bitcode_paths(path: &Path, paths: &[&str]) {
use object::Architecture;
let mut obj = write::Object::new(
BinaryFormat::Coff,
Architecture::X86_64,
object::Endianness::Little,
);
let text_id = obj.add_section(vec![], b".text".to_vec(), SectionKind::Text);
obj.section_mut(text_id).set_data(&[0xc3], 1);
let payload: String = paths.iter().map(|p| format!("{p}\n")).collect();
let section_id = obj.add_section(
vec![],
COFF_SECTION_NAME.as_bytes().to_vec(),
SectionKind::Unknown,
);
let section = obj.section_mut(section_id);
section.set_data(payload.as_bytes(), 1);
section.flags = SectionFlags::Coff {
characteristics: object::pe::SectionFlags(0),
};
let data = obj.write().expect("Failed to write object");
fs::write(path, data).expect("Failed to write object file");
}
fn create_minimal_coff_object(path: &Path) {
use object::Architecture;
let mut obj = write::Object::new(
BinaryFormat::Coff,
Architecture::X86_64,
object::Endianness::Little,
);
let section_id = obj.add_section(vec![], b".text".to_vec(), SectionKind::Text);
let section = obj.section_mut(section_id);
section.set_data(&[0xc3], 1);
let data = obj.write().expect("Failed to write COFF object");
fs::write(path, data).expect("Failed to write COFF file");
}
#[test]
fn test_coff_path_injection_and_extraction() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let coff_obj_path = dir.path().join("test.obj");
let output_path = dir.path().join("test.out.obj");
create_minimal_coff_object(&coff_obj_path);
let bitcode_pathbuf = tmp_bitcode("hello.bc");
let bitcode_filepath = bitcode_pathbuf.as_path();
embed_bitcode_filepath_to_object_file(bitcode_filepath, &coff_obj_path, Some(&output_path))
.expect("Failed to embed bitcode filepath into COFF object");
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(&output_path)
.expect("Failed to extract embedded filepaths from COFF object");
assert_eq!(embedded_filepaths.len(), 1);
assert_eq!(embedded_filepaths[0], tmp_bitcode("hello.bc"));
}
#[test]
fn test_coff_overwrite_in_place() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let coff_obj_path = dir.path().join("test.obj");
create_minimal_coff_object(&coff_obj_path);
let bitcode_pathbuf = tmp_bitcode("inplace.bc");
let bitcode_filepath = bitcode_pathbuf.as_path();
embed_bitcode_filepath_to_object_file::<&Path>(bitcode_filepath, &coff_obj_path, None)
.expect("Failed to embed bitcode filepath into COFF object in place");
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(&coff_obj_path)
.expect("Failed to extract embedded filepaths from COFF object");
assert_eq!(embedded_filepaths.len(), 1);
assert_eq!(embedded_filepaths[0], tmp_bitcode("inplace.bc"));
}
#[test]
fn test_coff_no_bitcode_section_returns_empty() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let coff_obj_path = dir.path().join("test.obj");
create_minimal_coff_object(&coff_obj_path);
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(&coff_obj_path)
.expect("Failed to extract from COFF object without bitcode section");
assert!(embedded_filepaths.is_empty());
}
fn create_minimal_wasm_object(path: &Path) {
let mut data = vec![];
data.extend_from_slice(&[0x00, 0x61, 0x73, 0x6d]);
data.extend_from_slice(&[0x01, 0x00, 0x00, 0x00]);
data.extend_from_slice(&[0x01, 0x01, 0x00]);
data.extend_from_slice(&[0x03, 0x01, 0x00]);
data.extend_from_slice(&[0x0a, 0x01, 0x00]);
fs::write(path, data).expect("Failed to write WASM file");
}
#[test]
fn test_wasm_path_injection_and_extraction() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let wasm_obj_path = dir.path().join("test.wasm");
let output_path = dir.path().join("test.out.wasm");
create_minimal_wasm_object(&wasm_obj_path);
let bitcode_pathbuf = tmp_bitcode("hello.bc");
let bitcode_filepath = bitcode_pathbuf.as_path();
embed_bitcode_filepath_to_object_file(bitcode_filepath, &wasm_obj_path, Some(&output_path))
.expect("Failed to embed bitcode filepath into WASM object");
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(&output_path)
.expect("Failed to extract embedded filepaths from WASM object");
assert_eq!(embedded_filepaths.len(), 1);
assert_eq!(embedded_filepaths[0], tmp_bitcode("hello.bc"));
}
#[test]
fn test_wasm_overwrite_in_place() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let wasm_obj_path = dir.path().join("test.wasm");
create_minimal_wasm_object(&wasm_obj_path);
let bitcode_pathbuf = tmp_bitcode("inplace.bc");
let bitcode_filepath = bitcode_pathbuf.as_path();
embed_bitcode_filepath_to_object_file::<&Path>(bitcode_filepath, &wasm_obj_path, None)
.expect("Failed to embed bitcode filepath into WASM object in place");
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(&wasm_obj_path)
.expect("Failed to extract embedded filepaths from WASM object");
assert_eq!(embedded_filepaths.len(), 1);
assert_eq!(embedded_filepaths[0], tmp_bitcode("inplace.bc"));
}
#[test]
fn test_wasm_no_bitcode_section_returns_empty() {
let dir = tempfile::tempdir().expect("Failed to create temp dir");
let wasm_obj_path = dir.path().join("test.wasm");
create_minimal_wasm_object(&wasm_obj_path);
let embedded_filepaths = extract_bitcode_filepaths_from_object_file(&wasm_obj_path)
.expect("Failed to extract from WASM object without bitcode section");
assert!(embedded_filepaths.is_empty());
}
}