Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ae758b5
Add EnclosingFunction field to Violation
xaldama Apr 20, 2026
371567b
Detect enclosing function for Java
xaldama Apr 20, 2026
fc3e8c9
Export enclosing function in SARIF logicalLocations
xaldama Apr 20, 2026
1403ef5
Add integration tests for method name detection
xaldama Apr 20, 2026
5ae9806
Improve doc comment for fully_qualified_name
xaldama Apr 20, 2026
d8cee53
Precompute Java file context once per file across violations
xaldama Apr 20, 2026
1a6d998
Fix formatting
xaldama Apr 20, 2026
dd3aea2
Scope integration test to Java only
xaldama Apr 20, 2026
90c1484
List all languages explicitly in enclosing-function dispatch
xaldama Apr 21, 2026
3249aa6
Use FQMN format for enclosing function (params, no return type)
xaldama Apr 21, 2026
ed06c28
Remove LanguageFileContext optimization (negligible perf gain)
xaldama Apr 21, 2026
6bb14e8
Pass violation start and end range to find_enclosing_function
xaldama Apr 21, 2026
9fe0102
cargo fmt
xaldama Apr 21, 2026
02c9435
Delete the logic to get the FQMN
xaldama Apr 27, 2026
3423c46
Delete unused code
xaldama Apr 27, 2026
75581b6
Fix format
xaldama Apr 27, 2026
a0c6673
Make integration test more deterministic
xaldama Apr 28, 2026
6a1009d
Add a new test for lambdas
xaldama Apr 28, 2026
c004e32
Use the value instead of a pointer
xaldama Apr 28, 2026
9eb18b7
Merge branch 'main' into xabier.aldama/detect_method_java
xaldama Apr 29, 2026
8a71d66
Merge branch 'main' into xabier.aldama/detect_method_java
xaldama May 4, 2026
69c3f02
Merge branch 'main' into xabier.aldama/detect_method_java
xaldama May 27, 2026
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
1 change: 1 addition & 0 deletions crates/cli/src/csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
}],
errors: vec![],
execution_error: None,
Expand Down
3 changes: 3 additions & 0 deletions crates/cli/src/file_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
};
let directory_string = d.into_os_string().into_string().unwrap();
let fingerprint = get_fingerprint_for_violation(
Expand Down Expand Up @@ -497,6 +498,7 @@ mod tests {
fixes: vec![],
taint_flow: Some(vec![region0, region1]),
is_suppressed: false,
enclosing_function: None,
};
let fingerprint = get_fingerprint_for_violation(
"taint_flow_rule".to_string(),
Expand Down Expand Up @@ -528,6 +530,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
};
let directory_string = d.into_os_string().into_string().unwrap();

Expand Down
6 changes: 6 additions & 0 deletions crates/cli/src/rule_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pub fn convert_secret_result_to_rule_result(secret_result: &SecretResult) -> Rul
fixes: vec![],
taint_flow: None,
is_suppressed: v.is_suppressed,
enclosing_function: None,
})
.collect(),
}
Expand Down Expand Up @@ -163,6 +164,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
},
Violation {
start: Position { line: 10, col: 12 },
Expand All @@ -173,6 +175,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
},
Violation {
start: Position { line: 10, col: 12 },
Expand All @@ -183,6 +186,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
},
],
errors: vec![],
Expand Down Expand Up @@ -227,6 +231,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
},
Violation {
start: Position { line: 20, col: 1 },
Expand All @@ -237,6 +242,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: true,
enclosing_function: None,
},
],
errors: vec![],
Expand Down
153 changes: 134 additions & 19 deletions crates/cli/src/sarif/sarif_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ use secrets::model::secret_result::{SecretResult, SecretValidationStatus, Valida
use secrets::model::secret_rule::SecretRule;
use serde_sarif::sarif::{
self, Artifact, ArtifactBuilder, ArtifactChangeBuilder, ArtifactLocationBuilder, FixBuilder,
LocationBuilder, MessageBuilder, PhysicalLocationBuilder, PropertyBagBuilder, RegionBuilder,
Replacement, ReportingDescriptor, Result as SarifResult, ResultBuilder, RunBuilder, Sarif,
SarifBuilder, SuppressionBuilder, Tool, ToolBuilder, ToolComponent, ToolComponentBuilder,
LocationBuilder, LogicalLocationBuilder, MessageBuilder, PhysicalLocationBuilder,
PropertyBagBuilder, RegionBuilder, Replacement, ReportingDescriptor, Result as SarifResult,
ResultBuilder, RunBuilder, Sarif, SarifBuilder, SuppressionBuilder, Tool, ToolBuilder,
ToolComponent, ToolComponentBuilder,
};

use crate::file_utils::get_fingerprint_for_violation;
Expand Down Expand Up @@ -196,6 +197,7 @@ impl SarifRuleResult {
fixes: vec![],
taint_flow: None,
is_suppressed: r.is_suppressed,
enclosing_function: None,
},
r.validation_status.clone(),
)
Expand Down Expand Up @@ -638,21 +640,27 @@ fn generate_results(
.map(move |sarif_violation| {
let violation = sarif_violation.get_violation();
// if we find the rule for this violation, get the id, level and category
let location = LocationBuilder::default()
.physical_location(
PhysicalLocationBuilder::default()
.artifact_location(artifact_loc.clone())
.region(
RegionBuilder::default()
.start_line(violation.start.line)
.start_column(violation.start.col)
.end_line(violation.end.line)
.end_column(violation.end.col)
.build()?,
)
.build()?,
)
.build()?;
let mut location_builder = LocationBuilder::default();
location_builder.physical_location(
PhysicalLocationBuilder::default()
.artifact_location(artifact_loc.clone())
.region(
RegionBuilder::default()
.start_line(violation.start.line)
.start_column(violation.start.col)
.end_line(violation.end.line)
.end_column(violation.end.col)
.build()?,
)
.build()?,
);
if let Some(ref ef) = violation.enclosing_function {
location_builder.logical_locations(vec![LogicalLocationBuilder::default()
.name(ef.name.clone())
.kind("function".to_string())
.build()?]);
}
let location = location_builder.build()?;

let fixes: Vec<sarif::Fix> = violation
.fixes
Expand Down Expand Up @@ -965,7 +973,7 @@ mod tests {
use super::*;
use assert_json_diff::{assert_json_eq, assert_json_include};
use common::model::position::{Position, PositionBuilder, Region};
use kernel::model::violation::{Fix, Violation};
use kernel::model::violation::{EnclosingFunction, Fix, Violation};
use kernel::model::{
common::Language,
rule::{RuleBuilder, RuleCategory, RuleResultBuilder, RuleSeverity, RuleType},
Expand Down Expand Up @@ -996,6 +1004,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
}));

// good location in the violation location and no fixes
Expand All @@ -1008,6 +1017,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
}));

