1 /* 2 * Copyright (c) 2015, 2020, 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 26 package jdk.javadoc.internal.doclets.toolkit; 27 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.SortedSet; 36 import java.util.TreeSet; 37 38 import javax.lang.model.element.AnnotationMirror; 39 import javax.lang.model.element.Element; 40 import javax.lang.model.element.ExecutableElement; 41 import javax.lang.model.element.ModuleElement; 42 import javax.lang.model.element.PackageElement; 43 import javax.lang.model.element.TypeElement; 44 import javax.lang.model.element.VariableElement; 45 import javax.lang.model.type.TypeMirror; 46 import javax.lang.model.util.Elements; 47 import javax.tools.FileObject; 48 import javax.tools.JavaFileManager.Location; 49 50 import com.sun.source.tree.CompilationUnitTree; 51 import com.sun.source.util.JavacTask; 52 import com.sun.source.util.TreePath; 53 import com.sun.tools.doclint.DocLint; 54 import com.sun.tools.javac.api.BasicJavacTask; 55 import com.sun.tools.javac.code.Attribute; 56 import com.sun.tools.javac.code.Flags; 57 import com.sun.tools.javac.code.Scope; 58 import com.sun.tools.javac.code.Source.Feature; 59 import com.sun.tools.javac.code.Symbol; 60 import com.sun.tools.javac.code.Symbol.ClassSymbol; 61 import com.sun.tools.javac.code.Symbol.MethodSymbol; 62 import com.sun.tools.javac.code.Symbol.ModuleSymbol; 63 import com.sun.tools.javac.code.Symbol.PackageSymbol; 64 import com.sun.tools.javac.code.Symbol.VarSymbol; 65 import com.sun.tools.javac.code.TypeTag; 66 import com.sun.tools.javac.comp.AttrContext; 67 import com.sun.tools.javac.comp.Env; 68 import com.sun.tools.javac.model.JavacElements; 69 import com.sun.tools.javac.model.JavacTypes; 70 import com.sun.tools.javac.util.Names; 71 72 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 73 import jdk.javadoc.internal.tool.ToolEnvironment; 74 import jdk.javadoc.internal.tool.DocEnvImpl; 75 76 import static com.sun.tools.javac.code.Kinds.Kind.*; 77 import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; 78 79 import static javax.lang.model.element.ElementKind.*; 80 81 /** 82 * A quarantine class to isolate all the workarounds and bridges to 83 * a locality. This class should eventually disappear once all the 84 * standard APIs support the needed interfaces. 85 * 86 * 87 * <p><b>This is NOT part of any supported API. 88 * If you write code that depends on this, you do so at your own risk. 89 * This code and its internal interfaces are subject to change or 90 * deletion without notice.</b> 91 */ 92 public class WorkArounds { 93 94 public final BaseConfiguration configuration; 95 public final ToolEnvironment toolEnv; 96 public final Utils utils; 97 98 private DocLint doclint; 99 100 public WorkArounds(BaseConfiguration configuration) { 101 this.configuration = configuration; 102 this.utils = this.configuration.utils; 103 this.toolEnv = ((DocEnvImpl)this.configuration.docEnv).toolEnv; 104 } 105 106 Map<CompilationUnitTree, Boolean> shouldCheck = new HashMap<>(); 107 // TODO: fix this up correctly 108 public void runDocLint(TreePath path) { 109 CompilationUnitTree unit = path.getCompilationUnit(); 110 if (doclint != null && shouldCheck.computeIfAbsent(unit, doclint::shouldCheck)) { 111 doclint.scan(path); 112 } 113 } 114 115 /** 116 * Initializes doclint, if appropriate, depending on options derived 117 * from the doclet command-line options, and the set of custom tags 118 * that should be ignored by doclint. 119 * 120 * DocLint is not enabled if the option {@code -Xmsgs:none} is given, 121 * and it is not followed by any options to enable any groups. 122 * Note that arguments for {@code -Xmsgs:} can be given individually 123 * in separate {@code -Xmsgs:} options, or in a comma-separated list 124 * for a single option. For example, the following are equivalent: 125 * <ul> 126 * <li>{@code -Xmsgs:all} {@code -Xmsgs:-html} 127 * <li>{@code -Xmsgs:all,-html} 128 * </ul> 129 * 130 * @param opts options for doclint, derived from the corresponding doclet 131 * command-line options 132 * @param customTagNames the names of custom tags, to be ignored by doclint 133 */ 134 public void initDocLint(List<String> opts, Set<String> customTagNames) { 135 List<String> doclintOpts = new ArrayList<>(); 136 137 // basic analysis of -Xmsgs and -Xmsgs: options to see if doclint is enabled 138 Set<String> groups = new HashSet<>(); 139 boolean seenXmsgs = false; 140 for (String opt : opts) { 141 if (opt.equals(DocLint.XMSGS_OPTION)) { 142 groups.add("all"); 143 seenXmsgs = true; 144 } else if (opt.startsWith(DocLint.XMSGS_CUSTOM_PREFIX)) { 145 String[] args = opt.substring(DocLint.XMSGS_CUSTOM_PREFIX.length()) 146 .split(DocLint.SEPARATOR); 147 for (String a : args) { 148 if (a.equals("none")) { 149 groups.clear(); 150 } else if (a.startsWith("-")) { 151 groups.remove(a.substring(1)); 152 } else { 153 groups.add(a); 154 } 155 } 156 seenXmsgs = true; 157 } 158 doclintOpts.add(opt); 159 } 160 161 if (seenXmsgs) { 162 if (groups.isEmpty()) { 163 // no groups enabled; do not init doclint 164 return; 165 } 166 } else { 167 // no -Xmsgs options of any kind, use default 168 doclintOpts.add(DocLint.XMSGS_OPTION); 169 } 170 171 if (!customTagNames.isEmpty()) { 172 String customTags = String.join(DocLint.SEPARATOR, customTagNames); 173 doclintOpts.add(DocLint.XCUSTOM_TAGS_PREFIX + customTags); 174 } 175 176 doclintOpts.add(DocLint.XHTML_VERSION_PREFIX + "html5"); 177 178 JavacTask t = BasicJavacTask.instance(toolEnv.context); 179 doclint = new DocLint(); 180 doclint.init(t, doclintOpts.toArray(new String[0]), false); 181 } 182 183 // TODO: fix this up correctly 184 public boolean haveDocLint() { 185 return (doclint == null); 186 } 187 188 /* 189 * TODO: This method exists because of a bug in javac which does not 190 * handle "@deprecated tag in package-info.java", when this issue 191 * is fixed this method and its uses must be jettisoned. 192 */ 193 public boolean isDeprecated0(Element e) { 194 if (!utils.getDeprecatedTrees(e).isEmpty()) { 195 return true; 196 } 197 JavacTypes jctypes = ((DocEnvImpl)configuration.docEnv).toolEnv.typeutils; 198 TypeMirror deprecatedType = utils.getDeprecatedType(); 199 for (AnnotationMirror anno : e.getAnnotationMirrors()) { 200 if (jctypes.isSameType(anno.getAnnotationType().asElement().asType(), deprecatedType)) 201 return true; 202 } 203 return false; 204 } 205 206 // TODO: fix jx.l.m add this method. 207 public boolean isSynthesized(AnnotationMirror aDesc) { 208 return ((Attribute)aDesc).isSynthesized(); 209 } 210 211 // TODO: fix the caller 212 public Object getConstValue(VariableElement ve) { 213 return ((VarSymbol)ve).getConstValue(); 214 } 215 216 // TODO: DocTrees: Trees.getPath(Element e) is slow a factor 4-5 times. 217 public Map<Element, TreePath> getElementToTreePath() { 218 return toolEnv.elementToTreePath; 219 } 220 221 // TODO: we need ElementUtils.getPackage to cope with input strings 222 // to return the proper unnamedPackage for all supported releases. 223 PackageElement getUnnamedPackage() { 224 return (Feature.MODULES.allowedInSource(toolEnv.source)) 225 ? toolEnv.syms.unnamedModule.unnamedPackage 226 : toolEnv.syms.noModule.unnamedPackage; 227 } 228 229 // TODO: implement in either jx.l.m API (preferred) or DocletEnvironment. 230 FileObject getJavaFileObject(PackageElement packageElement) { 231 return ((PackageSymbol)packageElement).sourcefile; 232 } 233 234 // TODO: needs to ported to jx.l.m. 235 public TypeElement searchClass(TypeElement klass, String className) { 236 TypeElement te; 237 238 // search by qualified name in current module first 239 ModuleElement me = utils.containingModule(klass); 240 if (me != null) { 241 te = configuration.docEnv.getElementUtils().getTypeElement(me, className); 242 if (te != null) { 243 return te; 244 } 245 } 246 247 // search inner classes 248 for (TypeElement ite : utils.getClasses(klass)) { 249 TypeElement innerClass = searchClass(ite, className); 250 if (innerClass != null) { 251 return innerClass; 252 } 253 } 254 255 // check in this package 256 te = utils.findClassInPackageElement(utils.containingPackage(klass), className); 257 if (te != null) { 258 return te; 259 } 260 261 ClassSymbol tsym = (ClassSymbol)klass; 262 // make sure that this symbol has been completed 263 // TODO: do we need this anymore ? 264 if (tsym.completer != null) { 265 tsym.complete(); 266 } 267 268 // search imports 269 if (tsym.sourcefile != null) { 270 271 //### This information is available only for source classes. 272 Env<AttrContext> compenv = toolEnv.getEnv(tsym); 273 if (compenv == null) { 274 return null; 275 } 276 Names names = tsym.name.table.names; 277 Scope s = compenv.toplevel.namedImportScope; 278 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 279 if (sym.kind == TYP) { 280 return (TypeElement)sym; 281 } 282 } 283 284 s = compenv.toplevel.starImportScope; 285 for (Symbol sym : s.getSymbolsByName(names.fromString(className))) { 286 if (sym.kind == TYP) { 287 return (TypeElement)sym; 288 } 289 } 290 } 291 292 // finally, search by qualified name in all modules 293 te = configuration.docEnv.getElementUtils().getTypeElement(className); 294 if (te != null) { 295 return te; 296 } 297 298 return null; // not found 299 } 300 301 // TODO: need to re-implement this using j.l.m. correctly!, this has 302 // implications on testInterface, the note here is that javac's supertype 303 // does the right thing returning Parameters in scope. 304 /** 305 * Return the type containing the method that this method overrides. 306 * It may be a <code>TypeElement</code> or a <code>TypeParameterElement</code>. 307 * @param method target 308 * @return a type 309 */ 310 public TypeMirror overriddenType(ExecutableElement method) { 311 if (utils.isStatic(method)) { 312 return null; 313 } 314 MethodSymbol sym = (MethodSymbol)method; 315 ClassSymbol origin = (ClassSymbol) sym.owner; 316 for (com.sun.tools.javac.code.Type t = toolEnv.getTypes().supertype(origin.type); 317 t.hasTag(TypeTag.CLASS); 318 t = toolEnv.getTypes().supertype(t)) { 319 ClassSymbol c = (ClassSymbol) t.tsym; 320 for (com.sun.tools.javac.code.Symbol sym2 : c.members().getSymbolsByName(sym.name)) { 321 if (sym.overrides(sym2, origin, toolEnv.getTypes(), true)) { 322 // Ignore those methods that may be a simple override 323 // and allow the real API method to be found. 324 if (sym2.type.hasTag(TypeTag.METHOD) && 325 utils.isSimpleOverride((MethodSymbol)sym2)) { 326 continue; 327 } 328 return t; 329 } 330 } 331 } 332 return null; 333 } 334 335 // TODO: the method jx.l.m.Elements::overrides does not check 336 // the return type, see JDK-8174840 until that is resolved, 337 // use a copy of the same method, with a return type check. 338 339 // Note: the rider.overrides call in this method *must* be consistent 340 // with the call in overrideType(....), the method above. 341 public boolean overrides(ExecutableElement e1, ExecutableElement e2, TypeElement cls) { 342 MethodSymbol rider = (MethodSymbol)e1; 343 MethodSymbol ridee = (MethodSymbol)e2; 344 ClassSymbol origin = (ClassSymbol)cls; 345 346 return rider.name == ridee.name && 347 348 // not reflexive as per JLS 349 rider != ridee && 350 351 // we don't care if ridee is static, though that wouldn't 352 // compile 353 !rider.isStatic() && 354 355 // Symbol.overrides assumes the following 356 ridee.isMemberOf(origin, toolEnv.getTypes()) && 357 358 // check access, signatures and check return types 359 rider.overrides(ridee, origin, toolEnv.getTypes(), true); 360 } 361 362 // TODO: jx.l.m ? 363 public Location getLocationForModule(ModuleElement mdle) { 364 ModuleSymbol msym = (ModuleSymbol)mdle; 365 return msym.sourceLocation != null 366 ? msym.sourceLocation 367 : msym.classLocation; 368 } 369 370 //------------------Start of Serializable Implementation---------------------// 371 private final Map<TypeElement, NewSerializedForm> serializedForms = new HashMap<>(); 372 373 private NewSerializedForm getSerializedForm(TypeElement typeElem) { 374 return serializedForms.computeIfAbsent(typeElem, 375 te -> new NewSerializedForm(utils, configuration.docEnv.getElementUtils(), te)); 376 } 377 378 public SortedSet<VariableElement> getSerializableFields(TypeElement typeElem) { 379 return getSerializedForm(typeElem).fields; 380 } 381 382 public SortedSet<ExecutableElement> getSerializationMethods(TypeElement typeElem) { 383 return getSerializedForm(typeElem).methods; 384 } 385 386 public boolean definesSerializableFields(TypeElement typeElem) { 387 if (!utils.isSerializable(typeElem) || utils.isExternalizable(typeElem)) { 388 return false; 389 } else { 390 return getSerializedForm(typeElem).definesSerializableFields; 391 } 392 } 393 394 /* TODO we need a clean port to jx.l.m 395 * The serialized form is the specification of a class' serialization state. 396 * <p> 397 * 398 * It consists of the following information: 399 * <p> 400 * 401 * <pre> 402 * 1. Whether class is Serializable or Externalizable. 403 * 2. Javadoc for serialization methods. 404 * a. For Serializable, the optional readObject, writeObject, 405 * readResolve and writeReplace. 406 * serialData tag describes, in prose, the sequence and type 407 * of optional data written by writeObject. 408 * b. For Externalizable, writeExternal and readExternal. 409 * serialData tag describes, in prose, the sequence and type 410 * of optional data written by writeExternal. 411 * 3. Javadoc for serialization data layout. 412 * a. For Serializable, the name,type and description 413 * of each Serializable fields. 414 * b. For Externalizable, data layout is described by 2(b). 415 * </pre> 416 * 417 */ 418 static class NewSerializedForm { 419 420 final Utils utils; 421 final Elements elements; 422 423 final SortedSet<ExecutableElement> methods; 424 425 /* List of FieldDocImpl - Serializable fields. 426 * Singleton list if class defines Serializable fields explicitly. 427 * Otherwise, list of default serializable fields. 428 * 0 length list for Externalizable. 429 */ 430 final SortedSet<VariableElement> fields; 431 432 /* True if class specifies serializable fields explicitly. 433 * using special static member, serialPersistentFields. 434 */ 435 boolean definesSerializableFields = false; 436 437 // Specially treated field/method names defined by Serialization. 438 private static final String SERIALIZABLE_FIELDS = "serialPersistentFields"; 439 private static final String READOBJECT = "readObject"; 440 private static final String WRITEOBJECT = "writeObject"; 441 private static final String READRESOLVE = "readResolve"; 442 private static final String WRITEREPLACE = "writeReplace"; 443 private static final String READOBJECTNODATA = "readObjectNoData"; 444 445 NewSerializedForm(Utils utils, Elements elements, TypeElement te) { 446 this.utils = utils; 447 this.elements = elements; 448 methods = new TreeSet<>(utils.makeGeneralPurposeComparator()); 449 fields = new TreeSet<>(utils.makeGeneralPurposeComparator()); 450 if (utils.isExternalizable(te)) { 451 /* look up required public accessible methods, 452 * writeExternal and readExternal. 453 */ 454 String[] readExternalParamArr = {"java.io.ObjectInput"}; 455 String[] writeExternalParamArr = {"java.io.ObjectOutput"}; 456 457 ExecutableElement md = findMethod(te, "readExternal", Arrays.asList(readExternalParamArr)); 458 if (md != null) { 459 methods.add(md); 460 } 461 md = findMethod(te, "writeExternal", Arrays.asList(writeExternalParamArr)); 462 if (md != null) { 463 methods.add(md); 464 } 465 } else if (utils.isSerializable(te)) { 466 VarSymbol dsf = getDefinedSerializableFields((ClassSymbol) te); 467 if (dsf != null) { 468 /* Define serializable fields with array of ObjectStreamField. 469 * Each ObjectStreamField should be documented by a 470 * serialField tag. 471 */ 472 definesSerializableFields = true; 473 fields.add(dsf); 474 } else { 475 476 /* Calculate default Serializable fields as all 477 * non-transient, non-static fields. 478 * Fields should be documented by serial tag. 479 */ 480 computeDefaultSerializableFields((ClassSymbol) te); 481 } 482 483 /* Check for optional customized readObject, writeObject, 484 * readResolve and writeReplace, which can all contain 485 * the serialData tag. */ 486 addMethodIfExist((ClassSymbol) te, READOBJECT); 487 addMethodIfExist((ClassSymbol) te, WRITEOBJECT); 488 addMethodIfExist((ClassSymbol) te, READRESOLVE); 489 addMethodIfExist((ClassSymbol) te, WRITEREPLACE); 490 addMethodIfExist((ClassSymbol) te, READOBJECTNODATA); 491 } 492 } 493 494 private VarSymbol getDefinedSerializableFields(ClassSymbol def) { 495 Names names = def.name.table.names; 496 497 /* SERIALIZABLE_FIELDS can be private, 498 */ 499 for (Symbol sym : def.members().getSymbolsByName(names.fromString(SERIALIZABLE_FIELDS))) { 500 if (sym.kind == VAR) { 501 VarSymbol f = (VarSymbol) sym; 502 if ((f.flags() & Flags.STATIC) != 0 503 && (f.flags() & Flags.PRIVATE) != 0) { 504 return f; 505 } 506 } 507 } 508 return null; 509 } 510 511 /* 512 * Catalog Serializable method if it exists in current ClassSymbol. 513 * Do not look for method in superclasses. 514 * 515 * Serialization requires these methods to be non-static. 516 * 517 * @param method should be an unqualified Serializable method 518 * name either READOBJECT, WRITEOBJECT, READRESOLVE 519 * or WRITEREPLACE. 520 * @param visibility the visibility flag for the given method. 521 */ 522 private void addMethodIfExist(ClassSymbol def, String methodName) { 523 Names names = def.name.table.names; 524 525 for (Symbol sym : def.members().getSymbolsByName(names.fromString(methodName))) { 526 if (sym.kind == MTH) { 527 MethodSymbol md = (MethodSymbol) sym; 528 if ((md.flags() & Flags.STATIC) == 0) { 529 /* 530 * WARNING: not robust if unqualifiedMethodName is overloaded 531 * method. Signature checking could make more robust. 532 * READOBJECT takes a single parameter, java.io.ObjectInputStream. 533 * WRITEOBJECT takes a single parameter, java.io.ObjectOutputStream. 534 */ 535 methods.add(md); 536 } 537 } 538 } 539 } 540 541 /* 542 * Compute default Serializable fields from all members of ClassSymbol. 543 * 544 * must walk over all members of ClassSymbol. 545 */ 546 private void computeDefaultSerializableFields(ClassSymbol te) { 547 for (Symbol sym : te.members().getSymbols(NON_RECURSIVE)) { 548 if (sym != null && sym.kind == VAR) { 549 VarSymbol f = (VarSymbol) sym; 550 if ((f.flags() & Flags.STATIC) == 0 551 && (f.flags() & Flags.TRANSIENT) == 0) { 552 //### No modifier filtering applied here. 553 //### Add to beginning. 554 //### Preserve order used by old 'javadoc'. 555 fields.add(f); 556 } 557 } 558 } 559 } 560 561 /** 562 * Find a method in this class scope. Search order: this class, interfaces, superclasses, 563 * outerclasses. Note that this is not necessarily what the compiler would do! 564 * 565 * @param methodName the unqualified name to search for. 566 * @param paramTypes the array of Strings for method parameter types. 567 * @return the first MethodDocImpl which matches, null if not found. 568 */ 569 public ExecutableElement findMethod(TypeElement te, String methodName, 570 List<String> paramTypes) { 571 List<? extends Element> allMembers = this.elements.getAllMembers(te); 572 loop: 573 for (Element e : allMembers) { 574 if (e.getKind() != METHOD) { 575 continue; 576 } 577 ExecutableElement ee = (ExecutableElement) e; 578 if (!ee.getSimpleName().contentEquals(methodName)) { 579 continue; 580 } 581 List<? extends VariableElement> parameters = ee.getParameters(); 582 if (paramTypes.size() != parameters.size()) { 583 continue; 584 } 585 for (int i = 0; i < parameters.size(); i++) { 586 VariableElement ve = parameters.get(i); 587 if (!ve.asType().toString().equals(paramTypes.get(i))) { 588 break loop; 589 } 590 } 591 return ee; 592 } 593 TypeElement encl = utils.getEnclosingTypeElement(te); 594 if (encl == null) { 595 return null; 596 } 597 return findMethod(encl, methodName, paramTypes); 598 } 599 } 600 601 // TODO: we need to eliminate this, as it is hacky. 602 /** 603 * Returns a representation of the package truncated to two levels. 604 * For instance if the given package represents foo.bar.baz will return 605 * a representation of foo.bar 606 * @param pkg the PackageElement 607 * @return an abbreviated PackageElement 608 */ 609 public PackageElement getAbbreviatedPackageElement(PackageElement pkg) { 610 String parsedPackageName = utils.parsePackageName(pkg); 611 ModuleElement encl = (ModuleElement) pkg.getEnclosingElement(); 612 return encl == null 613 ? utils.elementUtils.getPackageElement(parsedPackageName) 614 : ((JavacElements) utils.elementUtils).getPackageElement(encl, parsedPackageName); 615 } 616 }