1use super::{CoverageItem, CoverageItemKind, SourceLocation};
2use alloy_primitives::map::{HashMap, HashSet};
3use foundry_common::TestFunctionExt;
4use foundry_compilers::ProjectCompileOutput;
5use rayon::prelude::*;
6use solar::{
7 ast::{self, ExprKind, ItemKind, StmtKind, yul},
8 data_structures::{Never, map::FxHashMap},
9 interface::{RelativeBytePos, Span},
10 sema::{Gcx, hir},
11};
12use std::{
13 ops::{ControlFlow, Range},
14 path::PathBuf,
15 sync::Arc,
16};
17
18#[derive(Clone)]
20struct SourceVisitor<'gcx> {
21 source_id: u32,
23 gcx: Gcx<'gcx>,
25
26 contract_name: Arc<str>,
28
29 branch_id: u32,
31
32 items: Vec<CoverageItem>,
34
35 all_lines: Vec<u32>,
36 ternary_branches: Vec<u32>,
38 function_calls: Vec<(Span, Arc<str>)>,
41 function_call_scopes: FxHashMap<Span, Arc<str>>,
42}
43
44struct SourceVisitorCheckpoint {
45 items: usize,
46 all_lines: usize,
47 function_calls: usize,
48 ternary_branches: usize,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub(crate) enum EmptySpecialFunctionKind {
53 Constructor,
54 Receive,
55 Fallback,
56}
57
58struct ResolvedEmptySpecialFunction {
59 contract_source_id: u32,
60 contract_name: Arc<str>,
61 function_source_id: u32,
62 function_contract_name: Arc<str>,
63 function_bytes: Range<u32>,
64 kind: EmptySpecialFunctionKind,
65}
66
67fn resolve_empty_special_functions(
68 gcx: Gcx<'_>,
69 data: &SourceFiles,
70) -> Vec<ResolvedEmptySpecialFunction> {
71 let hir_source_ids = data
72 .sources
73 .iter()
74 .map(|(&source_id, path)| {
75 let (hir_source_id, _) = gcx.get_hir_source(path).unwrap();
76 (hir_source_id, source_id)
77 })
78 .collect::<HashMap<_, _>>();
79 let mut resolved = Vec::new();
80 for contract_id in gcx.hir.contract_ids() {
81 let contract = gcx.hir.contract(contract_id);
82 let Some(&contract_source_id) = hir_source_ids.get(&contract.source) else { continue };
83 let constructors = contract.linearized_bases.iter().filter_map(|&base_id| {
84 gcx.hir
85 .contract(base_id)
86 .ctor
87 .map(|function_id| (function_id, EmptySpecialFunctionKind::Constructor))
88 });
89 let runtime_functions = [
90 (contract.receive, EmptySpecialFunctionKind::Receive),
91 (contract.fallback, EmptySpecialFunctionKind::Fallback),
92 ]
93 .into_iter()
94 .filter_map(|(function_id, kind)| function_id.map(|function_id| (function_id, kind)));
95 for (function_id, kind) in constructors.chain(runtime_functions) {
96 let function = gcx.hir.function(function_id);
97 if !function.body.is_some_and(|body| body.is_empty()) {
98 continue;
99 }
100 let Some(&function_source_id) = hir_source_ids.get(&function.source) else { continue };
101 let Some(function_contract_id) = function.contract else { continue };
102 let function_contract = gcx.hir.contract(function_contract_id);
103 let function_bytes = gcx.sess.source_map().span_to_source(function.span).unwrap().data;
104 resolved.push(ResolvedEmptySpecialFunction {
105 contract_source_id,
106 contract_name: contract.name.as_str().into(),
107 function_source_id,
108 function_contract_name: function_contract.name.as_str().into(),
109 function_bytes: function_bytes.start as u32..function_bytes.end as u32,
110 kind,
111 });
112 }
113 }
114 resolved
115}
116
117impl<'gcx> SourceVisitor<'gcx> {
118 fn new(source_id: u32, gcx: Gcx<'gcx>) -> Self {
119 Self {
120 source_id,
121 gcx,
122 contract_name: Arc::default(),
123 branch_id: 0,
124 ternary_branches: Default::default(),
125 all_lines: Default::default(),
126 function_calls: Default::default(),
127 function_call_scopes: Default::default(),
128 items: Default::default(),
129 }
130 }
131
132 const fn checkpoint(&self) -> SourceVisitorCheckpoint {
133 SourceVisitorCheckpoint {
134 items: self.items.len(),
135 all_lines: self.all_lines.len(),
136 function_calls: self.function_calls.len(),
137 ternary_branches: self.ternary_branches.len(),
138 }
139 }
140
141 fn restore_checkpoint(&mut self, checkpoint: SourceVisitorCheckpoint) {
142 let SourceVisitorCheckpoint { items, all_lines, function_calls, ternary_branches } =
143 checkpoint;
144 self.items.truncate(items);
145 self.all_lines.truncate(all_lines);
146 self.function_calls.truncate(function_calls);
147 self.ternary_branches.truncate(ternary_branches);
148 }
149
150 fn visit_contract<'ast>(&mut self, contract: &'ast ast::ItemContract<'ast>) {
151 let _ = ast::Visit::visit_item_contract(self, contract);
152 }
153
154 fn has_tests(&self, checkpoint: &SourceVisitorCheckpoint) -> bool {
156 self.items[checkpoint.items..].iter().any(|item| {
157 if let CoverageItemKind::Function { name } = &item.kind {
158 name.is_any_test()
159 } else {
160 false
161 }
162 })
163 }
164
165 fn disambiguate_functions(&mut self) {
169 let mut dups = HashMap::<_, Vec<usize>>::default();
170 for (i, item) in self.items.iter().enumerate() {
171 if let CoverageItemKind::Function { name } = &item.kind {
172 dups.entry((item.loc.contract_name.clone(), name.clone())).or_default().push(i);
173 }
174 }
175 for dups in dups.values() {
176 if dups.len() > 1 {
177 for (i, &dup) in dups.iter().enumerate() {
178 let item = &mut self.items[dup];
179 if let CoverageItemKind::Function { name } = &item.kind {
180 item.kind =
181 CoverageItemKind::Function { name: format!("{name}.{i}").into() };
182 }
183 }
184 }
185 }
186 }
187
188 fn resolve_function_calls(&mut self, hir_source_id: hir::SourceId) {
189 self.function_call_scopes = self.function_calls.iter().cloned().collect();
190 let _ = hir::Visit::visit_nested_source(self, hir_source_id);
191 }
192
193 fn sort(&mut self) {
194 self.items.sort();
195 }
196
197 fn push_lines(&mut self) {
198 self.all_lines.sort_unstable();
199 self.all_lines.dedup();
200 let mut lines = Vec::with_capacity(self.all_lines.len());
201 let mut items = self.items.iter().peekable();
203 for &line in &self.all_lines {
204 while items.peek().is_some_and(|item| item.loc.lines.start < line) {
205 items.next();
206 }
207 if let Some(mut reference_item) = items.next_if(|item| item.loc.lines.start == line) {
208 while let Some(item) = items.next_if(|item| item.loc.lines.start == line) {
211 if item.loc.bytes.start < reference_item.loc.bytes.start {
212 reference_item = item;
213 }
214 }
215 lines.push(CoverageItem {
216 kind: CoverageItemKind::Line,
217 loc: reference_item.loc.clone(),
218 anchor_loc: None,
219 hits: 0,
220 });
221 }
222 }
223 self.items.extend(lines);
224 }
225
226 fn push_stmt(&mut self, span: Span) {
227 self.push_item_kind(CoverageItemKind::Statement, span);
228 }
229
230 fn push_item_kind(&mut self, kind: CoverageItemKind, span: Span) -> &mut CoverageItem {
233 let item =
234 CoverageItem { kind, loc: self.source_location_for(span), anchor_loc: None, hits: 0 };
235
236 debug_assert!(!matches!(item.kind, CoverageItemKind::Line));
237 self.all_lines.push(item.loc.lines.start);
238
239 self.items.push(item);
240 self.items.last_mut().unwrap()
241 }
242
243 fn source_location_for(&self, span: Span) -> SourceLocation {
244 let files = self.gcx.sess.source_map().files();
245 let source = files.span_to_source(span).unwrap();
246 let mut bytes = source.data;
247 if let Some(snippet) = source.file.src.get(bytes.clone())
249 && let Some(stripped) = snippet.strip_suffix(';')
250 {
251 bytes.end -= snippet.len() - stripped.trim_end().len();
252 }
253
254 assert!(!span.is_dummy());
256 let first = source.file.lookup_line(RelativeBytePos::from_usize(bytes.start)).unwrap();
257 let last = source.file.lookup_line(RelativeBytePos::from_usize(bytes.end)).unwrap();
258 SourceLocation {
259 source_id: self.source_id as usize,
260 contract_name: self.contract_name.clone(),
261 bytes: bytes.start as u32..bytes.end as u32,
262 lines: first as u32 + 1..last as u32 + 2,
263 }
264 }
265
266 const fn next_branch_id(&mut self) -> u32 {
267 let id = self.branch_id;
268 self.branch_id = id + 1;
269 id
270 }
271}
272
273impl<'ast> ast::Visit<'ast> for SourceVisitor<'_> {
274 type BreakValue = Never;
275
276 fn visit_item_contract(
277 &mut self,
278 contract: &'ast ast::ItemContract<'ast>,
279 ) -> ControlFlow<Self::BreakValue> {
280 self.contract_name = contract.name.as_str().into();
281 self.walk_item_contract(contract)
282 }
283
284 #[expect(clippy::single_match)]
285 fn visit_item(&mut self, item: &'ast ast::Item<'ast>) -> ControlFlow<Self::BreakValue> {
286 match &item.kind {
287 ItemKind::Function(func) => {
288 if func.kind == ast::FunctionKind::Modifier && !has_statements(func.body.as_ref()) {
291 return ControlFlow::Continue(());
292 }
293
294 let name = func.header.name.as_ref().map(|n| n.as_str()).unwrap_or_else(|| {
295 match func.kind {
296 ast::FunctionKind::Constructor => "constructor",
297 ast::FunctionKind::Receive => "receive",
298 ast::FunctionKind::Fallback => "fallback",
299 ast::FunctionKind::Function | ast::FunctionKind::Modifier => unreachable!(),
300 }
301 });
302
303 let exclude_func = func.header.virtual_() && !func.is_implemented();
305 if !exclude_func {
306 self.push_item_kind(
307 CoverageItemKind::Function { name: name.into() },
308 item.span,
309 );
310 }
311
312 TernaryVisitor(self).walk_item(item)?;
314 self.walk_item(item)?;
315 }
316 _ => {}
317 }
318 ControlFlow::Continue(())
320 }
321
322 fn visit_stmt(&mut self, stmt: &'ast ast::Stmt<'ast>) -> ControlFlow<Self::BreakValue> {
323 match &stmt.kind {
324 StmtKind::Break | StmtKind::Continue | StmtKind::Emit(..) | StmtKind::Revert(..) => {
325 self.push_stmt(stmt.span);
326 return ControlFlow::Continue(());
328 }
329 StmtKind::Return(_) | StmtKind::DeclSingle(_) | StmtKind::DeclMulti(..) => {
330 self.push_stmt(stmt.span);
331 }
332
333 StmtKind::If(_cond, then_stmt, else_stmt) => {
334 let branch_id = self.next_branch_id();
335
336 if stmt_has_statements(then_stmt)
338 || else_stmt.as_ref().is_some_and(|s| stmt_has_statements(s))
339 {
340 let anchor_loc = self.source_location_for(then_stmt.span);
343 self.push_item_kind(
344 CoverageItemKind::Branch { branch_id, path_id: 0, is_first_opcode: true },
345 stmt.span,
346 )
347 .anchor_loc = Some(anchor_loc);
348 if let Some(else_stmt) = else_stmt {
349 let is_first_opcode = stmt_has_statements(else_stmt);
350 let anchor_loc =
351 is_first_opcode.then(|| self.source_location_for(else_stmt.span));
352 self.push_item_kind(
353 CoverageItemKind::Branch { branch_id, path_id: 1, is_first_opcode },
354 stmt.span,
355 )
356 .anchor_loc = anchor_loc;
357 }
358 }
359 }
360
361 StmtKind::Try(ast::StmtTry { expr: _, clauses }) => {
362 let branch_id = self.next_branch_id();
363
364 let mut path_id = 0;
365 for catch in clauses.iter() {
366 let ast::TryCatchClause { span, name: _, args, block } = catch;
367 let span = if path_id == 0 { stmt.span.to(*span) } else { *span };
368 if path_id == 0 || has_statements(Some(block)) {
369 self.push_item_kind(
370 CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: true },
371 span,
372 );
373 path_id += 1;
374 } else if !args.is_empty() {
375 self.push_stmt(span);
379 }
380 }
381 }
382
383 StmtKind::Assembly(_)
385 | StmtKind::Block(_)
386 | StmtKind::UncheckedBlock(_)
387 | StmtKind::Placeholder
388 | StmtKind::Expr(_)
389 | StmtKind::While(..)
390 | StmtKind::DoWhile(..)
391 | StmtKind::For { .. } => {}
392 }
393 self.walk_stmt(stmt)
394 }
395
396 fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
397 match &expr.kind {
398 ExprKind::Assign(..)
399 | ExprKind::Unary(..)
400 | ExprKind::Binary(..)
401 | ExprKind::Ternary(..) => {
402 self.push_stmt(expr.span);
403 if matches!(expr.kind, ExprKind::Binary(..)) {
404 return self.walk_expr(expr);
405 }
406 }
407 ExprKind::Call(callee, _args) => {
408 self.function_calls.push((expr.span, self.contract_name.clone()));
410
411 if let ExprKind::Ident(ident) = &callee.kind {
412 if ident.as_str() == "require" {
415 let branch_id = self.next_branch_id();
416 self.push_item_kind(
417 CoverageItemKind::Branch {
418 branch_id,
419 path_id: 0,
420 is_first_opcode: false,
421 },
422 expr.span,
423 );
424 self.push_item_kind(
425 CoverageItemKind::Branch {
426 branch_id,
427 path_id: 1,
428 is_first_opcode: false,
429 },
430 expr.span,
431 );
432 }
433 }
434 }
435 _ => {}
436 }
437 ControlFlow::Continue(())
439 }
440
441 fn visit_yul_stmt(&mut self, stmt: &'ast yul::Stmt<'ast>) -> ControlFlow<Self::BreakValue> {
442 use yul::StmtKind;
443 match &stmt.kind {
444 StmtKind::VarDecl(..)
445 | StmtKind::AssignSingle(..)
446 | StmtKind::AssignMulti(..)
447 | StmtKind::Leave
448 | StmtKind::Break
449 | StmtKind::Continue => {
450 self.push_stmt(stmt.span);
451 return ControlFlow::Continue(());
453 }
454 StmtKind::If(..) => {
455 let branch_id = self.next_branch_id();
456 for path_id in 0..2 {
458 self.push_item_kind(
459 CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: false },
460 stmt.span,
461 );
462 }
463 }
464 StmtKind::For(yul::StmtFor { body, .. }) => {
465 self.push_stmt(body.span);
466 }
467 StmtKind::Switch(switch) => {
468 for case in switch.cases.iter() {
469 self.push_stmt(case.span);
470 self.push_stmt(case.body.span);
471 }
472 }
473 StmtKind::FunctionDef(func) => {
474 let name = func.name.as_str();
475 self.push_item_kind(CoverageItemKind::Function { name: name.into() }, stmt.span);
476 }
477 StmtKind::Expr(_) => {
479 self.push_stmt(stmt.span);
480 return ControlFlow::Continue(());
481 }
482 StmtKind::Block(_) => {}
483 }
484 self.walk_yul_stmt(stmt)
485 }
486
487 fn visit_yul_expr(&mut self, expr: &'ast yul::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
488 use yul::ExprKind;
489 match &expr.kind {
490 ExprKind::Path(_) | ExprKind::Lit(_) => {}
491 ExprKind::Call(_) => self.push_stmt(expr.span),
492 }
493 ControlFlow::Continue(())
495 }
496}
497
498struct TernaryVisitor<'a, 'gcx>(&'a mut SourceVisitor<'gcx>);
501
502impl<'ast> ast::Visit<'ast> for TernaryVisitor<'_, '_> {
503 type BreakValue = Never;
504
505 fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow<Self::BreakValue> {
506 if matches!(expr.kind, ExprKind::Ternary(..)) {
507 let branch_id = self.0.next_branch_id();
508 self.0.ternary_branches.push(branch_id);
509 for path_id in 0..2 {
511 self.0.push_item_kind(
512 CoverageItemKind::Branch { branch_id, path_id, is_first_opcode: false },
513 expr.span,
514 );
515 }
516 }
517 self.walk_expr(expr)
518 }
519}
520
521impl<'gcx> hir::Visit<'gcx> for SourceVisitor<'gcx> {
522 type BreakValue = Never;
523
524 fn hir(&self) -> &'gcx hir::Hir<'gcx> {
525 &self.gcx.hir
526 }
527
528 fn visit_expr(&mut self, expr: &'gcx hir::Expr<'gcx>) -> ControlFlow<Self::BreakValue> {
529 if let hir::ExprKind::Call(lhs, ..) = &expr.kind
530 && is_regular_call(lhs)
531 && let Some(scope) = self.function_call_scopes.get(&expr.span).cloned()
532 {
533 let prev = std::mem::replace(&mut self.contract_name, scope);
536 self.push_stmt(expr.span);
537 self.contract_name = prev;
538 }
539 self.walk_expr(expr)
540 }
541}
542
543fn is_regular_call(lhs: &hir::Expr<'_>) -> bool {
546 match lhs.peel_parens().kind {
547 hir::ExprKind::Ident([hir::Res::Item(hir::ItemId::Struct(_))]) => false,
549 hir::ExprKind::Type(_) => false,
551 _ => true,
552 }
553}
554
555fn has_statements(block: Option<&ast::Block<'_>>) -> bool {
556 block.is_some_and(|block| !block.is_empty())
557}
558
559fn stmt_has_statements(stmt: &ast::Stmt<'_>) -> bool {
560 match &stmt.kind {
561 StmtKind::Assembly(a) => !a.block.is_empty(),
562 StmtKind::Block(b) | StmtKind::UncheckedBlock(b) => has_statements(Some(b)),
563 _ => true,
564 }
565}
566
567#[derive(Clone, Debug, Default)]
569pub struct SourceAnalysis {
570 all_items: Vec<CoverageItem>,
572 ternary_branches: HashSet<(u32, u32)>,
574 map: Vec<(u32, u32)>,
576 empty_special_functions: HashMap<u32, EmptySpecialFunctionKind>,
578 contract_empty_special_functions: HashMap<u32, HashMap<Arc<str>, Vec<u32>>>,
580}
581
582impl SourceAnalysis {
583 #[instrument(name = "SourceAnalysis::new", skip_all)]
597 pub fn new(data: &SourceFiles, output: &ProjectCompileOutput) -> eyre::Result<Self> {
598 let mut resolved_empty_special_functions = Vec::new();
599 let mut sourced_items = output.parser().solc().compiler().enter(|compiler| {
600 resolved_empty_special_functions =
601 resolve_empty_special_functions(compiler.gcx(), data);
602 data.sources
603 .par_iter()
604 .map(|(&source_id, path)| {
605 let _guard = debug_span!("SourceAnalysis::new::visit", ?path).entered();
606
607 let (_, source) = compiler.gcx().get_ast_source(path).unwrap();
608 let ast = source.ast.as_ref().unwrap();
609 let (hir_source_id, _) = compiler.gcx().get_hir_source(path).unwrap();
610
611 let mut visitor = SourceVisitor::new(source_id, compiler.gcx());
612 for item in ast.items.iter() {
613 match &item.kind {
614 ItemKind::Contract(contract) => {
616 if contract.kind.is_interface() {
618 continue;
619 }
620
621 let checkpoint = visitor.checkpoint();
622 visitor.visit_contract(contract);
623 if visitor.has_tests(&checkpoint) {
624 visitor.restore_checkpoint(checkpoint);
625 }
626 }
627 ItemKind::Function(_) => {
631 visitor.contract_name = Arc::default();
635 let _ = ast::Visit::visit_item(&mut visitor, item);
636 }
637 _ => {}
638 }
639 }
640
641 if !visitor.function_calls.is_empty() {
642 visitor.resolve_function_calls(hir_source_id);
643 }
644
645 if !visitor.items.is_empty() {
646 visitor.disambiguate_functions();
647 visitor.sort();
648 visitor.push_lines();
649 visitor.sort();
650 }
651 (source_id, visitor.items, visitor.ternary_branches)
652 })
653 .collect::<Vec<_>>()
654 });
655
656 sourced_items.sort_by_key(|(id, items, _)| (*id, items.first().map(|i| i.loc.bytes.start)));
658 let Some(&(max_idx, _, _)) = sourced_items.last() else { return Ok(Self::default()) };
659 let len = max_idx + 1;
660 let mut all_items = Vec::new();
661 let mut map = vec![(u32::MAX, 0); len as usize];
662 let mut ternary_branches = HashSet::default();
663 for (idx, items, branches) in sourced_items {
664 ternary_branches.extend(branches.into_iter().map(|branch| (idx, branch)));
665 let idx = idx as usize;
667 if map[idx].0 == u32::MAX {
668 map[idx].0 = all_items.len() as u32;
669 }
670 map[idx].1 += items.len() as u32;
671 all_items.extend(items);
672 }
673
674 let mut empty_special_functions = HashMap::default();
675 let mut contract_empty_special_functions =
676 HashMap::<u32, HashMap<Arc<str>, Vec<u32>>>::default();
677 for resolved in resolved_empty_special_functions {
678 let item_ids = all_items
679 .iter()
680 .enumerate()
681 .filter(|(_, item)| {
682 item.loc.source_id == resolved.function_source_id as usize
683 && item.loc.contract_name == resolved.function_contract_name
684 && item.loc.bytes == resolved.function_bytes
685 })
686 .map(|(item_id, _)| item_id as u32)
687 .collect::<Vec<_>>();
688 empty_special_functions
689 .extend(item_ids.iter().map(|&item_id| (item_id, resolved.kind)));
690 contract_empty_special_functions
691 .entry(resolved.contract_source_id)
692 .or_default()
693 .entry(resolved.contract_name)
694 .or_default()
695 .extend(item_ids);
696 }
697
698 Ok(Self {
699 all_items,
700 map,
701 ternary_branches,
702 empty_special_functions,
703 contract_empty_special_functions,
704 })
705 }
706
707 pub(crate) fn is_ternary_branch(&self, source_id: u32, branch_id: u32) -> bool {
708 self.ternary_branches.contains(&(source_id, branch_id))
709 }
710
711 pub fn all_items(&self) -> &[CoverageItem] {
713 &self.all_items
714 }
715
716 pub const fn all_items_mut(&mut self) -> &mut Vec<CoverageItem> {
718 &mut self.all_items
719 }
720
721 pub fn items_for_source_enumerated(
723 &self,
724 source_id: u32,
725 ) -> impl Iterator<Item = (u32, &CoverageItem)> {
726 let (base_id, items) = self.items_for_source(source_id);
727 items.iter().enumerate().map(move |(idx, item)| (base_id + idx as u32, item))
728 }
729
730 pub fn items_for_source(&self, source_id: u32) -> (u32, &[CoverageItem]) {
732 let (mut offset, len) = self.map.get(source_id as usize).copied().unwrap_or_default();
733 if offset == u32::MAX {
734 offset = 0;
735 }
736 (offset, &self.all_items[offset as usize..][..len as usize])
737 }
738
739 #[inline]
741 pub fn get(&self, item_id: u32) -> Option<&CoverageItem> {
742 self.all_items.get(item_id as usize)
743 }
744
745 pub(crate) fn empty_special_function_kind(
746 &self,
747 item_id: u32,
748 ) -> Option<EmptySpecialFunctionKind> {
749 self.empty_special_functions.get(&item_id).copied()
750 }
751
752 pub(crate) fn empty_special_function_ids(
753 &self,
754 source_id: u32,
755 contract_name: &str,
756 ) -> impl Iterator<Item = u32> + '_ {
757 self.contract_empty_special_functions
758 .get(&source_id)
759 .and_then(|contracts| contracts.get(contract_name))
760 .into_iter()
761 .flatten()
762 .copied()
763 }
764}
765
766#[derive(Default)]
768pub struct SourceFiles {
769 pub sources: HashMap<u32, PathBuf>,
771}