[pitboss/grind] deferred session-0002 (20260516T052512Z-20f8)

This commit is contained in:
pitboss 2026-05-16 01:46:35 -05:00
parent 7a2f82c2ab
commit 282acddbbf
11 changed files with 214 additions and 45 deletions

View file

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

View file

@ -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-caponly 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,
}
}