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

Incorrect invocation mode when linkToInteface linker is eliminated

    XMLWordPrintable

Details

    • b103
    • b110
    • x86_64
    • generic

    Description

      FULL PRODUCT VERSION :
      java version "9-ea"
      Java(TM) SE Runtime Environment (build 9-ea+105-2016-02-11-003336.javare.4433.nc)
      Java HotSpot(TM) 64-Bit Server VM (build 9-ea+105-2016-02-11-003336.javare.4433.nc, mixed mode)

      FULL OS VERSION :
      Microsoft Windows [Version 6.1.7601]
      (also happens on Linux)

      A DESCRIPTION OF THE PROBLEM :
      During the development of a MethodHandle based implementation of Apache Lucene's MMapDirectory internals for the changes in Java 9 build 105+, we encountered the follwoing bug: Java 9 breaks invoking a MethodHandle that uses guardWithTest to invoke a DirectMethodHandle that points to an interface.

      THE PROBLEM WAS REPRODUCIBLE WITH -Xint FLAG: No

      THE PROBLEM WAS REPRODUCIBLE WITH -server FLAG: Yes

      REGRESSION. Last worked in version 8u74

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Compile and execute the the attached test code with Java 9 b105 (be sure to name the class and source code "Test.java") for the later java command line to work correctly:


      $ javac Test.java
      $ java -ea -Xbatch -XX:CompileCommand=exclude,Test$RunnableImpl,run Test

      The invocation with "-XX:CompileCommand=exclude,Test$RunnableImpl,run" is important to trigger the bug, because in this testcase the run method usually gets inlined. In the real world scenario with more complex classes this does not happen for sure, so triggering the bug. So we prevent compilation of the interface's run() method by Hotspot.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      The test program should pass.
      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      CompileCommand: exclude Test$RunnableImpl.run
      Exception in thread "main" java.lang.IncompatibleClassChangeError: Found class Test$RunnableImpl, but interface was expected
              at Test.main(Test.java:67)

      Test.java:67 points to composite_MH.invokeExect().

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      import static java.lang.invoke.MethodHandles.*;
      import static java.lang.invoke.MethodType.methodType;

      import java.lang.invoke.MethodHandle;
      import java.lang.invoke.MethodHandles.Lookup;
      import java.util.Objects;

      public final class Test {
       
        static class RunnableImpl implements Runnable {
          public int count = 0;
          
          @Override
          public void run() {
            count++;
          }
        }
        
        private Test(RunnableImpl impl) {
          this.impl = impl;
        }
        
        private final RunnableImpl impl;
        
        RunnableImpl get() {
          return impl;
        }
        
        public static void main(String... args) throws Throwable {
          /* This test creates a MethodHandle that executes the same code like:
           *
           * void composite(Test arg) {
           * Runnable r = (Runnable) arg.get();
           * if (Objects.nonNull(r)) {
           * r.run();
           * } else {
           * // fake else clause for MethodHandles.guardWithTest():
           * noop(r);
           * }
           * }
           */
          
          final Lookup lookup = lookup();
          MethodHandle getter_MH = lookup.findVirtual(Test.class, "get", methodType(RunnableImpl.class));
          // cast to Runnable:
          getter_MH = getter_MH.asType(getter_MH.type().changeReturnType(Runnable.class));
          
          // MH with "invokeinterface"
          MethodHandle run_MH = lookup.findVirtual(Runnable.class, "run", methodType(void.class));

          // MH for null check:
          MethodHandle nonNullTest_MH = lookup.findStatic(Objects.class, "nonNull", methodType(boolean.class, Object.class))
              .asType(methodType(boolean.class, Runnable.class));
          
          // MH to implement fake else check with noop(Runnable):
          MethodHandle noop_MH = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, Runnable.class);
          
          // our composite MH doing null check:
          MethodHandle composite_MH = filterReturnValue(getter_MH, guardWithTest(nonNullTest_MH, run_MH, noop_MH));
          
          // invoke our handle in loop executing null/non-null branch alternately:
          Test t1 = new Test(new RunnableImpl());
          Test t2 = new Test(null);
          int iters = 10_000;
          for (int i = 0; i < iters; i++) {
            Test t = (i % 2 == 0) ? t1 : t2;
            composite_MH.invokeExact(t);
          }
          assert t1.get().count == iters / 2;
        }
         
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Run program with Java 8 build 74. It passes without problems. It also passes if you don't prevent the interface's run() method to inline/compile.

      Attachments

        Issue Links

          Activity

            People

              vlivanov Vladimir Ivanov
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: