Skip to content

Commit 168a661

Browse files
authored
optimize/eliminate fs read in cloc phase (#908)
1 parent 1e54b34 commit 168a661

File tree

4 files changed

+52
-153
lines changed

4 files changed

+52
-153
lines changed

aderyn_core/src/fscloc/cloc.rs

+23
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,26 @@ mod parse_comments_to_rightfully_ignore_lines {
389389
}
390390
}
391391
}
392+
393+
#[cfg(test)]
394+
mod cloc_tests {
395+
use super::*;
396+
397+
#[test]
398+
fn test_print_cloc_heavily_commented_contract() {
399+
let content = include_str!(
400+
"../../../tests/contract-playground/src/cloc/HeavilyCommentedContract.sol"
401+
);
402+
let stats = get_stats(content, false);
403+
assert_eq!(stats.code, 21);
404+
}
405+
406+
#[test]
407+
fn test_print_cloc_another_heavily_commented_contract() {
408+
let content = include_str!(
409+
"../../../tests/contract-playground/src/cloc/AnotherHeavilyCommentedContract.sol"
410+
);
411+
let stats = get_stats(content, false);
412+
assert_eq!(stats.code, 32);
413+
}
414+
}

aderyn_core/src/fscloc/engine.rs

+23-75
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,32 @@
1-
use crate::fscloc::cloc;
2-
use ignore::{DirEntry, WalkBuilder, WalkState::Continue};
1+
use crate::context::workspace_context::WorkspaceContext;
32
use rayon::prelude::*;
43
use std::{
5-
collections::{HashMap, HashSet},
4+
collections::HashMap,
65
path::{Path, PathBuf},
7-
sync::Mutex,
6+
str::FromStr,
87
};
98

10-
use super::cloc::Stats;
9+
use super::cloc::{self, Stats};
1110

1211
pub fn count_lines_of_code_and_collect_line_numbers_to_ignore(
13-
src: &Path,
14-
src_filepaths: &[String],
12+
root: &Path,
1513
skip_cloc: bool,
16-
included: &HashSet<PathBuf>,
17-
) -> Mutex<HashMap<String, Stats>> {
18-
let (tx, rx) = crossbeam_channel::unbounded();
19-
20-
let form_path = |path: &String| {
21-
// Soldiity compiler shenanigans ??
22-
// In the line `==== ??? ====` of the output, we're supposed to see the filename
23-
// But sometimes solc puts filenames with path containing two forward slashes
24-
// Example `contracts/templegold//AuctionBase.sol` in there
25-
// Although there is a separate entry for `contracts/templegold/AuctionBase.sol`.
26-
// We want to omit reading the former
27-
if path.contains("//") {
28-
// When using foundry-compilers-aletheia, there is no separate entry so we'll
29-
// adjust the same entry
30-
let adjusted_path = path.replace("//", "/");
31-
Path::new(&src).join(adjusted_path)
32-
} else {
33-
Path::new(&src).join(path)
34-
}
35-
};
36-
37-
let src_filepaths_as_paths = src_filepaths
38-
.iter()
39-
.map(form_path)
40-
.filter(|s| included.contains(s.strip_prefix(src).unwrap()))
41-
.collect::<Vec<_>>();
42-
43-
if !src_filepaths_as_paths.is_empty() {
44-
// Only add the paths to WalkBuilder that we want to do analysis on.
45-
let mut walker = WalkBuilder::new(src_filepaths_as_paths[0].clone());
46-
47-
for item in src_filepaths_as_paths.iter().skip(1) {
48-
walker.add(item);
49-
}
50-
51-
walker.build_parallel().run(|| {
52-
let tx = tx.clone();
53-
Box::new(move |res| {
54-
if let Ok(target) = res {
55-
if target.file_type().unwrap().is_file() {
56-
let send = target.to_owned();
57-
tx.send(send).unwrap();
58-
}
59-
}
60-
Continue
61-
})
62-
});
63-
}
64-
65-
drop(tx); // without this, the program would not terminate .. because receiver would
66-
// think that the `tx` is still waiting to send something.. but we're done
67-
// the clones have been dropped but not the original
68-
// refer rust docs for more on automatic garbage collection :)
69-
70-
let lines_of_code = Mutex::new(HashMap::new());
71-
72-
let receive = |target_file: DirEntry| {
73-
// println!("Processing: {:?}", target_file.path());
74-
let r_content = std::fs::read_to_string(target_file.path()).unwrap();
75-
let stats = cloc::get_stats(&r_content, skip_cloc);
76-
let key = String::from(target_file.path().to_string_lossy());
77-
let mut lock = lines_of_code.lock().unwrap();
78-
// println!("Inserting: {} - {}", key, stats.code);
79-
lock.insert(key, stats);
80-
};
81-
82-
rx.into_iter().par_bridge().for_each(receive);
83-
lines_of_code
14+
context: &WorkspaceContext,
15+
) -> HashMap<String, Stats> {
16+
context
17+
.source_units()
18+
.par_iter()
19+
.map(|source_unit| {
20+
let path = source_unit.absolute_path.clone().expect("absolute path not inserted");
21+
let path = path.replace("//", "/"); // Condense entries that look like `contracts/templegold//AuctionBase.sol`
22+
if !context.included.contains(&PathBuf::from_str(&path).unwrap()) {
23+
return None;
24+
}
25+
let content = source_unit.source.as_ref().expect("source not filled");
26+
let stats = cloc::get_stats(content, skip_cloc);
27+
let full_path = root.join(&path).to_string_lossy().to_string();
28+
Some((full_path, stats))
29+
})
30+
.flatten()
31+
.collect()
8432
}

