Skip to content

Commit

Permalink
Enable partial missing metadata exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
loicottet committed Apr 6, 2023
1 parent cdf7c92 commit 4f424a9
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 113 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2023, 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
* 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;

import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;

import com.oracle.svm.core.option.OptionClassFilter;

public class MissingRegistrationSupport {

private final OptionClassFilter classFilter;

@Platforms(Platform.HOSTED_ONLY.class)
public MissingRegistrationSupport(OptionClassFilter classFilter) {
this.classFilter = classFilter;
}

@Fold
public static MissingRegistrationSupport singleton() {
return ImageSingletons.lookup(MissingRegistrationSupport.class);
}

public boolean reportMissingRegistrationErrors(StackTraceElement responsibleClass) {
return classFilter.isIncluded(responsibleClass.getModuleName(), getPackageName(responsibleClass.getClassName()), responsibleClass.getClassName()) != null;
}

private static String getPackageName(String className) {
int lastDot = className.lastIndexOf('.');
return (lastDot != -1) ? className.substring(0, lastDot) : "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,10 @@ public Boolean getValueOrDefault(UnmodifiableEconomicMap<OptionKey<?>, Object> v
@Option(help = "Instead of abort, only warn if image builder classes are found on the image class-path.", type = OptionType.Debug)//
public static final HostedOptionKey<Boolean> AllowDeprecatedBuilderClassesOnImageClasspath = new HostedOptionKey<>(false);

@Option(help = "Throw Native Image-specific exceptions when encountering an unregistered reflection call.", type = OptionType.User)//
public static final HostedOptionKey<Boolean> ThrowMissingRegistrationErrors = new HostedOptionKey<>(false);
@Option(help = "file:doc-files/MissingRegistrationHelp.txt")//
public static final HostedOptionKey<LocatableMultiOptionValue.Strings> ThrowMissingRegistrationErrors = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build());

@Option(help = "file:doc-files/MissingRegistrationPathsHelp.txt")//
public static final HostedOptionKey<LocatableMultiOptionValue.Strings> ThrowMissingRegistrationErrorsPaths = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build());

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Throw Native Image-specific errors when trying to access an element that has not been registered.
This can happen on reflection and serialization queries, and resource access.
If used without args, the errors will be thrown when calling the corresponding query from any class in scope of the option.

Using -H:ThrowMissingRegistrationErrors without arguments is only allowed on command line or when embedded in a
native-image.properties file of some zip/jar file on the module-path (but not on class-path).

In the module path case, the option will cause all classes of the module to trigger missing registration errors.
If used without arguments on command line all classes will trigger missing registration errors.

Using -H:ThrowMissingRegistrationErrors with arguments is allowed in every scope:

1. On command line
2. Embedded in a native-image.properties file of some zip/jar file on module-path
3. Embedded in a native-image.properties file of some zip/jar file on class-path

If the option is embedded in native-image.properties file in some zip/jar file all class-names
and package-names passed to the option have to be found in the zip/jar files the option is embedded
in. Using -H:ThrowMissingRegistrationErrors with arguments on command line does not have that restriction.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Trigger missing registration errors from all types in the given class or module-path entries.

This option requires arguments that are of the same type as the
arguments passed via -p (--module-path) or -cp (--class-path):

-H:ThrowMissingRegistrationErrorsPaths <class search path of directories and zip/jar files>

The given entries are searched and all classes inside are registered as -H:ThrowMissingRegistrationErrorsPaths classes.

This option is only allowed to be used on command line. I.e. the option will be rejected if it is provided
by Args of a native-image.properties file embedded in a zip/jar file.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
import com.oracle.svm.core.identityhashcode.IdentityHashCodeSupport;
import com.oracle.svm.core.meta.SharedType;
import com.oracle.svm.core.option.HostedOptionValues;
import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils;
import com.oracle.svm.core.snippets.SnippetRuntime;
import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor;
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
Expand Down Expand Up @@ -306,6 +307,9 @@ private static void instanceHubErrorStub(DynamicHub hub) throws InstantiationExc
} else if (!hub.isInstanceClass() || LayoutEncoding.isSpecial(hub.getLayoutEncoding())) {
throw new InstantiationException("Can only allocate instance objects for concrete classes.");
} else if (!hub.isInstantiated()) {
if (MissingReflectionRegistrationUtils.throwMissingRegistrationErrors()) {
MissingReflectionRegistrationUtils.forClass(hub.getTypeName());
}
throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." +
" Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + ".");
} else if (LayoutEncoding.isHybrid(hub.getLayoutEncoding())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2023, 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
* 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.option;

import java.util.Collections;
import java.util.Map;
import java.util.Set;

public class OptionClassFilter {
private final Set<OptionOrigin> reasonCommandLine = Collections.singleton(OptionOrigin.commandLineOptionOriginSingleton);

private final Map<String, Set<OptionOrigin>> requireCompletePackageOrClass;
private final Set<Module> requireCompleteModules;
private boolean requireCompleteAll;

public OptionClassFilter(Map<String, Set<OptionOrigin>> requireCompletePackageOrClass, Set<Module> requireCompleteModules, boolean requireCompleteAll) {
this.requireCompletePackageOrClass = requireCompletePackageOrClass;
this.requireCompleteModules = requireCompleteModules;
this.requireCompleteAll = requireCompleteAll;
}

public Object isIncluded(Class<?> clazz) {
Module module = clazz.getModule();
return isIncluded(module.isNamed() ? module.getName() : null, clazz.getPackageName(), clazz.getName());
}

public Object isIncluded(String moduleName, String packageName, String className) {
if (requireCompleteAll) {
return reasonCommandLine;
}

if (moduleName != null) {
for (Module module : requireCompleteModules) {
if (module.getName().equals(moduleName)) {
return module.toString();
}
}
}

Set<OptionOrigin> origins = requireCompletePackageOrClass.get(className);
if (origins != null) {
return origins;
}
return requireCompletePackageOrClass.get(packageName);
}

public void addPackageOrClass(String packageOrClass, Set<OptionOrigin> reason) {
requireCompletePackageOrClass.put(packageOrClass, reason);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,21 @@
package com.oracle.svm.core.reflect;

import java.io.Serial;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;

import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.MissingReflectionRegistrationError;

import com.oracle.svm.core.MissingRegistrationSupport;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.util.ExitStatus;

Expand All @@ -57,7 +61,7 @@ public enum ReportingMode {
}

public static boolean throwMissingRegistrationErrors() {
return SubstrateOptions.ThrowMissingRegistrationErrors.getValue();
return SubstrateOptions.ThrowMissingRegistrationErrors.hasBeenSet();
}

public static ReportingMode missingRegistrationReportingMode() {
Expand Down Expand Up @@ -117,6 +121,10 @@ private static String errorMessage(String failedAction, String elementDescriptor
private static final Set<String> seenOutputs = Options.MissingRegistrationReportingMode.getValue() == ReportingMode.Warn ? ConcurrentHashMap.newKeySet() : null;

private static void report(MissingReflectionRegistrationError exception) {
StackTraceElement responsibleClass = getResponsibleClass(exception);
if (responsibleClass != null && !MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(responsibleClass)) {
return;
}
switch (missingRegistrationReportingMode()) {
case Throw -> {
throw exception;
Expand Down Expand Up @@ -175,6 +183,60 @@ private static void printLine(StringBuilder sb, Object object) {
sb.append(" ").append(object).append(System.lineSeparator());
}

/*
* This is a list of all public JDK methods that end up potentially throwing missing
* registration errors. This should be implemented using wrapping substitutions once they are
* available.
*/
private static final Map<String, Set<String>> reflectionEntryPoints = Map.of(
Class.class.getTypeName(), Set.of(
"forName",
"getClasses",
"getDeclaredClasses",
"getConstructor",
"getConstructors",
"getDeclaredConstructor",
"getDeclaredConstructors",
"getField",
"getFields",
"getDeclaredField",
"getDeclaredFields",
"getMethod",
"getMethods",
"getDeclaredMethod",
"getDeclaredMethods",
"getNestMembers",
"getPermittedSubclasses",
"getRecordComponents",
"getSigners",
"arrayType",
"newInstance"),
Method.class.getTypeName(), Set.of("invoke"),
Constructor.class.getTypeName(), Set.of("newInstance"),
"java.lang.reflect.ReflectAccess", Set.of("newInstance"),
"jdk.internal.access.JavaLangAccess", Set.of("getDeclaredPublicMethods"),
sun.misc.Unsafe.class.getName(), Set.of("allocateInstance"),
/* For jdk.internal.misc.Unsafe.allocateInstance(), which is intrinsified */
SubstrateAllocationSnippets.class.getName(), Set.of("instanceHubErrorStub"));

private static StackTraceElement getResponsibleClass(Throwable t) {
StackTraceElement[] stackTrace = t.getStackTrace();
boolean returnNext = false;
for (StackTraceElement stackTraceElement : stackTrace) {
if (reflectionEntryPoints.getOrDefault(stackTraceElement.getClassName(), Set.of()).contains(stackTraceElement.getMethodName())) {
/*
* Multiple functions with the same name can be called in succession, like the
* Class.forName caller-sensitive adapters. We skip those until we find a method
* that is not a monitored reflection entry point.
*/
returnNext = true;
} else if (returnNext) {
return stackTraceElement;
}
}
return null;
}

public static final class ExitException extends Error {
@Serial//
private static final long serialVersionUID = -3638940737396726143L;
Expand Down
Loading

0 comments on commit 4f424a9

Please sign in to comment.