mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] deferred session-0002 (20260516T052512Z-20f8)
This commit is contained in:
parent
7a2f82c2ab
commit
282acddbbf
11 changed files with 214 additions and 45 deletions
|
|
@ -13,7 +13,7 @@
|
|||
//! that fires on its own.
|
||||
|
||||
use super::{DataStore, DataStoreKind, SourceLocation, SurfaceNode};
|
||||
use crate::summary::{FuncSummary, GlobalSummaries};
|
||||
use crate::summary::{CalleeSite, FuncSummary, GlobalSummaries};
|
||||
|
||||
/// One detection rule: leaf-name pattern → store kind + label. Stored
|
||||
/// as a flat list so adding a new ORM / driver is a one-line edit.
|
||||
|
|
@ -108,7 +108,7 @@ pub fn detect_data_stores(summaries: &GlobalSummaries) -> Vec<SurfaceNode> {
|
|||
let Some(rule) = match_rule(&callee.name) else {
|
||||
continue;
|
||||
};
|
||||
let location = call_site_location(summary, callee.ordinal);
|
||||
let location = call_site_location(summary, callee);
|
||||
let dedup = (
|
||||
location.file.clone(),
|
||||
location.line,
|
||||
|
|
@ -148,22 +148,23 @@ fn match_rule(callee: &str) -> Option<&'static DriverRule> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Best-effort source location for a call site. We only have file +
|
||||
/// (sometimes) sink-attribution metadata on `FuncSummary`, so the
|
||||
/// location falls back to the function's file with line 0 when no
|
||||
/// finer-grained data is available.
|
||||
fn call_site_location(summary: &FuncSummary, _ordinal: u32) -> SourceLocation {
|
||||
/// Source location of a call site. Reads the 1-based `(line, col)`
|
||||
/// recorded on the [`CalleeSite`] at CFG-build time (populated for every
|
||||
/// summary produced after the span field landed); for legacy summaries
|
||||
/// loaded from SQLite with no span, falls back to the function's host
|
||||
/// file with line 0.
|
||||
fn call_site_location(summary: &FuncSummary, callee: &CalleeSite) -> SourceLocation {
|
||||
let (line, col) = callee.span.unwrap_or((0, 0));
|
||||
SourceLocation {
|
||||
file: summary.file_path.clone(),
|
||||
line: 0,
|
||||
col: 0,
|
||||
line,
|
||||
col,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::summary::CalleeSite;
|
||||
use crate::symbol::{FuncKey, Lang};
|
||||
|
||||
fn summary_with_callees(name: &str, file: &str, callees: &[&str]) -> (FuncKey, FuncSummary) {
|
||||
|
|
@ -182,6 +183,33 @@ mod tests {
|
|||
(key, summary)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn datastore_carries_callee_span_when_present() {
|
||||
// When the CFG populates `CalleeSite.span`, the detected datastore
|
||||
// node's `SourceLocation` must reflect that 1-based `(line, col)`
|
||||
// — not the legacy `(0, 0)` fallback.
|
||||
let mut gs = GlobalSummaries::new();
|
||||
let key = FuncKey::new_function(Lang::Python, "app.py", "init", None);
|
||||
let mut callee = CalleeSite::bare("psycopg2.connect");
|
||||
callee.span = Some((42, 13));
|
||||
let summary = FuncSummary {
|
||||
name: "init".into(),
|
||||
file_path: "app.py".into(),
|
||||
lang: "python".into(),
|
||||
param_count: 0,
|
||||
callees: vec![callee],
|
||||
..Default::default()
|
||||
};
|
||||
gs.insert(key, summary);
|
||||
let nodes = detect_data_stores(&gs);
|
||||
assert_eq!(nodes.len(), 1);
|
||||
let SurfaceNode::DataStore(ds) = &nodes[0] else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!(ds.location.line, 42);
|
||||
assert_eq!(ds.location.col, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_psycopg2_connect() {
|
||||
let mut gs = GlobalSummaries::new();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
use super::{ExternalService, ExternalServiceKind, SourceLocation, SurfaceNode};
|
||||
use crate::labels::Cap;
|
||||
use crate::summary::{FuncSummary, GlobalSummaries};
|
||||
use crate::summary::{CalleeSite, FuncSummary, GlobalSummaries};
|
||||
|
||||
struct ClientRule {
|
||||
leaf: &'static str,
|
||||
|
|
@ -87,7 +87,7 @@ pub fn detect_external_services(summaries: &GlobalSummaries) -> Vec<SurfaceNode>
|
|||
let Some(rule) = match_rule(&callee.name) else {
|
||||
continue;
|
||||
};
|
||||
let location = call_site_location(summary);
|
||||
let location = call_site_location(summary, Some(callee));
|
||||
if !seen.insert((location.file.clone(), rule.label.to_string())) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ pub fn detect_external_services(summaries: &GlobalSummaries) -> Vec<SurfaceNode>
|
|||
// file as the location and synthesise a generic label.
|
||||
for (_key, summary) in summaries.iter() {
|
||||
if summary.sink_caps().contains(Cap::SSRF) {
|
||||
let loc = call_site_location(summary);
|
||||
let loc = call_site_location(summary, None);
|
||||
let dedup = (loc.file.clone(), "Outbound HTTP".to_string());
|
||||
if seen.insert(dedup) {
|
||||
out.push(SurfaceNode::ExternalService(ExternalService {
|
||||
|
|
@ -134,11 +134,16 @@ fn match_rule(callee: &str) -> Option<&'static ClientRule> {
|
|||
})
|
||||
}
|
||||
|
||||
fn call_site_location(summary: &FuncSummary) -> SourceLocation {
|
||||
/// Source location of an external-service call site. Reads the 1-based
|
||||
/// `(line, col)` recorded on the [`CalleeSite`] at CFG-build time when
|
||||
/// available; otherwise (sink-cap–only fallback path, or legacy summaries
|
||||
/// loaded from SQLite) returns the function's host file with line 0.
|
||||
fn call_site_location(summary: &FuncSummary, callee: Option<&CalleeSite>) -> SourceLocation {
|
||||
let (line, col) = callee.and_then(|c| c.span).unwrap_or((0, 0));
|
||||
SourceLocation {
|
||||
file: summary.file_path.clone(),
|
||||
line: 0,
|
||||
col: 0,
|
||||
line,
|
||||
col,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue