Skip to content

Commit

Permalink
chore: remove hc scoring reliance on Session to run more easily a…
Browse files Browse the repository at this point in the history
…nd quickly
  • Loading branch information
j-lanson authored and mchernicoff committed Nov 20, 2024
1 parent 287d595 commit 4598a39
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 49 deletions.
2 changes: 1 addition & 1 deletion hipcheck/src/analysis/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ pub fn score_results(_phase: &SpinnerPhase, db: &dyn ScoringProvider) -> Result<
// Values set with -1.0 are reseved for parent nodes whose score comes always
// from children nodes with a score set by hc_analysis algorithms

let analysis_tree = db.normalized_analysis_tree()?;
let analysis_tree = db.analysis_tree()?;
let mut plugin_results = PluginAnalysisResults::default();

// RFD4 analysis style - get all "leaf" analyses and call through plugin architecture
Expand Down
103 changes: 84 additions & 19 deletions hipcheck/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,10 @@ pub trait ConfigSource: salsa::Database {
/// Returns the directory being used to hold cache data
#[salsa::input]
fn cache_dir(&self) -> Rc<PathBuf>;
/// Returns the analysis tree as-is, i.e. without resolving policy expressions with plugins
fn unresolved_analysis_tree(&self) -> Result<Rc<AnalysisTree>>;
/// Returns a weight-normalized version of `unresolved_analysis_tree()`
fn normalized_unresolved_analysis_tree(&self) -> Result<Rc<AnalysisTree>>;
}

/// Query for accessing the risk threshold config
Expand Down Expand Up @@ -703,16 +707,37 @@ where
out_vals
}