aderyn_core/src/fscloc/mod.rs

-63
Original file line numberDiff line numberDiff line change
@@ -10,66 +10,3 @@ pub struct CodeLines {
1010
pub actual_first_line: isize,
1111
pub last_line_has_code: bool,
1212
}
13-
14-
#[cfg(test)]
15-
mod cloc_tests {
16-
use std::{collections::HashSet, path::PathBuf, str::FromStr};
17-
18-
use crate::fscloc::engine;
19-
20-
#[test]
21-
fn test_print_loc() {
22-
let src_filepaths = vec![
23-
"HeavilyCommentedContract.sol".to_string(),
24-
"AnotherHeavilyCommentedContract.sol".to_string(),
25-
];
26-
let included = HashSet::from_iter(vec![
27-
PathBuf::from_str("HeavilyCommentedContract.sol").unwrap(),
28-
PathBuf::from_str("AnotherHeavilyCommentedContract.sol").unwrap(),
29-
]);
30-
let sol = engine::count_lines_of_code_and_collect_line_numbers_to_ignore(
31-
PathBuf::from("../tests/contract-playground/src/cloc").as_path(),
32-
&src_filepaths,
33-
false,
34-
&included,
35-
);
36-
let result = sol.lock().unwrap();
37-
result.iter().for_each(|element| println!("{} - {}", element.0, element.1.code));
38-
assert_eq!(
39-
result
40-
.get("../tests/contract-playground/src/cloc/HeavilyCommentedContract.sol")
41-
.unwrap()
42-
.code,
43-
21
44-
);
45-
assert_eq!(
46-
result
47-
.get("../tests/contract-playground/src/cloc/AnotherHeavilyCommentedContract.sol")
48-
.unwrap()
49-
.code,
50-
32
51-
);
52-
}
53-
54-
#[test]
55-
fn test_print_loc_specific_file() {
56-
let src_filepaths = vec!["HeavilyCommentedContract.sol".to_string()];
57-
let included =
58-
HashSet::from_iter(vec![PathBuf::from_str("HeavilyCommentedContract.sol").unwrap()]);
59-
let sol = engine::count_lines_of_code_and_collect_line_numbers_to_ignore(
60-
PathBuf::from("../tests/contract-playground/src/cloc").as_path(),
61-
&src_filepaths,
62-
false,
63-
&included,
64-
);
65-
let result = sol.lock().unwrap();
66-
assert_eq!(result.len(), 1);
67-
assert_eq!(
68-
result
69-
.get("../tests/contract-playground/src/cloc/HeavilyCommentedContract.sol")
70-
.unwrap()
71-
.code,
72-
21
73-
);
74-
}
75-
}

aderyn_driver/src/process.rs

+6-15
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,14 @@ pub fn make_context(
5757
let absolute_root_path = &ensure_valid_root_path(&root_path);
5858
let stats = fscloc::engine::count_lines_of_code_and_collect_line_numbers_to_ignore(
5959
absolute_root_path.as_path(),
60-
&context.src_filepaths,
6160
common.skip_cloc,
62-
&context.included,
61+
context,
6362
);
64-
let sloc_stats: HashMap<String, usize> = stats
65-
.lock()
66-
.expect("failed to lock")
67-
.iter()
68-
.map(|(key, value)| (key.to_owned(), value.code))
69-
.collect();
70-
71-
let ignore_line_stats: HashMap<String, Vec<fscloc::cloc::IgnoreLine>> = stats
72-
.lock()
73-
.expect("failed to lock")
74-
.iter()
75-
.map(|(key, value)| (key.to_owned(), value.ignore_lines.clone()))
76-
.collect();
63+
let sloc_stats: HashMap<String, usize> =
64+
stats.iter().map(|(key, value)| (key.to_owned(), value.code)).collect();
65+
66+
let ignore_line_stats: HashMap<String, Vec<fscloc::cloc::IgnoreLine>> =
67+
stats.iter().map(|(key, value)| (key.to_owned(), value.ignore_lines.clone())).collect();
7768

7869
context.set_sloc_stats(sloc_stats);
7970
context.set_ignore_lines_stats(ignore_line_stats);

0 commit comments

Comments
 (0)