diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 690ff5d5421d..456c1f6566aa 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -41,12 +41,15 @@ import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.jdk.ServiceCatalogSupport; -import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; +import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.hosted.analysis.Inflation; +import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; +import com.oracle.svm.hosted.substitute.DeletedMethod; import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionType; +import jdk.vm.ci.meta.MetaAccessProvider; /** * Support for {@link ServiceLoader} on Substrate VM. @@ -178,10 +181,6 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class service if (!accessImpl.getHostVM().platformSupported(providerClass)) { continue; } - if (((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor().isDeleted(providerClass)) { - /* Disallow services with implementation classes that are marked as @Deleted */ - continue; - } /* * Find either a public static provider() method or a nullary constructor (or both). @@ -189,32 +188,15 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class service * * See ServiceLoader#loadProvider and ServiceLoader#findStaticProviderMethod. */ - Constructor nullaryConstructor = null; - Method nullaryProviderMethod = null; - try { - /* Only look for a provider() method if provider class is in an explicit module. */ - if (providerClass.getModule().isNamed() && !providerClass.getModule().getDescriptor().isAutomatic()) { - for (Method method : providerClass.getDeclaredMethods()) { - if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) && - method.getParameterCount() == 0 && method.getName().equals("provider")) { - if (nullaryProviderMethod == null) { - nullaryProviderMethod = method; - } else { - /* There must be at most one public static provider() method. */ - nullaryProviderMethod = null; - break; - } - } - } - } - - Constructor constructor = providerClass.getDeclaredConstructor(); - if (Modifier.isPublic(constructor.getModifiers())) { - nullaryConstructor = constructor; - } - } catch (NoSuchMethodException | SecurityException | LinkageError e) { - // ignore + Method nullaryProviderMethod = findProviderMethod(providerClass); + Constructor nullaryConstructor = findNullaryConstructor(providerClass); + MetaAccessProvider originalMetaAccess = accessImpl.getBigBang().getUniverse().getOriginalMetaAccess(); + AnnotationSubstitutionProcessor substitutionProcessor = ((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor(); + if (isServiceProviderDeleted(providerClass, nullaryProviderMethod, nullaryConstructor, originalMetaAccess, substitutionProcessor)) { + /* Disallow services with implementation classes that are marked as @Deleted */ + continue; } + if (nullaryConstructor != null || nullaryProviderMethod != null) { RuntimeReflection.register(providerClass); if (nullaryConstructor != null) { @@ -238,4 +220,59 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class service RuntimeResourceAccess.addResource(access.getApplicationClassLoader().getUnnamedModule(), serviceResourceLocation, serviceFileData); } } + + private static Constructor findNullaryConstructor(Class providerClass) { + Constructor nullaryConstructor = null; + try { + Constructor constructor = providerClass.getDeclaredConstructor(); + if (Modifier.isPublic(constructor.getModifiers())) { + nullaryConstructor = constructor; + } + } catch (NoSuchMethodException | SecurityException | LinkageError e) { + // ignore + } + return nullaryConstructor; + } + + private static Method findProviderMethod(Class providerClass) { + Method nullaryProviderMethod = null; + try { + /* Only look for a provider() method if provider class is in an explicit module. */ + if (providerClass.getModule().isNamed() && !providerClass.getModule().getDescriptor().isAutomatic()) { + for (Method method : providerClass.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) && + method.getParameterCount() == 0 && method.getName().equals("provider")) { + if (nullaryProviderMethod == null) { + nullaryProviderMethod = method; + } else { + /* There must be at most one public static provider() method. */ + nullaryProviderMethod = null; + break; + } + } + } + } + + } catch (SecurityException | LinkageError e) { + // ignore + } + return nullaryProviderMethod; + } + + private static boolean isServiceProviderDeleted(Class providerClass, Method nullaryProviderMethod, Constructor nullaryConstructor, MetaAccessProvider originalMetaAccess, + AnnotationSubstitutionProcessor substitutionProcessor) { + if (nullaryConstructor == null && nullaryProviderMethod == null) { + /* In case when service provider is not JCA compliant. */ + return false; + } + boolean isNullaryConstructorDeletedOrNull = nullaryConstructor == null || substitutionProcessor.lookup(originalMetaAccess.lookupJavaMethod(nullaryConstructor)) instanceof DeletedMethod; + boolean isNullaryProviderMethodDeletedOrNull = nullaryProviderMethod == null || + substitutionProcessor.lookup(originalMetaAccess.lookupJavaMethod(nullaryProviderMethod)) instanceof DeletedMethod; + /* + * If ReportUnsupportedElementsAtRuntime is false, we can directly check the @Delete + * annotation on the substitute class. Otherwise, we need to verify if its methods are + * marked as deleted. + */ + return substitutionProcessor.isDeleted(providerClass) || isNullaryConstructorDeletedOrNull && isNullaryProviderMethodDeletedOrNull; + } } diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/services/com.oracle.svm.test.DeletedServiceTest$ServiceInterface b/substratevm/src/com.oracle.svm.test/src/META-INF/services/com.oracle.svm.test.DeletedServiceTest$ServiceInterface new file mode 100644 index 000000000000..74526bbff4a9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/services/com.oracle.svm.test.DeletedServiceTest$ServiceInterface @@ -0,0 +1 @@ +com.oracle.svm.test.DeletedServiceTest$DeletedService \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/DeletedServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/DeletedServiceTest.java new file mode 100644 index 000000000000..c1fea169c227 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/DeletedServiceTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, 2024, 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.test; + +import java.util.ServiceLoader; + +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; +import org.junit.Assert; +import org.junit.Test; + +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Test if the service marked with {@link Delete} is not present at run time. + */ +public class DeletedServiceTest { + + public static class TestFeature implements Feature { + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + RuntimeClassInitialization.initializeAtBuildTime(DeletedService.class); + RuntimeClassInitialization.initializeAtBuildTime(ServiceInterface.class); + } + } + + interface ServiceInterface { + } + + /* Registered and deleted service. */ + public static class DeletedService implements ServiceInterface { + } + + @Delete + @TargetClass(DeletedService.class) + final static class Target_com_oracle_svm_test_ServiceLoaderTest_DeletedService { + } + + @Test + public void test01OwnService() { + ServiceLoader loader = ServiceLoader.load(ServiceInterface.class); + + int numFound = 0; + boolean foundDeleted = false; + + for (ServiceInterface instance : loader) { + numFound++; + String name = instance.getClass().getSimpleName(); + foundDeleted |= name.equals("DeletedService"); + } + + Assert.assertFalse("Should not find a service that is deleted.", foundDeleted); + Assert.assertEquals(0, numFound); + } +}