From 8bb0ca49715d8c713d6319b00a7684564ba57a9b Mon Sep 17 00:00:00 2001 From: Jorn Vernee Date: Fri, 11 Apr 2025 11:15:32 +0000 Subject: [PATCH] 8353917: jnativescan: Simplify ClassResolver Reviewed-by: mcimadamore --- .../tools/jnativescan/ClassFileSource.java | 19 +- .../sun/tools/jnativescan/ClassResolver.java | 163 ------------------ .../tools/jnativescan/JNativeScanTask.java | 32 +++- .../tools/jnativescan/NativeMethodFinder.java | 81 ++++----- .../jnativescan/SystemClassResolver.java | 112 ++++++++++++ 5 files changed, 183 insertions(+), 224 deletions(-) delete mode 100644 src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassResolver.java create mode 100644 src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/SystemClassResolver.java diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassFileSource.java b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassFileSource.java index 754904c9c7f..8239d3f1b62 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassFileSource.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassFileSource.java @@ -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 classFiles(Runtime.Version version) throws IOException; + Stream classFiles() throws IOException; + + default Stream 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 classFiles(Runtime.Version version) throws IOException { + public Stream 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 classFiles(Runtime.Version version) throws IOException { + public Stream 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 classFiles(Runtime.Version version) throws IOException { + public Stream classFiles() throws IOException { return Files.walk(path) .filter(file -> Files.isRegularFile(file) && file.toString().endsWith(".class")) .map(file -> { diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassResolver.java b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassResolver.java deleted file mode 100644 index 7a267a58aa5..00000000000 --- a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/ClassResolver.java +++ /dev/null @@ -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 sources, Runtime.Version version) throws IOException { - Map classMap = new HashMap<>(); - for (ClassFileSource source : sources) { - try (Stream 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 action); - public abstract Optional lookup(ClassDesc desc); - - @Override - public abstract void close() throws IOException; - - private static class SimpleClassResolver extends ClassResolver { - - private final Map classMap; - - public SimpleClassResolver(Map classMap) { - this.classMap = classMap; - } - - public void forEach(BiConsumer action) { - classMap.forEach(action); - } - - public Optional 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 packageToSystemModule; - private final Map cache = new HashMap<>(); - - public SystemModuleClassResolver(JavaFileManager platformFileManager) { - this.platformFileManager = platformFileManager; - this.packageToSystemModule = packageToSystemModule(platformFileManager); - } - - private static Map packageToSystemModule(JavaFileManager platformFileManager) { - try { - Set locations = platformFileManager.listLocationsForModules( - StandardLocation.SYSTEM_MODULES).iterator().next(); - - Map 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 action) { - throw new UnsupportedOperationException("NYI"); - } - - @Override - public Optional 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(); - } - } -} diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/JNativeScanTask.java b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/JNativeScanTask.java index c57e033427e..31102d742c1 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/JNativeScanTask.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/JNativeScanTask.java @@ -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 errors = new LinkedHashSet<>(); Diagnostics diagnostics = (context, error) -> errors.add("Error while processing method: " + context + ": " + error.getMessage()); - SortedMap>> allRestrictedMethods; - try(ClassResolver classesToScan = ClassResolver.forClassFileSources(toScan, version); - ClassResolver systemClassResolver = ClassResolver.forSystemModules(version)) { - NativeMethodFinder finder = NativeMethodFinder.create(diagnostics, classesToScan, systemClassResolver); - allRestrictedMethods = finder.findAll(); + SortedMap>> allRestrictedMethods + = new TreeMap<>(Comparator.comparing(ClassFileSource::path)); + try(SystemClassResolver systemClassResolver = SystemClassResolver.forRuntimeVersion(version)) { + NativeMethodFinder finder = NativeMethodFinder.create(diagnostics, systemClassResolver); + + for (ClassFileSource source : toScan) { + SortedMap> perClass + = new TreeMap<>(Comparator.comparing(JNativeScanTask::qualName)); + try (Stream stream = source.classModels()) { + stream.forEach(classModel -> { + List 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)); diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/NativeMethodFinder.java b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/NativeMethodFinder.java index b89c9db2858..d11a38658da 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/NativeMethodFinder.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/NativeMethodFinder.java @@ -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 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>> findAll() throws JNativeScanFatalError { - SortedMap>> restrictedMethods - = new TreeMap<>(Comparator.comparing(ClassFileSource::path)); - classesToScan.forEach((_, info) -> { - ClassModel classModel = info.model(); - List perClass = new ArrayList<>(); - classModel.methods().forEach(methodModel -> { + public List find(ClassModel model) throws JNativeScanFatalError { + return model.methods().stream().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 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 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 findRestrictedMethodInvocations(MethodModel methodModel) { + SortedSet 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 info = systemClassResolver.lookup(methodRef.owner()); - if (!info.isPresent()) { + Optional 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 = 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 diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/SystemClassResolver.java b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/SystemClassResolver.java new file mode 100644 index 00000000000..633a953e23c --- /dev/null +++ b/src/jdk.jdeps/share/classes/com/sun/tools/jnativescan/SystemClassResolver.java @@ -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 packageToSystemModule; + private final Map 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 packageToSystemModule(JavaFileManager platformFileManager) { + try { + Set locations = platformFileManager.listLocationsForModules( + StandardLocation.SYSTEM_MODULES).iterator().next(); + + Map 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 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(); + } +}