changeset 59592:f4dc7eafeb51 nestmates

[nestmates] javac should keep old behavior to synthesize a bridge method if target to release <= 14. Reviewed-by: vromero, jlahoda Contributed-by: jan.lahoda@oracle.com, mandy.chung@oracle.com
author mchung
date Mon, 30 Mar 2020 16:28:00 -0700
parents d97444469015
children 19f8a7827a31
files src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskPool.java src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTargetRelease14Test.java test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTest.java test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecode.java test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecodeTargetRelease14.java
diffstat 10 files changed, 491 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskPool.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTaskPool.java	Mon Mar 30 16:28:00 2020 -0700
@@ -68,12 +68,14 @@
 import com.sun.tools.javac.main.Arguments;
 import com.sun.tools.javac.main.JavaCompiler;
 import com.sun.tools.javac.model.JavacElements;
+import com.sun.tools.javac.platform.PlatformDescription;
 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
 import com.sun.tools.javac.tree.JCTree.LetExpr;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.DefinedBy;
 import com.sun.tools.javac.util.DefinedBy.Api;
 import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Options;
 
 /**
  * A pool of reusable JavacTasks. When a task is no valid anymore, it is returned to the pool,
@@ -252,6 +254,7 @@
             drop(JavacTask.class);
             drop(JavacTrees.class);
             drop(JavacElements.class);
+            drop(PlatformDescription.class);
 
             if (ht.get(Log.logKey) instanceof ReusableLog) {
                 //log already inited - not first round
@@ -266,6 +269,7 @@
                 Annotate.instance(this).newRound();
                 CompileStates.instance(this).clear();
                 MultiTaskListener.instance(this).clear();
+                Options.instance(this).clear();
 
                 //find if any of the roots have redefined java.* classes
                 Symtab syms = Symtab.instance(this);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java	Mon Mar 30 16:28:00 2020 -0700
@@ -1231,10 +1231,14 @@
 
     public static class RootPackageSymbol extends PackageSymbol {
         public final MissingInfoHandler missingInfoHandler;
+        public final boolean allowPrivateInvokeVirtual;
 
-        public RootPackageSymbol(Name name, Symbol owner, MissingInfoHandler missingInfoHandler) {
+        public RootPackageSymbol(Name name, Symbol owner,
+                                 MissingInfoHandler missingInfoHandler,
+                                 boolean allowPrivateInvokeVirtual) {
             super(name, owner);
             this.missingInfoHandler = missingInfoHandler;
+            this.allowPrivateInvokeVirtual = allowPrivateInvokeVirtual;
         }
 
     }
@@ -2311,6 +2315,8 @@
                 } else {
                     if (refSym.isStatic()) {
                         return ClassFile.REF_invokeStatic;
+                    } else if ((refSym.flags() & PRIVATE) != 0 && !allowPrivateInvokeVirtual()) {
+                        return ClassFile.REF_invokeSpecial;
                     } else if (refSym.enclClass().isInterface()) {
                         return ClassFile.REF_invokeInterface;
                     } else {
@@ -2320,6 +2326,13 @@
             }
         }
 
+        private boolean allowPrivateInvokeVirtual() {
+            Symbol rootPack = this;
+            while (rootPack != null && !(rootPack instanceof RootPackageSymbol)) {
+                rootPack = rootPack.owner;
+            }
+            return rootPack != null && ((RootPackageSymbol) rootPack).allowPrivateInvokeVirtual;
+        }
         @Override
         public int poolTag() {
             return ClassFile.CONSTANT_MethodHandle;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java	Mon Mar 30 16:28:00 2020 -0700
@@ -54,6 +54,7 @@
 import com.sun.tools.javac.code.Type.UnknownType;
 import com.sun.tools.javac.code.Types.UniqueType;
 import com.sun.tools.javac.comp.Modules;
+import com.sun.tools.javac.jvm.Target;
 import com.sun.tools.javac.util.Assert;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.Convert;
@@ -389,7 +390,10 @@
 
         MissingInfoHandler missingInfoHandler = MissingInfoHandler.instance(context);
 
-        rootPackage = new RootPackageSymbol(names.empty, null, missingInfoHandler);
+        Target target = Target.instance(context);
+        rootPackage = new RootPackageSymbol(names.empty, null,
+                                            missingInfoHandler,
+                                            target.runtimeUseNestAccess());
 
         // create the basic builtin symbols
         unnamedModule = new ModuleSymbol(names.empty, null) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java	Mon Mar 30 16:28:00 2020 -0700
@@ -36,7 +36,6 @@
 import com.sun.tools.javac.tree.TreeMaker;
 import com.sun.tools.javac.tree.TreeTranslator;
 import com.sun.tools.javac.code.Attribute;
-import com.sun.tools.javac.code.Scope.WriteableScope;
 import com.sun.tools.javac.code.Symbol;
 import com.sun.tools.javac.code.Symbol.ClassSymbol;
 import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol;
@@ -127,6 +126,9 @@
     /** deduplicate lambda implementation methods */
     private final boolean deduplicateLambdas;
 
+    /** lambda proxy is a dynamic nestmate */
+    private final boolean nestmateLambdas;
+
     /** Flag for alternate metafactories indicating the lambda object is intended to be serializable */
     public static final int FLAG_SERIALIZABLE = 1 << 0;
 
@@ -168,6 +170,7 @@
                 || options.isSet(Option.G_CUSTOM, "vars");
         verboseDeduplication = options.isSet("debug.dumpLambdaToMethodDeduplication");
         deduplicateLambdas = options.getBoolean("deduplicateLambdas", true);
+        nestmateLambdas = Target.instance(context).runtimeUseNestAccess();
     }
     // </editor-fold>
 
@@ -2253,6 +2256,20 @@
                 return tree.ownerAccessible;
             }
 
+            /**
+             * This method should be called only when target release <= 14
+             * where LambdaMetaFactory does not spin nestmate classes.
+             *
+             * This method should be removed when --release 14 is not supported.
+             */
+            boolean isPrivateInOtherClass() {
+                assert !nestmateLambdas;
+                return  (tree.sym.flags() & PRIVATE) != 0 &&
+                        !types.isSameType(
+                              types.erasure(tree.sym.enclClass().asType()),
+                              types.erasure(owner.enclClass().asType()));
+            }
+
             boolean isProtectedInSuperClassOfEnclosingClassInOtherPackage() {
                 return ((tree.sym.flags() & PROTECTED) != 0 &&
                         tree.sym.packge() != owner.packge());
@@ -2293,6 +2310,7 @@
                         isSuper ||
                         needsVarArgsConversion() ||
                         isArrayOp() ||
+                        (!nestmateLambdas && isPrivateInOtherClass()) ||
                         isProtectedInSuperClassOfEnclosingClassInOtherPackage() ||
                         !receiverAccessible() ||
                         (tree.getMode() == ReferenceMode.NEW &&
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java	Mon Mar 30 16:28:00 2020 -0700
@@ -169,6 +169,14 @@
         return compareTo(JDK1_11) >= 0;
     }
 
+    /** language runtime uses nest-based access.
+     *  e.g. lambda and string concat spin dynamic proxy class as a nestmate
+     *  of the target class
+     */
+    public boolean runtimeUseNestAccess() {
+        return compareTo(JDK1_15) >= 0;
+    }
+
     /** Does the target VM support virtual private invocations?
      */
     public boolean hasVirtualPrivateInvoke() {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java	Mon Mar 30 16:28:00 2020 -0700
@@ -181,4 +181,9 @@
         for (Runnable r: listeners)
             r.run();
     }
+
+    public void clear() {
+        values.clear();
+        listeners = List.nil();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTargetRelease14Test.java	Mon Mar 30 16:28:00 2020 -0700
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8238358
+ * @summary Checking ACC_SYNTHETIC flag is generated for bridge method
+ *          generated for lambda expressions and method references when
+ *          compiling with --release 14.
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ *          jdk.jdeps/com.sun.tools.classfile
+ * @library /tools/lib /tools/javac/lib ../lib
+ * @build toolbox.ToolBox InMemoryFileManager TestResult TestBase
+ * @build SyntheticTestDriver ExpectedClass ExpectedClasses
+ * @compile --source 14 -target 14 -XDdeduplicateLambdas=false BridgeMethodsForLambdaTargetRelease14Test.java
+ * @run main SyntheticTestDriver BridgeMethodsForLambdaTargetRelease14Test
+ */
+
+import java.util.Comparator;
+import java.util.stream.IntStream;
+
+/**
+ * Synthetic members:
+ * 1. inner class for Inner1.
+ * 2. method for () -> {} in Inner1
+ * 3. method for () -> {} in Inner2
+ * 4. method references to private methods.
+ * 5. method for super::function()
+ * 6. method references to private static methods.
+ * 7. access method for private method function().
+ * 8. access method for private static method staticFunction().
+ * 9. method reference to vararg method.
+ * 10. method reference to array's method.
+ * 11. constructors for Inner1 and Inner2.
+ */
+@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test",
+        expectedMethods = {"<init>()", "<clinit>()", "function(java.lang.Integer[])"},
+        expectedNumberOfSyntheticMethods = 6)
+@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner1",
+        expectedMethods = {"<init>(BridgeMethodsForLambdaTargetRelease14Test)", "function()", "run()"},
+        expectedFields = "lambda1",
+        expectedNumberOfSyntheticMethods = 1,
+        expectedNumberOfSyntheticFields = 1)
+@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner2",
+        expectedMethods = {"<init>()", "staticFunction()"},
+        expectedFields = "lambda1",
+        expectedNumberOfSyntheticMethods = 1)
+@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner3",
+        expectedMethods = {"<init>(BridgeMethodsForLambdaTargetRelease14Test)", "function()"},
+        expectedNumberOfSyntheticFields = 1)
+@ExpectedClass(className = "BridgeMethodsForLambdaTargetRelease14Test$Inner4",
+        expectedMethods = {"<init>(BridgeMethodsForLambdaTargetRelease14Test)", "function()"},
+        expectedNumberOfSyntheticMethods = 1,
+        expectedNumberOfSyntheticFields = 1)
+public class BridgeMethodsForLambdaTargetRelease14Test {
+
+    private class Inner1 implements Runnable {
+        private Inner1() {
+        }
+        private Runnable lambda1 = () -> {
+        };
+        private void function() {
+        }
+        @Override
+        public void run() {
+        }
+    }
+
+    private static class Inner2 {
+        private Runnable lambda1 = () -> {
+        };
+        private static void staticFunction() {
+        }
+    }
+
+    private class Inner3 {
+        public void function() {
+        }
+    }
+
+    private class Inner4 extends Inner3 {
+        @Override
+        public void function() {
+            Runnable r = super::function;
+        }
+    }
+
+    private static int function(Integer...vararg) {
+        return 0;
+    }
+
+    {
+        Inner1 inner = new Inner1();
+        Runnable l1 = inner::function;
+        Runnable l2 = Inner1::new;
+        inner.lambda1 = inner::function;
+        Comparator<Integer> c = BridgeMethodsForLambdaTargetRelease14Test::function;
+        IntStream.of(2).mapToObj(int[]::new);
+    }
+
+    static {
+        Inner2 inner = new Inner2();
+        Runnable l1 = Inner2::staticFunction;
+    }
+}
--- a/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTest.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/test/langtools/tools/javac/classfiles/attributes/Synthetic/BridgeMethodsForLambdaTest.java	Mon Mar 30 16:28:00 2020 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8044537 8200301
+ * @bug 8044537 8200301 8238358
  * @summary Checking ACC_SYNTHETIC flag is generated for bridge method
  *          generated for lambda expressions and method references.
  * @modules jdk.compiler/com.sun.tools.javac.api
--- a/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecode.java	Mon Mar 30 18:04:53 2020 +0000
+++ b/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecode.java	Mon Mar 30 16:28:00 2020 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,8 +23,9 @@
 
 /*
  * @test
- * @bug 8009649 8129962
- * @summary Lambda back-end should generate invokespecial for method handles referring to private instance methods
+ * @bug 8009649 8129962 8238358
+ * @summary Lambda back-end should generate invokevirtual for method handles referring to
+ *          private instance methods as lambda proxy is a nestmate of the target clsas
  * @library /tools/javac/lib
  * @modules jdk.jdeps/com.sun.tools.classfile
  *          jdk.compiler/com.sun.tools.javac.api
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/lambda/bytecode/TestLambdaBytecodeTargetRelease14.java	Mon Mar 30 16:28:00 2020 -0700
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8238358
+ * @summary Lambda back-end should generate invokespecial for method handles referring to
+ *          private instance methods when compiling with --release 14
+ * @library /tools/javac/lib
+ * @modules jdk.jdeps/com.sun.tools.classfile
+ *          jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.file
+ *          jdk.compiler/com.sun.tools.javac.util
+ * @build combo.ComboTestHelper
+ * @run main TestLambdaBytecodeTargetRelease14
+ */
+
+import com.sun.tools.classfile.Attribute;
+import com.sun.tools.classfile.BootstrapMethods_attribute;
+import com.sun.tools.classfile.ClassFile;
+import com.sun.tools.classfile.Code_attribute;
+import com.sun.tools.classfile.ConstantPool.*;
+import com.sun.tools.classfile.Instruction;
+import com.sun.tools.classfile.Method;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import combo.ComboInstance;
+import combo.ComboParameter;
+import combo.ComboTask.Result;
+import combo.ComboTestHelper;
+
+import javax.tools.JavaFileObject;
+
+public class TestLambdaBytecodeTargetRelease14 extends ComboInstance<TestLambdaBytecodeTargetRelease14> {
+
+    static final int MF_ARITY = 3;
+    static final String MH_SIG = "()V";
+
+    enum ClassKind implements ComboParameter {
+        CLASS("class"),
+        INTERFACE("interface");
+
+        String classStr;
+
+        ClassKind(String classStr) {
+            this.classStr = classStr;
+        }
+
+        @Override
+        public String expand(String optParameter) {
+            return classStr;
+        }
+    }
+
+    enum AccessKind implements ComboParameter {
+        PUBLIC("public"),
+        PRIVATE("private");
+
+        String accessStr;
+
+        AccessKind(String accessStr) {
+            this.accessStr = accessStr;
+        }
+
+        @Override
+        public String expand(String optParameter) {
+            return accessStr;
+        }
+    }
+
+    enum StaticKind implements ComboParameter {
+        STATIC("static"),
+        INSTANCE("");
+
+        String staticStr;
+
+        StaticKind(String staticStr) {
+            this.staticStr = staticStr;
+        }
+
+        @Override
+        public String expand(String optParameter) {
+            return staticStr;
+        }
+    }
+
+    enum DefaultKind implements ComboParameter {
+        DEFAULT("default"),
+        NO_DEFAULT("");
+
+        String defaultStr;
+
+        DefaultKind(String defaultStr) {
+            this.defaultStr = defaultStr;
+        }
+
+        @Override
+        public String expand(String optParameter) {
+            return defaultStr;
+        }
+    }
+
+    static class MethodKind {
+        ClassKind ck;
+        AccessKind ak;
+        StaticKind sk;
+        DefaultKind dk;
+
+        MethodKind(ClassKind ck, AccessKind ak, StaticKind sk, DefaultKind dk) {
+            this.ck = ck;
+            this.ak = ak;
+            this.sk = sk;
+            this.dk = dk;
+        }
+
+        boolean inInterface() {
+            return ck == ClassKind.INTERFACE;
+        }
+
+        boolean isPrivate() {
+            return ak == AccessKind.PRIVATE;
+        }
+
+        boolean isStatic() {
+            return sk == StaticKind.STATIC;
+        }
+
+        boolean isDefault() {
+            return dk == DefaultKind.DEFAULT;
+        }
+
+        boolean isOK() {
+            if (isDefault() && (!inInterface() || isStatic())) {
+                return false;
+            } else if (inInterface() &&
+                    ((!isStatic() && !isDefault()) || isPrivate())) {
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        new ComboTestHelper<TestLambdaBytecodeTargetRelease14>()
+                .withDimension("CLASSKIND", (x, ck) -> x.ck = ck, ClassKind.values())
+                .withArrayDimension("ACCESS", (x, acc, idx) -> x.accessKinds[idx] = acc, 2, AccessKind.values())
+                .withArrayDimension("STATIC", (x, sk, idx) -> x.staticKinds[idx] = sk, 2, StaticKind.values())
+                .withArrayDimension("DEFAULT", (x, dk, idx) -> x.defaultKinds[idx] = dk, 2, DefaultKind.values())
+                .run(TestLambdaBytecodeTargetRelease14::new, TestLambdaBytecodeTargetRelease14::init);
+    }
+
+    ClassKind ck;
+    AccessKind[] accessKinds = new AccessKind[2];
+    StaticKind[] staticKinds = new StaticKind[2];
+    DefaultKind[] defaultKinds = new DefaultKind[2];
+    MethodKind mk1, mk2;
+
+    void init() {
+        mk1 = new MethodKind(ck, accessKinds[0], staticKinds[0], defaultKinds[0]);
+        mk2 = new MethodKind(ck, accessKinds[1], staticKinds[1], defaultKinds[1]);
+    }
+
+    String source_template =
+                "#{CLASSKIND} Test {\n" +
+                "   #{ACCESS[0]} #{STATIC[0]} #{DEFAULT[0]} void test() { Runnable r = ()->{ target(); }; }\n" +
+                "   #{ACCESS[1]} #{STATIC[1]} #{DEFAULT[1]} void target() { }\n" +
+                "}\n";
+
+    @Override
+    public void doWork() throws IOException {
+        newCompilationTask()
+                .withSourceFromTemplate(source_template)
+                .withOption("--release").withOption("14")
+                .generate(this::verifyBytecode);
+    }
+
+    void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) {
+        if (res.hasErrors()) {
+            boolean errorExpected = !mk1.isOK() || !mk2.isOK();
+            errorExpected |= mk1.isStatic() && !mk2.isStatic();
+
+            if (!errorExpected) {
+                fail("Diags found when compiling instance; " + res.compilationInfo());
+            }
+            return;
+        }
+        try (InputStream is = res.get().iterator().next().openInputStream()) {
+            ClassFile cf = ClassFile.read(is);
+            Method testMethod = null;
+            for (Method m : cf.methods) {
+                if (m.getName(cf.constant_pool).equals("test")) {
+                    testMethod = m;
+                    break;
+                }
+            }
+            if (testMethod == null) {
+                fail("Test method not found");
+                return;
+            }
+            Code_attribute ea =
+                    (Code_attribute)testMethod.attributes.get(Attribute.Code);
+            if (testMethod == null) {
+                fail("Code attribute for test() method not found");
+                return;
+            }
+
+            int bsmIdx = -1;
+
+            for (Instruction i : ea.getInstructions()) {
+                if (i.getMnemonic().equals("invokedynamic")) {
+                    CONSTANT_InvokeDynamic_info indyInfo =
+                         (CONSTANT_InvokeDynamic_info)cf
+                            .constant_pool.get(i.getShort(1));
+                    bsmIdx = indyInfo.bootstrap_method_attr_index;
+                    if (!indyInfo.getNameAndTypeInfo().getType().equals(makeIndyType())) {
+                        fail("type mismatch for CONSTANT_InvokeDynamic_info " +
+                                res.compilationInfo() + "\n" + indyInfo.getNameAndTypeInfo().getType() +
+                                "\n" + makeIndyType());
+                        return;
+                    }
+                }
+            }
+            if (bsmIdx == -1) {
+                fail("Missing invokedynamic in generated code");
+                return;
+            }
+
+            BootstrapMethods_attribute bsm_attr =
+                    (BootstrapMethods_attribute)cf
+                    .getAttribute(Attribute.BootstrapMethods);
+            if (bsm_attr.bootstrap_method_specifiers.length != 1) {
+                fail("Bad number of method specifiers " +
+                        "in BootstrapMethods attribute");
+                return;
+            }
+            BootstrapMethods_attribute.BootstrapMethodSpecifier bsm_spec =
+                    bsm_attr.bootstrap_method_specifiers[0];
+
+            if (bsm_spec.bootstrap_arguments.length != MF_ARITY) {
+                fail("Bad number of static invokedynamic args " +
+                        "in BootstrapMethod attribute");
+                return;
+            }
+
+            CONSTANT_MethodHandle_info mh =
+                    (CONSTANT_MethodHandle_info)cf.constant_pool.get(bsm_spec.bootstrap_arguments[1]);
+
+            boolean kindOK;
+            switch (mh.reference_kind) {
+                case REF_invokeStatic: kindOK = mk2.isStatic(); break;
+                case REF_invokeSpecial: kindOK = !mk2.isStatic(); break;
+                case REF_invokeInterface: kindOK = mk2.inInterface(); break;
+                default:
+                    kindOK = false;
+            }
+
+            if (!kindOK) {
+                fail("Bad invoke kind in implementation method handle");
+                return;
+            }
+
+            if (!mh.getCPRefInfo().getNameAndTypeInfo().getType().toString().equals(MH_SIG)) {
+                fail("Type mismatch in implementation method handle");
+                return;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail("error reading " + res.compilationInfo() + ": " + e);
+        }
+    }
+
+    String makeIndyType() {
+        StringBuilder buf = new StringBuilder();
+        buf.append("(");
+        if (!mk2.isStatic()) {
+            buf.append("LTest;");
+        }
+        buf.append(")Ljava/lang/Runnable;");
+        return buf.toString();
+    }
+}