Skip to content

Commit 90c1f56

Browse files
committed
[GR-40187] Dectect invalid use of SVM specific classes on image class- or module-path.
PullRequest: graal/12791
2 parents 824967b + cd1c104 commit 90c1f56

File tree

10 files changed

+92
-30
lines changed

10 files changed

+92
-30
lines changed

java-benchmarks/mx.java-benchmarks/mx_java_benchmarks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ def extra_image_build_argument(self, benchmark, args):
371371
'-H:EnableURLProtocols=http',
372372
'-H:NativeLinkerOption=-no-pie',
373373
'-H:-UseServiceLoaderFeature',
374+
'-H:+TolerateBuilderClassesOnImageClasspath', # needs to be removed once GR-41746 is fixed
374375
'--add-exports=org.graalvm.sdk/org.graalvm.nativeimage.impl=ALL-UNNAMED',
375376
'--add-exports=org.graalvm.nativeimage.base/com.oracle.svm.util=ALL-UNNAMED',
376377
'--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.configure=ALL-UNNAMED',

substratevm/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
This changelog summarizes major changes to GraalVM Native Image.
44

5+
## Version 23.0.0
6+
* (GR-40187) Report invalid use of SVM specific classes on image class- or module-path as error. As a temporary workaround, -H:+TolerateBuilderClassesOnImageClasspath allows turning the error into a warning.
7+
58
## Version 22.3.0
69
* (GR-35721) Remove old build output style and the `-H:±BuildOutputUseNewStyle` option.
710
* (GR-39390) (GR-39649) (GR-40033) Red Hat added support for the JFR events `JavaMonitorEnter`, `JavaMonitorWait`, and `ThreadSleep`.

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,18 @@ def svm_unittest_config_participant(config):
100100
def classpath(args):
101101
if not args:
102102
return [] # safeguard against mx.classpath(None) behaviour
103-
return mx.classpath(args, jdk=mx_compiler.jdk)
103+
104+
transitive_excludes = set()
105+
def include_in_excludes(dep, dep_edge):
106+
# We need to exclude on the granularity of mx.Project entries so that classpath()
107+
# can also give us a builder-free classpath if args contains mx.Project entries.
108+
if dep.isJavaProject() or dep.isDistribution():
109+
transitive_excludes.add(dep)
110+
111+
implicit_excludes_deps = [mx.dependency(entry) for entry in mx_sdk_vm_impl.NativePropertiesBuildTask.implicit_excludes]
112+
mx.walk_deps(implicit_excludes_deps, visit=include_in_excludes)
113+
cpEntries = mx.classpath_entries(names=args, includeSelf=True, preferProjects=False, excludes=transitive_excludes)
114+
return mx._entries_to_classpath(cpEntries=cpEntries, resolve=True, includeBootClasspath=False, jdk=mx_compiler.jdk, unique=False, ignoreStripped=False)
104115

105116
def platform_name():
106117
return mx.get_os() + "-" + mx.get_arch()
@@ -614,7 +625,7 @@ def dummy_harness(test_deps, vm_launcher, vm_args):
614625
mx.abort('No matching unit tests found. Skip image build and execution.')
615626
with open(unittest_file, 'r') as f:
616627
mx.log('Building junit image for matching: ' + ' '.join(l.rstrip() for l in f))
617-
extra_image_args = mx.get_runtime_jvm_args(unittest_deps, jdk=mx_compiler.jdk, exclude_names=['substratevm:LIBRARY_SUPPORT'])
628+
extra_image_args = mx.get_runtime_jvm_args(unittest_deps, jdk=mx_compiler.jdk, exclude_names=mx_sdk_vm_impl.NativePropertiesBuildTask.implicit_excludes)
618629
macro_junit = '--macro:junit'
619630
if force_builder_on_cp:
620631
macro_junit += 'cp'

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,4 +846,7 @@ public Boolean getValueOrDefault(UnmodifiableEconomicMap<OptionKey<?>, Object> v
846846
}
847847
};
848848

