#![allow(dead_code)]
use super::*;
pub type DwarfLineTable = LineProgram;
pub struct LineEntry;
pub struct LineTableHeader;
#[derive(Debug, Clone)]
pub struct LineFileEntry {
pub name: String,
pub directory_index: u64,
pub mod_time: u64,
pub length: u64,
pub md5_checksum: Option<[u8; 16]>,
}
#[derive(Debug, Clone)]
pub struct LineProgramHeader {
pub unit_length: u32,
pub version: u16,
pub address_size: u8,
pub segment_selector_size: u8,
pub header_length: u32,
pub minimum_instruction_length: u8,
pub maximum_operations_per_instruction: u8,
pub default_is_stmt: u8,
pub line_base: i8,
pub line_range: u8,
pub opcode_base: u8,
pub standard_opcode_lengths: Vec<u8>,
pub directory_entry_format_count: u8,
pub directory_entry_format: Vec<(u16, u16)>,
pub directories: Vec<String>,
pub file_name_entry_format_count: u8,
pub file_name_entry_format: Vec<(u16, u16)>,
pub file_names: Vec<LineFileEntry>,
}
impl Default for LineProgramHeader {
fn default() -> Self {
Self {
unit_length: 0,
version: 5,
address_size: 8,
segment_selector_size: 0,
header_length: 0,
minimum_instruction_length: 1,
maximum_operations_per_instruction: 1,
default_is_stmt: 1,
line_base: -5,
line_range: 14,
opcode_base: 13,
standard_opcode_lengths: vec![0; 12], directory_entry_format_count: 1,
directory_entry_format: vec![(DW_LNCT_path, dwarf_forms::DW_FORM_string)],
directories: Vec::new(),
file_name_entry_format_count: 2,
file_name_entry_format: vec![
(DW_LNCT_path, dwarf_forms::DW_FORM_string),
(DW_LNCT_directory_index, dwarf_forms::DW_FORM_udata),
],
file_names: Vec::new(),
}
}
}
#[allow(dead_code, non_upper_case_globals)]
pub mod content_types {
pub const DW_LNCT_path: u16 = 0x01;
pub const DW_LNCT_directory_index: u16 = 0x02;
pub const DW_LNCT_timestamp: u16 = 0x03;
pub const DW_LNCT_size: u16 = 0x04;
pub const DW_LNCT_MD5: u16 = 0x05;
pub const DW_LNCT_lo_user: u16 = 0x2000;
pub const DW_LNCT_hi_user: u16 = 0x3fff;
}
use content_types::*;
#[allow(dead_code)]
pub mod std_opcodes {
pub const DW_LNS_copy: u8 = 1;
pub const DW_LNS_advance_pc: u8 = 2;
pub const DW_LNS_advance_line: u8 = 3;
pub const DW_LNS_set_file: u8 = 4;
pub const DW_LNS_set_column: u8 = 5;
pub const DW_LNS_negate_stmt: u8 = 6;
pub const DW_LNS_set_basic_block: u8 = 7;
pub const DW_LNS_const_add_pc: u8 = 8;
pub const DW_LNS_fixed_advance_pc: u8 = 9;
pub const DW_LNS_set_prologue_end: u8 = 10;
pub const DW_LNS_set_epilogue_begin: u8 = 11;
pub const DW_LNS_set_isa: u8 = 12;
}
#[allow(dead_code)]
pub mod ext_opcodes {
pub const DW_LNE_end_sequence: u8 = 1;
pub const DW_LNE_set_address: u8 = 2;
pub const DW_LNE_define_file: u8 = 3;
pub const DW_LNE_set_discriminator: u8 = 4;
pub const DW_LNE_lo_user: u8 = 0x80;
pub const DW_LNE_hi_user: u8 = 0xff;
}
#[derive(Debug, Clone)]
pub struct LineState {
pub address: u64,
pub op_index: u32,
pub file: u32,
pub line: u32,
pub column: u32,
pub is_stmt: bool,
pub basic_block: bool,
pub end_sequence: bool,
pub prologue_end: bool,
pub epilogue_begin: bool,
pub isa: u32,
pub discriminator: u32,
}
impl Default for LineState {
fn default() -> Self {
Self {
address: 0,
op_index: 0,
file: 1,
line: 1,
column: 0,
is_stmt: false,
basic_block: false,
end_sequence: false,
prologue_end: false,
epilogue_begin: false,
isa: 0,
discriminator: 0,
}
}
}
impl LineState {
pub fn new(default_is_stmt: bool) -> Self {
Self {
is_stmt: default_is_stmt,
..Default::default()
}
}
pub fn reset(&mut self, default_is_stmt: bool) {
self.address = 0;
self.op_index = 0;
self.file = 1;
self.line = 1;
self.column = 0;
self.is_stmt = default_is_stmt;
self.basic_block = false;
self.end_sequence = false;
self.prologue_end = false;
self.epilogue_begin = false;
self.isa = 0;
self.discriminator = 0;
}
}
impl LineProgram {
pub fn emit_header_v5(&self, address_size: u8) -> Vec<u8> {
let mut out = Vec::new();
let len_pos = out.len();
out.extend_from_slice(&[0u8; 4]);
out.extend_from_slice(&5u16.to_le_bytes());
out.push(address_size);
out.push(0);
let hdr_len_pos = out.len();
out.extend_from_slice(&[0u8; 4]);
out.push(self.minimum_instruction_length);
out.push(self.maximum_operations_per_instruction);
out.push(self.default_is_stmt as u8);
out.push(self.line_base as u8);
out.push(self.line_range);
out.push(self.opcode_base);
for i in 1..self.opcode_base as usize {
if i < self.standard_opcode_lengths.len() {
out.push(self.standard_opcode_lengths[i]);
} else {
out.push(0);
}
}
out.push(1); encode_uleb128(&mut out, DW_LNCT_path as u64);
encode_uleb128(&mut out, dwarf_forms::DW_FORM_string as u64);
encode_uleb128(&mut out, self.directories.len() as u64);
for dir in &self.directories {
out.extend_from_slice(dir.path.as_bytes());
out.push(0);
}
out.push(4); encode_uleb128(&mut out, DW_LNCT_path as u64);
encode_uleb128(&mut out, dwarf_forms::DW_FORM_string as u64);
encode_uleb128(&mut out, DW_LNCT_directory_index as u64);
encode_uleb128(&mut out, dwarf_forms::DW_FORM_udata as u64);
encode_uleb128(&mut out, DW_LNCT_timestamp as u64);
encode_uleb128(&mut out, dwarf_forms::DW_FORM_udata as u64);
encode_uleb128(&mut out, DW_LNCT_size as u64);
encode_uleb128(&mut out, dwarf_forms::DW_FORM_udata as u64);
encode_uleb128(&mut out, self.files.len() as u64);
for file in &self.files {
out.extend_from_slice(file.name.as_bytes());
out.push(0);
encode_uleb128(&mut out, file.directory_index);
encode_uleb128(&mut out, file.mod_time);
encode_uleb128(&mut out, file.length);
}
let hdr_len = (out.len() - hdr_len_pos - 4) as u32;
out[hdr_len_pos..hdr_len_pos + 4].copy_from_slice(&hdr_len.to_le_bytes());
let unit_len = (out.len() - len_pos - 4) as u32;
out[len_pos..len_pos + 4].copy_from_slice(&unit_len.to_le_bytes());
out
}
pub fn emit_body_v5(&self) -> Vec<u8> {
let mut out = Vec::new();
if self.rows.is_empty() {
out.push(0);
encode_uleb128(&mut out, 1);
out.push(ext_opcodes::DW_LNE_end_sequence);
return out;
}
let mut state = LineState::new(self.default_is_stmt);
for row in &self.rows {
if row.address != state.address {
out.push(0); let addr_size = 8; encode_uleb128(&mut out, 1 + addr_size as u64);
out.push(ext_opcodes::DW_LNE_set_address);
out.extend_from_slice(&row.address.to_le_bytes());
state.address = row.address;
}
if row.file_index as u32 != state.file {
out.push(std_opcodes::DW_LNS_set_file);
encode_uleb128(&mut out, row.file_index);
state.file = row.file_index as u32;
}
if row.column as u32 != state.column {
out.push(std_opcodes::DW_LNS_set_column);
encode_uleb128(&mut out, row.column);
state.column = row.column as u32;
}
let row_is_stmt = row.is_stmt;
if row_is_stmt != state.is_stmt {
out.push(std_opcodes::DW_LNS_negate_stmt);
state.is_stmt = !state.is_stmt;
}
if row.basic_block && !state.basic_block {
out.push(std_opcodes::DW_LNS_set_basic_block);
state.basic_block = true;
}
if row.prologue_end && !state.prologue_end {
out.push(std_opcodes::DW_LNS_set_prologue_end);
state.prologue_end = true;
}
if row.epilogue_begin && !state.epilogue_begin {
out.push(std_opcodes::DW_LNS_set_epilogue_begin);
state.epilogue_begin = true;
}
if row.isa as u32 != state.isa {
out.push(std_opcodes::DW_LNS_set_isa);
encode_uleb128(&mut out, row.isa);
state.isa = row.isa as u32;
}
if state.discriminator != 0 {
out.push(0); let mut disc_bytes = Vec::new();
encode_uleb128(&mut disc_bytes, state.discriminator as u64);
encode_uleb128(&mut out, 1 + disc_bytes.len() as u64);
out.push(ext_opcodes::DW_LNE_set_discriminator);
out.extend_from_slice(&disc_bytes);
}
let line_delta = (row.line as i64) - (state.line as i64);
if line_delta >= self.line_base as i64
&& line_delta < (self.line_base as i64 + self.line_range as i64)
&& row.address == state.address
{
let adjusted_line = (line_delta - self.line_base as i64) as u64;
let opcode = adjusted_line + self.opcode_base as u64;
if opcode <= 255 {
out.push(opcode as u8);
} else {
if line_delta != 0 {
out.push(std_opcodes::DW_LNS_advance_line);
encode_sleb128(&mut out, line_delta);
}
out.push(std_opcodes::DW_LNS_copy);
}
} else if line_delta != 0 {
out.push(std_opcodes::DW_LNS_advance_line);
encode_sleb128(&mut out, line_delta);
out.push(std_opcodes::DW_LNS_copy);
} else {
out.push(std_opcodes::DW_LNS_copy);
}
state.line = row.line as u32;
state.basic_block = false;
state.prologue_end = false;
state.epilogue_begin = false;
state.discriminator = 0;
}
out.push(0);
encode_uleb128(&mut out, 1);
out.push(ext_opcodes::DW_LNE_end_sequence);
out
}
pub fn add_row_full(
&mut self,
address: u64,
file_index: u64,
line: u64,
column: u64,
is_stmt: bool,
basic_block: bool,
prologue_end: bool,
epilogue_begin: bool,
isa: u64,
discriminator: u32,
) {
let mut row = LineRow {
address,
file_index,
line,
column,
is_stmt,
basic_block,
prologue_end,
epilogue_begin,
isa,
};
let _ = discriminator; self.rows.push(row);
}
pub fn to_header_v5(&self, address_size: u8) -> LineProgramHeader {
let mut header = LineProgramHeader::default();
header.address_size = address_size;
header.minimum_instruction_length = self.minimum_instruction_length;
header.maximum_operations_per_instruction = self.maximum_operations_per_instruction;
header.default_is_stmt = self.default_is_stmt as u8;
header.line_base = self.line_base;
header.line_range = self.line_range;
header.opcode_base = self.opcode_base;
header.standard_opcode_lengths = self.standard_opcode_lengths.clone();
header.directories = self.directories.iter().map(|d| d.path.clone()).collect();
header.file_names = self
.files
.iter()
.map(|f| LineFileEntry {
name: f.name.clone(),
directory_index: f.directory_index,
mod_time: f.mod_time,
length: f.length,
md5_checksum: None,
})
.collect();
header
}
pub fn emit_v5(&self, address_size: u8) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&self.emit_header_v5(address_size));
out.extend_from_slice(&self.emit_body_v5());
out
}
}
pub struct LineProgramBuilder {
pub program: LineProgram,
state: LineState,
}
impl LineProgramBuilder {
pub fn new() -> Self {
let program = LineProgram::new();
let state = LineState::new(program.default_is_stmt);
Self { program, state }
}
pub fn add_directory(&mut self, path: &str) -> u64 {
self.program.add_directory(path)
}
pub fn add_file(&mut self, name: &str, dir_idx: u64) -> u64 {
self.program.add_file(name, dir_idx)
}
pub fn set_address(&mut self, address: u64) {
self.state.address = address;
}
pub fn set_file(&mut self, file: u32) {
self.state.file = file;
}
pub fn set_line(&mut self, line: u32) {
self.state.line = line;
}
pub fn set_column(&mut self, column: u32) {
self.state.column = column;
}
pub fn set_is_stmt(&mut self, is_stmt: bool) {
self.state.is_stmt = is_stmt;
}
pub fn set_basic_block(&mut self) {
self.state.basic_block = true;
}
pub fn set_prologue_end(&mut self) {
self.state.prologue_end = true;
}
pub fn set_epilogue_begin(&mut self) {
self.state.epilogue_begin = true;
}
pub fn set_isa(&mut self, isa: u32) {
self.state.isa = isa;
}
pub fn set_discriminator(&mut self, discriminator: u32) {
self.state.discriminator = discriminator;
}
pub fn emit_row(&mut self) {
self.program.rows.push(LineRow {
address: self.state.address,
file_index: self.state.file as u64,
line: self.state.line as u64,
column: self.state.column as u64,
is_stmt: self.state.is_stmt,
basic_block: self.state.basic_block,
prologue_end: self.state.prologue_end,
epilogue_begin: self.state.epilogue_begin,
isa: self.state.isa as u64,
});
self.state.basic_block = false;
self.state.prologue_end = false;
self.state.epilogue_begin = false;
self.state.discriminator = 0;
}
pub fn current_state(&self) -> &LineState {
&self.state
}
pub fn into_program(self) -> LineProgram {
self.program
}
}
impl Default for LineProgramBuilder {
fn default() -> Self {
Self::new()
}
}
impl LineProgramHeader {
pub fn emit(&self) -> Vec<u8> {
let mut out = Vec::new();
let len_pos = out.len();
out.extend_from_slice(&[0u8; 4]);
out.extend_from_slice(&self.version.to_le_bytes());
out.push(self.address_size);
out.push(self.segment_selector_size);
let hdr_len_pos = out.len();
out.extend_from_slice(&[0u8; 4]);
out.push(self.minimum_instruction_length);
out.push(self.maximum_operations_per_instruction);
out.push(self.default_is_stmt);
out.push(self.line_base as u8);
out.push(self.line_range);
out.push(self.opcode_base);
for i in 1..self.opcode_base as usize {
if i < self.standard_opcode_lengths.len() {
out.push(self.standard_opcode_lengths[i]);
} else {
out.push(0);
}
}
out.push(self.directory_entry_format_count);
for &(content_type, form) in &self.directory_entry_format {
encode_uleb128(&mut out, content_type as u64);
encode_uleb128(&mut out, form as u64);
}
encode_uleb128(&mut out, self.directories.len() as u64);
for dir in &self.directories {
out.extend_from_slice(dir.as_bytes());
out.push(0);
}
out.push(self.file_name_entry_format_count);
for &(content_type, form) in &self.file_name_entry_format {
encode_uleb128(&mut out, content_type as u64);
encode_uleb128(&mut out, form as u64);
}
encode_uleb128(&mut out, self.file_names.len() as u64);
for file in &self.file_names {
out.extend_from_slice(file.name.as_bytes());
out.push(0);
encode_uleb128(&mut out, file.directory_index);
encode_uleb128(&mut out, file.mod_time);
encode_uleb128(&mut out, file.length);
}
let hdr_len = (out.len() - hdr_len_pos - 4) as u32;
out[hdr_len_pos..hdr_len_pos + 4].copy_from_slice(&hdr_len.to_le_bytes());
let unit_len = (out.len() - len_pos - 4) as u32;
out[len_pos..len_pos + 4].copy_from_slice(&unit_len.to_le_bytes());
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_content_type_values() {
assert_eq!(DW_LNCT_path, 0x01);
assert_eq!(DW_LNCT_directory_index, 0x02);
assert_eq!(DW_LNCT_timestamp, 0x03);
assert_eq!(DW_LNCT_size, 0x04);
assert_eq!(DW_LNCT_MD5, 0x05);
}
#[test]
fn test_std_opcode_values() {
assert_eq!(std_opcodes::DW_LNS_copy, 1);
assert_eq!(std_opcodes::DW_LNS_set_prologue_end, 10);
assert_eq!(std_opcodes::DW_LNS_set_epilogue_begin, 11);
assert_eq!(std_opcodes::DW_LNS_set_isa, 12);
}
#[test]
fn test_ext_opcode_values() {
assert_eq!(ext_opcodes::DW_LNE_end_sequence, 1);
assert_eq!(ext_opcodes::DW_LNE_set_address, 2);
assert_eq!(ext_opcodes::DW_LNE_define_file, 3);
assert_eq!(ext_opcodes::DW_LNE_set_discriminator, 4);
}
#[test]
fn test_line_state_default() {
let state = LineState::default();
assert_eq!(state.address, 0);
assert_eq!(state.file, 1);
assert_eq!(state.line, 1);
assert_eq!(state.column, 0);
assert!(!state.is_stmt);
assert!(!state.basic_block);
assert!(!state.end_sequence);
assert!(!state.prologue_end);
assert!(!state.epilogue_begin);
assert_eq!(state.isa, 0);
assert_eq!(state.discriminator, 0);
}
#[test]
fn test_line_state_new_with_stmt() {
let state = LineState::new(true);
assert!(state.is_stmt);
}
#[test]
fn test_line_state_reset() {
let mut state = LineState::default();
state.address = 0x1000;
state.file = 3;
state.line = 42;
state.reset(true);
assert_eq!(state.address, 0);
assert_eq!(state.file, 1);
assert_eq!(state.line, 1);
assert!(state.is_stmt);
}
#[test]
fn test_line_program_header_default() {
let header = LineProgramHeader::default();
assert_eq!(header.version, 5);
assert_eq!(header.address_size, 8);
assert_eq!(header.segment_selector_size, 0);
assert_eq!(header.minimum_instruction_length, 1);
assert_eq!(header.maximum_operations_per_instruction, 1);
assert_eq!(header.default_is_stmt, 1);
assert_eq!(header.line_base, -5);
assert_eq!(header.line_range, 14);
assert_eq!(header.opcode_base, 13);
}
#[test]
fn test_emit_header_v5_produces_valid_output() {
let lp = LineProgram::new();
let data = lp.emit_header_v5(8);
assert!(!data.is_empty());
let unit_len = u32::from_le_bytes(data[0..4].try_into().unwrap());
assert!(unit_len > 0);
let version = u16::from_le_bytes(data[4..6].try_into().unwrap());
assert_eq!(version, 5);
assert_eq!(data[6], 8);
assert_eq!(data[7], 0);
}
#[test]
fn test_emit_header_v5_with_directories() {
let mut lp = LineProgram::new();
lp.add_directory("/usr/src");
lp.add_directory("/usr/include");
let data = lp.emit_header_v5(8);
assert!(!data.is_empty());
let s = String::from_utf8_lossy(&data);
assert!(s.contains("/usr/src"));
assert!(s.contains("/usr/include"));
}
#[test]
fn test_emit_header_v5_with_files() {
let mut lp = LineProgram::new();
lp.add_directory("/src");
let idx = lp.add_file("main.c", 1);
lp.add_line(0x1000, idx, 1, 0);
let data = lp.emit_header_v5(8);
let s = String::from_utf8_lossy(&data);
assert!(s.contains("main.c"));
}
#[test]
fn test_emit_body_v5_empty() {
let lp = LineProgram::new();
let data = lp.emit_body_v5();
assert!(!data.is_empty());
assert_eq!(data[data.len() - 1], ext_opcodes::DW_LNE_end_sequence);
}
#[test]
fn test_emit_body_v5_single_row() {
let mut lp = LineProgram::new();
lp.add_line(0x1000, 1, 5, 0);
let data = lp.emit_body_v5();
assert!(!data.is_empty());
assert_eq!(data[data.len() - 1], ext_opcodes::DW_LNE_end_sequence);
}
#[test]
fn test_emit_body_v5_multiple_rows() {
let mut lp = LineProgram::new();
lp.add_line(0x1000, 1, 5, 0);
lp.add_line(0x1010, 1, 6, 0);
lp.add_line(0x1020, 1, 7, 0);
let data = lp.emit_body_v5();
assert!(!data.is_empty());
assert_eq!(data[data.len() - 1], ext_opcodes::DW_LNE_end_sequence);
}
#[test]
fn test_builder_new() {
let builder = LineProgramBuilder::new();
assert_eq!(builder.program.files.len(), 1);
assert_eq!(builder.state.file, 1);
}
#[test]
fn test_builder_add_directory_and_file() {
let mut builder = LineProgramBuilder::new();
let dir_idx = builder.add_directory("/src");
let file_idx = builder.add_file("test.c", dir_idx);
assert_eq!(dir_idx, 0);
assert_eq!(file_idx, 1);
assert_eq!(builder.program.files[1].name, "test.c");
}
#[test]
fn test_builder_set_state() {
let mut builder = LineProgramBuilder::new();
builder.set_address(0x4000);
builder.set_file(2);
builder.set_line(10);
builder.set_column(3);
let state = builder.current_state();
assert_eq!(state.address, 0x4000);
assert_eq!(state.file, 2);
assert_eq!(state.line, 10);
assert_eq!(state.column, 3);
}
#[test]
fn test_builder_set_flags() {
let mut builder = LineProgramBuilder::new();
builder.set_is_stmt(true);
builder.set_basic_block();
builder.set_prologue_end();
builder.set_epilogue_begin();
builder.set_isa(5);
builder.set_discriminator(42);
let state = builder.current_state();
assert!(state.is_stmt);
assert!(state.basic_block);
assert!(state.prologue_end);
assert!(state.epilogue_begin);
assert_eq!(state.isa, 5);
assert_eq!(state.discriminator, 42);
}
#[test]
fn test_builder_emit_row() {
let mut builder = LineProgramBuilder::new();
builder.set_address(0x2000);
builder.set_line(12);
builder.set_basic_block();
builder.emit_row();
assert_eq!(builder.program.rows.len(), 1);
let row = &builder.program.rows[0];
assert_eq!(row.address, 0x2000);
assert_eq!(row.line, 12);
assert!(row.basic_block);
let state = builder.current_state();
assert!(!state.basic_block);
}
#[test]
fn test_builder_into_program() {
let mut builder = LineProgramBuilder::new();
builder.set_address(0x1000);
builder.set_line(5);
builder.emit_row();
builder.set_address(0x1010);
builder.set_line(6);
builder.emit_row();
let program = builder.into_program();
assert_eq!(program.rows.len(), 2);
assert_eq!(program.rows[0].address, 0x1000);
assert_eq!(program.rows[1].address, 0x1010);
}
#[test]
fn test_emit_v5_complete() {
let mut lp = LineProgram::new();
lp.add_directory("/tmp");
let f_idx = lp.add_file("test.c", 1);
lp.add_line(0x4000, f_idx, 10, 0);
let data = lp.emit_v5(8);
assert!(!data.is_empty());
assert!(data.len() > 20);
}
#[test]
fn test_emit_header_v5_address_size_4() {
let lp = LineProgram::new();
let data = lp.emit_header_v5(4);
assert_eq!(data[6], 4); }
#[test]
fn test_to_header_v5() {
let mut lp = LineProgram::new();
lp.add_directory("/proj");
lp.add_file("main.rs", 1);
let header = lp.to_header_v5(8);
assert_eq!(header.version, 5);
assert_eq!(header.address_size, 8);
assert_eq!(header.directories.len(), 1);
assert_eq!(header.file_names.len(), 2); assert_eq!(header.file_names[1].name, "main.rs");
}
#[test]
fn test_header_emit() {
let mut header = LineProgramHeader::default();
header.address_size = 8;
header.directories.push("/src".to_string());
header.file_names.push(LineFileEntry {
name: "test.c".to_string(),
directory_index: 1,
mod_time: 0,
length: 0,
md5_checksum: None,
});
let data = header.emit();
assert!(!data.is_empty());
let version = u16::from_le_bytes(data[4..6].try_into().unwrap());
assert_eq!(version, 5);
let s = String::from_utf8_lossy(&data);
assert!(s.contains("/src"));
assert!(s.contains("test.c"));
}
#[test]
fn test_line_file_entry_default() {
let entry = LineFileEntry {
name: "test.rs".to_string(),
directory_index: 0,
mod_time: 12345,
length: 67890,
md5_checksum: Some([0xAB; 16]),
};
assert_eq!(entry.name, "test.rs");
assert_eq!(entry.directory_index, 0);
assert_eq!(entry.mod_time, 12345);
assert_eq!(entry.length, 67890);
assert!(entry.md5_checksum.is_some());
}
#[test]
fn test_v5_header_roundtrip() {
let mut lp = LineProgram::new();
lp.add_directory("/proj");
lp.add_file("hello.c", 1);
lp.add_line(0x1000, 1, 1, 0);
let v5_data = lp.emit_v5(8);
let v4_header = lp.emit_header();
let v4_body = lp.emit_body();
let v4_data: Vec<u8> = v4_header.iter().chain(v4_body.iter()).copied().collect();
assert!(!v5_data.is_empty());
assert!(!v4_data.is_empty());
let v5_version = u16::from_le_bytes(v5_data[4..6].try_into().unwrap());
assert_eq!(v5_version, 5);
let v4_version = u16::from_le_bytes(v4_data[4..6].try_into().unwrap());
assert_eq!(v4_version, 2);
}
#[test]
fn test_add_row_full() {
let mut lp = LineProgram::new();
lp.add_row_full(0x5000, 1, 42, 8, true, true, false, false, 0, 3);
assert_eq!(lp.rows.len(), 1);
let row = &lp.rows[0];
assert_eq!(row.address, 0x5000);
assert_eq!(row.line, 42);
assert_eq!(row.column, 8);
assert!(row.is_stmt);
assert!(row.basic_block);
assert!(!row.prologue_end);
assert!(!row.epilogue_begin);
}
}