@@ -41,11 +41,13 @@ func (e *DataflowExecutor) Execute() []DataflowDetection {
4141 return e .executeGlobal ()
4242}
4343
44- // executeLocal performs intra-procedural taint analysis.
44+ // executeLocal performs intra-procedural taint analysis with 3-tier fallback:
45+ // Tier 1: CFG-aware VDG (highest confidence) — uses control flow graph + variable dependency graph
46+ // Tier 2: Flat VDG — uses variable dependency graph without CFG
47+ // Tier 3: Line-number proximity (legacy fallback) — when no statements available.
4548func (e * DataflowExecutor ) executeLocal () []DataflowDetection {
4649 detections := []DataflowDetection {}
4750
48- // Resolve matchers polymorphically.
4951 sourceCalls := e .resolveMatchers (e .IR .Sources )
5052 if len (sourceCalls ) == 0 {
5153 e .Diagnostics .Addf ("debug" , "dataflow" , "0 sources found, skipping local analysis" )
@@ -60,45 +62,123 @@ func (e *DataflowExecutor) executeLocal() []DataflowDetection {
6062
6163 sanitizerCalls := e .resolveMatchers (e .IR .Sanitizers )
6264
63- // For local scope, check if source and sink are in the same function.
65+ sourcePatterns := e .extractTargetPatterns (sourceCalls )
66+ sinkPatterns := e .extractTargetPatterns (sinkCalls )
67+ sanitizerPatterns := e .extractTargetPatterns (sanitizerCalls )
68+
69+ candidateFuncs := e .findFunctionsWithSourcesAndSinks (sourceCalls , sinkCalls )
70+
71+ for _ , funcFQN := range candidateFuncs {
72+ stmts := e .getStatementsForFunction (funcFQN )
73+ if len (stmts ) == 0 {
74+ // Tier 3: Legacy line-number proximity (no statements available)
75+ e .executeLocalLegacy (funcFQN , sourceCalls , sinkCalls , sanitizerCalls , & detections )
76+ continue
77+ }
78+
79+ // Tier 1: CFG-aware VDG
80+ analysisMethod := "flat_vdg"
81+ var summary * core.TaintSummary
82+
83+ if raw , exists := e .CallGraph .CFGs [funcFQN ]; exists {
84+ if cfGraph , ok := raw .(* cfg.ControlFlowGraph ); ok {
85+ if rawBS , bsExists := e .CallGraph .CFGBlockStatements [funcFQN ]; bsExists {
86+ if blockStmts , bsOK := rawBS .(cfg.BlockStatements ); bsOK && len (blockStmts ) > 0 {
87+ summary = taint .AnalyzeWithCFG (funcFQN , cfGraph , blockStmts ,
88+ sourcePatterns , sinkPatterns , sanitizerPatterns )
89+ analysisMethod = "cfg_vdg"
90+ }
91+ }
92+ }
93+ }
94+
95+ // Tier 2: Flat VDG (if Tier 1 found no detections)
96+ if summary == nil || ! summary .HasDetections () {
97+ summary = taint .AnalyzeWithVDG (funcFQN , stmts ,
98+ sourcePatterns , sinkPatterns , sanitizerPatterns )
99+ analysisMethod = "flat_vdg"
100+ }
101+
102+ if summary != nil {
103+ for _ , det := range summary .Detections {
104+ detections = append (detections , DataflowDetection {
105+ FunctionFQN : funcFQN ,
106+ SourceLine : int (det .SourceLine ),
107+ SinkLine : int (det .SinkLine ),
108+ TaintedVar : det .SourceVar ,
109+ SinkCall : det .SinkCall ,
110+ Confidence : e .confidenceForMethod (analysisMethod ),
111+ Sanitized : false ,
112+ Scope : "local" ,
113+ MatchMethod : analysisMethod ,
114+ })
115+ }
116+ }
117+ }
118+
119+ return detections
120+ }
121+
122+ // confidenceForMethod returns the confidence score for a given analysis method.
123+ func (e * DataflowExecutor ) confidenceForMethod (method string ) float64 {
124+ switch method {
125+ case "cfg_vdg" :
126+ return 0.95
127+ case "flat_vdg" :
128+ return 0.85
129+ case "interprocedural_vdg" :
130+ return 0.80
131+ case "line_proximity" :
132+ return 0.50
133+ default :
134+ return 0.60
135+ }
136+ }
137+
138+ // executeLocalLegacy performs line-number proximity analysis (Tier 3 fallback).
139+ // Used when no statements are available for a function.
140+ func (e * DataflowExecutor ) executeLocalLegacy (
141+ funcFQN string ,
142+ sourceCalls , sinkCalls , sanitizerCalls []CallSiteMatch ,
143+ detections * []DataflowDetection ,
144+ ) {
64145 for _ , source := range sourceCalls {
146+ if source .FunctionFQN != funcFQN {
147+ continue
148+ }
65149 for _ , sink := range sinkCalls {
66- if source .FunctionFQN != sink . FunctionFQN {
150+ if sink .FunctionFQN != funcFQN {
67151 continue
68152 }
69153
70154 hasSanitizer := false
71- for _ , sanitizer := range sanitizerCalls {
72- if sanitizer .FunctionFQN == source . FunctionFQN {
73- if (sanitizer .Line > source .Line && sanitizer .Line < sink .Line ) ||
74- (sanitizer .Line > sink .Line && sanitizer .Line < source .Line ) {
155+ for _ , san := range sanitizerCalls {
156+ if san .FunctionFQN == funcFQN {
157+ if (san .Line > source .Line && san .Line < sink .Line ) ||
158+ (san .Line > sink .Line && san .Line < source .Line ) {
75159 hasSanitizer = true
76160 break
77161 }
78162 }
79163 }
80-
81164 if hasSanitizer {
82165 continue
83166 }
84167
85- detection := DataflowDetection {
86- FunctionFQN : source . FunctionFQN ,
168+ * detections = append ( * detections , DataflowDetection {
169+ FunctionFQN : funcFQN ,
87170 SourceLine : source .Line ,
88171 SourceColumn : source .CallSite .Location .Column ,
89172 SinkLine : sink .Line ,
90173 SinkColumn : sink .CallSite .Location .Column ,
91174 SinkCall : sink .CallSite .Target ,
92- Confidence : e . Config . getLocalScopeConfidence () ,
175+ Confidence : 0.50 ,
93176 Sanitized : false ,
94177 Scope : "local" ,
95- }
96-
97- detections = append (detections , detection )
178+ MatchMethod : "line_proximity" ,
179+ })
98180 }
99181 }
100-
101- return detections
102182}
103183
104184// executeGlobal performs inter-procedural taint analysis.
0 commit comments