Decommission rbpf-cli, replaced by solana-ledger-tool run subcommand (#31512)
This commit is contained in:
parent
cfaf744051
commit
078304888d
|
@ -4175,16 +4175,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "rbpf-cli"
|
||||
version = "1.16.0"
|
||||
dependencies = [
|
||||
"clap 3.2.23",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solana-bpf-loader-program",
|
||||
"solana-logger 1.16.0",
|
||||
"solana-program-runtime",
|
||||
"solana-sdk 1.16.0",
|
||||
"solana_rbpf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
|
|
|
@ -113,21 +113,17 @@ To turn on SBF interpreter trace messages in a local cluster configure the
|
|||
## Source level debugging
|
||||
|
||||
Source level debugging of on-chain programs written in Rust or C can
|
||||
be done using the stand-alone tool rbpf-cli, included in the SDK, and
|
||||
lldb, distrubuted with Solana Rust and Clang compiler binary package
|
||||
be done using the `run` subcommand of `solana-ledger-tool`, and lldb,
|
||||
distrubuted with Solana Rust and Clang compiler binary package
|
||||
platform-tools.
|
||||
|
||||
The rbpf-cli tool loads a compiled on-chain program, executes it in
|
||||
RBPF virtual machine and runs a gdb server that accepts incoming
|
||||
connections from LLDB or GDB. Once lldb is connected to rbpf-cli
|
||||
gdbserver, it can control execution of an on-chain program. The
|
||||
execution environment of rbpf-cli is limited to the loaded program and
|
||||
the input data only. Run `rbpf-cli --help` for an example of
|
||||
specifying input data for parameters of the program entrypoint
|
||||
function. Any information that exists on a blockchain is not
|
||||
available to the program when executed in rbpf-cli. For example, CPI
|
||||
calls don't work, reading any information from blockchain doesn't
|
||||
work, etc.
|
||||
The `solana-ledger-tool run` subcommand loads a compiled on-chain
|
||||
program, executes it in RBPF virtual machine and runs a gdb server
|
||||
that accepts incoming connections from LLDB or GDB. Once lldb is
|
||||
connected to `solana-ledger-tool` gdbserver, it can control execution of
|
||||
an on-chain program. Run `solana-ledger-tool run --help`
|
||||
for an example of specifying input data for parameters of the program
|
||||
entrypoint function.
|
||||
|
||||
To compile a program for debugging use cargo-build-sbf build utility
|
||||
with the command line option `--debug`. The utility will generate two
|
||||
|
@ -135,28 +131,35 @@ loadable files, one a usual loadable module with the extension `.so`,
|
|||
and another the same loadable module but containing Dwarf debug
|
||||
information, a file with extension `.debug`.
|
||||
|
||||
To execute a program in debugger, run rbpf-cli with `-u debugger`
|
||||
command line option. For example, a crate named 'helloworld' is
|
||||
compiled and an executable program is built in `target/deploy`
|
||||
directory. There should be three files in that directory
|
||||
To execute a program in debugger, run `solana-ledger-tool run` with
|
||||
`-u debugger` command line option. For example, a crate named
|
||||
'helloworld' is compiled and an executable program is built in
|
||||
`target/deploy` directory. There should be three files in that
|
||||
directory
|
||||
- helloworld-keypair.json -- a keypair for deploying the program,
|
||||
- helloworld.debug -- a binary file containg debug information,
|
||||
- helloworld.so -- an executable file loadable into the virtual machine.
|
||||
The command line for running rbp-cli would be something like this
|
||||
The command line for running `solana-ledger-tool` would be something like this
|
||||
```
|
||||
rbpf-cli -u debugger target/deploy/helloworld.so
|
||||
solana-ledger-tool run -l test-ledger -u debugger target/deploy/helloworld.so
|
||||
```
|
||||
Note that `solana-ledger-tool` always loads a ledger database. Most
|
||||
on-chain programs interact with a ledger in some manner. Even if for
|
||||
debugging purpose a ledger is not needed, it has to be provided to
|
||||
`solana-ledger-tool`. A minimal ladger database can be created by
|
||||
running `solana-test-validator`, which creates a ledger in
|
||||
`test-ledger` subdirectory.
|
||||
|
||||
In debugger mode rbpf-cli loads an `.so` file and starts listening for
|
||||
an incoming connection from a debugger
|
||||
In debugger mode `solana-ledger-tool run` loads an `.so` file and
|
||||
starts listening for an incoming connection from a debugger
|
||||
```
|
||||
Waiting for a Debugger connection on "127.0.0.1:9001"...
|
||||
```
|
||||
|
||||
To connect to rbpf-cli and execute the program, run lldb. For
|
||||
To connect to `solana-ledger-tool` and execute the program, run lldb. For
|
||||
debugging rust programs it may be beneficial to run solana-lldb
|
||||
wrapper to lldb, i.e. at a new shell prompt (other than the one used
|
||||
to run rbpf-cli) run the command
|
||||
to run `solana-ledger-tool`) run the command
|
||||
|
||||
```
|
||||
solana-lldb
|
||||
|
@ -178,8 +181,8 @@ If the debugger finds the file, it will print something like this
|
|||
Current executable set to '/path/helloworld.debug' (bpf).
|
||||
```
|
||||
|
||||
Now, connect to the gdb server that rbpf-cli implements, and debug the
|
||||
program as usual. Enter the following command at lldb prompt
|
||||
Now, connect to the gdb server that `solana-ledger-tool` implements, and
|
||||
debug the program as usual. Enter the following command at lldb prompt
|
||||
```
|
||||
(lldb) gdb-remote 127.0.0.1:9001
|
||||
```
|
||||
|
@ -225,15 +228,15 @@ First file is `tasks.json` with the following content
|
|||
}
|
||||
},
|
||||
{
|
||||
"label": "rbpf",
|
||||
"label": "solana-debugger",
|
||||
"type": "shell",
|
||||
"command": "rbpf-cli -u debugger ${workspaceFolder}/target/deploy/helloworld.so"
|
||||
"command": "solana-ledger-tool run -l test-ledger -u debugger ${workspaceFolder}/target/deploy/helloworld.so"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
The first task is to build the on-chain program using cargo-build-sbf
|
||||
utility. The second task is to run rbpf-cli in debugger mode.
|
||||
utility. The second task is to run `solana-ledger-tool run` in debugger mode.
|
||||
|
||||
Another file is `launch.json` with the following content
|
||||
```
|
||||
|
@ -251,12 +254,12 @@ Another file is `launch.json` with the following content
|
|||
}
|
||||
```
|
||||
This file specifies how to run debugger and to connect it to the gdb
|
||||
server implemented by rbpf-cli.
|
||||
server implemented by `solana-ledger-tool`.
|
||||
|
||||
To start debugging a program, first build it by running the build
|
||||
task. The next step is to run rbpf task. The tasks specified in
|
||||
task. The next step is to run `solana-debugger` task. The tasks specified in
|
||||
`tasks.json` file are started from `Terminal >> Run Task...` menu of
|
||||
VSCode. When rbpf-cli is running and listening from incoming
|
||||
VSCode. When `solana-ledger-tool` is running and listening from incoming
|
||||
connections, it's time to start the debugger. Launch it from VSCode
|
||||
`Run and Debug` menu. If everything is set up correctly, VSCode will
|
||||
start a debugging session and the porogram execution should stop on
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "rbpf-cli"
|
||||
description = "CLI to test and analyze SBF programs"
|
||||
description = "Decommissioned CLI to test and analyze SBF programs"
|
||||
keywords = ["SBF", "interpreter", "JIT"]
|
||||
publish = false
|
||||
version = { workspace = true }
|
||||
|
@ -11,11 +11,3 @@ license = { workspace = true }
|
|||
edition = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.1.5", features = ["cargo"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
solana-bpf-loader-program = { workspace = true }
|
||||
solana-logger = { workspace = true }
|
||||
solana-program-runtime = { workspace = true }
|
||||
solana-sdk = { workspace = true }
|
||||
solana_rbpf = { workspace = true, features = ["debugger"] }
|
||||
|
|
|
@ -1,421 +1,6 @@
|
|||
use {
|
||||
clap::{crate_version, Arg, Command},
|
||||
serde::{Deserialize, Serialize},
|
||||
serde_json::Result,
|
||||
solana_bpf_loader_program::{
|
||||
create_vm, load_program_from_bytes, serialization::serialize_parameters,
|
||||
syscalls::create_loader,
|
||||
},
|
||||
solana_program_runtime::{
|
||||
invoke_context::InvokeContext,
|
||||
loaded_programs::{LoadProgramMetrics, LoadedProgramType},
|
||||
with_mock_invoke_context,
|
||||
},
|
||||
solana_rbpf::{
|
||||
assembler::assemble, elf::Executable, static_analysis::Analysis,
|
||||
verifier::RequisiteVerifier, vm::VerifiedExecutable,
|
||||
},
|
||||
solana_sdk::{
|
||||
account::AccountSharedData,
|
||||
bpf_loader,
|
||||
pubkey::Pubkey,
|
||||
slot_history::Slot,
|
||||
transaction_context::{IndexOfAccount, InstructionAccount},
|
||||
},
|
||||
std::{
|
||||
fmt::{Debug, Formatter},
|
||||
fs::File,
|
||||
io::{Read, Seek, Write},
|
||||
path::Path,
|
||||
time::{Duration, Instant},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Account {
|
||||
key: Pubkey,
|
||||
owner: Pubkey,
|
||||
is_signer: bool,
|
||||
is_writable: bool,
|
||||
lamports: u64,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Input {
|
||||
accounts: Vec<Account>,
|
||||
instruction_data: Vec<u8>,
|
||||
}
|
||||
fn load_accounts(path: &Path) -> Result<Input> {
|
||||
let file = File::open(path).unwrap();
|
||||
let input: Input = serde_json::from_reader(file)?;
|
||||
eprintln!("Program input:");
|
||||
eprintln!("accounts {:?}", &input.accounts);
|
||||
eprintln!("instruction_data {:?}", &input.instruction_data);
|
||||
eprintln!("----------------------------------------");
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
let matches = Command::new("Solana SBF CLI")
|
||||
.version(crate_version!())
|
||||
.author("Solana Labs Maintainers <maintainers@solanalabs.com>")
|
||||
.about(
|
||||
r##"CLI to test and analyze SBF programs.
|
||||
|
||||
The tool executes SBF programs in a mocked environment.
|
||||
Some features, such as sysvars syscall and CPI, are not
|
||||
available for the programs executed by the CLI tool.
|
||||
|
||||
The input data for a program execution have to be in JSON format
|
||||
and the following fields are required
|
||||
{
|
||||
"accounts": [
|
||||
{
|
||||
"key": [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
],
|
||||
"owner": [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
],
|
||||
"is_signer": false,
|
||||
"is_writable": true,
|
||||
"lamports": 1000,
|
||||
"data": [0, 0, 0, 3]
|
||||
}
|
||||
],
|
||||
"instruction_data": []
|
||||
}
|
||||
"##,
|
||||
)
|
||||
.arg(
|
||||
Arg::new("PROGRAM")
|
||||
.help(
|
||||
"Program file to use. This is either an ELF shared-object file to be executed, \
|
||||
or an assembly file to be assembled and executed.",
|
||||
)
|
||||
.required(true)
|
||||
.index(1)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("input")
|
||||
.help(
|
||||
"Input for the program to run on, where FILE is a name of a JSON file \
|
||||
with input data, or BYTES is the number of 0-valued bytes to allocate for program parameters",
|
||||
)
|
||||
.short('i')
|
||||
.long("input")
|
||||
.value_name("FILE / BYTES")
|
||||
.takes_value(true)
|
||||
.default_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("memory")
|
||||
.help("Heap memory for the program to run on")
|
||||
.short('m')
|
||||
.long("memory")
|
||||
.value_name("BYTES")
|
||||
.takes_value(true)
|
||||
.default_value("0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("use")
|
||||
.help(
|
||||
"Method of execution to use, where 'cfg' generates Control Flow Graph \
|
||||
of the program, 'disassembler' dumps disassembled code of the program, 'interpreter' runs \
|
||||
the program in the virtual machine's interpreter, 'debugger' is the same as 'interpreter' \
|
||||
but hosts a GDB interface, and 'jit' precompiles the program to native machine code \
|
||||
before execting it in the virtual machine.",
|
||||
)
|
||||
.short('u')
|
||||
.long("use")
|
||||
.takes_value(true)
|
||||
.value_name("VALUE")
|
||||
.possible_values(["cfg", "disassembler", "interpreter", "debugger", "jit"])
|
||||
.default_value("jit"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("instruction limit")
|
||||
.help("Limit the number of instructions to execute")
|
||||
.short('l')
|
||||
.long("limit")
|
||||
.takes_value(true)
|
||||
.value_name("COUNT")
|
||||
.default_value(&std::i64::MAX.to_string()),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("port")
|
||||
.help("Port to use for the connection with a remote debugger")
|
||||
.long("port")
|
||||
.takes_value(true)
|
||||
.value_name("PORT")
|
||||
.default_value("9001"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("output_format")
|
||||
.help("Return information in specified output format")
|
||||
.long("output")
|
||||
.value_name("FORMAT")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.possible_values(["json", "json-compact"]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("trace")
|
||||
.help("Output instruction trace")
|
||||
.short('t')
|
||||
.long("trace")
|
||||
.takes_value(true)
|
||||
.value_name("FILE"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let loader_id = bpf_loader::id();
|
||||
let mut transaction_accounts = vec![
|
||||
(
|
||||
loader_id,
|
||||
AccountSharedData::new(0, 0, &solana_sdk::native_loader::id()),
|
||||
),
|
||||
(
|
||||
Pubkey::new_unique(),
|
||||
AccountSharedData::new(0, 0, &loader_id),
|
||||
),
|
||||
];
|
||||
let mut instruction_accounts = Vec::new();
|
||||
let instruction_data = match matches.value_of("input").unwrap().parse::<usize>() {
|
||||
Ok(allocation_size) => {
|
||||
let pubkey = Pubkey::new_unique();
|
||||
transaction_accounts.push((
|
||||
pubkey,
|
||||
AccountSharedData::new(0, allocation_size, &Pubkey::new_unique()),
|
||||
));
|
||||
instruction_accounts.push(InstructionAccount {
|
||||
index_in_transaction: 0,
|
||||
index_in_caller: 0,
|
||||
index_in_callee: 0,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
});
|
||||
vec![]
|
||||
}
|
||||
Err(_) => {
|
||||
let input = load_accounts(Path::new(matches.value_of("input").unwrap())).unwrap();
|
||||
for (index, account_info) in input.accounts.into_iter().enumerate() {
|
||||
let mut account = AccountSharedData::new(
|
||||
account_info.lamports,
|
||||
account_info.data.len(),
|
||||
&account_info.owner,
|
||||
);
|
||||
account.set_data(account_info.data);
|
||||
transaction_accounts.push((account_info.key, account));
|
||||
instruction_accounts.push(InstructionAccount {
|
||||
index_in_transaction: index as IndexOfAccount,
|
||||
index_in_caller: index as IndexOfAccount,
|
||||
index_in_callee: index as IndexOfAccount,
|
||||
is_signer: account_info.is_signer,
|
||||
is_writable: account_info.is_writable,
|
||||
});
|
||||
}
|
||||
input.instruction_data
|
||||
}
|
||||
};
|
||||
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.get_next_instruction_context()
|
||||
.unwrap()
|
||||
.configure(&[0, 1], &instruction_accounts, &instruction_data);
|
||||
invoke_context.push().unwrap();
|
||||
let (_parameter_bytes, regions, account_lengths) = serialize_parameters(
|
||||
invoke_context.transaction_context,
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.get_current_instruction_context()
|
||||
.unwrap(),
|
||||
true, // should_cap_ix_accounts
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let program = matches.value_of("PROGRAM").unwrap();
|
||||
let mut file = File::open(Path::new(program)).unwrap();
|
||||
let mut magic = [0u8; 4];
|
||||
file.read_exact(&mut magic).unwrap();
|
||||
file.rewind().unwrap();
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents).unwrap();
|
||||
// Allowing mut here, since it is needed for jit compile, which is under a config flag
|
||||
#[allow(unused_mut)]
|
||||
let mut verified_executable = if magic == [0x7f, 0x45, 0x4c, 0x46] {
|
||||
let mut load_program_metrics = LoadProgramMetrics::default();
|
||||
let result = load_program_from_bytes(
|
||||
&invoke_context.feature_set,
|
||||
invoke_context.get_compute_budget(),
|
||||
None,
|
||||
&mut load_program_metrics,
|
||||
&contents,
|
||||
&bpf_loader::id(),
|
||||
contents.len(),
|
||||
Slot::default(),
|
||||
true, /* reject_deployment_of_broken_elfs */
|
||||
true, /* debugging_features */
|
||||
);
|
||||
match result {
|
||||
Ok(loaded_program) => match loaded_program.program {
|
||||
LoadedProgramType::LegacyV1(program) => Ok(unsafe { std::mem::transmute(program) }),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Err(err) => Err(format!("Loading executable failed: {err:?}")),
|
||||
}
|
||||
} else {
|
||||
let loader = create_loader(
|
||||
&invoke_context.feature_set,
|
||||
invoke_context.get_compute_budget(),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
let executable =
|
||||
assemble::<InvokeContext>(std::str::from_utf8(contents.as_slice()).unwrap(), loader)
|
||||
.unwrap();
|
||||
VerifiedExecutable::<RequisiteVerifier, InvokeContext>::from_executable(executable)
|
||||
.map_err(|err| format!("Assembling executable failed: {err:?}"))
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
|
||||
verified_executable.jit_compile().unwrap();
|
||||
let mut analysis = LazyAnalysis::new(verified_executable.get_executable());
|
||||
|
||||
match matches.value_of("use") {
|
||||
Some("cfg") => {
|
||||
let mut file = File::create("cfg.dot").unwrap();
|
||||
analysis
|
||||
.analyze()
|
||||
.visualize_graphically(&mut file, None)
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
Some("disassembler") => {
|
||||
let stdout = std::io::stdout();
|
||||
analysis.analyze().disassemble(&mut stdout.lock()).unwrap();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
create_vm!(
|
||||
vm,
|
||||
&verified_executable,
|
||||
regions,
|
||||
account_lengths,
|
||||
&mut invoke_context,
|
||||
println!(
|
||||
r##"rbpf-cli is replaced by solana-ledger-tool run subcommand.
|
||||
Please, use 'solana-ledger-tool run --help' for more information."##
|
||||
);
|
||||
let mut vm = vm.unwrap();
|
||||
let start_time = Instant::now();
|
||||
if matches.value_of("use").unwrap() == "debugger" {
|
||||
vm.debug_port = Some(matches.value_of("port").unwrap().parse::<u16>().unwrap());
|
||||
}
|
||||
let (instruction_count, result) = vm.execute_program(matches.value_of("use").unwrap() != "jit");
|
||||
let duration = Instant::now() - start_time;
|
||||
if matches.occurrences_of("trace") > 0 {
|
||||
for (frame, syscall_context) in vm
|
||||
.env
|
||||
.context_object_pointer
|
||||
.syscall_context
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if syscall_context.is_none() {
|
||||
continue;
|
||||
}
|
||||
let trace_log = syscall_context.as_ref().unwrap().trace_log.as_slice();
|
||||
if matches.value_of("trace").unwrap() == "stdout" {
|
||||
writeln!(&mut std::io::stdout(), "Frame {frame}").unwrap();
|
||||
analysis
|
||||
.analyze()
|
||||
.disassemble_trace_log(&mut std::io::stdout(), trace_log)
|
||||
.unwrap();
|
||||
} else {
|
||||
let filename = format!("{}.{}", matches.value_of("trace").unwrap(), frame);
|
||||
let mut fd = File::create(filename).unwrap();
|
||||
writeln!(&fd, "Frame {frame}").unwrap();
|
||||
analysis
|
||||
.analyze()
|
||||
.disassemble_trace_log(&mut fd, trace_log)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(vm);
|
||||
|
||||
let output = Output {
|
||||
result: format!("{result:?}"),
|
||||
instruction_count,
|
||||
execution_time: duration,
|
||||
log: invoke_context
|
||||
.get_log_collector()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.get_recorded_content()
|
||||
.to_vec(),
|
||||
};
|
||||
match matches.value_of("output_format") {
|
||||
Some("json") => {
|
||||
println!("{}", serde_json::to_string_pretty(&output).unwrap());
|
||||
}
|
||||
Some("json-compact") => {
|
||||
println!("{}", serde_json::to_string(&output).unwrap());
|
||||
}
|
||||
_ => {
|
||||
println!("Program output:");
|
||||
println!("{output:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Output {
|
||||
result: String,
|
||||
instruction_count: u64,
|
||||
execution_time: Duration,
|
||||
log: Vec<String>,
|
||||
}
|
||||
|
||||
impl Debug for Output {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Result: {}", self.result)?;
|
||||
writeln!(f, "Instruction Count: {}", self.instruction_count)?;
|
||||
writeln!(f, "Execution time: {} us", self.execution_time.as_micros())?;
|
||||
for line in &self.log {
|
||||
writeln!(f, "{line}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Replace with std::lazy::Lazy when stabilized.
|
||||
// https://github.com/rust-lang/rust/issues/74465
|
||||
struct LazyAnalysis<'a, 'b> {
|
||||
analysis: Option<Analysis<'a>>,
|
||||
executable: &'a Executable<InvokeContext<'b>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> LazyAnalysis<'a, 'b> {
|
||||
fn new(executable: &'a Executable<InvokeContext<'b>>) -> Self {
|
||||
Self {
|
||||
analysis: None,
|
||||
executable,
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze(&mut self) -> &Analysis {
|
||||
if let Some(ref analysis) = self.analysis {
|
||||
return analysis;
|
||||
}
|
||||
self.analysis
|
||||
.insert(Analysis::from_executable(self.executable).unwrap())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue