Skip to content

Infinite recursion issue with synthetic fields #81

@lukehutch

Description

@lukehutch

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

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions