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
103 lines
2.9 KiB
Go
103 lines
2.9 KiB
Go
package resolver
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// flutterSetStateVia marks a synthesized Flutter setState→build reachability
|
|
// edge.
|
|
const flutterSetStateVia = "flutter.setstate"
|
|
|
|
// ResolveFlutterSetStateCalls is the framework-dispatch synthesizer for the
|
|
// Flutter widget re-build hop. `setState(() { … })` schedules the State's
|
|
// `build(...)` to re-run, but that hop is framework-internal — no static edge —
|
|
// so a flow dead-ends at setState even though everything `build` reaches is
|
|
// call-connected. This pass bridges it: for each State class that has a `build`
|
|
// method, it links every sibling method whose body calls `setState(` to that
|
|
// `build`. The setState call is the gate that keeps this to Flutter State
|
|
// classes — a plain class with a `build` method that never calls `setState`
|
|
// produces no edge.
|
|
//
|
|
// Over-approximation by design, full recompute and idempotent; edges ride at
|
|
// ast_inferred and carry synthesizer provenance. Returns the number of
|
|
// setState→build edges synthesized.
|
|
func ResolveFlutterSetStateCalls(g graph.Store) int {
|
|
if g == nil {
|
|
return 0
|
|
}
|
|
|
|
classByMethod := map[string]string{}
|
|
buildByClass := map[string]*graph.Node{}
|
|
for _, n := range nodesByKindsOrAll(g, graph.KindMethod) {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
for _, e := range g.GetOutEdges(n.ID) {
|
|
if e == nil || e.Kind != graph.EdgeMemberOf {
|
|
continue
|
|
}
|
|
classByMethod[n.ID] = e.To
|
|
if n.Name == "build" {
|
|
buildByClass[e.To] = n
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if len(buildByClass) == 0 {
|
|
return 0
|
|
}
|
|
|
|
var setStateMethods []*graph.Node
|
|
for _, n := range nodesByKindsOrAll(g, graph.KindMethod) {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
build := buildByClass[classByMethod[n.ID]]
|
|
if build == nil || build.ID == n.ID {
|
|
continue
|
|
}
|
|
if !methodCallsSetState(g, n.ID) {
|
|
continue
|
|
}
|
|
setStateMethods = append(setStateMethods, n)
|
|
}
|
|
sort.Slice(setStateMethods, func(i, j int) bool {
|
|
return setStateMethods[i].ID < setStateMethods[j].ID
|
|
})
|
|
|
|
var batch []*graph.Edge
|
|
synthesized := 0
|
|
for _, m := range setStateMethods {
|
|
build := buildByClass[classByMethod[m.ID]]
|
|
batch = append(batch, flutterSetStateEdge(m, build, classByMethod[m.ID]))
|
|
synthesized++
|
|
}
|
|
|
|
for _, e := range batch {
|
|
g.AddEdge(e)
|
|
}
|
|
return synthesized
|
|
}
|
|
|
|
// flutterSetStateEdge builds one setState-method → build synthesized edge.
|
|
func flutterSetStateEdge(from, build *graph.Node, class string) *graph.Edge {
|
|
return &graph.Edge{
|
|
From: from.ID,
|
|
To: build.ID,
|
|
Kind: graph.EdgeCalls,
|
|
FilePath: from.FilePath,
|
|
Line: from.StartLine,
|
|
Confidence: 0.6,
|
|
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeCalls, 0.6),
|
|
Origin: graph.OriginASTInferred,
|
|
Meta: map[string]any{
|
|
"via": flutterSetStateVia,
|
|
"state_class": class,
|
|
MetaSynthesizedBy: SynthFlutterSetState,
|
|
MetaProvenance: ProvenanceHeuristic,
|
|
},
|
|
}
|
|
}
|