Skip to content

Commit

Permalink
feat: Scoring weight tree command
Browse files Browse the repository at this point in the history
  • Loading branch information
vcfxb committed Jul 1, 2024
1 parent 9a52086 commit 9aa945c
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 23 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
5 changes: 1 addition & 4 deletions site/templates/macros/breadcrumbs.tera.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
{% macro breadcrumbs(current) %}

{% set root = config.base_url | replace(from="https://mitre.github.io/", to="") %}

<div class="text-sm text-zinc-500 px-14 dark:text-zinc-400 border-t border-zinc-100 dark:border-zinc-800 rounded-md p-2 mx-[-0.5rem]">
<a class="font-normal no-underline hover:underline" href="{{ root }}/">Home</a>
<a class="font-normal no-underline hover:underline" href="/">Home</a>

{% for ancestor in current.ancestors %}
{% if loop.first %}
Expand Down
6 changes: 2 additions & 4 deletions site/templates/partials/footer.tera.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@

{% set root = config.base_url | replace(from="https://mitre.github.io/", to="") %}

<div class="max-w-6xl mx-auto mt-32">
<div class="pb-16">
<div class="flex gap-8 dark:text-zinc-400">
<div class="basis-1/4 text-lg">
<a class="hover:text-green-600 font-semibold" href="{{ root }}/">Hipcheck <span class="leading-none font-extrabold text-green-600 text-2xl"></span></a>
<a class="hover:text-green-600 font-semibold" href="/">Hipcheck <span class="leading-none font-extrabold text-green-600 text-2xl"></span></a>
<p class="text-sm text-zinc-500 mt-3 leading-6">Automated supply chain risk assessment for software packages.</p>
</div>
{% for list in config.extra.footer %}
Expand All @@ -16,7 +14,7 @@
{% if item.title %}
<span class="font-semibold mb-4 mt-8 block">{{ item.name }}</span>
{% else %}
<a class="block mb-2 hover:text-blue-500" href="{{ root }}{{ item.url }}">
<a class="block mb-2 hover:text-blue-500" href="{{ item.url }}">
{{ item.name }}
{% if item.external %}
<span class="text-xs text-zinc-500"></span>
Expand Down
8 changes: 3 additions & 5 deletions site/templates/partials/nav.tera.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@

{% import "macros/icon.tera.html" as ic %}

{% set root = config.base_url | replace(from="https://mitre.github.io/", to="") %}

<nav class="
text-sm
py-4 px-8
Expand All @@ -12,15 +10,15 @@
mx-4
">
<li class="block md:flex md:flex-1 items-stretch">
<a href="{{ root }}/" class="
<a href="/" class="
items-stretch content-center
text-black dark:text-zinc-200 hover:text-green-600
text-xl
font-semibold
">Hipcheck&nbsp;<span class="leading-none font-extrabold text-green-600 text-2xl"></span></a>

{% if config.extra.announce %}
<a href="{{ root }}{{ config.extra.announce.url }}"
<a href="{{ config.extra.announce.url }}"
class="
rounded-3xl
bg-sky-100 dark:bg-sky-900 hover:bg-sky-200 dark:hover:bg-sky-800
Expand Down Expand Up @@ -57,7 +55,7 @@
{% else %}
p-0
{% endif %}
" href="{{ root }}{{ item.url }}" {% if item.id %}id="{{ item.id }}"{% endif %}>
" href="{{ item.url }}" {% if item.id %}id="{{ item.id }}"{% endif %}>
{% if item.icon %}
{% set name = item.icon %}
{{ ic::icon(name=name, classes="mt-[-2px] ml-[-4px] mr-1") }}
Expand Down

0 comments on commit 9aa945c

Please sign in to comment.