#[salsa::query_group(WeightTreeQueryStorage)]
pub trait WeightTreeProvider: ConfigSource + HcEngine {
/// Returns the weight tree including policy expressions
fn analysis_tree(&self) -> Result<Rc<AnalysisTree>>;
/// Returns the tree of normalized weights for analyses from the config
fn normalized_analysis_tree(&self) -> Result<Rc<AnalysisTree>>;
// Generic function for visiting and performing operations on an indexmap::Arena.
// A function `acc_op` is applied to each node, and the results this function build up a
// "scope" which is a vector of `acc_op` output from the root node to the current node.
// When a leaf node is detected, `chil_op` is called, and the function receives both
// the current node and a slice-view of the scope vector. The output of calling `chil_op`
// on each leaf node is aggregated and returned.
pub fn mutate_leaves<T, F>(node: NodeId, tree: &mut Arena<T>, op: F) -> Result<()>
where
F: Fn(&mut T) -> Result<()>,
{
let mut last_start: NodeId = node;
let edges: Vec<_> = node.traverse(tree).collect();
for edge in edges {
match edge {
// Entering a new scope, update the tracker vec
NodeEdge::Start(n) => {
last_start = n;
}
NodeEdge::End(n) => {
// If we just saw Start on the same NodeId, this is a leaf
if n == last_start {
let node = tree.get_mut(n).unwrap().get_mut();
op(node)?;
}
}
}
}
Ok(())
}

fn add_analysis(
core: &dyn WeightTreeProvider,
tree: &mut AnalysisTree,
under: NodeId,
analysis: PolicyAnalysis,
Expand All @@ -724,9 +749,9 @@ fn add_analysis(
None => F64::new(1.0)?,
};
let raw_policy = match analysis.policy_expression {
Some(x) => x,
None => core.default_policy_expr(publisher.0.clone(), plugin.0.clone())?.ok_or(hc_error!("plugin {}::{} does not have a default policy, please define a policy in your policy file", publisher.0, plugin.0))?
};
Some(x) => x,
None => "".to_owned(),
};
let analysis = Analysis {
publisher: publisher.0,
plugin: plugin.0,
Expand All @@ -736,7 +761,6 @@ fn add_analysis(
}

fn add_category(
core: &dyn WeightTreeProvider,
tree: &mut AnalysisTree,
under: NodeId,
category: &PolicyCategory,
Expand All @@ -750,28 +774,61 @@ fn add_category(
for c in category.children.iter() {
match c {
PolicyCategoryChild::Analysis(analysis) => {
add_analysis(core, tree, id, analysis.clone())?;
add_analysis(tree, id, analysis.clone())?;
}
PolicyCategoryChild::Category(category) => {
add_category(core, tree, id, category)?;
add_category(tree, id, category)?;
}
}
}
Ok(id)
}

pub fn analysis_tree(db: &dyn WeightTreeProvider) -> Result<Rc<AnalysisTree>> {
let policy = db.policy();
// Lowest-level function to turn a PolicyFile into an AnalysisTree
pub fn unresolved_analysis_tree_from_policy(policy: &PolicyFile) -> Result<AnalysisTree> {
let mut tree = AnalysisTree::new("risk");
let root = tree.root;

for c in policy.analyze.categories.iter() {
add_category(db, &mut tree, root, c)?;
add_category(&mut tree, root, c)?;
}

Ok(Rc::new(tree))
Ok(tree)
}

pub fn unresolved_analysis_tree(db: &dyn ConfigSource) -> Result<Rc<AnalysisTree>> {
let policy = db.policy();
unresolved_analysis_tree_from_policy(&policy).map(Rc::new)
}

#[salsa::query_group(WeightTreeQueryStorage)]
pub trait WeightTreeProvider: ConfigSource + HcEngine {
/// Returns the normalized weight tree including resolved policy expressions
fn analysis_tree(&self) -> Result<Rc<AnalysisTree>>;
}

pub fn analysis_tree(db: &dyn WeightTreeProvider) -> Result<Rc<AnalysisTree>> {
let unresolved_tree = db.normalized_unresolved_analysis_tree()?;
let mut res_tree: AnalysisTree = (*unresolved_tree).clone();

// If the policy is empty, try to look up from plugin engine
let update_policy = |node: &mut AnalysisTreeNode| -> Result<()> {
if let AnalysisTreeNode::Analysis { analysis, .. } = node {
let a: &Analysis = &analysis.0;
if analysis.1.is_empty() {
analysis.1 = db.default_policy_expr(a.publisher.clone(), a.plugin.clone())?.ok_or(hc_error!("plugin {}::{} does not have a default policy, please define a policy in your policy file", a.publisher.clone(), a.plugin.clone()))?;
}
}
Ok(())
};

// Walk the tree, applying the above closure to each leaf (i.e. Analysis) node
mutate_leaves(res_tree.root, &mut res_tree.tree, update_policy)?;

Ok(Rc::new(res_tree))
}

// Recursive implementation of tree weight normalization
fn normalize_at_internal(node: NodeId, tree: &mut Arena<AnalysisTreeNode>) -> F64 {
let children: Vec<NodeId> = node.children(tree).collect();
let weight_sum: F64 = children
Expand All @@ -787,8 +844,16 @@ fn normalize_at_internal(node: NodeId, tree: &mut Arena<AnalysisTreeNode>) -> F6
tree.get(node).unwrap().get().get_weight()
}

pub fn normalized_analysis_tree(db: &dyn WeightTreeProvider) -> Result<Rc<AnalysisTree>> {
let tree = db.analysis_tree();
pub fn normalized_unresolved_analysis_tree_from_policy(
policy: &PolicyFile,
) -> Result<Rc<AnalysisTree>> {
let mut tree = unresolved_analysis_tree_from_policy(policy)?;
normalize_at_internal(tree.root, &mut tree.tree);
Ok(Rc::new(tree))
}

pub fn normalized_unresolved_analysis_tree(db: &dyn ConfigSource) -> Result<Rc<AnalysisTree>> {
let tree = db.unresolved_analysis_tree();
let mut norm_tree: AnalysisTree = (*tree?).clone();
normalize_at_internal(norm_tree.root, &mut norm_tree.tree);
Ok(Rc::new(norm_tree))
Expand Down
39 changes: 14 additions & 25 deletions hipcheck/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ use crate::{
analysis::score::score_results,
cache::repo::HcRepoCache,
cli::Format,
config::WeightTreeProvider,
config::{normalized_unresolved_analysis_tree_from_policy, Config},
error::{Context as _, Error, Result},
plugin::{try_set_arch, Plugin, PluginExecutor, PluginWithConfig},
policy::{config_to_policy, PolicyFile},
report::report_builder::{build_report, Report},
session::Session,
setup::{resolve_and_transform_source, SourceType},
Expand Down Expand Up @@ -61,7 +62,7 @@ use std::{
result::Result as StdResult,
time::Duration,
};
use target::{RemoteGitRepo, TargetSeed, TargetSeedKind, ToTargetSeed};
use target::{TargetSeed, ToTargetSeed};
use util::command::DependentProgram;
use util::fs::create_dir_all;
use which::which;
Expand Down Expand Up @@ -166,28 +167,19 @@ fn cmd_schema(args: &SchemaArgs) {
}

fn cmd_print_weights(config: &CliConfig) -> Result<()> {
// Silence the global shell while we're checking the dummy repo to prevent progress bars and
// title messages from displaying while calculating the weight tree.
let silence_guard = Shell::silence();

// Create a dummy session to query the salsa database for a weight graph for printing.
let session = Session::new(
// Use the hipcheck repo as a dummy url until checking is de-coupled from `Session`.
&TargetSeed {
kind: TargetSeedKind::RemoteRepo(RemoteGitRepo {
url: url::Url::parse("https://github.com/mitre/hipcheck.git").unwrap(),
known_remote: None,
}),
refspec: Some("HEAD".to_owned()),
},
config.config().map(ToOwned::to_owned),
config.cache().map(ToOwned::to_owned),
config.policy().map(ToOwned::to_owned),
config.format(),
)?;
let policy = if let Some(p) = config.policy() {
PolicyFile::load_from(p)
.context("Failed to load policy. Plase make sure the policy file is in the provided location and is formatted correctly.")?
} else if let Some(c) = config.config() {
let config = Config::load_from(c)
.context("Failed to load configuration. If you have not yet done so on this system, try running `hc setup`. Otherwise, please make sure the config files are in the config directory.")?;
config_to_policy(config)?
} else {
return Err(hc_error!("No policy file or (deprecated) config file found. Please provide a policy file before running Hipcheck."));
};

// Get the weight tree and print it.
let weight_tree = session.normalized_analysis_tree()?;
let weight_tree = normalized_unresolved_analysis_tree_from_policy(&policy)?;

// Create a special wrapper to override `Debug` so that we can use indextree's \
// debug pretty print function instead of writing our own.
Expand Down Expand Up @@ -249,9 +241,6 @@ fn cmd_print_weights(config: &CliConfig) -> Result<()> {
}
}

// Drop the silence guard to make the shell produce output again.
drop(silence_guard);

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);

Expand Down
4 changes: 3 additions & 1 deletion hipcheck/src/policy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

//! Data types and functions for parsing policy KDL files

pub mod config_to_policy;
mod config_to_policy;
pub mod policy_file;
mod tests;

pub use config_to_policy::config_to_policy;

use crate::{
error::Result,
hc_error,
Expand Down
6 changes: 3 additions & 3 deletions hipcheck/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::{
metric::{
binary_detector::BinaryFileStorage, linguist::LinguistStorage, MetricProviderStorage,
},
policy::{config_to_policy::config_to_policy, PolicyFile},
policy::{config_to_policy, PolicyFile},
report::{ReportParams, ReportParamsStorage},
session::{
cyclone_dx::extract_cyclonedx_download_url,
Expand Down Expand Up @@ -253,7 +253,7 @@ fn load_software_versions() -> Result<(String, String)> {
Ok((git_version, npm_version))
}

fn load_config_and_data(config_path: Option<&Path>) -> Result<(PolicyFile, PathBuf, String)> {
pub fn load_config_and_data(config_path: Option<&Path>) -> Result<(PolicyFile, PathBuf, String)> {
// Start the phase.
let phase = SpinnerPhase::start("Loading configuration and data files from config file. Note: The use of a config TOML file is deprecated. Please consider using a policy KDL file in the future.");
// Increment the phase into the "running" stage.
Expand All @@ -280,7 +280,7 @@ fn load_config_and_data(config_path: Option<&Path>) -> Result<(PolicyFile, PathB
Ok((policy, valid_config_path.to_path_buf(), hc_github_token))
}

fn load_policy_and_data(policy_path: Option<&Path>) -> Result<(PolicyFile, PathBuf, String)> {
pub fn load_policy_and_data(policy_path: Option<&Path>) -> Result<(PolicyFile, PathBuf, String)> {
// Start the phase.
let phase = SpinnerPhase::start("loading policy and data files");
// Increment the phase into the "running" stage.
Expand Down
1 change: 1 addition & 0 deletions hipcheck/src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ impl Shell {
///
/// This is equivalent to using [Shell::set_verbosity] to silence the global shell,
/// and then reseting it back to the previous value whenever the returned [SilenceGuard] is [drop]ped.
#[allow(unused)]
pub fn silence() -> SilenceGuard {
let previous_verbosity = Shell::get_verbosity();
Shell::set_verbosity(Verbosity::Silent);
Expand Down

0 comments on commit 4598a39

Please sign in to comment.