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
224 lines
5.9 KiB
Go
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
|
|
}
|