8353917: jnativescan: Simplify ClassResolver

Reviewed-by: mcimadamore
This commit is contained in:
Jorn Vernee 2025-04-11 11:15:32 +00:00
parent 36069f6efa
commit 8bb0ca4971
5 changed files with 183 additions and 224 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025, 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
@ -27,6 +27,8 @@ package com.sun.tools.jnativescan;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassModel;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
@ -40,7 +42,12 @@ sealed interface ClassFileSource {
String moduleName();
Path path();
Stream<byte[]> classFiles(Runtime.Version version) throws IOException;
Stream<byte[]> classFiles() throws IOException;
default Stream<ClassModel> classModels() throws IOException {
ClassFile classFile = ClassFile.of();
return classFiles().map(classFile::parse);
}
record Module(ModuleReference reference) implements ClassFileSource {
@Override
@ -55,7 +62,7 @@ sealed interface ClassFileSource {
}
@Override
public Stream<byte[]> classFiles(Runtime.Version version) throws IOException {
public Stream<byte[]> classFiles() throws IOException {
ModuleReader reader = reference().open();
return reader.list()
.filter(resourceName -> resourceName.endsWith(".class"))
@ -75,14 +82,14 @@ sealed interface ClassFileSource {
}
}
record ClassPathJar(Path path) implements ClassFileSource {
record ClassPathJar(Path path, Runtime.Version version) implements ClassFileSource {
@Override
public String moduleName() {
return "ALL-UNNAMED";
}
@Override
public Stream<byte[]> classFiles(Runtime.Version version) throws IOException {
public Stream<byte[]> classFiles() throws IOException {
JarFile jf = new JarFile(path().toFile(), false, ZipFile.OPEN_READ, version);
return jf.versionedStream()
.filter(je -> je.getName().endsWith(".class"))
@ -109,7 +116,7 @@ sealed interface ClassFileSource {
}
@Override
public Stream<byte[]> classFiles(Runtime.Version version) throws IOException {
public Stream<byte[]> classFiles() throws IOException {
return Files.walk(path)
.filter(file -> Files.isRegularFile(file) && file.toString().endsWith(".class"))
.map(file -> {

View File

@ -1,163 +0,0 @@
/*
* Copyright (c) 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
* 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.sun.tools.jnativescan;
import com.sun.tools.javac.platform.PlatformDescription;
import com.sun.tools.javac.platform.PlatformProvider;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassModel;
import java.lang.constant.ClassDesc;
import java.lang.module.ModuleDescriptor;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
abstract class ClassResolver implements AutoCloseable {
static ClassResolver forClassFileSources(List<ClassFileSource> sources, Runtime.Version version) throws IOException {
Map<ClassDesc, Info> classMap = new HashMap<>();
for (ClassFileSource source : sources) {
try (Stream<byte[]> classFiles = source.classFiles(version)) {
classFiles.forEach(bytes -> {
ClassModel model = ClassFile.of().parse(bytes);
ClassDesc desc = model.thisClass().asSymbol();
classMap.put(desc, new Info(source, model));
});
}
}
return new SimpleClassResolver(classMap);
}
static ClassResolver forSystemModules(Runtime.Version version) {
String platformName = String.valueOf(version.feature());
PlatformProvider platformProvider = ServiceLoader.load(PlatformProvider.class).findFirst().orElseThrow();
PlatformDescription platform;
try {
platform = platformProvider.getPlatform(platformName, null);
} catch (PlatformProvider.PlatformNotSupported e) {
throw new JNativeScanFatalError("Release: " + platformName + " not supported", e);
}
JavaFileManager fm = platform.getFileManager();
return new SystemModuleClassResolver(fm);
}
record Info(ClassFileSource source, ClassModel model) {}
public abstract void forEach(BiConsumer<ClassDesc, ClassResolver.Info> action);
public abstract Optional<ClassResolver.Info> lookup(ClassDesc desc);
@Override
public abstract void close() throws IOException;
private static class SimpleClassResolver extends ClassResolver {
private final Map<ClassDesc, ClassResolver.Info> classMap;
public SimpleClassResolver(Map<ClassDesc, Info> classMap) {
this.classMap = classMap;
}
public void forEach(BiConsumer<ClassDesc, ClassResolver.Info> action) {
classMap.forEach(action);
}
public Optional<ClassResolver.Info> lookup(ClassDesc desc) {
return Optional.ofNullable(classMap.get(desc));
}
@Override
public void close() {}
}
private static class SystemModuleClassResolver extends ClassResolver {
private final JavaFileManager platformFileManager;
private final Map<String, String> packageToSystemModule;
private final Map<ClassDesc, Info> cache = new HashMap<>();
public SystemModuleClassResolver(JavaFileManager platformFileManager) {
this.platformFileManager = platformFileManager;
this.packageToSystemModule = packageToSystemModule(platformFileManager);
}
private static Map<String, String> packageToSystemModule(JavaFileManager platformFileManager) {
try {
Set<JavaFileManager.Location> locations = platformFileManager.listLocationsForModules(
StandardLocation.SYSTEM_MODULES).iterator().next();
Map<String, String> result = new HashMap<>();
for (JavaFileManager.Location loc : locations) {
JavaFileObject jfo = platformFileManager.getJavaFileForInput(loc, "module-info", JavaFileObject.Kind.CLASS);
ModuleDescriptor descriptor = ModuleDescriptor.read(jfo.openInputStream());
for (ModuleDescriptor.Exports export : descriptor.exports()) {
if (!export.isQualified()) {
result.put(export.source(), descriptor.name());
}
}
}
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void forEach(BiConsumer<ClassDesc, Info> action) {
throw new UnsupportedOperationException("NYI");
}
@Override
public Optional<Info> lookup(ClassDesc desc) {
return Optional.ofNullable(cache.computeIfAbsent(desc, _ -> {
String qualName = JNativeScanTask.qualName(desc);
String moduleName = packageToSystemModule.get(desc.packageName());
if (moduleName != null) {
try {
JavaFileManager.Location loc = platformFileManager.getLocationForModule(StandardLocation.SYSTEM_MODULES, moduleName);
JavaFileObject jfo = platformFileManager.getJavaFileForInput(loc, qualName, JavaFileObject.Kind.CLASS);
if (jfo == null) {
throw new JNativeScanFatalError("System class can not be found: " + qualName);
}
ClassModel model = ClassFile.of().parse(jfo.openInputStream().readAllBytes());
return new Info(null, model);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}));
}
@Override
public void close() throws IOException {
platformFileManager.close();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025, 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
@ -26,17 +26,18 @@ package com.sun.tools.jnativescan;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.classfile.ClassModel;
import java.lang.constant.ClassDesc;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipFile;
class JNativeScanTask {
@ -76,11 +77,26 @@ class JNativeScanTask {
Set<String> errors = new LinkedHashSet<>();
Diagnostics diagnostics = (context, error) ->
errors.add("Error while processing method: " + context + ": " + error.getMessage());
SortedMap<ClassFileSource, SortedMap<ClassDesc, List<RestrictedUse>>> allRestrictedMethods;
try(ClassResolver classesToScan = ClassResolver.forClassFileSources(toScan, version);
ClassResolver systemClassResolver = ClassResolver.forSystemModules(version)) {
NativeMethodFinder finder = NativeMethodFinder.create(diagnostics, classesToScan, systemClassResolver);
allRestrictedMethods = finder.findAll();
SortedMap<ClassFileSource, SortedMap<ClassDesc, List<RestrictedUse>>> allRestrictedMethods
= new TreeMap<>(Comparator.comparing(ClassFileSource::path));
try(SystemClassResolver systemClassResolver = SystemClassResolver.forRuntimeVersion(version)) {
NativeMethodFinder finder = NativeMethodFinder.create(diagnostics, systemClassResolver);
for (ClassFileSource source : toScan) {
SortedMap<ClassDesc, List<RestrictedUse>> perClass
= new TreeMap<>(Comparator.comparing(JNativeScanTask::qualName));
try (Stream<ClassModel> stream = source.classModels()) {
stream.forEach(classModel -> {
List<RestrictedUse> restrictedUses = finder.find(classModel);
if (!restrictedUses.isEmpty()) {
perClass.put(classModel.thisClass().asSymbol(), restrictedUses);
}
});
}
if (!perClass.isEmpty()) {
allRestrictedMethods.put(source, perClass);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -115,7 +131,7 @@ class JNativeScanTask {
jarsToScan.offer(otherJar);
}
}
result.add(new ClassFileSource.ClassPathJar(jar));
result.add(new ClassFileSource.ClassPathJar(jar, version));
}
} else if (Files.isDirectory(path)) {
result.add(new ClassFileSource.ClassPathDirectory(path));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025, 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
@ -33,7 +33,6 @@ import java.lang.classfile.Attributes;
import java.lang.classfile.ClassModel;
import java.lang.classfile.MethodModel;
import java.lang.classfile.instruction.InvokeInstruction;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.lang.reflect.AccessFlag;
import java.util.*;
@ -46,61 +45,46 @@ class NativeMethodFinder {
private final Map<MethodRef, Boolean> cache = new HashMap<>();
private final Diagnostics diagnostics;
private final ClassResolver classesToScan;
private final ClassResolver systemClassResolver;
private final SystemClassResolver systemClassResolver;
private NativeMethodFinder(Diagnostics diagnostics, ClassResolver classesToScan, ClassResolver systemClassResolver) {
private NativeMethodFinder(Diagnostics diagnostics, SystemClassResolver systemClassResolver) {
this.diagnostics = diagnostics;
this.classesToScan = classesToScan;
this.systemClassResolver = systemClassResolver;
}
public static NativeMethodFinder create(Diagnostics diagnostics, ClassResolver classesToScan,
ClassResolver systemClassResolver) throws JNativeScanFatalError, IOException {
return new NativeMethodFinder(diagnostics, classesToScan, systemClassResolver);
public static NativeMethodFinder create(Diagnostics diagnostics, SystemClassResolver systemClassResolver) throws JNativeScanFatalError, IOException {
return new NativeMethodFinder(diagnostics, systemClassResolver);
}
public SortedMap<ClassFileSource, SortedMap<ClassDesc, List<RestrictedUse>>> findAll() throws JNativeScanFatalError {
SortedMap<ClassFileSource, SortedMap<ClassDesc, List<RestrictedUse>>> restrictedMethods
= new TreeMap<>(Comparator.comparing(ClassFileSource::path));
classesToScan.forEach((_, info) -> {
ClassModel classModel = info.model();
List<RestrictedUse> perClass = new ArrayList<>();
classModel.methods().forEach(methodModel -> {
public List<RestrictedUse> find(ClassModel model) throws JNativeScanFatalError {
return model.methods().stream().<RestrictedUse>mapMulti((methodModel, sink) -> {
if (methodModel.flags().has(AccessFlag.NATIVE)) {
perClass.add(new NativeMethodDecl(MethodRef.ofModel(methodModel)));
sink.accept(new NativeMethodDecl(MethodRef.ofModel(methodModel)));
} else {
SortedSet<MethodRef> perMethod = new TreeSet<>(Comparator.comparing(MethodRef::toString));
methodModel.code().ifPresent(code -> {
code.forEach(e -> {
switch (e) {
case InvokeInstruction invoke -> {
MethodRef ref = MethodRef.ofInvokeInstruction(invoke);
try {
if (isRestrictedMethod(ref)) {
perMethod.add(ref);
}
} catch (JNativeScanFatalError ex) {
diagnostics.error(MethodRef.ofModel(methodModel), ex);
}
}
default -> {
}
}
});
});
SortedSet<MethodRef> perMethod = findRestrictedMethodInvocations(methodModel);
if (!perMethod.isEmpty()) {
perClass.add(new RestrictedMethodRefs(MethodRef.ofModel(methodModel), perMethod));
sink.accept(new RestrictedMethodRefs(MethodRef.ofModel(methodModel), perMethod));
}
}
});
if (!perClass.isEmpty()) {
restrictedMethods.computeIfAbsent(info.source(),
_ -> new TreeMap<>(Comparator.comparing(JNativeScanTask::qualName)))
.put(classModel.thisClass().asSymbol(), perClass);
})
.toList();
}
private SortedSet<MethodRef> findRestrictedMethodInvocations(MethodModel methodModel) {
SortedSet<MethodRef> perMethod = new TreeSet<>(Comparator.comparing(MethodRef::toString));
methodModel.code().ifPresent(code -> code.forEach(e -> {
if (e instanceof InvokeInstruction invoke) {
MethodRef ref = MethodRef.ofInvokeInstruction(invoke);
try {
if (isRestrictedMethod(ref)) {
perMethod.add(ref);
}
} catch (JNativeScanFatalError ex) {
diagnostics.error(MethodRef.ofModel(methodModel), ex);
}
}
});
return restrictedMethods;
}));
return perMethod;
}
private boolean isRestrictedMethod(MethodRef ref) throws JNativeScanFatalError {
@ -109,11 +93,14 @@ class NativeMethodFinder {
// no restricted methods in arrays atm, and we can't look them up since they have no class file
return false;
}
Optional<ClassResolver.Info> info = systemClassResolver.lookup(methodRef.owner());
if (!info.isPresent()) {
Optional<ClassModel> modelOpt = systemClassResolver.lookup(methodRef.owner());
if (!modelOpt.isPresent()) {
// The target class is not in a system module. Since only system modules
// contain classes with restricted methods, we can safely assume that
// the target method is not restricted.
return false;
}
ClassModel classModel = info.get().model();
ClassModel classModel = modelOpt.get();
Optional<MethodModel> methodModel = findMethod(classModel, methodRef.name(), methodRef.type());
if (!methodModel.isPresent()) {
// If we are here, the method was referenced through a subclass of the class containing the actual

View File

@ -0,0 +1,112 @@
/*
* Copyright (c) 2024, 2025, 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.sun.tools.jnativescan;
import com.sun.tools.javac.platform.PlatformDescription;
import com.sun.tools.javac.platform.PlatformProvider;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.InputStream;
import java.lang.classfile.ClassFile;
import java.lang.classfile.ClassModel;
import java.lang.constant.ClassDesc;
import java.lang.module.ModuleDescriptor;
import java.util.*;
class SystemClassResolver implements AutoCloseable {
private final JavaFileManager platformFileManager;
private final Map<String, String> packageToSystemModule;
private final Map<ClassDesc, ClassModel> cache = new HashMap<>();
private SystemClassResolver(JavaFileManager platformFileManager) {
this.platformFileManager = platformFileManager;
this.packageToSystemModule = packageToSystemModule(platformFileManager);
}
public static SystemClassResolver forRuntimeVersion(Runtime.Version version) {
String platformName = String.valueOf(version.feature());
PlatformProvider platformProvider = ServiceLoader.load(PlatformProvider.class).findFirst().orElseThrow();
PlatformDescription platform;
try {
platform = platformProvider.getPlatform(platformName, null);
} catch (PlatformProvider.PlatformNotSupported e) {
throw new JNativeScanFatalError("Release: " + platformName + " not supported", e);
}
JavaFileManager fm = platform.getFileManager();
return new SystemClassResolver(fm);
}
private static Map<String, String> packageToSystemModule(JavaFileManager platformFileManager) {
try {
Set<JavaFileManager.Location> locations = platformFileManager.listLocationsForModules(
StandardLocation.SYSTEM_MODULES).iterator().next();
Map<String, String> result = new HashMap<>();
for (JavaFileManager.Location loc : locations) {
JavaFileObject jfo = platformFileManager.getJavaFileForInput(loc, "module-info", JavaFileObject.Kind.CLASS);
ModuleDescriptor descriptor = ModuleDescriptor.read(jfo.openInputStream());
for (ModuleDescriptor.Exports export : descriptor.exports()) {
if (!export.isQualified()) {
result.put(export.source(), descriptor.name());
}
}
}
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Optional<ClassModel> lookup(ClassDesc desc) {
return Optional.ofNullable(cache.computeIfAbsent(desc, _ -> {
String qualName = JNativeScanTask.qualName(desc);
String moduleName = packageToSystemModule.get(desc.packageName());
if (moduleName != null) {
try {
JavaFileManager.Location loc = platformFileManager.getLocationForModule(StandardLocation.SYSTEM_MODULES, moduleName);
JavaFileObject jfo = platformFileManager.getJavaFileForInput(loc, qualName, JavaFileObject.Kind.CLASS);
if (jfo == null) {
throw new JNativeScanFatalError("System class can not be found: " + qualName);
}
try (InputStream inputStream = jfo.openInputStream()) {
return ClassFile.of().parse(inputStream.readAllBytes());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}));
}
@Override
public void close() throws IOException {
platformFileManager.close();
}
}