Files
zzet--gortex/internal/resolver/fn_pointer_dispatch.go
T
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

224 lines
5.9 KiB
Go

package resolver
import (
"sort"
"github.com/zzet/gortex/internal/graph"
)
// fnPtrDispatchVia marks a C/C++ indirect-dispatch placeholder
// (`cmds[i].fn(...)`); fnPtrRegVia marks a registration carrier (a concrete
// function bound to a struct's fn-pointer field). Both must match the
// extractor's constants.
const (
fnPtrDispatchVia = "fn-pointer-dispatch"
fnPtrRegVia = "fn-pointer-reg"
)
// fnPointerFanoutCap bounds the functions a single dispatch slot may fan out
// to. fnPointerConfidence is the struct+field-keyed confidence — higher than
// a pure-name guess, lower than a typed binding.
const (
fnPointerFanoutCap = 64
fnPointerConfidence = 0.7
)
// ResolveFnPointerDispatch binds C/C++ function-pointer dispatch: a function
// registered into a struct's fn-pointer field
// (`struct cmd cmds[] = {{"add", cmd_add}}`) is linked to the indirect call
// `cmds[i].fn(...)` whose receiver resolves to that struct type. Registrations
// (positional + designated initializers, `x.field = fn` assignments, and
// `a.field = b.field` copies propagated to fixpoint) build a (struct, field)
// → {functions} index; each dispatch site fans out to every function in its
// slot.
//
// Returns the number of dispatcher → function edges synthesized.
func ResolveFnPointerDispatch(g graph.Store) int {
if g == nil {
return 0
}
slotFns := map[string]map[string]*graph.Node{}
addFn := func(key string, n *graph.Node) {
if n == nil {
return
}
if slotFns[key] == nil {
slotFns[key] = map[string]*graph.Node{}
}
slotFns[key][n.ID] = n
}
type copyEdge struct{ to, from string }
var copies []copyEdge
var regReindex []graph.EdgeReindex
for e := range g.EdgesByKind(graph.EdgeReferences) {
if e == nil || e.Meta == nil {
continue
}
if v, _ := e.Meta["via"].(string); v != fnPtrRegVia {
continue
}
st, _ := e.Meta["fnptr_struct"].(string)
field, _ := e.Meta["fnptr_field"].(string)
if st == "" || field == "" {
continue
}
key := st + "\x00" + field
if cs, _ := e.Meta["fnptr_copy_struct"].(string); cs != "" {
cf, _ := e.Meta["fnptr_copy_field"].(string)
copies = append(copies, copyEdge{to: key, from: cs + "\x00" + cf})
continue
}
fn, _ := e.Meta["fnptr_fn"].(string)
if fn == "" {
continue
}
target := fnPtrFunctionByName(g, e, fn)
if target == nil {
continue
}
addFn(key, target)
if e.To != target.ID {
oldTo := e.To
e.To = target.ID
e.Origin = graph.OriginASTInferred
regReindex = append(regReindex, graph.EdgeReindex{Edge: e, OldTo: oldTo})
}
}
if len(regReindex) > 0 {
g.ReindexEdges(regReindex)
}
if len(slotFns) == 0 {
return 0
}
// Field←field propagation fixpoint.
for iter := 0; iter < 8; iter++ {
changed := false
for _, c := range copies {
for id, n := range slotFns[c.from] {
if slotFns[c.to] == nil {
slotFns[c.to] = map[string]*graph.Node{}
}
if _, ok := slotFns[c.to][id]; !ok {
slotFns[c.to][id] = n
changed = true
}
}
}
if !changed {
break
}
}
resolved := 0
var reindex []graph.EdgeReindex
var batch []*graph.Edge
for e := range g.EdgesByKind(graph.EdgeCalls) {
if e == nil || e.Meta == nil {
continue
}
if v, _ := e.Meta["via"].(string); v != fnPtrDispatchVia {
continue
}
st, _ := e.Meta["fnptr_struct"].(string)
field, _ := e.Meta["fnptr_field"].(string)
if st == "" || field == "" {
continue
}
fns := fnPtrSortedSlot(slotFns[st+"\x00"+field])
if len(fns) > fnPointerFanoutCap {
fns = fns[:fnPointerFanoutCap]
}
if len(fns) == 0 {
resolved += fnPtrRebind(e, nil, &reindex)
continue
}
resolved += fnPtrRebind(e, fns[0], &reindex)
for _, n := range fns[1:] {
batch = append(batch, fnPtrFanoutEdge(e, n, st, field))
resolved++
}
}
if len(reindex) > 0 {
g.ReindexEdges(reindex)
}
for _, ne := range batch {
g.AddEdge(ne)
}
return resolved
}
func fnPtrRebind(e *graph.Edge, target *graph.Node, reindex *[]graph.EdgeReindex) int {
field, _ := e.Meta["fnptr_field"].(string)
want := "unresolved::*." + field
if target != nil {
want = target.ID
}
if e.To == want {
if target != nil {
return 1
}
return 0
}
oldTo := e.To
e.To = want
hit := 0
if target != nil {
e.Origin = graph.OriginASTInferred
e.Confidence = fnPointerConfidence
e.ConfidenceLabel = graph.ConfidenceLabelFor(graph.EdgeCalls, fnPointerConfidence)
StampSynthesized(e, SynthFnPointerDispatch)
hit = 1
} else {
e.Origin = graph.OriginASTInferred
e.Confidence = 0
e.ConfidenceLabel = ""
UnstampSynthesized(e)
}
*reindex = append(*reindex, graph.EdgeReindex{Edge: e, OldTo: oldTo})
return hit
}
func fnPtrFanoutEdge(e *graph.Edge, target *graph.Node, st, field string) *graph.Edge {
return &graph.Edge{
From: e.From, To: target.ID, Kind: graph.EdgeCalls,
FilePath: e.FilePath, Line: e.Line,
Origin: graph.OriginASTInferred,
Confidence: fnPointerConfidence,
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeCalls, fnPointerConfidence),
Meta: map[string]any{
"via": fnPtrDispatchVia,
"fnptr_struct": st,
"fnptr_field": field,
MetaSynthesizedBy: SynthFnPointerDispatch,
MetaProvenance: ProvenanceHeuristic,
},
}
}
// fnPtrFunctionByName resolves a registered function name to its node,
// preferring the same file as the registration, then a unique match.
func fnPtrFunctionByName(g graph.Store, reg *graph.Edge, name string) *graph.Node {
var cands []*graph.Node
for _, n := range g.FindNodesByName(name) {
if n == nil || (n.Kind != graph.KindFunction && n.Kind != graph.KindMethod) {
continue
}
if graph.IsStub(n.ID) || graph.IsUnresolvedTarget(n.ID) {
continue
}
cands = append(cands, n)
}
return pickStoreAction(g, reg, sameBoundaryCandidates(g, reg.From, cands))
}
func fnPtrSortedSlot(m map[string]*graph.Node) []*graph.Node {
out := make([]*graph.Node, 0, len(m))
for _, n := range m {
out = append(out, n)
}
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
return out
}