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
144 lines
4.2 KiB
Go
144 lines
4.2 KiB
Go
package resolver
|
|
|
|
import (
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// Speculative dynamic-dispatch synthesis. Some call shapes are genuine static
|
|
// blind spots that no precision-first framework rule covers: computed member
|
|
// calls `obj["foo"]()`, getattr-style dispatch `getattr(o, "foo")()`, and
|
|
// decorator registries. The extractor stamps these dropped calls with
|
|
// Meta["dyn_shape"] + Meta["dyn_key"] (the method name when it is a literal),
|
|
// and this opt-in pass mints LOW-confidence best-guess `calls` edges to the
|
|
// plausible same-name targets — tagged OriginSpeculative + Meta[speculative]
|
|
// so they are hidden from every default query and surfaced only on demand.
|
|
//
|
|
// This is what codegraph's playbook refused as "partial coverage worse than
|
|
// none": gortex can ship it because the edges are present-but-hidden-by-default
|
|
// (zero pollution) and explicitly auditable via `analyze kind=speculative`.
|
|
|
|
const (
|
|
// speculativeFanoutCap: above this many candidates the confidence floors;
|
|
// above the hard cap the whole set is dropped as noise (codegraph's rule).
|
|
speculativeFanoutCap = 12
|
|
speculativeHardCap = 40
|
|
)
|
|
|
|
// ResolveSpeculativeDispatch mints speculative call edges for the tagged
|
|
// blind-spot call shapes. No-op when disabled. Returns the edge count.
|
|
func ResolveSpeculativeDispatch(g graph.Store, enabled bool) int {
|
|
if g == nil || !enabled {
|
|
return 0
|
|
}
|
|
|
|
// Existing resolved call pairs, so a speculative edge never duplicates a
|
|
// real one from the same caller to the same target.
|
|
existing := map[string]bool{}
|
|
for e := range g.EdgesByKind(graph.EdgeCalls) {
|
|
if e == nil || e.IsSpeculative() {
|
|
continue
|
|
}
|
|
if !graph.IsUnresolvedTarget(e.To) {
|
|
existing[e.From+"\x00"+e.To] = true
|
|
}
|
|
}
|
|
|
|
type spec struct {
|
|
from, to, file, shape, key string
|
|
line, n int
|
|
}
|
|
var specs []spec
|
|
seen := map[string]bool{}
|
|
for e := range g.EdgesByKind(graph.EdgeCalls) {
|
|
if e == nil || e.Meta == nil {
|
|
continue
|
|
}
|
|
shape, _ := e.Meta["dyn_shape"].(string)
|
|
if shape == "" {
|
|
continue
|
|
}
|
|
// A precision-first synthesizer (object-registry) already bound this
|
|
// computed-member dispatch site; do not also mint a hidden guess.
|
|
if claimed, _ := e.Meta["registry_claimed"].(bool); claimed {
|
|
continue
|
|
}
|
|
key, _ := e.Meta["dyn_key"].(string)
|
|
if key == "" {
|
|
continue // v1: literal-key shapes only (variable-key is unbounded)
|
|
}
|
|
callerLang := ""
|
|
if c := g.GetNode(e.From); c != nil {
|
|
callerLang = c.Language
|
|
}
|
|
cands := speculativeCandidates(g, key, callerLang)
|
|
if len(cands) == 0 || len(cands) > speculativeHardCap {
|
|
continue
|
|
}
|
|
for _, c := range cands {
|
|
if existing[e.From+"\x00"+c.ID] {
|
|
continue
|
|
}
|
|
k := e.From + "\x00" + c.ID
|
|
if seen[k] {
|
|
continue
|
|
}
|
|
seen[k] = true
|
|
specs = append(specs, spec{from: e.From, to: c.ID, file: e.FilePath, line: e.Line, shape: shape, key: key, n: len(cands)})
|
|
}
|
|
}
|
|
|
|
resolved := 0
|
|
for _, s := range specs {
|
|
conf := 1.0 / float64(s.n)
|
|
if s.n > speculativeFanoutCap {
|
|
conf = 0.05
|
|
}
|
|
if conf > 0.45 {
|
|
conf = 0.45
|
|
}
|
|
if conf < 0.05 {
|
|
conf = 0.05
|
|
}
|
|
g.AddEdge(&graph.Edge{
|
|
From: s.from, To: s.to, Kind: graph.EdgeCalls,
|
|
FilePath: s.file, Line: s.line,
|
|
Origin: graph.OriginSpeculative,
|
|
Confidence: conf,
|
|
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeCalls, conf),
|
|
Meta: map[string]any{
|
|
graph.MetaSpeculative: true,
|
|
MetaSynthesizedBy: SynthSpeculative,
|
|
MetaProvenance: ProvenanceHeuristic,
|
|
"via": "speculative." + s.shape,
|
|
"candidate_count": s.n,
|
|
"dyn_key": s.key,
|
|
},
|
|
})
|
|
resolved++
|
|
}
|
|
return resolved
|
|
}
|
|
|
|
// speculativeCandidates returns the plausible targets for a literal dispatch
|
|
// key: function/method definitions of that exact name, in the caller's
|
|
// language family, excluding stubs.
|
|
func speculativeCandidates(g graph.Store, key, callerLang string) []*graph.Node {
|
|
var out []*graph.Node
|
|
for _, n := range g.FindNodesByName(key) {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
if n.Kind != graph.KindFunction && n.Kind != graph.KindMethod {
|
|
continue
|
|
}
|
|
if graph.IsStub(n.ID) || graph.IsUnresolvedTarget(n.ID) {
|
|
continue
|
|
}
|
|
if callerLang != "" && n.Language != "" && n.Language != callerLang {
|
|
continue
|
|
}
|
|
out = append(out, n)
|
|
}
|
|
return out
|
|
}
|