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
173 lines
4.5 KiB
Go
173 lines
4.5 KiB
Go
package languages
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// lsExtractGo runs the Go extractor over src and returns the emitted
|
|
// nodes, so a test can read the loop-region signals StampLoopSignals
|
|
// stamps on a function/method node's Meta.
|
|
func lsExtractGo(t *testing.T, src string) []*graph.Node {
|
|
t.Helper()
|
|
res, err := NewGoExtractor().Extract("signals.go", []byte(src))
|
|
if err != nil {
|
|
t.Fatalf("extract: %v", err)
|
|
}
|
|
return res.Nodes
|
|
}
|
|
|
|
func lsNodeByName(t *testing.T, nodes []*graph.Node, name string) *graph.Node {
|
|
t.Helper()
|
|
for _, n := range nodes {
|
|
if n != nil && n.Name == name {
|
|
return n
|
|
}
|
|
}
|
|
t.Fatalf("node %q not found", name)
|
|
return nil
|
|
}
|
|
|
|
func lsMetaInt(n *graph.Node, key string) (int, bool) {
|
|
if n == nil || n.Meta == nil {
|
|
return 0, false
|
|
}
|
|
v, ok := n.Meta[key]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
switch x := v.(type) {
|
|
case int:
|
|
return x, true
|
|
case int64:
|
|
return int(x), true
|
|
case float64:
|
|
return int(x), true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func lsMetaBool(n *graph.Node, key string) (val, present bool) {
|
|
if n == nil || n.Meta == nil {
|
|
return false, false
|
|
}
|
|
v, ok := n.Meta[key]
|
|
if !ok {
|
|
return false, false
|
|
}
|
|
b, _ := v.(bool)
|
|
return b, true
|
|
}
|
|
|
|
// TestLoopSignals_MaxAccessDepth pins the documented counting: the deepest
|
|
// member-access chain length is the number of identifier segments in it —
|
|
// a.b.c.d.e is 5 (four selector hops plus the base operand).
|
|
func TestLoopSignals_MaxAccessDepth(t *testing.T) {
|
|
nodes := lsExtractGo(t, `package p
|
|
|
|
func deep() {
|
|
_ = a.b.c.d.e
|
|
}
|
|
`)
|
|
got, ok := lsMetaInt(lsNodeByName(t, nodes, "deep"), "max_access_depth")
|
|
if !ok {
|
|
t.Fatal("max_access_depth not stamped")
|
|
}
|
|
if got != 5 {
|
|
t.Fatalf("max_access_depth = %d, want 5 (segments of a.b.c.d.e)", got)
|
|
}
|
|
}
|
|
|
|
// TestLoopSignals_LinearScanInLoop proves loop-region precision for a
|
|
// linear-scan call: the same Contains call is flagged inside a loop and
|
|
// not flagged outside one.
|
|
func TestLoopSignals_LinearScanInLoop(t *testing.T) {
|
|
nodes := lsExtractGo(t, `package p
|
|
|
|
func scanInLoop(xs []int, v int) bool {
|
|
for _, x := range xs {
|
|
_ = x
|
|
if slices.Contains(xs, v) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func scanNoLoop(xs []int, v int) bool {
|
|
return slices.Contains(xs, v)
|
|
}
|
|
`)
|
|
if v, present := lsMetaBool(lsNodeByName(t, nodes, "scanInLoop"), "linear_scan_in_loop"); !present || !v {
|
|
t.Fatalf("scanInLoop: linear_scan_in_loop present=%v val=%v, want true", present, v)
|
|
}
|
|
if _, present := lsMetaBool(lsNodeByName(t, nodes, "scanNoLoop"), "linear_scan_in_loop"); present {
|
|
t.Fatal("scanNoLoop: linear_scan_in_loop must not be flagged outside a loop")
|
|
}
|
|
}
|
|
|
|
// TestLoopSignals_AllocInLoop proves loop-region precision for an
|
|
// allocation: an append inside a loop is flagged; a composite literal and
|
|
// append outside a loop are not.
|
|
func TestLoopSignals_AllocInLoop(t *testing.T) {
|
|
nodes := lsExtractGo(t, `package p
|
|
|
|
func allocInLoop(n int) []int {
|
|
var xs []int
|
|
for i := 0; i < n; i++ {
|
|
xs = append(xs, i)
|
|
}
|
|
return xs
|
|
}
|
|
|
|
func allocNoLoop() []int {
|
|
xs := []int{}
|
|
xs = append(xs, 1)
|
|
return xs
|
|
}
|
|
`)
|
|
if v, present := lsMetaBool(lsNodeByName(t, nodes, "allocInLoop"), "alloc_in_loop"); !present || !v {
|
|
t.Fatalf("allocInLoop: alloc_in_loop present=%v val=%v, want true", present, v)
|
|
}
|
|
if _, present := lsMetaBool(lsNodeByName(t, nodes, "allocNoLoop"), "alloc_in_loop"); present {
|
|
t.Fatal("allocNoLoop: alloc_in_loop must not be flagged outside a loop")
|
|
}
|
|
}
|
|
|
|
// TestLoopSignals_RecursionInLoop proves the self-recursion-in-loop signal
|
|
// distinguishes a self-call inside a loop (flagged) from a self-call
|
|
// outside a loop and a call to a different function inside a loop (both not
|
|
// flagged).
|
|
func TestLoopSignals_RecursionInLoop(t *testing.T) {
|
|
nodes := lsExtractGo(t, `package p
|
|
|
|
func recurInLoop(n int) {
|
|
for i := 0; i < n; i++ {
|
|
recurInLoop(i)
|
|
}
|
|
}
|
|
|
|
func recurNoLoop(n int) {
|
|
if n > 0 {
|
|
recurNoLoop(n - 1)
|
|
}
|
|
}
|
|
|
|
func callsOtherInLoop(n int) {
|
|
for i := 0; i < n; i++ {
|
|
other(i)
|
|
}
|
|
}
|
|
`)
|
|
if v, present := lsMetaBool(lsNodeByName(t, nodes, "recurInLoop"), "recursion_in_loop"); !present || !v {
|
|
t.Fatalf("recurInLoop: recursion_in_loop present=%v val=%v, want true", present, v)
|
|
}
|
|
if _, present := lsMetaBool(lsNodeByName(t, nodes, "recurNoLoop"), "recursion_in_loop"); present {
|
|
t.Fatal("recurNoLoop: recursion_in_loop must not be flagged outside a loop")
|
|
}
|
|
if _, present := lsMetaBool(lsNodeByName(t, nodes, "callsOtherInLoop"), "recursion_in_loop"); present {
|
|
t.Fatal("callsOtherInLoop: a call to a different function must not be flagged as recursion")
|
|
}
|
|
}
|