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

[groovy] 01/01: Tweak the initialization of permitted subclasses

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

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

commit e24c02fe2b45faa70048574beb0d4a70c0e02810
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Aug 8 21:04:53 2021 +0800

    Tweak the initialization of permitted subclasses
---
 .../java/org/codehaus/groovy/ast/ClassNode.java    | 27 ++++++++++++++------
 .../groovy/reflection/ReflectionUtils.java         | 29 +++++++++++++++++++---
 .../transform/DelegateASTTransformation.java       |  2 +-
 .../org/codehaus/groovy/vmplugin/v8/Java8.java     | 11 ++++++++
 .../org/codehaus/groovy/ast/ClassNodeTest.java     |  9 +++++++
 5 files changed, 67 insertions(+), 11 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index 87b278f..d7d6c73 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -29,7 +29,6 @@ import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.ast.tools.ParameterUtils;
 import org.codehaus.groovy.control.CompilePhase;
-import org.codehaus.groovy.reflection.ReflectionUtils;
 import org.codehaus.groovy.transform.ASTTransformation;
 import org.codehaus.groovy.transform.GroovyASTTransformation;
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
@@ -148,6 +147,7 @@ public class ClassNode extends AnnotatedNode {
     private int modifiers;
     private boolean syntheticPublic;
     private ClassNode[] interfaces;
+    private List<ClassNode> permittedSubclasses = new ArrayList<>(4);
     private MixinNode[] mixins;
     private List<Statement> objectInitializers;
     private List<ConstructorNode> constructors;
@@ -164,7 +164,6 @@ public class ClassNode extends AnnotatedNode {
     private ClassNode superClass;
     protected boolean isPrimaryNode;
     protected List<InnerClassNode> innerClasses;
-    private final List<ClassNode> permittedSubclasses = new ArrayList<>(4);
     private List<AnnotationNode> typeAnnotations = Collections.emptyList();
 
     /**
@@ -244,10 +243,6 @@ public class ClassNode extends AnnotatedNode {
         return redirect().isPrimaryNode || (componentType != null && componentType.isPrimaryClassNode());
     }
 
-    public List<ClassNode> getPermittedSubclasses() {
-        return redirect().permittedSubclasses;
-    }
-
     /**
      * Constructor used by {@code makeArray()} if no real class is available.
      */
@@ -393,6 +388,24 @@ public class ClassNode extends AnnotatedNode {
     }
 
     /**
+     * @return permitted subclasses of sealed type
+     */
+    public List<ClassNode> getPermittedSubclasses() {
+        if (redirect != null)
+            return redirect.getPermittedSubclasses();
+        lazyClassInit();
+        return permittedSubclasses;
+    }
+
+    public void setPermittedSubclasses(List<ClassNode> permittedSubclasses) {
+        if (redirect != null) {
+            redirect.setPermittedSubclasses(permittedSubclasses);
+        } else {
+            this.permittedSubclasses = permittedSubclasses;
+        }
+    }
+
+    /**
      * @return the mixins associated with this {@code ClassNode}
      */
     public MixinNode[] getMixins() {
@@ -1349,7 +1362,7 @@ public class ClassNode extends AnnotatedNode {
     }
 
     public boolean isSealed() {
-        return !getAnnotations(SEALED_TYPE).isEmpty() || !permittedSubclasses.isEmpty() || (null != clazz && ReflectionUtils.isSealed(clazz));
+        return !getAnnotations(SEALED_TYPE).isEmpty() || !getPermittedSubclasses().isEmpty();
     }
 
     public boolean isResolved() {
diff --git a/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java b/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
index bdb23c2..0057c02 100644
--- a/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
+++ b/src/main/java/org/codehaus/groovy/reflection/ReflectionUtils.java
@@ -51,6 +51,8 @@ public class ReflectionUtils {
 
     /** The packages in the call stack that are only part of the Groovy MOP. */
     private static final Set<String> IGNORED_PACKAGES;
+    private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+
     static {
         Set<String> set = new HashSet<>();
 
@@ -240,6 +242,7 @@ public class ReflectionUtils {
 
     public static boolean isSealed(Class<?> clazz) {
         if (null == IS_SEALED_METHODHANDLE) return false;
+        if (null == clazz) return false;
 
         boolean sealed = false;
         try {
@@ -249,6 +252,18 @@ public class ReflectionUtils {
         return sealed;
     }
 
+    public static Class<?>[] getPermittedSubclasses(Class<?> clazz) {
+        if (null == GET_PERMITTED_SUBCLASSES_METHODHANDLE) return EMPTY_CLASS_ARRAY;
+        if (null == clazz) return EMPTY_CLASS_ARRAY;
+
+        Class<?>[] result = EMPTY_CLASS_ARRAY;
+        try {
+            result = (Class<?>[]) GET_PERMITTED_SUBCLASSES_METHODHANDLE.bindTo(clazz).invokeExact();
+        } catch (Throwable ignored) {
+        }
+        return result;
+    }
+
     private static boolean classShouldBeIgnored(final Class c, final Collection<String> extraIgnoredPackages) {
         return (c != null
                 && (c.isSynthetic()
@@ -265,12 +280,20 @@ public class ReflectionUtils {
     }
 
     private static final MethodHandle IS_SEALED_METHODHANDLE;
+    private static final MethodHandle GET_PERMITTED_SUBCLASSES_METHODHANDLE;
     static {
-        MethodHandle mh = null;
+        MethodHandle isSealedMethodHandle = null;
+        try {
+            isSealedMethodHandle = MethodHandles.lookup().findVirtual(Class.class, "isSealed", MethodType.methodType(boolean.class, new Class[0]));
+        } catch (NoSuchMethodException | IllegalAccessException ignored) {
+        }
+        IS_SEALED_METHODHANDLE = isSealedMethodHandle;
+
+        MethodHandle getPermittedSubclassesMethodHandle = null;
         try {
-            mh = MethodHandles.lookup().findVirtual(Class.class, "isSealed", MethodType.methodType(boolean.class, new Class[0]));
+            getPermittedSubclassesMethodHandle = MethodHandles.lookup().findVirtual(Class.class, "getPermittedSubclasses", MethodType.methodType(Class[].class, new Class[0]));
         } catch (NoSuchMethodException | IllegalAccessException ignored) {
         }
-        IS_SEALED_METHODHANDLE = mh;
+        GET_PERMITTED_SUBCLASSES_METHODHANDLE = getPermittedSubclassesMethodHandle;
     }
 }
diff --git a/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java
index af03683..4080e31 100644
--- a/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java
+++ b/src/main/java/org/codehaus/groovy/transform/DelegateASTTransformation.java
@@ -200,7 +200,7 @@ public class DelegateASTTransformation extends AbstractASTTransformation {
 
             final Set<ClassNode> allInterfaces =
                     getInterfacesAndSuperInterfaces(delegate.type).stream()
-                            .filter(c -> !c.redirect().isSealed())
+                            .filter(c -> !c.isSealed())
                             .collect(Collectors.toSet());
             final Set<ClassNode> ownerIfaces = delegate.owner.getAllInterfaces();
             Map<String,ClassNode> genericsSpec = createGenericsSpec(delegate.owner);
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
index ff6da29..18fd483 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
@@ -69,8 +69,10 @@ import java.lang.reflect.WildcardType;
 import java.security.AccessController;
 import java.security.Permission;
 import java.security.PrivilegedAction;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * Java 8 based functions.
@@ -456,6 +458,7 @@ public class Java8 implements VMPlugin {
             Class<?> sc = clazz.getSuperclass();
             if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit, clazz.getGenericSuperclass(), sc));
             makeInterfaceTypes(compileUnit, classNode, clazz);
+            makePermittedSubclasses(compileUnit, classNode, clazz);
             setAnnotationMetaData(clazz.getAnnotations(), classNode);
 
             PackageNode packageNode = classNode.getPackage();
@@ -514,6 +517,14 @@ public class Java8 implements VMPlugin {
         return annotations;
     }
 
+    private void makePermittedSubclasses(CompileUnit cu, ClassNode classNode, Class<?> clazz) {
+        if (!ReflectionUtils.isSealed(clazz)) return;
+        List<ClassNode> permittedSubclasses = Arrays.stream(ReflectionUtils.getPermittedSubclasses(clazz))
+                .map(c -> makeClassNode(cu, c, c))
+                .collect(Collectors.toList());
+        classNode.setPermittedSubclasses(permittedSubclasses);
+    }
+
     private void makeInterfaceTypes(CompileUnit cu, ClassNode classNode, Class<?> clazz) {
         Type[] interfaceTypes = clazz.getGenericInterfaces();
         if (interfaceTypes.length == 0) {
diff --git a/src/test/org/codehaus/groovy/ast/ClassNodeTest.java b/src/test/org/codehaus/groovy/ast/ClassNodeTest.java
index 864d8a2..22eaef1 100644
--- a/src/test/org/codehaus/groovy/ast/ClassNodeTest.java
+++ b/src/test/org/codehaus/groovy/ast/ClassNodeTest.java
@@ -21,6 +21,9 @@ package org.codehaus.groovy.ast;
 import junit.framework.TestCase;
 import org.objectweb.asm.Opcodes;
 
+import static groovy.test.GroovyAssert.isAtLeastJdk;
+import static org.junit.Assume.assumeTrue;
+
 /**
  * Tests the ClassNode
  */
@@ -49,4 +52,10 @@ public class ClassNodeTest extends TestCase implements Opcodes {
         ClassNode packageNode = new ClassNode("com.acme.Foo", ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
         assertEquals("Package", "com.acme", packageNode.getPackageName());
     }
+
+    public void testPermittedSubclasses() throws ClassNotFoundException {
+        assumeTrue(isAtLeastJdk("12.0"));
+        Class<?> clazz = Class.forName("java.lang.constant.ConstantDesc");
+        assertTrue(!new ClassNode(clazz).getPermittedSubclasses().isEmpty());
+    }
 }