-
-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stream + ParseBuffer lifetimes are awkward to work with #12
Comments
Yep, you've got it.
A So far this has worked out okay, with the pattern for the existing features being: // create a PDB from a File
let mut pdb = PDB::open(…)?;
// type_information() reads the TPI stream in its entirety, and TypeInformation owns that copy
// note also that reading things can mutate them, hence why `pdb` is `mut`
let type_information = pdb.type_information()?;
// do things with type_information -- parsing its Stream and whatnot -- but without mutating type_information I have also struggled with this at times. For example, I started by assuming that For me, I think the biggest mental hurdle is the |
Just ran across this again and wanted to drop a note about what I did to solve a similar issue in my pub struct Minidump<'a, T>
where T: Deref<Target=[u8]> + 'a,
{
data: T,
// ...
} That way it's possible to create a |
(I don't want to create a new issue since mine is pretty similar to what you wrote above.) I want to populate the type finder (or a custom structure: hashmap/whatever) in a constructor such that I can use it for lookup later in a method. Right now it doesn't seem to be possible. Basically, I have something similar in my I see no way to store the updated type finder information as part of the structure, to be used later from the method I mentioned. You can store Trying to store the type finder itself will lead you nowhere because it depends on local variables, which will get destroyed when the constructor exits, so Rust doesn't allow that, which is fine. I've also tried storing Doing something like The only way to achieve this at the moment (that I can think of) is to copy each enum variant manually, but this is not good. Not interested in changing my APIs either. Am I missing something? In addition, zero-copy is great, however, lifetimes is a pretty complex topic and this complicates things (especially for beginners). Maybe there should be a way to copy everything (you can write giant warnings in docs for such methods, but at least provide them in case people need that). |
You're not missing anything, this is indeed this complicated. Your issue is slightly different from the one in the issue description. The complexity comes from two facts:
You can go about this in multiple ways:
To give an example for // ItemMap borrows an item iterator and finder, and on-demand advances the
// iterator to populate the finder. It requires the caller to hold on to `TypeInformation`.
struct ItemMap<'s, I: ItemIndex> {
iter: pdb::ItemIter<'s, I>,
finder: pdb::ItemFinder<'s, I>,
}
impl<'s, I> ItemMap<'s, I>
where
I: ItemIndex,
{
pub fn try_get(&mut self, index: I) -> Result<pdb::Item<'s, I>, PdbError> {
if index <= self.finder.max_index() {
return Ok(self.finder.find(index)?);
}
while let Some(item) = self.iter.next()? {
self.finder.update(&self.iter);
match item.index().partial_cmp(&index) {
Some(Ordering::Equal) => return Ok(item),
Some(Ordering::Greater) => break,
_ => continue,
}
}
Err(pdb::Error::TypeNotFound(index.into()).into())
}
}
// This holds on to all owned data and must be put somewhere on the stack
// while someone is using TypeMap.
struct MyPdbUtil<'d> {
type_info: pdb::TypeInformation<'d>,
// ...
}
impl<'d> MyPdbUtil<'d> {
fn from_pdb(pdb: &mut PDB<'d>) -> Result<Self, PdbError> {
Ok(Self {
type_info: pdb.type_information()?,
// ...
})
}
fn type_map(&self) -> TypeMap<'_> {
ItemMap {
iter: self.type_info.iter(),
finder: self.type_info.finder(),
}
}
} Apart from that, the clean solution to achieve true zero-copy and solve the awkward lifetimes is by moving the paging logic. Right now, we read and assemble pages for the entire stream into a Vec. Instead, we could resolve the physical address of RVAs at access time, and would just have to ensure that we never read across page boundaries (so potentially return |
Thanks for the quick and detailed reply! FWIW, I "solved" it for now by creating a copy of I'll take a closer look at your suggestions sometime later. I've already spent too much time on this and need to focus on something else. But I'll let you know if there are any issues! Thanks again! |
I've also run into this problem in pdb-addr2line. I've ended up propagating the awkwardness to the consumer of my API; rather than just creating a |
I keep running into this when hacking on this crate. I think the root of the problem is that
SourceView::as_slice
returns a slice whose lifetime matches the lifetime of the reference to theSourceView
, not the contained lifetime's
, and thenStream::parse_buffer
returns aParseBuffer
with a lifetime derived from a slice returned fromSourceView::as_slice
. This means that in practice you have to keep both theStream
andParseBuffer
alive to do anything where you want to hand out references to data borrowed from the stream. This seems to mean that you always need an intermediate type in this situation to own theParseBuffer
.I understand why you've written things this way--you want to potentially support zero-copy operation by having a
SourceView
that points into a memory-mapped file. However, sinceReadView
owns its data, it can't actually hand out a reference that outlives itself, so you can't just changeSourceView::as_slice
to make the slice lifetime match theSourceView
lifetime.I don't know if there's a straightforward way to fix this, but it's frustrated me many times so I figured I'd at least write it down to get it out of my head.
The text was updated successfully, but these errors were encountered: