diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 10401d7f995b..cfa93a181f00 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1,7 +1,7 @@ # # ---------------------------------------------------------------------------------------------------- # -# Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2023, 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 @@ -301,7 +301,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' +_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' IMAGE_ASSERTION_FLAGS = svm_experimental_options(['-H:+VerifyGraalGraphs', '-H:+VerifyPhases']) diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 0228ffb07f67..db4b5197f477 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -2212,12 +2212,18 @@ private void addImageClasspathEntry(LinkedHashSet destination, Path classp Path classpathEntryFinal = useBundle() ? bundleSupport.substituteClassPath(classpathEntry) : classpathEntry; if (!imageClasspath.contains(classpathEntryFinal) && !customImageClasspath.contains(classpathEntryFinal)) { + /* + * Maintain correct order by adding entry before processing its potential "Class-Path" + * attributes from META-INF/MANIFEST.MF (in case the entry is a jar-file). + */ + boolean added = destination.add(classpathEntryFinal); if (ClasspathUtils.isJar(classpathEntryFinal)) { processJarManifestMainAttributes(classpathEntryFinal, (jarFilePath, attributes) -> handleClassPathAttribute(destination, jarFilePath, attributes)); } - boolean ignore = processClasspathNativeImageMetaInf(classpathEntryFinal); - if (!ignore) { - destination.add(classpathEntryFinal); + boolean forcedOnModulePath = processClasspathNativeImageMetaInf(classpathEntryFinal); + if (added && forcedOnModulePath) { + /* Entry makes use of ForceOnModulePath. Undo adding to classpath. */ + destination.remove(classpathEntryFinal); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java index dab90726dfbf..8af9e717078f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -26,6 +26,7 @@ import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -38,6 +39,8 @@ public abstract class ConditionalConfigurationRegistry { private final Map> pendingReachabilityHandlers = new ConcurrentHashMap<>(); protected void registerConditionalConfiguration(ConfigurationCondition condition, Runnable runnable) { + Objects.requireNonNull(condition, "Cannot use null value as condition for conditional configuration. Please ensure that you register a non-null condition."); + Objects.requireNonNull(runnable, "Cannot use null value as runnable for conditional configuration. Please ensure that you register a non-null runnable."); if (ConfigurationCondition.alwaysTrue().equals(condition)) { /* analysis optimization to include new types as early as possible */ runnable.run(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java index 11cc46472653..e4a5e2bdadda 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java @@ -348,9 +348,12 @@ protected boolean handleArrayCopy(ImageHeapArray source, int sourcePos, ImageHea } if (destComponentType.getJavaKind() == JavaKind.Object && !destComponentType.isJavaLangObject() && !sourceComponentType.equals(destComponentType)) { for (int i = 0; i < length; i++) { - var elementValueType = ((TypedConstant) source.getElement(sourcePos + i)).getType(metaAccess); - if (!destComponentType.isAssignableFrom(elementValueType)) { - return false; + var elementValue = (JavaConstant) source.getElement(sourcePos + i); + if (elementValue.isNonNull()) { + var elementValueType = ((TypedConstant) elementValue).getType(metaAccess); + if (!destComponentType.isAssignableFrom(elementValueType)) { + return false; + } } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 2f1dfeb40bf1..37bf159f74b7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -34,6 +34,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; @@ -202,18 +203,21 @@ private class JNIRuntimeAccessibilitySupportImpl extends ConditionalConfiguratio @Override public void register(ConfigurationCondition condition, boolean unsafeAllocated, Class clazz) { assert !unsafeAllocated : "unsafeAllocated can be only set via Unsafe.allocateInstance, not via JNI."; + Objects.requireNonNull(clazz, () -> nullErrorMessage("class")); abortIfSealed(); registerConditionalConfiguration(condition, () -> newClasses.add(clazz)); } @Override public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods) { + requireNonNull(methods, "methods"); abortIfSealed(); registerConditionalConfiguration(condition, () -> newMethods.addAll(Arrays.asList(methods))); } @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { + requireNonNull(fields, "field"); abortIfSealed(); registerConditionalConfiguration(condition, () -> registerFields(finalIsWritable, fields)); } @@ -622,4 +626,14 @@ private static boolean anyFieldMatches(ResolvedJavaType sub, String name) { return false; } } + + private static void requireNonNull(Object[] values, String kind) { + for (Object value : values) { + Objects.requireNonNull(value, () -> nullErrorMessage(kind)); + } + } + + private static String nullErrorMessage(String kind) { + return "Cannot register null value as " + kind + " for JNI access. Please ensure that all values you register are not null."; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index d7fa9f2f1c37..3c959bd9f521 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -56,6 +56,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; @@ -166,6 +167,7 @@ private void setQueryFlag(Class clazz, int flag) { @Override public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class clazz) { + Objects.requireNonNull(clazz, () -> nullErrorMessage("class")); checkNotSealed(); register(analysisUniverse -> registerConditionalConfiguration(condition, () -> analysisUniverse.getBigbang().postTask(debug -> registerClass(clazz, unsafeInstantiated)))); @@ -269,6 +271,7 @@ public void registerAllSignersQuery(ConfigurationCondition condition, Class c @Override public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... executables) { + requireNonNull(executables, "executable"); checkNotSealed(); register(analysisUniverse -> registerConditionalConfiguration(condition, () -> { for (Executable executable : executables) { @@ -397,6 +400,7 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { + requireNonNull(fields, "field"); checkNotSealed(); registerInternal(condition, fields); } @@ -1060,4 +1064,14 @@ public int getReflectionMethodsCount() { public int getReflectionFieldsCount() { return registeredFields.size(); } + + private static void requireNonNull(Object[] values, String kind) { + for (Object value : values) { + Objects.requireNonNull(value, () -> nullErrorMessage(kind)); + } + } + + private static String nullErrorMessage(String kind) { + return "Cannot register null value as " + kind + " for reflection. Please ensure that all values you register are not null."; + } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ReflectionRegistrationTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ReflectionRegistrationTest.java new file mode 100644 index 000000000000..555cc683657d --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ReflectionRegistrationTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. 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 com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeReflection; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import org.junit.Test; + +import java.lang.reflect.Executable; +import java.lang.reflect.Field; + +/** + * Tests the {@link RuntimeReflection}. + */ +public class ReflectionRegistrationTest { + + public static class TestFeature implements Feature { + + @SuppressWarnings("unused")// + int unusedVariableOne = 1; + @SuppressWarnings("unused")// + int unusedVariableTwo = 2; + + @Override + public void beforeAnalysis(final BeforeAnalysisAccess access) { + try { + RuntimeReflection.register((Class) null); + assert false; + } catch (NullPointerException e) { + assert e.getMessage().startsWith("Cannot register null value"); + } + try { + RuntimeReflection.register((Executable) null); + assert false; + } catch (NullPointerException e) { + assert e.getMessage().startsWith("Cannot register null value"); + } + try { + RuntimeReflection.register((Field) null); + assert false; + } catch (NullPointerException e) { + assert e.getMessage().startsWith("Cannot register null value"); + } + + try { + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(null, this.getClass()); + assert false; + } catch (NullPointerException e) { + assert e.getMessage().startsWith("Cannot use null value"); + } + + try { + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(null, true, this.getClass().getMethods()); + assert false; + } catch (NullPointerException e) { + assert e.getMessage().startsWith("Cannot use null value"); + } + + try { + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(null, true, this.getClass().getFields()); + assert false; + } catch (NullPointerException e) { + assert e.getMessage().startsWith("Cannot use null value"); + } + + FeatureImpl.BeforeAnalysisAccessImpl impl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + try { + SubstitutionReflectivityFilter.shouldExclude((Class) null, impl.getMetaAccess(), impl.getUniverse()); + assert false; + } catch (NullPointerException e) { + // expected + } + try { + SubstitutionReflectivityFilter.shouldExclude((Executable) null, impl.getMetaAccess(), impl.getUniverse()); + assert false; + } catch (NullPointerException e) { + // expected + } + try { + SubstitutionReflectivityFilter.shouldExclude((Field) null, impl.getMetaAccess(), impl.getUniverse()); + assert false; + } catch (NullPointerException e) { + // expected + } + } + + } + + @Test + public void test() { + // nothing to do + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java index ffa6fad54a78..983a46edc6e9 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java @@ -612,7 +612,7 @@ public static Object defaultValue(Class clazz) { System.arraycopy(shorts, 1, S1, 2, 5); System.arraycopy(S1, 3, S1, 5, 5); - Object[] objects = {"42", "43", "44", "45", "46", "47", "48"}; + Object[] objects = {"42", null, "44", "45", null, "47", "48"}; O1 = Arrays.copyOf(objects, 3); O2 = Arrays.copyOfRange(objects, 3, 6, String[].class); } @@ -943,8 +943,8 @@ public static void main(String[] args) { assertSame(Short.class, BoxingMustBeSimulated.defaultValue(short.class).getClass()); assertSame(Float.class, BoxingMustBeSimulated.defaultValue(float.class).getClass()); assertTrue(Arrays.equals((short[]) BoxingMustBeSimulated.S1, new short[]{0, 0, 43, 44, 45, 44, 45, 46, 47, 0, 0, 0})); - assertTrue(Arrays.equals((Object[]) BoxingMustBeSimulated.O1, new Object[]{"42", "43", "44"})); - assertTrue(Arrays.equals((Object[]) BoxingMustBeSimulated.O2, new String[]{"45", "46", "47"})); + assertTrue(Arrays.equals((Object[]) BoxingMustBeSimulated.O1, new Object[]{"42", null, "44"})); + assertTrue(Arrays.equals((Object[]) BoxingMustBeSimulated.O2, new String[]{"45", null, "47"})); /* * The unsafe field offset lookup is constant folded at image build time, which also