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/19 14:26:55 UTC

[groovy] branch danielsun/tweak-build updated (998bb22 -> 4eb356b)

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

sunlan pushed a change to branch danielsun/tweak-build
in repository https://gitbox.apache.org/repos/asf/groovy.git.


 discard 998bb22  GROOVY-10145: Support JDK16
     new 4eb356b  GROOVY-10145: Support JDK16

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (998bb22)
            \
             N -- N -- N   refs/heads/danielsun/tweak-build (4eb356b)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/main/java/org/codehaus/groovy/classgen/Verifier.java | 1 -
 1 file changed, 1 deletion(-)

[groovy] 01/01: GROOVY-10145: Support JDK16

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch danielsun/tweak-build
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 4eb356bb2aa3580fc3f7f12a8de9e375133093f4
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Jun 19 21:38:11 2021 +0800

    GROOVY-10145: Support JDK16
---
 .travis.yml                                        |   6 +-
 .../src/main/groovy/org.apache.groovy-core.gradle  |  12 ++
 .../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     |  39 +++++-
 .../groovy/reflection/ReflectionUtils.java         |  39 +++---
 .../org/codehaus/groovy/runtime/ArrayUtil.java     |   7 +-
 .../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   |  59 +++++++++
 .../codehaus/groovy/reflection/SecurityTest.java   |   3 +
 .../runtime/typehandling/NumberMathTest.groovy     |   2 +-
 .../TransformsAndCustomClassLoadersTest.groovy     |   5 +-
 22 files changed, 625 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/buildSrc/src/main/groovy/org.apache.groovy-core.gradle b/buildSrc/src/main/groovy/org.apache.groovy-core.gradle
index e937221..fb52abf 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']
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..1aebcf3 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 MethodHolder {
+        private static final MethodHandle CLONE_ARRAY_METHODHANDLE;
+        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 CLONE_ARRAY_METHOD = methodOptional.get();
+
+            try {
+                CLONE_ARRAY_METHODHANDLE = MethodHandles.lookup().in(ArrayUtil.class).unreflect(CLONE_ARRAY_METHOD);
+            } catch (IllegalAccessException e) {
+                throw new GroovyBugError("Failed to create method handle for " + CLONE_ARRAY_METHOD);
+            }
+        }
+        private MethodHolder() {}
+    }
+
+    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[]) MethodHolder.CLONE_ARRAY_METHODHANDLE.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 messageName, Class[] argTypes) {
+        Set<Method> methods = SPECIAL_METHODS_MAP.get(clazz).get(messageName);
+
+        if (null != methods) {
+            for (Method method : methods) {
+                if (method.getName().equals(messageName) && parameterTypeMatches(method.getParameterTypes(), argTypes)) {
+                    return method;
+                }
+            }
+        }
+
+        return doFindMethod(clazz, messageName, 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 b2761b0..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;
@@ -98,6 +100,11 @@ import static org.apache.groovy.ast.tools.MethodNodeUtils.getCodeAsBlock;
 import static org.apache.groovy.ast.tools.MethodNodeUtils.getPropertyName;
 import static org.apache.groovy.ast.tools.MethodNodeUtils.methodDescriptorWithoutReturnType;
 import static org.codehaus.groovy.ast.AnnotationNode.METHOD_TARGET;
+import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveDouble;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveLong;
+import static org.codehaus.groovy.ast.ClassHelper.isWrapperBoolean;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX;
@@ -114,11 +121,6 @@ import static org.codehaus.groovy.ast.tools.GenericsUtils.addMethodGenerics;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
 import static org.codehaus.groovy.ast.tools.PropertyNodeUtils.adjustPropertyModifiersForMethod;
-import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
-import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean;
-import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveDouble;
-import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveLong;
-import static org.codehaus.groovy.ast.ClassHelper.isWrapperBoolean;
 
 /**
  * Verifies the AST node and adds any default AST code before bytecode generation occurs.
@@ -139,6 +141,7 @@ import static org.codehaus.groovy.ast.ClassHelper.isWrapperBoolean;
  *     <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/reflection/ReflectionUtils.java b/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
index e83b3d9..e0b5273 100644
--- a/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
+++ b/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
@@ -145,35 +145,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);
     }
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/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..ff0a6e2
--- /dev/null
+++ b/src/test/org/codehaus/groovy/classgen/VerifierTest.groovy
@@ -0,0 +1,59 @@
+/*
+ *  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 {}
+                class Inner {}
+            }
+            
+            {
+                def getLookupMethod = Outer.getDeclaredMethods().grep(m -> '$getLookup' == m.name)[0]
+                def modifiers = getLookupMethod.getModifiers()
+                assert Modifier.isPublic(modifiers)
+                assert Modifier.isStatic(modifiers)
+            }
+            
+            {
+                def getLookupMethod = Outer.StaticInner.getDeclaredMethods().grep(m -> '$getLookup' == m.name)[0]
+                def modifiers = getLookupMethod.getModifiers()
+                assert Modifier.isPublic(modifiers)
+                assert Modifier.isStatic(modifiers)
+            }
+            
+            {
+                def getLookupMethod = Outer.Inner.getDeclaredMethods().grep(m -> '$getLookup' == m.name)[0]
+                def modifiers = getLookupMethod.getModifiers()
+                assert Modifier.isPublic(modifiers)
+                assert !Modifier.isStatic(modifiers)
+            }
+        '''
+    }
+
+}
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)
         }