8213909: jdeps --print-module-deps should report missing dependences

8168869: jdeps: localized messages don't use proper line breaks

Reviewed-by: sundar
This commit is contained in:
Mandy Chung 2018-11-21 22:34:01 -08:00
parent 9d7509e647
commit 0126fdbef0
15 changed files with 475 additions and 233 deletions

@ -32,6 +32,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -39,6 +40,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -74,6 +76,7 @@ public class Analyzer {
protected final Map<Location, Archive> locationToArchive = new HashMap<>();
static final Archive NOT_FOUND
= new Archive(JdepsTask.getMessage("artifact.not.found"));
static final Predicate<Archive> ANY = a -> true;
/**
* Constructs an Analyzer instance.
@ -161,7 +164,7 @@ public class Analyzer {
* Visit the dependencies of the given source.
* If the requested level is SUMMARY, it will visit the required archives list.
*/
void visitDependences(Archive source, Visitor v, Type level) {
void visitDependences(Archive source, Visitor v, Type level, Predicate<Archive> targetFilter) {
if (level == Type.SUMMARY) {
final Dependences result = results.get(source);
final Set<Archive> reqs = result.requires();
@ -184,7 +187,7 @@ public class Analyzer {
Dependences result = results.get(source);
if (level != type) {
// requesting different level of analysis
result = new Dependences(source, level);
result = new Dependences(source, level, targetFilter);
source.visitDependences(result);
}
result.dependencies().stream()
@ -196,7 +199,11 @@ public class Analyzer {
}
void visitDependences(Archive source, Visitor v) {
visitDependences(source, v, type);
visitDependences(source, v, type, ANY);
}
void visitDependences(Archive source, Visitor v, Type level) {
visitDependences(source, v, level, ANY);
}
/**
@ -208,12 +215,17 @@ public class Analyzer {
protected final Set<Archive> requires;
protected final Set<Dep> deps;
protected final Type level;
protected final Predicate<Archive> targetFilter;
private Profile profile;
Dependences(Archive archive, Type level) {
this(archive, level, ANY);
}
Dependences(Archive archive, Type level, Predicate<Archive> targetFilter) {
this.archive = archive;
this.deps = new HashSet<>();
this.requires = new HashSet<>();
this.level = level;
this.targetFilter = targetFilter;
}
Set<Dep> dependencies() {
@ -266,7 +278,7 @@ public class Analyzer {
@Override
public void visit(Location o, Location t) {
Archive targetArchive = findArchive(t);
if (filter.accepts(o, archive, t, targetArchive)) {
if (filter.accepts(o, archive, t, targetArchive) && targetFilter.test(targetArchive)) {
addDep(o, t);
if (archive != targetArchive && !requires.contains(targetArchive)) {
requires.add(targetArchive);
@ -368,13 +380,21 @@ public class Analyzer {
}
}
/*
* Returns true if the given archive represents not found.
*/
static boolean notFound(Archive archive) {
return archive == NOT_FOUND || archive == REMOVED_JDK_INTERNALS;
}
static final Jdk8Internals REMOVED_JDK_INTERNALS = new Jdk8Internals();
static class Jdk8Internals extends Module {
private final String JDK8_INTERNALS = "/com/sun/tools/jdeps/resources/jdk8_internals.txt";
private static final String NAME = "JDK removed internal API";
private static final String JDK8_INTERNALS = "/com/sun/tools/jdeps/resources/jdk8_internals.txt";
private final Set<String> jdk8Internals;
private Jdk8Internals() {
super("JDK removed internal API");
super(NAME, ModuleDescriptor.newModule("jdk8internals").build(), true);
try (InputStream in = JdepsTask.class.getResourceAsStream(JDK8_INTERNALS);
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
this.jdk8Internals = reader.lines()
@ -393,11 +413,6 @@ public class Analyzer {
return jdk8Internals.contains(pn);
}
@Override
public String name() {
return getName();
}
@Override
public boolean isJDK() {
return true;

@ -38,6 +38,7 @@ import com.sun.tools.classfile.Dependency.Location;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
@ -172,7 +173,7 @@ class DependencyFinder {
parsedArchives.get(finder).add(archive);
trace("parsing %s %s%n", archive.getName(), archive.path());
trace("parsing %s %s%n", archive.getName(), archive.getPathName());
FutureTask<Set<Location>> task = new FutureTask<>(() -> {
Set<Location> targets = new HashSet<>();
for (ClassFile cf : archive.reader().getClassFiles()) {
@ -206,7 +207,6 @@ class DependencyFinder {
parsedClasses.putIfAbsent(d.getOrigin(), archive);
}
}
return targets;
});
tasks.add(task);
@ -264,8 +264,7 @@ class DependencyFinder {
FutureTask<Set<Location>> task;
while ((task = tasks.poll()) != null) {
// wait for completion
if (!task.isDone())
targets.addAll(task.get());
targets.addAll(task.get());
}
return targets;
} catch (InterruptedException|ExecutionException e) {

@ -83,42 +83,27 @@ public class JdepsConfiguration implements AutoCloseable {
private final List<Archive> classpathArchives = new ArrayList<>();
private final List<Archive> initialArchives = new ArrayList<>();
private final Set<Module> rootModules = new HashSet<>();
private final Configuration configuration;
private final Runtime.Version version;
private JdepsConfiguration(SystemModuleFinder systemModulePath,
private JdepsConfiguration(Configuration config,
SystemModuleFinder systemModulePath,
ModuleFinder finder,
Set<String> roots,
List<Path> classpaths,
List<Archive> initialArchives,
Set<String> tokens,
Runtime.Version version)
throws IOException
{
trace("root: %s%n", roots);
trace("initial archives: %s%n", initialArchives);
trace("class path: %s%n", classpaths);
this.system = systemModulePath;
this.finder = finder;
this.version = version;
// build root set for resolution
Set<String> mods = new HashSet<>(roots);
if (tokens.contains(ALL_SYSTEM)) {
systemModulePath.findAll().stream()
.map(mref -> mref.descriptor().name())
.forEach(mods::add);
}
if (tokens.contains(ALL_DEFAULT)) {
mods.addAll(systemModulePath.defaultSystemRoots());
}
this.configuration = Configuration.empty()
.resolve(finder, ModuleFinder.of(), mods);
this.configuration.modules().stream()
.map(ResolvedModule::reference)
.forEach(this::addModuleReference);
config.modules().stream()
.map(ResolvedModule::reference)
.forEach(this::addModuleReference);
// packages in unnamed module
initialArchives.forEach(archive -> {
@ -538,14 +523,6 @@ public class JdepsConfiguration implements AutoCloseable {
.forEach(rootModules::add);
}
// add all modules to the root set for unnamed module or set explicitly
boolean unnamed = !initialArchives.isEmpty() || !classPaths.isEmpty();
if ((unnamed || tokens.contains(ALL_MODULE_PATH)) && appModulePath != null) {
appModulePath.findAll().stream()
.map(mref -> mref.descriptor().name())
.forEach(rootModules::add);
}
// no archive is specified for analysis
// add all system modules as root if --add-modules ALL-SYSTEM is specified
if (tokens.contains(ALL_SYSTEM) && rootModules.isEmpty() &&
@ -556,16 +533,41 @@ public class JdepsConfiguration implements AutoCloseable {
.forEach(rootModules::add);
}
if (unnamed && !tokens.contains(ALL_DEFAULT)) {
tokens.add(ALL_SYSTEM);
// add all modules on app module path as roots if ALL-MODULE-PATH is specified
if ((tokens.contains(ALL_MODULE_PATH)) && appModulePath != null) {
appModulePath.findAll().stream()
.map(mref -> mref.descriptor().name())
.forEach(rootModules::add);
}
return new JdepsConfiguration(systemModulePath,
// build root set for module resolution
Set<String> mods = new HashSet<>(rootModules);
// if archives are specified for analysis, then consider as unnamed module
boolean unnamed = !initialArchives.isEmpty() || !classPaths.isEmpty();
if (tokens.contains(ALL_DEFAULT)) {
mods.addAll(systemModulePath.defaultSystemRoots());
} else if (tokens.contains(ALL_SYSTEM) || unnamed) {
// resolve all system modules as unnamed module may reference any class
systemModulePath.findAll().stream()
.map(mref -> mref.descriptor().name())
.forEach(mods::add);
}
if (unnamed && appModulePath != null) {
// resolve all modules on module path as unnamed module may reference any class
appModulePath.findAll().stream()
.map(mref -> mref.descriptor().name())
.forEach(mods::add);
}
// resolve the module graph
Configuration config = Configuration.empty().resolve(finder, ModuleFinder.of(), mods);
return new JdepsConfiguration(config,
systemModulePath,
finder,
rootModules,
classPaths,
initialArchives,
tokens,
version);
}

@ -55,6 +55,7 @@ public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
private final boolean filterSamePackage;
private final boolean filterSameArchive;
private final boolean findJDKInternals;
private final boolean findMissingDeps;
private final Pattern includePattern;
private final Set<String> requires;
@ -64,6 +65,7 @@ public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
boolean filterSamePackage,
boolean filterSameArchive,
boolean findJDKInternals,
boolean findMissingDeps,
Pattern includePattern,
Set<String> requires) {
this.filter = filter;
@ -71,6 +73,7 @@ public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
this.filterSamePackage = filterSamePackage;
this.filterSameArchive = filterSameArchive;
this.findJDKInternals = findJDKInternals;
this.findMissingDeps = findMissingDeps;
this.includePattern = includePattern;
this.requires = requires;
}
@ -153,6 +156,8 @@ public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
Module module = targetArchive.getModule();
return originArchive != targetArchive &&
isJDKInternalPackage(module, target.getPackageName());
} else if (findMissingDeps) {
return Analyzer.notFound(targetArchive);
} else if (filterSameArchive) {
// accepts origin and target that from different archive
return originArchive != targetArchive;
@ -188,6 +193,7 @@ public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
boolean filterSamePackage;
boolean filterSameArchive;
boolean findJDKInterals;
boolean findMissingDeps;
// source filters
Pattern includePattern;
Set<String> requires = new HashSet<>();
@ -221,6 +227,10 @@ public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
this.findJDKInterals = value;
return this;
}
public Builder findMissingDeps(boolean value) {
this.findMissingDeps = value;
return this;
}
public Builder includePattern(Pattern regex) {
this.includePattern = regex;
return this;
@ -238,6 +248,7 @@ public class JdepsFilter implements Dependency.Filter, Analyzer.Filter {
filterSamePackage,
filterSameArchive,
findJDKInterals,
findMissingDeps,
includePattern,
requires);
}

@ -357,6 +357,11 @@ class JdepsTask {
task.command = task.listModuleDeps(CommandOption.PRINT_MODULE_DEPS);
}
},
new Option(false, "--ignore-missing-deps") {
void process(JdepsTask task, String opt, String arg) {
task.options.ignoreMissingDeps = true;
}
},
// ---- Target filtering options ----
new Option(true, "-p", "-package", "--package") {
@ -401,6 +406,11 @@ class JdepsTask {
}
}
},
new Option(false, "--missing-deps") {
void process(JdepsTask task, String opt, String arg) {
task.options.findMissingDeps = true;
}
},
// ---- Source filtering options ----
new Option(true, "-include") {
@ -415,15 +425,19 @@ class JdepsTask {
}
},
new Option(false, "-R", "-recursive") {
void process(JdepsTask task, String opt, String arg) {
task.options.depth = 0;
new Option(false, "-R", "-recursive", "--recursive") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.recursive = Options.RECURSIVE;
// turn off filtering
task.options.filterSameArchive = false;
task.options.filterSamePackage = false;
}
},
new Option(false, "--no-recursive") {
void process(JdepsTask task, String opt, String arg) throws BadArgs {
task.options.recursive = Options.NO_RECURSIVE;
}
},
new Option(false, "-I", "--inverse") {
void process(JdepsTask task, String opt, String arg) {
task.options.inverse = true;
@ -437,9 +451,9 @@ class JdepsTask {
new Option(false, "--compile-time") {
void process(JdepsTask task, String opt, String arg) {
task.options.compileTimeView = true;
task.options.recursive = Options.RECURSIVE;
task.options.filterSamePackage = true;
task.options.filterSameArchive = true;
task.options.depth = 0;
}
},
@ -611,6 +625,13 @@ class JdepsTask {
}
private ListModuleDeps listModuleDeps(CommandOption option) throws BadArgs {
// do transitive dependence analysis unless --no-recursive is set
if (options.recursive != Options.NO_RECURSIVE) {
options.recursive = Options.RECURSIVE;
}
// no need to record the dependences on the same archive or same package
options.filterSameArchive = true;
options.filterSamePackage = true;
switch (option) {
case LIST_DEPS:
return new ListModuleDeps(option, true, false);
@ -677,16 +698,16 @@ class JdepsTask {
@Override
boolean checkOptions() {
if (options.findJDKInternals) {
if (options.findJDKInternals || options.findMissingDeps) {
// cannot set any filter, -verbose and -summary option
if (options.showSummary || options.verbose != null) {
reportError("err.invalid.options", "-summary or -verbose",
"-jdkinternals");
options.findJDKInternals ? "-jdkinternals" : "--missing-deps");
return false;
}
if (options.hasFilter()) {
reportError("err.invalid.options", "--package, --regex, --require",
"-jdkinternals");
options.findJDKInternals ? "-jdkinternals" : "--missing-deps");
return false;
}
}
@ -715,7 +736,7 @@ class JdepsTask {
if (options.showSummary)
return Type.SUMMARY;
if (options.findJDKInternals)
if (options.findJDKInternals || options.findMissingDeps)
return Type.CLASS;
// default to package-level verbose
@ -744,7 +765,7 @@ class JdepsTask {
type,
options.apiOnly);
boolean ok = analyzer.run(options.compileTimeView, options.depth);
boolean ok = analyzer.run(options.compileTimeView, options.depth());
// print skipped entries, if any
if (!options.nowarning) {
@ -797,8 +818,8 @@ class JdepsTask {
@Override
boolean checkOptions() {
if (options.depth != 1) {
reportError("err.invalid.options", "-R", "--inverse");
if (options.recursive != -1 || options.depth != -1) {
reportError("err.invalid.options", "--recursive and --no-recursive", "--inverse");
return false;
}
@ -925,12 +946,7 @@ class JdepsTask {
if (!ok && !options.nowarning) {
reportError("err.missing.dependences");
builder.visitMissingDeps(
(origin, originArchive, target, targetArchive) -> {
if (builder.notFound(targetArchive))
log.format(" %-50s -> %-50s %s%n",
origin, target, targetArchive.getName());
});
builder.visitMissingDeps(new SimpleDepVisitor());
}
return ok;
}
@ -993,13 +1009,15 @@ class JdepsTask {
@Override
boolean checkOptions() {
if (options.showSummary || options.verbose != null) {
reportError("err.invalid.options", "-summary or -verbose",
option);
reportError("err.invalid.options", "-summary or -verbose", option);
return false;
}
if (options.findJDKInternals) {
reportError("err.invalid.options", "-jdkinternals",
option);
reportError("err.invalid.options", "-jdkinternals", option);
return false;
}
if (options.findMissingDeps) {
reportError("err.invalid.options", "--missing-deps", option);
return false;
}
@ -1015,16 +1033,22 @@ class JdepsTask {
@Override
boolean run(JdepsConfiguration config) throws IOException {
return new ModuleExportsAnalyzer(config,
dependencyFilter(config),
jdkinternals,
reduced,
log,
separator).run();
ModuleExportsAnalyzer analyzer = new ModuleExportsAnalyzer(config,
dependencyFilter(config),
jdkinternals,
reduced,
log,
separator);
boolean ok = analyzer.run(options.depth(), options.ignoreMissingDeps);
if (!ok) {
reportError("err.cant.list.module.deps");
log.println();
analyzer.visitMissingDeps(new SimpleDepVisitor());
}
return ok;
}
}
class GenDotFile extends AnalyzeDeps {
final Path dotOutputDir;
GenDotFile(Path dotOutputDir) {
@ -1053,6 +1077,18 @@ class JdepsTask {
}
}
class SimpleDepVisitor implements Analyzer.Visitor {
private Archive source;
@Override
public void visitDependence(String origin, Archive originArchive, String target, Archive targetArchive) {
if (source != originArchive) {
source = originArchive;
log.format("%s%n", originArchive);
}
log.format(" %-50s -> %-50s %s%n", origin, target, targetArchive.getName());
}
}
/**
* Returns a filter used during dependency analysis
*/
@ -1066,6 +1102,7 @@ class JdepsTask {
// target filters
builder.filter(options.filterSamePackage, options.filterSameArchive);
builder.findJDKInternals(options.findJDKInternals);
builder.findMissingDeps(options.findMissingDeps);
// --require
if (!options.requires.isEmpty()) {
@ -1158,11 +1195,8 @@ class JdepsTask {
private String version(String key) {
// key=version: mm.nn.oo[-milestone]
// key=full: mm.mm.oo[-milestone]-build
if (ResourceBundleHelper.versionRB == null) {
return System.getProperty("java.version");
}
try {
return ResourceBundleHelper.versionRB.getString(key);
return ResourceBundleHelper.getVersion(key);
} catch (MissingResourceException e) {
return getMessage("version.unknown", System.getProperty("java.version"));
}
@ -1170,13 +1204,15 @@ class JdepsTask {
static String getMessage(String key, Object... args) {
try {
return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args);
return MessageFormat.format(ResourceBundleHelper.getMessage(key), args);
} catch (MissingResourceException e) {
throw new InternalError("Missing message: " + key);
}
}
private static class Options {
static final int NO_RECURSIVE = 0;
static final int RECURSIVE = 1;
boolean help;
boolean version;
boolean fullVersion;
@ -1186,6 +1222,8 @@ class JdepsTask {
boolean apiOnly;
boolean showLabel;
boolean findJDKInternals;
boolean findMissingDeps;
boolean ignoreMissingDeps;
boolean nowarning = false;
Analyzer.Type verbose;
// default filter references from same package
@ -1193,7 +1231,8 @@ class JdepsTask {
boolean filterSameArchive = false;
Pattern filterRegex;
String classpath;
int depth = 1;
int recursive = -1; // 0: --no-recursive, 1: --recursive
int depth = -1;
Set<String> requires = new HashSet<>();
Set<String> packageNames = new HashSet<>();
Pattern regex; // apply to the dependences
@ -1222,9 +1261,23 @@ class JdepsTask {
if (packageNames.size() > 0) count++;
return count;
}
int depth() {
// ignore -depth if --no-recursive is set
if (recursive == NO_RECURSIVE)
return 1;
// depth == 0 if recursive
if (recursive == RECURSIVE && depth == -1)
return 0;
// default depth is 1 unless specified via -depth option
return depth == -1 ? 1 : depth;
}
}
private static class ResourceBundleHelper {
static final String LS = System.lineSeparator();
static final ResourceBundle versionRB;
static final ResourceBundle bundle;
static final ResourceBundle jdkinternals;
@ -1247,6 +1300,21 @@ class JdepsTask {
throw new InternalError("Cannot find jdkinternals resource bundle");
}
}
static String getMessage(String key) {
return bundle.getString(key).replace("\n", LS);
}
static String getVersion(String key) {
if (ResourceBundleHelper.versionRB == null) {
return System.getProperty("java.version");
}
return versionRB.getString(key).replace("\n", LS);
}
static String getSuggestedReplacement(String key) {
return ResourceBundleHelper.jdkinternals.getString(key).replace("\n", LS);
}
}
/**
@ -1258,7 +1326,7 @@ class JdepsTask {
String value = null;
while (value == null && name != null) {
try {
value = ResourceBundleHelper.jdkinternals.getString(name);
value = ResourceBundleHelper.getSuggestedReplacement(name);
} catch (MissingResourceException e) {
// go up one subpackage level
int i = name.lastIndexOf('.');

@ -58,12 +58,16 @@ class Module extends Archive {
private final URI location;
protected Module(String name) {
this(name, null, false);
}
protected Module(String name, ModuleDescriptor descriptor, boolean isSystem) {
super(name);
this.descriptor = null;
this.descriptor = descriptor;
this.location = null;
this.exports = Collections.emptyMap();
this.opens = Collections.emptyMap();
this.isSystem = true;
this.isSystem = isSystem;
}
private Module(String name,
@ -89,11 +93,11 @@ class Module extends Archive {
}
public boolean isNamed() {
return true;
return descriptor != null;
}
public boolean isAutomatic() {
return descriptor.isAutomatic();
return descriptor != null && descriptor.isAutomatic();
}
public Module getModule() {
@ -232,9 +236,7 @@ class Module extends Archive {
private static class UnnamedModule extends Module {
private UnnamedModule() {
super("unnamed", null, null,
Collections.emptyMap(), Collections.emptyMap(),
false, null);
super("unnamed", null, false);
}
@Override
@ -242,16 +244,6 @@ class Module extends Archive {
return "unnamed";
}
@Override
public boolean isNamed() {
return false;
}
@Override
public boolean isAutomatic() {
return false;
}
@Override
public boolean isExported(String pn) {
return true;

@ -49,42 +49,47 @@ import java.util.stream.Stream;
public class ModuleExportsAnalyzer extends DepsAnalyzer {
// source archive to its dependences and JDK internal APIs it references
private final Map<Archive, Map<Archive,Set<String>>> deps = new HashMap<>();
private final boolean showJdkInternals;
private final Map<String, Set<String>> missingDeps = new HashMap<>();
private final boolean showInternals;
private final boolean reduced;
private final PrintWriter writer;
private final String separator;
public ModuleExportsAnalyzer(JdepsConfiguration config,
JdepsFilter filter,
boolean showJdkInternals,
boolean showInternals,
boolean reduced,
PrintWriter writer,
String separator) {
super(config, filter, null,
Analyzer.Type.PACKAGE,
false /* all classes */);
this.showJdkInternals = showJdkInternals;
this.showInternals = showInternals;
this.reduced = reduced;
this.writer = writer;
this.separator = separator;
}
@Override
public boolean run() throws IOException {
// analyze dependences
boolean rc = super.run();
public boolean run(int maxDepth, boolean ignoreMissingDeps) throws IOException {
// use compile time view so that the entire archive on classpath is analyzed
boolean rc = super.run(true, maxDepth);
// A visitor to record the module-level dependences as well as
// use of JDK internal APIs
// use of internal APIs
Analyzer.Visitor visitor = (origin, originArchive, target, targetArchive) -> {
Set<String> jdkInternals =
Set<String> internals =
deps.computeIfAbsent(originArchive, _k -> new HashMap<>())
.computeIfAbsent(targetArchive, _k -> new HashSet<>());
Module module = targetArchive.getModule();
if (showJdkInternals && originArchive.getModule() != module &&
module.isJDK() && !module.isExported(target)) {
// use of JDK internal APIs
jdkInternals.add(target);
if (showInternals && originArchive.getModule() != module &&
module.isNamed() && !module.isExported(target, module.name())) {
// use of internal APIs
internals.add(target);
}
if (!ignoreMissingDeps && Analyzer.notFound(targetArchive)) {
Set<String> notFound =
missingDeps.computeIfAbsent(origin, _k -> new HashSet<>());
notFound.add(target);
}
};
@ -94,10 +99,26 @@ public class ModuleExportsAnalyzer extends DepsAnalyzer {
.sorted(Comparator.comparing(Archive::getName))
.forEach(archive -> analyzer.visitDependences(archive, visitor));
// error if any missing dependence
if (!rc || !missingDeps.isEmpty()) {
return false;
}
Map<Module, Set<String>> internalPkgs = internalPackages();
Set<Module> modules = modules();
if (showJdkInternals) {
if (showInternals) {
// print modules and JDK internal API dependences
printDependences(modules);
Stream.concat(modules.stream(), internalPkgs.keySet().stream())
.sorted(Comparator.comparing(Module::name))
.distinct()
.forEach(m -> {
if (internalPkgs.containsKey(m)) {
internalPkgs.get(m).stream()
.forEach(pn -> writer.format(" %s/%s%s", m, pn, separator));
} else {
writer.format(" %s%s", m, separator);
}
});
} else {
// print module dependences
writer.println(modules.stream().map(Module::name).sorted()
@ -106,16 +127,28 @@ public class ModuleExportsAnalyzer extends DepsAnalyzer {
return rc;
}
/*
* Prints missing dependences
*/
void visitMissingDeps(Analyzer.Visitor visitor) {
archives.stream()
.filter(analyzer::hasDependences)
.sorted(Comparator.comparing(Archive::getName))
.filter(m -> analyzer.requires(m).anyMatch(Analyzer::notFound))
.forEach(m -> {
analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);
});
}
private Set<Module> modules() {
// build module graph
ModuleGraphBuilder builder = new ModuleGraphBuilder(configuration);
Module root = new RootModule("root");
Module root = new RootModule();
builder.addModule(root);
// find named module dependences
dependenceStream()
.flatMap(map -> map.keySet().stream())
.filter(m -> m.getModule().isNamed()
&& !configuration.rootModules().contains(m))
.filter(m -> m.getModule().isNamed() && !configuration.rootModules().contains(m))
.map(Archive::getModule)
.forEach(m -> builder.addEdge(root, m));
@ -125,28 +158,15 @@ public class ModuleExportsAnalyzer extends DepsAnalyzer {
return g.adjacentNodes(root);
}
private void printDependences(Set<Module> modules) {
// find use of JDK internals
Map<Module, Set<String>> jdkinternals = new HashMap<>();
private Map<Module, Set<String>> internalPackages() {
Map<Module, Set<String>> internalPkgs = new HashMap<>();
dependenceStream()
.flatMap(map -> map.entrySet().stream())
.filter(e -> e.getValue().size() > 0)
.forEach(e -> jdkinternals.computeIfAbsent(e.getKey().getModule(),
_k -> new TreeSet<>())
.forEach(e -> internalPkgs.computeIfAbsent(e.getKey().getModule(),
_k -> new TreeSet<>())
.addAll(e.getValue()));
// print modules and JDK internal API dependences
Stream.concat(modules.stream(), jdkinternals.keySet().stream())
.sorted(Comparator.comparing(Module::name))
.distinct()
.forEach(m -> {
if (jdkinternals.containsKey(m)) {
jdkinternals.get(m).stream()
.forEach(pn -> writer.format(" %s/%s%s", m, pn, separator));
} else {
writer.format(" %s%s", m, separator);
}
});
return internalPkgs;
}
/*
@ -160,18 +180,13 @@ public class ModuleExportsAnalyzer extends DepsAnalyzer {
.map(deps::get);
}
private class RootModule extends Module {
final ModuleDescriptor descriptor;
RootModule(String name) {
super(name);
ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(name);
this.descriptor = builder.build();
}
@Override
public ModuleDescriptor descriptor() {
return descriptor;
/*
* RootModule serves as the root node for building the module graph
*/
private static class RootModule extends Module {
static final String NAME = "root";
RootModule() {
super(NAME, ModuleDescriptor.newModule(NAME).build(), false);
}
}

@ -137,17 +137,13 @@ public class ModuleInfoBuilder {
}
}
boolean notFound(Archive m) {
return m == NOT_FOUND || m == REMOVED_JDK_INTERNALS;
}
private Module toNormalModule(Module module, Set<Archive> requiresTransitive)
throws IOException
{
// done analysis
module.close();
if (analyzer.requires(module).anyMatch(this::notFound)) {
if (analyzer.requires(module).anyMatch(Analyzer::notFound)) {
// missing dependencies
return null;
}
@ -182,9 +178,9 @@ public class ModuleInfoBuilder {
void visitMissingDeps(Analyzer.Visitor visitor) {
automaticModules().stream()
.filter(m -> analyzer.requires(m).anyMatch(this::notFound))
.filter(m -> analyzer.requires(m).anyMatch(Analyzer::notFound))
.forEach(m -> {
analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE);
analyzer.visitDependences(m, visitor, Analyzer.Type.VERBOSE, Analyzer::notFound);
});
}

@ -57,6 +57,13 @@ main.opt.require=\
\ name (may be given multiple times). --package,\n\
\ --regex, --require are mutual exclusive.
main.opt.missing-deps=\
\ --missing-deps Finds missing dependences. This option\n\
\ cannot be used with -p, -e and -s options.
main.opt.ignore-missing-deps=\
\ --ignore-missing-deps Ignore missing dependences.
main.opt.include=\n\
\Options to filter classes to be analyzed:\n\
\ -include <regex> Restrict analysis to classes matching pattern\n\
@ -86,13 +93,18 @@ main.opt.add-modules=\
\ Adds modules to the root set for analysis
main.opt.R=\
\ -R -recursive Recursively traverse all run-time dependences.\n\
\ -R\n\
\ --recursive Recursively traverse all run-time dependences.\n\
\ The -R option implies -filter:none. If -p,\n\
\ -e, -f option is specified, only the matching\n\
\ dependences are analyzed.
main.opt.no-recursive=\
\ --no-recursive Do not recursively traverse dependences.
main.opt.I=\
\ -I --inverse Analyzes the dependences per other given options\n\
\ -I\n\
\ --inverse Analyzes the dependences per other given options\n\
\ and then find all artifacts that directly\n\
\ and indirectly depend on the matching nodes.\n\
\ This is equivalent to the inverse of\n\
@ -157,9 +169,11 @@ main.opt.jdkinternals=\
main.opt.list-deps=\
\ --list-deps Lists the module dependences. It also prints\n\
\ any JDK internal API packages if referenced.\n\
\ This option does not show dependences on the\n\
\ class path or not found.
\ any internal API packages if referenced.\n\
\ This option transitively analyzes libraries on\n\
\ class path and module path if referenced.\n\
\ Use --no-recursive option for non-transitive\n\
\ dependency analysis.
main.opt.list-reduced-deps=\
\ --list-reduced-deps Same as --list-deps with not listing\n\
@ -207,6 +221,10 @@ err.multirelease.option.exists={0} is not a multi-release jar file but --multi-r
err.multirelease.option.notfound={0} is a multi-release jar file but --multi-release option is not set
err.multirelease.version.associated=class {0} already associated with version {1}, trying to add version {2}
err.multirelease.jar.malformed=malformed multi-release jar, {0}, bad entry: {1}
err.cant.list.module.deps=\
Missing dependencies from the module path and classpath.\n\
To suppress this error, use --ignore-missing-deps to continue.
warn.invalid.arg=Path does not exist: {0}
warn.skipped.entry={0}
warn.split.package=split package: {0} {1}

@ -37,6 +37,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.*;
import java.util.stream.Collectors;
@ -135,6 +136,19 @@ public class Basic {
new String[] {"java.lang.Object", "java.lang.String", "p.Foo", "p.Bar"},
new String[] {"compact1", "compact1", dir1.toFile().getName(), dir2.toFile().getName()},
new String[] {"-v", "-classpath", cpath.toString(), "Test.class"});
// tests --missing-deps option
test(new File(testDir, "Test.class"),
new String[] {"p.Foo", "p.Bar"},
new String[] {"not found", "not found"},
new String[] {"--missing-deps"});
// no missing dependence
test(new File(testDir, "Test.class"),
new String[0],
new String[0],
new String[] {"--missing-deps", "-classpath", cpath.toString()});
return errors;
}

@ -27,7 +27,7 @@
* @summary Tests for jdeps tool with multi-release jar files
* @modules jdk.jdeps/com.sun.tools.jdeps
* @library mrjar mrjar/base mrjar/9 mrjar/10 mrjar/v9 mrjar/v10
* @build test.* p.* q.* foo/*
* @build test.* p.* q.* foo/* Main
* @run testng MultiReleaseJar
*/
@ -59,10 +59,7 @@ public class MultiReleaseJar {
testJdk = System.getProperty("test.jdk");
fileSep = System.getProperty("file.separator");
cmdPath = Paths.get(testJdk, "bin");
}
@Test
public void basic() throws Exception {
// build Version.jar, Version_9.jar and main.jar
Result r = run("jar -cf Version.jar -C base test --release 9 -C 9 test --release 10 -C 10 test");
checkResult(r);
@ -70,11 +67,20 @@ public class MultiReleaseJar {
r = run("jar -cf Version_9.jar -C base test --release 9 -C 9 test");
checkResult(r);
r = run("jar -cf Main.jar test/Main.class");
r = run("jar -cf Main.jar Main.class");
checkResult(r);
// try out a bunch of things
r = run("jdeps --multi-release 9 -v missing.jar");
r = run("jar -cf Foo.jar -C base p");
checkResult(r);
Path foo = Paths.get(System.getProperty("test.classes")).resolve("modules").resolve("foo");
r = run("jar -uf Foo.jar --release 9 -C " + foo.toString() + " module-info.class --release 10 -C v10 q");
checkResult(r);
}
@Test
public void basic() throws Exception {
Result r = run("jdeps --multi-release 9 -v missing.jar");
checkResult(r, false, "Warning: Path does not exist: missing.jar");
r = run("jdeps -v Version.jar");
@ -115,7 +121,7 @@ public class MultiReleaseJar {
r = run("jdeps --multi-release 9.1 -v Version.jar");
checkResult(r, false, "Error: invalid argument for option: 9.1");
runJdeps("test/Main.class");
runJdeps("Main.class");
runJdeps("Main.jar");
}
@ -124,14 +130,14 @@ public class MultiReleaseJar {
Result r = run("jdeps -v -R -cp Version.jar " + path);
checkResult(r, false, "--multi-release option is not set");
r = run("jdeps -v -R -cp Version.jar -multi-release 9 " + path);
r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar -multi-release 9 " + path);
checkResult(r, false,
"Error: unknown option: -multi-release",
"Usage: jdeps <options> <path",
"use --help"
);
r = run("jdeps -v -R -cp Version.jar --multi-release 9 " + path);
r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar --multi-release 9 " + path);
String name = path;
int index = path.lastIndexOf('/');
@ -139,66 +145,94 @@ public class MultiReleaseJar {
name = path.substring(index + 1, path.length());
}
checkResult(r, true,
name + " ->",
name + " ->",
"test.Main",
"test.Main",
"test.Main",
"Version.jar ->",
name + " -> Version.jar",
name + " -> foo",
name + " -> java.base",
"Main",
"Main",
"Main",
"Main",
"Version.jar -> java.base",
"9/test.NonPublic",
"9/test.NonPublic",
"9/test.Version",
"9/test.Version",
"9/test.Version",
"9/test.Version"
"9/test.Version",
"foo",
"Foo.jar",
"requires mandated java.base",
"foo -> java.base",
"p.Foo"
);
r = run("jdeps -v -R -cp Version.jar --multi-release 10 " + path);
r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar --multi-release 10 " + path);
checkResult(r, true,
name + " ->",
name + " ->",
"test.Main",
"test.Main",
"test.Main",
name + " -> Version.jar",
name + " -> foo",
name + " -> java.base",
"Main",
"Main",
"Main",
"Main",
"Version.jar ->",
"10/test.Version",
"10/test.Version",
"10/test.Version",
"10/test.Version",
"9/test.NonPublic",
"9/test.NonPublic"
"9/test.NonPublic",
"foo",
"Foo.jar",
"requires mandated java.base",
"foo -> java.base",
"p.Foo"
);
r = run("jdeps -v -R -cp Version.jar --multi-release base " + path);
r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar --multi-release base " + path);
checkResult(r, true,
name + " ->",
name + " ->",
"test.Main",
"test.Main",
"test.Main",
name + " -> Version.jar",
name + " -> foo",
name + " -> java.base",
"Main",
"Main",
"Main",
"Main",
"Version.jar ->",
"test.Version",
"test.Version"
"test.Version",
"foo",
"Foo.jar",
"requires mandated java.base",
"foo -> java.base",
"p.Foo"
);
r = run("jdeps -v -R -cp Version.jar --multi-release 9.1 " + path);
r = run("jdeps -v -R -cp Version.jar --module-path Foo.jar --multi-release 9.1 " + path);
checkResult(r, false, "Error: invalid argument for option: 9.1");
// Version_9.jar does not have any version 10 entry
r = run("jdeps -v -R -cp Version_9.jar --multi-release 10 " + path);
r = run("jdeps -v -R -cp Version_9.jar --module-path Foo.jar --multi-release 10 " + path);
checkResult(r, true,
name + " ->",
name + " ->",
"test.Main",
"test.Main",
"test.Main",
name + " -> Version_9.jar",
name + " -> foo",
name + " -> java.base",
"Main",
"Main",
"Main",
"Main",
"Version_9.jar ->",
"9/test.NonPublic",
"9/test.NonPublic",
"9/test.Version",
"9/test.Version",
"9/test.Version",
"9/test.Version"
"9/test.Version",
"foo",
"Foo.jar",
"requires mandated java.base",
"foo -> java.base",
"p.Foo"
);
}
@ -236,17 +270,10 @@ public class MultiReleaseJar {
@Test
public void modularJar() throws Exception {
Result r = run("jar -cf foo.jar -C base p");
checkResult(r);
Path foo = Paths.get(System.getProperty("test.classes")).resolve("modules").resolve("foo");
r = run("jar -uf foo.jar --release 9 -C " + foo.toString() + " module-info.class --release 10 -C v10 q");
checkResult(r);
r = run("jdeps -v --multi-release 10 --module-path foo.jar -m foo");
Result r = run("jdeps -v --multi-release 10 --module-path Foo.jar -m foo");
checkResult(r, true,
"foo", // module name
"foo.jar", // the path to foo.jar
"Foo.jar", // the path to Foo.jar
"requires mandated java.base", // module dependences
"foo -> java.base",
"10/q.Bar",
@ -255,27 +282,24 @@ public class MultiReleaseJar {
"p.Foo"
);
r = run("jdeps --multi-release 9 --module-path foo.jar Main.jar");
r = run("jdeps --multi-release 9 -cp Version.jar --module-path Foo.jar Main.jar");
checkResult(r, true,
"Main.jar ->",
"test",
"foo", // module name
"foo.jar", // the path to foo.jar
"requires mandated java.base", // module dependences
"foo -> java.base",
"p"
"Main.jar -> Version.jar",
"Main.jar -> foo",
"Main.jar -> java.base",
"-> java.lang",
"-> p",
"-> test"
);
r = run("jdeps --multi-release 10 --module-path foo.jar Main.jar");
r = run("jdeps --multi-release 10 -cp Version.jar --module-path Foo.jar Main.jar");
checkResult(r, true,
"Main.jar ->",
"test",
"foo", // module name
"foo.jar", // the path to foo.jar
"requires mandated java.base", // module dependences
"foo -> java.base",
"p",
"q"
"Main.jar -> Version.jar",
"Main.jar -> foo",
"Main.jar -> java.base",
"-> java.lang",
"-> p",
"-> test"
);
}

@ -53,7 +53,15 @@ public class Options {
},
{
new String[] { "-jdkinternal", "-p", "java.lang", TEST_CLASSES },
"--package, --regex, --require cannot be used with -jdkinternals option"
"--package, --regex, --require cannot be used with -jdkinternals option"
},
{
new String[] { "--missing-deps", "-summary", TEST_CLASSES },
"-summary or -verbose cannot be used with --missing-deps option"
},
{
new String[] { "--missing-deps", "-p", "java.lang", TEST_CLASSES },
"--package, --regex, --require cannot be used with --missing-deps option"
},
{
new String[] { "--inverse", TEST_CLASSES },

@ -35,9 +35,11 @@
* @run testng ListModuleDeps
*/
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.testng.annotations.BeforeTest;
@ -53,6 +55,7 @@ public class ListModuleDeps {
private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
private static final Path CLASSES_DIR = Paths.get("classes");
private static final Path LIB_DIR = Paths.get("lib");
private static final Path LIB2_DIR = Paths.get("lib2");
private static final Path HI_CLASS =
CLASSES_DIR.resolve("hi").resolve("Hi.class");
@ -69,7 +72,8 @@ public class ListModuleDeps {
@BeforeTest
public void compileAll() throws Exception {
// compile library
assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "lib"), LIB_DIR));
assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "lib2"), LIB2_DIR));
assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "lib"), LIB_DIR, "-cp", LIB2_DIR.toString()));
// simple program depends only on java.base
assertTrue(CompilerUtils.compile(Paths.get(TEST_SRC, "src", "hi"), CLASSES_DIR));
@ -111,7 +115,7 @@ public class ListModuleDeps {
@Test(dataProvider = "listdeps")
public void testListDeps(Path classes, String[] expected) {
JdepsRunner jdeps = JdepsRunner.run(
"--class-path", LIB_DIR.toString(),
"--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
"--list-deps", classes.toString()
);
String[] output = Arrays.stream(jdeps.output())
@ -123,7 +127,7 @@ public class ListModuleDeps {
@Test(dataProvider = "reduceddeps")
public void testListReducedDeps(Path classes, String[] expected) {
JdepsRunner jdeps = JdepsRunner.run(
"--class-path", LIB_DIR.toString(),
"--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
"--list-reduced-deps", classes.toString()
);
String[] output = Arrays.stream(jdeps.output())
@ -140,6 +144,7 @@ public class ListModuleDeps {
"java.base/jdk.internal.misc",
"java.base/sun.security.util",
"java.logging",
"java.management",
"java.sql",
"java.xml/jdk.xml.internal",
"jdk.unsupported"
@ -154,6 +159,7 @@ public class ListModuleDeps {
{ FOO_CLASS, new String[] {
"java.base",
"java.logging",
"java.management",
"java.sql",
"java.xml"
}
@ -181,6 +187,7 @@ public class ListModuleDeps {
{ CLASSES_DIR, new String[] {
"java.base/jdk.internal.misc",
"java.base/sun.security.util",
"java.management",
"java.sql",
"java.xml/jdk.xml.internal",
"jdk.unsupported"
@ -194,6 +201,7 @@ public class ListModuleDeps {
{ FOO_CLASS, new String[] {
"java.base",
"java.management",
"java.sql"
}
},
@ -215,7 +223,7 @@ public class ListModuleDeps {
@Test(dataProvider = "moduledeps")
public void testPrintModuleDeps(Path classes, String expected) {
JdepsRunner jdeps = JdepsRunner.run(
"--class-path", LIB_DIR.toString(),
"--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
"--print-module-deps", classes.toString()
);
String output = Arrays.stream(jdeps.output())
@ -229,6 +237,32 @@ public class ListModuleDeps {
public Object[][] moduledeps() {
Path barClass = CLASSES_DIR.resolve("z").resolve("Bar.class");
return new Object[][] {
// java.xml is an implied reads edge from java.sql
{ CLASSES_DIR, "java.base,java.management,java.sql,jdk.unsupported"},
{ HI_CLASS, "java.base"},
{ FOO_CLASS, "java.base,java.management,java.sql"},
{ BAR_CLASS, "java.base,java.xml"},
{ UNSAFE_CLASS, "java.base,jdk.unsupported"},
};
}
@Test(dataProvider = "noRecursiveModuledeps")
public void testNoRecursiveModuleDeps(Path classes, String expected) {
JdepsRunner jdeps = JdepsRunner.run(
"--class-path", LIB_DIR.toString() + File.pathSeparator + LIB2_DIR.toString(),
"--print-module-deps", "--no-recursive", classes.toString()
);
String output = Arrays.stream(jdeps.output())
.map(s -> s.trim())
.collect(Collectors.joining(","));
assertEquals(output, expected);
}
@DataProvider(name = "noRecursiveModuledeps")
public Object[][] noRecursiveModuledeps() {
Path barClass = CLASSES_DIR.resolve("z").resolve("Bar.class");
return new Object[][] {
// java.xml is an implied reads edge from java.sql
{ CLASSES_DIR, "java.base,java.sql,jdk.unsupported"},
@ -238,4 +272,47 @@ public class ListModuleDeps {
{ UNSAFE_CLASS, "java.base,jdk.unsupported"},
};
}
@DataProvider(name = "recursiveDeps")
public Object[][] recursiveDeps() {
return new Object[][] {
{ // lib2 is classpath but not analyzed because lib.Lib is not present
// but it is the only class depending on lib2.Lib2
List.of("--list-deps", "--class-path", LIB2_DIR.toString(),
"--ignore-missing-deps", CLASSES_DIR.toString()),
new String[] {
"java.base/jdk.internal.misc",
"java.base/sun.security.util",
"java.logging",
"java.sql",
"java.xml/jdk.xml.internal",
"jdk.unsupported"
}
},
{ // lib2 is classpath but not analyzed because lib.Lib is not present
// but it is the only class depending on lib2.Lib2
List.of("--print-module-deps", "--class-path", LIB2_DIR.toString(),
"--ignore-missing-deps", CLASSES_DIR.toString()),
new String[] {
"java.base,java.sql,jdk.unsupported"
}
},
{ // Foo depends on lib.Lib which depends on lib2.Libs
List.of("--print-module-deps",
"--ignore-missing-deps", FOO_CLASS.toString()),
new String[] {
"java.base,java.sql"
}
},
};
}
@Test(dataProvider = "recursiveDeps")
public void testRecursiveDeps(List<String> options, String[] expected) {
JdepsRunner jdeps = JdepsRunner.run(options.toArray(new String[0]));
String[] output = Arrays.stream(jdeps.output())
.map(s -> s.trim())
.toArray(String[]::new);
assertEquals(output, expected);
}
}

@ -28,4 +28,5 @@ import javax.xml.stream.XMLInputFactory;
public class Lib {
public static final String isCoalescing = XMLInputFactory.IS_COALESCING;
public static boolean check() { return true; }
public static long getPid() { return lib2.Lib2.getPid(); }
}

@ -21,7 +21,8 @@
* questions.
*/
package test;
import test.Version;
import p.Foo;
public class Main {
public void run() {
@ -31,5 +32,6 @@ public class Main {
public static void main(String[] args) {
(new Main()).run();
Foo foo = new Foo();
}
}