java-topology/defects/groovy/patch/groovy-diamond-recursion-CLEAN.md
russell@unturf.com 61a717bfcc diamond-scan-deeper: kotlin/scala3/groovy/ghc O(2^D) hierarchy traversal — CLEAN + groovy-0001/0002 patches
Kotlin: confirmed CLEAN (DFS.VisitedWithSet throughout, existing marker valid)
Scala3: confirmed CLEAN (BaseDataBuilder.addAll deduplicates, existing marker valid)
GHC: CLEAN — NameSet/UniqSet/ExpansionFuel tracking in all superclass expansion paths
  (checkClassCycles, mk_strict_superclasses, transSuperClasses, closeWrtFunDeps)
Groovy: CLEAN for diamond — getAllInterfaces/collectAllInterfacesReverseOrder/addAllInterfaces
  all use if(set.add(node)) guards (GROOVY-11036)
  groovy-0001/0002 patch docs created (O(N²) membership scans, pre-existing UNDF assigned)
Clojure: CLEAN (set-based BFS, supers/ancestors use set-based worklist)
2026-03-29 20:30:49 -04:00

2.1 KiB
Raw Blame History

Groovy Compiler — Diamond Recursion CWE-407 Scan: CLEAN

Pattern: Recursive O(2^D) traversal of interface/trait hierarchy without visited set Scan date: 2026-03-29 Scope: src/main/java/org/codehaus/groovy/

Method

Searched for recursive ClassNode hierarchy traversal without proper if (set.add()) guards. Traced all callers of getInterfaces(), getSuperClass(), getAllInterfaces(), getInterfacesAndSuperInterfaces(), and collectAllInterfacesReverseOrder().

Key candidates reviewed

ClassNode.getAllInterfaces() — private getAllInterfaces(Set)

src/main/java/org/codehaus/groovy/ast/ClassNode.java lines 441452.

Fixed in GROOVY-11036: now uses if (set.add(face)) face.getAllInterfaces(set). The guard on set.add() return value prevents re-traversal of shared ancestors. CLEAN (post-fix).

GeneralUtils.addAllInterfaces() / getInterfacesAndSuperInterfaces()

src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java lines 491518.

Uses if (result.add(in)) { addAllInterfaces(result, in); } for interface branches. Superclass branch is linear (Java single-inheritance), so no diamond possible there. CLEAN.

Traits.collectAllInterfacesReverseOrder()

src/main/java/org/codehaus/groovy/transform/trait/Traits.java lines 307316.

Uses if (interfaces.add(iNode)) collectAllInterfacesReverseOrder(iNode, interfaces). Correct guard. CLEAN.

ResolveVisitor cycle detection

src/main/java/org/codehaus/groovy/control/ResolveVisitor.java lines 13631381.

Uses BFS with done.add(next) as the loop guard. CLEAN.

WideningCategories.lowestUpperBound()

src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java lines 319447.

Recursion on superclass chain (linear, no diamond). Interface sets pre-computed via getInterfacesAndSuperInterfaces() which is guarded. CLEAN.

Verdict

CLEAN for O(2^D) diamond recursion. Groovy uses if (set.add(node)) guards on all recursive interface hierarchy traversal. The two existing defects (groovy-0001, groovy-0002) are O(N²) list-membership scans, not diamond recursion.