2021-12-03 09:00:31 -08:00
|
|
|
use {
|
2022-03-07 09:30:47 -08:00
|
|
|
clap::{crate_version, Arg, Command},
|
2021-12-03 09:00:31 -08:00
|
|
|
serde::{Deserialize, Serialize},
|
|
|
|
serde_json::Result,
|
|
|
|
solana_bpf_loader_program::{
|
2023-03-10 10:05:29 -08:00
|
|
|
create_ebpf_vm, create_vm, serialization::serialize_parameters, syscalls::create_loader,
|
2022-12-11 23:47:09 -08:00
|
|
|
},
|
2023-04-03 08:23:24 -07:00
|
|
|
solana_program_runtime::{invoke_context::InvokeContext, with_mock_invoke_context},
|
2021-12-03 09:00:31 -08:00
|
|
|
solana_rbpf::{
|
2022-12-11 23:47:09 -08:00
|
|
|
assembler::assemble, elf::Executable, static_analysis::Analysis,
|
|
|
|
verifier::RequisiteVerifier, vm::VerifiedExecutable,
|
2021-12-03 09:00:31 -08:00
|
|
|
},
|
2021-12-17 05:01:12 -08:00
|
|
|
solana_sdk::{
|
2023-04-03 08:23:24 -07:00
|
|
|
account::AccountSharedData,
|
|
|
|
bpf_loader,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
transaction_context::{IndexOfAccount, InstructionAccount},
|
2021-12-17 05:01:12 -08:00
|
|
|
},
|
2021-12-03 09:00:31 -08:00
|
|
|
std::{
|
2022-02-13 09:50:22 -08:00
|
|
|
fmt::{Debug, Formatter},
|
2021-12-03 09:00:31 -08:00
|
|
|
fs::File,
|
2023-03-31 12:52:49 -07:00
|
|
|
io::{Read, Seek, Write},
|
2021-12-03 09:00:31 -08:00
|
|
|
path::Path,
|
2022-02-13 09:50:22 -08:00
|
|
|
time::{Duration, Instant},
|
2021-12-03 09:00:31 -08:00
|
|
|
},
|
2021-05-28 04:28:30 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct Account {
|
2021-11-04 13:47:32 -07:00
|
|
|
key: Pubkey,
|
|
|
|
owner: Pubkey,
|
|
|
|
is_signer: bool,
|
|
|
|
is_writable: bool,
|
2021-05-28 04:28:30 -07:00
|
|
|
lamports: u64,
|
|
|
|
data: Vec<u8>,
|
|
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
struct Input {
|
|
|
|
accounts: Vec<Account>,
|
2021-11-04 13:47:32 -07:00
|
|
|
instruction_data: Vec<u8>,
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
fn load_accounts(path: &Path) -> Result<Input> {
|
|
|
|
let file = File::open(path).unwrap();
|
|
|
|
let input: Input = serde_json::from_reader(file)?;
|
2022-05-20 20:29:10 -07:00
|
|
|
eprintln!("Program input:");
|
|
|
|
eprintln!("accounts {:?}", &input.accounts);
|
|
|
|
eprintln!("instruction_data {:?}", &input.instruction_data);
|
|
|
|
eprintln!("----------------------------------------");
|
2021-05-28 04:28:30 -07:00
|
|
|
Ok(input)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
solana_logger::setup();
|
2022-10-28 11:30:31 -07:00
|
|
|
let matches = Command::new("Solana SBF CLI")
|
2021-05-28 04:28:30 -07:00
|
|
|
.version(crate_version!())
|
2023-01-31 05:07:13 -08:00
|
|
|
.author("Solana Labs Maintainers <maintainers@solanalabs.com>")
|
2021-05-28 04:28:30 -07:00
|
|
|
.about(
|
2022-10-28 11:30:31 -07:00
|
|
|
r##"CLI to test and analyze SBF programs.
|
2021-05-28 04:28:30 -07:00
|
|
|
|
2022-10-28 11:30:31 -07:00
|
|
|
The tool executes SBF programs in a mocked environment.
|
2021-05-28 04:28:30 -07:00
|
|
|
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": [
|
|
|
|
{
|
2021-11-04 13:47:32 -07:00
|
|
|
"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
|
|
|
|
],
|
2021-05-28 04:28:30 -07:00
|
|
|
"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
|
2021-11-04 13:47:32 -07:00
|
|
|
],
|
|
|
|
"is_signer": false,
|
|
|
|
"is_writable": true,
|
|
|
|
"lamports": 1000,
|
|
|
|
"data": [0, 0, 0, 3]
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
],
|
2021-11-04 13:47:32 -07:00
|
|
|
"instruction_data": []
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
"##,
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::new("PROGRAM")
|
2022-03-07 09:30:47 -08:00
|
|
|
.help(
|
2021-05-28 04:28:30 -07:00
|
|
|
"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")
|
2022-03-07 09:30:47 -08:00
|
|
|
.help(
|
2021-05-28 04:28:30 -07:00
|
|
|
"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")
|
2022-03-07 09:30:47 -08:00
|
|
|
.help("Heap memory for the program to run on")
|
2021-05-28 04:28:30 -07:00
|
|
|
.short('m')
|
|
|
|
.long("memory")
|
|
|
|
.value_name("BYTES")
|
|
|
|
.takes_value(true)
|
|
|
|
.default_value("0"),
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::new("use")
|
2022-03-07 09:30:47 -08:00
|
|
|
.help(
|
2021-05-28 04:28:30 -07:00
|
|
|
"Method of execution to use, where 'cfg' generates Control Flow Graph \
|
|
|
|
of the program, 'disassembler' dumps disassembled code of the program, 'interpreter' runs \
|
2022-11-15 06:21:11 -08:00
|
|
|
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.",
|
2021-05-28 04:28:30 -07:00
|
|
|
)
|
|
|
|
.short('u')
|
|
|
|
.long("use")
|
|
|
|
.takes_value(true)
|
|
|
|
.value_name("VALUE")
|
2022-11-15 06:21:11 -08:00
|
|
|
.possible_values(["cfg", "disassembler", "interpreter", "debugger", "jit"])
|
2021-07-14 13:30:38 -07:00
|
|
|
.default_value("jit"),
|
2021-05-28 04:28:30 -07:00
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::new("instruction limit")
|
2022-03-07 09:30:47 -08:00
|
|
|
.help("Limit the number of instructions to execute")
|
2021-05-28 04:28:30 -07:00
|
|
|
.short('l')
|
|
|
|
.long("limit")
|
|
|
|
.takes_value(true)
|
|
|
|
.value_name("COUNT")
|
|
|
|
.default_value(&std::i64::MAX.to_string()),
|
|
|
|
)
|
|
|
|
.arg(
|
2022-11-15 06:21:11 -08:00
|
|
|
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"),
|
2021-05-28 04:28:30 -07:00
|
|
|
)
|
2022-02-13 09:50:22 -08:00
|
|
|
.arg(
|
|
|
|
Arg::new("output_format")
|
2022-03-07 09:30:47 -08:00
|
|
|
.help("Return information in specified output format")
|
2022-02-13 09:50:22 -08:00
|
|
|
.long("output")
|
|
|
|
.value_name("FORMAT")
|
|
|
|
.global(true)
|
|
|
|
.takes_value(true)
|
2022-09-22 15:23:03 -07:00
|
|
|
.possible_values(["json", "json-compact"]),
|
2022-02-13 09:50:22 -08:00
|
|
|
)
|
2022-12-15 17:08:00 -08:00
|
|
|
.arg(
|
|
|
|
Arg::new("trace")
|
|
|
|
.help("Output instruction trace")
|
|
|
|
.short('t')
|
|
|
|
.long("trace")
|
|
|
|
.takes_value(true)
|
|
|
|
.value_name("FILE"),
|
|
|
|
)
|
2021-05-28 04:28:30 -07:00
|
|
|
.get_matches();
|
|
|
|
|
2021-11-04 13:47:32 -07:00
|
|
|
let loader_id = bpf_loader::id();
|
2021-12-17 05:01:12 -08:00
|
|
|
let mut transaction_accounts = vec![
|
2021-11-04 13:47:32 -07:00
|
|
|
(
|
|
|
|
loader_id,
|
2021-12-17 05:01:12 -08:00
|
|
|
AccountSharedData::new(0, 0, &solana_sdk::native_loader::id()),
|
2021-11-04 13:47:32 -07:00
|
|
|
),
|
|
|
|
(
|
|
|
|
Pubkey::new_unique(),
|
2021-12-17 05:01:12 -08:00
|
|
|
AccountSharedData::new(0, 0, &loader_id),
|
2021-11-04 13:47:32 -07:00
|
|
|
),
|
|
|
|
];
|
2021-12-17 05:01:12 -08:00
|
|
|
let mut instruction_accounts = Vec::new();
|
2021-11-04 13:47:32 -07:00
|
|
|
let instruction_data = match matches.value_of("input").unwrap().parse::<usize>() {
|
|
|
|
Ok(allocation_size) => {
|
2021-12-17 05:01:12 -08:00
|
|
|
let pubkey = Pubkey::new_unique();
|
|
|
|
transaction_accounts.push((
|
|
|
|
pubkey,
|
|
|
|
AccountSharedData::new(0, allocation_size, &Pubkey::new_unique()),
|
2021-11-04 13:47:32 -07:00
|
|
|
));
|
2023-04-03 08:23:24 -07:00
|
|
|
instruction_accounts.push(InstructionAccount {
|
|
|
|
index_in_transaction: 0,
|
|
|
|
index_in_caller: 0,
|
|
|
|
index_in_callee: 0,
|
2021-12-17 05:01:12 -08:00
|
|
|
is_signer: false,
|
|
|
|
is_writable: true,
|
|
|
|
});
|
2021-11-04 13:47:32 -07:00
|
|
|
vec![]
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
Err(_) => {
|
2021-06-18 06:34:46 -07:00
|
|
|
let input = load_accounts(Path::new(matches.value_of("input").unwrap())).unwrap();
|
2023-04-03 08:23:24 -07:00
|
|
|
for (index, account_info) in input.accounts.into_iter().enumerate() {
|
2021-12-17 05:01:12 -08:00
|
|
|
let mut account = AccountSharedData::new(
|
|
|
|
account_info.lamports,
|
|
|
|
account_info.data.len(),
|
|
|
|
&account_info.owner,
|
2021-11-04 13:47:32 -07:00
|
|
|
);
|
2021-12-17 05:01:12 -08:00
|
|
|
account.set_data(account_info.data);
|
2023-04-03 08:23:24 -07:00
|
|
|
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,
|
2021-12-17 05:01:12 -08:00
|
|
|
is_signer: account_info.is_signer,
|
|
|
|
is_writable: account_info.is_writable,
|
|
|
|
});
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
2021-11-04 13:47:32 -07:00
|
|
|
input.instruction_data
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
};
|
2023-04-03 08:23:24 -07:00
|
|
|
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
|
2023-02-10 07:17:44 -08:00
|
|
|
invoke_context.enable_instruction_tracing = true;
|
2021-11-04 13:47:32 -07:00
|
|
|
invoke_context
|
2022-09-03 01:34:57 -07:00
|
|
|
.transaction_context
|
|
|
|
.get_next_instruction_context()
|
|
|
|
.unwrap()
|
2023-04-03 08:23:24 -07:00
|
|
|
.configure(&[0, 1], &instruction_accounts, &instruction_data);
|
2022-09-03 01:34:57 -07:00
|
|
|
invoke_context.push().unwrap();
|
2022-10-06 23:45:05 -07:00
|
|
|
let (_parameter_bytes, regions, account_lengths) = serialize_parameters(
|
2022-01-03 14:30:56 -08:00
|
|
|
invoke_context.transaction_context,
|
|
|
|
invoke_context
|
|
|
|
.transaction_context
|
|
|
|
.get_current_instruction_context()
|
|
|
|
.unwrap(),
|
2022-07-20 05:12:43 -07:00
|
|
|
true, // should_cap_ix_accounts
|
2021-11-04 13:47:32 -07:00
|
|
|
)
|
|
|
|
.unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
|
|
|
|
let program = matches.value_of("PROGRAM").unwrap();
|
2022-09-22 15:23:03 -07:00
|
|
|
let mut file = File::open(Path::new(program)).unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
let mut magic = [0u8; 4];
|
|
|
|
file.read_exact(&mut magic).unwrap();
|
2022-12-06 06:11:03 -08:00
|
|
|
file.rewind().unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
let mut contents = Vec::new();
|
|
|
|
file.read_to_end(&mut contents).unwrap();
|
2022-12-11 23:47:09 -08:00
|
|
|
let loader = create_loader(
|
|
|
|
&invoke_context.feature_set,
|
|
|
|
&ComputeBudget::default(),
|
|
|
|
true,
|
|
|
|
true,
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
.unwrap();
|
2022-06-07 04:45:07 -07:00
|
|
|
let executable = if magic == [0x7f, 0x45, 0x4c, 0x46] {
|
2022-12-11 23:47:09 -08:00
|
|
|
Executable::<InvokeContext>::from_elf(&contents, loader)
|
2022-12-06 06:30:06 -08:00
|
|
|
.map_err(|err| format!("Executable constructor failed: {err:?}"))
|
2021-05-28 04:28:30 -07:00
|
|
|
} else {
|
2022-12-11 23:47:09 -08:00
|
|
|
assemble::<InvokeContext>(std::str::from_utf8(contents.as_slice()).unwrap(), loader)
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
|
|
|
.unwrap();
|
|
|
|
|
2022-12-14 11:40:13 -08:00
|
|
|
#[allow(unused_mut)]
|
2022-06-07 04:45:07 -07:00
|
|
|
let mut verified_executable =
|
2022-11-15 06:21:11 -08:00
|
|
|
VerifiedExecutable::<RequisiteVerifier, InvokeContext>::from_executable(executable)
|
2022-12-06 06:30:06 -08:00
|
|
|
.map_err(|err| format!("Executable verifier failed: {err:?}"))
|
2022-10-06 11:31:58 -07:00
|
|
|
.unwrap();
|
2022-06-07 04:45:07 -07:00
|
|
|
|
2022-12-12 19:46:45 -08:00
|
|
|
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
|
2022-06-07 04:45:07 -07:00
|
|
|
verified_executable.jit_compile().unwrap();
|
|
|
|
let mut analysis = LazyAnalysis::new(verified_executable.get_executable());
|
2021-05-28 04:28:30 -07:00
|
|
|
|
|
|
|
match matches.value_of("use") {
|
|
|
|
Some("cfg") => {
|
|
|
|
let mut file = File::create("cfg.dot").unwrap();
|
2022-02-15 18:35:05 -08:00
|
|
|
analysis
|
|
|
|
.analyze()
|
|
|
|
.visualize_graphically(&mut file, None)
|
|
|
|
.unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some("disassembler") => {
|
|
|
|
let stdout = std::io::stdout();
|
2022-02-15 18:35:05 -08:00
|
|
|
analysis.analyze().disassemble(&mut stdout.lock()).unwrap();
|
2021-05-28 04:28:30 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2023-03-10 10:05:29 -08:00
|
|
|
create_vm!(
|
|
|
|
vm,
|
2022-06-07 04:45:07 -07:00
|
|
|
&verified_executable,
|
2023-03-10 10:05:29 -08:00
|
|
|
stack,
|
|
|
|
heap,
|
2022-10-06 23:45:05 -07:00
|
|
|
regions,
|
2022-05-19 15:14:28 -07:00
|
|
|
account_lengths,
|
2023-03-10 10:05:29 -08:00
|
|
|
&mut invoke_context
|
|
|
|
);
|
|
|
|
let mut vm = vm.unwrap();
|
2021-07-13 19:00:56 -07:00
|
|
|
let start_time = Instant::now();
|
2022-12-11 23:47:09 -08:00
|
|
|
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");
|
2021-07-13 19:00:56 -07:00
|
|
|
let duration = Instant::now() - start_time;
|
2022-12-15 17:08:00 -08:00
|
|
|
if matches.occurrences_of("trace") > 0 {
|
2023-03-31 12:52:49 -07:00
|
|
|
for (frame, trace) in vm
|
2023-01-28 01:03:31 -08:00
|
|
|
.env
|
|
|
|
.context_object_pointer
|
|
|
|
.trace_log_stack
|
2023-03-31 12:52:49 -07:00
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
{
|
|
|
|
let trace_log = trace.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();
|
|
|
|
}
|
2022-12-15 17:08:00 -08:00
|
|
|
}
|
|
|
|
}
|
2022-06-18 02:11:11 -07:00
|
|
|
drop(vm);
|
|
|
|
|
|
|
|
let output = Output {
|
2022-12-06 06:30:06 -08:00
|
|
|
result: format!("{result:?}"),
|
2022-06-18 02:11:11 -07:00
|
|
|
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:");
|
2022-12-06 06:30:06 -08:00
|
|
|
println!("{output:?}");
|
2022-06-18 02:11:11 -07:00
|
|
|
}
|
|
|
|
}
|
2021-05-28 04:28:30 -07:00
|
|
|
}
|
2022-02-13 09:50:22 -08:00
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
struct Output {
|
|
|
|
result: String,
|
|
|
|
instruction_count: u64,
|
|
|
|
execution_time: Duration,
|
2022-06-18 02:11:11 -07:00
|
|
|
log: Vec<String>,
|
2022-02-13 09:50:22 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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())?;
|
2022-06-18 02:11:11 -07:00
|
|
|
for line in &self.log {
|
2022-12-06 06:30:06 -08:00
|
|
|
writeln!(f, "{line}")?;
|
2022-06-18 02:11:11 -07:00
|
|
|
}
|
2022-02-13 09:50:22 -08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2022-02-15 18:35:05 -08:00
|
|
|
|
|
|
|
// Replace with std::lazy::Lazy when stabilized.
|
|
|
|
// https://github.com/rust-lang/rust/issues/74465
|
2022-11-15 06:21:11 -08:00
|
|
|
struct LazyAnalysis<'a, 'b> {
|
2022-12-11 23:47:09 -08:00
|
|
|
analysis: Option<Analysis<'a>>,
|
2022-11-15 06:21:11 -08:00
|
|
|
executable: &'a Executable<InvokeContext<'b>>,
|
2022-02-15 18:35:05 -08:00
|
|
|
}
|
|
|
|
|
2022-11-15 06:21:11 -08:00
|
|
|
impl<'a, 'b> LazyAnalysis<'a, 'b> {
|
|
|
|
fn new(executable: &'a Executable<InvokeContext<'b>>) -> Self {
|
2022-02-15 18:35:05 -08:00
|
|
|
Self {
|
|
|
|
analysis: None,
|
|
|
|
executable,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-11 23:47:09 -08:00
|
|
|
fn analyze(&mut self) -> &Analysis {
|
2022-02-15 18:35:05 -08:00
|
|
|
if let Some(ref analysis) = self.analysis {
|
|
|
|
return analysis;
|
|
|
|
}
|
|
|
|
self.analysis
|
2022-04-30 01:58:12 -07:00
|
|
|
.insert(Analysis::from_executable(self.executable).unwrap())
|
2022-02-15 18:35:05 -08:00
|
|
|
}
|
|
|
|
}
|