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
142 lines
4.1 KiB
Go
142 lines
4.1 KiB
Go
package resolver
|
||
|
||
import (
|
||
"sort"
|
||
|
||
"github.com/zzet/gortex/internal/graph"
|
||
)
|
||
|
||
// ginMiddlewareVia tags the synthesized dispatcher→handler edges.
|
||
const ginMiddlewareVia = "gin_middleware_chain"
|
||
|
||
// ginFanoutCap bounds the dispatcher×handler fan-out so a pathological repo
|
||
// (a custom chain plus hundreds of registered handlers) cannot explode the
|
||
// edge set.
|
||
const ginFanoutCap = 512
|
||
|
||
// ResolveGinMiddlewareCalls bridges a Gin middleware-chain dispatcher to the
|
||
// handlers it dispatches to. The Go extractor stamps gin_dispatcher=true on the
|
||
// method that invokes `c.handlers[idx](c)` (the indexed-slice indirection a
|
||
// call graph drops) and gin_handlers=[names] on each function that registers
|
||
// routes/middleware with `.GET`/`.Use`/`.Handle`. This pass resolves those
|
||
// names to their definitions and emits a tier-tagged dispatcher→handler call
|
||
// edge per pair, so request→handler reachability flows through get_call_chain
|
||
// where a same-file scanner sees a dead end. Gated on a dispatcher existing
|
||
// (so it is inert outside a Gin-style chain) and repo-scoped via
|
||
// sameDispatchBoundary (per the intra-process dispatch discipline).
|
||
func ResolveGinMiddlewareCalls(g graph.Store) int {
|
||
if g == nil {
|
||
return 0
|
||
}
|
||
|
||
var dispatchers []*graph.Node
|
||
var registrars []*graph.Node
|
||
nameIndex := map[string][]*graph.Node{}
|
||
for _, n := range nodesByKindsOrAll(g, graph.KindMethod, graph.KindFunction) {
|
||
if n == nil {
|
||
continue
|
||
}
|
||
nameIndex[n.Name] = append(nameIndex[n.Name], n)
|
||
if n.Meta == nil {
|
||
continue
|
||
}
|
||
if d, _ := n.Meta["gin_dispatcher"].(bool); d {
|
||
dispatchers = append(dispatchers, n)
|
||
}
|
||
if _, ok := n.Meta["gin_handlers"]; ok {
|
||
registrars = append(registrars, n)
|
||
}
|
||
}
|
||
// Gated on the dispatcher existing: no chain dispatcher in the graph means
|
||
// no Gin-style indirection to bridge.
|
||
if len(dispatchers) == 0 || len(registrars) == 0 {
|
||
return 0
|
||
}
|
||
|
||
// Collect the distinct handler names registered anywhere, deterministically.
|
||
handlerNames := map[string]bool{}
|
||
for _, r := range registrars {
|
||
for _, name := range ginHandlerNames(r.Meta["gin_handlers"]) {
|
||
handlerNames[name] = true
|
||
}
|
||
}
|
||
names := make([]string, 0, len(handlerNames))
|
||
for name := range handlerNames {
|
||
names = append(names, name)
|
||
}
|
||
sort.Strings(names)
|
||
|
||
sort.Slice(dispatchers, func(i, j int) bool { return dispatchers[i].ID < dispatchers[j].ID })
|
||
|
||
var batch []*graph.Edge
|
||
seen := map[string]bool{}
|
||
for _, d := range dispatchers {
|
||
for _, name := range names {
|
||
cands := nameIndex[name]
|
||
sort.Slice(cands, func(i, j int) bool { return cands[i].ID < cands[j].ID })
|
||
for _, h := range cands {
|
||
if h == nil || h.ID == d.ID {
|
||
continue
|
||
}
|
||
// Repo-scope: a dispatcher only reaches handlers in its own
|
||
// dispatch boundary (vendored Gin + app handlers in one repo).
|
||
if !sameDispatchBoundary(d, h) {
|
||
continue
|
||
}
|
||
key := d.ID + "\x00" + h.ID
|
||
if seen[key] {
|
||
continue
|
||
}
|
||
seen[key] = true
|
||
batch = append(batch, ginMiddlewareEdge(d, h))
|
||
if len(batch) >= ginFanoutCap {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for _, e := range batch {
|
||
g.AddEdge(e)
|
||
}
|
||
return len(batch)
|
||
}
|
||
|
||
// ginHandlerNames coerces the gin_handlers Meta value (stamped as []string,
|
||
// possibly []any after a serialization round-trip) into a name slice.
|
||
func ginHandlerNames(v any) []string {
|
||
switch t := v.(type) {
|
||
case []string:
|
||
return t
|
||
case []any:
|
||
out := make([]string, 0, len(t))
|
||
for _, e := range t {
|
||
if s, ok := e.(string); ok && s != "" {
|
||
out = append(out, s)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ginMiddlewareEdge builds one dispatcher→handler speculative call edge.
|
||
func ginMiddlewareEdge(from, to *graph.Node) *graph.Edge {
|
||
return &graph.Edge{
|
||
From: from.ID,
|
||
To: to.ID,
|
||
Kind: graph.EdgeCalls,
|
||
FilePath: from.FilePath,
|
||
Line: from.StartLine,
|
||
Confidence: 0.4,
|
||
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeCalls, 0.4),
|
||
Origin: graph.OriginSpeculative,
|
||
Meta: map[string]any{
|
||
"via": ginMiddlewareVia,
|
||
"speculative": true,
|
||
MetaSynthesizedBy: SynthGinMiddleware,
|
||
MetaProvenance: ProvenanceHeuristic,
|
||
},
|
||
}
|
||
}
|