libafl/schedulers/
testcase_score.rs1use alloc::string::{String, ToString};
3
4use libafl_bolts::{HasLen, HasRefCnt};
5use num_traits::Zero;
6
7use crate::{
8 Error, HasMetadata,
9 corpus::{Corpus, SchedulerTestcaseMetadata, Testcase},
10 feedbacks::MapIndexesMetadata,
11 schedulers::{
12 minimizer::{IsFavoredMetadata, TopRatedsMetadata},
13 powersched::{BaseSchedule, SchedulerMetadata},
14 },
15 state::HasCorpus,
16};
17
18pub trait TestcaseScore<I, S> {
20 fn compute(state: &S, entry: &mut Testcase<I>) -> Result<f64, Error>;
22}
23
24#[derive(Debug, Clone)]
27pub struct LenTimeMulTestcaseScore {}
28
29impl<I, S> TestcaseScore<I, S> for LenTimeMulTestcaseScore
30where
31 S: HasCorpus<I>,
32 I: HasLen,
33{
34 #[expect(clippy::cast_precision_loss)]
35 fn compute(state: &S, entry: &mut Testcase<I>) -> Result<f64, Error> {
36 Ok(entry.exec_time().map_or(1, |d| d.as_millis()) as f64
38 * entry.load_len(state.corpus())? as f64)
39 }
40}
41
42const POWER_BETA: f64 = 1.0;
44const MAX_FACTOR: f64 = POWER_BETA * 32.0;
45const HAVOC_MAX_MULT: f64 = 64.0;
46
47#[derive(Debug, Clone)]
50pub struct CorpusPowerTestcaseScore {}
51
52impl<I, S> TestcaseScore<I, S> for CorpusPowerTestcaseScore
53where
54 S: HasCorpus<I> + HasMetadata,
55{
56 #[expect(clippy::cast_precision_loss, clippy::too_many_lines)]
58 fn compute(state: &S, entry: &mut Testcase<I>) -> Result<f64, Error> {
59 let psmeta = state.metadata::<SchedulerMetadata>()?;
60
61 let fuzz_mu = if let Some(strat) = psmeta.strat() {
62 if *strat.base() == BaseSchedule::COE {
63 let corpus = state.corpus();
64 let mut n_paths = 0;
65 let mut v = 0.0;
66 let cur_index = state.corpus().current().unwrap();
67 for id in corpus.ids() {
68 let n_fuzz_entry = if cur_index == id {
69 entry
70 .metadata::<SchedulerTestcaseMetadata>()?
71 .n_fuzz_entry()
72 } else {
73 corpus
74 .get(id)?
75 .borrow()
76 .metadata::<SchedulerTestcaseMetadata>()?
77 .n_fuzz_entry()
78 };
79 v += libm::log2(f64::from(psmeta.n_fuzz()[n_fuzz_entry]));
80 n_paths += 1;
81 }
82
83 if n_paths == 0 {
84 return Err(Error::unknown(String::from("Queue state corrput")));
85 }
86
87 v /= f64::from(n_paths);
88 v
89 } else {
90 0.0
91 }
92 } else {
93 0.0
94 };
95
96 let mut perf_score = 100.0;
97 let q_exec_us = entry
98 .exec_time()
99 .ok_or_else(|| Error::key_not_found("exec_time not set".to_string()))?
100 .as_nanos() as f64;
101
102 let avg_exec_us = psmeta.exec_time().as_nanos() as f64 / psmeta.cycles() as f64;
103 let avg_bitmap_size = if psmeta.bitmap_entries() == 0 {
104 1
105 } else {
106 psmeta.bitmap_size() / psmeta.bitmap_entries()
107 };
108
109 let favored = entry.has_metadata::<IsFavoredMetadata>();
110 let tcmeta = entry.metadata::<SchedulerTestcaseMetadata>()?;
111
112 if q_exec_us * 0.1 > avg_exec_us {
113 perf_score = 10.0;
114 } else if q_exec_us * 0.2 > avg_exec_us {
115 perf_score = 25.0;
116 } else if q_exec_us * 0.5 > avg_exec_us {
117 perf_score = 50.0;
118 } else if q_exec_us * 0.75 > avg_exec_us {
119 perf_score = 75.0;
120 } else if q_exec_us * 4.0 < avg_exec_us {
121 perf_score = 300.0;
122 } else if q_exec_us * 3.0 < avg_exec_us {
123 perf_score = 200.0;
124 } else if q_exec_us * 2.0 < avg_exec_us {
125 perf_score = 150.0;
126 }
127
128 let q_bitmap_size = tcmeta.bitmap_size() as f64;
129 if q_bitmap_size * 0.3 > avg_bitmap_size as f64 {
130 perf_score *= 3.0;
131 } else if q_bitmap_size * 0.5 > avg_bitmap_size as f64 {
132 perf_score *= 2.0;
133 } else if q_bitmap_size * 0.75 > avg_bitmap_size as f64 {
134 perf_score *= 1.5;
135 } else if q_bitmap_size * 3.0 < avg_bitmap_size as f64 {
136 perf_score *= 0.25;
137 } else if q_bitmap_size * 2.0 < avg_bitmap_size as f64 {
138 perf_score *= 0.5;
139 } else if q_bitmap_size * 1.5 < avg_bitmap_size as f64 {
140 perf_score *= 0.75;
141 }
142
143 if tcmeta.handicap() >= 4 {
144 perf_score *= 4.0;
145 } else if tcmeta.handicap() > 0 {
147 perf_score *= 2.0;
148 }
150
151 if tcmeta.depth() >= 4 && tcmeta.depth() < 8 {
152 perf_score *= 2.0;
153 } else if tcmeta.depth() >= 8 && tcmeta.depth() < 14 {
154 perf_score *= 3.0;
155 } else if tcmeta.depth() >= 14 && tcmeta.depth() < 25 {
156 perf_score *= 4.0;
157 } else if tcmeta.depth() >= 25 {
158 perf_score *= 5.0;
159 }
160
161 let mut factor: f64 = 1.0;
162
163 if let Some(strat) = psmeta.strat() {
166 match strat.base() {
167 BaseSchedule::EXPLORE => {
168 }
170 BaseSchedule::EXPLOIT => {
171 factor = MAX_FACTOR;
172 }
173 BaseSchedule::COE => {
174 if libm::log2(f64::from(psmeta.n_fuzz()[tcmeta.n_fuzz_entry()])) > fuzz_mu
175 && !favored
176 {
177 factor = 0.0;
179 }
180 }
181 BaseSchedule::FAST => {
182 if entry.scheduled_count() != 0 {
183 let lg = libm::log2(f64::from(psmeta.n_fuzz()[tcmeta.n_fuzz_entry()]));
184
185 match lg {
186 f if f < 2.0 => {
187 factor = 4.0;
188 }
189 f if (2.0..4.0).contains(&f) => {
190 factor = 3.0;
191 }
192 f if (4.0..5.0).contains(&f) => {
193 factor = 2.0;
194 }
195 f if (6.0..7.0).contains(&f) => {
196 if !favored {
197 factor = 0.8;
198 }
199 }
200 f if (7.0..8.0).contains(&f) => {
201 if !favored {
202 factor = 0.6;
203 }
204 }
205 f if f >= 8.0 => {
206 if !favored {
207 factor = 0.4;
208 }
209 }
210 _ => {
211 factor = 1.0;
212 }
213 }
214
215 if favored {
216 factor *= 1.15;
217 }
218 }
219 }
220 BaseSchedule::LIN => {
221 factor = (entry.scheduled_count() as f64)
222 / f64::from(psmeta.n_fuzz()[tcmeta.n_fuzz_entry()] + 1);
223 }
224 BaseSchedule::QUAD => {
225 factor = ((entry.scheduled_count() * entry.scheduled_count()) as f64)
226 / f64::from(psmeta.n_fuzz()[tcmeta.n_fuzz_entry()] + 1);
227 }
228 }
229 }
230
231 if let Some(strat) = psmeta.strat() {
232 if *strat.base() != BaseSchedule::EXPLORE {
233 if factor > MAX_FACTOR {
234 factor = MAX_FACTOR;
235 }
236
237 perf_score *= factor / POWER_BETA;
238 }
239 }
240
241 if let Some(strat) = psmeta.strat() {
243 if *strat.base() == BaseSchedule::COE && perf_score < 1.0 {
244 perf_score = 1.0;
245 }
246 }
247
248 if perf_score > HAVOC_MAX_MULT * 100.0 {
250 perf_score = HAVOC_MAX_MULT * 100.0;
251 }
252
253 if entry.objectives_found() > 0 && psmeta.strat().is_some_and(|s| s.avoid_crash()) {
254 perf_score *= 0.00;
255 }
256
257 Ok(perf_score)
258 }
259}
260
261#[derive(Debug, Clone)]
264pub struct CorpusWeightTestcaseScore {}
265
266impl<I, S> TestcaseScore<I, S> for CorpusWeightTestcaseScore
267where
268 S: HasCorpus<I> + HasMetadata,
269{
270 #[expect(clippy::cast_precision_loss)]
272 fn compute(state: &S, entry: &mut Testcase<I>) -> Result<f64, Error> {
273 let mut weight = 1.0;
274 let psmeta = state.metadata::<SchedulerMetadata>()?;
275
276 let tcmeta = entry.metadata::<SchedulerTestcaseMetadata>()?;
277 if entry.scheduled_count() == 0 || psmeta.cycles() == 0 {
281 return Ok(weight);
282 }
283
284 let q_exec_us = entry
285 .exec_time()
286 .ok_or_else(|| Error::key_not_found("exec_time not set".to_string()))?
287 .as_nanos() as f64;
288 let favored = entry.has_metadata::<IsFavoredMetadata>();
289
290 let avg_exec_us = psmeta.exec_time().as_nanos() as f64 / psmeta.cycles() as f64;
291 let avg_bitmap_size = psmeta.bitmap_size_log() / psmeta.bitmap_entries() as f64;
292
293 let q_bitmap_size = tcmeta.bitmap_size() as f64;
294
295 if let Some(ps) = psmeta.strat() {
296 match ps.base() {
297 BaseSchedule::FAST | BaseSchedule::COE | BaseSchedule::LIN | BaseSchedule::QUAD => {
298 let hits = psmeta.n_fuzz()[tcmeta.n_fuzz_entry()];
299 if hits > 0 {
300 weight /= libm::log10(f64::from(hits)) + 1.0;
301 }
302 }
303 _ => (),
304 }
305 }
306
307 weight *= avg_exec_us / q_exec_us;
308 weight *= if avg_bitmap_size.is_zero() {
309 1.0
311 } else {
312 libm::log2(q_bitmap_size).max(1.0) / avg_bitmap_size
313 };
314
315 let tc_ref = match entry.metadata_map().get::<MapIndexesMetadata>() {
316 Some(meta) => meta.refcnt() as f64,
317 None => 0.0,
318 };
319
320 let avg_top_size = match state.metadata::<TopRatedsMetadata>() {
321 Ok(m) => m.map().len() as f64,
322 Err(e) => {
323 return Err(Error::key_not_found(format!(
324 "{e:?} You have to use Minimizer scheduler with this.",
325 )));
326 }
327 };
328
329 weight *= 1.0 + (tc_ref / avg_top_size);
330
331 if favored {
332 weight *= 5.0;
333 }
334
335 if entry.scheduled_count() == 0 {
337 weight *= 2.0;
338 }
339
340 if entry.objectives_found() > 0 && psmeta.strat().is_some_and(|s| s.avoid_crash()) {
341 weight *= 0.00;
342 }
343
344 assert!(weight.is_normal());
345
346 Ok(weight)
347 }
348}