Skip to content

Commit 96e0020

Browse files
Tudor-Stefan Magirescuolpaw
authored andcommitted
[GR-65623] Implement layer compatibility check for class and module-path entries.
1 parent e93e8f1 commit 96e0020

File tree

8 files changed

+276
-7
lines changed

8 files changed

+276
-7
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/Digest.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ public static String digest(byte[] bytes, int offset, int length) {
102102
* new byte array.
103103
*/
104104
public static byte[] digestAsByteArray(byte[] bytes, int offset, int length) {
105-
LongLong hash = MurmurHash3_x64_128(bytes, offset, length, HASH_SEED);
105+
return longLongToByteArray(MurmurHash3_x64_128(bytes, offset, length, HASH_SEED));
106+
}
106107

108+
private static byte[] longLongToByteArray(LongLong hash) {
107109
byte[] array = new byte[DIGEST_SIZE];
108110
encodeBase62(hash.l1, array, 0);
109111
encodeBase62(hash.l2, array, BASE62_DIGITS_PER_LONG);
@@ -281,4 +283,20 @@ private static long fmix64(long input) {
281283

282284
return k;
283285
}
286+
287+
public static final class DigestBuilder {
288+
private LongLong digest;
289+
290+
public DigestBuilder() {
291+
digest = new LongLong(0L, 0L);
292+
}
293+
294+
public void update(byte[] input) {
295+
digest = MurmurHash3_x64_128(input, 0, input.length, digest.l1 ^ digest.l2);
296+
}
297+
298+
public byte[] digest() {
299+
return longLongToByteArray(digest);
300+
}
301+
}
284302
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JRTSupport.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.net.URL;
3030
import java.net.URLConnection;
3131
import java.nio.ByteBuffer;
32+
import java.nio.file.FileSystem;
3233
import java.nio.file.Path;
3334
import java.util.Arrays;
3435
import java.util.HashMap;
@@ -181,6 +182,13 @@ protected URLConnection openConnection(URL url) throws IOException {
181182
final class Target_jdk_internal_jrtfs_JrtFileSystemProvider_JRTDisabled {
182183
}
183184

185+
@TargetClass(className = "jdk.internal.jrtfs.JrtFileSystemProvider", onlyWith = JRTEnabled.class)
186+
final class Target_jdk_internal_jrtfs_JrtFileSystemProvider_BuildTime {
187+
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)//
188+
@Alias//
189+
volatile FileSystem theFileSystem;
190+
}
191+
184192
// endregion Disable jimage/jrtfs
185193

186194
@TargetClass(className = "jdk.internal.jimage.BasicImageReader")

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

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
*/
2525
package com.oracle.svm.hosted;
2626

27+
import static jdk.graal.compiler.util.Digest.DigestBuilder;
28+
2729
import java.io.File;
2830
import java.io.IOException;
2931
import java.lang.module.Configuration;
@@ -36,9 +38,12 @@
3638
import java.lang.reflect.Method;
3739
import java.net.URI;
3840
import java.net.URISyntaxException;
41+
import java.net.URL;
3942
import java.nio.channels.ClosedByInterruptException;
43+
import java.nio.charset.StandardCharsets;
4044
import java.nio.file.FileSystem;
4145
import java.nio.file.FileSystems;
46+
import java.nio.file.FileVisitOption;
4247
import java.nio.file.FileVisitResult;
4348
import java.nio.file.FileVisitor;
4449
import java.nio.file.Files;
@@ -52,6 +57,8 @@
5257
import java.util.Collections;
5358
import java.util.Comparator;
5459
import java.util.Deque;
60+
import java.util.EnumSet;
61+
import java.util.Enumeration;
5562
import java.util.HashSet;
5663
import java.util.LinkedHashMap;
5764
import java.util.LinkedHashSet;
@@ -67,9 +74,12 @@
6774
import java.util.concurrent.TimeUnit;
6875
import java.util.concurrent.atomic.LongAdder;
6976
import java.util.function.BiConsumer;
77+
import java.util.jar.JarEntry;
78+
import java.util.jar.JarFile;
7079
import java.util.stream.Collector;
7180
import java.util.stream.Collectors;
7281
import java.util.stream.Stream;
82+
import java.util.zip.ZipFile;
7383

7484
import org.graalvm.collections.EconomicMap;
7585
import org.graalvm.collections.EconomicSet;
@@ -116,6 +126,7 @@ public final class NativeImageClassLoaderSupport {
116126
private final List<Path> buildmp;
117127

118128
private final Set<Path> imageProvidedJars;
129+
private PathDigests pathDigests;
119130

120131
private final EconomicMap<URI, EconomicSet<String>> classes;
121132
private final EconomicMap<URI, EconomicSet<String>> packages;
@@ -300,6 +311,47 @@ public NativeImageClassLoader getClassLoader() {
300311
return classLoader;
301312
}
302313

314+
public Optional<PathDigests> getPathDigests(boolean free) {
315+
Optional<PathDigests> res = Optional.ofNullable(pathDigests);
316+
if (free) {
317+
pathDigests = null;
318+
}
319+
return res;
320+
}
321+
322+
public void initializePathDigests(Path digestIgnoreRelativePath) {
323+
List<Path> digestIgnorePaths = new ArrayList<>();
324+
if (digestIgnoreRelativePath != null) {
325+
try {
326+
Enumeration<URL> urls = classLoader.getResources(digestIgnoreRelativePath.toString());
327+
while (urls.hasMoreElements()) {
328+
URI uri = urls.nextElement().toURI();
329+
if ("jar".equalsIgnoreCase(uri.getScheme())) {
330+
String uriString = uri.toString();
331+
String fileUriString = uriString.substring("jar:".length(), uriString.indexOf("!/"));
332+
uri = URI.create(fileUriString);
333+
}
334+
digestIgnorePaths.add(Path.of(uri));
335+
}
336+
} catch (URISyntaxException | IOException e) {
337+
throw UserError.abort("Error while looking for %s in %s", digestIgnoreRelativePath, classLoader);
338+
}
339+
}
340+
341+
pathDigests = new PathDigests(filterIgnoredPathEntries(imagecp, digestIgnorePaths), filterIgnoredPathEntries(imagemp, digestIgnorePaths));
342+
}
343+
344+
private List<Path> filterIgnoredPathEntries(List<Path> pathEntries, List<Path> digestIgnorePaths) {
345+
return pathEntries.stream().filter(pathEntry -> {
346+
for (Path p : digestIgnorePaths) {
347+
if (p.startsWith(pathEntry)) {
348+
return false;
349+
}
350+
}
351+
return true;
352+
}).toList();
353+
}
354+
303355
public LibGraalLoader getLibGraalLoader() {
304356
VMError.guarantee(libGraalLoader != null, "Invalid access to libGraalLoader before getting set up");
305357
return libGraalLoader.orElse(null);
@@ -932,13 +984,18 @@ private void initModule(ModuleReference moduleReference, boolean moduleRequiresI
932984
if (ModuleLayer.boot().equals(module.getLayer())) {
933985
builderURILocations.add(container);
934986
}
987+
final boolean isInImageModulePathOfLayeredBuild = pathDigests != null && pathDigests.mpDigests.containsKey(container);
988+
final boolean isJar = ClasspathUtils.isJar(Path.of(container));
935989
moduleReader.list().forEach(moduleResource -> {
936990
char fileSystemSeparatorChar = '/';
937991
String className = extractClassName(moduleResource, fileSystemSeparatorChar);
938992
if (className != null) {
939993
currentlyProcessedEntry = moduleReferenceLocation + fileSystemSeparatorChar + moduleResource;
940994
executor.execute(() -> handleClassFileName(container, module, className, includeUnconditionally, moduleRequiresInit, preserveModule));
941995
}
996+
if (isInImageModulePathOfLayeredBuild) {
997+
executor.execute(() -> PathDigests.storePathFileDigest(container, moduleResource, isJar, pathDigests.mpDigests));
998+
}
942999
entriesProcessed.increment();
9431000
});
9441001
} catch (IOException e) {
@@ -962,7 +1019,7 @@ private void loadClassesFromPath(Path path) {
9621019
}
9631020
if (probeJarFileSystem != null) {
9641021
try (FileSystem jarFileSystem = probeJarFileSystem) {
965-
loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet(), includeUnconditionally, includeAllMetadata);
1022+
loadClassesFromPath(container, jarFileSystem.getPath("/"), null, Collections.emptySet(), includeUnconditionally, includeAllMetadata, true);
9661023
}
9671024
}
9681025
} catch (ClosedByInterruptException ignored) {
@@ -973,20 +1030,21 @@ private void loadClassesFromPath(Path path) {
9731030
} else {
9741031
URI container = path.toUri();
9751032
loadClassesFromPath(container, path, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT, ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES, includeUnconditionally,
976-
includeAllMetadata);
1033+
includeAllMetadata, false);
9771034
}
9781035
}
9791036

9801037
private static final String CLASS_EXTENSION = ".class";
9811038

982-
private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set<Path> excludes, boolean includeUnconditionally, boolean includeAllMetadata) {
1039+
private void loadClassesFromPath(URI container, Path root, Path excludeRoot, Set<Path> excludes, boolean includeUnconditionally, boolean includeAllMetadata, boolean isJar) {
9831040
boolean useFilter = root.equals(excludeRoot);
9841041
if (useFilter) {
9851042
String excludesStr = excludes.stream().map(Path::toString).collect(Collectors.joining(", "));
9861043
LogUtils.warning("Using directory %s on classpath is discouraged. Reading classes/resources from directories %s will be suppressed.", excludeRoot, excludesStr);
9871044
}
9881045
FileVisitor<Path> visitor = new SimpleFileVisitor<>() {
9891046
private final char fileSystemSeparatorChar = root.getFileSystem().getSeparator().charAt(0);
1047+
private final boolean isInImageClassPathOfLayeredBuild = pathDigests != null && pathDigests.getCpDigests().containsKey(container);
9901048

9911049
@Override
9921050
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
@@ -1007,6 +1065,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
10071065
currentlyProcessedEntry = file.toUri().toString();
10081066
executor.execute(() -> handleClassFileName(container, null, className, includeUnconditionally, true, includeAllMetadata));
10091067
}
1068+
if (isInImageClassPathOfLayeredBuild) {
1069+
executor.execute(() -> PathDigests.storePathFileDigest(container, fileName, isJar, pathDigests.cpDigests));
1070+
}
10101071
entriesProcessed.increment();
10111072
return FileVisitResult.CONTINUE;
10121073
}
@@ -1019,7 +1080,7 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) {
10191080
};
10201081

10211082
try {
1022-
Files.walkFileTree(root, visitor);
1083+
Files.walkFileTree(root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, visitor);
10231084
} catch (IOException ex) {
10241085
throw VMError.shouldNotReachHere(ex);
10251086
}
@@ -1372,4 +1433,54 @@ public Set<IncludeOptionsSupport.PackageOptionValue> packages() {
13721433
return packages.keySet();
13731434
}
13741435
}
1436+
1437+
public static final class PathDigests {
1438+
private final EconomicMap<URI, List<String>> cpDigests = EconomicMap.create();
1439+
private final EconomicMap<URI, List<String>> mpDigests = EconomicMap.create();
1440+
1441+
private PathDigests(List<Path> imagecp, List<Path> imagemp) {
1442+
imagecp.stream()
1443+
.map(Path::toUri)
1444+
.forEach(path -> cpDigests.put(path, new ArrayList<>()));
1445+
imagemp.stream()
1446+
.map(Path::toUri)
1447+
.forEach(path -> mpDigests.put(path, new ArrayList<>()));
1448+
}
1449+
1450+
private static void storePathFileDigest(URI container, String resource, boolean isJar, EconomicMap<URI, List<String>> digests) {
1451+
byte[] fileContent;
1452+
try {
1453+
if (isJar) {
1454+
try (JarFile jarFile = new JarFile(new File(container), true, ZipFile.OPEN_READ, JarFile.runtimeVersion())) {
1455+
JarEntry jarEntry = jarFile.getJarEntry(resource);
1456+
fileContent = jarFile.getInputStream(jarEntry).readAllBytes();
1457+
}
1458+
} else {
1459+
Path resourcePath = Path.of(container).resolve(resource);
1460+
if (!resourcePath.toFile().isFile()) {
1461+
return;
1462+
}
1463+
fileContent = Files.readAllBytes(resourcePath);
1464+
}
1465+
} catch (IOException e) {
1466+
throw UserError.abort("Image builder cannot read file: " + resource);
1467+
}
1468+
1469+
DigestBuilder db = new DigestBuilder();
1470+
db.update(fileContent);
1471+
db.update(resource.getBytes(StandardCharsets.UTF_8));
1472+
List<String> containerDigests = digests.get(container);
1473+
synchronized (containerDigests) {
1474+
containerDigests.add(new String(db.digest(), StandardCharsets.UTF_8));
1475+
}
1476+
}
1477+
1478+
public EconomicMap<URI, List<String>> getCpDigests() {
1479+
return cpDigests;
1480+
}
1481+
1482+
public EconomicMap<URI, List<String>> getMpDigests() {
1483+
return mpDigests;
1484+
}
1485+
}
13751486
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/driver/LayerOptionsSupport.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
3535
public class LayerOptionsSupport extends IncludeOptionsSupport {
3636

3737
public record LayerOption(Path fileName, ExtendedOption[] extendedOptions) {
38-
/** Split a layer option into its components. */
38+
/**
39+
* Split a layer option into its components.
40+
*/
3941
public static LayerOption parse(String layerOptionValue) {
4042
VMError.guarantee(!layerOptionValue.isEmpty());
4143
// Given an argument of form layer-file.nil,module=m1,package=p1

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/HostedImageLayerBuildingSupport.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ public Function<Class<?>, SingletonTrait[]> getSingletonTraitInjector() {
187187
*/
188188
public static void processLayerOptions(EconomicMap<OptionKey<?>, Object> values, NativeImageClassLoaderSupport classLoaderSupport) {
189189
OptionValues hostedOptions = new OptionValues(values);
190+
Path digestIgnorePath = null;
190191

191192
if (isLayerCreateOptionEnabled(hostedOptions)) {
192193
ValueWithOrigin<String> valueWithOrigin = getLayerCreateValueWithOrigin(hostedOptions);
@@ -205,7 +206,12 @@ public static void processLayerOptions(EconomicMap<OptionKey<?>, Object> values,
205206
classLoaderSupport.setLayerFile(layerFile);
206207

207208
NativeImageClassLoaderSupport.IncludeSelectors layerSelectors = classLoaderSupport.getLayerSelectors();
209+
IncludeOptionsSupport.ExtendedOption digestIgnoreExtendedOption = new IncludeOptionsSupport.ExtendedOption("digest-ignore", null);
208210
for (IncludeOptionsSupport.ExtendedOption option : layerOption.extendedOptions()) {
211+
if (option.equals(digestIgnoreExtendedOption)) {
212+
digestIgnorePath = valueWithOrigin.origin().location();
213+
continue;
214+
}
209215
IncludeOptionsSupport.parseIncludeSelector(layerCreateArg, valueWithOrigin, layerSelectors, option, layerCreatePossibleOptions());
210216
}
211217

@@ -238,6 +244,10 @@ public static void processLayerOptions(EconomicMap<OptionKey<?>, Object> values,
238244
SubstrateOptions.ApplicationLayerInitializedClasses.update(values, Module.class.getName());
239245
setOptionIfHasNotBeenSet(values, SubstrateOptions.ConcealedOptions.RelativeCodePointers, true);
240246
}
247+
248+
if (isLayerCreateOptionEnabled(hostedOptions) || isLayerUseOptionEnabled(hostedOptions)) {
249+
classLoaderSupport.initializePathDigests(digestIgnorePath);
250+
}
241251
}
242252

243253
private static void setOptionIfHasNotBeenSet(EconomicMap<OptionKey<?>, Object> values, HostedOptionKey<Boolean> option, boolean boxedValue) {
@@ -285,7 +295,7 @@ private static String getLayerCreateValue(ValueWithOrigin<String> valueWithOrigi
285295
return String.join(",", OptionUtils.resolveOptionValuesRedirection(SubstrateOptions.LayerCreate, valueWithOrigin));
286296
}
287297

288-
private static boolean isLayerUseOptionEnabled(OptionValues values) {
298+
public static boolean isLayerUseOptionEnabled(OptionValues values) {
289299
if (SubstrateOptions.LayerUse.hasBeenSet(values)) {
290300
return !getLayerUseValue(values).toString().isEmpty();
291301
}

0 commit comments

Comments
 (0)