849+
@Option(help = "Instead of abort, only warn if image builder classes are found on the image class-path.", type = Debug)//
850+
public static final HostedOptionKey<Boolean> TolerateBuilderClassesOnImageClasspath = new HostedOptionKey<>(false);
851+
849852
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public void execute(Runnable task) {
104104
executor.shutdown();
105105
executor.awaitTermination(CLASS_LOADING_TIMEOUT_IN_MINUTES, TimeUnit.MINUTES);
106106
}
107+
classLoaderSupport.reportBuilderClassesInApplication();
107108
}
108109

109110
private void findSystemElements(Class<?> systemClass) {

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

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373

7474
import org.graalvm.collections.EconomicMap;
7575
import org.graalvm.collections.EconomicSet;
76+
import org.graalvm.collections.MapCursor;
7677
import org.graalvm.collections.Pair;
7778
import org.graalvm.compiler.options.OptionKey;
7879
import org.graalvm.compiler.options.OptionValues;
@@ -103,6 +104,7 @@ public class NativeImageClassLoaderSupport {
103104
private final EconomicMap<URI, EconomicSet<String>> classes;
104105
private final EconomicMap<URI, EconomicSet<String>> packages;
105106
private final EconomicSet<String> emptySet;
107+
private final EconomicSet<URI> builderURILocations;
106108

107109
private final ClassPathClassLoader classPathClassLoader;
108110
private final ClassLoader classLoader;
@@ -124,6 +126,7 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St
124126
classes = EconomicMap.create();
125127
packages = EconomicMap.create();
126128
emptySet = EconomicSet.create();
129+
builderURILocations = EconomicSet.create();
127130

128131
classPathClassLoader = new ClassPathClassLoader(Util.verifyClassPathAndConvertToURLs(classpath), defaultSystemClassLoader);
129132

@@ -142,6 +145,7 @@ protected NativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, St
142145
.map(Path::of)
143146
.map(Util::toRealPath)
144147
.collect(Collectors.toUnmodifiableList());
148+
buildcp.stream().map(Path::toUri).forEach(builderURILocations::add);
145149

146150
imagemp = Arrays.stream(modulePath)
147151
.map(Path::of)
@@ -650,10 +654,16 @@ private void initModule(ModuleReference moduleReference) {
650654
}
651655
try (ModuleReader moduleReader = moduleReference.open()) {
652656
Module module = optionalModule.get();
657+
var container = moduleReference.location().orElseThrow();
658+
if (ModuleLayer.boot().equals(module.getLayer())) {
659+
builderURILocations.add(container);
660+
}
653661
moduleReader.list().forEach(moduleResource -> {
654-
if (moduleResource.endsWith(CLASS_EXTENSION)) {
655-
currentlyProcessedEntry = moduleReferenceLocation + "/" + moduleResource;
656-
executor.execute(() -> handleClassFileName(moduleReference.location().orElseThrow(), module, moduleResource, '/'));
662+
char fileSystemSeparatorChar = '/';
663+
String className = extractClassName(moduleResource, fileSystemSeparatorChar);
664+
if (className != null) {
665+
currentlyProcessedEntry = moduleReferenceLocation + fileSystemSeparatorChar + moduleResource;
666+
executor.execute(() -> handleClassFileName(container, module, className));
657667
}
658668
entriesProcessed.increment();
659669
});
@@ -715,9 +725,10 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th
715725
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
716726
assert !excludes.contains(file.getParent()) : "Visiting file '" + file + "' with excluded parent directory";
717727
String fileName = root.relativize(file).toString();
718-
if (fileName.endsWith(CLASS_EXTENSION)) {
728+
String className = extractClassName(fileName, fileSystemSeparatorChar);
729+
if (className != null) {
719730
currentlyProcessedEntry = file.toUri().toString();
720-
executor.execute(() -> handleClassFileName(container, null, fileName, fileSystemSeparatorChar));
731+
executor.execute(() -> handleClassFileName(container, null, className));
721732
}
722733
entriesProcessed.increment();
723734
return FileVisitResult.CONTINUE;
@@ -739,35 +750,34 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) {
739750

740751
/**
741752
* Take a file name from a possibly-multi-versioned jar file and remove the versioning
742-
* information. See https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html for
743-
* the specification of the versioning strings.
744-
*
753+
* information. See
754+
* <a href="https://docs.oracle.com/javase/9/docs/api/java/util/jar/JarFile.html">...</a>
755+
* for the specification of the versioning strings.
756+
* <p>
745757
* Then, depend on the JDK class loading mechanism to prefer the appropriately-versioned
746758
* class when the class is loaded. The same class name be loaded multiple times, but each
747759
* request will return the same appropriately-versioned class. If a higher-versioned class
748760
* is not available in a lower-versioned JDK, a ClassNotFoundException will be thrown, which
749761
* will be handled appropriately.
750762
*/
751-
private String strippedClassFileName(String fileName) {
752-
final String versionedPrefix = "META-INF/versions/";
753-
final String versionedSuffix = "/";
763+
private String extractClassName(String fileName, char fileSystemSeparatorChar) {
764+
if (!fileName.endsWith(CLASS_EXTENSION)) {
765+
return null;
766+
}
767+
String versionedPrefix = "META-INF/versions/";
768+
String versionedSuffix = "/";
754769
String result = fileName;
755770
if (fileName.startsWith(versionedPrefix)) {
756771
final int versionedSuffixIndex = fileName.indexOf(versionedSuffix, versionedPrefix.length());
757772
if (versionedSuffixIndex >= 0) {
758773
result = fileName.substring(versionedSuffixIndex + versionedSuffix.length());
759774
}
760775
}
761-
return result.substring(0, result.length() - CLASS_EXTENSION.length());
776+
String strippedClassFileName = result.substring(0, result.length() - CLASS_EXTENSION.length());
777+
return strippedClassFileName.equals("module-info") ? null : strippedClassFileName.replace(fileSystemSeparatorChar, '.');
762778
}
763779

764-
private void handleClassFileName(URI container, Module module, String fileName, char fileSystemSeparatorChar) {
765-
String strippedClassFileName = strippedClassFileName(fileName);
766-
if (strippedClassFileName.equals("module-info")) {
767-
return;
768-
}
769-
770-
String className = strippedClassFileName.replace(fileSystemSeparatorChar, '.');
780+
private void handleClassFileName(URI container, Module module, String className) {
771781
synchronized (classes) {
772782
EconomicSet<String> classNames = classes.get(container);
773783
if (classNames == null) {
@@ -800,4 +810,37 @@ private void handleClassFileName(URI container, Module module, String fileName,
800810
}
801811
}
802812
}
813+
814+
public void reportBuilderClassesInApplication() {
815+
EconomicMap<URI, EconomicSet<String>> builderClasses = EconomicMap.create();
816+
EconomicMap<URI, EconomicSet<String>> applicationClasses = EconomicMap.create();
817+
MapCursor<URI, EconomicSet<String>> classesEntries = classes.getEntries();
818+
while (classesEntries.advance()) {
819+
var destinationMap = builderURILocations.contains(classesEntries.getKey()) ? builderClasses : applicationClasses;
820+
destinationMap.put(classesEntries.getKey(), classesEntries.getValue());
821+
}
822+
boolean tolerateViolations = SubstrateOptions.TolerateBuilderClassesOnImageClasspath.getValue(parsedHostedOptions);
823+
MapCursor<URI, EconomicSet<String>> applicationClassesEntries = applicationClasses.getEntries();
824+
while (applicationClassesEntries.advance()) {
825+
var applicationClassContainer = applicationClassesEntries.getKey();
826+
for (String applicationClass : applicationClassesEntries.getValue()) {
827+
MapCursor<URI, EconomicSet<String>> builderClassesEntries = builderClasses.getEntries();
828+
while (builderClassesEntries.advance()) {
829+
var builderClassContainer = builderClassesEntries.getKey();
830+
if (builderClassesEntries.getValue().contains(applicationClass)) {
831+
String message = String.format("Class-path entry %s contains class %s. This class is part of the image builder itself (in %s) and must not be passed via -cp.",
832+
applicationClassContainer, applicationClass, builderClassContainer);
833+
if (!tolerateViolations) {
834+
String errorMessage = String.join(" ", message,
835+
"This can be caused by a fat-jar that illegally includes svm.jar (or graal-sdk.jar) due to its build-time dependency on it.",
836+
"As a temporary workaround, %s allows turning this error into a warning.");
837+
throw UserError.abort(errorMessage, SubstrateOptionsParser.commandArgument(SubstrateOptions.TolerateBuilderClassesOnImageClasspath, "+"));
838+
} else {
839+
System.out.println("Warning: " + message);
840+
}
841+
}
842+
}
843+
}
844+
}
845+
}
803846
}

substratevm/src/com.oracle.svm.truffle.nfi.posix/src/com/oracle/svm/truffle/nfi/posix/PosixTruffleNFIFeature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ private static PointerBase dlmopen(Lmid_t lmid, String filename, int mode) {
110110
@Platforms(Platform.LINUX.class)
111111
private static PointerBase loadLibraryInNamespace(long nativeContext, String name, int mode) {
112112
assert (mode & isolatedNamespaceFlag) == 0;
113-
Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextLinux context = SubstrateUtil.cast(getContext(nativeContext), Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextLinux.class);
113+
var context = SubstrateUtil.cast(getContext(nativeContext), Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Linux.class);
114114

115115
// Double-checked locking on the NFI context instance.
116116
long namespaceId = context.isolatedNamespaceId;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@
3434

3535
@Platforms(Platform.LINUX.class)
3636
@TargetClass(className = "com.oracle.truffle.nfi.backend.libffi.LibFFIContext", onlyWith = TruffleNFIFeature.IsEnabled.class)
37-
final class Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextLinux {
37+
final class Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Linux {
3838

3939
// Checkstyle: stop
4040
@Alias volatile long isolatedNamespaceId;
4141

4242
@Alias @InjectAccessors(IsolatedAccessor.class) int ISOLATED_NAMESPACE;
4343

4444
static class IsolatedAccessor {
45-
static int getISOLATED_NAMESPACE(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextLinux ctx) {
45+
static int getISOLATED_NAMESPACE(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Linux ctx) {
4646
return PosixTruffleNFISupport.isolatedNamespaceFlag;
4747
}
4848
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,24 @@
3535

3636
@TargetClass(className = "com.oracle.truffle.nfi.backend.libffi.LibFFIContext", onlyWith = TruffleNFIFeature.IsEnabled.class)
3737
@Platforms({Platform.LINUX.class, Platform.DARWIN.class})
38-
final class Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextPosix {
38+
final class Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Posix {
3939

4040
// Checkstyle: stop
4141
static class RTLDAccessor {
4242

43-
static int getRTLD_GLOBAL(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextPosix ctx) {
43+
static int getRTLD_GLOBAL(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Posix ctx) {
4444
return Dlfcn.RTLD_GLOBAL();
4545
}
4646

47-
static int getRTLD_LOCAL(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextPosix ctx) {
47+
static int getRTLD_LOCAL(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Posix ctx) {
4848
return Dlfcn.RTLD_LOCAL();
4949
}
5050

51-
static int getRTLD_LAZY(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextPosix ctx) {
51+
static int getRTLD_LAZY(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Posix ctx) {
5252
return Dlfcn.RTLD_LAZY();
5353
}
5454

55-
static int getRTLD_NOW(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContextPosix ctx) {
55+
static int getRTLD_NOW(@SuppressWarnings("unused") Target_com_oracle_truffle_nfi_backend_libffi_LibFFIContext_Posix ctx) {
5656
return Dlfcn.RTLD_NOW();
5757
}
5858
}

vm/mx.vm/mx_vm_gate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ def build_tests_image(image_dir, options, unit_tests=None, additional_deps=None,
476476

477477
if additional_deps:
478478
build_deps = build_deps + additional_deps
479-
extra_image_args = mx.get_runtime_jvm_args(build_deps, jdk=mx_compiler.jdk)
479+
extra_image_args = mx.get_runtime_jvm_args(build_deps, jdk=mx_compiler.jdk, exclude_names=mx_sdk_vm_impl.NativePropertiesBuildTask.implicit_excludes)
480480
tests_image = native_image(build_options + extra_image_args)
481481
import configparser
482482
artifacts = configparser.RawConfigParser(allow_no_value=True)

0 commit comments

Comments
 (0)