From 13bb22c4f23af74322551cf139b766414dc1ea84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Tue, 2 Nov 2021 15:24:42 +0100 Subject: [PATCH] Implement sealed class support Add agent support for getPermittedSubclasses Store permittedSubclasses to reflection data. --- .../graalvm/compiler/core/common/NumUtil.java | 7 +- .../svm/agent/BreakpointInterceptor.java | 8 +- .../configure/config/ConfigurationType.java | 17 +++- .../config/ParserConfigurationAdapter.java | 7 +- .../configure/trace/ReflectionProcessor.java | 4 + .../jdk17/SealedClassSupportJDK17OrLater.java | 60 ++++++++++++++ .../ReflectionConfigurationParser.java | 9 +- ...ReflectionConfigurationParserDelegate.java | 4 +- .../com/oracle/svm/core/hub/DynamicHub.java | 53 +++++++++--- .../svm/core/hub/DynamicHubCompanion.java | 3 +- .../svm/core/jdk/SealedClassSupport.java | 83 +++++++++++++++++++ .../src/com/oracle/svm/hosted/SVMHost.java | 5 +- .../config/ReflectionRegistryAdapter.java | 11 ++- .../reflect/hosted/ReflectionDataBuilder.java | 17 +++- 14 files changed, 261 insertions(+), 27 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core.jdk17/src/com/oracle/svm/core/jdk17/SealedClassSupportJDK17OrLater.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SealedClassSupport.java diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java index cdc5e44ae124..ccb2dea3289d 100644 --- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java +++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/NumUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -105,6 +105,11 @@ public static boolean is32bit(long x) { return -0x80000000L <= x && x < 0x80000000L; } + public static byte safeToUByte(int v) { + assert isUByte(v); + return (byte) v; + } + public static byte safeToByte(int v) { assert isByte(v); return (byte) v; diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 011a68a0d3b6..2b8f2fd4b5be 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -255,6 +255,10 @@ private static boolean getDeclaredClasses(JNIEnvironment jni, Breakpoint bp, Int return handleGetClasses(jni, bp, state); } + private static boolean getPermittedSubclasses(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { + return handleGetClasses(jni, bp, state); + } + private static boolean handleGetClasses(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { JNIObjectHandle callerClass = state.getDirectCallerClass(); JNIObjectHandle self = getObjectArgument(0); @@ -1520,7 +1524,9 @@ private interface BreakpointHandler { BreakpointInterceptor::constantBootstrapGetStaticFinal), optionalBrk("java/lang/invoke/MethodType", "fromMethodDescriptorString", "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;", - BreakpointInterceptor::methodTypeFromDescriptor) + BreakpointInterceptor::methodTypeFromDescriptor), + optionalBrk("java/lang/Class", "getPermittedSubclasses", "()[Ljava/lang/Class;", + BreakpointInterceptor::getPermittedSubclasses) }; private static final BreakpointSpecification CLASSLOADER_LOAD_CLASS_BREAKPOINT_SPECIFICATION = optionalBrk("java/lang/ClassLoader", "loadClass", diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 960010051069..5245033e81eb 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,6 +74,7 @@ static ConfigurationType copyAndSubtract(ConfigurationType type, ConfigurationTy private Map methods; private boolean allDeclaredClasses; + private boolean allPermittedSubclasses; private boolean allPublicClasses; private boolean allDeclaredFields; private boolean allPublicFields; @@ -211,6 +212,7 @@ private void removeMethods(ConfigurationType other) { private void setFlagsFromOther(ConfigurationType other, BiPredicate flagPredicate, BiFunction accessCombiner) { allDeclaredClasses = flagPredicate.test(allDeclaredClasses, other.allDeclaredClasses); + allPermittedSubclasses = flagPredicate.test(allPermittedSubclasses, other.allPermittedSubclasses); allPublicClasses = flagPredicate.test(allPublicClasses, other.allPublicClasses); allDeclaredFields = flagPredicate.test(allDeclaredFields, other.allDeclaredFields); allPublicFields = flagPredicate.test(allPublicFields, other.allPublicFields); @@ -225,7 +227,7 @@ private boolean isEmpty() { } private boolean allFlagsFalse() { - return !(allDeclaredClasses || allPublicClasses || allDeclaredFields || allPublicFields || + return !(allDeclaredClasses || allPermittedSubclasses || allPublicClasses || allDeclaredFields || allPublicFields || allDeclaredMethodsAccess != ConfigurationMemberAccessibility.NONE || allPublicMethodsAccess != ConfigurationMemberAccessibility.NONE || allDeclaredConstructorsAccess != ConfigurationMemberAccessibility.NONE || allPublicConstructorsAccess != ConfigurationMemberAccessibility.NONE); } @@ -310,6 +312,10 @@ public synchronized void setAllDeclaredClasses() { allDeclaredClasses = true; } + public synchronized void setAllPermittedSubclasses() { + allPermittedSubclasses = true; + } + public synchronized void setAllPublicClasses() { allPublicClasses = true; } @@ -365,6 +371,7 @@ public synchronized void printJson(JsonWriter writer) throws IOException { optionallyPrintJsonBoolean(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors"); optionallyPrintJsonBoolean(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors"); optionallyPrintJsonBoolean(writer, allDeclaredClasses, "allDeclaredClasses"); + optionallyPrintJsonBoolean(writer, allPermittedSubclasses, "allPermittedSubclasses"); optionallyPrintJsonBoolean(writer, allPublicClasses, "allPublicClasses"); optionallyPrintJsonBoolean(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredMethods"); optionallyPrintJsonBoolean(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicMethods"); @@ -393,7 +400,7 @@ public synchronized void printJson(JsonWriter writer) throws IOException { } } - writer.append('}').unindent().newline(); + writer.unindent().newline().append('}'); } private Set getMethodsByAccessibility(ConfigurationMemberAccessibility accessibility) { @@ -463,6 +470,10 @@ public static boolean haveAllDeclaredClasses(ConfigurationType type) { return type.allDeclaredClasses; } + public static boolean haveAllPermittedSubclasses(ConfigurationType type) { + return type.allPermittedSubclasses; + } + public static boolean haveAllPublicClasses(ConfigurationType type) { return type.allPublicClasses; } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index 17884c0ccdf8..163c362ac8d7 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,6 +98,11 @@ public void registerDeclaredClasses(ConfigurationType type) { type.setAllDeclaredClasses(); } + @Override + public void registerPermittedSubclasses(ConfigurationType type) { + type.setAllPermittedSubclasses(); + } + @Override public void registerPublicFields(ConfigurationType type) { type.setAllPublicFields(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index e4eb9ffaf46a..623933ba661f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -156,6 +156,10 @@ public void processEntry(Map entry) { configuration.getOrCreateType(condition, clazz).setAllDeclaredClasses(); break; } + case "getPermittedSubclasses": { + configuration.getOrCreateType(condition, clazz).setAllPermittedSubclasses(); + break; + } case "getClasses": { configuration.getOrCreateType(condition, clazz).setAllPublicClasses(); break; diff --git a/substratevm/src/com.oracle.svm.core.jdk17/src/com/oracle/svm/core/jdk17/SealedClassSupportJDK17OrLater.java b/substratevm/src/com.oracle.svm.core.jdk17/src/com/oracle/svm/core/jdk17/SealedClassSupportJDK17OrLater.java new file mode 100644 index 000000000000..63a0c4f87b18 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.jdk17/src/com/oracle/svm/core/jdk17/SealedClassSupportJDK17OrLater.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk17; + +// Checkstyle: allow reflection + +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.jdk.SealedClassSupport; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; + +final class SealedClassSupportJDK17OrLater extends SealedClassSupport { + + @Override + public boolean isSealed(Class clazz) { + return clazz.isSealed(); + } + + @Override + public Class[] getPermittedSubclasses(Class clazz) { + return clazz.getPermittedSubclasses(); + } +} + +@AutomaticFeature +final class SealedClassFeatureJDK17OrLater implements Feature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return JavaVersionUtil.JAVA_SPEC >= 17; + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(SealedClassSupport.class, new SealedClassSupportJDK17OrLater()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 5af838f649de..872214ad2d6d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,7 +52,7 @@ public final class ReflectionConfigurationParser extends ConfigurationParser private final boolean allowIncompleteClasspath; private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", - "allDeclaredClasses", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, + "allDeclaredClasses", "allPermittedSubclasses", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods"); public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { @@ -139,6 +139,11 @@ private void parseClass(Map data) { delegate.registerDeclaredClasses(clazz); } break; + case "allPermittedSubclasses": + if (asBoolean(value, "allPermittedSubclasses")) { + delegate.registerPermittedSubclasses(clazz); + } + break; case "allPublicClasses": if (asBoolean(value, "allPublicClasses")) { delegate.registerPublicClasses(clazz); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 530950dfb595..f21bda93a86c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,6 +42,8 @@ public interface ReflectionConfigurationParserDelegate { void registerDeclaredClasses(T type); + void registerPermittedSubclasses(T type); + void registerPublicFields(T type); void registerDeclaredFields(T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 0acdec7d882b..e315a2042ef8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -166,7 +166,7 @@ public final class DynamicHub implements JavaKind.FormatWithToString, AnnotatedE /** Is this an interface. */ private static final int IS_INTERFACE_FLAG_BIT = 1; /** Is this a Hidden Class. */ - private static final int IS_HIDDED_FLAG_BIT = 2; + private static final int IS_HIDDEN_FLAG_BIT = 2; /** Is this a Record Class. */ private static final int IS_RECORD_FLAG_BIT = 3; /** Holds assertionStatus determined by {@link RuntimeAssertionsSupport}. */ @@ -183,7 +183,8 @@ public final class DynamicHub implements JavaKind.FormatWithToString, AnnotatedE * {@link ClassInitializationInfo}. */ private static final int DECLARES_DEFAULT_METHODS_FLAG_BIT = 6; - + /** Is this a Sealed Class. */ + private static final int IS_SEALED_FLAG_BIT = 7; /** * Has the type been discovered as instantiated by the static analysis? */ @@ -317,7 +318,7 @@ public void setModule(Object module) { @Platforms(Platform.HOSTED_ONLY.class) public DynamicHub(Class hostedJavaClass, String name, HubType hubType, ReferenceType referenceType, Object isLocalClass, Object isAnonymousClass, DynamicHub superType, DynamicHub componentHub, String sourceFileName, int modifiers, ClassLoader classLoader, boolean isHidden, boolean isRecord, Class nestHost, boolean assertionStatus, - boolean hasDefaultMethods, boolean declaresDefaultMethods) { + boolean hasDefaultMethods, boolean declaresDefaultMethods, boolean isSealed) { this.hostedJavaClass = hostedJavaClass; this.name = name; this.hubType = hubType.getValue(); @@ -331,13 +332,14 @@ public DynamicHub(Class hostedJavaClass, String name, HubType hubType, Refere this.classLoader = PredefinedClassesSupport.isPredefined(hostedJavaClass) ? NO_CLASS_LOADER : classLoader; this.nestHost = nestHost; - this.flags = NumUtil.safeToByte(makeFlag(IS_PRIMITIVE_FLAG_BIT, hostedJavaClass.isPrimitive()) | + this.flags = NumUtil.safeToUByte(makeFlag(IS_PRIMITIVE_FLAG_BIT, hostedJavaClass.isPrimitive()) | makeFlag(IS_INTERFACE_FLAG_BIT, hostedJavaClass.isInterface()) | - makeFlag(IS_HIDDED_FLAG_BIT, isHidden) | + makeFlag(IS_HIDDEN_FLAG_BIT, isHidden) | makeFlag(IS_RECORD_FLAG_BIT, isRecord) | makeFlag(ASSERTION_STATUS_FLAG_BIT, assertionStatus) | makeFlag(HAS_DEFAULT_METHODS_FLAG_BIT, hasDefaultMethods) | - makeFlag(DECLARES_DEFAULT_METHODS_FLAG_BIT, declaresDefaultMethods)); + makeFlag(DECLARES_DEFAULT_METHODS_FLAG_BIT, declaresDefaultMethods) | + makeFlag(IS_SEALED_FLAG_BIT, isSealed)); } @Platforms(Platform.HOSTED_ONLY.class) @@ -767,7 +769,7 @@ private boolean isAnonymousClass() { @Substitute @TargetElement(onlyWith = JDK17OrLater.class) public boolean isHidden() { - return isFlagSet(IS_HIDDED_FLAG_BIT); + return isFlagSet(IS_HIDDEN_FLAG_BIT); } @Substitute @@ -776,6 +778,12 @@ public boolean isRecord() { return isFlagSet(IS_RECORD_FLAG_BIT); } + @Substitute + @TargetElement(onlyWith = JDK17OrLater.class) + public boolean isSealed() { + return isFlagSet(IS_SEALED_FLAG_BIT); + } + @Substitute private boolean isLocalClass() { return booleanOrError(isLocalClass); @@ -986,19 +994,20 @@ public T getDeclaredAnnotation(Class annotationClass) */ public static final class ReflectionData { static final ReflectionData EMPTY = new ReflectionData(new Field[0], new Field[0], new Field[0], new Method[0], new Method[0], new Constructor[0], new Constructor[0], null, new Field[0], - new Method[0], new Class[0], new Class[0], null, null); + new Method[0], new Class[0], null, new Class[0], null, null); public static ReflectionData get(Field[] declaredFields, Field[] publicFields, Field[] publicUnhiddenFields, Method[] declaredMethods, Method[] publicMethods, Constructor[] declaredConstructors, Constructor[] publicConstructors, Constructor nullaryConstructor, Field[] declaredPublicFields, - Method[] declaredPublicMethods, Class[] declaredClasses, Class[] publicClasses, Executable enclosingMethodOrConstructor, Object[] recordComponents) { + Method[] declaredPublicMethods, Class[] declaredClasses, Class[] permittedSubclasses, Class[] publicClasses, Executable enclosingMethodOrConstructor, + Object[] recordComponents) { if (z(declaredFields) && z(publicFields) && z(publicUnhiddenFields) && z(declaredMethods) && z(publicMethods) && z(declaredConstructors) && z(publicConstructors) && nullaryConstructor == null && z(declaredPublicFields) && z(declaredPublicMethods) && z(declaredClasses) && - z(publicClasses) && enclosingMethodOrConstructor == null && (recordComponents == null || z(recordComponents))) { + permittedSubclasses == null && z(publicClasses) && enclosingMethodOrConstructor == null && (recordComponents == null || z(recordComponents))) { return EMPTY; // avoid redundant objects in image heap } return new ReflectionData(declaredFields, publicFields, publicUnhiddenFields, declaredMethods, publicMethods, declaredConstructors, publicConstructors, nullaryConstructor, - declaredPublicFields, declaredPublicMethods, declaredClasses, publicClasses, enclosingMethodOrConstructor, recordComponents); + declaredPublicFields, declaredPublicMethods, declaredClasses, permittedSubclasses, publicClasses, enclosingMethodOrConstructor, recordComponents); } private static boolean z(Object[] array) { // for better readability above @@ -1016,6 +1025,7 @@ private static boolean z(Object[] array) { // for better readability above final Field[] declaredPublicFields; final Method[] declaredPublicMethods; final Class[] declaredClasses; + final Class[] permittedSubclasses; final Class[] publicClasses; final Object[] recordComponents; @@ -1027,7 +1037,7 @@ private static boolean z(Object[] array) { // for better readability above ReflectionData(Field[] declaredFields, Field[] publicFields, Field[] publicUnhiddenFields, Method[] declaredMethods, Method[] publicMethods, Constructor[] declaredConstructors, Constructor[] publicConstructors, Constructor nullaryConstructor, Field[] declaredPublicFields, Method[] declaredPublicMethods, Class[] declaredClasses, - Class[] publicClasses, Executable enclosingMethodOrConstructor, + Class[] permittedSubclasses, Class[] publicClasses, Executable enclosingMethodOrConstructor, Object[] recordComponents) { this.declaredFields = declaredFields; this.publicFields = publicFields; @@ -1040,6 +1050,7 @@ private static boolean z(Object[] array) { // for better readability above this.declaredPublicFields = declaredPublicFields; this.declaredPublicMethods = declaredPublicMethods; this.declaredClasses = declaredClasses; + this.permittedSubclasses = permittedSubclasses; this.publicClasses = publicClasses; this.enclosingMethodOrConstructor = enclosingMethodOrConstructor; this.recordComponents = recordComponents; @@ -1188,6 +1199,22 @@ private Target_java_lang_reflect_RecordComponent[] getRecordComponents0() { return (Target_java_lang_reflect_RecordComponent[]) result; } + @Substitute + @TargetElement(onlyWith = JDK17OrLater.class) + private Class[] getPermittedSubclasses() { + /* + * We make several assumptions here: we precompute this value by using the cached value from + * image build time, agent run / custom reflection configuration is required, we ignore all + * classloader checks, and assume that cached result would be valid. + */ + if (rd.permittedSubclasses != null) { + for (Class clazz : rd.permittedSubclasses) { + PredefinedClassesSupport.throwIfUnresolvable(clazz, getClassLoader0()); + } + } + return rd.permittedSubclasses; + } + @Substitute @TargetElement(name = "checkMemberAccess", onlyWith = JDK8OrEarlier.class) @SuppressWarnings("unused") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java index 5db04a2066cb..8218432a3226 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java @@ -143,7 +143,8 @@ public ReflectionData getCompleteReflectionData() { completeReflectionData = new ReflectionData(hub.rd.declaredFields, hub.rd.publicFields, hub.rd.publicUnhiddenFields, newDeclaredMethods.toArray(new Method[0]), newPublicMethods.toArray(new Method[0]), newDeclaredConstructors.toArray(new Constructor[0]), newPublicConstructors.toArray(new Constructor[0]), hub.rd.nullaryConstructor, hub.rd.declaredPublicFields, - newDeclaredPublicMethods.toArray(new Method[0]), hub.rd.declaredClasses, hub.rd.publicClasses, hub.rd.enclosingMethodOrConstructor, hub.rd.recordComponents); + newDeclaredPublicMethods.toArray(new Method[0]), hub.rd.declaredClasses, hub.rd.permittedSubclasses, hub.rd.publicClasses, hub.rd.enclosingMethodOrConstructor, + hub.rd.recordComponents); } return completeReflectionData; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SealedClassSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SealedClassSupport.java new file mode 100644 index 000000000000..b1b3d452cfee --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SealedClassSupport.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +// Checkstyle: allow reflection + +import com.oracle.svm.core.annotate.AutomaticFeature; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + +/** + * Abstracts the information about sealed classes, which are not available in Java 11 and Java 8. + * This class provides all information about sealed classes without exposing any JDK types and + * methods that are not yet present in the old JDKs. + */ +@Platforms(Platform.HOSTED_ONLY.class) +public abstract class SealedClassSupport { + + public static SealedClassSupport singleton() { + return ImageSingletons.lookup(SealedClassSupport.class); + } + + /** Same as {@code Class.isSealed()}. */ + public abstract boolean isSealed(Class clazz); + + /** Same as {@code Class.getPermittedSubclasses()}. */ + public abstract Class[] getPermittedSubclasses(Class clazz); +} + +/** + * Placeholder implementation for JDK versions that do not have sealed classes. + */ +final class SealedClassSupportJDK11OrEarlier extends SealedClassSupport { + + @Override + public boolean isSealed(Class clazz) { + return false; + } + + @Override + public Class[] getPermittedSubclasses(Class clazz) { + return null; + } +} + +@AutomaticFeature +final class SealedClassFeatureJDK11OrEarlier implements Feature { + + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return JavaVersionUtil.JAVA_SPEC <= 11; + } + + @Override + public void afterRegistration(AfterRegistrationAccess access) { + ImageSingletons.add(SealedClassSupport.class, new SealedClassSupportJDK11OrEarlier()); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 6af08a8b481d..6cda94905328 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -45,6 +45,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; +import com.oracle.svm.core.jdk.SealedClassSupport; import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor; import org.graalvm.compiler.core.common.spi.ForeignCallsProvider; import org.graalvm.compiler.debug.MethodFilter; @@ -404,10 +405,12 @@ private DynamicHub createHub(AnalysisType type) { boolean isHidden = SubstrateUtil.isHiddenClass(javaClass); boolean isRecord = RecordSupport.singleton().isRecord(javaClass); boolean assertionStatus = RuntimeAssertionsSupport.singleton().desiredAssertionStatus(javaClass); + boolean isSealed = SealedClassSupport.singleton().isSealed(javaClass); final DynamicHub dynamicHub = new DynamicHub(javaClass, className, computeHubType(type), computeReferenceType(type), isLocalClass(javaClass), isAnonymousClass(javaClass), superHub, componentHub, sourceFileName, - modifiers, hubClassLoader, isHidden, isRecord, nestHost, assertionStatus, type.hasDefaultMethods(), type.declaresDefaultMethods()); + modifiers, hubClassLoader, isHidden, isRecord, nestHost, assertionStatus, type.hasDefaultMethods(), + type.declaresDefaultMethods(), isSealed); if (JavaVersionUtil.JAVA_SPEC > 8) { ModuleAccess.extractAndSetModule(dynamicHub, javaClass); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index 9e82b114b9a8..e44926182bb6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,7 @@ import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate; +import com.oracle.svm.core.jdk.SealedClassSupport; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.util.ClassUtil; @@ -87,6 +88,14 @@ public void registerDeclaredClasses(ConditionalElement> type) { registry.register(type.getCondition(), type.getElement().getDeclaredClasses()); } + @Override + public void registerPermittedSubclasses(ConditionalElement> type) { + Class[] classes = SealedClassSupport.singleton().getPermittedSubclasses(type.getElement()); + if (classes != null) { + registry.register(type.getCondition(), classes); + } + } + @Override public void registerPublicFields(ConditionalElement> type) { registry.register(type.getCondition(), false, type.getElement().getFields()); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index e7d522fd282e..e584db56523b 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,6 +50,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import com.oracle.svm.core.jdk.SealedClassSupport; import org.graalvm.compiler.debug.GraalError; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; @@ -132,6 +133,7 @@ private static DynamicHub.ReflectionData getArrayReflectionData() { EMPTY_FIELDS, EMPTY_METHODS, EMPTY_CLASSES, + null, EMPTY_CLASSES, null, null); @@ -495,6 +497,7 @@ private void processClass(DuringAnalysisAccessImpl access, Class clazz) { query(clazz::getDeclaredConstructors, errors); query(clazz::getConstructors, errors); Class[] declaredClasses = query(clazz::getDeclaredClasses, errors); + Class[] permittedClasses = SealedClassSupport.singleton().getPermittedSubclasses(clazz); Class[] classes = query(clazz::getClasses, errors); reportLinkingErrors(clazz, errors); @@ -517,6 +520,8 @@ private void processClass(DuringAnalysisAccessImpl access, Class clazz) { filterFields(accessors.getDeclaredPublicFields(originalReflectionData), reflectionFields, access), filterMethods(accessors.getDeclaredPublicMethods(originalReflectionData), reflectionMethods, access), filterClasses(declaredClasses, reflectionClasses, access), + filterClasses(permittedClasses, reflectionClasses, access, true), + /* null is different from Class[0] here. */ filterClasses(classes, reflectionClasses, access), enclosingMethodOrConstructor(clazz), buildRecordComponents(clazz, access)); @@ -679,8 +684,16 @@ private static T[] filterMethods(Object methods, Set[] filterClasses(Object classes, Set> filter, DuringAnalysisAccessImpl access) { + return filterClasses(classes, filter, access, false); + } + + private static Class[] filterClasses(Object classes, Set> filter, DuringAnalysisAccessImpl access, boolean keepNull) { if (classes == null) { - return EMPTY_CLASSES; + if (keepNull) { + return null; + } else { + return EMPTY_CLASSES; + } } List> result = new ArrayList<>(); for (Class clazz : (Class[]) classes) {