Search code examples
rustlifetime

How to extend variable lifetime when borrowing


I'm preparing a script for PDB information extraction. Currently I cannot use info variable even once because of its short lifetime.

I get 'borrowed value does not live long enough' error with the code below.

I understand this happens because of info.symbols()? borrow, but I ran out of ideas on how this could be fixed.

How can I append the items into mod_symbols?

Relevant library code (github.com/willglynn/pdb):

pub struct PDB<'s, S> {
    msf: Box<dyn Msf<'s, S> + 's>,
    dbi_header: Option<DBIHeader>,
    dbi_extra_streams: Option<DBIExtraStreams>,
}

impl<'s, S: Source<'s> + 's> PDB<'s, S> {
    pub fn module_info<'m>(&mut self, module: &Module<'m>) -> Result<Option<ModuleInfo<'s>>> {
        Ok(self
            .raw_stream(module.info().stream)?
            .map(|stream| ModuleInfo::parse(stream, module)))
    }
}


pub struct ModuleInfo<'s> {
    stream: Stream<'s>,
    symbols_size: usize,
    lines_size: LinesSize,
}

impl<'s> ModuleInfo<'s> {
    /// Get an iterator over the all symbols in this module.
    pub fn symbols(&self) -> Result<SymbolIter<'_>> {
        let mut buf = self.stream.parse_buffer();
        buf.truncate(self.symbols_size)?;
        if self.symbols_size > 0 {
            let sig = buf.parse_u32()?;
            if sig != constants::CV_SIGNATURE_C13 {
                return Err(Error::UnimplementedFeature(
                    "Unsupported symbol data format",
                ));
            }
        }
        Ok(SymbolIter::new(buf))
    }
}

My script:

fn dump_pdb<'a>(filename: &str, imagebase: u64) -> pdb::Result<()> {
    let file: File = std::fs::File::open(filename)?;
    let mut pdb: PDB<File> = pdb::PDB::open(file)?;
    
    // ...
    
    let dbi: DebugInformation = pdb.debug_information()?;
    let mut modules: ModuleIter = dbi.modules()?;
    
    // merge the module functions
    let mut mod_symbols: Vec<pdb::Symbol> = Vec::new();
    
    while let Some(module) = modules.next()? {
        println!("Module: {}", module.object_file_name());
        let info: ModuleInfo = match pdb.module_info(&module)? {
            Some(info) => info,
            None => {
                println!("no module info");
                continue;
            }
        };

        while let Some(symbol) = info.symbols()?.next()? {  // `info` does not live long enough
            mod_symbols.push(symbol);                       // borrow later used here
        }
    }
    
    // ...
}

Error:

error[E0597]: `info` does not live long enough
   --> examples\pdb_symbols2json.rs:251:34
    |
251 |         while let Some(symbol) = info.symbols()?.next()? {
    |                                  ^^^^^^^^^^^^^^ borrowed value does not live long enough
252 |             mod_symbols.push(symbol);
    |             ------------------------ borrow later used here
...
282 |     }
    |     - `info` dropped here while still borrowed



Solution

  • The problem is that it looks like most types in pdb have a hidden lifetime argument.

    IMO, you should write pdb::Symbol<'_> and pdb::Module<'_> to make that apparent. I expect that a future Rust edition will make not writing the lifetime argument a hard error.

    About your particular situation, I don't know the details of that crate, but I guess that your mod_symbols is storing values ultimately borrowed from info, so one should outlive the other. But you are creating and destroying info with each iteration of the loop, and that would invalidate the values stored in the mod_symbols.

    You can try storing the info values in an outer scope. Something like this (untested):

        // infos lives longer than mod_symbols!
        let mut infos: Vec<ModuleInfo<'_>> = Vec::new();
        let mut mod_symbols: Vec<pdb::Symbol<'_>> = Vec::new();
    
        while let Some(module) = modules.next()? {
            println!("Module: {}", module.object_file_name());
            let info = match pdb.module_info(&module)? {
                Some(info) => info,
                None => {
                    println!("no module info");
                    continue;
                }
            };
            infos.push(info);
        }
        for info in &infos {
            while let Some(symbol) = info.symbols()?.next()? {
                mod_symbols.push(symbol);
            }
        }