cleanup: remove duplicate groovy-0001/0002 docs (same UNDF as originals); stamp swift-0001

This commit is contained in:
russell@unturf.com 2026-03-29 20:32:55 -04:00
parent 61a717bfcc
commit 7b07951882
3 changed files with 1 additions and 177 deletions

View file

@ -1,89 +0,0 @@
# 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 | 32053242 |
| 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<String>`,
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<String> collectedNames = new ArrayList<>();
// ... populate collectedNames via processNamedParam() ...
if (!collectedNames.isEmpty()) {
for (Map.Entry<Object, MapEntryExpression> 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<String>` accumulator:
```java
private void processNamedParam(final AnnotationNode namedParam, ..., final List<String> 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<String>` to `Set<String>`:
```java
// FIX: Set.contains() is O(1) → O(E) total
Set<String> collectedNames = new HashSet<>();
// ... populate same way via processNamedParam() ...
if (!collectedNames.isEmpty()) {
for (Map.Entry<Object, MapEntryExpression> 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<String>` instead of `List<String>`:
```java
private void processNamedParam(final AnnotationNode namedParam,
final Map<Object, MapEntryExpression> entries,
final MapExpression args,
final Set<String> collectedNames) { // was List<String>
...
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×).

View file

@ -1,87 +0,0 @@
# 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<Parameter>`.
## Fix
Hoist the set construction outside the visitor, share it as an effectively-final capture:
```java
// FIX: build once before the visitor loop
Set<Parameter> paramSet = new HashSet<>(Arrays.asList(params));
Set<Parameter> 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<Parameter> 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.

View file

@ -1,4 +1,4 @@
# UNDF: (pending)
# UNDF: UNDF-2026-000000545
# swift-0001: QualifiedLookupRequest — O(2^D) diamond re-traversal via unguarded protocol superclass push
## CWE-407 — Algorithmic Complexity: O(2^D) recursive diamond re-traversal