24 */ |
24 */ |
25 |
25 |
26 package com.sun.tools.jdeps; |
26 package com.sun.tools.jdeps; |
27 |
27 |
28 import java.io.PrintStream; |
28 import java.io.PrintStream; |
|
29 import java.util.ArrayList; |
|
30 import java.util.Collections; |
29 import java.util.Comparator; |
31 import java.util.Comparator; |
|
32 import java.util.Deque; |
30 import java.util.HashMap; |
33 import java.util.HashMap; |
31 import java.util.HashSet; |
34 import java.util.HashSet; |
|
35 import java.util.LinkedList; |
32 import java.util.List; |
36 import java.util.List; |
33 import java.util.Map; |
37 import java.util.Map; |
34 import java.util.Objects; |
38 import java.util.Objects; |
35 import java.util.Set; |
39 import java.util.Set; |
36 import java.util.stream.Collectors; |
40 import java.util.stream.Collectors; |
61 boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive); |
65 boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive); |
62 } |
66 } |
63 |
67 |
64 protected final Type type; |
68 protected final Type type; |
65 protected final Filter filter; |
69 protected final Filter filter; |
66 protected final Map<Archive, ArchiveDeps> results = new HashMap<>(); |
70 protected final Map<Archive, Dependences> results = new HashMap<>(); |
67 protected final Map<Location, Archive> map = new HashMap<>(); |
71 protected final Map<Location, Archive> locationToArchive = new HashMap<>(); |
68 private static final Archive NOT_FOUND |
72 static final Archive NOT_FOUND |
69 = new Archive(JdepsTask.getMessage("artifact.not.found")); |
73 = new Archive(JdepsTask.getMessage("artifact.not.found")); |
70 |
74 |
71 /** |
75 /** |
72 * Constructs an Analyzer instance. |
76 * Constructs an Analyzer instance. |
73 * |
77 * |
80 } |
84 } |
81 |
85 |
82 /** |
86 /** |
83 * Performs the dependency analysis on the given archives. |
87 * Performs the dependency analysis on the given archives. |
84 */ |
88 */ |
85 public boolean run(List<Archive> archives) { |
89 public boolean run(Stream<? extends Archive> archives) { |
|
90 return run(archives.collect(Collectors.toList())); |
|
91 } |
|
92 |
|
93 /** |
|
94 * Performs the dependency analysis on the given archives. |
|
95 */ |
|
96 public boolean run(Iterable<? extends Archive> archives) { |
86 // build a map from Location to Archive |
97 // build a map from Location to Archive |
87 buildLocationArchiveMap(archives); |
98 buildLocationArchiveMap(archives); |
88 |
99 |
89 // traverse and analyze all dependencies |
100 // traverse and analyze all dependencies |
90 for (Archive archive : archives) { |
101 for (Archive archive : archives) { |
91 ArchiveDeps deps = new ArchiveDeps(archive, type); |
102 Dependences deps = new Dependences(archive, type); |
92 archive.visitDependences(deps); |
103 archive.visitDependences(deps); |
93 results.put(archive, deps); |
104 results.put(archive, deps); |
94 } |
105 } |
95 return true; |
106 return true; |
96 } |
107 } |
97 |
108 |
98 protected void buildLocationArchiveMap(List<Archive> archives) { |
109 protected void buildLocationArchiveMap(Iterable<? extends Archive> archives) { |
99 // build a map from Location to Archive |
110 // build a map from Location to Archive |
100 for (Archive archive: archives) { |
111 for (Archive archive: archives) { |
101 for (Location l: archive.getClasses()) { |
112 archive.getClasses() |
102 if (!map.containsKey(l)) { |
113 .forEach(l -> locationToArchive.putIfAbsent(l, archive)); |
103 map.put(l, archive); |
|
104 } else { |
|
105 // duplicated class warning? |
|
106 } |
|
107 } |
|
108 } |
114 } |
109 } |
115 } |
110 |
116 |
111 public boolean hasDependences(Archive archive) { |
117 public boolean hasDependences(Archive archive) { |
112 if (results.containsKey(archive)) { |
118 if (results.containsKey(archive)) { |
114 } |
120 } |
115 return false; |
121 return false; |
116 } |
122 } |
117 |
123 |
118 public Set<String> dependences(Archive source) { |
124 public Set<String> dependences(Archive source) { |
119 ArchiveDeps result = results.get(source); |
125 if (!results.containsKey(source)) { |
|
126 return Collections.emptySet(); |
|
127 } |
|
128 Dependences result = results.get(source); |
120 return result.dependencies().stream() |
129 return result.dependencies().stream() |
121 .map(Dep::target) |
130 .map(Dep::target) |
122 .collect(Collectors.toSet()); |
131 .collect(Collectors.toSet()); |
|
132 } |
|
133 |
|
134 public Stream<Archive> requires(Archive source) { |
|
135 if (!results.containsKey(source)) { |
|
136 return Stream.empty(); |
|
137 } |
|
138 Dependences result = results.get(source); |
|
139 return result.requires().stream().filter(a -> !a.isEmpty()); |
123 } |
140 } |
124 |
141 |
125 public interface Visitor { |
142 public interface Visitor { |
126 /** |
143 /** |
127 * Visits a recorded dependency from origin to target which can be |
144 * Visits a recorded dependency from origin to target which can be |
136 * Visit the dependencies of the given source. |
153 * Visit the dependencies of the given source. |
137 * If the requested level is SUMMARY, it will visit the required archives list. |
154 * If the requested level is SUMMARY, it will visit the required archives list. |
138 */ |
155 */ |
139 public void visitDependences(Archive source, Visitor v, Type level) { |
156 public void visitDependences(Archive source, Visitor v, Type level) { |
140 if (level == Type.SUMMARY) { |
157 if (level == Type.SUMMARY) { |
141 final ArchiveDeps result = results.get(source); |
158 final Dependences result = results.get(source); |
142 final Set<Archive> reqs = result.requires(); |
159 final Set<Archive> reqs = result.requires(); |
143 Stream<Archive> stream = reqs.stream(); |
160 Stream<Archive> stream = reqs.stream(); |
144 if (reqs.isEmpty()) { |
161 if (reqs.isEmpty()) { |
145 if (hasDependences(source)) { |
162 if (hasDependences(source)) { |
146 // If reqs.isEmpty() and we have dependences, then it means |
163 // If reqs.isEmpty() and we have dependences, then it means |
150 } |
167 } |
151 stream.sorted(Comparator.comparing(Archive::getName)) |
168 stream.sorted(Comparator.comparing(Archive::getName)) |
152 .forEach(archive -> { |
169 .forEach(archive -> { |
153 Profile profile = result.getTargetProfile(archive); |
170 Profile profile = result.getTargetProfile(archive); |
154 v.visitDependence(source.getName(), source, |
171 v.visitDependence(source.getName(), source, |
155 profile != null ? profile.profileName() : archive.getName(), archive); |
172 profile != null ? profile.profileName() |
|
173 : archive.getName(), archive); |
156 }); |
174 }); |
157 } else { |
175 } else { |
158 ArchiveDeps result = results.get(source); |
176 Dependences result = results.get(source); |
159 if (level != type) { |
177 if (level != type) { |
160 // requesting different level of analysis |
178 // requesting different level of analysis |
161 result = new ArchiveDeps(source, level); |
179 result = new Dependences(source, level); |
162 source.visitDependences(result); |
180 source.visitDependences(result); |
163 } |
181 } |
164 result.dependencies().stream() |
182 result.dependencies().stream() |
165 .sorted(Comparator.comparing(Dep::origin) |
183 .sorted(Comparator.comparing(Dep::origin) |
166 .thenComparing(Dep::target)) |
184 .thenComparing(Dep::target)) |
167 .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), d.target(), d.targetArchive())); |
185 .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), |
|
186 d.target(), d.targetArchive())); |
168 } |
187 } |
169 } |
188 } |
170 |
189 |
171 public void visitDependences(Archive source, Visitor v) { |
190 public void visitDependences(Archive source, Visitor v) { |
172 visitDependences(source, v, type); |
191 visitDependences(source, v, type); |
173 } |
192 } |
174 |
193 |
175 /** |
194 /** |
176 * ArchiveDeps contains the dependencies for an Archive that can have one or |
195 * Dependences contains the dependencies for an Archive that can have one or |
177 * more classes. |
196 * more classes. |
178 */ |
197 */ |
179 class ArchiveDeps implements Archive.Visitor { |
198 class Dependences implements Archive.Visitor { |
180 protected final Archive archive; |
199 protected final Archive archive; |
181 protected final Set<Archive> requires; |
200 protected final Set<Archive> requires; |
182 protected final Set<Dep> deps; |
201 protected final Set<Dep> deps; |
183 protected final Type level; |
202 protected final Type level; |
184 private Profile profile; |
203 private Profile profile; |
185 ArchiveDeps(Archive archive, Type level) { |
204 Dependences(Archive archive, Type level) { |
186 this.archive = archive; |
205 this.archive = archive; |
187 this.deps = new HashSet<>(); |
206 this.deps = new HashSet<>(); |
188 this.requires = new HashSet<>(); |
207 this.requires = new HashSet<>(); |
189 this.level = level; |
208 this.level = level; |
190 } |
209 } |
196 Set<Archive> requires() { |
215 Set<Archive> requires() { |
197 return requires; |
216 return requires; |
198 } |
217 } |
199 |
218 |
200 Profile getTargetProfile(Archive target) { |
219 Profile getTargetProfile(Archive target) { |
201 if (target instanceof Module) { |
220 if (target.getModule().isJDK()) { |
202 return Profile.getProfile((Module) target); |
221 return Profile.getProfile((Module) target); |
203 } else { |
222 } else { |
204 return null; |
223 return null; |
205 } |
224 } |
206 } |
225 } |
207 |
226 |
208 Archive findArchive(Location t) { |
227 Archive findArchive(Location t) { |
209 Archive target = archive.getClasses().contains(t) ? archive : map.get(t); |
228 if (archive.getClasses().contains(t)) |
210 if (target == null) { |
229 return archive; |
211 map.put(t, target = NOT_FOUND); |
230 |
212 } |
231 return locationToArchive.computeIfAbsent(t, _k -> NOT_FOUND); |
213 return target; |
|
214 } |
232 } |
215 |
233 |
216 // return classname or package name depedning on the level |
234 // return classname or package name depedning on the level |
217 private String getLocationName(Location o) { |
235 private String getLocationName(Location o) { |
218 if (level == Type.CLASS || level == Type.VERBOSE) { |
236 if (level == Type.CLASS || level == Type.VERBOSE) { |
313 return false; |
331 return false; |
314 } |
332 } |
315 |
333 |
316 @Override |
334 @Override |
317 public int hashCode() { |
335 public int hashCode() { |
318 int hash = 7; |
336 return Objects.hash(this.origin, |
319 hash = 67*hash + Objects.hashCode(this.origin) |
337 this.originArchive, |
320 + Objects.hashCode(this.originArchive) |
338 this.target, |
321 + Objects.hashCode(this.target) |
339 this.targetArchive); |
322 + Objects.hashCode(this.targetArchive); |
|
323 return hash; |
|
324 } |
340 } |
325 |
341 |
326 public String toString() { |
342 public String toString() { |
327 return String.format("%s (%s) -> %s (%s)%n", |
343 return String.format("%s (%s) -> %s (%s)%n", |
328 origin, originArchive.getName(), |
344 origin, originArchive.getName(), |
329 target, targetArchive.getName()); |
345 target, targetArchive.getName()); |
330 } |
346 } |
331 } |
347 } |
332 |
|
333 static Analyzer getExportedAPIsAnalyzer() { |
|
334 return new ModuleAccessAnalyzer(ModuleAccessAnalyzer.reexportsFilter, true); |
|
335 } |
|
336 |
|
337 static Analyzer getModuleAccessAnalyzer() { |
|
338 return new ModuleAccessAnalyzer(ModuleAccessAnalyzer.accessCheckFilter, false); |
|
339 } |
|
340 |
|
341 private static class ModuleAccessAnalyzer extends Analyzer { |
|
342 private final boolean apionly; |
|
343 ModuleAccessAnalyzer(Filter filter, boolean apionly) { |
|
344 super(Type.VERBOSE, filter); |
|
345 this.apionly = apionly; |
|
346 } |
|
347 /** |
|
348 * Verify module access |
|
349 */ |
|
350 public boolean run(List<Archive> archives) { |
|
351 // build a map from Location to Archive |
|
352 buildLocationArchiveMap(archives); |
|
353 |
|
354 // traverse and analyze all dependencies |
|
355 int count = 0; |
|
356 for (Archive archive : archives) { |
|
357 ArchiveDeps checker = new ArchiveDeps(archive, type); |
|
358 archive.visitDependences(checker); |
|
359 count += checker.dependencies().size(); |
|
360 // output if any error |
|
361 Module m = (Module)archive; |
|
362 printDependences(System.err, m, checker.dependencies()); |
|
363 results.put(archive, checker); |
|
364 } |
|
365 return count == 0; |
|
366 } |
|
367 |
|
368 private void printDependences(PrintStream out, Module m, Set<Dep> deps) { |
|
369 if (deps.isEmpty()) |
|
370 return; |
|
371 |
|
372 String msg = apionly ? "API reference:" : "inaccessible reference:"; |
|
373 deps.stream().sorted(Comparator.comparing(Dep::origin) |
|
374 .thenComparing(Dep::target)) |
|
375 .forEach(d -> out.format("%s %s (%s) -> %s (%s)%n", msg, |
|
376 d.origin(), d.originArchive().getName(), |
|
377 d.target(), d.targetArchive().getName())); |
|
378 if (apionly) { |
|
379 out.format("Dependences missing re-exports=\"true\" attribute:%n"); |
|
380 deps.stream() |
|
381 .map(Dep::targetArchive) |
|
382 .map(Archive::getName) |
|
383 .distinct() |
|
384 .sorted() |
|
385 .forEach(d -> out.format(" %s -> %s%n", m.name(), d)); |
|
386 } |
|
387 } |
|
388 |
|
389 private static Module findModule(Archive archive) { |
|
390 if (Module.class.isInstance(archive)) { |
|
391 return (Module) archive; |
|
392 } else { |
|
393 return null; |
|
394 } |
|
395 } |
|
396 |
|
397 // returns true if target is accessible by origin |
|
398 private static boolean canAccess(Location o, Archive originArchive, Location t, Archive targetArchive) { |
|
399 Module origin = findModule(originArchive); |
|
400 Module target = findModule(targetArchive); |
|
401 |
|
402 if (targetArchive == Analyzer.NOT_FOUND) { |
|
403 return false; |
|
404 } |
|
405 |
|
406 // unnamed module |
|
407 // ## should check public type? |
|
408 if (target == null) |
|
409 return true; |
|
410 |
|
411 // module-private |
|
412 if (origin == target) |
|
413 return true; |
|
414 |
|
415 return target.isAccessibleTo(t.getClassName(), origin); |
|
416 } |
|
417 |
|
418 static final Filter accessCheckFilter = new Filter() { |
|
419 @Override |
|
420 public boolean accepts(Location o, Archive originArchive, Location t, Archive targetArchive) { |
|
421 return !canAccess(o, originArchive, t, targetArchive); |
|
422 } |
|
423 }; |
|
424 |
|
425 static final Filter reexportsFilter = new Filter() { |
|
426 @Override |
|
427 public boolean accepts(Location o, Archive originArchive, Location t, Archive targetArchive) { |
|
428 Module origin = findModule(originArchive); |
|
429 Module target = findModule(targetArchive); |
|
430 if (!origin.isExportedPackage(o.getPackageName())) { |
|
431 // filter non-exported classes |
|
432 return false; |
|
433 } |
|
434 |
|
435 boolean accessible = canAccess(o, originArchive, t, targetArchive); |
|
436 if (!accessible) |
|
437 return true; |
|
438 |
|
439 String mn = target.name(); |
|
440 // skip checking re-exports for java.base |
|
441 if (origin == target || "java.base".equals(mn)) |
|
442 return false; |
|
443 |
|
444 assert origin.requires().containsKey(mn); // otherwise, should not be accessible |
|
445 if (origin.requires().get(mn)) { |
|
446 return false; |
|
447 } |
|
448 return true; |
|
449 } |
|
450 }; |
|
451 } |
|
452 } |
348 } |