Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge conflict resolution strategy #747

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions aderyn_core/src/detect/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,15 @@ pub enum IssueSeverity {
High,
}

/// When different contexts report different number of instances for the same file, this is a merge
/// conflict and we need a resolution strategy.
pub enum MergeConflictResolutionStrategy {
/// Consider the common instances reported across all contexts
Intersection,
/// Consider all the instances reported across all contexts
Union,
}

impl Display for IssueSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let issue_description = match self {
Expand Down Expand Up @@ -494,4 +503,8 @@ pub trait IssueDetector: Send + Sync + 'static {
fn hints(&self) -> BTreeMap<(String, usize, String), String> {
BTreeMap::new()
}

fn merge_conflict_resolution_strategy(&self) -> MergeConflictResolutionStrategy {
MergeConflictResolutionStrategy::Union
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::ast::{FunctionKind, Mutability, NodeID};

use crate::capture;
use crate::context::browser::ApproximateStorageChangeFinder;
use crate::detect::detector::IssueDetectorNamePool;
use crate::detect::detector::{IssueDetectorNamePool, MergeConflictResolutionStrategy};
use crate::detect::helpers;
use crate::{
context::workspace_context::WorkspaceContext,
Expand Down Expand Up @@ -153,6 +153,10 @@ impl IssueDetector for StateVariableCouldBeImmutableDetector {
self.found_instances.clone()
}

fn merge_conflict_resolution_strategy(&self) -> MergeConflictResolutionStrategy {
MergeConflictResolutionStrategy::Intersection
}

fn name(&self) -> String {
format!(
"{}",
Expand Down
113 changes: 107 additions & 6 deletions aderyn_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,22 +122,123 @@ pub fn get_report(

let collection_of_instances = contexts
.into_par_iter()
.flat_map(|context| {
.map(|context| {
let mut d = detector.skeletal_clone();
if let Ok(found) = d.detect(context) {
if found {
let instances = d.instances();
let hints = d.hints();
return Some((instances, hints));
return (instances, hints, context.src_filepaths.clone());
}
}
None
(
Default::default(),
Default::default(),
context.src_filepaths.clone(),
)
})
.collect::<Vec<_>>();

for (instances, hints) in collection_of_instances {
detectors_instances.extend(instances);
detector_hints.extend(hints);
// Commit detector instances
//
// NOTE: Possible merge conflict here
//
// For a given detector D, in a file F,
//
// Context C1 captures instances A, B, C
// Context C2 captures instances B, C, D
//
// This is a conflict!
//
// We need a strategy to resolve this and it depends on the detector
//
// For example, if the detector determines that A, B, C are immutable when considering
// one set of files but B, C, D when considering another set of files, it is only safe
// to conclude that the B, C are immutable.
//
// Such a technique to resolve this conflict would be called INTERSECTION strategy
//
// Alternative way would be UNION strategy
//
//

match detector.merge_conflict_resolution_strategy() {
detect::detector::MergeConflictResolutionStrategy::Intersection => {
#[allow(clippy::complexity)]
let mut grouped_instances: BTreeMap<
String,
Vec<BTreeMap<(String, usize, String), i64>>,
> = Default::default();

for (instances, hints, src_filepaths) in collection_of_instances {
let mut grouped_instances_context: BTreeMap<
String,
BTreeMap<(String, usize, String), i64>,
> = BTreeMap::new();

for (key, value) in instances {
match grouped_instances_context.entry(key.0.clone()) {
Entry::Vacant(v) => {
let mut mini_btree = BTreeMap::new();
mini_btree.insert(key, value);
v.insert(mini_btree);
}
Entry::Occupied(mut o) => {
o.get_mut().insert(key, value);
}
};
}

for key in src_filepaths {
if let Entry::Vacant(v) = grouped_instances_context.entry(key) {
v.insert(Default::default());
}
}

for (key, value) in grouped_instances_context {
match grouped_instances.entry(key.clone()) {
Entry::Vacant(v) => {
v.insert(vec![value]);
}
Entry::Occupied(mut o) => {
o.get_mut().push(value);
}
}
}

detector_hints.extend(hints);
}

for (_filename, value) in grouped_instances {
// Find the common instances across all the contexts' BTrees.

let mut selected_instances = BTreeMap::new();

for instances in &value {
for instance in instances {
if value
.iter()
.all(|tree| tree.contains_key(&instance.0.clone()))
{
selected_instances.insert(instance.0.clone(), *instance.1);
}
}
}

detectors_instances.extend(selected_instances);
}
}

// Default to the old way for this strategy
detect::detector::MergeConflictResolutionStrategy::Union => {
for (instances, hints, _src_filepaths) in collection_of_instances.into_iter() {
if instances.is_empty() {
continue;
}
detectors_instances.extend(instances);
detector_hints.extend(hints);
}
}
}

if detectors_instances.is_empty() {
Expand Down
6 changes: 0 additions & 6 deletions reports/report.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 1 addition & 7 deletions reports/report.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 0 additions & 11 deletions reports/report.sarif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading