You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/06/25 05:33:33 UTC

[groovy] branch master updated: GROOVY-10145: Support JDK16 (#1598)

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new d4f1636  GROOVY-10145: Support JDK16 (#1598)
d4f1636 is described below

commit d4f1636dec61f5236f1f0532a1d3bdab45446501
Author: Daniel.Sun <su...@apache.org>
AuthorDate: Fri Jun 25 13:33:27 2021 +0800

    GROOVY-10145: Support JDK16 (#1598)
    
    * GROOVY-10145: Support JDK16
    
    * Bump JVM compatibility version to 11
    
    * Trivial refactoring: move `isSealed` to `ReflectionUtils`
---
 .travis.yml                                        |   6 +-
 build.gradle                                       |   2 +-
 .../src/main/groovy/org.apache.groovy-core.gradle  |  14 ++-
 .../main/groovy/org.apache.groovy-tested.gradle    |   7 ++
 src/main/java/groovy/lang/MetaClassImpl.java       | 139 +++++++++++++++++++++
 .../org/apache/groovy/lang/GroovyObjectHelper.java | 127 +++++++++++++++++++
 .../org/codehaus/groovy/classgen/Verifier.java     |  29 +++++
 .../groovy/control/CompilerConfiguration.java      |   2 +-
 .../groovy/reflection/ReflectionUtils.java         |  63 +++++++---
 .../org/codehaus/groovy/runtime/ArrayUtil.java     |   7 +-
 .../groovy/runtime/ProxyGeneratorAdapter.java      |   4 +-
 .../codehaus/groovy/vmplugin/VMPluginFactory.java  |   1 +
 .../org/codehaus/groovy/vmplugin/v10/Java10.java   |   5 +
 .../org/codehaus/groovy/vmplugin/v16/Java16.java   | 116 +++++++++++++++++
 .../vmplugin/v16/PluginDefaultGroovyMethods.java}  |  24 ++--
 .../vmplugin/v16/ProxyDefaultMethodHandle.java     |  38 ++++++
 .../org/codehaus/groovy/vmplugin/v8/Java8.java     |  23 +++-
 .../org/codehaus/groovy/vmplugin/v9/Java9.java     |  10 +-
 src/test/groovy/IllegalAccessTests.groovy          |   4 +-
 src/test/groovy/bugs/Groovy8815.groovy             |   7 ++
 .../groovy/classgen/TransientMetaClassTest.groovy  |   4 +-
 .../codehaus/groovy/classgen/VerifierTest.groovy   |  72 +++++++++++
 .../codehaus/groovy/reflection/SecurityTest.java   |   3 +
 .../runtime/typehandling/NumberMathTest.groovy     |   2 +-
 .../TransformsAndCustomClassLoadersTest.groovy     |   5 +-
 25 files changed, 662 insertions(+), 52 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 14edd60..f6a28cd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,6 +25,8 @@ install:
 
 matrix:
   include:
+    - env: BC='indy' FEATURE='16' TARGET_JAVA_HOME="/home/travis/openjdk$FEATURE" LICENSE='GPL'
+      jdk: openjdk11
     - env: BC='indy'
       jdk: openjdk15
     - env: BC='indy'
@@ -50,7 +52,9 @@ before_script:
 
 script:
   - ./gradlew -version
-  - if [ "$BC" == "indy" ]; then travis_wait 60 ./gradlew test; elif [ "$BC" == "sonar" ]; then travis_wait 60 ./gradlew sonarqube -Dsonar.login=$SONAR_LOGIN -Pcoverage=true; fi
+  - if [ "$TARGET_JAVA_HOME" != "" ]; then wget https://github.com/sormuras/bach/raw/master/install-jdk.sh -P /tmp/ && chmod 755 /tmp/install-jdk.sh; fi
+  - if [ "$TARGET_JAVA_HOME" != "" ]; then /tmp/install-jdk.sh --target "$TARGET_JAVA_HOME" --workspace "/home/travis/.cache/install-jdk" --feature "$FEATURE" --license "$LICENSE" --cacerts; fi
+  - if [ "$BC" == "indy" ]; then travis_wait 60 ./gradlew test -Ptarget.java.home=$TARGET_JAVA_HOME; elif [ "$BC" == "sonar" ]; then travis_wait 60 ./gradlew sonarqube -Dsonar.login=$SONAR_LOGIN -Pcoverage=true; fi
 
 # As recommended in:
 # https://docs.travis-ci.com/user/languages/java/#Caching
diff --git a/build.gradle b/build.gradle
index 28a08ff..8cc55d9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -229,7 +229,7 @@ tasks.named('test') {
     dependsOn testExtensionModuleJar
 }
 
-if (!JavaVersion.current().java10Compatible) {
+if (!JavaVersion.current().java11Compatible) {
     logger.lifecycle '''
 **************************************** WARNING ********************************************
 ******   You are running the build with an older JDK. NEVER try to release with 1.8.   ******
diff --git a/buildSrc/src/main/groovy/org.apache.groovy-core.gradle b/buildSrc/src/main/groovy/org.apache.groovy-core.gradle
index e937221..4638c77 100644
--- a/buildSrc/src/main/groovy/org.apache.groovy-core.gradle
+++ b/buildSrc/src/main/groovy/org.apache.groovy-core.gradle
@@ -50,6 +50,10 @@ sourceSets {
                 exclude '**/v10/*'
                 exclude '**/vm10/*'
             }
+            if (!JavaVersion.current().isJava11Compatible()) {
+                exclude '**/v16/*'
+                exclude '**/vm16/*'
+            }
         }
         groovy {
             if (!JavaVersion.current().isJava9Compatible()) {
@@ -60,6 +64,10 @@ sourceSets {
                 exclude '**/v10/*'
                 exclude '**/vm10/*'
             }
+            if (!JavaVersion.current().isJava11Compatible()) {
+                exclude '**/v16/*'
+                exclude '**/vm16/*'
+            }
         }
         antlr {
             srcDirs = ['src/antlr']
@@ -79,6 +87,10 @@ sourceSets {
                 exclude '**/v10/*'
                 exclude '**/vm10/*'
             }
+            if (!JavaVersion.current().isJava11Compatible()) {
+                exclude '**/v16/*'
+                exclude '**/vm16/*'
+            }
         }
         resources {
             srcDirs += ['src/test-resources']
@@ -165,7 +177,7 @@ tasks.named('compileTestGroovy') {
 
 tasks.register('checkCompatibility') {
     doLast {
-        assert JavaVersion.current().java9Compatible
+        assert JavaVersion.current().java11Compatible
     }
 }
 
diff --git a/buildSrc/src/main/groovy/org.apache.groovy-tested.gradle b/buildSrc/src/main/groovy/org.apache.groovy-tested.gradle
index e2cb9de..254b9e4 100644
--- a/buildSrc/src/main/groovy/org.apache.groovy-tested.gradle
+++ b/buildSrc/src/main/groovy/org.apache.groovy-tested.gradle
@@ -55,6 +55,13 @@ tasks.withType(Test).configureEach {
             'gradle.home': gradle.gradleHomeDir, // this is needed by the security.policy
             'user.home': temporaryDir.absolutePath // make sure tests are isolated from real user home or tests using Grape may fail
 
+    if (rootProject.hasProperty('target.java.home')) {
+        String targetJavaHome = rootProject.property('target.java.home')
+        if (targetJavaHome?.trim()) {
+            executable = "${targetJavaHome}/bin/java"
+        }
+    }
+
     forkEvery = 50
     maxParallelForks = sharedConfiguration.isRunningOnCI ? 1 : Runtime.runtime.availableProcessors().intdiv(2) ?: 1
     scanForTestClasses = true
diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 2046b3e..e8068f2 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -19,6 +19,7 @@
 package groovy.lang;
 
 import org.apache.groovy.internal.util.UncheckedThrow;
+import org.apache.groovy.lang.GroovyObjectHelper;
 import org.apache.groovy.util.BeanUtils;
 import org.apache.groovy.util.SystemUtil;
 import org.codehaus.groovy.GroovyBugError;
@@ -35,8 +36,10 @@ import org.codehaus.groovy.reflection.ClassInfo;
 import org.codehaus.groovy.reflection.GeneratedMetaMethod;
 import org.codehaus.groovy.reflection.ParameterTypes;
 import org.codehaus.groovy.reflection.ReflectionCache;
+import org.codehaus.groovy.reflection.ReflectionUtils;
 import org.codehaus.groovy.reflection.android.AndroidSupport;
 import org.codehaus.groovy.runtime.ArrayTypeUtils;
+import org.codehaus.groovy.runtime.ArrayUtil;
 import org.codehaus.groovy.runtime.ConvertedClosure;
 import org.codehaus.groovy.runtime.CurriedClosure;
 import org.codehaus.groovy.runtime.DefaultGroovyMethods;
@@ -85,6 +88,8 @@ import java.beans.BeanInfo;
 import java.beans.EventSetDescriptor;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
@@ -106,6 +111,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -115,6 +121,7 @@ import static java.lang.Character.isUpperCase;
 import static org.apache.groovy.util.Arrays.concat;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.inSamePackage;
 import static org.codehaus.groovy.reflection.ReflectionCache.isAssignableFrom;
+import static org.codehaus.groovy.reflection.ReflectionUtils.parameterTypeMatches;
 
 /**
  * Allows methods to be dynamically added to existing classes at runtime
@@ -1127,6 +1134,138 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
      */
     @Override
     public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
+        try {
+            return doInvokeMethod(sender, object, methodName, originalArguments, isCallToSuper, fromInsideClass);
+        } catch (MissingMethodException mme) {
+            return doInvokeMethodFallback(sender, object, methodName, originalArguments, isCallToSuper, mme);
+        }
+    }
+
+    private static class MethodHandleHolder {
+        private static final MethodHandle CLONE_ARRAY_METHOD_HANDLE;
+        static {
+            Optional<Method> methodOptional = Arrays.stream(ArrayUtil.class.getDeclaredMethods()).filter(m -> "cloneArray".equals(m.getName())).findFirst();
+            if (!methodOptional.isPresent()) {
+                throw new GroovyBugError("Failed to find `cloneArray` method in class `" + ArrayUtil.class.getName() + "`");
+            }
+            Method cloneArrayMethod = methodOptional.get();
+
+            try {
+                CLONE_ARRAY_METHOD_HANDLE = MethodHandles.lookup().in(ArrayUtil.class).unreflect(cloneArrayMethod);
+            } catch (IllegalAccessException e) {
+                throw new GroovyBugError("Failed to create method handle for " + cloneArrayMethod);
+            }
+        }
+        private MethodHandleHolder() {}
+    }
+
+    private static final ClassValue<Map<String, Set<Method>>> SPECIAL_METHODS_MAP = new ClassValue<Map<String, Set<Method>>>() {
+        @Override
+        protected Map<String, Set<Method>> computeValue(Class<?> type) {
+            return new ConcurrentHashMap<>(4);
+        }
+    };
+
+    private Object doInvokeMethodFallback(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, MissingMethodException mme) {
+        MethodHandles.Lookup lookup = null;
+        if (object instanceof GroovyObject) {
+            Optional<MethodHandles.Lookup> lookupOptional = GroovyObjectHelper.lookup((GroovyObject) object);
+            if (!lookupOptional.isPresent()) throw mme;
+            lookup = lookupOptional.get();
+        }
+
+        final Class<?> receiverClass = object.getClass();
+        if (isCallToSuper) {
+            if (null == lookup) throw mme;
+            Class[] argTypes = MetaClassHelper.castArgumentsToClassArray(originalArguments);
+            Method superMethod = findMethod(sender, methodName, argTypes);
+            if (null == superMethod) throw mme;
+            MethodHandle superMethodHandle;
+            try {
+                superMethodHandle = lookup.unreflectSpecial(superMethod, receiverClass);
+            } catch (IllegalAccessException e) {
+                throw mme;
+            }
+            cacheMethod(sender, superMethod);
+            try {
+                return superMethodHandle.bindTo(object).invokeWithArguments(originalArguments);
+            } catch (Throwable t) {
+                throw new GroovyRuntimeException(t);
+            }
+        } else {
+            if (receiverClass.isArray()) {
+                if ("clone".equals(methodName) && 0 == originalArguments.length) {
+                    try {
+                        Object[] array = (Object[]) object;
+                        Object[] result = (Object[]) MethodHandleHolder.CLONE_ARRAY_METHOD_HANDLE.invokeExact(array);
+                        return result;
+                    } catch (Throwable t) {
+                        throw new GroovyRuntimeException(t);
+                    }
+                }
+                throw mme;
+            }
+
+            if (null == lookup) {
+                try {
+                    lookup = MethodHandles.lookup().in(receiverClass);
+                } catch (IllegalArgumentException e) {
+                    throw mme;
+                }
+            }
+            Class[] argTypes = MetaClassHelper.castArgumentsToClassArray(originalArguments);
+            Method thisMethod = findMethod(receiverClass, methodName, argTypes);
+            if (null == thisMethod) throw mme;
+            MethodHandle thisMethodHandle;
+            try {
+                thisMethodHandle = lookup.unreflect(thisMethod);
+            } catch (IllegalAccessException e) {
+                throw mme;
+            }
+            cacheMethod(receiverClass, thisMethod);
+            try {
+                return thisMethodHandle.bindTo(object).invokeWithArguments(originalArguments);
+            } catch (Throwable t) {
+                throw new GroovyRuntimeException(t);
+            }
+        }
+    }
+
+    private static void cacheMethod(Class clazz, Method method) {
+        SPECIAL_METHODS_MAP.get(clazz)
+                .computeIfAbsent(method.getName(), k -> Collections.newSetFromMap(new ConcurrentHashMap<>(2)))
+                .add(method);
+    }
+
+    private static Method findMethod(Class clazz, String methodName, Class[] argTypes) {
+        Set<Method> methods = SPECIAL_METHODS_MAP.get(clazz).get(methodName);
+
+        if (null != methods) {
+            for (Method method : methods) {
+                if (parameterTypeMatches(method.getParameterTypes(), argTypes)) {
+                    return method;
+                }
+            }
+        }
+
+        return doFindMethod(clazz, methodName, argTypes);
+    }
+
+    private static Method doFindMethod(Class clazz, String messageName, Class[] argTypes) {
+        for (Class<?> c = clazz; null != c; c = c.getSuperclass()) {
+            List<Method> declaredMethodList = ReflectionUtils.getDeclaredMethods(c, messageName, argTypes);
+            if (!declaredMethodList.isEmpty()) {
+                Method superMethod = declaredMethodList.get(0);
+                if (Modifier.isAbstract(superMethod.getModifiers())) {
+                    continue;
+                }
+                return superMethod;
+            }
+        }
+        return null;
+    }
+
+    private Object doInvokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
         checkInitalised();
         if (object == null) {
             throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
diff --git a/src/main/java/org/apache/groovy/lang/GroovyObjectHelper.java b/src/main/java/org/apache/groovy/lang/GroovyObjectHelper.java
new file mode 100644
index 0000000..de260d2
--- /dev/null
+++ b/src/main/java/org/apache/groovy/lang/GroovyObjectHelper.java
@@ -0,0 +1,127 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.groovy.lang;
+
+import groovy.lang.GroovyObject;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Helper for {@link groovy.lang.GroovyObject}
+ *
+ * @since 4.0.0
+ */
+public class GroovyObjectHelper {
+    /**
+     * Get the {@link Lookup} instance of the {@link GroovyObject} instance
+     *
+     * @param groovyObject the {@link GroovyObject} instance
+     * @return the {@link Lookup} instance
+     * @since 4.0.0
+     */
+    public static Optional<Lookup> lookup(GroovyObject groovyObject) {
+        AtomicReference<Lookup> lookupAtomicRef = LOOKUP_MAP.get(groovyObject.getClass());
+        Lookup lookup = lookupAtomicRef.get();
+        if (null != lookup) return Optional.of(lookup);
+
+        final Class<? extends GroovyObject> groovyObjectClass = groovyObject.getClass();
+        if (groovyObjectClass.isMemberClass() && Modifier.isStatic(groovyObjectClass.getModifiers())) {
+            List<Class<?>> classList = new ArrayList<>(3);
+            for (Class<?> clazz = groovyObjectClass; null != clazz; clazz = clazz.getEnclosingClass()) {
+                if (isNonStaticInnerClass(clazz)) {
+                    return Optional.empty();
+                }
+                classList.add(clazz);
+            }
+
+            Lookup caller = MethodHandles.lookup();
+            for (int i = classList.size() - 1; i >= 0; i--) {
+                Class<?> c = classList.get(i);
+                caller = doLookup(c, caller);
+                if (null == caller) {
+                    return Optional.empty();
+                }
+            }
+            lookup = caller;
+        } else {
+            lookup = doLookup(groovyObject);
+        }
+
+        if (null != lookup) lookupAtomicRef.set(lookup);
+
+        return Optional.ofNullable(lookup);
+    }
+
+    private static boolean isNonStaticInnerClass(Class<?> clazz) {
+        return clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers());
+    }
+
+    private static Lookup doLookup(GroovyObject groovyObject) {
+        MethodHandles.Lookup lookup;
+        try {
+            final Class<? extends GroovyObject> groovyObjectClass = groovyObject.getClass();
+            if (groovyObjectClass.isAnonymousClass() ||
+                    (isNonStaticInnerClass(groovyObjectClass))) {
+                lookup = (MethodHandles.Lookup) MethodHandles.lookup()
+                        .unreflect(findGetLookupMethod(groovyObjectClass))
+                        .bindTo(groovyObject)
+                        .invokeExact();
+            } else {
+                lookup = doLookup(groovyObjectClass);
+            }
+        } catch (Throwable e) {
+            lookup = null;
+        }
+
+        return lookup;
+    }
+
+    private static Lookup doLookup(Class<?> groovyObjectClass) {
+        return doLookup(groovyObjectClass, MethodHandles.lookup());
+    }
+
+    private static Lookup doLookup(Class<?> groovyObjectClass, Lookup caller) {
+        try {
+            return (Lookup) caller
+                    .unreflect(findGetLookupMethod(groovyObjectClass))
+                    .invokeExact();
+        } catch (Throwable throwable) {
+            return null;
+        }
+    }
+
+    private static Method findGetLookupMethod(Class<?> groovyObjectClass) throws NoSuchMethodException {
+        return groovyObjectClass.getDeclaredMethod("$getLookup");
+    }
+
+    private GroovyObjectHelper() {}
+    private static final ClassValue<AtomicReference<Lookup>> LOOKUP_MAP = new ClassValue<AtomicReference<Lookup>>() {
+        @Override
+        protected AtomicReference<Lookup> computeValue(Class<?> type) {
+            return new AtomicReference<>();
+        }
+    };
+}
diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 4293633..ec068b4 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -74,7 +74,9 @@ import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 import java.beans.Transient;
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -139,6 +141,7 @@ import static org.codehaus.groovy.ast.tools.PropertyNodeUtils.adjustPropertyModi
  *     <li>Property accessor methods</li>
  *     <li>Covariant methods</li>
  *     <li>Additional methods/constructors as needed for default parameters</li>
+ *     <li>{@link java.lang.invoke.MethodHandles.Lookup} getter</li>
  * </ul>
  */
 public class Verifier implements GroovyClassVisitor, Opcodes {
@@ -252,6 +255,8 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
 
             addFastPathHelperFieldsAndHelperMethod(node, classInternalName, knownSpecialCase);
             if (!knownSpecialCase) addGroovyObjectInterfaceAndMethods(node, classInternalName);
+
+            addGetLookupMethod(node);
         }
 
         addDefaultConstructor(node);
@@ -354,6 +359,30 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
         node.addConstructor(constructor);
     }
 
+    private void addGetLookupMethod(final ClassNode node) {
+        int modifiers = ACC_PUBLIC;
+        boolean nonStaticInnerClass = null != node.getOuterClass() && !Modifier.isStatic(node.getModifiers());
+        if (!nonStaticInnerClass) {
+            // static method cannot be declared in non-static inner class util Java 16
+            modifiers |= ACC_STATIC;
+        }
+
+        node.addSyntheticMethod(
+                "$getLookup",
+                modifiers,
+                ClassHelper.make(MethodHandles.Lookup.class),
+                Parameter.EMPTY_ARRAY,
+                ClassNode.EMPTY_ARRAY,
+                new BytecodeSequence(new BytecodeInstruction() {
+                    @Override
+                    public void visit(final MethodVisitor mv) {
+                        mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "lookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false);
+                        mv.visitInsn(ARETURN);
+                    }
+                })
+        );
+    }
+
     private void addStaticMetaClassField(final ClassNode node, final String classInternalName) {
         String _staticClassInfoFieldName = "$staticClassInfo";
         while (node.getDeclaredField(_staticClassInfoFieldName) != null)
diff --git a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
index d706387..40a7432 100644
--- a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
+++ b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java
@@ -134,7 +134,7 @@ public class CompilerConfiguration {
     /**
      * The ASM API version used when loading/parsing classes and generating proxy adapter classes.
      */
-    public static final int ASM_API_VERSION = Opcodes.ASM8;
+    public static final int ASM_API_VERSION = Opcodes.ASM9;
 
     /**
      * The default source encoding.
diff --git a/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java b/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
index e83b3d9..bdb23c2 100644
--- a/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
+++ b/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
@@ -22,6 +22,9 @@ import org.codehaus.groovy.classgen.asm.util.TypeUtil;
 import org.codehaus.groovy.vmplugin.VMPlugin;
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
 
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Array;
 import java.lang.reflect.Method;
@@ -145,35 +148,44 @@ public class ReflectionUtils {
     private static List<Method> doGetMethods(final Class<?> type, final String name, final Class<?>[] parameterTypes, final Function<? super Class<?>, ? extends Method[]> f) {
         List<Method> methodList = new LinkedList<>();
 
-        out:
         for (Method m : f.apply(type)) {
             if (!m.getName().equals(name)) {
                 continue;
             }
-
             Class<?>[] methodParameterTypes = m.getParameterTypes();
-            if (methodParameterTypes.length != parameterTypes.length) {
+            if (!parameterTypeMatches(methodParameterTypes, parameterTypes)) {
                 continue;
             }
 
-            for (int i = 0, n = methodParameterTypes.length; i < n; i += 1) {
-                Class<?> parameterType = TypeUtil.autoboxType(parameterTypes[i]);
-                if (null == parameterType) {
-                    continue out;
-                }
-
-                Class<?> methodParameterType = TypeUtil.autoboxType(methodParameterTypes[i]);
-                if (!methodParameterType.isAssignableFrom(parameterType)) {
-                    continue out;
-                }
-            }
-
             methodList.add(m);
         }
 
         return methodList;
     }
 
+    public static boolean parameterTypeMatches(final Class<?>[] parameterTypes, final Class<?>[] argTypes) {
+        if (parameterTypes.length != argTypes.length) {
+            return false;
+        }
+
+        for (int i = 0, n = parameterTypes.length; i < n; i += 1) {
+            Class<?> parameterType = parameterTypes[i];
+            if (Object.class == parameterType) continue;
+
+            Class<?> argType = argTypes[i];
+            if (null == argType) return false;
+            if (parameterType == argType) continue;
+
+            Class<?> boxedArgType = TypeUtil.autoboxType(argType);
+            Class<?> boxedParameterType = TypeUtil.autoboxType(parameterType);
+            if (!boxedParameterType.isAssignableFrom(boxedArgType)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     public static boolean checkCanSetAccessible(final AccessibleObject accessibleObject, final Class<?> caller) {
         return VM_PLUGIN.checkCanSetAccessible(accessibleObject, caller);
     }
@@ -226,6 +238,17 @@ public class ReflectionUtils {
         }
     }
 
+    public static boolean isSealed(Class<?> clazz) {
+        if (null == IS_SEALED_METHODHANDLE) return false;
+
+        boolean sealed = false;
+        try {
+            sealed = (boolean) IS_SEALED_METHODHANDLE.bindTo(clazz).invokeExact();
+        } catch (Throwable ignored) {
+        }
+        return sealed;
+    }
+
     private static boolean classShouldBeIgnored(final Class c, final Collection<String> extraIgnoredPackages) {
         return (c != null
                 && (c.isSynthetic()
@@ -240,4 +263,14 @@ public class ReflectionUtils {
             return super.getClassContext();
         }
     }
+
+    private static final MethodHandle IS_SEALED_METHODHANDLE;
+    static {
+        MethodHandle mh = null;
+        try {
+            mh = MethodHandles.lookup().findVirtual(Class.class, "isSealed", MethodType.methodType(boolean.class, new Class[0]));
+        } catch (NoSuchMethodException | IllegalAccessException ignored) {
+        }
+        IS_SEALED_METHODHANDLE = mh;
+    }
 }
diff --git a/src/main/java/org/codehaus/groovy/runtime/ArrayUtil.java b/src/main/java/org/codehaus/groovy/runtime/ArrayUtil.java
index aa8096f..d0697de 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ArrayUtil.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ArrayUtil.java
@@ -53,8 +53,11 @@ package org.codehaus.groovy.runtime;
 * absolutely no sense in normal Java. But it is not used in normal Java, but from the bytecode. 
 */ 
 public class ArrayUtil {
-    private static final Object[] EMPTY = new Object[0]
-            ;
+    private static final Object[] EMPTY = new Object[0];
+
+    public static <T> T[] cloneArray(T[] array) {
+        return array.clone();
+    }
 
     public static Object[] createArray() {
         return EMPTY;
diff --git a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
index 422ab8a..6ad943e 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ProxyGeneratorAdapter.java
@@ -60,6 +60,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 
+import static org.codehaus.groovy.reflection.ReflectionUtils.isSealed;
 import static org.objectweb.asm.Opcodes.AASTORE;
 import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
 import static org.objectweb.asm.Opcodes.ACC_FINAL;
@@ -210,7 +211,7 @@ public class ProxyGeneratorAdapter extends ClassVisitor {
         this.classList.add(superClass);
         if (generateDelegateField) {
             classList.add(delegateClass);
-            Collections.addAll(this.classList, delegateClass.getInterfaces());
+            Collections.addAll(this.classList, Arrays.stream(delegateClass.getInterfaces()).filter(c -> !isSealed(c)).toArray(Class[]::new));
         }
         if (interfaces != null) {
             Collections.addAll(this.classList, interfaces);
@@ -940,5 +941,4 @@ public class ProxyGeneratorAdapter extends ClassVisitor {
             return value;
         }
     }
-
 }
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/VMPluginFactory.java b/src/main/java/org/codehaus/groovy/vmplugin/VMPluginFactory.java
index 7e2d66b..d3ac172 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/VMPluginFactory.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/VMPluginFactory.java
@@ -39,6 +39,7 @@ public class VMPluginFactory {
     private static final Logger LOGGER = Logger.getLogger(VMPluginFactory.class.getName());
     private static final Map<BigDecimal, String> PLUGIN_MAP = Maps.of(
             // Note: list the vm plugin entries in *descending* order:
+            new BigDecimal("16"), "org.codehaus.groovy.vmplugin.v16.Java16",
             new BigDecimal("10"), "org.codehaus.groovy.vmplugin.v10.Java10",
             new BigDecimal("9"), "org.codehaus.groovy.vmplugin.v9.Java9",
             new BigDecimal("1.8"), "org.codehaus.groovy.vmplugin.v8.Java8"
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v10/Java10.java b/src/main/java/org/codehaus/groovy/vmplugin/v10/Java10.java
index e9f1bd9..94be0f1 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v10/Java10.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v10/Java10.java
@@ -42,4 +42,9 @@ public class Java10 extends Java9 {
     public Class<?>[] getPluginDefaultGroovyMethods() {
         return PLUGIN_DGM;
     }
+
+    @Override
+    public int getVersion() {
+        return 10;
+    }
 }
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java b/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java
new file mode 100644
index 0000000..5eef73d
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v16/Java16.java
@@ -0,0 +1,116 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.vmplugin.v16;
+
+import groovy.lang.GroovyRuntimeException;
+import org.codehaus.groovy.vmplugin.v10.Java10;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Additional Java 16 based functions will be added here as needed.
+ */
+public class Java16 extends Java10 {
+    private final Class<?>[] PLUGIN_DGM;
+
+    public Java16() {
+        super();
+        List<Class<?>> dgmClasses = new ArrayList<>();
+        Collections.addAll(dgmClasses, super.getPluginDefaultGroovyMethods());
+        dgmClasses.add(PluginDefaultGroovyMethods.class);
+        PLUGIN_DGM = dgmClasses.toArray(new Class<?>[0]);
+    }
+
+    @Override
+    protected MethodHandles.Lookup newLookup(final Class<?> declaringClass) {
+        return of(declaringClass);
+    }
+
+    public static MethodHandles.Lookup of(final Class<?> declaringClass) {
+        try {
+            final Method privateLookup = getPrivateLookup();
+            if (privateLookup != null) {
+                MethodHandles.Lookup caller = MethodHandles.lookup();
+                Class<?> callerClass = caller.lookupClass();
+                Module callerModule = callerClass.getModule();
+                Module targetModule = declaringClass.getModule();
+                if (targetModule != callerModule) {
+                    if (targetModule.isNamed()) {
+                        String pn = declaringClass.getPackageName();
+                        if (!targetModule.isOpen(pn, callerModule)) {
+                            return MethodHandles.lookup().in(declaringClass);
+                        }
+                    }
+                }
+
+                return (MethodHandles.Lookup) privateLookup.invoke(null, declaringClass, caller);
+            }
+            return getLookupConstructor().newInstance(declaringClass, MethodHandles.Lookup.PRIVATE).in(declaringClass);
+        } catch (final IllegalAccessException | InstantiationException e) {
+            throw new IllegalArgumentException(e);
+        } catch (final InvocationTargetException e) {
+            throw new GroovyRuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object getInvokeSpecialHandle(Method method, Object receiver) {
+        try {
+            final Class<?> receiverType = receiver.getClass();
+            if (method.isDefault() && Proxy.isProxyClass(receiverType)) {
+                return new ProxyDefaultMethodHandle((Proxy) receiver, method);
+            }
+
+            MethodHandles.Lookup lookup = getLookup(receiver);
+            if (0 != (MethodHandles.Lookup.PRIVATE & lookup.lookupModes())) {
+                return lookup.unreflectSpecial(method, receiverType).bindTo(receiver);
+            }
+            return lookup.unreflect(method).bindTo(receiver);
+        } catch (ReflectiveOperationException e) {
+            return new GroovyRuntimeException(e);
+        }
+    }
+
+    @Override
+    public Object invokeHandle(Object handle, Object[] args) throws Throwable {
+        if (handle instanceof ProxyDefaultMethodHandle) {
+            return ((ProxyDefaultMethodHandle) handle).invokeWithArguments(args);
+        }
+        if (handle instanceof Throwable) throw (Throwable) handle;
+        MethodHandle mh = (MethodHandle) handle;
+        return mh.invokeWithArguments(args);
+    }
+
+    @Override
+    public Class<?>[] getPluginDefaultGroovyMethods() {
+        return PLUGIN_DGM;
+    }
+
+    @Override
+    public int getVersion() {
+        return 16;
+    }
+}
diff --git a/src/test/org/codehaus/groovy/classgen/TransientMetaClassTest.groovy b/src/main/java/org/codehaus/groovy/vmplugin/v16/PluginDefaultGroovyMethods.java
similarity index 54%
copy from src/test/org/codehaus/groovy/classgen/TransientMetaClassTest.groovy
copy to src/main/java/org/codehaus/groovy/vmplugin/v16/PluginDefaultGroovyMethods.java
index 18265cf..232ef8b 100644
--- a/src/test/org/codehaus/groovy/classgen/TransientMetaClassTest.groovy
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v16/PluginDefaultGroovyMethods.java
@@ -16,22 +16,14 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.codehaus.groovy.classgen
+package org.codehaus.groovy.vmplugin.v16;
 
-import groovy.test.GroovyTestCase
-
-class TransientMetaClassTest extends GroovyTestCase {
-    // GROOVY-8284
-    void testGetMetaClassMethodIsDeemedTransient() {
-        assertScript '''
-            def gcl = new GroovyClassLoader()
-            def fooClass = gcl.parseClass('class Foo {}')
-            def fooInfo = java.beans.Introspector.getBeanInfo(fooClass)
-            assert fooInfo.propertyDescriptors.find{ it.name == 'metaClass' }.transient
+/**
+ * Defines new Groovy methods which appear on normal JDK 16
+ * classes inside the Groovy environment.
+ *
+ * @since 4.0.0
+ */
+public class PluginDefaultGroovyMethods {
 
-            Closure c = { -> }
-            def cInfo = java.beans.Introspector.getBeanInfo(c.getClass())
-            assert cInfo.propertyDescriptors.find{ it.name == 'metaClass' }.transient
-        '''
-    }
 }
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v16/ProxyDefaultMethodHandle.java b/src/main/java/org/codehaus/groovy/vmplugin/v16/ProxyDefaultMethodHandle.java
new file mode 100644
index 0000000..b1fbd5d
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v16/ProxyDefaultMethodHandle.java
@@ -0,0 +1,38 @@
+package org.codehaus.groovy.vmplugin.v16;
+
+import org.codehaus.groovy.GroovyBugError;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+class ProxyDefaultMethodHandle {
+    private static final MethodHandle INVOKE_DEFAULT_METHOD_HANDLE;
+    static {
+        try {
+            // `invokeDefault` is JDK 16+ API, but we still build Groovy with JDK11,
+            // so use method handle instead of invoking the method directly
+            INVOKE_DEFAULT_METHOD_HANDLE = MethodHandles.lookup().findStatic(
+                                                InvocationHandler.class, "invokeDefault",
+                                                MethodType.methodType(Object.class, Object.class, Method.class, Object[].class));
+        } catch (NoSuchMethodException | IllegalAccessException e) {
+            throw new GroovyBugError(e);
+        }
+    }
+
+    private final Proxy proxy;
+    private final Method method;
+
+    ProxyDefaultMethodHandle(Proxy proxy, Method method) {
+        this.proxy = proxy;
+        this.method = method;
+    }
+
+    Object invokeWithArguments(Object... arguments) throws Throwable {
+        Object proxy = this.proxy;
+        return INVOKE_DEFAULT_METHOD_HANDLE.invokeExact(proxy, method, arguments);
+    }
+}
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
index 3f54df0..ff6da29 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
@@ -18,9 +18,11 @@
  */
 package org.codehaus.groovy.vmplugin.v8;
 
+import groovy.lang.GroovyObject;
 import groovy.lang.GroovyRuntimeException;
 import groovy.lang.MetaClass;
 import groovy.lang.MetaMethod;
+import org.apache.groovy.lang.GroovyObjectHelper;
 import org.codehaus.groovy.GroovyBugError;
 import org.codehaus.groovy.ast.AnnotatedNode;
 import org.codehaus.groovy.ast.AnnotationNode;
@@ -68,6 +70,7 @@ import java.security.AccessController;
 import java.security.Permission;
 import java.security.PrivilegedAction;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Java 8 based functions.
@@ -623,11 +626,27 @@ public class Java8 implements VMPlugin {
         IndyInterface.invalidateSwitchPoints();
     }
 
+    protected MethodHandles.Lookup getLookup(Object receiver) {
+        MethodHandles.Lookup lookup = null;
+        if (receiver instanceof GroovyObject) {
+            Optional<MethodHandles.Lookup> lookupOptional = GroovyObjectHelper.lookup((GroovyObject) receiver);
+            if (lookupOptional.isPresent()) {
+                lookup = lookupOptional.get();
+            }
+        }
+        if (null == lookup) {
+            final Class<?> receiverType = receiver.getClass();
+            lookup = newLookup(receiverType);
+        }
+        return lookup;
+    }
+
     @Override
     public Object getInvokeSpecialHandle(Method method, Object receiver) {
-        final Class<?> receiverType = receiver.getClass();
         try {
-            return newLookup(receiverType).unreflectSpecial(method, receiverType).bindTo(receiver);
+            MethodHandles.Lookup lookup = getLookup(receiver);
+            Class<?> receiverType = receiver.getClass();
+            return lookup.unreflectSpecial(method, receiverType).bindTo(receiver);
         } catch (ReflectiveOperationException e) {
             return getInvokeSpecialHandleFallback(method, receiver);
         }
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java b/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java
index 1600070..bb050d8 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v9/Java9.java
@@ -169,11 +169,11 @@ public class Java9 extends Java8 {
         return of(declaringClass);
     }
 
-    private static Constructor<MethodHandles.Lookup> getLookupConstructor() {
+    protected static Constructor<MethodHandles.Lookup> getLookupConstructor() {
         return LookupHolder.LOOKUP_Constructor;
     }
 
-    private static Method getPrivateLookup() {
+    protected static Method getPrivateLookup() {
         return LookupHolder.PRIVATE_LOOKUP;
     }
 
@@ -354,8 +354,8 @@ public class Java9 extends Java8 {
     private static List<CachedMethod> getMetaMethods(CachedMethod metaMethod, Class<?>[] params, Class<?> sc, boolean declared) {
         String metaMethodName = metaMethod.getName();
         List<Method> optionalMethodList = declared
-                                            ? ReflectionUtils.getDeclaredMethods(sc, metaMethodName, params)
-                                            : ReflectionUtils.getMethods(sc, metaMethodName, params);
+                ? ReflectionUtils.getDeclaredMethods(sc, metaMethodName, params)
+                : ReflectionUtils.getMethods(sc, metaMethodName, params);
         return optionalMethodList.stream().map(CachedMethod::new).collect(Collectors.toList());
     }
 
@@ -1813,4 +1813,4 @@ public class Java9 extends Java8 {
                 "sun.util.xml"
         };
     }
-}
+}
\ No newline at end of file
diff --git a/src/test/groovy/IllegalAccessTests.groovy b/src/test/groovy/IllegalAccessTests.groovy
index f0d1192..ceb5fca 100644
--- a/src/test/groovy/IllegalAccessTests.groovy
+++ b/src/test/groovy/IllegalAccessTests.groovy
@@ -30,14 +30,14 @@ import static org.junit.Assume.assumeTrue
  * Java via means such as reflection.
  *
  * In JDK versions < 9, Groovy supports permissive access and no warnings are given by the JDK.
- * In JDK versions >= 9, Groovy supports permissive access but the JDK gives illegal access warnings.
+ * In 9 <= JDK versions < 16, Groovy supports permissive access but the JDK gives illegal access warnings.
  * At some point, the JDK may further restrict permissive access and Groovy's support for this feature may be limited.
  */
 final class IllegalAccessTests {
 
     @Before
     void setUp() {
-        assumeTrue(isAtLeastJdk('9.0') && !Boolean.getBoolean('groovy.force.illegal.access'))
+        assumeTrue(!isAtLeastJdk('16.0') && isAtLeastJdk('9.0') && !Boolean.getBoolean('groovy.force.illegal.access'))
     }
 
     @Test
diff --git a/src/test/groovy/bugs/Groovy8815.groovy b/src/test/groovy/bugs/Groovy8815.groovy
index 8a8c9f4..e520b8c 100644
--- a/src/test/groovy/bugs/Groovy8815.groovy
+++ b/src/test/groovy/bugs/Groovy8815.groovy
@@ -22,10 +22,17 @@ import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit
 import org.junit.Test
 
+import static groovy.test.GroovyAssert.isAtLeastJdk
+import static org.junit.Assume.assumeTrue
+
 final class Groovy8815 {
 
     @Test
     void testGenerics() {
+        // illegal access to `java.lang.reflect.Method.getGenericSignature`, i.e. `method.genericSignature` in the test
+        // but illegal access is not allowed by default since JDK 16
+        assumeTrue(!isAtLeastJdk('16.0'))
+
         def config = new CompilerConfiguration(
             targetDirectory: File.createTempDir(),
             jointCompilationOptions: [memStub: true]
diff --git a/src/test/org/codehaus/groovy/classgen/TransientMetaClassTest.groovy b/src/test/org/codehaus/groovy/classgen/TransientMetaClassTest.groovy
index 18265cf..cc14bfa 100644
--- a/src/test/org/codehaus/groovy/classgen/TransientMetaClassTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/TransientMetaClassTest.groovy
@@ -27,11 +27,11 @@ class TransientMetaClassTest extends GroovyTestCase {
             def gcl = new GroovyClassLoader()
             def fooClass = gcl.parseClass('class Foo {}')
             def fooInfo = java.beans.Introspector.getBeanInfo(fooClass)
-            assert fooInfo.propertyDescriptors.find{ it.name == 'metaClass' }.transient
+            assert fooInfo.propertyDescriptors.find{ it.name == 'metaClass' }.getValue('transient')
 
             Closure c = { -> }
             def cInfo = java.beans.Introspector.getBeanInfo(c.getClass())
-            assert cInfo.propertyDescriptors.find{ it.name == 'metaClass' }.transient
+            assert cInfo.propertyDescriptors.find{ it.name == 'metaClass' }.getValue('transient')
         '''
     }
 }
diff --git a/src/test/org/codehaus/groovy/classgen/VerifierTest.groovy b/src/test/org/codehaus/groovy/classgen/VerifierTest.groovy
new file mode 100644
index 0000000..942911d
--- /dev/null
+++ b/src/test/org/codehaus/groovy/classgen/VerifierTest.groovy
@@ -0,0 +1,72 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.classgen
+
+
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+class VerifierTest {
+    @Test
+    void testGenGetLookupMethod() {
+        assertScript '''
+            import java.lang.reflect.Modifier
+            class Outer {
+                static class StaticInner {
+                    static class StaticInnest {}
+                }
+                class Inner {}
+            }
+            
+            {
+                def getLookupMethod = Outer.getDeclaredMethods().grep(m -> '$getLookup' == m.name)[0]
+                def modifiers = getLookupMethod.getModifiers()
+                assert Modifier.isPublic(modifiers)
+                assert Modifier.isStatic(modifiers)
+                assert getLookupMethod.synthetic
+            }
+            
+            {
+                def getLookupMethod = Outer.StaticInner.getDeclaredMethods().grep(m -> '$getLookup' == m.name)[0]
+                def modifiers = getLookupMethod.getModifiers()
+                assert Modifier.isPublic(modifiers)
+                assert Modifier.isStatic(modifiers)
+                assert getLookupMethod.synthetic
+            }
+            
+            {
+                def getLookupMethod = Outer.StaticInner.StaticInnest.getDeclaredMethods().grep(m -> '$getLookup' == m.name)[0]
+                def modifiers = getLookupMethod.getModifiers()
+                assert Modifier.isPublic(modifiers)
+                assert Modifier.isStatic(modifiers)
+                assert getLookupMethod.synthetic
+            }
+            
+            {
+                def getLookupMethod = Outer.Inner.getDeclaredMethods().grep(m -> '$getLookup' == m.name)[0]
+                def modifiers = getLookupMethod.getModifiers()
+                assert Modifier.isPublic(modifiers)
+                assert !Modifier.isStatic(modifiers)
+                assert getLookupMethod.synthetic
+            }
+        '''
+    }
+
+}
diff --git a/src/test/org/codehaus/groovy/reflection/SecurityTest.java b/src/test/org/codehaus/groovy/reflection/SecurityTest.java
index d466847..5f24859 100644
--- a/src/test/org/codehaus/groovy/reflection/SecurityTest.java
+++ b/src/test/org/codehaus/groovy/reflection/SecurityTest.java
@@ -241,6 +241,9 @@ public class SecurityTest extends GroovyTestCase {
 
 
     public void testChecksCreateClassLoaderPermissionForClassLoaderProtectedMethodAccess() throws Exception {
+        // Illegal access to java.lang.ClassLoader.defineClass(java.lang.String,java.nio.ByteBuffer,java.security.ProtectionDomain)
+        if (isAtLeastJdk("16.0")) return;
+
         cachedMethodUnderTest = createCachedMethod(ClassLoader.class, "defineClass", new Class[]{String.class, ByteBuffer.class, ProtectionDomain.class});
         forbidden = new Permissions();
         forbidden.add(new RuntimePermission("createClassLoader"));
diff --git a/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy b/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy
index dbf0d6a..2c4a35f 100644
--- a/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy
+++ b/src/test/org/codehaus/groovy/runtime/typehandling/NumberMathTest.groovy
@@ -109,7 +109,7 @@ class NumberMathTest extends GroovyTestCase {
         def BD1 = new BigDecimal("1.0")
         def BD2 = new BigDecimal("2.0")
         def BD20 = new BigDecimal("2.00")
-        def BD100 = new BigDecimal(new BigInteger(1), -2) // 100
+        def BD100 = new BigDecimal(new BigInteger("1"), -2) // 100
 
         assert I1 / I2 instanceof BigDecimal
         assert I1 / I2 == new BigDecimal("0.5")
diff --git a/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy b/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
index c04c371..4676486 100644
--- a/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/classloading/TransformsAndCustomClassLoadersTest.groovy
@@ -137,7 +137,10 @@ class TransformsAndCustomClassLoadersTest extends GroovyTestCase {
             }
             if (name.startsWith("java.") || name.startsWith("groovy.")
                     || name.startsWith("org.codehaus.groovy.") || name.startsWith("org.apache.groovy.")) {
-                return getClass().classLoader.loadClass(name, resolve)
+                def loader = getClass().classLoader
+                def result = loader.loadClass(name)
+                if (resolve) loader.resolveClass(result)
+                return result
             }
             throw new ClassNotFoundException(name)
         }