Skip to content

Commit f94a6a2

Browse files
Deleted services should not be present at run time.
1 parent bbdf41b commit f94a6a2

File tree

3 files changed

+151
-27
lines changed

3 files changed

+151
-27
lines changed

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@
4444
import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue;
4545
import com.oracle.svm.core.option.HostedOptionKey;
4646
import com.oracle.svm.hosted.analysis.Inflation;
47+
import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor;
48+
import com.oracle.svm.hosted.substitute.DeletedMethod;
4749

4850
import jdk.graal.compiler.options.Option;
4951
import jdk.graal.compiler.options.OptionType;
52+
import jdk.vm.ci.meta.MetaAccessProvider;
5053

5154
/**
5255
* Support for {@link ServiceLoader} on Substrate VM.
@@ -178,8 +181,14 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class<?> service
178181
if (!accessImpl.getHostVM().platformSupported(providerClass)) {
179182
continue;
180183
}
181-
if (((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor().isDeleted(providerClass)) {
182-
/* Disallow services with implementation classes that are marked as @Deleted */
184+
AnnotationSubstitutionProcessor substitutionProcessor = ((Inflation) accessImpl.getBigBang()).getAnnotationSubstitutionProcessor();
185+
/*
186+
* If ReportUnsupportedElementsAtRuntime is false, we can directly check the @Delete
187+
* annotation on the substitute class. Otherwise, this check will return false even if
188+
* providerClass is deleted. In such cases, the check, a couple of lines below, will
189+
* handle the scenario.
190+
*/
191+
if (substitutionProcessor.isDeleted(providerClass)) {
183192
continue;
184193
}
185194

