Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-60293] Throw a ServiceConfigurationError if the service is not JCA-compliant. #10339

Merged
merged 2 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions substratevm/mx.substratevm/mx_substratevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def native_image_func(args, **kwargs):
yield native_image_func

native_image_context.hosted_assertions = ['-J-ea', '-J-esa']
_native_unittest_features = '--features=com.oracle.svm.test.ImageInfoTest$TestFeature,com.oracle.svm.test.ServiceLoaderTest$TestFeature,com.oracle.svm.test.SecurityServiceTest$TestFeature,com.oracle.svm.test.ReflectionRegistrationTest$TestFeature'
_native_unittest_features = '--features=com.oracle.svm.test.ImageInfoTest$TestFeature,com.oracle.svm.test.services.ServiceLoaderTest$TestFeature,com.oracle.svm.test.services.SecurityServiceTest$TestFeature,com.oracle.svm.test.ReflectionRegistrationTest$TestFeature'

IMAGE_ASSERTION_FLAGS = svm_experimental_options(['-H:+VerifyGraalGraphs', '-H:+VerifyPhases'])

Expand Down Expand Up @@ -545,8 +545,8 @@ def native_unittests_task(extra_build_args=None):
out.write(f"Simple file{i}" + '\n')

additional_build_args = svm_experimental_options([
'-H:AdditionalSecurityProviders=com.oracle.svm.test.SecurityServiceTest$NoOpProvider',
'-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.SecurityServiceTest$JCACompliantNoOpService',
'-H:AdditionalSecurityProviders=com.oracle.svm.test.services.SecurityServiceTest$NoOpProvider',
'-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.services.SecurityServiceTest$JCACompliantNoOpService',
'-cp', cp_entry_name
])
if extra_build_args is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
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 jdk.graal.compiler.options.Option;
Expand Down Expand Up @@ -218,7 +218,21 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class<?> service
if (nullaryConstructor != null || nullaryProviderMethod != null) {
RuntimeReflection.register(providerClass);
if (nullaryConstructor != null) {
/*
* Registering a constructor with
* RuntimeReflection.registerConstructorLookup(providerClass) does not produce
* the same behavior as using RuntimeReflection.register(nullaryConstructor). In
* the first case, the constructor is marked for query purposes only, so this
* if-statement cannot be eliminated.
*
*/
RuntimeReflection.register(nullaryConstructor);
} else {
/*
* If there's no nullary constructor, register it as negative lookup to avoid
* throwing a MissingReflectionRegistrationError at run time.
*/
RuntimeReflection.registerConstructorLookup(providerClass);
}
if (nullaryProviderMethod != null) {
RuntimeReflection.register(nullaryProviderMethod);
Expand All @@ -229,8 +243,14 @@ void handleServiceClassIsReachable(DuringAnalysisAccess access, Class<?> service
*/
RuntimeReflection.registerMethodLookup(providerClass, "provider");
}
registeredProviders.add(provider);
}
/*
* Register the provider in both cases: when it is JCA-compliant (has a nullary
* constructor or a provider method) or when it lacks both. If neither is present, a
* ServiceConfigurationError will be thrown at runtime, consistent with HotSpot
* behavior.
*/
registeredProviders.add(provider);
}
if (!registeredProviders.isEmpty()) {
String serviceResourceLocation = "META-INF/services/" + serviceProvider.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ Args= \
--initialize-at-run-time=com.oracle.svm.test \
--initialize-at-build-time=com.oracle.svm.test.AbstractClassSerializationTest,com.oracle.svm.test.SerializationRegistrationTest,com.oracle.svm.test.SerializationRegistrationTest$SerializableTestClass,com.oracle.svm.test.SerializationRegistrationTest$AssociatedRegistrationTestClass,com.oracle.svm.test.LambdaClassSerializationTest,com.oracle.svm.test.LambdaClassDeserializationTest \
--features=com.oracle.svm.test.SerializationRegistrationTestFeature \
--features=com.oracle.svm.test.AbstractServiceLoaderTest$TestFeature \
--features=com.oracle.svm.test.NoProviderConstructorServiceLoaderTest$TestFeature \
--features=com.oracle.svm.test.services.AbstractServiceLoaderTest$TestFeature \
--features=com.oracle.svm.test.services.NoProviderConstructorServiceLoaderTest$TestFeature \
--features=com.oracle.svm.test.NativeImageResourceUtils$TestFeature \
--features=com.oracle.svm.test.jfr.JfrTestFeature \
--add-opens=java.base/java.lang=ALL-UNNAMED \
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
com.oracle.svm.test.services.AbstractServiceLoaderTest$ConcreteService
com.oracle.svm.test.services.AbstractServiceLoaderTest$AbstractService
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
com.oracle.svm.test.services.NoProviderConstructorServiceLoaderTest$ProperService
com.oracle.svm.test.services.NoProviderConstructorServiceLoaderTest$NoProviderConstructorService
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
com.oracle.svm.test.services.ServiceLoaderTest$ServiceA
com.oracle.svm.test.services.ServiceLoaderTest$ServiceB
com.oracle.svm.test.services.ServiceLoaderTest$ServiceHostedOnly
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.test;
package com.oracle.svm.test.services;

import java.util.HashSet;
import java.util.ServiceConfigurationError;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 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
Expand All @@ -22,7 +22,7 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.test;
package com.oracle.svm.test.services;

import java.util.HashSet;
import java.util.ServiceConfigurationError;
Expand All @@ -40,12 +40,8 @@
// Checkstyle: allow Class.getSimpleName

/**
* Tests a workaround for {@linkplain ServiceLoader services} without a {@linkplain ServiceLoader
* provider constructor} (nullary constructor) [GR-19958]. The workaround completely ignores
* services without a provider constructor, instead of throwing an {@link ServiceConfigurationError}
* when iterating the services. See the Github issue
* <a href="https://github.com/oracle/graal/issues/2652">"Spring Service Registry native-image
* failure due to service loader handling in jersey #2652"</a> for more details.
* Test both JCA-compliant services and non-JCA-compliant services (without a nullary constructor),
* and compare the behavior between Native Image and Hotspot.
*/
public class NoProviderConstructorServiceLoaderTest {

Expand Down Expand Up @@ -73,81 +69,77 @@ public abstract static class NoProviderConstructorService implements ServiceInte

private static final Set<String> EXPECTED = Set.of(ProperService.class.getSimpleName());

/**
* This should actually throw an {@link ServiceConfigurationError}.
*
* @see #testLazyStreamHotspot()
*/
@Test
@Test(expected = ServiceConfigurationError.class)
public void testLazyStreamNativeImage() {
Assume.assumeTrue("native image specific behavior", ImageInfo.inImageRuntimeCode());
Set<String> simpleNames = ServiceLoader.load(ServiceInterface.class).stream()
.map(provider -> provider.type().getSimpleName())
.collect(Collectors.toSet());
assumeEnvironment(true);
Set<String> simpleNames = loadLazyStreamNames();
Assert.assertEquals(EXPECTED, simpleNames);
}

/**
* This should actually throw an {@link ServiceConfigurationError}.
*
* @see #testEagerStreamHotspot()
*/
@Test
@Test(expected = ServiceConfigurationError.class)
public void testEagerStreamNativeImage() {
Assume.assumeTrue("native image specific behavior", ImageInfo.inImageRuntimeCode());
Set<String> simpleNames = ServiceLoader.load(ServiceInterface.class).stream()
.map(provider -> provider.get().getClass().getSimpleName())
.collect(Collectors.toSet());
assumeEnvironment(true);
Set<String> simpleNames = loadEagerStreamNames();
Assert.assertEquals(EXPECTED, simpleNames);
}

/**
* This should actually throw an {@link ServiceConfigurationError}.
*
* @see #testEagerIteratorHotspot()
*/
@Test
@Test(expected = ServiceConfigurationError.class)
public void testEagerIteratorNativeImage() {
Assume.assumeTrue("native image specific behavior", ImageInfo.inImageRuntimeCode());
Set<String> simpleNames = new HashSet<>();
ServiceLoader.load(ServiceInterface.class).iterator()
.forEachRemaining(s -> simpleNames.add(s.getClass().getSimpleName()));
assumeEnvironment(true);
Set<String> simpleNames = loadEagerIteratorNames();
Assert.assertEquals(EXPECTED, simpleNames);
}

/**
* @see #testLazyStreamNativeImage()
*/
@Test(expected = ServiceConfigurationError.class)
public void testLazyStreamHotspot() {
Assume.assumeFalse("hotspot specific behavior", ImageInfo.inImageRuntimeCode());
Set<String> simpleNames = ServiceLoader.load(ServiceInterface.class).stream()
assumeEnvironment(false);
Set<String> simpleNames = loadLazyStreamNames();
Assert.assertNull("should not reach", simpleNames);
}

@Test(expected = ServiceConfigurationError.class)
public void testEagerIteratorHotspot() {
assumeEnvironment(false);
Set<String> simpleNames = loadEagerIteratorNames();
Assert.assertNull("should not reach", simpleNames);
}

/**
* Helper method to assume the environment (hotspot/native image).
*/
private static void assumeEnvironment(boolean isNativeImage) {
if (isNativeImage) {
Assume.assumeTrue("native image specific behavior", ImageInfo.inImageRuntimeCode());
} else {
Assume.assumeFalse("hotspot specific behavior", ImageInfo.inImageRuntimeCode());
}
}

/**
* Helper method for lazy stream tests.
*/
private static Set<String> loadLazyStreamNames() {
return ServiceLoader.load(ServiceInterface.class).stream()
.map(provider -> provider.type().getSimpleName())
.collect(Collectors.toSet());
Assert.assertNull("should not reach", simpleNames);
}

/**
* @see #testEagerStreamNativeImage()
* Helper method for eager stream tests.
*/
@Test(expected = ServiceConfigurationError.class)
public void testEagerStreamHotspot() {
Assume.assumeFalse("hotspot specific behavior", ImageInfo.inImageRuntimeCode());
Set<String> simpleNames = ServiceLoader.load(ServiceInterface.class).stream()
private static Set<String> loadEagerStreamNames() {
return ServiceLoader.load(ServiceInterface.class).stream()
.map(provider -> provider.get().getClass().getSimpleName())
.collect(Collectors.toSet());
Assert.assertNull("should not reach", simpleNames);
}

/**
* @see #testEagerIteratorNativeImage()
* Helper method for eager iterator tests.
*/
@Test(expected = ServiceConfigurationError.class)
public void testEagerIteratorHotspot() {
Assume.assumeFalse("hotspot specific behavior", ImageInfo.inImageRuntimeCode());
private static Set<String> loadEagerIteratorNames() {
Set<String> simpleNames = new HashSet<>();
ServiceLoader.load(ServiceInterface.class).iterator()
.forEachRemaining(s -> simpleNames.add(s.getClass().getSimpleName()));
Assert.assertNull("should not reach", simpleNames);
return simpleNames;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.test;
package com.oracle.svm.test.services;

import java.security.NoSuchAlgorithmException;
import java.security.Provider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.test;
package com.oracle.svm.test.services;

import java.util.ArrayList;
import java.util.List;
Expand Down