-
Notifications
You must be signed in to change notification settings - Fork 131
Open
Description
I was getting stack overflow errors trying to extract JavaDoc for non-static inner classes. I used Claude to analyze the code, and it gave me this analysis:
> Where exactly is the recursive call made?
⏺ Great question! Let me trace through the exact recursive call chain that causes the StackOverflowError.
The Issue: It's NOT in FnJavaDocReader
Looking at the FnJavaDocReader.java code I provided earlier, there are actually no recursive calls within this class itself. The infinite recursion is happening inside the
third-party libraries that FnJavaDocReader depends on.
The Actual Recursive Call Chain
The recursion occurs in this sequence:
// FnJavaDocReader.java:65
var type = Type.of(classRef); // This is where it starts going wrong
The infinite recursion happens inside the Strobel reflection library (Type.of()) when it tries to analyze a local class:
1. Strobel Reflection Library Internal Recursion
// Inside com.strobel.reflection.Type.of() - simplified version
public static Type<?> of(Class<?> clazz) {
// Analyzes class structure including fields, methods, etc.
return new TypeImpl(clazz);
}
// Inside TypeImpl constructor (conceptual)
private TypeImpl(Class<?> clazz) {
this.clazz = clazz;
// This is where the trouble starts - it analyzes ALL fields
for (Field field : clazz.getDeclaredFields()) {
if (field.isSynthetic()) {
// For local classes, this includes the "this$0" field
// which references the enclosing test instance
Type<?> fieldType = Type.of(field.getType()); // RECURSIVE CALL!
}
}
}
2. The Circular Reference Chain
Here's the exact recursion that occurs:
1. Type.of(LocalTestClass.class)
2. → Analyzes LocalTestClass fields
3. → Finds synthetic field "this$0" of type TestClass
4. → Calls Type.of(TestClass.class)
5. → Analyzes TestClass fields/methods
6. → Finds reference back to LocalTestClass (through method local variables)
7. → Calls Type.of(LocalTestClass.class) again
8. → INFINITE RECURSION
The JavaDoc Library Recursion
There's also potential recursion in the JavaDoc extraction:
// FnJavaDocReader.java:77
var classDoc = RuntimeJavadoc.getJavadoc(classRef.getName());
The RuntimeJavadoc.getJavadoc() method might also trigger recursion when it tries to process the annotation metadata for local classes.
Where to Detect/Fix It
Since the recursion is happening inside third-party libraries, the fix needs to be at the entry point in FnJavaDocReader:
public static String readV8FunctionJavaDocs(Class<?> classRef) {
// FIX NEEDED HERE - before calling any third-party code
if (classRef.isLocalClass()) {
throw new RuntimeException(
"Local classes not supported due to circular references in reflection libraries"
);
}
// These calls are where the actual recursion happens (in third-party code):
var type = Type.of(classRef); // Strobel library recursion
var classDoc = RuntimeJavadoc.getJavadoc(classRef.getName()); // JavaDoc library recursion
// ... rest of method
}
Debugging the Exact Stack Trace
If you wanted to see the exact recursive calls, you'd see a stack trace like:
java.lang.StackOverflowError
at com.strobel.reflection.TypeImpl.getFields(TypeImpl.java:xxx)
at com.strobel.reflection.TypeImpl.analyzeSyntheticFields(TypeImpl.java:xxx)
at com.strobel.reflection.TypeImpl.<init>(TypeImpl.java:xxx)
at com.strobel.reflection.Type.of(Type.java:xxx)
at com.strobel.reflection.TypeImpl.analyzeSyntheticFields(TypeImpl.java:xxx) // RECURSION
at com.strobel.reflection.TypeImpl.<init>(TypeImpl.java:xxx) // RECURSION
at com.strobel.reflection.Type.of(Type.java:xxx) // RECURSION
[... this pattern repeats hundreds of times ...]
at com.bamboohr.ai.utils.FnJavaDocReader.readV8FunctionJavaDocs(FnJavaDocReader.java:65)
The Key Insight
The recursion isn't in our code - it's in the reflection libraries we're using. Those libraries weren't designed to handle the circular reference patterns that local classes create.
The fix has to be defensive programming - detecting problematic class types before passing them to the libraries that can't handle them.
This is why the solution was to use static member classes instead - they don't have the synthetic this$0 field that creates the circular reference in the first place.
Metadata
Metadata
Assignees
Labels
No labels