@@ -189,32 +198,14 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class<?> service
189198
*
190199
* See ServiceLoader#loadProvider and ServiceLoader#findStaticProviderMethod.
191200
*/
192-
Constructor<?> nullaryConstructor = null;
193-
Method nullaryProviderMethod = null;
194-
try {
195-
/* Only look for a provider() method if provider class is in an explicit module. */
196-
if (providerClass.getModule().isNamed() && !providerClass.getModule().getDescriptor().isAutomatic()) {
197-
for (Method method : providerClass.getDeclaredMethods()) {
198-
if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) &&
199-
method.getParameterCount() == 0 && method.getName().equals("provider")) {
200-
if (nullaryProviderMethod == null) {
201-
nullaryProviderMethod = method;
202-
} else {
203-
/* There must be at most one public static provider() method. */
204-
nullaryProviderMethod = null;
205-
break;
206-
}
207-
}
208-
}
209-
}
210-
211-
Constructor<?> constructor = providerClass.getDeclaredConstructor();
212-
if (Modifier.isPublic(constructor.getModifiers())) {
213-
nullaryConstructor = constructor;
214-
}
215-
} catch (NoSuchMethodException | SecurityException | LinkageError e) {
216-
// ignore
201+
Method nullaryProviderMethod = findProviderMethod(providerClass);
202+
Constructor<?> nullaryConstructor = findNullaryConstructor(providerClass);
203+
MetaAccessProvider originalMetaAccess = accessImpl.getBigBang().getUniverse().getOriginalMetaAccess();
204+
if (isServiceProviderDeleted(nullaryProviderMethod, nullaryConstructor, originalMetaAccess, substitutionProcessor)) {
205+
/* Disallow services with implementation classes that are marked as @Deleted */
206+
continue;
217207
}
208+
218209
if (nullaryConstructor != null || nullaryProviderMethod != null) {
219210
RuntimeReflection.register(providerClass);
220211
if (nullaryConstructor != null) {
@@ -258,4 +249,55 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class<?> service
258249
RuntimeResourceAccess.addResource(access.getApplicationClassLoader().getUnnamedModule(), serviceResourceLocation, serviceFileData);
259250
}
260251
}
252+
253+
private static Constructor<?> findNullaryConstructor(Class<?> providerClass) {
254+
Constructor<?> nullaryConstructor = null;
255+
try {
256+
Constructor<?> constructor = providerClass.getDeclaredConstructor();
257+
if (Modifier.isPublic(constructor.getModifiers())) {
258+
nullaryConstructor = constructor;
259+
}
260+
} catch (NoSuchMethodException | SecurityException | LinkageError e) {
261+
// ignore
262+
}
263+
return nullaryConstructor;
264+
}
265+
266+
private static Method findProviderMethod(Class<?> providerClass) {
267+
Method nullaryProviderMethod = null;
268+
try {
269+
/* Only look for a provider() method if provider class is in an explicit module. */
270+
if (providerClass.getModule().isNamed() && !providerClass.getModule().getDescriptor().isAutomatic()) {
271+
for (Method method : providerClass.getDeclaredMethods()) {
272+
if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) &&
273+
method.getParameterCount() == 0 && method.getName().equals("provider")) {
274+
if (nullaryProviderMethod == null) {
275+
nullaryProviderMethod = method;
276+
} else {
277+
/* There must be at most one public static provider() method. */
278+
nullaryProviderMethod = null;
279+
break;
280+
}
281+
}
282+
}
283+
}
284+
285+
} catch (SecurityException | LinkageError e) {
286+
// ignore
287+
}
288+
return nullaryProviderMethod;
289+
}
290+
291+
private static boolean isServiceProviderDeleted(Method nullaryProviderMethod, Constructor<?> nullaryConstructor, MetaAccessProvider originalMetaAccess,
292+
AnnotationSubstitutionProcessor substitutionProcessor) {
293+
if (nullaryConstructor == null && nullaryProviderMethod == null) {
294+
/* In case when service provider is not JCA compliant. */
295+
return false;
296+
}
297+
298+
boolean isNullaryConstructorDeletedOrNull = nullaryConstructor == null || substitutionProcessor.lookup(originalMetaAccess.lookupJavaMethod(nullaryConstructor)) instanceof DeletedMethod;
299+
boolean isNullaryProviderMethodDeletedOrNull = nullaryProviderMethod == null ||
300+
substitutionProcessor.lookup(originalMetaAccess.lookupJavaMethod(nullaryProviderMethod)) instanceof DeletedMethod;
301+
return isNullaryConstructorDeletedOrNull && isNullaryProviderMethodDeletedOrNull;
302+
}
261303
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.oracle.svm.test.services.DeletedServiceTest$DeletedService
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package com.oracle.svm.test.services;
27+
28+
import java.util.ServiceLoader;
29+
30+
import org.graalvm.nativeimage.hosted.Feature;
31+
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
32+
import org.junit.Assert;
33+
import org.junit.Test;
34+
35+
import com.oracle.svm.core.annotate.Delete;
36+
import com.oracle.svm.core.annotate.TargetClass;
37+
38+
// Checkstyle: allow Class.getSimpleName
39+
40+
/**
41+
* Test if the service marked with {@link Delete} is not present at run time.
42+
*/
43+
public class DeletedServiceTest {
44+
45+
public static class TestFeature implements Feature {
46+
@Override
47+
public void beforeAnalysis(BeforeAnalysisAccess access) {
48+
RuntimeClassInitialization.initializeAtBuildTime(DeletedService.class);
49+
RuntimeClassInitialization.initializeAtBuildTime(ServiceInterface.class);
50+
}
51+
}
52+
53+
interface ServiceInterface {
54+
}
55+
56+
/* Registered and deleted service. */
57+
public static class DeletedService implements ServiceInterface {
58+
}
59+
60+
@Delete
61+
@TargetClass(DeletedService.class)
62+
static final class Target_com_oracle_svm_test_ServiceLoaderTest_DeletedService {
63+
}
64+
65+
@Test
66+
public void testDeletedService() {
67+
ServiceLoader<ServiceInterface> loader = ServiceLoader.load(ServiceInterface.class);
68+
69+
int numFound = 0;
70+
boolean foundDeleted = false;
71+
72+
for (ServiceInterface instance : loader) {
73+
numFound++;
74+
String name = instance.getClass().getSimpleName();
75+
foundDeleted |= name.equals("DeletedService");
76+
}
77+
78+
Assert.assertFalse("Should not find a service that is deleted.", foundDeleted);
79+
Assert.assertEquals(0, numFound);
80+
}
81+
}

0 commit comments

Comments
 (0)