Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

183 lines
5.8 KiB
Go

package resolver
import (
"regexp"
"sort"
"github.com/zzet/gortex/internal/graph"
)
// observerChannelVia marks a synthesized field-backed observer-channel call
// edge.
const observerChannelVia = "observer.channel"
// maxObserverCallbacksPerChannel caps how many callbacks one channel field may
// fan a dispatcher out to. A field registered with dozens of callbacks is
// almost always a generic bus where a dispatcher→callback edge per pair is
// noise; real domain observers wire a handful.
const maxObserverCallbacksPerChannel = 40
var (
// observerRegistrarRe matches a method that registers an observer into a
// channel field: onFoo / subscribe / addListener / register / watch / …
observerRegistrarRe = regexp.MustCompile(`(?i)^(on[a-z]\w*|subscribe|addlistener|addeventlistener|register|watch|listen|addcallback)$`)
// observerDispatcherRe matches a method that fires a channel field:
// emit / trigger / notify / dispatch / fire / publish / flush.
observerDispatcherRe = regexp.MustCompile(`(?i)(emit|trigger|notify|dispatch|fire|publish|flush)`)
)
// observerChannel pairs the registrar and dispatcher methods that share one
// field-backed channel.
type observerChannel struct {
registrars []*graph.Node
dispatchers []*graph.Node
}
// ResolveObserverChannelCalls is the framework-dispatch synthesizer for
// field-backed observer channels — the dynamic-dispatch hole where a method
// stores callbacks in a field and another method invokes them. A field is a
// channel when a registrar-named method (onFoo / subscribe / register / …) and
// a dispatcher-named method (emit / trigger / notify / …) both access it. For
// each such channel the pass finds the functions registered at the registrar's
// call sites — a function referenced on the same line as the registrar call —
// and synthesizes a calls edge from every dispatcher to every registered
// callback, the runtime dispatch the static call graph cannot see.
//
// High-precision, low-recall by design: named registrar/dispatcher methods
// paired by a shared field, callbacks taken from the registrar's call sites,
// and the fan-out capped. Full recompute and idempotent: edges are re-derived
// from the field-access and call metadata, graph.AddEdge dedupes by key, and
// graph.EvictFile drops the edge when either endpoint's file is reindexed.
// Edges ride at ast_inferred and carry synthesizer provenance.
//
// Returns the number of observer-channel call edges synthesized.
func ResolveObserverChannelCalls(g graph.Store) int {
if g == nil {
return 0
}
channels := map[string]*observerChannel{}
for e := range g.EdgesByKind(graph.EdgeAccessesField) {
if e == nil || e.To == "" || e.From == "" {
continue
}
m := g.GetNode(e.From)
if m == nil || (m.Kind != graph.KindMethod && m.Kind != graph.KindFunction) {
continue
}
isReg := observerRegistrarRe.MatchString(m.Name)
isDisp := observerDispatcherRe.MatchString(m.Name)
if !isReg && !isDisp {
continue
}
c := channels[e.To]
if c == nil {
c = &observerChannel{}
channels[e.To] = c
}
if isReg {
c.registrars = append(c.registrars, m)
}
if isDisp {
c.dispatchers = append(c.dispatchers, m)
}
}
if len(channels) == 0 {
return 0
}
// Stable iteration so a re-run produces edges in the same order.
fieldIDs := make([]string, 0, len(channels))
for id := range channels {
fieldIDs = append(fieldIDs, id)
}
sort.Strings(fieldIDs)
var batch []*graph.Edge
synthesized := 0
for _, field := range fieldIDs {
c := channels[field]
if len(c.registrars) == 0 || len(c.dispatchers) == 0 {
continue
}
callbacks := observerCallbacks(g, c.registrars)
if len(callbacks) == 0 || len(callbacks) > maxObserverCallbacksPerChannel {
continue
}
for _, disp := range c.dispatchers {
for _, cb := range callbacks {
if cb == disp.ID {
continue
}
if !sameDispatchBoundary(disp, g.GetNode(cb)) {
continue
}
batch = append(batch, observerChannelEdge(disp, cb, field))
synthesized++
}
}
}
for _, e := range batch {
g.AddEdge(e)
}
return synthesized
}
// observerCallbacks collects the functions registered through a channel's
// registrar methods: a function/method referenced on the same file+line as a
// call to the registrar (the callback argument). Returns a sorted, de-duped ID
// list for deterministic edge synthesis.
func observerCallbacks(g graph.Store, registrars []*graph.Node) []string {
seen := map[string]bool{}
for _, reg := range registrars {
for _, in := range g.GetInEdges(reg.ID) {
if in == nil || in.Kind != graph.EdgeCalls || in.From == "" {
continue
}
for _, ref := range g.GetOutEdges(in.From) {
if ref == nil || ref.To == "" {
continue
}
if ref.Line != in.Line || ref.FilePath != in.FilePath {
continue
}
if ref.Kind != graph.EdgeReads && ref.Kind != graph.EdgeReferences {
continue
}
t := g.GetNode(ref.To)
if t == nil || (t.Kind != graph.KindFunction && t.Kind != graph.KindMethod) {
continue
}
seen[ref.To] = true
}
}
}
out := make([]string, 0, len(seen))
for id := range seen {
out = append(out, id)
}
sort.Strings(out)
return out
}
// observerChannelEdge builds one dispatcher→callback synthesized call edge.
func observerChannelEdge(from *graph.Node, toID, field string) *graph.Edge {
return &graph.Edge{
From: from.ID,
To: toID,
Kind: graph.EdgeCalls,
FilePath: from.FilePath,
Line: from.StartLine,
Confidence: 0.5,
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeCalls, 0.5),
Origin: graph.OriginASTInferred,
Meta: map[string]any{
"via": observerChannelVia,
"channel_field": field,
MetaSynthesizedBy: SynthObserverChannel,
MetaProvenance: ProvenanceHeuristic,
},
}
}