use measureme::stringtable::{METADATA_STRING_ID, TERMINATOR};
use measureme::{
file_header::{
strip_file_header, verify_file_header, FILE_MAGIC_STRINGTABLE_DATA,
FILE_MAGIC_STRINGTABLE_INDEX,
},
stringtable::STRING_REF_ENCODED_SIZE,
stringtable::STRING_REF_TAG,
};
use measureme::{Addr, StringId};
use memchr::{memchr, memchr2};
use rustc_hash::FxHashMap;
use std::borrow::Cow;
use std::convert::TryInto;
use std::error::Error;
use std::path::Path;
const INDEX_ENTRY_SIZE: usize = std::mem::size_of::<StringId>() + std::mem::size_of::<Addr>();
fn deserialize_index_entry(bytes: &[u8]) -> (StringId, Addr) {
(
StringId::new(u64::from_le_bytes(bytes[0..8].try_into().unwrap())),
Addr(u64::from_le_bytes(bytes[8..16].try_into().unwrap())),
)
}
#[derive(Copy, Clone)]
pub struct StringRef<'st> {
id: StringId,
table: &'st StringTable,
}
const UNKNOWN_STRING: &str = "<unknown>";
const INVALID_STRING: &str = "<invalid>";
impl<'st> StringRef<'st> {
pub fn to_string(&self) -> Cow<'st, str> {
let addr = match self.get_addr() {
Ok(addr) => addr,
Err(_) => return Cow::from(UNKNOWN_STRING),
};
let pos = addr.as_usize();
let slice_to_search = &self.table.string_data[pos..];
let terminator_pos = memchr(TERMINATOR, slice_to_search).unwrap();
let first_byte = self.table.string_data[pos];
if first_byte == STRING_REF_TAG && terminator_pos == pos + STRING_REF_ENCODED_SIZE {
let id = decode_string_ref_from_data(&self.table.string_data[pos..]);
return StringRef {
id,
table: self.table,
}
.to_string();
}
if let Ok(s) = std::str::from_utf8(&slice_to_search[..terminator_pos]) {
Cow::from(s)
} else {
let mut output = String::new();
self.write_to_string(&mut output);
Cow::from(output)
}
}
pub fn write_to_string(&self, output: &mut String) {
let addr = match self.get_addr() {
Ok(addr) => addr,
Err(_) => {
output.push_str(UNKNOWN_STRING);
return;
}
};
let mut pos = addr.as_usize();
loop {
let byte = self.table.string_data[pos];
if byte == TERMINATOR {
return;
} else if byte == STRING_REF_TAG {
let string_ref = StringRef {
id: decode_string_ref_from_data(&self.table.string_data[pos..]),
table: self.table,
};
string_ref.write_to_string(output);
pos += STRING_REF_ENCODED_SIZE;
} else {
let remaining_data = &self.table.string_data[pos..];
if let Some(len) = memchr2(0xFF, 0xFE, remaining_data) {
let value = String::from_utf8_lossy(&remaining_data[..len]);
output.push_str(&value);
pos += len;
} else {
output.push_str(INVALID_STRING);
return;
}
}
}
}
fn get_addr(&self) -> Result<Addr, ()> {
if self.id.is_virtual() {
match self.table.index.get(&self.id) {
Some(&addr) => Ok(addr),
None => Err(()),
}
} else if self.id == StringId::INVALID {
Err(())
} else {
Ok(self.id.to_addr())
}
}
}
fn decode_string_ref_from_data(bytes: &[u8]) -> StringId {
assert!(bytes[0] == STRING_REF_TAG);
assert!(STRING_REF_ENCODED_SIZE == 9);
let id = u64::from_le_bytes(bytes[1..9].try_into().unwrap());
StringId::new(id)
}
#[derive(Debug)]
pub struct StringTable {
string_data: Vec<u8>,
index: FxHashMap<StringId, Addr>,
}
impl StringTable {
pub fn new(
string_data: Vec<u8>,
index_data: Vec<u8>,
diagnostic_file_path: Option<&Path>,
) -> Result<StringTable, Box<dyn Error + Send + Sync>> {
verify_file_header(
&string_data,
FILE_MAGIC_STRINGTABLE_DATA,
diagnostic_file_path,
"StringTable Data",
)?;
verify_file_header(
&index_data,
FILE_MAGIC_STRINGTABLE_INDEX,
diagnostic_file_path,
"StringTable Index",
)?;
assert!(
(index_data.len() - measureme::file_header::FILE_HEADER_SIZE) % INDEX_ENTRY_SIZE == 0,
"StringTable index size appears malformed",
);
assert_eq!(INDEX_ENTRY_SIZE, 16);
let index: FxHashMap<_, _> = strip_file_header(&index_data)
.chunks(INDEX_ENTRY_SIZE)
.map(deserialize_index_entry)
.collect();
Ok(StringTable { string_data, index })
}
#[inline]
pub fn get<'a>(&'a self, id: StringId) -> StringRef<'a> {
StringRef { id, table: self }
}
pub fn get_metadata<'a>(&'a self) -> StringRef<'a> {
let id = StringId::new(METADATA_STRING_ID);
self.get(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use measureme::{PageTag, SerializationSinkBuilder, StringComponent, StringTableBuilder};
use std::sync::Arc;
#[test]
fn simple_strings() {
let sink_builder = SerializationSinkBuilder::new_in_memory();
let data_sink = Arc::new(sink_builder.new_sink(PageTag::StringData));
let index_sink = Arc::new(sink_builder.new_sink(PageTag::StringIndex));
let expected_strings = &[
"abc",
"",
"xyz",
"g2h9284hgjv282y32983849&(*^&YIJ#R)(F83 f 23 2g4 35g5y",
"",
"",
"g2h9284hgjv282y32983849&35g5y",
];
let mut string_ids = vec![];
{
let builder = StringTableBuilder::new(data_sink.clone(), index_sink.clone()).unwrap();
for &s in expected_strings {
string_ids.push(builder.alloc(s));
}
}
let data_bytes = Arc::try_unwrap(data_sink).unwrap().into_bytes();
let index_bytes = Arc::try_unwrap(index_sink).unwrap().into_bytes();
let string_table = StringTable::new(data_bytes, index_bytes, None).unwrap();
for (&id, &expected_string) in string_ids.iter().zip(expected_strings.iter()) {
let str_ref = string_table.get(id);
assert_eq!(str_ref.to_string(), expected_string);
let mut write_to = String::new();
str_ref.write_to_string(&mut write_to);
assert_eq!(str_ref.to_string(), write_to);
}
}
#[test]
fn composite_string() {
let sink_builder = SerializationSinkBuilder::new_in_memory();
let data_sink = Arc::new(sink_builder.new_sink(PageTag::StringData));
let index_sink = Arc::new(sink_builder.new_sink(PageTag::StringIndex));
let expected_strings = &[
"abc", "abcabc", "abcabcabc", "abcabcabc", "abcabcabc", "abcabcabcabc", "xxabcabcuuuabcabcqqq", "xxxxxx", ];
let mut string_ids = vec![];
{
let builder = StringTableBuilder::new(data_sink.clone(), index_sink.clone()).unwrap();
let r = |id| StringComponent::Ref(id);
let v = |s| StringComponent::Value(s);
string_ids.push(builder.alloc("abc")); string_ids.push(builder.alloc(&[r(string_ids[0]), r(string_ids[0])])); string_ids.push(builder.alloc(&[r(string_ids[0]), r(string_ids[0]), r(string_ids[0])])); string_ids.push(builder.alloc(&[r(string_ids[1]), r(string_ids[0])])); string_ids.push(builder.alloc(&[r(string_ids[0]), r(string_ids[1])])); string_ids.push(builder.alloc(&[r(string_ids[1]), r(string_ids[1])])); string_ids.push(builder.alloc(&[
v("xx"),
r(string_ids[1]),
v("uuu"),
r(string_ids[1]),
v("qqq"),
])); }
let data_bytes = Arc::try_unwrap(data_sink).unwrap().into_bytes();
let index_bytes = Arc::try_unwrap(index_sink).unwrap().into_bytes();
let string_table = StringTable::new(data_bytes, index_bytes, None).unwrap();
for (&id, &expected_string) in string_ids.iter().zip(expected_strings.iter()) {
let str_ref = string_table.get(id);
assert_eq!(str_ref.to_string(), expected_string);
let mut write_to = String::new();
str_ref.write_to_string(&mut write_to);
assert_eq!(str_ref.to_string(), write_to);
}
}
}