You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2021/12/03 20:50:19 UTC

[groovy] branch master updated: GROOVY-8693, GROOVY-9851, GROOVY-9884, GROOVY-9909, GROOVY-10302: super!

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

emilles 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 a63ee5e  GROOVY-8693, GROOVY-9851, GROOVY-9884, GROOVY-9909, GROOVY-10302: super!
a63ee5e is described below

commit a63ee5e26023dd22db81834d7134053bd566f441
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Dec 3 13:58:14 2021 -0600

    GROOVY-8693, GROOVY-9851, GROOVY-9884, GROOVY-9909, GROOVY-10302: super!
---
 src/main/java/groovy/lang/MetaClassImpl.java       | 242 +++----
 .../groovy/classgen/asm/InvocationWriter.java      |  15 +-
 .../codehaus/groovy/classgen/asm/MopWriter.java    |  94 ++-
 .../groovy/runtime/DefaultGroovyMethods.java       |   9 +-
 .../transform/stc/StaticTypeCheckingVisitor.java   |   4 +
 src/test/gls/invocation/MethodSelectionTest.groovy | 787 +++++++++------------
 src/test/groovy/ThisAndSuperTest.groovy            | 397 +++++++++--
 src/test/groovy/bugs/Groovy5212.groovy             |  62 ++
 src/test/groovy/bugs/Groovy5212Bug.groovy          | 107 ---
 .../{Groovy5260Bug.groovy => Groovy5260.groovy}    |  61 +-
 src/test/groovy/bugs/Groovy9615.groovy             |  63 --
 src/test/groovy/transform/stc/BugsSTCTest.groovy   |  29 +
 .../stc/Groovy9909.java}                           |  19 +-
 .../classgen/asm/sc/BugsStaticCompileTest.groovy   |  16 -
 14 files changed, 939 insertions(+), 966 deletions(-)

diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 4e2e327..45f506d 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -503,8 +503,6 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
     }
 
     private void replaceWithMOPCalls(final CachedMethod[] mopMethods) {
-        if (mopMethods == null || mopMethods.length == 0) return;
-
         class MOPIter extends MethodIndexAction {
             boolean useThis;
 
@@ -516,12 +514,11 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
                     Object[] data = methods.getArray();
                     for (int i = 0; i < methods.size(); i += 1) {
                         MetaMethod method = (MetaMethod) data[i];
-                        int matchingMethod = mopArrayIndex(method);
-                        if (matchingMethod >= 0) {
-                            methods.set(i, mopMethods[matchingMethod]);
-                        } else if (!useThis && !(method instanceof NewMetaMethod)
-                                && c == method.getDeclaringClass().getTheClass()) {
-                            methods.remove(i--); // not fit for super usage; StackOverflowError
+                        int matchedMethod = mopArrayIndex(method, c);
+                        if (matchedMethod >= 0) {
+                            methods.set(i, mopMethods[matchedMethod]);
+                        } else if (!useThis && !isDGM(method) && c == method.getDeclaringClass().getTheClass()) {
+                            methods.remove(i--); // not fit for super usage
                         }
                     }
                     if (!useThis) {
@@ -530,29 +527,26 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
                         else if (n == 1) e.methodsForSuper = data[0];
                     }
                 } else if (arrayOrMethod != null) {
-                    int matchingMethod = mopArrayIndex((MetaMethod) arrayOrMethod);
-                    if (matchingMethod >= 0) {
-                        if (useThis) e.methods = mopMethods[matchingMethod];
-                        else e.methodsForSuper = mopMethods[matchingMethod];
+                    MetaMethod method = (MetaMethod) arrayOrMethod;
+                    int matchedMethod = mopArrayIndex(method, c);
+                    if (matchedMethod >= 0) {
+                        if (useThis) e.methods = mopMethods[matchedMethod];
+                        else e.methodsForSuper = mopMethods[matchedMethod];
+                    } else if (!useThis && !isDGM(method) && c == method.getDeclaringClass().getTheClass()) {
+                        e.methodsForSuper = null; // not fit for super usage
                     }
                 }
             }
 
-            private int mopArrayIndex(final MetaMethod method) {
-                if (method instanceof NewMetaMethod) return -1;
-                if (useThis ^ method.isPrivate()) return -1;
-
-                String mopName = method.getMopName();
-                if (useThis) {
-                    return mopArrayIndex(method, mopName);
-                }
+            private int mopArrayIndex(final MetaMethod method, final Class<?> c) {
+                if (mopMethods == null || mopMethods.length == 0) return -1;
+                if (isDGM(method) || (useThis ^ method.isPrivate())) return -1;
+                if (useThis) return mopArrayIndex(method, method.getMopName());
                 // GROOVY-4922: Due to a numbering scheme change, find the super$number$methodName with
                 // the highest value. If we don't, no method may be found, leading to a stack overflow!
-                String[] tokens = decomposeMopName(mopName);
-                int distance = Integer.parseInt(tokens[1]);
+                int distance = ReflectionCache.getCachedClass(c).getSuperClassDistance() - 1;
                 while (distance > 0) {
-                    mopName = tokens[0] + distance + tokens[2];
-                    int index = mopArrayIndex(method, mopName);
+                    int index = mopArrayIndex(method, "super$" + distance + "$" + method.getName());
                     if (index >= 0) return index;
                     distance -= 1;
                 }
@@ -577,19 +571,8 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
                 return -1;
             }
 
-            private String[] decomposeMopName(final String mopName) {
-                int idx = mopName.indexOf('$');
-                if (idx > 0) {
-                    int eidx = mopName.indexOf('$', idx + 1);
-                    if (eidx > 0) {
-                        return new String[]{
-                                mopName.substring(0, idx + 1),
-                                mopName.substring(idx + 1, eidx),
-                                mopName.substring(eidx)
-                        };
-                    }
-                }
-                return new String[]{"", "0", mopName};
+            private boolean isDGM(final MetaMethod method) {
+                return method instanceof GeneratedMetaMethod || method instanceof NewMetaMethod;
             }
         }
         MOPIter iter = new MOPIter();
@@ -597,6 +580,9 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         // replace all calls for super with the correct MOP method
         iter.useThis = false;
         iter.iterate();
+
+        if (mopMethods == null || mopMethods.length == 0) return;
+
         // replace all calls for this with the correct MOP method
         iter.useThis = true;
         iter.iterate();
@@ -1092,87 +1078,55 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
     }
 
     /**
-     * <p>Invokes a method on the given receiver for the specified arguments. The sender is the class that invoked the method on the object.
+     * Invokes a method on the given receiver for the specified arguments. The sender is the class that invoked the method on the object.
      * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided.
-     *
-     * <p>The isCallToSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly
+     * <p>
+     * The isCallToSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly
      * to the super class if necessary
      *
      * @param sender            The java.lang.Class instance that invoked the method
      * @param object            The object which the method was invoked on
      * @param methodName        The name of the method
-     * @param originalArguments The arguments to the method
+     * @param arguments         The arguments to the method
      * @param isCallToSuper     Whether the method is a call to a super class method
      * @param fromInsideClass   Whether the call was invoked from the inside or the outside of the class
      * @return The return value of the method
      * @see MetaClass#invokeMethod(Class, Object, String, Object[], boolean, boolean)
      */
     @Override
-    public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
+    public Object invokeMethod(final Class sender, final Object object, final String methodName, final Object[] arguments, final boolean isCallToSuper, final boolean fromInsideClass) {
         try {
-            return doInvokeMethod(sender, object, methodName, originalArguments, isCallToSuper, fromInsideClass);
+            return doInvokeMethod(sender, object, methodName, arguments, 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() + "`");
+            MethodHandles.Lookup lookup = null;
+            if (object instanceof GroovyObject) {
+                lookup = GroovyObjectHelper.lookup((GroovyObject) object).orElseThrow(() -> mme);
             }
-            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() {}
-    }
+            Class<?> receiverClass = object.getClass();
 
-    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;
-            MethodHandles.Lookup tmpLookup = lookup;
-            MethodHandle superMethodHandle = findMethod(sender, methodName, castArgumentsToClassArray(originalArguments), superMethod -> {
+            if (isCallToSuper) {
+                if (lookup == null) throw mme;
+                MethodHandles.Lookup theLookup = lookup;
+                MethodHandle methodHandle = findMethod(sender.getSuperclass(), methodName, castArgumentsToClassArray(arguments), method -> {
+                    try {
+                        return theLookup.unreflectSpecial(method, receiverClass);
+                    } catch (IllegalAccessException e) {
+                        return null;
+                    }
+                });
+                if (methodHandle == null) throw mme;
                 try {
-                    return tmpLookup.unreflectSpecial(superMethod, receiverClass);
-                } catch (IllegalAccessException e) {
-                    return null;
+                    return methodHandle.bindTo(object).invokeWithArguments(arguments);
+                } catch (Throwable t) {
+                    throw new GroovyRuntimeException(t);
                 }
-            });
-            if (null == superMethodHandle) throw mme;
-            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) {
+                if (methodName.equals("clone") && arguments.length == 0) {
                     try {
-                        Object[] array = (Object[]) object;
-                        Object[] result = (Object[]) MethodHandleHolder.CLONE_ARRAY_METHOD_HANDLE.invokeExact(array);
-                        return result;
+                        return (Object[]) MethodHandleHolder.CLONE_ARRAY_METHOD_HANDLE.invokeExact((Object[]) object);
                     } catch (Throwable t) {
                         throw new GroovyRuntimeException(t);
                     }
@@ -1180,7 +1134,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
                 throw mme;
             }
 
-            final boolean spyFound = null != lookup;
+            boolean spyFound = (lookup != null);
             if (!spyFound) {
                 try {
                     lookup = MethodHandles.lookup().in(receiverClass);
@@ -1188,31 +1142,53 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
                     throw mme;
                 }
             }
-            MethodHandles.Lookup tmpLookup = lookup;
-            MethodHandle thisMethodHandle = findMethod(receiverClass, methodName, castArgumentsToClassArray(originalArguments), thisMethod -> {
+            MethodHandles.Lookup theLookup = lookup;
+            MethodHandle methodHandle = findMethod(receiverClass, methodName, castArgumentsToClassArray(arguments), method -> {
                 try {
-                    if (spyFound) return tmpLookup.unreflectSpecial(thisMethod, receiverClass);
-
-                    return tmpLookup.unreflect(thisMethod);
+                    return spyFound ? theLookup.unreflectSpecial(method, receiverClass) : theLookup.unreflect(method);
                 } catch (IllegalAccessException e) {
                     return null;
                 }
             });
-            if (null == thisMethodHandle) throw mme;
+            if (methodHandle == null) throw mme;
             try {
-                return thisMethodHandle.bindTo(object).invokeWithArguments(originalArguments);
+                return methodHandle.bindTo(object).invokeWithArguments(arguments);
             } catch (Throwable t) {
                 throw new GroovyRuntimeException(t);
             }
         }
     }
 
-    private static MethodHandle findMethod(Class<?> clazz, String methodName, Class[] argTypes, Function<Method, MethodHandle> mhFunc) {
-        final Map<String, Set<Method>> methodMap = SPECIAL_METHODS_MAP.get(clazz);
-        final Set<Method> methods = methodMap.get(methodName);
+    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(final Class<?> type) {
+            return new ConcurrentHashMap<>(4);
+        }
+    };
 
+    private static MethodHandle findMethod(final Class<?> clazz, final String methodName, final Class[] argTypes, final Function<Method, MethodHandle> mhFunc) {
+        Map<String, Set<Method>> map = SPECIAL_METHODS_MAP.get(clazz);
+        Set<Method> methods = map.get(methodName);
         Method foundMethod = null;
-        if (null != methods) {
+        if (methods != null) {
             for (Method method : methods) {
                 if (parameterTypeMatches(method.getParameterTypes(), argTypes)) {
                     foundMethod = method;
@@ -1221,30 +1197,22 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
             }
         }
 
-        if (null == foundMethod) {
-            foundMethod = doFindMethod(clazz, methodName, argTypes);
-            if (null == foundMethod) return null;
-            methodMap.computeIfAbsent(
-                            methodName,
-                            k -> Collections.newSetFromMap(new ConcurrentHashMap<>(2))
-                        ).add(foundMethod);
-        }
-
-        return mhFunc.apply(foundMethod);
-    }
-
-    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;
+        if (foundMethod == null) {
+            for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
+                List<Method> declaredMethods = ReflectionUtils.getDeclaredMethods(c, methodName, argTypes);
+                if (!declaredMethods.isEmpty()) {
+                    Method method = declaredMethods.get(0);
+                    if (!Modifier.isAbstract(method.getModifiers())) {
+                        foundMethod = method;
+                        break;
+                    }
                 }
-                return superMethod;
             }
+            if (foundMethod == null) return null;
+            map.computeIfAbsent(methodName, k -> Collections.newSetFromMap(new ConcurrentHashMap<>(2))).add(foundMethod);
         }
-        return null;
+
+        return mhFunc.apply(foundMethod);
     }
 
     private Object doInvokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
@@ -1494,16 +1462,22 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
     }
 
     public MetaMethod getMethodWithCaching(Class sender, String methodName, Object[] arguments, boolean isCallToSuper) {
-        // let's try use the cache to find the method
         if (!isCallToSuper && GroovyCategorySupport.hasCategoryInCurrentThread()) {
             return getMethodWithoutCaching(sender, methodName, MetaClassHelper.convertToTypeArray(arguments), isCallToSuper);
         }
-        final MetaMethodIndex.Entry e = metaMethodIndex.getMethods(sender, methodName);
+        MetaMethodIndex.Entry e = metaMethodIndex.getMethods(sender, methodName);
+        if (isCallToSuper && e != null && e.methodsForSuper == null) {
+            // allow "super.name()" to find DGM if class declares method "name"
+            e = metaMethodIndex.getMethods(sender.getSuperclass(), methodName);
+        }
         if (e == null) {
             return null;
         }
-
-        return isCallToSuper ? getSuperMethodWithCaching(arguments, e) : getNormalMethodWithCaching(arguments, e);
+        if (isCallToSuper) {
+            return getSuperMethodWithCaching(arguments, e);
+        } else {
+            return getNormalMethodWithCaching(arguments, e);
+        }
     }
 
     private static boolean sameClasses(final Class[] params, final Class[] arguments) {
@@ -2983,7 +2957,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
     }
 
     private MetaProperty getMetaProperty(final Class clazz, final String name, final boolean useSuper, final boolean useStatic) {
-        if (clazz == theClass)
+        if (clazz == theClass && !useSuper)
             return getMetaProperty(name, useStatic);
 
         CachedClass cachedClass = ReflectionCache.getCachedClass(clazz);
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java
index 4757f8f..9181b6b 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java
@@ -110,17 +110,14 @@ public class InvocationWriter {
 
     public void makeCall(final Expression origin, final Expression receiver, final Expression message, final Expression arguments, final MethodCallerMultiAdapter adapter, boolean safe, final boolean spreadSafe, boolean implicitThis) {
         ClassNode sender;
-        if (isSuperExpression(receiver) || (isThisExpression(receiver) && !implicitThis)) {
+        if (isSuperExpression(receiver) || isThisExpression(receiver) && !implicitThis) {
+            // GROOVY-6045, GROOVY-8693, et al.
             sender = controller.getThisType();
-            if (isSuperExpression(receiver)) {
-                sender = sender.getSuperClass(); // GROOVY-4035
-                implicitThis = false; // prevent recursion
-                safe = false; // GROOVY-6045
-            }
+            implicitThis = false;
+            safe = false;
         } else {
             sender = controller.getClassNode();
         }
-
         makeCall(origin, new ClassExpression(sender), receiver, message, arguments, adapter, safe, spreadSafe, implicitThis);
     }
 
@@ -187,8 +184,8 @@ public class InvocationWriter {
         }
 
         ClassNode ownerClass = declaringClass;
-        if (opcode == INVOKESPECIAL) {
-            ownerClass = receiverType; // GROOVY-8693
+        if (opcode == INVOKESPECIAL) { // GROOVY-8693, GROOVY-9909
+            if (!declaringClass.isInterface() || receiverType.implementsInterface(declaringClass)) ownerClass = receiverType;
         } else if (opcode == INVOKEVIRTUAL && isObjectType(declaringClass)) {
             // avoid using a narrowed type if the method is defined on Object, because it can interfere
             // with delegate type inference in static compilation mode and trigger a ClassCastException
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/MopWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/MopWriter.java
index fa463ff..c096cbf 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/MopWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/MopWriter.java
@@ -30,9 +30,9 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 
+import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toSet;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveDouble;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveLong;
@@ -84,14 +84,14 @@ public class MopWriter {
     private final WriterController controller;
 
     public MopWriter(final WriterController controller) {
-        this.controller = Objects.requireNonNull(controller);
+        this.controller = requireNonNull(controller);
     }
 
     public void createMopMethods() {
         ClassNode classNode = controller.getClassNode();
         if (!ClassHelper.isGeneratedFunction(classNode)) {
             visitMopMethodList(classNode.getMethods(), true, Collections.emptySet(), Collections.emptyList());
-            visitMopMethodList(classNode.getSuperClass().getAllDeclaredMethods(), false, classNode.getMethods().stream()
+            visitMopMethodList(getSuperMethods(classNode)/*GROOVY-8693, et al.*/, false, classNode.getMethods().stream()
                     .map(mn -> new MopKey(mn.getName(), mn.getParameters())).collect(toSet()), controller.getSuperMethodNames());
         }
     }
@@ -108,7 +108,7 @@ public class MopWriter {
      *
      * @see #generateMopCalls(LinkedList, boolean)
      */
-    private void visitMopMethodList(final List<MethodNode> methods, final boolean isThis, final Set<MopKey> useOnlyIfDeclaredHereToo, final List<String> orNameMentionedHere) {
+    private void visitMopMethodList(final Iterable<MethodNode> methods, final boolean isThis, final Set<MopKey> onlyIfThis, final List<String> orThis) {
         LinkedList<MethodNode> list = new LinkedList<>();
         Map<MopKey, MethodNode> map = new HashMap<>();
         for (MethodNode mn : methods) {
@@ -124,10 +124,7 @@ public class MopWriter {
             Parameter[] parameters = mn.getParameters();
             if (isMopMethod(methodName)) {
                 map.put(new MopKey(methodName, parameters), mn);
-            } else if (!methodName.startsWith("<")) {
-                if (!useOnlyIfDeclaredHereToo.contains(new MopKey(methodName, parameters)) && !orNameMentionedHere.contains(methodName)) {
-                    continue;
-                }
+            } else if (!methodName.startsWith("<") && (onlyIfThis.contains(new MopKey(methodName, parameters)) || orThis.contains(methodName))) {
                 if (map.put(new MopKey(getMopMethodName(mn, isThis), parameters), mn) == null) {
                     list.add(mn);
                 }
@@ -137,36 +134,6 @@ public class MopWriter {
     }
 
     /**
-     * Creates a MOP method name from a method.
-     *
-     * @param method  the method to be called by the mop method
-     * @param useThis if true, then it is a call on "this", "super" else
-     * @return the mop method name
-     */
-    public static String getMopMethodName(final MethodNode method, final boolean useThis) {
-        ClassNode declaringClass = method.getDeclaringClass();
-        int distance = 1;
-        if (!declaringClass.isInterface()) { // GROOVY-8693: fixed distance for interface methods
-            for (ClassNode sc = declaringClass.getSuperClass(); sc != null; sc = sc.getSuperClass()) {
-                distance += 1;
-            }
-        }
-        return (useThis ? "this" : "super") + "$" + distance + "$" + method.getName();
-    }
-
-    /**
-     * Determines if a method is a MOP method. This is done by the method name.
-     * If the name starts with "this$" or "super$" but does not contain "$dist$",
-     * then it is an MOP method.
-     *
-     * @param methodName name of the method to test
-     * @return true if the method is a MOP method
-     */
-    public static boolean isMopMethod(final String methodName) {
-        return (methodName.startsWith("this$") || methodName.startsWith("super$")) && !methodName.contains("$dist$");
-    }
-
-    /**
      * Generates a Meta Object Protocol method that is used to call a non-public
      * method or to make a call to super.
      *
@@ -196,7 +163,12 @@ public class MopWriter {
 
             // make call to this or super method with operands
             ClassNode receiverType = controller.getThisType();
-            if (!useThis) receiverType = receiverType.getSuperClass();
+            if (!useThis) {
+                receiverType = receiverType.getSuperClass();
+                ClassNode declaringType = method.getDeclaringClass();
+                // GROOVY-8693, GROOVY-9909, et al.: method from interface not implemented by super class
+                if (declaringType.isInterface() && !receiverType.implementsInterface(declaringType)) receiverType = declaringType;
+            }
             mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(receiverType), method.getName(), signature, receiverType.isInterface());
 
             BytecodeHelper.doReturn(mv, returnType);
@@ -207,6 +179,50 @@ public class MopWriter {
         }
     }
 
+    //--------------------------------------------------------------------------
+
+    private static Iterable<MethodNode> getSuperMethods(final ClassNode classNode) {
+        Map<String, MethodNode> result = classNode.getSuperClass().getDeclaredMethodsMap();
+        for (ClassNode in : classNode.getInterfaces()) { // declared!
+            if (!classNode.getSuperClass().implementsInterface(in)) {
+                for (MethodNode mn : in.getMethods()) { // only direct default methods!
+                    if (mn.isDefault()) result.putIfAbsent(mn.getTypeDescriptor(), mn);
+                }
+            }
+        }
+        return result.values();
+    }
+
+    /**
+     * Creates a MOP method name from a method.
+     *
+     * @param method  the method to be called by the mop method
+     * @param useThis if true, then it is a call on "this", "super" else
+     * @return the mop method name
+     */
+    public static String getMopMethodName(final MethodNode method, final boolean useThis) {
+        ClassNode declaringClass = method.getDeclaringClass();
+        int distance = 1;
+        if (!declaringClass.isInterface()) { // GROOVY-8693: fixed distance for interface methods
+            for (ClassNode sc = declaringClass.getSuperClass(); sc != null; sc = sc.getSuperClass()) {
+                distance += 1;
+            }
+        }
+        return (useThis ? "this" : "super") + "$" + distance + "$" + method.getName();
+    }
+
+    /**
+     * Determines if a method is a MOP method. This is done by the method name.
+     * If the name starts with "this$" or "super$" but does not contain "$dist$",
+     * then it is an MOP method.
+     *
+     * @param methodName name of the method to test
+     * @return true if the method is a MOP method
+     */
+    public static boolean isMopMethod(final String methodName) {
+        return (methodName.startsWith("this$") || methodName.startsWith("super$")) && !methodName.contains("$dist$");
+    }
+
     @Deprecated
     public static boolean equalParameterTypes(final Parameter[] p1, final Parameter[] p2) {
         return ParameterUtils.parametersEqual(p1, p2);
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 7283925..6a3dd4a 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -17685,7 +17685,8 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
     }
 
     /**
-     * Set the metaclass for an object.
+     * Sets the metaclass for an object.
+     *
      * @param self the object whose metaclass we want to set
      * @param metaClass the new metaclass value
      * @since 1.6.0
@@ -17695,20 +17696,20 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
             metaClass = ((HandleMetaClass)metaClass).getAdaptee();
 
         if (self instanceof Class) {
-            GroovySystem.getMetaClassRegistry().setMetaClass((Class) self, metaClass);
+            GroovySystem.getMetaClassRegistry().setMetaClass((Class)self, metaClass);
         } else {
             ((MetaClassRegistryImpl)GroovySystem.getMetaClassRegistry()).setMetaClass(self, metaClass);
         }
     }
 
     /**
-     * Set the metaclass for a GroovyObject.
+     * Sets the metaclass for a {@code GroovyObject}.
+     *
      * @param self the object whose metaclass we want to set
      * @param metaClass the new metaclass value
      * @since 2.0.0
      */
     public static void setMetaClass(GroovyObject self, MetaClass metaClass) {
-        // this method was introduced as to prevent from a stack overflow, described in GROOVY-5285
         if (metaClass instanceof HandleMetaClass)
             metaClass = ((HandleMetaClass)metaClass).getAdaptee();
 
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index 73d239c..5d7b402 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -3743,6 +3743,10 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
                 addTraitType(receiver, owners);
                 if (receiver.redirect().isInterface()) {
                     owners.add(Receiver.make(OBJECT_TYPE));
+                } else if (isSuperExpression(objectExpression)) { //GROOVY-9909: super.defaultMethod()
+                    for (ClassNode in : typeCheckingContext.getEnclosingClassNode().getInterfaces()) {
+                        if (!receiver.implementsInterface(in)) owners.add(Receiver.make(in));
+                    }
                 }
             }
         }
diff --git a/src/test/gls/invocation/MethodSelectionTest.groovy b/src/test/gls/invocation/MethodSelectionTest.groovy
index 97379c9..e347bbb 100644
--- a/src/test/gls/invocation/MethodSelectionTest.groovy
+++ b/src/test/gls/invocation/MethodSelectionTest.groovy
@@ -18,500 +18,375 @@
  */
 package gls.invocation
 
-import gls.CompilableTestSupport
-
-class MethodSelectionTest extends CompilableTestSupport {
-
-  /**
-   * This test ensures Groovy can choose a method based on interfaces.
-   * Choosing such an interface should not be hidden by subclasses.
-   */
-  void testMostSpecificInterface() {
-    assertScript """
-      interface A{}
-      interface B extends A{}
-      class C implements B,A{}
-      class D extends C{}
-      class E implements B{}
-
-      def m(A a){1}
-      def m(B b){2}
-
-      assert m(new D()) == 2
-      assert m(new C()) == 2
-      assert m(new E()) == 2
-    """   
-
-    assertScript """
-      class A implements I1 {}
-      class B extends A implements I2 {}
-      class C extends B implements I3 {}
-      interface I1 {}
-      interface I2 extends I1 {}
-      interface I3 extends I2 {}
-
-      def foo(bar) {0}
-      def foo(I1 bar) {1}
-      def foo(I2 bar) {2}
-      def foo(I3 bar) {3}
-
-      assert foo(new A())==1
-      assert foo(new B())==2
-      assert foo(new C())==3
-    """
-  }
-
-  void testMostGeneralForNull() {
-    // we use the same signatures with different method orders,
-    // because we want to catch method ordering bugs
-    assertScript """
-      def m(String x){1}
-      def m(Integer x){2}
-      assert m(null) == 1
-
-      def n(Integer x){2}
-      def n(String x){1}
-      assert n(null) == 1
-    """
-
-    // Exception defines Throwable and String versions, which are both equal
-    shouldFail """
-        new Exception(null)
-    """
-
-    shouldFail """
-        class A {
-            public A(String a){}
-            public A(Throwable t){}
-            public A(){this(null)}
-        }
-        new A()
-    """
-
-    shouldFail """
-        class A{}
-        class B{}
-        def m(A a){}
-        def m(B b){}
-        m(null)
-    """
-    shouldFail """
-        class A extends Exception {
-            public A(){super(null)}
-        }
-        new A()
-    """
-  }
-
-  void testMethodSelectionException() {
-    assertScript """
-      import org.codehaus.groovy.runtime.metaclass.MethodSelectionException as MSE
-
-      def foo(int x) {}
-      def foo(Number x) {}
-
-      try {
-        foo()
-        assert false
-      } catch (MSE mse) {
-        assert mse.message.indexOf("foo()") >0
-        assert mse.message.indexOf("#foo(int)") >0
-        assert mse.message.indexOf("#foo(java.lang.Number)") >0
-      }
-
-    """  
-  }
-
-  void testMethodSelectionWithInterfaceVargsMethod() {
+final class MethodSelectionTest extends gls.CompilableTestSupport {
+
+    /**
+     * This test ensures Groovy can choose a method based on interfaces.
+     * Choosing such an interface should not be hidden by subclasses.
+     */
+    void testMostSpecificInterface() {
+        assertScript '''
+            interface A{}
+            interface B extends A{}
+            class C implements B,A{}
+            class D extends C{}
+            class E implements B{}
+
+            def m(A a){1}
+            def m(B b){2}
+
+            assert m(new D()) == 2
+            assert m(new C()) == 2
+            assert m(new E()) == 2
+        '''
+        assertScript '''
+            class A implements I1 {}
+            class B extends A implements I2 {}
+            class C extends B implements I3 {}
+            interface I1 {}
+            interface I2 extends I1 {}
+            interface I3 extends I2 {}
+
+            def foo(bar) {0}
+            def foo(I1 bar) {1}
+            def foo(I2 bar) {2}
+            def foo(I3 bar) {3}
+
+            assert foo(new A())==1
+            assert foo(new B())==2
+            assert foo(new C())==3
+        '''
+    }
+
+    void testMostGeneralForNull() {
+        // we use the same signatures with different method orders,
+        // because we want to catch method ordering bugs
+        assertScript '''
+          def m(String x){1}
+          def m(Integer x){2}
+          assert m(null) == 1
+
+          def n(Integer x){2}
+          def n(String x){1}
+          assert n(null) == 1
+        '''
+        // Exception defines Throwable and String versions, which are both equal
+        shouldFail '''
+            new Exception(null)
+        '''
+        shouldFail '''
+            class A {
+                public A(String a){}
+                public A(Throwable t){}
+                public A(){this(null)}
+            }
+            new A()
+        '''
+        shouldFail '''
+            class A{}
+            class B{}
+            def m(A a){}
+            def m(B b){}
+            m(null)
+        '''
+        shouldFail '''
+            class A extends Exception {
+                public A(){super(null)}
+            }
+            new A()
+        '''
+    }
+
+    void testMethodSelectionException() {
+        assertScript '''
+            import org.codehaus.groovy.runtime.metaclass.MethodSelectionException as MSE
+
+            def foo(int x) {}
+            def foo(Number x) {}
+
+            try {
+              foo()
+              assert false
+            } catch (MSE mse) {
+              assert mse.message.indexOf("foo()") >0
+              assert mse.message.indexOf("#foo(int)") >0
+              assert mse.message.indexOf("#foo(java.lang.Number)") >0
+            }
+        '''
+    }
+
     // GROOVY-2719
-    assertScript """
-      public class Thing {}
-      public class Subthing extends Thing {}
+    void testMethodSelectionWithInterfaceVargsMethod() {
+        assertScript '''
+            public class Thing {}
+            public class Subthing extends Thing {}
 
-      def foo(Thing i) {1}
-      def foo(Runnable[] l) {2}
+            def foo(Thing i) {1}
+            def foo(Runnable[] l) {2}
 
-      assert foo(new Subthing())==1
-    """
-  }
+            assert foo(new Subthing())==1
+        '''
+    }
 
-  void testComplexInterfaceInheritance() {
     // GROOVY-2698
-    assertScript """
-        import javax.swing.*
+    void testComplexInterfaceInheritance() {
+        assertScript '''
+            import javax.swing.*
 
-        interface ISub extends Action {}
+            interface ISub extends Action {}
 
-        class Super extends AbstractAction {
-        void actionPerformed(java.awt.event.ActionEvent e){}
-        }
-        class Sub extends AbstractAction implements ISub {
-        void actionPerformed(java.awt.event.ActionEvent e){}
-        }
+            class Super extends AbstractAction {
+            void actionPerformed(java.awt.event.ActionEvent e){}
+            }
+            class Sub extends AbstractAction implements ISub {
+            void actionPerformed(java.awt.event.ActionEvent e){}
+            }
 
-        static String foo(Action x) { "super" }
-        static String foo(ISub x) { "sub" }
+            static String foo(Action x) { "super" }
+            static String foo(ISub x) { "sub" }
 
-        def result = [new Super(), new Sub()].collect { foo(it) }
+            def result = [new Super(), new Sub()].collect { foo(it) }
 
-        assert ["super","sub"] == result
-    """
+            assert ["super","sub"] == result
+        '''
+    }
 
-    // GROOVY-6001
-    assertScript """
-        class Mark {
-            static log = ""
+    void testNullUsageForPrimitivesWithExplicitNull() {
+        [byte,int,short,float,double,boolean,char].each { type ->
+            assertScript """
+                def foo($type x) {}
+
+                boolean catched = false
+                try {
+                  foo(null)
+                } catch (MissingMethodException mme) {
+                  catched = true
+                }
+                def txt = "expected to see a MissingMethodEception when using foo(null) "+
+                          "to call foo($type), but no exception was thrown"
+                assert catched, txt
+            """
         }
+    }
 
-        interface PortletRequest {}
-        interface PortletResponse {}
-        interface HttpServletRequest {}
-        interface HttpServletResponse {}
-        class HttpServletRequestWrapper implements HttpServletRequest {}
-        class PortletRequestImpl extends HttpServletRequestWrapper implements PortletRequest {}
-        class ClientDataRequestImpl extends PortletRequestImpl {}
-        class ResourceRequestImpl extends ClientDataRequestImpl {}
-        class Application {}
-        interface HttpServletRequestListener {
-            void onRequestStart(HttpServletRequest request, HttpServletResponse response);
-            void onRequestEnd(HttpServletRequest request, HttpServletResponse response);    
+    void testNullUsageForPrimitivesWithImplicitNull() {
+        [byte,int,short,float,double,boolean,char].each { type ->
+            assertScript """
+                def foo($type x) {}
+
+                boolean catched = false
+                try {
+                  foo()
+                } catch (MissingMethodException mme) {
+                  catched = true
+                }
+                def txt = "expected to see a MissingMethodEception when using foo(null) "+
+                          "to call foo($type), but no exception was thrown"
+                assert catched, txt
+            """
         }
-        interface PortletRequestListener {
-            void onRequestStart(PortletRequest request, PortletResponse response);
-            void onRequestEnd(PortletRequest request, PortletResponse response);    
+    }
+
+    void testNullUsageForPrimitivesAndOverloading() {
+        [byte,int,short,float,double].each { type ->
+            assertScript """
+                def foo(String x){1}
+                def foo($type x) {2}
+
+                def txt = "foo($type) was called where foo(String) should have been called"
+                assert foo(null)==1, txt
+            """
         }
+    }
 
-        class FooApplication extends Application implements HttpServletRequestListener, PortletRequestListener{
-            void onRequestStart(HttpServletRequest request, HttpServletResponse response) {
-                Mark.log += "FooApplication.onRequestStart(HttpServletRequest, HttpServletResponse)"
+    void testPrivateMethodSelectionFromClosure(){
+        assertScript '''
+            class I1 {
+                private foo() {1}
+                def bar(){
+                  def x = "foo"
+                  return {this."\$x"()}
+                }
             }
-            void onRequestEnd(HttpServletRequest request, HttpServletResponse response) {
-                Mark.log += "FooApplication.onRequestEnd(HttpServletRequest, HttpServletResponse)"
+            class I2 extends I1 {
+                def foo(){2}
             }
-            void onRequestStart(PortletRequest request, PortletResponse response) {
-                Mark.log += "FooApplication.onRequestStart(PortletRequest, PortletResponse)"
-            }
-            void onRequestEnd(PortletRequest request, PortletResponse response) {
-                Mark.log += "FooApplication.onRequestEnd(PortletRequest, PortletResponse)"
-            }
-        }
+            def i = new I2()
+            assert i.bar()() == 1
+        '''
+    }
 
-        class BazApplication extends FooApplication {
-            void onRequestStart(PortletRequest request, PortletResponse response) {
-                Mark.log += 'BazApplication.onRequestStart(PortletRequest, PortletResponse)'
-                super.onRequestStart(request, response);
+    void testCachingForNullAndPrimitive(){
+        assertScript '''
+            boolean shaky(boolean defaultValue) {false}
+            shaky(false)
+            try {
+                shaky(null)
+                assert false : "method caching allowed null for primitive"
+            } catch (MissingMethodException mme){
+                assert true
             }
-        }
-
-        BazApplication application = new BazApplication()
-        Mark.log = ""
-        PortletRequest request = new ResourceRequestImpl()
-        application.onRequestStart(request, null)
-        assert Mark.log == "BazApplication.onRequestStart(PortletRequest, PortletResponse)FooApplication.onRequestStart(PortletRequest, PortletResponse)"
-    """
-  }
-
-  void testNullUsageForPrimitivesWithExplicitNull() {
-    [byte,int,short,float,double,boolean,char].each { type ->
-      assertScript """
-         def foo($type x) {}
-
-         boolean catched = false
-         try {
-           foo(null)
-         } catch (MissingMethodException mme) {
-           catched = true
-         }
-         def txt = "expected to see a MissingMethodEception when using foo(null) "+
-                   "to call foo($type), but no exception was thrown"
-         assert catched, txt
-      """
+        '''
     }
-  }
-
-  void testNullUsageForPrimitivesWithImplicitNull() {
-    [byte,int,short,float,double,boolean,char].each { type ->
-      assertScript """
-         def foo($type x) {}
-
-         boolean catched = false
-         try {
-           foo()
-         } catch (MissingMethodException mme) {
-           catched = true
-         }
-         def txt = "expected to see a MissingMethodEception when using foo(null) "+
-                   "to call foo($type), but no exception was thrown"
-         assert catched, txt
-      """
+
+    void testSpreadOperatorAndVarargs(){
+        assertScript '''
+            class SpreadBug {
+                def foo(String... args) {
+                    bar(*args)
+                }
+                def bar(String... args) {args.length}
+            }
+            def sb = new SpreadBug()
+            assert sb.foo("1","42")==2
+        '''
     }
-  }
-
-  void testNullUsageForPrimitivesAndOverloading() {
-    [byte,int,short,float,double].each { type ->
-      assertScript """
-         def foo(String x){1}
-         def foo($type x) {2}
-
-         def txt = "foo($type) was called where foo(String) should have been called"
-         assert foo(null)==1, txt
-      """
-    } 
-  }
-
-  void testPrivateMethodSelectionFromClosure(){
-      assertScript """
-          class I1 {
-              private foo() {1}
-              def bar(){
-                def x = "foo"
-                return {this."\$x"()}
-              }
-          }
-          class I2 extends I1 {
-              def foo(){2}
-          }
-          def i = new I2()
-          assert i.bar()() == 1
-
-      """
-  }
-
-  void testCachingForNullAndPrimitive(){
-      assertScript """
-          boolean shaky(boolean defaultValue) {false}
-          shaky(false)
-          try {
-              shaky(null)
-              assert false : "method caching allowed null for primitive"
-          } catch (MissingMethodException mme){
-              assert true
-          }
-      """
-  }
-
-  void testSpreadOperatorAndVarargs(){
-      assertScript """
-          class SpreadBug {
-              def foo(String... args) {
-                  bar(*args)
-              }
-              def bar(String... args) {args.length}
-          }
-          def sb = new SpreadBug()
-          assert sb.foo("1","42")==2  
-      """
-  }
-
-  void testBDandBIToFloatAutoConversionInMethodSelection() {
-    def foo = new Foo3977()
-
-    // double examples were working already but float ones were not
-    // adding both here just to make sure the inconsistency doesn't creep in again
-    foo.setMyDouble1 1.0
-    assert foo.getMyDouble1() == 1.0
-
-    foo.setMyDouble1 new BigInteger('1')
-    assert foo.getMyDouble1() == 1.0
-
-    foo.setMyDouble2 1.0
-    assert foo.getMyDouble2() == 1.0
-
-    foo.setMyDouble2 new BigInteger('1')
-    assert foo.getMyDouble2() == 1.0
-
-    foo.setMyFloat1 1.0
-    assert foo.getMyFloat1() == 1.0
-
-    foo.setMyFloat1 new BigInteger('1')
-    assert foo.getMyFloat1() == 1.0
-
-    foo.setMyFloat2 1.0
-    assert foo.getMyFloat2() == 1.0
-
-    foo.setMyFloat2 new BigInteger('1')
-    assert foo.getMyFloat2() == 1.0
-  }
-
-  void testCallWithExendedBigDecimal() {
-      assertScript """
-        BigDecimal f (BigDecimal x) {
-            return x
-        }
 
-        public class ExtendedBigDecimal extends java.math.BigDecimal {
-            public ExtendedBigDecimal (int i) {
-                super (i)
+    // GROOVY-3977
+    void testBDandBIToFloatAutoConversionInMethodSelection() {
+        assertScript '''
+            class Foo3977 {
+                double myDouble1
+                Double myDouble2
+                float myFloat1
+                Float myFloat2
             }
-        }
 
-        assert f(new ExtendedBigDecimal(1)) == 1      
-      """
-  }
+            def foo = new Foo3977()
 
-  void testVargsClass() {
-      assertScript """
-        interface Parent {}
-        interface Child extends Parent {}
+            // double examples were working already but float ones were not
+            // adding both here just to make sure the inconsistency doesn't creep in again
+            foo.setMyDouble1 1.0
+            assert foo.getMyDouble1() == 1.0
 
-        class Child1 implements Child { }
-        def a = new Child1()
+            foo.setMyDouble1 new BigInteger('1')
+            assert foo.getMyDouble1() == 1.0
 
-        class Child2 implements Child { }
-        def b = new Child2()
+            foo.setMyDouble2 1.0
+            assert foo.getMyDouble2() == 1.0
 
-        def foo(Parent... values) { 
-          assert values.class == Parent[]
-        }
-        foo(a, b)
-      """
-  }
-
-  // GROOVY-5812
-  void testDirectMethodCall() {
-      assertScript """
-          private String[] getStringArrayDirectly() { ["string_00", "string_01"] }   
-          private String[] getStringArrayIndirectlyWithType(String[] stringarray) { stringarray }    
-          private String[] getStringArrayIndirectlyWithoutType(stringarray) { stringarray }
-
-          public int getStringArrayDirectly_Length() { getStringArrayDirectly().length }            
-          public int getStringArrayIndirectlyWithType_Length() { getStringArrayIndirectlyWithType(getStringArrayDirectly()).length }
-          public int getStringArrayIndirectlyWithoutType_Length() { getStringArrayIndirectlyWithoutType(getStringArrayDirectly()).length }
-
-          assert getStringArrayDirectly_Length() == getStringArrayIndirectlyWithoutType_Length()
-          assert getStringArrayDirectly_Length() == getStringArrayIndirectlyWithType_Length()
-          assert getStringArrayIndirectlyWithType_Length() == getStringArrayIndirectlyWithoutType_Length()
-      """
-  }
-
-  // GROOVY-6189, GROOVY-9852
-  void testSAMs() {
-      // simple direct case
-      assertScript '''
-          interface MySAM {
-              def someMethod()
-          }
-          def foo(MySAM sam) {sam.someMethod()}
-          assert foo {1} == 1
-      '''
-
-      // overloads with classes implemented by Closure
-      [
-          'groovy.lang.Closure'            : 'not',
-          'groovy.lang.GroovyCallable'     : 'not',
-          'groovy.lang.GroovyObject'       : 'not',
-          'groovy.lang.GroovyObjectSupport': 'not',
-
-          'java.lang.Object'               : 'sam',
-          'java.lang.Runnable'             : 'not',
-          'java.lang.Cloneable'            : 'not',
-          'java.io.Serializable'           : 'not',
-          'java.util.concurrent.Callable'  : 'not',
-      ].each { type, which ->
-          assertScript """
-              interface MySAM {
-                  def someMethod()
-              }
-              def foo($type ref) { 'not' }
-              def foo(MySAM sam) { sam.someMethod() }
-              assert foo { 'sam' } == '$which' : '$type'
-          """
-      }
-  }
-
-  // GROOVY-6431
-  void testBigDecAndBigIntSubClass() {
-      assertScript'''
-          class MyDecimal extends BigDecimal {
-              public MyDecimal(String s) {super(s)}
-          }
-          class MyInteger extends BigInteger {
-              public MyInteger(String s) {super(s)}
-          }
-          class Expression {
-              public int takeNumber(Number a) {return 1}
-              public int takeBigDecimal(BigDecimal a) {return 2}
-              public int takeBigInteger(BigInteger a) {return 3}
-          }
-
-          Expression exp = new Expression();
-          assert 1 == exp.takeNumber(new MyInteger("3"))
-          assert 2 == exp.takeBigDecimal(new MyDecimal("3.0"))
-          assert 3 == exp.takeBigInteger(new MyInteger("3"))
-      '''
-  }
-
-  // GROOVY-7655
-  void testOverloadAndSuper() {
-      assertScript '''
-        class A {
-            boolean aCalled = false
-            def myMethod( def item ) {
-                aCalled = true
-            }
-        }
+            foo.setMyDouble2 new BigInteger('1')
+            assert foo.getMyDouble2() == 1.0
 
-        class B extends A {
-            boolean bCalled = false
-            def myMethod( def item ) {
-                super.myMethod( item+"B" )
-                bCalled = true
-            }
-        }
+            foo.setMyFloat1 1.0
+            assert foo.getMyFloat1() == 1.0
 
-        class C extends B {
-            boolean cCalled = false
-            def cMethod( def item ) {
-                super.myMethod( item )
-                cCalled = true
-            }
-        }
+            foo.setMyFloat1 new BigInteger('1')
+            assert foo.getMyFloat1() == 1.0
+
+            foo.setMyFloat2 1.0
+            assert foo.getMyFloat2() == 1.0
+
+            foo.setMyFloat2 new BigInteger('1')
+            assert foo.getMyFloat2() == 1.0
+        '''
+    }
 
-        def c = new C()
-        c.cMethod( "stuff" )
-
-        assert c.aCalled
-        assert c.bCalled
-        assert c.cCalled
-      '''
-      assertScript '''
-        class A {
-            boolean aCalled = false
-            def myMethod( def item ) {
-                aCalled = true
+    void testCallWithExendedBigDecimal() {
+        assertScript '''
+            BigDecimal f (BigDecimal x) {
+                return x
             }
-        }
 
-        class B extends A {
-            boolean bCalled = false
-            def myMethod( def item ) {
-                super.myMethod( item+"B" )
-                bCalled = true
+            public class ExtendedBigDecimal extends java.math.BigDecimal {
+                public ExtendedBigDecimal (int i) {
+                    super (i)
+                }
             }
-        }
 
-        class C extends B { }
-        class D extends C {
-            boolean dCalled = false
-            def dMethod( def item ) {
-                super.myMethod( item )
-                dCalled = true
+            assert f(new ExtendedBigDecimal(1)) == 1
+        '''
+    }
+
+    // GROOVY-6431
+    void testBigDecAndBigIntSubClass() {
+        assertScript'''
+            class MyDecimal extends BigDecimal {
+                public MyDecimal(String s) {super(s)}
             }
-        }
+            class MyInteger extends BigInteger {
+                public MyInteger(String s) {super(s)}
+            }
+            class Expression {
+                public int takeNumber(Number a) {return 1}
+                public int takeBigDecimal(BigDecimal a) {return 2}
+                public int takeBigInteger(BigInteger a) {return 3}
+            }
+
+            Expression exp = new Expression();
+            assert 1 == exp.takeNumber(new MyInteger("3"))
+            assert 2 == exp.takeBigDecimal(new MyDecimal("3.0"))
+            assert 3 == exp.takeBigInteger(new MyInteger("3"))
+        '''
+    }
 
-        def d = new D()
-        d.dMethod( "stuff" )
+    void testVargsClass() {
+        assertScript '''
+            interface Parent {}
+            interface Child extends Parent {}
 
-        assert d.aCalled
-        assert d.bCalled
-        assert d.dCalled
-      '''
-  }
-}
+            class Child1 implements Child { }
+            def a = new Child1()
 
-class Foo3977 {
-    double myDouble1
-    Double myDouble2
-    float myFloat1
-    Float myFloat2
+            class Child2 implements Child { }
+            def b = new Child2()
+
+            def foo(Parent... values) {
+                assert values.class == Parent[]
+            }
+            foo(a, b)
+        '''
+    }
+
+    // GROOVY-5812
+    void testDirectMethodCall() {
+        assertScript '''
+            private String[] getStringArrayDirectly() { ["string_00", "string_01"] }
+            private String[] getStringArrayIndirectlyWithType(String[] stringarray) { stringarray }
+            private String[] getStringArrayIndirectlyWithoutType(stringarray) { stringarray }
+
+            public int getStringArrayDirectly_Length() { getStringArrayDirectly().length }
+            public int getStringArrayIndirectlyWithType_Length() { getStringArrayIndirectlyWithType(getStringArrayDirectly()).length }
+            public int getStringArrayIndirectlyWithoutType_Length() { getStringArrayIndirectlyWithoutType(getStringArrayDirectly()).length }
+
+            assert getStringArrayDirectly_Length() == getStringArrayIndirectlyWithoutType_Length()
+            assert getStringArrayDirectly_Length() == getStringArrayIndirectlyWithType_Length()
+            assert getStringArrayIndirectlyWithType_Length() == getStringArrayIndirectlyWithoutType_Length()
+        '''
+    }
+
+    // GROOVY-6189, GROOVY-9852
+    void testSAMs() {
+        // simple direct case
+        assertScript '''
+            interface MySAM {
+                def someMethod()
+            }
+            def foo(MySAM sam) {sam.someMethod()}
+            assert foo {1} == 1
+        '''
+
+        // overloads with classes implemented by Closure
+        [
+            'groovy.lang.Closure'            : 'not',
+            'groovy.lang.GroovyCallable'     : 'not',
+            'groovy.lang.GroovyObject'       : 'not',
+            'groovy.lang.GroovyObjectSupport': 'not',
+
+            'java.lang.Object'               : 'sam',
+            'java.lang.Runnable'             : 'not',
+            'java.lang.Cloneable'            : 'not',
+            'java.io.Serializable'           : 'not',
+            'java.util.concurrent.Callable'  : 'not',
+        ].each { type, which ->
+            assertScript """
+                interface MySAM {
+                    def someMethod()
+                }
+                def foo($type ref) { 'not' }
+                def foo(MySAM sam) { sam.someMethod() }
+                assert foo { 'sam' } == '$which' : '$type'
+            """
+        }
+    }
 }
diff --git a/src/test/groovy/ThisAndSuperTest.groovy b/src/test/groovy/ThisAndSuperTest.groovy
index b948b76..79ecefc 100644
--- a/src/test/groovy/ThisAndSuperTest.groovy
+++ b/src/test/groovy/ThisAndSuperTest.groovy
@@ -18,6 +18,7 @@
  */
 package groovy
 
+import org.junit.Ignore
 import org.junit.Test
 
 import static groovy.test.GroovyAssert.assertScript
@@ -39,10 +40,10 @@ final class ThisAndSuperTest {
         assert helper.aClosureUsingSuper() == 1
         // accessing private method should not be changed
         // by a public method of the same name and signature!
-        assert helper.closureUsingPrivateMethod() == "bar"
-        assert helper.bar() == "no bar"
+        assert helper.closureUsingPrivateMethod() == 'bar'
+        assert helper.bar() == 'no bar'
 
-        assert helper.aField == "I am a field"
+        assert helper.aField == 'I am a field'
         helper.closureFieldAccessUsingImplicitThis(1)
         assert helper.aField == 1
         helper.closureFieldAccessUsingExplicitThis(2)
@@ -54,13 +55,13 @@ final class ThisAndSuperTest {
         def map = [:]
         def helper = new TestForSuperHelper2()
 
-        helper.aField = "I am a field"
+        helper.aField = 'I am a field'
         helper.closureFieldAccessUsingExplicitThis.delegate = map
         helper.closureFieldAccessUsingExplicitThis(3)
         assert helper.aField == 3
         assert map.aField == null
 
-        helper.aField = "I am a field"
+        helper.aField = 'I am a field'
         helper.closureFieldAccessUsingImplicitThis.delegate = map
         helper.closureFieldAccessUsingImplicitThis(4)
         assert helper.aField == 4
@@ -87,49 +88,333 @@ final class ThisAndSuperTest {
 
     @Test
     void testConstructorChain() {
-        def helper = new TestForSuperHelper4()
-        assert helper.x == 1
-        helper = new TestForSuperHelper4("foo")
-        assert helper.x == "Object"
+        assertScript '''
+            class TestForSuperHelper3 {
+                def x
+
+                TestForSuperHelper3(int i) {
+                    this("1")
+                    x = 1
+                }
+
+                TestForSuperHelper3(Object j) {
+                    x = "Object"
+                }
+            }
+
+            class TestForSuperHelper4 extends TestForSuperHelper3 {
+                TestForSuperHelper4() {
+                    super(1)
+                }
+
+                TestForSuperHelper4(Object j) {
+                    super(j)
+                }
+            }
+
+            def helper = new TestForSuperHelper4()
+            assert helper.x == 1
+            helper = new TestForSuperHelper4("foo")
+            assert helper.x == "Object"
+        '''
     }
 
     @Test
-    void testChainingForAsType() {
-        def helper = new TestForSuperHelper1()
-        def ret = helper as Object[]
-        assert ret instanceof Object[]
-        assert ret[0] == helper
-
-        shouldFail(ClassCastException) {
-            helper as Integer
-        }
+    void testSuperAsTypeChain() {
+        assertScript '''
+            class C {
+                def <T> T asType(Class<T> type) {
+                    if (type == Object[]) {
+                        return [this] as Object[]
+                    }
+                    return super.asType(type)
+                }
+            }
+
+            def x = new C()
+            def y = x as Object[]
+            assert y instanceof Object[]
+            assert y[0] === x
+
+            try {
+                x as Integer
+                assert false : 'should fail'
+            } catch (ClassCastException cce) {
+            }
+        '''
     }
 
     @Test
-    void testSuperEach() {
-        def x = new TestForSuperEach()
-        x.each {
-            x.res << "I am it: ${it.class.name}"
-        }
+    void testSuperEachChain() {
+        assertScript '''
+            class C {
+                def out = []
+                def each(Closure c) {
+                    out << "start each in subclass"
+                    super.each(c)
+                    out << "end of each in subclass"
+                }
+            }
+
+            def x = new C()
+            x.each {
+                x.out << "I am ${it.class.name}"
+            }
+
+            assert x.out.size() == 3
+            assert x.out[0] == "start each in subclass"
+            assert x.out[1] == "I am C"
+            assert x.out[2] == "end of each in subclass"
+        '''
+    }
 
-        assert x.res.size() == 3
-        assert x.res[0] == "start each in subclass"
-        assert x.res[1] == "I am it: groovy.TestForSuperEach"
-        assert x.res[2] == "end of each in subclass"
+    @Ignore @Test // GROOVY-5285
+    void testSuperSetMetaClassChain() {
+        assertScript '''
+            class C {
+                void setMetaClass(MetaClass metaClass) {
+                    super.setMetaClass(metaClass)
+                }
+            }
+
+            def x = new C()
+            x.metaClass = x.metaClass
+        '''
+    }
+
+    @Test // GROOVY-9884
+    void testSuperSetPropertyChain() {
+        assertScript '''
+            class A {
+                def p = "p"
+            }
+            class B extends A {
+            }
+            class C extends B {
+                void setProperty(String name, Object value) {
+                    super.setProperty(name, value)
+                }
+            }
+
+            def x = new C()
+            x.setProperty("p", "q")
+            assert x.p == "q"
+        '''
     }
 
     @Test // GROOVY-2555
-    void testAbstractSuperMethodShouldBeTreatedLikeMissingMethod() {
+    void testSuperDotAbstractMethodShouldBeTreatedLikeMissingMethod() {
         shouldFail MissingMethodException, '''
             abstract class A {
                 abstract void m()
             }
-            class B extends A {
+            class C extends A {
                 void m() {
                     super.m()
                 }
             }
-            new B().m()
+            new C().m()
+        '''
+    }
+
+    @Test // GROOVY-4945
+    void testSuperDotSelfMethod1() {
+        def err = shouldFail MissingMethodException, '''
+            class C {
+                void test() {
+                    super.whatever()
+                }
+                void whatever() {
+                    assert false : 'should not have been called!'
+                }
+            }
+            new C().test()
+        '''
+        assert err =~ /No signature of method: java\.lang\.Object\.whatever\(\) is applicable for argument types: \(\) values: \[\]/
+    }
+
+    @Test // GROOVY-9615
+    void testSuperDotSelfMethod2() {
+        def err = shouldFail MissingMethodException, '''
+            class Outer {
+                class Inner {
+                    void test() {
+                        super.whatever()
+                    }
+                }
+                void whatever() {
+                    assert false : 'should not have been called!'
+                }
+            }
+            new Outer.Inner(new Outer()).test()
+        '''
+        assert err =~ /No signature of method: java\.lang\.Object\.whatever\(\) is applicable for argument types: \(\) values: \[\]/
+    }
+
+    @Test // GROOVY-6001
+    void testSuperDotMethod1() {
+        assertScript '''
+            class The {
+                static log = ""
+            }
+
+            interface PortletRequest {}
+            interface PortletResponse {}
+            interface HttpServletRequest {}
+            interface HttpServletResponse {}
+            class HttpServletRequestWrapper implements HttpServletRequest {}
+            class PortletRequestImpl extends HttpServletRequestWrapper implements PortletRequest {}
+            class ClientDataRequestImpl extends PortletRequestImpl {}
+            class ResourceRequestImpl extends ClientDataRequestImpl {}
+            class Application {}
+            interface HttpServletRequestListener {
+                void onRequestStart(HttpServletRequest request, HttpServletResponse response);
+                void onRequestEnd(HttpServletRequest request, HttpServletResponse response);
+            }
+            interface PortletRequestListener {
+                void onRequestStart(PortletRequest request, PortletResponse response);
+                void onRequestEnd(PortletRequest request, PortletResponse response);
+            }
+
+            class FooApplication extends Application implements HttpServletRequestListener, PortletRequestListener{
+                void onRequestStart(HttpServletRequest request, HttpServletResponse response) {
+                    The.log += "FooApplication.onRequestStart(HttpServletRequest, HttpServletResponse)"
+                }
+                void onRequestEnd(HttpServletRequest request, HttpServletResponse response) {
+                    The.log += "FooApplication.onRequestEnd(HttpServletRequest, HttpServletResponse)"
+                }
+                void onRequestStart(PortletRequest request, PortletResponse response) {
+                    The.log += "FooApplication.onRequestStart(PortletRequest, PortletResponse)"
+                }
+                void onRequestEnd(PortletRequest request, PortletResponse response) {
+                    The.log += "FooApplication.onRequestEnd(PortletRequest, PortletResponse)"
+                }
+            }
+
+            class BazApplication extends FooApplication {
+                void onRequestStart(PortletRequest request, PortletResponse response) {
+                    The.log += 'BazApplication.onRequestStart(PortletRequest, PortletResponse)'
+                    super.onRequestStart(request, response);
+                }
+            }
+
+            BazApplication application = new BazApplication()
+            The.log = ""
+            PortletRequest request = new ResourceRequestImpl()
+            application.onRequestStart(request, null)
+            assert The.log == "BazApplication.onRequestStart(PortletRequest, PortletResponse)FooApplication.onRequestStart(PortletRequest, PortletResponse)"
+        '''
+    }
+
+    @Test // GROOVY-7655
+    void testSuperDotMethod2() {
+        assertScript '''
+            class A {
+                def myMethod(item) {
+                    aCalled = true
+                    item+"A"
+                }
+                protected boolean aCalled
+            }
+
+            class B extends A {
+                def myMethod(item) {
+                    bCalled = true
+                    super.myMethod(item+"B")
+                }
+                protected boolean bCalled
+            }
+
+            class C extends B {
+                def cMethod(item) {
+                    cCalled = true
+                    super.myMethod(item+"C")
+                }
+                protected boolean cCalled
+            }
+
+            def c = new C()
+            c.cMethod("")
+
+            assert c.aCalled
+            assert c.bCalled
+            assert c.cCalled
+        '''
+    }
+
+    @Test // GROOVY-7655
+    void testSuperDotMethod3() {
+        assertScript '''
+            class A {
+                def myMethod(item) {
+                    aCalled = true
+                    item+"A"
+                }
+                protected boolean aCalled
+            }
+
+            class B extends A {
+                def myMethod(item) {
+                    bCalled = true
+                    super.myMethod(item+"B")
+                }
+                protected boolean bCalled
+            }
+
+            class C extends B {
+            }
+
+            class D extends C {
+                def dMethod( def item ) {
+                    dCalled = true
+                    super.myMethod(item+"D")
+                }
+                protected boolean dCalled
+            }
+
+            def d = new D()
+            d.dMethod("")
+
+            assert d.aCalled
+            assert d.bCalled
+            assert d.dCalled
+        '''
+    }
+
+    @Test // GROOVY-10302
+    void testSuperDotMethod4() {
+        shouldFail ClassNotFoundException, '''
+            void test() {
+                def list = []
+                def loader = new GroovyClassLoader() {
+                    @Override
+                    protected Class<?> findClass(String name) throws ClassNotFoundException {
+                        list.add(name); super.findClass(name)
+                    }
+                }
+                try (loader) {
+                    loader.findClass('foo.bar.Baz')
+                } finally {
+                    assert 'foo.bar.Baz' in list
+                }
+            }
+            test()
+        '''
+    }
+
+    @Test // GROOVY-9851
+    void testPrivateSuperMethod() {
+        shouldFail MissingMethodException, '''
+            abstract class A {
+                private x() {
+                }
+            }
+            class C extends A {
+                void m() {
+                    super.x()
+                }
+            }
+            new C().m()
         '''
     }
 
@@ -140,11 +425,11 @@ final class ThisAndSuperTest {
                 private x = 1
                 def getX() { 2 }
             }
-            class B extends A {
+            class C extends A {
                 private x = 3
                 def m() { super.@x }
             }
-            new B().m()
+            new C().m()
         '''
 
         assert err =~ /No such field: x for class: A/
@@ -158,11 +443,11 @@ final class ThisAndSuperTest {
                 def getX() { 2 }
                 void setX(x) { this.x = 3 }
             }
-            class B extends A {
+            class C extends A {
                 private x = 4
                 def m() { super.@x = 5; return x }
             }
-            new B().m()
+            new C().m()
         '''
 
         assert err =~ /No such field: x for class: A/
@@ -183,27 +468,12 @@ final class ThisAndSuperTest {
 
 //------------------------------------------------------------------------------
 
-class TestForSuperEach {
-    def res = []
-
-    def each(Closure c) {
-        res << "start each in subclass"
-        super.each(c)
-        res << "end of each in subclass"
-    }
-}
-
 class TestForSuperHelper1 {
     def foo() {1}
 
-    private bar() {"bar"}
+    private bar() {'bar'}
 
     def closureUsingPrivateMethod() {bar()}
-
-    def asType(Class c) {
-        if (c == Object[]) return [this] as Object[]
-        return super.asType(c)
-    }
 }
 
 class TestForSuperHelper2 extends TestForSuperHelper1 {
@@ -214,32 +484,9 @@ class TestForSuperHelper2 extends TestForSuperHelper1 {
     def aClosureUsingSuper = {super.foo()}
     def aClosureUsingThis = {this.foo()}
 
-    def bar() {"no bar"}
+    def bar() {'no bar'}
 
-    public aField = "I am a field"
+    public aField = 'I am a field'
     def closureFieldAccessUsingImplicitThis = {x -> aField = x}
     def closureFieldAccessUsingExplicitThis = {x -> this.aField = x}
 }
-
-class TestForSuperHelper3 {
-    def x
-
-    TestForSuperHelper3(int i) {
-        this("1")
-        x = 1
-    }
-
-    TestForSuperHelper3(Object j) {
-        x = "Object"
-    }
-}
-
-class TestForSuperHelper4 extends TestForSuperHelper3 {
-    TestForSuperHelper4() {
-        super(1)
-    }
-
-    TestForSuperHelper4(Object j) {
-        super(j)
-    }
-}
diff --git a/src/test/groovy/bugs/Groovy5212.groovy b/src/test/groovy/bugs/Groovy5212.groovy
new file mode 100644
index 0000000..8c2dc4e
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy5212.groovy
@@ -0,0 +1,62 @@
+/*
+ *  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 groovy.bugs
+
+import groovy.transform.AutoFinal
+import org.codehaus.groovy.antlr.EnumHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.CompileUnit
+import org.codehaus.groovy.ast.ModuleNode
+import org.codehaus.groovy.tools.javac.JavaStubGenerator
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.shouldFail
+import static org.objectweb.asm.Opcodes.*
+
+@AutoFinal
+final class Groovy5212 {
+
+    @Test
+    void testShouldNotAllowExtendingEnum() {
+        shouldFail '''
+            enum MyEnum { X }
+            enum MyExtendedEnum extends MyEnum { Y }
+        '''
+    }
+
+    @Test
+    void testShouldNotAllowExtendingEnumWithClass() {
+        shouldFail '''
+            enum MyEnum { X }
+            class MyExtendedEnum extends MyEnum { }
+        '''
+    }
+
+    @Test
+    void testGeneratedEnumJavaStubShouldNotHaveFinalModifier() {
+        ClassNode cn = EnumHelper.makeEnumNode('MyEnum', ACC_PUBLIC, ClassNode.EMPTY_ARRAY, null)
+        ModuleNode module = new ModuleNode(new CompileUnit(null, null))
+        cn.module = module
+
+        String stub = new JavaStubGenerator(null).with {
+            generateClass(cn); javaStubCompilationUnitSet[0].getCharContent(true)
+        }
+        assert !(stub =~ /final/)
+    }
+}
diff --git a/src/test/groovy/bugs/Groovy5212Bug.groovy b/src/test/groovy/bugs/Groovy5212Bug.groovy
deleted file mode 100644
index 0f09cd0..0000000
--- a/src/test/groovy/bugs/Groovy5212Bug.groovy
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- *  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 groovy.bugs
-
-import groovy.test.GroovyTestCase
-import org.codehaus.groovy.ast.ClassNode
-import org.objectweb.asm.Opcodes
-import org.codehaus.groovy.tools.javac.JavaStubGenerator
-import org.codehaus.groovy.ast.ModuleNode
-import org.codehaus.groovy.ast.CompileUnit
-import org.codehaus.groovy.antlr.EnumHelper
-
-class Groovy5212Bug extends GroovyTestCase implements Opcodes {
-    File outputDir
-
-    @Override
-    protected void setUp() {
-        super.setUp()
-        outputDir = File.createTempFile("stub","groovy")
-        outputDir.mkdirs()
-    }
-
-    @Override
-    protected void tearDown() {
-        super.tearDown()
-        outputDir.delete()
-    }
-
-    void testGeneratedEnumJavaStubShouldNotHaveFinalModifier() {
-        ClassNode cn = EnumHelper.makeEnumNode("MyEnum", ACC_PUBLIC, ClassNode.EMPTY_ARRAY, null)
-        ModuleNode module = new ModuleNode(new CompileUnit(null,null))
-        cn.setModule(module)
-        StringWriter wrt = new StringWriter()
-        PrintWriter out = new PrintWriter(wrt)
-        JavaStubGenerator generator = new StringJavaStubGenerator(outputDir, out)
-        generator.generateClass(cn)
-
-        String stub = wrt.toString()
-        assert !(stub =~ /final/)
-    }
-
-    void testShouldNotAllowExtendingEnum() {
-        shouldFail {
-            assertScript '''
-                enum MyEnum { a }
-                enum MyExtendedEnum extends MyEnum { b }
-            '''
-        }
-    }
-
-    void testShouldNotAllowExtendingEnumWithClass() {
-        shouldFail {
-            assertScript '''
-                enum MyEnum { a }
-                class MyExtendedEnum extends MyEnum { }
-                new MyExtendedEnum()
-            '''
-        }
-    }
-
-    /**
-     * Helper class, which generates code in a string instead of an output file.
-     */
-    private static final class StringJavaStubGenerator extends JavaStubGenerator {
-        PrintWriter out
-        StringJavaStubGenerator(File outFile, PrintWriter out) {
-            super(outFile)
-            this.out = out
-        }
-        public void generateClass(ClassNode classNode) throws FileNotFoundException {
-
-
-            try {
-                String packageName = classNode.getPackageName();
-                if (packageName != null) {
-                    out.println("package " + packageName + ";\n");
-                }
-
-                super.printImports(out, classNode);
-                super.printClassContents(out, classNode);
-
-            } finally {
-                try {
-                    out.close();
-                } catch (Exception e) {
-                    // ignore
-                }
-            }
-        }
-    }
-}
diff --git a/src/test/groovy/bugs/Groovy5260Bug.groovy b/src/test/groovy/bugs/Groovy5260.groovy
similarity index 63%
rename from src/test/groovy/bugs/Groovy5260Bug.groovy
rename to src/test/groovy/bugs/Groovy5260.groovy
index b193651..60dffde 100644
--- a/src/test/groovy/bugs/Groovy5260Bug.groovy
+++ b/src/test/groovy/bugs/Groovy5260.groovy
@@ -18,59 +18,51 @@
  */
 package groovy.bugs
 
-import groovy.test.GroovyTestCase
 import groovy.transform.AutoFinal
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.ast.CompileUnit
 import org.codehaus.groovy.ast.ModuleNode
 import org.codehaus.groovy.tools.javac.JavaStubGenerator
+import org.junit.Test
 
 import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
 import static org.objectweb.asm.Opcodes.*
 
 @AutoFinal
-final class Groovy5260Bug extends GroovyTestCase {
-
-    private File outputDir
-
-    @Override
-    protected void setUp() {
-        super.setUp()
-        outputDir = File.createTempFile('stub', 'groovy')
-        outputDir.mkdirs()
-    }
-
-    @Override
-    protected void tearDown() {
-        super.tearDown()
-        outputDir.delete()
-    }
+final class Groovy5260 {
 
+    @Test
     void testIntConstant() {
         checkConstant(ClassHelper.int_TYPE, 666, /final int constant = 666;/)
     }
 
+    @Test
     void testLongConstant() {
         checkConstant(ClassHelper.long_TYPE, 666, 'final long constant = 666L;')
     }
 
+    @Test
     void testFloatConstant() {
         checkConstant(ClassHelper.float_TYPE, 3.14f, 'final float constant = 3.14f;')
     }
 
+    @Test
     void testByteConstant() {
         checkConstant(ClassHelper.byte_TYPE, 123, /final byte constant = \(byte\)123;/)
     }
 
+    @Test
     void testBooleanConstant() {
         checkConstant(ClassHelper.boolean_TYPE, true, 'final boolean constant = true;')
     }
 
+    @Test
     void testCharConstant() {
         checkConstant(ClassHelper.char_TYPE, 'c', 'final char constant = \'c\';')
     }
 
+    @Test
     void testStringConstant() {
         checkConstant(ClassHelper.STRING_TYPE, 'foo', 'final java.lang.String constant = "foo";')
     }
@@ -84,38 +76,11 @@ final class Groovy5260Bug extends GroovyTestCase {
         ClassNode cn = new ClassNode('script', ACC_PUBLIC, ClassHelper.OBJECT_TYPE)
         cn.addField('constant', ACC_PUBLIC | ACC_STATIC | ACC_FINAL, constant.type, constant)
         ModuleNode module = new ModuleNode(new CompileUnit(null, null))
-        cn.setModule(module)
-
-        StringWriter wrt = new StringWriter()
-        PrintWriter out = new PrintWriter(wrt)
-        JavaStubGenerator generator = new StringJavaStubGenerator(outputDir, out)
-        generator.generateClass(cn)
-
-        String stub = wrt.toString()
-        assert (stub =~ expectation)
-    }
-
-    /**
-     * Helper class, which generates code in a string instead of an output file.
-     */
-    private static final class StringJavaStubGenerator extends JavaStubGenerator {
-
-        PrintWriter out
-
-        StringJavaStubGenerator(File outFile, PrintWriter out) {
-            super(outFile)
-            this.out = out
-        }
+        cn.module = module
 
-        public void generateClass(ClassNode classNode) throws FileNotFoundException {
-            try (out) {
-                String packageName = classNode.getPackageName();
-                if (packageName != null) {
-                    out.println("package " + packageName + ";\n");
-                }
-                super.printImports(out, classNode);
-                super.printClassContents(out, classNode);
-            }
+        String stub = new JavaStubGenerator(null).with {
+            generateClass(cn); javaStubCompilationUnitSet[0].getCharContent(true)
         }
+        assert stub =~ expectation
     }
 }
diff --git a/src/test/groovy/bugs/Groovy9615.groovy b/src/test/groovy/bugs/Groovy9615.groovy
deleted file mode 100644
index fd8e6ab..0000000
--- a/src/test/groovy/bugs/Groovy9615.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- *  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 groovy.bugs
-
-import org.junit.Test
-
-import static groovy.test.GroovyAssert.shouldFail
-
-final class Groovy9615 {
-
-    @Test // GROOVY-4945
-    void testCallSuperMethod1() {
-        def err = shouldFail MissingMethodException, '''
-            class C {
-                void test() {
-                    super.whatever()
-                }
-                void whatever() {
-                    assert false : 'should not have been called!'
-                }
-            }
-            new C().test()
-        '''
-
-        assert err =~ /No signature of method: java\.lang\.Object\.whatever\(\) is applicable for argument types: \(\) values: \[\]
-Possible solutions: every\(\)$/
-    }
-
-    @Test // GROOVY-9615
-    void testCallSuperMethod2() {
-        def err = shouldFail MissingMethodException, '''
-            class Outer {
-                class Inner {
-                    void test() {
-                        super.whatever()
-                    }
-                }
-                void whatever() {
-                    assert false : 'should not have been called!'
-                }
-            }
-            new Outer.Inner(new Outer()).test()
-        '''
-
-        assert err =~ /No signature of method: java\.lang\.Object\.whatever\(\) is applicable for argument types: \(\) values: \[\]/
-    }
-}
diff --git a/src/test/groovy/transform/stc/BugsSTCTest.groovy b/src/test/groovy/transform/stc/BugsSTCTest.groovy
index 07c965f..c405b65 100644
--- a/src/test/groovy/transform/stc/BugsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/BugsSTCTest.groovy
@@ -1055,6 +1055,35 @@ Printer
         '''
     }
 
+    // GROOVY-8693
+    void testInvokeDefaultMethodFromPackagePrivateInterface2() {
+        assertScript '''
+            class C extends groovy.transform.stc.Groovy10380 {
+                @Override String m() {
+                    super.m() // default method
+                }
+                void test() {
+                    String result = this.m()
+                    assert result == 'works'
+                }
+            }
+            new C().test()
+        '''
+    }
+
+    // GROOVY-9909
+    void testInvokeDefaultMethodFromDirectInterface() {
+        assertScript '''
+            class C implements groovy.transform.stc.Groovy9909 {
+                @Override String m() {
+                    'it ' + super.m() // default method
+                }
+            }
+            String result = new C().m()
+            assert result == 'it works'
+        '''
+    }
+
     void testInvokeSuperMethodFromCovariantOverride() {
         assertScript '''
             abstract class A { int i = 0
diff --git a/src/test/groovy/bugs/Groovy5285Bug.groovy b/src/test/groovy/transform/stc/Groovy9909.java
similarity index 66%
rename from src/test/groovy/bugs/Groovy5285Bug.groovy
rename to src/test/groovy/transform/stc/Groovy9909.java
index a704627..83d5913 100644
--- a/src/test/groovy/bugs/Groovy5285Bug.groovy
+++ b/src/test/groovy/transform/stc/Groovy9909.java
@@ -16,21 +16,10 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package groovy.bugs
+package groovy.transform.stc;
 
-import groovy.test.GroovyTestCase
-
-class Groovy5285Bug extends GroovyTestCase {
-    void testShouldNotThrowStackOverflow() {
-        assertScript '''
-            class Test {
-                void setMetaClass(MetaClass metaClass) {
-                    super.setMetaClass(metaClass)
-                }
-            }
-
-            def obj = new Test()
-            obj.metaClass = obj.metaClass
-        '''
+public interface Groovy9909 {
+    default String m() {
+        return "works";
     }
 }
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy
index 77abc66..5576b16 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/BugsStaticCompileTest.groovy
@@ -1524,20 +1524,4 @@ println someInt
             test()
         '''
     }
-
-    // GROOVY-8693
-    void testInvokeDefaultMethodFromPackagePrivateInterface2() {
-        assertScript '''
-            class C extends groovy.transform.stc.Groovy10380 {
-                @Override String m() {
-                    super.m() // default method
-                }
-                void test() {
-                    String result = this.m()
-                    assert result == 'works'
-                }
-            }
-            new C().test()
-        '''
-    }
 }