mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-18 12:48:07 +00:00
handle libfuzzer targets failing without dropping crash files (#108)
This commit is contained in:
@ -14,6 +14,7 @@ anyhow = "1.0"
|
|||||||
appinsights = "0.1"
|
appinsights = "0.1"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
|
tempfile = "3.1"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
@ -9,6 +9,7 @@ pub fn run(args: &clap::ArgMatches) -> Result<()> {
|
|||||||
("generic-crash-report", Some(sub)) => crate::debug::generic_crash_report::run(sub)?,
|
("generic-crash-report", Some(sub)) => crate::debug::generic_crash_report::run(sub)?,
|
||||||
("libfuzzer-coverage", Some(sub)) => crate::debug::libfuzzer_coverage::run(sub)?,
|
("libfuzzer-coverage", Some(sub)) => crate::debug::libfuzzer_coverage::run(sub)?,
|
||||||
("libfuzzer-crash-report", Some(sub)) => crate::debug::libfuzzer_crash_report::run(sub)?,
|
("libfuzzer-crash-report", Some(sub)) => crate::debug::libfuzzer_crash_report::run(sub)?,
|
||||||
|
("libfuzzer-fuzz", Some(sub)) => crate::debug::libfuzzer_fuzz::run(sub)?,
|
||||||
_ => println!("missing subcommand\nUSAGE : {}", args.usage()),
|
_ => println!("missing subcommand\nUSAGE : {}", args.usage()),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,4 +22,5 @@ pub fn args() -> App<'static, 'static> {
|
|||||||
.subcommand(crate::debug::generic_crash_report::args())
|
.subcommand(crate::debug::generic_crash_report::args())
|
||||||
.subcommand(crate::debug::libfuzzer_coverage::args())
|
.subcommand(crate::debug::libfuzzer_coverage::args())
|
||||||
.subcommand(crate::debug::libfuzzer_crash_report::args())
|
.subcommand(crate::debug::libfuzzer_crash_report::args())
|
||||||
|
.subcommand(crate::debug::libfuzzer_fuzz::args())
|
||||||
}
|
}
|
||||||
|
103
src/agent/onefuzz-agent/src/debug/libfuzzer_fuzz.rs
Normal file
103
src/agent/onefuzz-agent/src/debug/libfuzzer_fuzz.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
use crate::tasks::{
|
||||||
|
config::{CommonConfig, SyncedDir},
|
||||||
|
fuzz::libfuzzer_fuzz::{Config, LibFuzzerFuzzTask},
|
||||||
|
utils::parse_key_value,
|
||||||
|
};
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::{App, Arg, SubCommand};
|
||||||
|
use onefuzz::blob::BlobContainerUrl;
|
||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
use url::Url;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
async fn run_impl(config: Config) -> Result<()> {
|
||||||
|
let fuzzer = LibFuzzerFuzzTask::new(config)?;
|
||||||
|
let result = fuzzer.start_fuzzer_monitor(0, None).await?;
|
||||||
|
println!("{:#?}", result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(args: &clap::ArgMatches) -> Result<()> {
|
||||||
|
let crashes_dir = value_t!(args, "crashes_dir", String)?;
|
||||||
|
let inputs_dir = value_t!(args, "inputs_dir", String)?;
|
||||||
|
let target_exe = value_t!(args, "target_exe", PathBuf)?;
|
||||||
|
let target_options = args.values_of_lossy("target_options").unwrap_or_default();
|
||||||
|
let mut target_env = HashMap::new();
|
||||||
|
for opt in args.values_of_lossy("target_env").unwrap_or_default() {
|
||||||
|
let (k, v) = parse_key_value(opt)?;
|
||||||
|
target_env.insert(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
let readonly_inputs = None;
|
||||||
|
let target_workers = Some(1);
|
||||||
|
|
||||||
|
let inputs = SyncedDir {
|
||||||
|
path: inputs_dir.into(),
|
||||||
|
url: BlobContainerUrl::new(Url::parse("https://contoso.com/inputs")?)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let crashes = SyncedDir {
|
||||||
|
path: crashes_dir.into(),
|
||||||
|
url: BlobContainerUrl::new(Url::parse("https://contoso.com/crashes")?)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
inputs,
|
||||||
|
readonly_inputs,
|
||||||
|
crashes,
|
||||||
|
target_exe,
|
||||||
|
target_env,
|
||||||
|
target_options,
|
||||||
|
target_workers,
|
||||||
|
common: CommonConfig {
|
||||||
|
heartbeat_queue: None,
|
||||||
|
instrumentation_key: None,
|
||||||
|
telemetry_key: None,
|
||||||
|
job_id: Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
|
||||||
|
task_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rt = Runtime::new()?;
|
||||||
|
rt.block_on(async { run_impl(config).await })?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn args() -> App<'static, 'static> {
|
||||||
|
SubCommand::with_name("libfuzzer-fuzz")
|
||||||
|
.about("execute a local-only libfuzzer crash report task")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("target_exe")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("target_env")
|
||||||
|
.long("target_env")
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("target_options")
|
||||||
|
.long("target_options")
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true)
|
||||||
|
.allow_hyphen_values(true)
|
||||||
|
.help("Supports hyphens. Recommendation: Set target_env first"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("inputs_dir")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("crashes_dir")
|
||||||
|
.takes_value(true)
|
||||||
|
.required(true),
|
||||||
|
)
|
||||||
|
}
|
@ -5,3 +5,4 @@ pub mod cmd;
|
|||||||
pub mod generic_crash_report;
|
pub mod generic_crash_report;
|
||||||
pub mod libfuzzer_coverage;
|
pub mod libfuzzer_coverage;
|
||||||
pub mod libfuzzer_crash_report;
|
pub mod libfuzzer_crash_report;
|
||||||
|
pub mod libfuzzer_fuzz;
|
||||||
|
@ -9,8 +9,10 @@ use crate::tasks::{
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::{future::try_join_all, stream::StreamExt};
|
use futures::{future::try_join_all, stream::StreamExt};
|
||||||
use onefuzz::{
|
use onefuzz::{
|
||||||
|
fs::list_files,
|
||||||
libfuzzer::{LibFuzzer, LibFuzzerLine},
|
libfuzzer::{LibFuzzer, LibFuzzerLine},
|
||||||
monitor::DirectoryMonitor,
|
monitor::DirectoryMonitor,
|
||||||
|
process::ExitStatus,
|
||||||
system,
|
system,
|
||||||
telemetry::{
|
telemetry::{
|
||||||
Event::{new_coverage, new_result, process_stats, runtime_stats},
|
Event::{new_coverage, new_result, process_stats, runtime_stats},
|
||||||
@ -19,9 +21,11 @@ use onefuzz::{
|
|||||||
uploader::BlobUploader,
|
uploader::BlobUploader,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, path::PathBuf, process::ExitStatus};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
use tempfile::tempdir;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io,
|
fs::rename,
|
||||||
|
io::{AsyncBufReadExt, BufReader},
|
||||||
sync::mpsc,
|
sync::mpsc,
|
||||||
task,
|
task,
|
||||||
time::{self, Duration},
|
time::{self, Duration},
|
||||||
@ -82,7 +86,7 @@ impl LibFuzzerFuzzTask {
|
|||||||
let report_stats = report_runtime_stats(workers as usize, stats_receiver, hb_client);
|
let report_stats = report_runtime_stats(workers as usize, stats_receiver, hb_client);
|
||||||
|
|
||||||
let fuzzers: Vec<_> = (0..workers)
|
let fuzzers: Vec<_> = (0..workers)
|
||||||
.map(|id| self.start_fuzzer_monitor(id, stats_sender.clone()))
|
.map(|id| self.start_fuzzer_monitor(id, Some(&stats_sender)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let fuzzers = try_join_all(fuzzers);
|
let fuzzers = try_join_all(fuzzers);
|
||||||
@ -96,22 +100,21 @@ impl LibFuzzerFuzzTask {
|
|||||||
//
|
//
|
||||||
// A run is one session of continuous fuzzing, terminated by a fuzzing error
|
// A run is one session of continuous fuzzing, terminated by a fuzzing error
|
||||||
// or discovered fault. The monitor restarts the libFuzzer when it exits.
|
// or discovered fault. The monitor restarts the libFuzzer when it exits.
|
||||||
async fn start_fuzzer_monitor(&self, worker_id: u64, stats_sender: StatsSender) -> Result<()> {
|
pub async fn start_fuzzer_monitor(
|
||||||
|
&self,
|
||||||
|
worker_id: u64,
|
||||||
|
stats_sender: Option<&StatsSender>,
|
||||||
|
) -> Result<()> {
|
||||||
loop {
|
loop {
|
||||||
let run = self.run_fuzzer(worker_id, stats_sender.clone());
|
self.run_fuzzer(worker_id, stats_sender).await?;
|
||||||
|
|
||||||
if let Err(err) = run.await {
|
|
||||||
error!("Fuzzer run failed: {}", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fuzz with a libFuzzer until it exits.
|
// Fuzz with a libFuzzer until it exits.
|
||||||
//
|
//
|
||||||
// While it runs, parse stderr for progress metrics, and report them.
|
// While it runs, parse stderr for progress metrics, and report them.
|
||||||
async fn run_fuzzer(&self, worker_id: u64, stats_sender: StatsSender) -> Result<ExitStatus> {
|
async fn run_fuzzer(&self, worker_id: u64, stats_sender: Option<&StatsSender>) -> Result<()> {
|
||||||
use io::AsyncBufReadExt;
|
let crash_dir = tempdir()?;
|
||||||
|
|
||||||
let run_id = Uuid::new_v4();
|
let run_id = Uuid::new_v4();
|
||||||
|
|
||||||
info!("starting fuzzer run, run_id = {}", run_id);
|
info!("starting fuzzer run, run_id = {}", run_id);
|
||||||
@ -129,8 +132,7 @@ impl LibFuzzerFuzzTask {
|
|||||||
&self.config.target_options,
|
&self.config.target_options,
|
||||||
&self.config.target_env,
|
&self.config.target_env,
|
||||||
);
|
);
|
||||||
let mut running =
|
let mut running = fuzzer.fuzz(crash_dir.path(), &self.config.inputs.path, &inputs)?;
|
||||||
fuzzer.fuzz(&self.config.crashes.path, &self.config.inputs.path, &inputs)?;
|
|
||||||
|
|
||||||
let sys_info = task::spawn(report_fuzzer_sys_info(worker_id, run_id, running.id()));
|
let sys_info = task::spawn(report_fuzzer_sys_info(worker_id, run_id, running.id()));
|
||||||
|
|
||||||
@ -139,27 +141,42 @@ impl LibFuzzerFuzzTask {
|
|||||||
.stderr
|
.stderr
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| format_err!("stderr not captured"))?;
|
.ok_or_else(|| format_err!("stderr not captured"))?;
|
||||||
let stderr = io::BufReader::new(stderr);
|
let stderr = BufReader::new(stderr);
|
||||||
|
|
||||||
stderr
|
let mut libfuzzer_output = Vec::new();
|
||||||
.lines()
|
let mut lines = stderr.lines();
|
||||||
.for_each(|line| {
|
while let Some(line) = lines.next_line().await? {
|
||||||
let stats_sender = stats_sender.clone();
|
if let Some(stats_sender) = stats_sender {
|
||||||
|
if let Err(err) = try_report_iter_update(stats_sender, worker_id, run_id, &line) {
|
||||||
async move {
|
error!("could not parse fuzzing interation update: {}", err);
|
||||||
let line = line.map_err(|e| e.into());
|
|
||||||
|
|
||||||
if let Err(err) = try_report_iter_update(stats_sender, worker_id, run_id, line)
|
|
||||||
{
|
|
||||||
error!("could not parse fuzzing iteration update: {}", err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
libfuzzer_output.push(line);
|
||||||
.await;
|
}
|
||||||
|
|
||||||
let (exit_status, _) = tokio::join!(running, sys_info);
|
let (exit_status, _) = tokio::join!(running, sys_info);
|
||||||
|
let exit_status: ExitStatus = exit_status?.into();
|
||||||
|
|
||||||
Ok(exit_status?)
|
let files = list_files(crash_dir.path()).await?;
|
||||||
|
|
||||||
|
// ignore libfuzzer exiting cleanly without crashing, which could happen via
|
||||||
|
// -runs=N
|
||||||
|
if !exit_status.success && files.is_empty() {
|
||||||
|
bail!(
|
||||||
|
"libfuzzer exited without generating crashes. status:{} stderr:{:?}",
|
||||||
|
serde_json::to_string(&exit_status)?,
|
||||||
|
libfuzzer_output.join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for file in &files {
|
||||||
|
if let Some(filename) = file.file_name() {
|
||||||
|
let dest = self.config.crashes.path.join(filename);
|
||||||
|
rename(file, dest).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_directories(&self) -> Result<()> {
|
async fn init_directories(&self) -> Result<()> {
|
||||||
@ -195,9 +212,7 @@ impl LibFuzzerFuzzTask {
|
|||||||
|
|
||||||
async fn monitor_new_corpus(&self) -> Result<()> {
|
async fn monitor_new_corpus(&self) -> Result<()> {
|
||||||
let url = self.config.inputs.url.url();
|
let url = self.config.inputs.url.url();
|
||||||
let dir = self.config.inputs.path.clone();
|
let mut monitor = DirectoryMonitor::new(&self.config.inputs.path);
|
||||||
|
|
||||||
let mut monitor = DirectoryMonitor::new(dir);
|
|
||||||
monitor.start()?;
|
monitor.start()?;
|
||||||
|
|
||||||
monitor
|
monitor
|
||||||
@ -247,15 +262,12 @@ impl LibFuzzerFuzzTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn try_report_iter_update(
|
fn try_report_iter_update(
|
||||||
stats_sender: StatsSender,
|
stats_sender: &StatsSender,
|
||||||
worker_id: u64,
|
worker_id: u64,
|
||||||
run_id: Uuid,
|
run_id: Uuid,
|
||||||
line: Result<String>,
|
line: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let line = line?;
|
if let Some(line) = LibFuzzerLine::parse(line)? {
|
||||||
let line = LibFuzzerLine::parse(line)?;
|
|
||||||
|
|
||||||
if let Some(line) = line {
|
|
||||||
stats_sender.send(RuntimeStats {
|
stats_sender.send(RuntimeStats {
|
||||||
worker_id,
|
worker_id,
|
||||||
run_id,
|
run_id,
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use downcast_rs::Downcast;
|
use downcast_rs::Downcast;
|
||||||
|
use onefuzz::process::Output;
|
||||||
use reqwest::{Client, Request, Response, StatusCode};
|
use reqwest::{Client, Request, Response, StatusCode};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::auth::AccessToken;
|
use crate::auth::AccessToken;
|
||||||
use crate::config::Registration;
|
use crate::config::Registration;
|
||||||
use crate::process::Output;
|
|
||||||
use crate::work::{TaskId, WorkSet};
|
use crate::work::{TaskId, WorkSet};
|
||||||
use crate::worker::WorkerEvent;
|
use crate::worker::WorkerEvent;
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use onefuzz::blob::BlobContainerUrl;
|
use onefuzz::blob::BlobContainerUrl;
|
||||||
|
use onefuzz::process::ExitStatus;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::coordinator::*;
|
use crate::coordinator::*;
|
||||||
use crate::process::*;
|
|
||||||
use crate::work::*;
|
use crate::work::*;
|
||||||
use crate::worker::*;
|
use crate::worker::*;
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ pub mod config;
|
|||||||
pub mod coordinator;
|
pub mod coordinator;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod done;
|
pub mod done;
|
||||||
pub mod process;
|
|
||||||
pub mod reboot;
|
pub mod reboot;
|
||||||
pub mod scheduler;
|
pub mod scheduler;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use onefuzz::process::Output;
|
||||||
|
|
||||||
use crate::coordinator::NodeCommand;
|
use crate::coordinator::NodeCommand;
|
||||||
use crate::process::Output;
|
|
||||||
use crate::reboot::RebootContext;
|
use crate::reboot::RebootContext;
|
||||||
use crate::setup::ISetupRunner;
|
use crate::setup::ISetupRunner;
|
||||||
use crate::work::*;
|
use crate::work::*;
|
||||||
|
@ -7,10 +7,10 @@ use std::process::Stdio;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use downcast_rs::Downcast;
|
use downcast_rs::Downcast;
|
||||||
use onefuzz::az_copy;
|
use onefuzz::az_copy;
|
||||||
|
use onefuzz::process::Output;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::process::Output;
|
|
||||||
use crate::work::*;
|
use crate::work::*;
|
||||||
|
|
||||||
const SETUP_PATH_ENV: &str = "ONEFUZZ_TARGET_SETUP_PATH";
|
const SETUP_PATH_ENV: &str = "ONEFUZZ_TARGET_SETUP_PATH";
|
||||||
|
@ -5,9 +5,9 @@ use std::process::{Child, Command, Stdio};
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use downcast_rs::Downcast;
|
use downcast_rs::Downcast;
|
||||||
|
use onefuzz::process::{ExitStatus, Output};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::process::*;
|
|
||||||
use crate::work::*;
|
use crate::work::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
@ -7,6 +7,9 @@ extern crate anyhow;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod telemetry;
|
pub mod telemetry;
|
||||||
|
|
||||||
@ -19,9 +22,9 @@ pub mod input_tester;
|
|||||||
pub mod libfuzzer;
|
pub mod libfuzzer;
|
||||||
pub mod machine_id;
|
pub mod machine_id;
|
||||||
pub mod monitor;
|
pub mod monitor;
|
||||||
|
pub mod process;
|
||||||
pub mod sha256;
|
pub mod sha256;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub mod triage;
|
pub mod triage;
|
||||||
|
|
||||||
|
@ -179,10 +179,10 @@ impl LibFuzzerLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(line: String) -> Result<Option<Self>> {
|
pub fn parse(line: &str) -> Result<Option<Self>> {
|
||||||
let re = regex::Regex::new(r"#(\d+)\s*(?:pulse|INITED|NEW|REDUCE).*exec/s: (\d+)")?;
|
let re = regex::Regex::new(r"#(\d+)\s*(?:pulse|INITED|NEW|REDUCE).*exec/s: (\d+)")?;
|
||||||
|
|
||||||
let caps = match re.captures(&line) {
|
let caps = match re.captures(line) {
|
||||||
Some(caps) => caps,
|
Some(caps) => caps,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
@ -190,7 +190,7 @@ impl LibFuzzerLine {
|
|||||||
let iters = caps[1].parse()?;
|
let iters = caps[1].parse()?;
|
||||||
let execs_sec = caps[2].parse()?;
|
let execs_sec = caps[2].parse()?;
|
||||||
|
|
||||||
Ok(Some(Self::new(line, iters, execs_sec)))
|
Ok(Some(Self::new(line.to_string(), iters, execs_sec)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iters(&self) -> u64 {
|
pub fn iters(&self) -> u64 {
|
||||||
|
Reference in New Issue
Block a user