java-topology/defects/graal/patch/graal-0002-classfileconstant-resolvemethod-diamond.md

92 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# UNDF: UNDF-2026-000000600
# UNDF: (pending)
# graal-0002: ClassfileConstant.resolveMethod/resolveField — O(2^D) diamond re-traversal without visited set
## CWE-407 — Algorithmic Complexity: O(2^D) recursive interface re-traversal in classfile method/field resolution
| Field | Value |
|--------------|-------|
| ID | graal-0002 |
| Severity | MEDIUM |
| Ecosystem | graal |
| Package | jdk.graal.compiler |
| File | `compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/replacements/classfile/ClassfileConstant.java` |
| Lines | 281319 |
| Complexity | O(2^D) on interface diamond hierarchies |
| Hot path | Called during classfile-based bytecode provider method/field resolution for Graal substitutions |
## Defect
`ClassfileConstant.resolveMethod` and `resolveField` recursively traverse the type hierarchy to find
a matching method/field. They have NO visited set — on a diamond interface hierarchy, shared ancestor
interfaces are traversed exponentially:
```java
// ClassfileConstant.java:281-299 (DEFECT)
static ResolvedJavaMethod resolveMethod(ClassfileBytecodeProvider context, ResolvedJavaType c,
String name, String descriptor, boolean isStatic) {
ResolvedJavaMethod method = context.findMethod(c, name, descriptor, isStatic);
if (method != null) { return method; }
if (!c.isJavaLangObject() && !c.isInterface()) {
method = resolveMethod(context, c.getSuperclass(), name, descriptor, isStatic);
if (method != null) { return method; }
}
for (ResolvedJavaType i : c.getInterfaces()) {
method = resolveMethod(context, i, name, descriptor, isStatic); // DEFECT: no visited guard
if (method != null) { return method; }
}
return null;
}
// ClassfileConstant.java:301-319 — identical pattern for fields (DEFECT)
static ResolvedJavaField resolveField(ClassfileBytecodeProvider context, ResolvedJavaType c,
String name, String fieldType, boolean isStatic) {
// ... same structure, no visited set
}
```
For a diamond (class C implements I1 and I2; both I1 and I2 extend I_base):
- `resolveMethod(C)``resolveMethod(I1)``resolveMethod(I_base)` → not found
- Back in C: → `resolveMethod(I2)``resolveMethod(I_base)` → traversed AGAIN
At diamond depth D, `I_base` is visited 2^D times when the method is not found.
## Fix
Add a `Set<ResolvedJavaType> visited` parameter with a public entry-point wrapper:
```java
// AFTER — O(N+E) where N=types, E=hierarchy edges
static ResolvedJavaMethod resolveMethod(ClassfileBytecodeProvider context, ResolvedJavaType c,
String name, String descriptor, boolean isStatic) {
return resolveMethod(context, c, name, descriptor, isStatic, new HashSet<>());
}
private static ResolvedJavaMethod resolveMethod(ClassfileBytecodeProvider context, ResolvedJavaType c,
String name, String descriptor, boolean isStatic, Set<ResolvedJavaType> visited) {
if (!visited.add(c)) { return null; } // skip already-visited types
ResolvedJavaMethod method = context.findMethod(c, name, descriptor, isStatic);
if (method != null) { return method; }
if (!c.isJavaLangObject() && !c.isInterface()) {
method = resolveMethod(context, c.getSuperclass(), name, descriptor, isStatic, visited);
if (method != null) { return method; }
}
for (ResolvedJavaType i : c.getInterfaces()) {
method = resolveMethod(context, i, name, descriptor, isStatic, visited);
if (method != null) { return method; }
}
return null;
}
// Same fix applies to resolveField
```
## Speedup
| Diamond depth (D) | Before (visits) | After (visits) | Speedup |
|------------------|----------------|----------------|---------|
| 5 | 31 | 5 | 6× |
| 10 | 1,023 | 10 | 102× |
| 15 | 32,767 | 15 | 2,184× |
Growth before: O(2^D). Growth after: O(D).