Files
zzet--gortex/internal/indexer/receiver_mutations_test.go
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

142 lines
3.3 KiB
Go

package indexer
import (
"testing"
"github.com/zzet/gortex/internal/graph"
)
// indirectField returns true if some method named `caller` has an indirect
// accesses_field write edge to a field named `field` with the given `via`.
func indirectField(g *graph.Graph, caller, field, via string) bool {
for _, n := range g.AllNodes() {
if n.Kind != graph.KindMethod || n.Name != caller {
continue
}
for _, e := range g.GetOutEdges(n.ID) {
if e.Kind != graph.EdgeAccessesField || e.Meta == nil {
continue
}
if ind, _ := e.Meta["indirect"].(bool); !ind {
continue
}
fn := g.GetNode(e.To)
if fn == nil || fn.Name != field {
continue
}
if via == "" || e.Meta["via"] == via {
return true
}
}
}
return false
}
func TestIndirectMutation_FieldMethodCall(t *testing.T) {
g := indexFixture(t, map[string]string{
"m.go": `package main
type Counter struct{ n int }
func (c *Counter) Increment() { c.n++ }
type Server struct {
counter Counter
}
func (s *Server) Tick() { s.counter.Increment() }
`,
})
if !indirectField(g, "Tick", "counter", "Increment") {
t.Errorf("expected Tick to indirectly mutate counter via Increment")
}
}
func TestIndirectMutation_SiblingTransitive(t *testing.T) {
// gograph explicitly defers this transitive case; gortex ships it.
g := indexFixture(t, map[string]string{
"m.go": `package main
type Server struct {
running int
}
func (s *Server) doWork() { s.running = 1 }
func (s *Server) Run() { s.doWork() }
`,
})
if !indirectField(g, "Run", "running", "doWork") {
t.Errorf("expected Run to transitively mutate running via doWork (sibling call)")
}
}
func TestIndirectMutation_TransitiveFieldChain(t *testing.T) {
// Increment mutates inner via Bump (field-method), so a caller that does
// s.counter.Increment() must still see counter as mutated — the fixpoint
// must mark Increment a mutator transitively.
g := indexFixture(t, map[string]string{
"m.go": `package main
type Leaf struct{ v int }
func (l *Leaf) Bump() { l.v++ }
type Counter struct{ inner Leaf }
func (c *Counter) Increment() { c.inner.Bump() }
type Server struct{ counter Counter }
func (s *Server) Tick() { s.counter.Increment() }
`,
})
if !indirectField(g, "Increment", "inner", "Bump") {
t.Errorf("expected Increment to mutate inner via Bump")
}
if !indirectField(g, "Tick", "counter", "Increment") {
t.Errorf("expected Tick to transitively mutate counter via Increment")
}
}
func TestIndirectMutation_StdlibAllowlist(t *testing.T) {
g := indexFixture(t, map[string]string{
"m.go": `package main
import "sync/atomic"
type Server struct {
running atomic.Bool
}
func (s *Server) Start() { s.running.Store(true) }
`,
})
if !indirectField(g, "Start", "running", "Store") {
t.Errorf("expected Start to indirectly mutate running via Store (stdlib allowlist)")
}
}
func TestIndirectMutation_NegativeLocalReceiver(t *testing.T) {
// A call on a local variable (not the receiver) must NOT be attributed to
// a receiver field.
g := indexFixture(t, map[string]string{
"m.go": `package main
type Counter struct{ n int }
func (c *Counter) Increment() { c.n++ }
type Server struct{ counter Counter }
func (s *Server) Stray() {
local := Counter{}
local.Increment()
}
`,
})
if indirectField(g, "Stray", "counter", "") {
t.Errorf("local.Increment() must not be attributed to the receiver field counter")
}
}