mirror of
https://github.com/securego/gosec.git
synced 2026-06-20 00:15:59 +02:00
Route redirect dependency checks through the cycle-safe dependencyChecker instead of raw recursive valueDependsOn traversal. This ensures Phi-cycle graphs terminate quickly and avoids recursive work amplification that can look like hangs on large/generated codebases. Also remove the nil fallback in dependencyChecker.dependsOn so all analyzer paths consistently use cycle-aware logic. Add regression tests covering raw valueDependsOn behavior on: Phi cycle without target (must return false) Phi cycle with target path (must return true) Self-referential Phi node (must return false) Signed-off-by: Cosmin Cojocar <cosmin@cojocar.ch>
This commit is contained in:
@@ -46,9 +46,6 @@ func newDependencyChecker() *dependencyChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *dependencyChecker) dependsOn(value ssa.Value, target ssa.Value) bool {
|
func (c *dependencyChecker) dependsOn(value ssa.Value, target ssa.Value) bool {
|
||||||
if c == nil {
|
|
||||||
return valueDependsOn(value, target, 0)
|
|
||||||
}
|
|
||||||
return c.dependsOnDepth(value, target, 0)
|
return c.dependsOnDepth(value, target, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,3 +43,50 @@ func TestDependencyCheckerFindsTargetInPhiCycle(t *testing.T) {
|
|||||||
t.Fatal("expected stable memoized result on repeated call")
|
t.Fatal("expected stable memoized result on repeated call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValueDependsOnHandlesPhiCycleWithoutTarget(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
target := ssa.NewConst(constant.MakeInt64(42), types.Typ[types.Int])
|
||||||
|
|
||||||
|
phiA := &ssa.Phi{}
|
||||||
|
phiB := &ssa.Phi{}
|
||||||
|
phiA.Edges = []ssa.Value{phiB}
|
||||||
|
phiB.Edges = []ssa.Value{phiA}
|
||||||
|
|
||||||
|
if valueDependsOn(phiA, target, 0) {
|
||||||
|
t.Fatal("expected false for Phi cycle with no path to target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueDependsOnFindsTargetInPhiCycle(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
target := ssa.NewConst(constant.MakeInt64(7), types.Typ[types.Int])
|
||||||
|
|
||||||
|
phiA := &ssa.Phi{}
|
||||||
|
phiB := &ssa.Phi{}
|
||||||
|
phiA.Edges = []ssa.Value{phiB, target}
|
||||||
|
phiB.Edges = []ssa.Value{phiA}
|
||||||
|
|
||||||
|
if !valueDependsOn(phiA, target, 0) {
|
||||||
|
t.Fatal("expected true when cycle has a path to target")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !valueDependsOn(phiA, target, 0) {
|
||||||
|
t.Fatal("expected stable result on repeated call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueDependsOnSelfReferentialPhi(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
target := ssa.NewConst(constant.MakeInt64(1), types.Typ[types.Int])
|
||||||
|
|
||||||
|
phi := &ssa.Phi{}
|
||||||
|
phi.Edges = []ssa.Value{phi}
|
||||||
|
|
||||||
|
if valueDependsOn(phi, target, 0) {
|
||||||
|
t.Fatal("expected false for self-referential Phi with no path to target")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -261,61 +261,6 @@ func extractStringConst(v ssa.Value) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func valueDependsOn(value ssa.Value, target ssa.Value, depth int) bool {
|
func valueDependsOn(value ssa.Value, target ssa.Value, depth int) bool {
|
||||||
if value == nil || target == nil || depth > MaxDepth {
|
checker := newDependencyChecker()
|
||||||
return false
|
return checker.dependsOnDepth(value, target, depth)
|
||||||
}
|
|
||||||
if value == target {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := value.(type) {
|
|
||||||
case *ssa.ChangeType:
|
|
||||||
return valueDependsOn(v.X, target, depth+1)
|
|
||||||
case *ssa.MakeInterface:
|
|
||||||
return valueDependsOn(v.X, target, depth+1)
|
|
||||||
case *ssa.TypeAssert:
|
|
||||||
return valueDependsOn(v.X, target, depth+1)
|
|
||||||
case *ssa.UnOp:
|
|
||||||
return valueDependsOn(v.X, target, depth+1)
|
|
||||||
case *ssa.FieldAddr:
|
|
||||||
return valueDependsOn(v.X, target, depth+1)
|
|
||||||
case *ssa.Field:
|
|
||||||
return valueDependsOn(v.X, target, depth+1)
|
|
||||||
case *ssa.IndexAddr:
|
|
||||||
return valueDependsOn(v.X, target, depth+1) || valueDependsOn(v.Index, target, depth+1)
|
|
||||||
case *ssa.Index:
|
|
||||||
return valueDependsOn(v.X, target, depth+1) || valueDependsOn(v.Index, target, depth+1)
|
|
||||||
case *ssa.Slice:
|
|
||||||
if valueDependsOn(v.X, target, depth+1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if v.Low != nil && valueDependsOn(v.Low, target, depth+1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if v.High != nil && valueDependsOn(v.High, target, depth+1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return v.Max != nil && valueDependsOn(v.Max, target, depth+1)
|
|
||||||
case *ssa.Extract:
|
|
||||||
return valueDependsOn(v.Tuple, target, depth+1)
|
|
||||||
case *ssa.Phi:
|
|
||||||
for _, edge := range v.Edges {
|
|
||||||
if valueDependsOn(edge, target, depth+1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case *ssa.Call:
|
|
||||||
if v.Call.Value != nil && valueDependsOn(v.Call.Value, target, depth+1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, arg := range v.Call.Args {
|
|
||||||
if valueDependsOn(arg, target, depth+1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user