diff --git a/defects/ghc/patch/ghc-diamond-recursion-CLEAN.md b/defects/ghc/patch/ghc-diamond-recursion-CLEAN.md new file mode 100644 index 000000000..8e9fa8668 --- /dev/null +++ b/defects/ghc/patch/ghc-diamond-recursion-CLEAN.md @@ -0,0 +1,67 @@ +# GHC Compiler — Diamond Recursion CWE-407 Scan: CLEAN + +**Pattern:** Recursive traversal of typeclass constraint hierarchy without visited set (O(2^D)) +**Scan date:** 2026-03-29 +**Scope:** `compiler/GHC/Tc/`, `compiler/GHC/Core/`, `compiler/GHC/Tc/Instance/` + +## Method + +Searched for recursive superclass/typeclass expansion without proper cycle/duplicate +tracking across the GHC type-checker (`GHC/Tc/`) and core (`GHC/Core/`). Traced all +callers of `immSuperClasses`, `classSCTheta`, `transSuperClasses`, `expandSuperClasses`, +and `mk_strict_superclasses`. Verified cycle-breaking mechanism in each path. + +## Key candidates reviewed + +### `GHC.Tc.TyCl.Utils.checkClassCycles` — `go_cls` +`compiler/GHC/Tc/TyCl/Utils.hs` lines 295–340. + +Recursive function that expands superclass predicates. Uses `ClassSet` (`UniqSet Class`) +threaded as `so_far`. Before recursing, checks `cls \`elementOfUniqSet\` so_far`. +Correct visited tracking. CLEAN. + +### `GHC.Tc.Utils.TcType.transSuperClasses` — `go` +`compiler/GHC/Tc/Utils/TcType.hs` lines 1931–1948. + +Recursive superclass expansion for constraint solving. Uses `go emptyNameSet` with +`extendNameSet cls_nm` and `not (cls_nm \`elemNameSet\` rec_clss)` guard before +recursing. Correct visited tracking via `NameSet`. CLEAN. + +### `GHC.Tc.Solver.Dict.mk_strict_superclasses` / `mk_superclasses_of` +`compiler/GHC/Tc/Solver/Dict.hs` lines 1658–1820. + +Superclass expansion for Given/Wanted constraints during solving. Uses `NameSet rec_clss` +threaded through `mk_superclasses → mk_superclasses_of → mk_strict_superclasses`. +Guard: `loop_found = not (isCTupleClass cls) && cls_nm \`elemNameSet\` rec_clss`. +Also uses `ExpansionFuel` counter to prevent infinite expansion. Correct. CLEAN. + +### `GHC.Tc.Instance.FunDeps.closeWrtFunDeps` +`compiler/GHC/Tc/Instance/FunDeps.hs` lines 547–584. + +Calls `transSuperClasses pred` (line 570) which already has proper NameSet tracking. +`fixVarSet` outer loop terminates on fixpoint. CLEAN. + +### `GHC.Tc.Instance.FunDeps.checkFunDeps` +`compiler/GHC/Tc/Instance/FunDeps.hs` lines 644–650. + +Uses `nubBy eq_inst` to deduplicate instances — O(N²) but N is bounded by number of +instances in scope, not a type hierarchy depth. Not O(2^D). CLEAN. + +### `GHC.Tc.Utils.TcType.mkMinimalBySCs` +`compiler/GHC/Tc/Utils/TcType.hs` lines 1874–1925. + +Calls `transSuperClasses pred` for each predicate to build superclass sets, then uses +list scan `in_cloud`. O(N × |superclasses|) where superclasses are deduplicated by +`transSuperClasses`. Not O(2^D). CLEAN. + +## Verdict + +CLEAN for diamond O(2^D) recursion. GHC uses: +- `ClassSet` / `NameSet` / `UniqSet` throughout typeclass hierarchy traversal +- `elemNameSet` / `elementOfUniqSet` guards before recursing +- `ExpansionFuel` counter as belt-and-suspenders against infinite expansion +- `transSuperClasses` with `go emptyNameSet` for all transitive expansion + +No unprotected recursive DAG traversal found. Previous ghc-0001/ghc-0003 UNDF entries +(UNDF-2026-000000078, UNDF-2026-000000079) represent pre-existing CWE-407 issues from +earlier scan passes; diamond recursion is not present in GHC's typeclass hierarchy code. diff --git a/defects/groovy/patch/groovy-0001-stc-collectednames-arraylist.md b/defects/groovy/patch/groovy-0001-stc-collectednames-arraylist.md new file mode 100644 index 000000000..3d91ab8a2 --- /dev/null +++ b/defects/groovy/patch/groovy-0001-stc-collectednames-arraylist.md @@ -0,0 +1,89 @@ +# UNDF: UNDF-2026-000000409 +# groovy-0001: StaticTypeCheckingVisitor.checkNamedParamsAnnotation — O(E×C) ArrayList.contains membership scan + +## CWE-407 — Algorithmic Complexity: O(E×C) linear scan for named param validation + +| Field | Value | +|-------|-------| +| ID | groovy-0001 | +| Severity | MEDIUM | +| Ecosystem | groovy | +| Package | org.codehaus.groovy.transform.stc | +| File | `src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java` | +| Lines | 3205–3242 | +| Complexity | O(E×C) where E = map entry count, C = collected annotation names | +| Hot path | Static type checking of @NamedParams/@NamedParam annotations on method calls | + +## Defect + +`checkNamedParamsAnnotation` builds `collectedNames` as an `ArrayList`, +then performs `collectedNames.contains(name)` inside a loop over `entries.entrySet()`. +Each `contains()` is O(C), making the overall check O(E×C) instead of O(E). + +```java +// DEFECT: ArrayList.contains() is O(C) inside O(E) loop → O(E×C) total +List collectedNames = new ArrayList<>(); +// ... populate collectedNames via processNamedParam() ... +if (!collectedNames.isEmpty()) { + for (Map.Entry entry : entries.entrySet()) { + Object name = entry.getKey(); + if (!collectedNames.contains(name)) { // O(C) scan each iteration + addStaticTypeError("unexpected named arg: " + name, entry.getValue()); + } + } +} +``` + +`processNamedParam` also appends to the same `List` accumulator: +```java +private void processNamedParam(final AnnotationNode namedParam, ..., final List collectedNames) { + ... + collectedNames.add(name); // appends to ArrayList +} +``` + +With E map entries and C collected names, the inner loop runs E×C comparisons. +For methods with many @NamedParam annotations and many call-site named args, this +is measurably quadratic: C=100 names × E=100 entries = 10,000 string comparisons +vs 200 with a HashSet. + +## Fix + +Change `collectedNames` from `ArrayList` to `Set`: + +```java +// FIX: Set.contains() is O(1) → O(E) total +Set collectedNames = new HashSet<>(); +// ... populate same way via processNamedParam() ... +if (!collectedNames.isEmpty()) { + for (Map.Entry entry : entries.entrySet()) { + Object name = entry.getKey(); + if (!collectedNames.contains(name)) { // O(1) lookup + addStaticTypeError("unexpected named arg: " + name, entry.getValue()); + } + } +} +``` + +Update `processNamedParam` signature to accept `Set` instead of `List`: + +```java +private void processNamedParam(final AnnotationNode namedParam, + final Map entries, + final MapExpression args, + final Set collectedNames) { // was List + ... + collectedNames.add(name); +} +``` + +## Speedup + +| C (names) | E (entries) | Before ops | After ops | Speedup | +|-----------|-------------|------------|-----------|---------| +| 10 | 10 | 100 | 10 | 10× | +| 50 | 50 | 2,500 | 50 | 50× | +| 100 | 100 | 10,000 | 100 | 100× | +| 500 | 500 | 250,000 | 500 | 500× | + +At C=E=1000 (pathological @NamedParams with many fields): 1,000,000 → 1,000 ops (1000×). diff --git a/defects/groovy/patch/groovy-0002-verifier-params-arrays-aslist.md b/defects/groovy/patch/groovy-0002-verifier-params-arrays-aslist.md new file mode 100644 index 000000000..4bd99befa --- /dev/null +++ b/defects/groovy/patch/groovy-0002-verifier-params-arrays-aslist.md @@ -0,0 +1,87 @@ +# UNDF: UNDF-2026-000000410 +# groovy-0002: Verifier — O(V×P) Arrays.asList(params).contains() re-allocation per check + +## CWE-407 — Algorithmic Complexity: O(V×P) repeated array-to-list conversion + linear scan + +| Field | Value | +|-------|-------| +| ID | groovy-0002 | +| Severity | MEDIUM | +| Ecosystem | groovy | +| Package | org.codehaus.groovy.classgen | +| File | `src/main/java/org/codehaus/groovy/classgen/Verifier.java` | +| Lines | 963, 1043, 1054 | +| Complexity | O(V×P) where V = variable expression visits, P = params array length | +| Hot path | Verifier pass during compilation — constructor and method parameter resolution | + +## Defect + +Three sites in `Verifier.java` use `Arrays.asList(params).contains(p)` inside +visitor methods that iterate over variable expressions: + +**Site 1 (line 963):** Inside `visitVariableExpression` of an anonymous `CodeVisitorSupport` +that traverses a closure block, called for every variable expression: +```java +// DEFECT: Arrays.asList(params) allocates a new List wrapper each call; .contains() is O(P) +if (!Arrays.asList(params).contains(p) && Arrays.asList(method.getParameters()).contains(p)) { + // GROOVY-10602 +``` + +**Site 2 (line 1043):** Same pattern in a different `visitVariableExpression`: +```java +// DEFECT: re-allocates List each invocation, O(P) scan +if (p.getInitialExpression() instanceof ConstantExpression && !Arrays.asList(params).contains(p)){ +``` + +**Site 3 (line 1054):** +```java +// DEFECT: same pattern +if (p.hasInitialExpression() && !Arrays.asList(params).contains(p)) { +``` + +`Arrays.asList()` creates a wrapper each time — O(1) allocation but O(P) scan. +Called V times (once per variable expression in the method body), total cost is O(V×P). +For a method with P=20 parameters and V=100 variable references, that is 2,000 pointer +comparisons vs 100 with a pre-built `Set`. + +## Fix + +Hoist the set construction outside the visitor, share it as an effectively-final capture: + +```java +// FIX: build once before the visitor loop +Set paramSet = new HashSet<>(Arrays.asList(params)); +Set methodParamSet = new HashSet<>(Arrays.asList(method.getParameters())); + +// Inside visitVariableExpression: +if (!paramSet.contains(p) && methodParamSet.contains(p)) { // O(1) each + // GROOVY-10602 +``` + +For sites 2 and 3: +```java +Set paramSet = new HashSet<>(Arrays.asList(params)); +// ... +if (p.getInitialExpression() instanceof ConstantExpression && !paramSet.contains(p)) { +// ... +if (p.hasInitialExpression() && !paramSet.contains(p)) { +``` + +## Speedup + +| P (params) | V (var refs) | Before ops | After ops | Speedup | +|------------|--------------|------------|-----------|---------| +| 5 | 20 | 100 | 20 | 5× | +| 20 | 100 | 2,000 | 100 | 20× | +| 50 | 500 | 25,000 | 500 | 50× | +| 100 | 1,000 | 100,000 | 1,000 | 100× | + +Typical: P=20, V=50 method body references → 1,000 → 50 ops (20×). +Pathological generated code: P=100, V=1000 → 100,000 → 1,000 ops (100×). + +## Note on diamond recursion + +Groovy's interface hierarchy traversal (ClassNode.getAllInterfaces, Traits.collectAllInterfacesReverseOrder, +GeneralUtils.addAllInterfaces) all use `if (set.add(node))` guards introduced in GROOVY-11036. +These sites are CLEAN for O(2^D) diamond recursion. groovy-0001 and groovy-0002 are +O(N²) membership-scan defects, distinct from diamond recursion. diff --git a/defects/groovy/patch/groovy-diamond-recursion-CLEAN.md b/defects/groovy/patch/groovy-diamond-recursion-CLEAN.md new file mode 100644 index 000000000..02fbb3259 --- /dev/null +++ b/defects/groovy/patch/groovy-diamond-recursion-CLEAN.md @@ -0,0 +1,50 @@ +# 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 441–452. + +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 491–518. + +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 307–316. + +Uses `if (interfaces.add(iNode)) collectAllInterfacesReverseOrder(iNode, interfaces)`. +Correct guard. CLEAN. + +### `ResolveVisitor` cycle detection +`src/main/java/org/codehaus/groovy/control/ResolveVisitor.java` lines 1363–1381. + +Uses BFS with `done.add(next)` as the loop guard. CLEAN. + +### `WideningCategories.lowestUpperBound()` +`src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java` lines 319–447. + +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.