use std::convert::{TryFrom, TryInto};
use indexmap::IndexMap;
use llvm_bitstream::parser::StreamEntry;
use llvm_bitstream::record::{Block, Record};
use llvm_bitstream::Bitstream;
use llvm_constants::IrBlockId;
use crate::block::{BlockId, BlockMapError, Identification, Module, Strtab, Symtab};
use crate::error::Error;
use crate::map::{MapCtx, Mappable};
use crate::record::RecordMapError;
#[derive(Clone, Debug)]
pub struct UnrolledRecord(Record);
impl UnrolledRecord {
pub fn code(&self) -> u64 {
self.0.code
}
pub fn try_string(&self, idx: usize) -> Result<String, RecordMapError> {
if idx >= self.0.fields.len() - 1 {
return Err(RecordMapError::BadField(format!(
"impossible string index: {} exceeds record fields",
idx
)));
}
let raw = self.0.fields[idx..]
.iter()
.map(|f| u8::try_from(*f))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| RecordMapError::BadField("impossible character value in string".into()))?;
String::from_utf8(raw)
.map_err(|_| RecordMapError::BadField("invalid string encoding".into()))
}
pub fn try_blob(&self, idx: usize) -> Result<Vec<u8>, RecordMapError> {
if idx >= self.0.fields.len() - 1 {
return Err(RecordMapError::BadField(format!(
"impossible blob index: {} exceeds record fields",
idx
)));
}
self.0.fields[idx..]
.iter()
.map(|f| u8::try_from(*f))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| RecordMapError::BadField("impossible byte value in blob".into()))
}
pub fn fields(&self) -> &[u64] {
&self.0.fields
}
pub fn get_field(&self, idx: usize) -> Result<u64, RecordMapError> {
self.0.fields.get(idx).copied().ok_or_else(|| {
RecordMapError::BadField(format!("invalid field index for {:?}: {}", self, idx))
})
}
}
#[derive(Clone, Debug)]
pub struct UnrolledBlock {
pub id: BlockId,
records: Vec<UnrolledRecord>,
blocks: IndexMap<BlockId, Vec<UnrolledBlock>>,
}
impl UnrolledBlock {
pub(self) fn new(id: u64) -> Self {
Self {
id: id.into(),
records: vec![],
blocks: IndexMap::new(),
}
}
pub fn maybe_one_record(&self, code: u64) -> Result<Option<&UnrolledRecord>, BlockMapError> {
let records = self.records(code).collect::<Vec<_>>();
match records.len() {
0 => Ok(None),
1 => Ok(Some(records[0])),
_ => Err(BlockMapError::BlockRecordMismatch(code, self.id)),
}
}
pub fn one_record(&self, code: u64) -> Result<&UnrolledRecord, BlockMapError> {
let records = self.records(code).collect::<Vec<_>>();
if records.is_empty() || records.len() > 1 {
return Err(BlockMapError::BlockRecordMismatch(code, self.id));
}
Ok(records[0])
}
pub fn records(&self, code: u64) -> impl Iterator<Item = &UnrolledRecord> + '_ {
self.records.iter().filter(move |r| r.code() == code)
}
pub fn all_records(&self) -> impl Iterator<Item = &UnrolledRecord> + '_ {
self.records.iter()
}
pub fn blocks(&self, id: BlockId) -> impl Iterator<Item = &UnrolledBlock> + '_ {
self.blocks.get(&id).into_iter().flatten()
}
pub fn maybe_one_block(&self, id: BlockId) -> Result<Option<&UnrolledBlock>, BlockMapError> {
let blocks = self.blocks(id).collect::<Vec<_>>();
match blocks.len() {
0 => Ok(None),
1 => Ok(Some(blocks[0])),
_ => Err(BlockMapError::BlockBlockMismatch(id, self.id)),
}
}
pub fn one_block(&self, id: BlockId) -> Result<&UnrolledBlock, BlockMapError> {
if let Some(block) = self.maybe_one_block(id)? {
Ok(block)
} else {
Err(BlockMapError::BlockBlockMismatch(id, self.id))
}
}
}
#[derive(Debug)]
pub struct UnrolledBitcode {
pub(crate) modules: Vec<BitcodeModule>,
}
impl TryFrom<&[u8]> for UnrolledBitcode {
type Error = Error;
fn try_from(buf: &[u8]) -> Result<UnrolledBitcode, Self::Error> {
let (_, bitstream) = Bitstream::from(buf)?;
bitstream.try_into()
}
}
impl<T: AsRef<[u8]>> TryFrom<Bitstream<T>> for UnrolledBitcode {
type Error = Error;
fn try_from(mut bitstream: Bitstream<T>) -> Result<UnrolledBitcode, Self::Error> {
fn enter_block<T: AsRef<[u8]>>(
bitstream: &mut Bitstream<T>,
block: Block,
) -> Result<UnrolledBlock, Error> {
let mut unrolled_block = UnrolledBlock::new(block.block_id);
loop {
let entry = bitstream.next().ok_or_else(|| {
Error::BadUnroll("unexpected stream end during unroll".into())
})?;
match entry? {
StreamEntry::Record(record) => {
unrolled_block.records.push(UnrolledRecord(record))
}
StreamEntry::SubBlock(block) => {
let unrolled_child = enter_block(bitstream, block)?;
unrolled_block
.blocks
.entry(unrolled_child.id)
.or_insert_with(Vec::new)
.push(unrolled_child);
}
StreamEntry::EndBlock => {
break;
}
}
}
Ok(unrolled_block)
}
let mut partial_modules = Vec::new();
loop {
let entry = bitstream.next();
if entry.is_none() {
break;
}
let top_block = {
#[allow(clippy::unwrap_used)]
let block = entry.unwrap()?.as_block().ok_or_else(|| {
Error::BadUnroll("bitstream has non-blocks at the top-level scope".into())
})?;
enter_block(&mut bitstream, block)?
};
match top_block.id {
BlockId::Ir(IrBlockId::Identification) => {
partial_modules.push(PartialBitcodeModule::new(top_block));
}
BlockId::Ir(IrBlockId::Module) => {
let last_partial = partial_modules.last_mut().ok_or_else(|| {
Error::BadUnroll("malformed bitstream: MODULE_BLOCK with no preceding IDENTIFICATION_BLOCK".into())
})?;
match &last_partial.module {
Some(_) => {
return Err(Error::BadUnroll(
"malformed bitstream: adjacent MODULE_BLOCKs".into(),
))
}
None => last_partial.module = Some(top_block),
}
}
BlockId::Ir(IrBlockId::Strtab) => {
for prev_partial in partial_modules
.iter_mut()
.rev()
.take_while(|p| p.strtab.is_none())
{
prev_partial.strtab = Some(top_block.clone());
}
}
BlockId::Ir(IrBlockId::Symtab) => {
for prev_partial in partial_modules
.iter_mut()
.rev()
.take_while(|p| p.symtab.is_none())
{
prev_partial.symtab = Some(top_block.clone());
}
}
_ => {
return Err(Error::BadUnroll(format!(
"unexpected top-level block: {:?}",
top_block.id
)))
}
}
}
let modules = partial_modules
.into_iter()
.map(|p| p.reify())
.collect::<Result<Vec<_>, _>>()?;
let unrolled = UnrolledBitcode { modules };
Ok(unrolled)
}
}
#[derive(Debug)]
struct PartialBitcodeModule {
identification: UnrolledBlock,
module: Option<UnrolledBlock>,
strtab: Option<UnrolledBlock>,
symtab: Option<UnrolledBlock>,
}
impl PartialBitcodeModule {
pub(self) fn new(identification: UnrolledBlock) -> Self {
Self {
identification: identification,
module: None,
strtab: None,
symtab: None,
}
}
pub(self) fn reify(self) -> Result<BitcodeModule, Error> {
let mut ctx = MapCtx::default();
let strtab = Strtab::try_map(
&self.strtab.ok_or_else(|| {
Error::BadUnroll("missing STRTAB_BLOCK for bitcode module".into())
})?,
&mut ctx,
)?;
ctx.strtab = Some(strtab);
let identification = Identification::try_map(&self.identification, &mut ctx)?;
let module = Module::try_map(
&self.module.ok_or_else(|| {
Error::BadUnroll("missing MODULE_BLOCK for bitcode module".into())
})?,
&mut ctx,
)?;
let symtab = self
.symtab
.map(|s| Symtab::try_map(&s, &mut ctx))
.transpose()?;
#[allow(clippy::unwrap_used)]
Ok(BitcodeModule {
identification: identification,
module: module,
strtab: ctx.strtab.unwrap(),
symtab: symtab,
})
}
}
#[derive(Debug)]
pub struct BitcodeModule {
pub identification: Identification,
pub module: Module,
pub strtab: Strtab,
pub symtab: Option<Symtab>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unrolled_record_try_string() {
let record = UnrolledRecord(Record {
abbrev_id: None,
code: 0,
fields: b"\xff\xffvalid string!".iter().map(|b| *b as u64).collect(),
});
assert_eq!(record.try_string(2).unwrap(), "valid string!");
assert_eq!(record.try_string(8).unwrap(), "string!");
assert!(record.try_string(0).is_err());
assert!(record.try_string(record.0.fields.len()).is_err());
assert!(record.try_string(record.0.fields.len() - 1).is_err());
}
#[test]
fn test_unrolled_record_try_blob() {
let record = UnrolledRecord(Record {
abbrev_id: None,
code: 0,
fields: b"\xff\xffvalid string!".iter().map(|b| *b as u64).collect(),
});
assert_eq!(record.try_blob(0).unwrap(), b"\xff\xffvalid string!");
assert_eq!(record.try_blob(8).unwrap(), b"string!");
assert!(record.try_blob(record.0.fields.len()).is_err());
assert!(record.try_blob(record.0.fields.len() - 1).is_err());
}
}