whitepaper: re-add 10 missing entries + 11 new defects this session, count 578→590; rebuild PDF

This commit is contained in:
russell@unturf.com 2026-03-27 22:18:31 -04:00
parent e4ee168b1e
commit 2e4f7807d5
401 changed files with 3914 additions and 114 deletions

View file

@ -0,0 +1,99 @@
# eclipse-jdt-0001: minimalErasedCandidates BFS work-queue ArrayList.contains O(N²)
**CWE-407** — Inefficient Algorithmic Complexity
**Severity:** HIGH
**Status:** PATCHED (patch below)
## Location
`org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java`
Method: `minimalErasedCandidates(TypeBinding[], Map<TypeBinding,Object>)`
Lines: **4273, 42954383** (current HEAD)
## Root Cause
`typesToVisit` is declared as `new ArrayList<>()` (line 4273).
The BFS loop at line 4295 (`for (int i = 0; i < max; i++)`) expands the supertype
hierarchy by appending to `typesToVisit` while walking it. For every candidate
supertype it calls `typesToVisit.contains(superType)` to deduplicate — up to **5
times per iteration** (elementType, Serializable, Cloneable, Object, each
interface, superclass).
`ArrayList.contains` is O(N) linear scan. With N types in the common supertype
hierarchy the BFS performs O(N) contains checks per step × O(N) steps =
**O(N²) overall**.
This fires during every `lub()` / common-supertype computation, which is invoked:
- for every conditional/ternary expression (`? :`)
- for every multi-catch clause (`catch (A | B e)`)
- for every intersection cast
- for lambda return-type inference when multiple branches have different types
A class hierarchy with N=200 supertypes (achievable with deep interface diamond
graphs or generated code) produces 40 000 list scans where 200 HashMap lookups
would suffice.
## Defective Code
```java
// Scope.java line 4273
List<TypeBinding> typesToVisit = new ArrayList<>(); // <-- O(N) contains
// ... inside BFS at line 4295:
for (int i = 0; i < max; i++) {
...
if (!typesToVisit.contains(superType)) { // O(N) × O(N) = O(N²)
typesToVisit.add(superType);
max++;
}
}
```
## Fix
Replace the single `ArrayList` with a parallel `HashSet` used exclusively for
deduplication; the `ArrayList` is kept for ordered iteration (the method later
indexes into it).
```java
// PATCHED
List<TypeBinding> typesToVisit = new ArrayList<>();
Set<TypeBinding> visitedSet = new HashSet<>(); // O(1) contains
visitedSet.add(firstType);
// In the BFS loop, every !typesToVisit.contains(x) becomes:
if (visitedSet.add(x)) { // Set.add returns false if already present
typesToVisit.add(x);
max++;
}
```
`Set.add` returns `false` when the element is already present, giving O(1) dedup
while preserving the original ordered traversal semantics.
Note: `TypeBinding.equals` / `TypeBinding.hashCode` are already properly
implemented in the JDT codebase (identity-based via `IdentityHashMap` usage
elsewhere), so `HashSet<TypeBinding>` is safe here.
## Complexity Table
| Metric | Before (ArrayList) | After (HashSet dedup) |
|--------|-------------------|----------------------|
| Contains check | O(N) | O(1) |
| BFS total work | O(N²) | O(N) |
| Memory | O(N) | O(N) — one extra set |
## Speedup (measured in unit test, BRANCH=4)
| N (supertype count) | SLOW ops | FAST ops | Ratio |
|---------------------|----------|----------|-------|
| 50 | 4 870 | 184 | 26.5× |
| 100 | 19 770 | 384 | 51.5× |
| 200 | 79 570 | 784 | 101.5× |
| 500 | 498 970 | 1 984 | 251.5× |
## Affected Callers
- `Scope.lub(TypeBinding[])` — line 4176: every `? :` ternary in compiled code
- `Scope.lub(TypeBinding, TypeBinding)` — line 3749: pairwise common supertype
- Any flow-analysis pass that calls `lub` (exception merging, return type merging)