// bad location in the fixes location
Expand All @@ -1028,6 +1038,7 @@ mod tests {
}],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
}));

// good location everywhere
Expand All @@ -1048,6 +1059,7 @@ mod tests {
}],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
}));
}

Expand Down Expand Up @@ -1116,6 +1128,7 @@ mod tests {
fixes: vec![],
taint_flow: Some(vec![region0, region1, region2]),
is_suppressed: false,
enclosing_function: None,
};

let rule_result_single_region = RuleResultBuilder::default()
Expand Down Expand Up @@ -1298,6 +1311,106 @@ mod tests {
assert!(validate_data(&sarif_report_to_string));
}

#[test]
fn test_generate_sarif_report_logical_location() {
let rule = RuleBuilder::default()
.name("my-rule".to_string())
.description_base64(None)
.language(Language::Python)
.checksum("abc".to_string())
.pattern(None)
.tree_sitter_query_base64(None)
.category(RuleCategory::BestPractices)
.code_base64("Zm9v".to_string())
.short_description_base64(None)
.entity_checked(None)
.rule_type(RuleType::TreeSitterQuery)
.severity(RuleSeverity::Error)
.cwe(None)
.arguments(vec![])
.tests(vec![])
.is_testing(false)
.documentation_url(None)
.build()
.unwrap();

let violation_with_method = Violation {
start: Position { line: 10, col: 1 },
end: Position { line: 10, col: 20 },
message: "some violation".to_string(),
severity: RuleSeverity::Error,
category: RuleCategory::BestPractices,
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: Some(EnclosingFunction {
name: "my_method".to_string(),
}),
};
let violation_without_method = Violation {
start: Position { line: 20, col: 1 },
end: Position { line: 20, col: 5 },
message: "another violation".to_string(),
severity: RuleSeverity::Error,
category: RuleCategory::BestPractices,
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
};

let rule_result = RuleResult {
rule_name: "my-rule".to_string(),
filename: "myfile.py".to_string(),
violations: vec![violation_with_method, violation_without_method],
errors: vec![],
execution_error: None,
output: None,
execution_time_ms: 0,
parsing_time_ms: 0,
query_node_time_ms: 0,
};

let sarif_report = generate_sarif_report(
&[rule.into()],
&[rule_result.try_into().unwrap()],
&"mydir".to_string(),
SarifReportMetadata {
add_git_info: false,
debug: false,
config_digest: "abc".to_string(),
diff_aware_parameters: None,
execution_time_secs: 0,
},
&Default::default(),
)
.expect("generate sarif report");

let sarif_json = serde_json::to_value(sarif_report).unwrap();

// Violation with enclosing_function: logicalLocations must carry name and kind.
let logical_locations = sarif_json
.pointer("/runs/0/results/0/locations/0/logicalLocations")
.expect("logicalLocations should be present when enclosing_function is set");
assert_json_include!(
actual: logical_locations,
expected: serde_json::json!([{
"kind": "function",
"name": "my_method"
}])
);

// Violation without enclosing_function: no logicalLocations key at all.
let no_logical_locations =
sarif_json.pointer("/runs/0/results/1/locations/0/logicalLocations");
assert!(
no_logical_locations.is_none(),
"logicalLocations should be absent when enclosing_function is None"
);

assert!(validate_data(&sarif_json));
}

// Ensure that diff-aware scanning information are correctly surfaced
#[test]
fn test_generate_sarif_diff_aware_scanning() {
Expand Down Expand Up @@ -1956,6 +2069,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
};
let rr = RuleResult {
rule_name: format!("rule-{idx}"),
Expand Down Expand Up @@ -2049,6 +2163,7 @@ mod tests {
fixes: vec![],
taint_flow: None,
is_suppressed: false,
enclosing_function: None,
};
let rule_results = [TEST_FILE_PATH, NON_TEST_FILE_PATH]
.into_iter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl Violation<Instance> {
fixes,
taint_flow,
is_suppressed: false,
enclosing_function: None,
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions crates/static-analysis-kernel/src/analysis/ddsa_lib/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,18 @@ impl JsRuntime {
let violations = js_violations
.into_iter()
.map(|v| v.into_violation(rule.severity, rule.category))
.map(|mut v| {
v.enclosing_function = analysis::languages::find_enclosing_function_with_tree(
source_text.as_ref(),
source_tree.as_ref(),
v.start.line,
v.start.col,
v.end.line,
v.end.col,
&rule.language,
);
v
})
.collect::<Vec<_>>();

let timing = ExecutionTimingCompat {
Expand Down
Loading
Loading