1 /* 2 * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.sun.tools.jdeps; 26 27 import java.io.PrintStream; 28 import java.util.Comparator; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Objects; 34 import java.util.Set; 35 import java.util.stream.Collectors; 36 37 import com.sun.tools.classfile.Dependency.Location; 38 39 /** 40 * Dependency Analyzer. 41 */ 42 public class Analyzer { 43 /** 44 * Type of the dependency analysis. Appropriate level of data 45 * will be stored. 46 */ 47 public enum Type { 48 SUMMARY, 49 PACKAGE, 50 CLASS, 51 VERBOSE 52 } 53 54 /** 55 * Filter to be applied when analyzing the dependencies from the given archives. 56 * Only the accepted dependencies are recorded. 57 */ 58 interface Filter { 59 boolean accepts(Location origin, Archive originArchive, Location target, Archive targetArchive); 60 } 61 62 protected final Type type; 63 protected final Filter filter; 64 protected final Map<Archive, ArchiveDeps> results = new HashMap<>(); 65 protected final Map<Location, Archive> map = new HashMap<>(); 66 private static final Archive NOT_FOUND 67 = new Archive(JdepsTask.getMessage("artifact.not.found")); 68 69 /** 70 * Constructs an Analyzer instance. 71 * 72 * @param type Type of the dependency analysis 73 * @param filter 74 */ 75 public Analyzer(Type type, Filter filter) { 76 this.type = type; 77 this.filter = filter; 78 } 79 80 /** 81 * Performs the dependency analysis on the given archives. 82 */ 83 public boolean run(List<Archive> archives) { 84 // build a map from Location to Archive 85 buildLocationArchiveMap(archives); 86 87 // traverse and analyze all dependencies 88 for (Archive archive : archives) { 89 ArchiveDeps deps = new ArchiveDeps(archive, type); 90 archive.visitDependences(deps); 91 results.put(archive, deps); 92 } 93 return true; 94 } 95 96 protected void buildLocationArchiveMap(List<Archive> archives) { 97 // build a map from Location to Archive 98 for (Archive archive: archives) { 99 for (Location l: archive.getClasses()) { 100 if (!map.containsKey(l)) { 101 map.put(l, archive); 102 } else { 103 // duplicated class warning? 104 } 105 } 106 } 107 } 108 109 public boolean hasDependences(Archive archive) { 110 if (results.containsKey(archive)) { 111 return results.get(archive).dependencies().size() > 0; 112 } 113 return false; 114 } 115 116 public Set<String> dependences(Archive source) { 117 ArchiveDeps result = results.get(source); 118 return result.dependencies().stream() 119 .map(Dep::target) 120 .collect(Collectors.toSet()); 121 } 122 123 public interface Visitor { 124 /** 125 * Visits a recorded dependency from origin to target which can be 126 * a fully-qualified classname, a package name, a module or 127 * archive name depending on the Analyzer's type. 128 */ 129 public void visitDependence(String origin, Archive originArchive, 130 String target, Archive targetArchive); 131 } 132 133 /** 134 * Visit the dependencies of the given source. 135 * If the requested level is SUMMARY, it will visit the required archives list. 136 */ 137 public void visitDependences(Archive source, Visitor v, Type level) { 138 if (level == Type.SUMMARY) { 139 final ArchiveDeps result = results.get(source); 140 result.requires().stream() 141 .sorted(Comparator.comparing(Archive::getName)) 142 .forEach(archive -> { 143 Profile profile = result.getTargetProfile(archive); 144 v.visitDependence(source.getName(), source, 145 profile != null ? profile.profileName() : archive.getName(), archive); 146 }); 147 } else { 148 ArchiveDeps result = results.get(source); 149 if (level != type) { 150 // requesting different level of analysis 151 result = new ArchiveDeps(source, level); 152 source.visitDependences(result); 153 } 154 result.dependencies().stream() 155 .sorted(Comparator.comparing(Dep::origin) 156 .thenComparing(Dep::target)) 157 .forEach(d -> v.visitDependence(d.origin(), d.originArchive(), d.target(), d.targetArchive())); 158 } 159 } 160 161 public void visitDependences(Archive source, Visitor v) { 162 visitDependences(source, v, type); 163 } 164 165 /** 166 * ArchiveDeps contains the dependencies for an Archive that can have one or 167 * more classes. 168 */ 169 class ArchiveDeps implements Archive.Visitor { 170 protected final Archive archive; 171 protected final Set<Archive> requires; 172 protected final Set<Dep> deps; 173 protected final Type level; 174 private Profile profile; 175 ArchiveDeps(Archive archive, Type level) { 176 this.archive = archive; 177 this.deps = new HashSet<>(); 178 this.requires = new HashSet<>(); 179 this.level = level; 180 } 181 182 Set<Dep> dependencies() { 183 return deps; 184 } 185 186 Set<Archive> requires() { 187 return requires; 188 } 189 190 Profile getTargetProfile(Archive target) { 191 if (target instanceof Module) { 192 return Profile.getProfile((Module) target); 193 } else { 194 return null; 195 } 196 } 197 198 Archive findArchive(Location t) { 199 Archive target = archive.getClasses().contains(t) ? archive : map.get(t); 200 if (target == null) { 201 map.put(t, target = NOT_FOUND); 202 } 203 return target; 204 } 205 206 // return classname or package name depedning on the level 207 private String getLocationName(Location o) { 208 if (level == Type.CLASS || level == Type.VERBOSE) { 209 return o.getClassName(); 210 } else { 211 String pkg = o.getPackageName(); 212 return pkg.isEmpty() ? "<unnamed>" : pkg; 213 } 214 } 215 216 @Override 217 public void visit(Location o, Location t) { 218 Archive targetArchive = findArchive(t); 219 if (filter.accepts(o, archive, t, targetArchive)) { 220 addDep(o, t); 221 if (archive != targetArchive && !requires.contains(targetArchive)) { 222 requires.add(targetArchive); 223 } 224 } 225 if (targetArchive instanceof Module) { 226 Profile p = Profile.getProfile(t.getPackageName()); 227 if (profile == null || (p != null && p.compareTo(profile) > 0)) { 228 profile = p; 229 } 230 } 231 } 232 233 private Dep curDep; 234 protected Dep addDep(Location o, Location t) { 235 String origin = getLocationName(o); 236 String target = getLocationName(t); 237 Archive targetArchive = findArchive(t); 238 if (curDep != null && 239 curDep.origin().equals(origin) && 240 curDep.originArchive() == archive && 241 curDep.target().equals(target) && 242 curDep.targetArchive() == targetArchive) { 243 return curDep; 244 } 245 246 Dep e = new Dep(origin, archive, target, targetArchive); 247 if (deps.contains(e)) { 248 for (Dep e1 : deps) { 249 if (e.equals(e1)) { 250 curDep = e1; 251 } 252 } 253 } else { 254 deps.add(e); 255 curDep = e; 256 } 257 return curDep; 258 } 259 } 260 261 /* 262 * Class-level or package-level dependency 263 */ 264 class Dep { 265 final String origin; 266 final Archive originArchive; 267 final String target; 268 final Archive targetArchive; 269 270 Dep(String origin, Archive originArchive, String target, Archive targetArchive) { 271 this.origin = origin; 272 this.originArchive = originArchive; 273 this.target = target; 274 this.targetArchive = targetArchive; 275 } 276 277 String origin() { 278 return origin; 279 } 280 281 Archive originArchive() { 282 return originArchive; 283 } 284 285 String target() { 286 return target; 287 } 288 289 Archive targetArchive() { 290 return targetArchive; 291 } 292 293 @Override 294 @SuppressWarnings("unchecked") 295 public boolean equals(Object o) { 296 if (o instanceof Dep) { 297 Dep d = (Dep) o; 298 return this.origin.equals(d.origin) && 299 this.originArchive == d.originArchive && 300 this.target.equals(d.target) && 301 this.targetArchive == d.targetArchive; 302 } 303 return false; 304 } 305 306 @Override 307 public int hashCode() { 308 int hash = 7; 309 hash = 67*hash + Objects.hashCode(this.origin) 310 + Objects.hashCode(this.originArchive) 311 + Objects.hashCode(this.target) 312 + Objects.hashCode(this.targetArchive); 313 return hash; 314 } 315 316 public String toString() { 317 return String.format("%s (%s) -> %s (%s)%n", 318 origin, originArchive.getName(), 319 target, targetArchive.getName()); 320 } 321 } 322 323 static Analyzer getExportedAPIsAnalyzer() { 324 return new ModuleAccessAnalyzer(ModuleAccessAnalyzer.reexportsFilter, true); 325 } 326 327 static Analyzer getModuleAccessAnalyzer() { 328 return new ModuleAccessAnalyzer(ModuleAccessAnalyzer.accessCheckFilter, false); 329 } 330 331 private static class ModuleAccessAnalyzer extends Analyzer { 332 private final boolean apionly; 333 ModuleAccessAnalyzer(Filter filter, boolean apionly) { 334 super(Type.VERBOSE, filter); 335 this.apionly = apionly; 336 } 337 /** 338 * Verify module access 339 */ 340 public boolean run(List<Archive> archives) { 341 // build a map from Location to Archive 342 buildLocationArchiveMap(archives); 343 344 // traverse and analyze all dependencies 345 int count = 0; 346 for (Archive archive : archives) { 347 ArchiveDeps checker = new ArchiveDeps(archive, type); 348 archive.visitDependences(checker); 349 count += checker.dependencies().size(); 350 // output if any error 351 Module m = (Module)archive; 352 printDependences(System.err, m, checker.dependencies()); 353 results.put(archive, checker); 354 } 355 return count == 0; 356 } 357 358 private void printDependences(PrintStream out, Module m, Set<Dep> deps) { 359 if (deps.isEmpty()) 360 return; 361 362 String msg = apionly ? "API reference:" : "inaccessible reference:"; 363 deps.stream().sorted(Comparator.comparing(Dep::origin) 364 .thenComparing(Dep::target)) 365 .forEach(d -> out.format("%s %s (%s) -> %s (%s)%n", msg, 366 d.origin(), d.originArchive().getName(), 367 d.target(), d.targetArchive().getName())); 368 if (apionly) { 369 out.format("Dependences missing re-exports=\"true\" attribute:%n"); 370 deps.stream() 371 .map(Dep::targetArchive) 372 .map(Archive::getName) 373 .distinct() 374 .sorted() 375 .forEach(d -> out.format(" %s -> %s%n", m.name(), d)); 376 } 377 } 378 379 private static Module findModule(Archive archive) { 380 if (Module.class.isInstance(archive)) { 381 return (Module) archive; 382 } else { 383 return null; 384 } 385 } 386 387 // returns true if target is accessible by origin 388 private static boolean canAccess(Location o, Archive originArchive, Location t, Archive targetArchive) { 389 Module origin = findModule(originArchive); 390 Module target = findModule(targetArchive); 391 392 if (targetArchive == Analyzer.NOT_FOUND) { 393 return false; 394 } 395 396 // unnamed module 397 // ## should check public type? 398 if (target == null) 399 return true; 400 401 // module-private 402 if (origin == target) 403 return true; 404 405 return target.isAccessibleTo(t.getClassName(), origin); 406 } 407 408 static final Filter accessCheckFilter = new Filter() { 409 @Override 410 public boolean accepts(Location o, Archive originArchive, Location t, Archive targetArchive) { 411 return !canAccess(o, originArchive, t, targetArchive); 412 } 413 }; 414 415 static final Filter reexportsFilter = new Filter() { 416 @Override 417 public boolean accepts(Location o, Archive originArchive, Location t, Archive targetArchive) { 418 Module origin = findModule(originArchive); 419 Module target = findModule(targetArchive); 420 if (!origin.isExportedPackage(o.getPackageName())) { 421 // filter non-exported classes 422 return false; 423 } 424 425 boolean accessible = canAccess(o, originArchive, t, targetArchive); 426 if (!accessible) 427 return true; 428 429 String mn = target.name(); 430 // skip checking re-exports for java.base 431 if (origin == target || "java.base".equals(mn)) 432 return false; 433 434 assert origin.requires().containsKey(mn); // otherwise, should not be accessible 435 if (origin.requires().get(mn)) { 436 return false; 437 } 438 return true; 439 } 440 }; 441 } 442 }