Skip to content

Commit

Permalink
feat: Scoring subcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
vcfxb authored and alilleybrinker committed Jul 2, 2024
1 parent 9a52086 commit c69527f
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 10 deletions.
23 changes: 14 additions & 9 deletions hipcheck/src/analysis/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,20 +147,22 @@ fn normalize_st_internal(node: NodeId, tree: &mut Arena<ScoreTreeNode>) -> f64 {
}

#[derive(Debug, Clone)]
pub struct AltScoreTree {
pub struct ScoreTree {
pub tree: Arena<ScoreTreeNode>,
pub root: NodeId,
}
impl AltScoreTree {

impl ScoreTree {
pub fn new(root_label: &str) -> Self {
let mut tree = Arena::<ScoreTreeNode>::new();
let root = tree.new_node(ScoreTreeNode {
label: root_label.to_owned(),
score: -1.0,
weight: 1.0,
});
AltScoreTree { tree, root }
ScoreTree { tree, root }
}

pub fn add_child(&mut self, under: NodeId, label: &str, score: f64, weight: f64) -> NodeId {
let child = self.tree.new_node(ScoreTreeNode {
label: label.to_owned(),
Expand All @@ -170,10 +172,12 @@ impl AltScoreTree {
under.append(child, &mut self.tree);
child
}

pub fn normalize(mut self) -> Self {
let _ = normalize_st_internal(self.root, &mut self.tree);
self
}

// Given a weight tree and set of analysis results, produce an AltScoreTree by creating
// ScoreTreeNode objects for each analysis that was not skipped, and composing them into
// a tree structure matching that of the WeightTree
Expand Down Expand Up @@ -214,11 +218,12 @@ impl AltScoreTree {
};
}

Ok(AltScoreTree {
Ok(ScoreTree {
tree,
root: score_root,
})
}

// As our scope, we track the weight of each node. Once we get to a leaf node, we multiply all
// the weights (already normalized) by the score (0/1) then sum each value
pub fn score(&self) -> f64 {
Expand Down Expand Up @@ -465,7 +470,7 @@ pub fn score_results(phase: &mut Phase, db: &dyn ScoringProvider) -> Result<Scor
}
}

let alt_score_tree = AltScoreTree::synthesize(&weight_tree, &results)?;
let alt_score_tree = ScoreTree::synthesize(&weight_tree, &results)?;
score.total = alt_score_tree.score();

Ok(ScoringResults { results, score })
Expand Down Expand Up @@ -497,7 +502,7 @@ mod test {
#[test]
#[ignore = "test of tree scoring"]
fn test_graph1() {
let mut score_tree = AltScoreTree::new("risk");
let mut score_tree = ScoreTree::new("risk");
let core = score_tree.root;
let practices = score_tree.add_child(core, PRACTICES_PHASE, -1.0, 10.0);
let review = score_tree.add_child(practices, REVIEW_PHASE, 1.0, 5.0);
Expand Down Expand Up @@ -527,7 +532,7 @@ mod test {
#[test]
#[ignore = "test2 of tree scoring"]
fn test_graph2() {
let mut score_tree = AltScoreTree::new("risk");
let mut score_tree = ScoreTree::new("risk");
let core = score_tree.root;
let practices = score_tree.add_child(core, PRACTICES_PHASE, -1.0, 10.0);
let review = score_tree.add_child(practices, REVIEW_PHASE, 1.0, 4.0);
Expand All @@ -553,7 +558,7 @@ mod test {
#[test]
#[ignore = "test3 of tree scoring"]
fn test_graph3() {
let mut score_tree = AltScoreTree::new("risk");
let mut score_tree = ScoreTree::new("risk");
let core = score_tree.root;
let practices = score_tree.add_child(core, PRACTICES_PHASE, -1.0, 33.0);
let review = score_tree.add_child(practices, REVIEW_PHASE, 1.0, 10.0);
Expand All @@ -580,7 +585,7 @@ mod test {
#[test]
#[ignore = "test4 of tree scoring"]
fn test_graph4() {
let mut score_tree = AltScoreTree::new("risk");
let mut score_tree = ScoreTree::new("risk");
let core = score_tree.root;
let practices = score_tree.add_child(core, PRACTICES_PHASE, -1.0, 40.0);
let review = score_tree.add_child(practices, REVIEW_PHASE, 0.0, 12.0);
Expand Down
4 changes: 4 additions & 0 deletions hipcheck/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ pub enum FullCommands {
PrintConfig,
PrintData,
PrintCache,
Scoring,
}

impl From<&Commands> for FullCommands {
Expand All @@ -378,6 +379,7 @@ impl From<&Commands> for FullCommands {
Commands::Schema(args) => FullCommands::Schema(args.clone()),
Commands::Setup(args) => FullCommands::Setup(args.clone()),
Commands::Ready => FullCommands::Ready,
Commands::Scoring => FullCommands::Scoring,
}
}
}
Expand All @@ -400,6 +402,8 @@ pub enum Commands {
Setup(SetupArgs),
/// Check if Hipcheck is ready to run.
Ready,
/// Print the tree used to weight analyses during scoring.
Scoring,
}

// If no subcommand matched, default to use of '-t <TYPE> <TARGET' syntax. In
Expand Down
109 changes: 109 additions & 0 deletions hipcheck/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,14 @@ use cli::SchemaArgs;
use cli::SchemaCommand;
use cli::SetupArgs;
use command_util::DependentProgram;
use config::WeightTreeNode;
use config::WeightTreeProvider;
use core::fmt;
use env_logger::Builder as EnvLoggerBuilder;
use env_logger::Env;
use indextree::Arena;
use indextree::NodeId;
use ordered_float::NotNan;
use pathbuf::pathbuf;
use schemars::schema_for;
use std::env;
Expand Down Expand Up @@ -89,6 +94,15 @@ fn main() -> ExitCode {
Some(FullCommands::PrintConfig) => cmd_print_config(config.config()),
Some(FullCommands::PrintData) => cmd_print_data(config.data()),
Some(FullCommands::PrintCache) => cmd_print_home(config.cache()),
Some(FullCommands::Scoring) => {
return cmd_print_weights(&config)
.map(|_| ExitCode::SUCCESS)
.unwrap_or_else(|err| {
eprintln!("{}", err);
ExitCode::FAILURE
});
}

None => print_error(&hc_error!("missing subcommand")),
};

Expand Down Expand Up @@ -148,6 +162,101 @@ fn cmd_schema(args: &SchemaArgs) {
}
}

fn cmd_print_weights(config: &CliConfig) -> Result<()> {
// Get the raw hipcheck version.
let raw_version = env!("CARGO_PKG_VERSION", "can't find Hipcheck package version");

// Create a dummy session to query the salsa database for a weight graph for printing.
let session_result = Session::new(
// Use a sink output here since we just want to print the tree and not the shell prelude or anything else.
Shell::new(
Output::sink(),
Output::stdout(shell::ColorChoice::Auto),
Verbosity::Normal,
),
&Check {
target: "dummy".to_owned(),
kind: CheckKind::Repo,
},
// Use the hipcheck repo as a dummy url until checking is de-coupled from `Session`.
"https://github.com/mitre/hipcheck.git",
config.config().map(ToOwned::to_owned),
config.data().map(ToOwned::to_owned),
config.cache().map(ToOwned::to_owned),
config.format(),
raw_version,
);

// Unwrap the session, get the weight tree and print it.
let session = session_result.map_err(|(_, err)| err)?;
let weight_tree = session.normalized_weight_tree()?;

// Create a special wrapper to override `Debug` so that we can use indextree's \
// debug pretty print function instead of writing our own.
struct PrintNode(String);

impl std::fmt::Debug for PrintNode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(self.0.as_ref())
}
}

// Struct to help us convert the tree to PrintNodes.
// This has to be a struct not a closure because we use a recursive function to convert the tree.
struct ConvertTree(Arena<PrintNode>);

impl ConvertTree {
fn convert_tree(&mut self, old_root: NodeId, old_arena: &Arena<WeightTreeNode>) -> NodeId {
// Get a reference to the old node.
let old_node = old_arena
.get(old_root)
.expect("root is present in old arena");

// Format the new node depending on whether there are any children.
let new_node = if old_root.children(old_arena).count() == 0 {
// If no children, include the weight product.
let weight_product = old_root
.ancestors(old_arena)
.map(|ancestor| old_arena.get(ancestor).unwrap().get().weight)
.product::<NotNan<f64>>();

// Format as percentage.
PrintNode(format!(
"{}: {:.2}%",
old_node.get().label,
weight_product * 100f64
))
} else {
PrintNode(old_node.get().label.clone())
};

// Add the new node to the new arena.
let new_root = self.0.new_node(new_node);

// Recursively add all children.
for child in old_root.children(old_arena) {
// Convert the child node and its subnodes.
let new_child_id = self.convert_tree(child, old_arena);

// Attach the child node and its tree as a child of this node.
new_root.append(new_child_id, &mut self.0);
}

// Return the new root's ID.
new_root
}
}

let mut print_tree = ConvertTree(Arena::with_capacity(weight_tree.tree.capacity()));
let print_root = print_tree.convert_tree(weight_tree.root, &weight_tree.tree);

// Print the output using indextree's debug pretty printer.
let output = print_root.debug_pretty_print(&print_tree.0);
println!("{output:?}");

Ok(())
}

/// Copy individual files in dir instead of entire dir, to avoid users accidentally
/// overwriting important dirs such as /usr/bin/
fn copy_dir_contents<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
Expand Down
1 change: 0 additions & 1 deletion hipcheck/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,6 @@ impl Output {
}

/// Create a new [`Output`] that prints/writes to the void using [io::Sink].
#[allow(dead_code)]
pub fn sink() -> Output {
Output {
out: Box::new(io::sink()),
Expand Down

0 comments on commit c69527f

Please sign in to comment.