Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8352070

Inconsistent Behavior in Recursive Method Invocation via Reflection on OpenJDK 8

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      Software:

          System Software Overview:

            System Version: macOS 13.2 (22D49)
            Kernel Version: Darwin 22.3.0
            Boot Volume: Macintosh HD
            Boot Mode: Normal
            Computer Name: MacBook Pro
            User Name: MacBook
            Secure Virtual Memory: Enabled
            System Integrity Protection: Enabled
            Time since boot: 1 day, 2 hours, 19 minutes

      Hardware:

          Hardware Overview:

            Model Name: MacBook Pro
            Model Identifier: MacBookPro18,1
            Model Number: MK193CH/A
            Chip: Apple M1 Pro
            Total Number of Cores: 10 (8 performance and 2 efficiency)
            Memory: 16 GB
            System Firmware Version: 8419.80.7
            OS Loader Version: 8419.80.7
            Activation Lock Status: Disabled

      OpenJDK version:
            openjdk version "1.8.0_442"
           OpenJDK Runtime Environment (Temurin)(build 1.8.0_442-b06)
           OpenJDK 64-Bit Server VM (Temurin)(build 25.442-b06, mixed mode)

      A DESCRIPTION OF THE PROBLEM :
      The provided test program demonstrates inconsistent behavior when invoking a method recursively via reflection in OpenJDK 8. The program is designed to recursively call the vMeth method using reflection, which should ideally result in a StackOverflowError. However, the program exhibits inconsistent outputs across different runs on OpenJDK 8, while it behaves consistently on OpenJDK 11, 17, and 21. Additionally, the behavior is consistent on OpenJ9.

      ```
      public class Test {

          public static final int N = 1;

          public void vMeth(int b) {
              
              for (int f = 1; f < 10000; ++f) {
                  java.util.function.IntConsumer consumer = (int value) -> {
                      try {
                          java.lang.reflect.Method method = Test.class.getMethod("vMeth", int.class);
                          method.invoke(this, value);
                      } catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
                          e.printStackTrace();
                      }
                  };
                  consumer.accept(N);
              }
          }

          public static void main(String[] args) {
              new Test().vMeth(N);
          }
      }
      ```

      ### Observed Behavior:

      1. **Inconsistent Output on OpenJDK 8**:

      (1)In some runs, the program throws a `NoClassDefFoundError`:

      ```
      Exception in thread "main"
      Exception: java.lang.NoClassDefFoundError thrown from the UncaughtExceptionHandler in thread "main"
      ```

      (2)In other runs, it throws an `InvocationTargetException`:

      ```
      java.lang.reflect.InvocationTargetExceptionjava.lang.reflect.InvocationTargetException
      at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:498)
      at Test.lambda$vMeth$0(Test.java:12)
      at Test.vMeth(Test.java:17)
      ```

      2. **Consistent Output Without Reflection**:

      When the recursive invocation is performed directly (without reflection), the program consistently throws a `StackOverflowError`:

      ```
      public class Normal {

          public static final int N = 1;

          public void vMeth(int b) {
              
              for (int f = 1; f < 10000; ++f) {

                  try {
                      vMeth(b);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }

          public static void main(String[] args) {
              new Normal().vMeth(N);
          }
      }
      ```

      Output:

      ```
      Exception in thread "main" java.lang.StackOverflowError
      at Normal.vMeth(Normal.java:11)
      at Normal.vMeth(Normal.java:11)
      at Normal.vMeth(Normal.java:11)
      at Normal.vMeth(Normal.java:11)
      ```

      3. **Consistent Behavior on OpenJ9**:

      On OpenJ9, the program consistently throws an `InvocationTargetException`:

      ```
      java.lang.reflect.InvocationTargetException
      at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:503)
      at Test.lambda$vMeth$0(Test.java:12)
      at Test.vMeth(Test.java:17)
      ```

      4. **Consistent Behavior on OpenJDK 11, 17, and 21**:

      On OpenJDK 11, 17, and 21, the program consistently throws a `StackOverflowError` as expected.

      This bug highlights an inconsistency in how OpenJDK 8 handles recursive method invocations via reflection. However, it seems that this issue is resolved in later versions, but remains unsolved in OpenJDK8.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Compile the `Test.java` file using `javac`.
      2. Run the `Test` class multiple times using OpenJDK 8 to observe the inconsistent behavior.
          - Alternatively, use the following shell script to run the program 50 times and observe the output:
          
          ```
          for i in {1..50}; do
            pathToOpenJDK8/java -cp . Test
          done
          ```

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The output should be consistent
      ACTUAL -
      Inconsistent output

      ---------- BEGIN SOURCE ----------
      public class Test {

          public static final int N = 1;

          public void vMeth(int b) {
              
              for (int f = 1; f < 10000; ++f) {
                  java.util.function.IntConsumer consumer = (int value) -> {
                      try {
                          java.lang.reflect.Method method = Test.class.getMethod("vMeth", int.class);
                          method.invoke(this, value);
                      } catch (NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
                          e.printStackTrace();
                      }
                  };
                  consumer.accept(N);
              }
          }

          public static void main(String[] args) {
              new Test().vMeth(N);
          }
      }
      ---------- END SOURCE ----------

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: