1 /* 2 * Copyright (c) 2023, 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 com.sun.tools.javac.comp; 27 28 import java.util.ArrayDeque; 29 import java.util.ArrayList; 30 import java.util.Comparator; 31 import java.util.LinkedHashMap; 32 import java.util.HashSet; 33 import java.util.Map.Entry; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.Optional; 37 import java.util.Set; 38 import java.util.concurrent.atomic.AtomicBoolean; 39 import java.util.function.BiPredicate; 40 import java.util.function.Consumer; 41 import java.util.function.Function; 42 import java.util.stream.Collectors; 43 import java.util.stream.Stream; 44 45 import com.sun.tools.javac.code.Directive; 46 import com.sun.tools.javac.code.Flags; 47 import com.sun.tools.javac.code.Lint; 48 import com.sun.tools.javac.code.Symbol; 49 import com.sun.tools.javac.code.Symbol.*; 50 import com.sun.tools.javac.code.Symtab; 51 import com.sun.tools.javac.code.Type; 52 import com.sun.tools.javac.code.Types; 53 import com.sun.tools.javac.resources.CompilerProperties.Warnings; 54 import com.sun.tools.javac.tree.JCTree; 55 import com.sun.tools.javac.tree.JCTree.*; 56 import com.sun.tools.javac.tree.TreeInfo; 57 import com.sun.tools.javac.tree.TreeScanner; 58 import com.sun.tools.javac.util.Assert; 59 import com.sun.tools.javac.util.JCDiagnostic; 60 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; 61 import com.sun.tools.javac.util.List; 62 import com.sun.tools.javac.util.Log; 63 import com.sun.tools.javac.util.Name; 64 import com.sun.tools.javac.util.Names; 65 import com.sun.tools.javac.util.Pair; 66 67 import static com.sun.tools.javac.code.Kinds.Kind.*; 68 import static com.sun.tools.javac.code.TypeTag.*; 69 import static com.sun.tools.javac.tree.JCTree.Tag.*; 70 71 /** 72 * Looks for possible 'this' escapes and generates corresponding warnings. 73 * 74 * <p> 75 * A 'this' escape is when a constructor invokes a method that could be overridden in a 76 * subclass, in which case the method will execute before the subclass constructor has 77 * finished initializing the instance. 78 * 79 * <p> 80 * This class attempts to identify possible 'this' escapes while also striking a balance 81 * between false positives, false negatives, and code complexity. We do this by "executing" 82 * the code in candidate constructors and tracking where the original 'this' reference goes. 83 * If it passes to code outside of the current module, we declare a possible leak. 84 * 85 * <p> 86 * As we analyze constructors and the methods they invoke, we track the various things in scope 87 * that could possibly reference the 'this' instance we are following. Such references are 88 * represented by {@link Ref} instances, of which there are these varieties: 89 * <ul> 90 * <li>The current 'this' reference; see {@link ThisRef} 91 * <li>The current outer 'this' reference; see {@link OuterRef} 92 * <li>Local variables and method parameters; see {@link VarRef} 93 * <li>The current expression being evaluated, i.e.,what's on top of the Java stack; see {@link ExprRef} 94 * <li>The current switch expressions's yield value; see {@link YieldRef} 95 * <li>The current method's return value; see {@link ReturnRef} 96 * </ul> 97 * 98 * <p> 99 * For each type of reference, we distinguish between <i>direct</i> and <i>indirect</i> references. 100 * A direct reference means the reference directly refers to the 'this' instance we are tracking. 101 * An indirect reference means the reference refers to the 'this' instance we are tracking through 102 * at least one level of indirection. 103 * 104 * <p> 105 * Currently we do not attempt to explicitly track references stored in fields (for future study). 106 * 107 * <p> 108 * A few notes on this implementation: 109 * <ul> 110 * <li>We "execute" constructors and track where the 'this' reference goes as the constructor executes. 111 * <li>We use a very simplified flow analysis that you might call a "flood analysis", where the union 112 * of every possible code branch is taken. 113 * <li>A "leak" is defined as the possible passing of a subclassed 'this' reference to code defined 114 * outside of the current module. 115 * <ul> 116 * <li>In other words, we don't try to protect the current module's code from itself. 117 * <li>For example, we ignore private constructors because they can never be directly invoked 118 * by external subclasses, etc. However, they can be indirectly invoked by other constructors. 119 * </ul> 120 * <li>If a constructor invokes a method defined in the same compilation unit, and that method cannot 121 * be overridden, then our analysis can safely "recurse" into the method. 122 * <ul> 123 * <li>When this occurs the warning displays each step in the stack trace to help in comprehension. 124 * </ul> 125 * <li>We assume that native methods do not leak. 126 * <li>We don't try to follow {@code super()} invocations; that's for the superclass analysis to handle. 127 * </ul> 128 */ 129 class ThisEscapeAnalyzer extends TreeScanner { 130 131 private final Names names; 132 private final Symtab syms; 133 private final Types types; 134 private final Log log; 135 private Lint lint; 136 137 // These fields are scoped to the entire compilation unit 138 139 /** Maps symbols of all methods to their corresponding declarations. 140 */ 141 private final Map<Symbol, MethodInfo> methodMap = new LinkedHashMap<>(); 142 143 /** Contains symbols of fields and constructors that have warnings suppressed. 144 */ 145 private final Set<Symbol> suppressed = new HashSet<>(); 146 147 /** The declaring class of the constructor we're currently analyzing. 148 * This is the 'this' type we're trying to detect leaks of. 149 */ 150 private JCClassDecl targetClass; 151 152 /** Snapshots of {@link #callStack} where possible 'this' escapes occur. 153 */ 154 private final ArrayList<DiagnosticPosition[]> warningList = new ArrayList<>(); 155 156 // These fields are scoped to the constructor being analyzed 157 158 /** The declaring class of the "invoked" method we're currently analyzing. 159 * This is either the analyzed constructor or some method it invokes. 160 */ 161 private JCClassDecl methodClass; 162 163 /** The current "call stack" during our analysis. The first entry is some method 164 * invoked from the target constructor; if empty, we're still in the constructor. 165 */ 166 private final ArrayDeque<DiagnosticPosition> callStack = new ArrayDeque<>(); 167 168 /** Used to terminate recursion in {@link #invokeInvokable invokeInvokable()}. 169 */ 170 private final Set<Pair<JCMethodDecl, RefSet<Ref>>> invocations = new HashSet<>(); 171 172 /** Snapshot of {@link #callStack} where a possible 'this' escape occurs. 173 * If non-null, a 'this' escape warning has been found in the current 174 * constructor statement, initialization block statement, or field initializer. 175 */ 176 private DiagnosticPosition[] pendingWarning; 177 178 // These fields are scoped to the constructor or invoked method being analyzed 179 180 /** Current lexical scope depth in the constructor or method we're currently analyzing. 181 * Depth zero is the outermost scope. Depth -1 means we're not analyzing. 182 */ 183 private int depth = -1; 184 185 /** Possible 'this' references in the constructor or method we're currently analyzing. 186 * Null value means we're not analyzing. 187 */ 188 private RefSet<Ref> refs; 189 190 // Constructor 191 192 ThisEscapeAnalyzer(Names names, Symtab syms, Types types, Log log, Lint lint) { 193 this.names = names; 194 this.syms = syms; 195 this.types = types; 196 this.log = log; 197 this.lint = lint; 198 } 199 200 // 201 // Main method 202 // 203 204 public void analyzeTree(Env<AttrContext> env) { 205 206 // Sanity check 207 Assert.check(checkInvariants(false, false)); 208 Assert.check(methodMap.isEmpty()); // we are not prepared to be used more than once 209 210 // Short circuit if warnings are totally disabled 211 if (!lint.isEnabled(Lint.LintCategory.THIS_ESCAPE)) 212 return; 213 214 // Determine which packages are exported by the containing module, if any. 215 // A null set indicates the unnamed module: all packages are implicitly exported. 216 Set<PackageSymbol> exportedPackages = Optional.ofNullable(env.toplevel.modle) 217 .filter(mod -> mod != syms.noModule) 218 .filter(mod -> mod != syms.unnamedModule) 219 .map(mod -> mod.exports.stream() 220 .map(Directive.ExportsDirective::getPackage) 221 .collect(Collectors.toSet())) 222 .orElse(null); 223 224 // Build a set of symbols for classes declared in this file 225 final Set<Symbol> classSyms = new HashSet<>(); 226 new TreeScanner() { 227 @Override 228 public void visitClassDef(JCClassDecl tree) { 229 classSyms.add(tree.sym); 230 super.visitClassDef(tree); 231 } 232 }.scan(env.tree); 233 234 // Build a mapping from symbols of methods to their declarations. 235 // Classify all ctors and methods as analyzable and/or invokable. 236 // Track which constructors and fields have warnings suppressed. 237 new TreeScanner() { 238 239 private Lint lint = ThisEscapeAnalyzer.this.lint; 240 private JCClassDecl currentClass; 241 private boolean nonPublicOuter; 242 243 @Override 244 public void visitClassDef(JCClassDecl tree) { 245 JCClassDecl currentClassPrev = currentClass; 246 boolean nonPublicOuterPrev = nonPublicOuter; 247 Lint lintPrev = lint; 248 lint = lint.augment(tree.sym); 249 try { 250 currentClass = tree; 251 nonPublicOuter |= tree.sym.isAnonymous(); 252 nonPublicOuter |= (tree.mods.flags & Flags.PUBLIC) == 0; 253 254 // Recurse 255 super.visitClassDef(tree); 256 } finally { 257 currentClass = currentClassPrev; 258 nonPublicOuter = nonPublicOuterPrev; 259 lint = lintPrev; 260 } 261 } 262 263 @Override 264 public void visitVarDef(JCVariableDecl tree) { 265 Lint lintPrev = lint; 266 lint = lint.augment(tree.sym); 267 try { 268 269 // Track warning suppression of fields 270 if (tree.sym.owner.kind == TYP && !lint.isEnabled(Lint.LintCategory.THIS_ESCAPE)) 271 suppressed.add(tree.sym); 272 273 // Recurse 274 super.visitVarDef(tree); 275 } finally { 276 lint = lintPrev; 277 } 278 } 279 280 @Override 281 public void visitMethodDef(JCMethodDecl tree) { 282 Lint lintPrev = lint; 283 lint = lint.augment(tree.sym); 284 try { 285 286 // Track warning suppression of constructors 287 if (TreeInfo.isConstructor(tree) && !lint.isEnabled(Lint.LintCategory.THIS_ESCAPE)) 288 suppressed.add(tree.sym); 289 290 // Determine if this is a constructor we should analyze 291 boolean extendable = currentClassIsExternallyExtendable(); 292 boolean analyzable = extendable && 293 TreeInfo.isConstructor(tree) && 294 (tree.sym.flags() & (Flags.PUBLIC | Flags.PROTECTED)) != 0 && 295 !suppressed.contains(tree.sym); 296 297 // Determine if this method is "invokable" in an analysis (can't be overridden) 298 boolean invokable = !extendable || 299 TreeInfo.isConstructor(tree) || 300 (tree.mods.flags & (Flags.STATIC | Flags.PRIVATE | Flags.FINAL)) != 0; 301 302 // Add method or constructor to map 303 methodMap.put(tree.sym, new MethodInfo(currentClass, tree, analyzable, invokable)); 304 305 // Recurse 306 super.visitMethodDef(tree); 307 } finally { 308 lint = lintPrev; 309 } 310 } 311 312 // Determines if the current class could be extended in some other package/module 313 private boolean currentClassIsExternallyExtendable() { 314 return !currentClass.sym.isFinal() && 315 currentClass.sym.isPublic() && 316 (exportedPackages == null || exportedPackages.contains(currentClass.sym.packge())) && 317 !currentClass.sym.isSealed() && 318 !currentClass.sym.isDirectlyOrIndirectlyLocal() && 319 !nonPublicOuter; 320 } 321 }.scan(env.tree); 322 323 // Analyze non-static field initializers and initialization blocks, 324 // but only for classes having at least one analyzable constructor. 325 methodMap.values().stream() 326 .filter(MethodInfo::analyzable) 327 .map(MethodInfo::declaringClass) 328 .distinct() 329 .forEach(klass -> { 330 for (List<JCTree> defs = klass.defs; defs.nonEmpty(); defs = defs.tail) { 331 332 // Ignore static stuff 333 if ((TreeInfo.flags(defs.head) & Flags.STATIC) != 0) 334 continue; 335 336 // Handle field initializers 337 if (defs.head instanceof JCVariableDecl vardef) { 338 visitTopLevel(klass, () -> { 339 scan(vardef); 340 copyPendingWarning(); 341 }); 342 continue; 343 } 344 345 // Handle initialization blocks 346 if (defs.head instanceof JCBlock block) { 347 visitTopLevel(klass, () -> analyzeStatements(block.stats)); 348 continue; 349 } 350 } 351 }); 352 353 // Analyze all of the analyzable constructors we found 354 methodMap.values().stream() 355 .filter(MethodInfo::analyzable) 356 .forEach(methodInfo -> { 357 visitTopLevel(methodInfo.declaringClass(), 358 () -> analyzeStatements(methodInfo.declaration().body.stats)); 359 }); 360 361 // Eliminate duplicate warnings. Warning B duplicates warning A if the stack trace of A is a prefix 362 // of the stack trace of B. For example, if constructor Foo(int x) has a leak, and constructor 363 // Foo() invokes this(0), then emitting a warning for Foo() would be redundant. 364 BiPredicate<DiagnosticPosition[], DiagnosticPosition[]> extendsAsPrefix = (warning1, warning2) -> { 365 if (warning2.length < warning1.length) 366 return false; 367 for (int index = 0; index < warning1.length; index++) { 368 if (warning2[index].getPreferredPosition() != warning1[index].getPreferredPosition()) 369 return false; 370 } 371 return true; 372 }; 373 374 // Stack traces are ordered top to bottom, and so duplicates always have the same first element(s). 375 // Sort the stack traces lexicographically, so that duplicates immediately follow what they duplicate. 376 Comparator<DiagnosticPosition[]> ordering = (warning1, warning2) -> { 377 for (int index1 = 0, index2 = 0; true; index1++, index2++) { 378 boolean end1 = index1 >= warning1.length; 379 boolean end2 = index2 >= warning2.length; 380 if (end1 && end2) 381 return 0; 382 if (end1) 383 return -1; 384 if (end2) 385 return 1; 386 int posn1 = warning1[index1].getPreferredPosition(); 387 int posn2 = warning2[index2].getPreferredPosition(); 388 int diff = Integer.compare(posn1, posn2); 389 if (diff != 0) 390 return diff; 391 } 392 }; 393 warningList.sort(ordering); 394 395 // Now emit the warnings, but skipping over duplicates as we go through the list 396 DiagnosticPosition[] previous = null; 397 for (DiagnosticPosition[] warning : warningList) { 398 399 // Skip duplicates 400 if (previous != null && extendsAsPrefix.test(previous, warning)) 401 continue; 402 previous = warning; 403 404 // Emit warnings showing the entire stack trace 405 JCDiagnostic.Warning key = Warnings.PossibleThisEscape; 406 int remain = warning.length; 407 do { 408 DiagnosticPosition pos = warning[--remain]; 409 log.warning(Lint.LintCategory.THIS_ESCAPE, pos, key); 410 key = Warnings.PossibleThisEscapeLocation; 411 } while (remain > 0); 412 } 413 warningList.clear(); 414 } 415 416 // Analyze statements, but stop at (and record) the first warning generated 417 private void analyzeStatements(List<JCStatement> stats) { 418 for (JCStatement stat : stats) { 419 scan(stat); 420 if (copyPendingWarning()) 421 break; 422 } 423 } 424 425 @Override 426 public void scan(JCTree tree) { 427 428 // Check node 429 if (tree == null || tree.type == Type.stuckType) 430 return; 431 432 // Sanity check 433 Assert.check(checkInvariants(true, false)); 434 435 // Can this expression node possibly leave a 'this' reference on the stack? 436 boolean referenceExpressionNode; 437 switch (tree.getTag()) { 438 case SWITCH_EXPRESSION: 439 case CONDEXPR: 440 case YIELD: 441 case APPLY: 442 case NEWCLASS: 443 case NEWARRAY: 444 case LAMBDA: 445 case PARENS: 446 case ASSIGN: 447 case TYPECAST: 448 case INDEXED: 449 case SELECT: 450 case REFERENCE: 451 case IDENT: 452 case NULLCHK: 453 case LETEXPR: 454 referenceExpressionNode = true; 455 break; 456 default: 457 referenceExpressionNode = false; 458 break; 459 } 460 461 // Scan node 462 super.scan(tree); 463 464 // Sanity check 465 Assert.check(checkInvariants(true, referenceExpressionNode)); 466 } 467 468 // 469 // Visitor methods - Class Declarations 470 // 471 472 @Override 473 public void visitClassDef(JCClassDecl tree) { 474 return; // we're busy analyzing another class - skip 475 } 476 477 // 478 // Visitor methods - Variable Declarations 479 // 480 481 @Override 482 public void visitVarDef(JCVariableDecl tree) { 483 484 // Skip if ignoring warnings for this field 485 if (suppressed.contains(tree.sym)) 486 return; 487 488 // Scan initializer, if any 489 scan(tree.init); 490 if (isParamOrVar(tree.sym)) 491 refs.replaceExprs(depth, direct -> new VarRef(tree.sym, direct)); 492 else 493 refs.discardExprs(depth); // we don't track fields yet 494 } 495 496 // 497 // Visitor methods - Methods 498 // 499 500 @Override 501 public void visitMethodDef(JCMethodDecl tree) { 502 Assert.check(false); // we should never get here 503 } 504 505 @Override 506 public void visitApply(JCMethodInvocation invoke) { 507 508 // Get method symbol 509 Symbol sym = TreeInfo.symbolFor(invoke.meth); 510 511 // Recurse on method expression 512 scan(invoke.meth); 513 boolean direct = refs.remove(ExprRef.direct(depth)); 514 boolean indirect = refs.remove(ExprRef.indirect(depth)); 515 516 // Determine if method receiver represents a possible reference 517 RefSet<ThisRef> receiverRefs = RefSet.newEmpty(); 518 if (sym != null && !sym.isStatic()) { 519 if (direct) 520 receiverRefs.add(ThisRef.direct()); 521 if (indirect) 522 receiverRefs.add(ThisRef.indirect()); 523 } 524 525 // If "super()": ignore - we don't try to track into superclasses 526 if (TreeInfo.name(invoke.meth) == names._super) 527 return; 528 529 // "Invoke" the method 530 invoke(invoke, sym, invoke.args, receiverRefs); 531 } 532 533 private void invoke(JCTree site, Symbol sym, List<JCExpression> args, RefSet<?> receiverRefs) { 534 535 // Skip if ignoring warnings for a constructor invoked via 'this()' 536 if (suppressed.contains(sym)) 537 return; 538 539 // Ignore final methods in java.lang.Object (getClass(), notify(), etc.) 540 if (sym != null && 541 sym.owner.kind == TYP && 542 sym.owner.type.tsym == syms.objectType.tsym && 543 sym.isFinal()) { 544 return; 545 } 546 547 // Analyze method if possible, otherwise assume nothing 548 MethodInfo methodInfo = methodMap.get(sym); 549 if (methodInfo != null && methodInfo.invokable()) 550 invokeInvokable(site, args, receiverRefs, methodInfo); 551 else 552 invokeUnknown(site, args, receiverRefs); 553 } 554 555 // Handle the invocation of a local analyzable method or constructor 556 private void invokeInvokable(JCTree site, List<JCExpression> args, 557 RefSet<?> receiverRefs, MethodInfo methodInfo) { 558 Assert.check(methodInfo.invokable()); 559 560 // Collect 'this' references found in method parameters 561 JCMethodDecl method = methodInfo.declaration(); 562 RefSet<VarRef> paramRefs = RefSet.newEmpty(); 563 List<JCVariableDecl> params = method.params; 564 while (args.nonEmpty() && params.nonEmpty()) { 565 VarSymbol sym = params.head.sym; 566 scan(args.head); 567 refs.removeExprs(depth, direct -> paramRefs.add(new VarRef(sym, direct))); 568 args = args.tail; 569 params = params.tail; 570 } 571 572 // "Invoke" the method 573 JCClassDecl methodClassPrev = methodClass; 574 methodClass = methodInfo.declaringClass(); 575 RefSet<Ref> refsPrev = refs; 576 refs = RefSet.newEmpty(); 577 int depthPrev = depth; 578 depth = 0; 579 callStack.push(site); 580 try { 581 582 // Add initial references from method receiver 583 refs.addAll(receiverRefs); 584 585 // Add initial references from parameters 586 refs.addAll(paramRefs); 587 588 // Stop trivial cases here 589 if (refs.isEmpty()) 590 return; 591 592 // Stop infinite recursion here 593 Pair<JCMethodDecl, RefSet<Ref>> invocation = Pair.of(methodInfo.declaration, refs.clone()); 594 if (!invocations.add(invocation)) 595 return; 596 597 // Scan method body to "execute" it 598 try { 599 scan(method.body); 600 } finally { 601 invocations.remove(invocation); 602 } 603 604 // "Return" any references from method return value 605 refs.mapInto(refsPrev, ReturnRef.class, direct -> new ExprRef(depthPrev, direct)); 606 } finally { 607 callStack.pop(); 608 depth = depthPrev; 609 refs = refsPrev; 610 methodClass = methodClassPrev; 611 } 612 } 613 614 // Handle invocation of an unknown or overridable method or constructor 615 private void invokeUnknown(JCTree invoke, List<JCExpression> args, RefSet<?> receiverRefs) { 616 617 // Detect leak via receiver 618 if (!receiverRefs.isEmpty()) 619 leakAt(invoke); 620 621 // Detect leaks via method parameters 622 for (JCExpression arg : args) { 623 scan(arg); 624 if (refs.discardExprs(depth)) 625 leakAt(arg); 626 } 627 } 628 629 // 630 // Visitor methods - new Foo() 631 // 632 633 @Override 634 public void visitNewClass(JCNewClass tree) { 635 MethodInfo methodInfo = methodMap.get(tree.constructor); 636 if (methodInfo != null && methodInfo.invokable()) 637 invokeInvokable(tree, tree.args, outerThisRefs(tree.encl, tree.clazz.type), methodInfo); 638 else 639 invokeUnknown(tree, tree.args, outerThisRefs(tree.encl, tree.clazz.type)); 640 } 641 642 // Determine 'this' references passed to a constructor via the outer 'this' instance 643 private RefSet<OuterRef> outerThisRefs(JCExpression explicitOuterThis, Type type) { 644 RefSet<OuterRef> outerRefs = RefSet.newEmpty(); 645 if (explicitOuterThis != null) { 646 scan(explicitOuterThis); 647 refs.removeExprs(depth, direct -> outerRefs.add(new OuterRef(direct))); 648 } else if (type.tsym != methodClass.sym && type.tsym.isEnclosedBy(methodClass.sym)) { 649 refs.mapInto(outerRefs, ThisRef.class, OuterRef::new); 650 } 651 return outerRefs; 652 } 653 654 // 655 // Visitor methods - Codey Bits 656 // 657 658 @Override 659 public void visitBlock(JCBlock tree) { 660 visitScoped(false, () -> super.visitBlock(tree)); 661 Assert.check(checkInvariants(true, false)); 662 } 663 664 @Override 665 public void visitDoLoop(JCDoWhileLoop tree) { 666 visitLooped(tree, super::visitDoLoop); 667 } 668 669 @Override 670 public void visitWhileLoop(JCWhileLoop tree) { 671 visitLooped(tree, super::visitWhileLoop); 672 } 673 674 @Override 675 public void visitForLoop(JCForLoop tree) { 676 visitLooped(tree, super::visitForLoop); 677 } 678 679 @Override 680 public void visitForeachLoop(JCEnhancedForLoop tree) { 681 visitLooped(tree, foreach -> { 682 scan(foreach.expr); 683 refs.discardExprs(depth); // we don't handle iterator() yet 684 scan(foreach.body); 685 }); 686 } 687 688 @Override 689 public void visitSwitch(JCSwitch tree) { 690 visitScoped(false, () -> { 691 scan(tree.selector); 692 refs.discardExprs(depth); 693 scan(tree.cases); 694 }); 695 } 696 697 @Override 698 public void visitSwitchExpression(JCSwitchExpression tree) { 699 visitScoped(true, () -> { 700 scan(tree.selector); 701 refs.discardExprs(depth); 702 RefSet<ExprRef> combinedRefs = new RefSet<>(); 703 for (List<JCCase> cases = tree.cases; cases.nonEmpty(); cases = cases.tail) { 704 scan(cases.head.stats); 705 refs.replace(YieldRef.class, direct -> new ExprRef(depth, direct)); 706 combinedRefs.addAll(refs.removeExprs(depth)); 707 } 708 refs.addAll(combinedRefs); 709 }); 710 } 711 712 @Override 713 public void visitCase(JCCase tree) { 714 scan(tree.stats); // no need to scan labels 715 } 716 717 @Override 718 public void visitYield(JCYield tree) { 719 scan(tree.value); 720 refs.replaceExprs(depth, YieldRef::new); 721 } 722 723 @Override 724 public void visitLetExpr(LetExpr tree) { 725 visitScoped(true, () -> super.visitLetExpr(tree)); 726 } 727 728 @Override 729 public void visitReturn(JCReturn tree) { 730 scan(tree.expr); 731 refs.replaceExprs(depth, ReturnRef::new); 732 } 733 734 @Override 735 public void visitLambda(JCLambda lambda) { 736 visitDeferred(() -> visitScoped(false, () -> { 737 scan(lambda.body); 738 refs.discardExprs(depth); // needed in case body is a JCExpression 739 })); 740 } 741 742 @Override 743 public void visitAssign(JCAssign tree) { 744 scan(tree.lhs); 745 refs.discardExprs(depth); 746 scan(tree.rhs); 747 VarSymbol sym = (VarSymbol)TreeInfo.symbolFor(tree.lhs); 748 if (isParamOrVar(sym)) 749 refs.replaceExprs(depth, direct -> new VarRef(sym, direct)); 750 else 751 refs.discardExprs(depth); // we don't track fields yet 752 } 753 754 @Override 755 public void visitIndexed(JCArrayAccess tree) { 756 scan(tree.indexed); 757 refs.remove(ExprRef.direct(depth)); 758 boolean indirectRef = refs.remove(ExprRef.indirect(depth)); 759 scan(tree.index); 760 refs.discardExprs(depth); 761 if (indirectRef) { 762 refs.add(ExprRef.direct(depth)); 763 refs.add(ExprRef.indirect(depth)); 764 } 765 } 766 767 @Override 768 public void visitSelect(JCFieldAccess tree) { 769 770 // Scan the selected thing 771 scan(tree.selected); 772 boolean selectedDirectRef = refs.remove(ExprRef.direct(depth)); 773 boolean selectedIndirectRef = refs.remove(ExprRef.indirect(depth)); 774 775 // Explicit 'this' reference? 776 Type.ClassType currentClassType = (Type.ClassType)methodClass.sym.type; 777 if (isExplicitThisReference(types, currentClassType, tree)) { 778 refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct)); 779 return; 780 } 781 782 // Explicit outer 'this' reference? 783 Type selectedType = types.erasure(tree.selected.type); 784 if (selectedType.hasTag(CLASS)) { 785 ClassSymbol currentClassSym = (ClassSymbol)currentClassType.tsym; 786 ClassSymbol selectedTypeSym = (ClassSymbol)selectedType.tsym; 787 if (tree.name == names._this && 788 selectedTypeSym != currentClassSym && 789 currentClassSym.isEnclosedBy(selectedTypeSym)) { 790 refs.mapInto(refs, OuterRef.class, direct -> new ExprRef(depth, direct)); 791 return; 792 } 793 } 794 795 // Methods - the "value" of a non-static method is a reference to its instance 796 Symbol sym = tree.sym; 797 if (sym.kind == MTH) { 798 if ((sym.flags() & Flags.STATIC) == 0) { 799 if (selectedDirectRef) 800 refs.add(ExprRef.direct(depth)); 801 if (selectedIndirectRef) 802 refs.add(ExprRef.indirect(depth)); 803 } 804 return; 805 } 806 807 // Unknown 808 return; 809 } 810 811 @Override 812 public void visitReference(JCMemberReference tree) { 813 if (tree.type.isErroneous()) { 814 //error recovery - ignore erroneous member references 815 return ; 816 } 817 818 // Scan target expression and extract 'this' references, if any 819 scan(tree.expr); 820 boolean direct = refs.remove(ExprRef.direct(depth)); 821 boolean indirect = refs.remove(ExprRef.indirect(depth)); 822 823 // Gather receiver references for deferred invocation 824 RefSet<Ref> receiverRefs = RefSet.newEmpty(); 825 switch (tree.kind) { 826 case UNBOUND: 827 case STATIC: 828 case TOPLEVEL: 829 case ARRAY_CTOR: 830 return; 831 case SUPER: 832 refs.mapInto(receiverRefs, ThisRef.class, ThisRef::new); 833 break; 834 case BOUND: 835 if (direct) 836 receiverRefs.add(ThisRef.direct()); 837 if (indirect) 838 receiverRefs.add(ThisRef.indirect()); 839 break; 840 case IMPLICIT_INNER: 841 receiverRefs.addAll(outerThisRefs(null, tree.expr.type)); 842 break; 843 default: 844 throw new RuntimeException("non-exhaustive?"); 845 } 846 847 // Treat method reference just like the equivalent lambda 848 visitDeferred(() -> invoke(tree, (MethodSymbol)tree.sym, List.nil(), receiverRefs)); 849 } 850 851 @Override 852 public void visitIdent(JCIdent tree) { 853 854 // Reference to this? 855 if (tree.name == names._this || tree.name == names._super) { 856 refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct)); 857 return; 858 } 859 860 // Parameter or local variable? 861 if (isParamOrVar(tree.sym)) { 862 VarSymbol sym = (VarSymbol)tree.sym; 863 if (refs.contains(VarRef.direct(sym))) 864 refs.add(ExprRef.direct(depth)); 865 if (refs.contains(VarRef.indirect(sym))) 866 refs.add(ExprRef.indirect(depth)); 867 return; 868 } 869 870 // An unqualified, non-static method invocation must reference 'this' or outer 'this'. 871 // The "value" of a non-static method is a reference to its instance. 872 if (tree.sym.kind == MTH && (tree.sym.flags() & Flags.STATIC) == 0) { 873 MethodSymbol sym = (MethodSymbol)tree.sym; 874 875 // Check for implicit 'this' reference 876 ClassSymbol methodClassSym = methodClass.sym; 877 if (methodClassSym.isSubClass(sym.owner, types)) { 878 refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct)); 879 return; 880 } 881 882 // Check for implicit outer 'this' reference 883 if (methodClassSym.isEnclosedBy((ClassSymbol)sym.owner)) { 884 refs.mapInto(refs, OuterRef.class, direct -> new ExprRef(depth, direct)); 885 return; 886 } 887 888 // What could it be? 889 //Assert.check(false); 890 return; 891 } 892 893 // Unknown 894 return; 895 } 896 897 @Override 898 public void visitSynchronized(JCSynchronized tree) { 899 scan(tree.lock); 900 refs.discardExprs(depth); 901 scan(tree.body); 902 } 903 904 @Override 905 public void visitConditional(JCConditional tree) { 906 scan(tree.cond); 907 refs.discardExprs(depth); 908 RefSet<ExprRef> combinedRefs = new RefSet<>(); 909 scan(tree.truepart); 910 combinedRefs.addAll(refs.removeExprs(depth)); 911 scan(tree.falsepart); 912 combinedRefs.addAll(refs.removeExprs(depth)); 913 refs.addAll(combinedRefs); 914 } 915 916 @Override 917 public void visitIf(JCIf tree) { 918 scan(tree.cond); 919 refs.discardExprs(depth); 920 scan(tree.thenpart); 921 scan(tree.elsepart); 922 } 923 924 @Override 925 public void visitExec(JCExpressionStatement tree) { 926 scan(tree.expr); 927 refs.discardExprs(depth); 928 } 929 930 @Override 931 public void visitThrow(JCThrow tree) { 932 scan(tree.expr); 933 if (refs.discardExprs(depth)) // we don't try to "catch" refs from thrown exceptions 934 leakAt(tree); 935 } 936 937 @Override 938 public void visitAssert(JCAssert tree) { 939 scan(tree.cond); 940 refs.discardExprs(depth); 941 scan(tree.detail); 942 refs.discardExprs(depth); 943 } 944 945 @Override 946 public void visitNewArray(JCNewArray tree) { 947 boolean ref = false; 948 if (tree.elems != null) { 949 for (List<JCExpression> elems = tree.elems; elems.nonEmpty(); elems = elems.tail) { 950 scan(elems.head); 951 ref |= refs.discardExprs(depth); 952 } 953 } 954 if (ref) 955 refs.add(ExprRef.indirect(depth)); 956 } 957 958 @Override 959 public void visitTypeCast(JCTypeCast tree) { 960 scan(tree.expr); 961 } 962 963 @Override 964 public void visitConstantCaseLabel(JCConstantCaseLabel tree) { 965 } 966 967 @Override 968 public void visitPatternCaseLabel(JCPatternCaseLabel tree) { 969 } 970 971 @Override 972 public void visitRecordPattern(JCRecordPattern that) { 973 } 974 975 @Override 976 public void visitTypeTest(JCInstanceOf tree) { 977 scan(tree.expr); 978 refs.discardExprs(depth); 979 } 980 981 @Override 982 public void visitTypeArray(JCArrayTypeTree tree) { 983 } 984 985 @Override 986 public void visitTypeApply(JCTypeApply tree) { 987 } 988 989 @Override 990 public void visitTypeUnion(JCTypeUnion tree) { 991 } 992 993 @Override 994 public void visitTypeIntersection(JCTypeIntersection tree) { 995 } 996 997 @Override 998 public void visitTypeParameter(JCTypeParameter tree) { 999 } 1000 1001 @Override 1002 public void visitWildcard(JCWildcard tree) { 1003 } 1004 1005 @Override 1006 public void visitTypeBoundKind(TypeBoundKind that) { 1007 } 1008 1009 @Override 1010 public void visitModifiers(JCModifiers tree) { 1011 } 1012 1013 @Override 1014 public void visitAnnotation(JCAnnotation tree) { 1015 } 1016 1017 @Override 1018 public void visitAnnotatedType(JCAnnotatedType tree) { 1019 } 1020 1021 // 1022 // Visitor methods - Non-Reference Stuff 1023 // 1024 1025 @Override 1026 public void visitAssignop(JCAssignOp tree) { 1027 scan(tree.lhs); 1028 refs.discardExprs(depth); 1029 scan(tree.rhs); 1030 refs.discardExprs(depth); 1031 } 1032 1033 @Override 1034 public void visitUnary(JCUnary tree) { 1035 scan(tree.arg); 1036 refs.discardExprs(depth); 1037 } 1038 1039 @Override 1040 public void visitBinary(JCBinary tree) { 1041 scan(tree.lhs); 1042 refs.discardExprs(depth); 1043 scan(tree.rhs); 1044 refs.discardExprs(depth); 1045 } 1046 1047 // Helper methods 1048 1049 private void visitTopLevel(JCClassDecl klass, Runnable action) { 1050 Assert.check(targetClass == null); 1051 Assert.check(methodClass == null); 1052 Assert.check(depth == -1); 1053 Assert.check(refs == null); 1054 targetClass = klass; 1055 methodClass = klass; 1056 try { 1057 1058 // Add the initial 'this' reference 1059 refs = RefSet.newEmpty(); 1060 refs.add(ThisRef.direct()); 1061 1062 // Perform action 1063 this.visitScoped(false, action); 1064 } finally { 1065 Assert.check(depth == -1); 1066 methodClass = null; 1067 targetClass = null; 1068 refs = null; 1069 } 1070 } 1071 1072 // Recurse through indirect code that might get executed later, e.g., a lambda. 1073 // We stash any pending warning and the current RefSet, then recurse into the deferred 1074 // code (still using the current RefSet) to see if it would leak. Then we restore the 1075 // pending warning and the current RefSet. Finally, if the deferred code would have 1076 // leaked, we create an indirect ExprRef because it must be holding a 'this' reference. 1077 // If the deferred code would not leak, then obviously no leak is possible, period. 1078 private <T extends JCTree> void visitDeferred(Runnable recurse) { 1079 DiagnosticPosition[] pendingWarningPrev = pendingWarning; 1080 pendingWarning = null; 1081 RefSet<Ref> refsPrev = refs.clone(); 1082 boolean deferredCodeLeaks; 1083 try { 1084 recurse.run(); 1085 deferredCodeLeaks = pendingWarning != null; 1086 } finally { 1087 refs = refsPrev; 1088 pendingWarning = pendingWarningPrev; 1089 } 1090 if (deferredCodeLeaks) 1091 refs.add(ExprRef.indirect(depth)); 1092 } 1093 1094 // Repeat loop as needed until the current set of references converges 1095 private <T extends JCTree> void visitLooped(T tree, Consumer<T> visitor) { 1096 visitScoped(false, () -> { 1097 while (true) { 1098 RefSet<Ref> prevRefs = refs.clone(); 1099 visitor.accept(tree); 1100 if (refs.equals(prevRefs)) 1101 break; 1102 } 1103 }); 1104 } 1105 1106 // Perform the given action within a new scope 1107 private void visitScoped(boolean promote, Runnable action) { 1108 pushScope(); 1109 try { 1110 1111 // Perform action 1112 Assert.check(checkInvariants(true, false)); 1113 action.run(); 1114 Assert.check(checkInvariants(true, promote)); 1115 1116 // "Promote" ExprRef's to the enclosing lexical scope, if requested 1117 if (promote) { 1118 Assert.check(depth > 0); 1119 refs.removeExprs(depth, direct -> refs.add(new ExprRef(depth - 1, direct))); 1120 } 1121 } finally { 1122 popScope(); 1123 } 1124 } 1125 1126 private void pushScope() { 1127 depth++; 1128 } 1129 1130 private void popScope() { 1131 Assert.check(depth >= 0); 1132 depth--; 1133 refs.removeIf(ref -> ref.getDepth() > depth); 1134 } 1135 1136 // Note a possible 'this' reference leak at the specified location 1137 private void leakAt(JCTree tree) { 1138 1139 // Generate at most one warning per statement 1140 if (pendingWarning != null) 1141 return; 1142 1143 // Snapshot the current stack trace 1144 callStack.push(tree.pos()); 1145 pendingWarning = callStack.toArray(new DiagnosticPosition[0]); 1146 callStack.pop(); 1147 } 1148 1149 // Copy pending warning, if any, to the warning list and reset 1150 private boolean copyPendingWarning() { 1151 if (pendingWarning == null) 1152 return false; 1153 warningList.add(pendingWarning); 1154 pendingWarning = null; 1155 return true; 1156 } 1157 1158 // Does the symbol correspond to a parameter or local variable (not a field)? 1159 private boolean isParamOrVar(Symbol sym) { 1160 return sym != null && 1161 sym.kind == VAR && 1162 (sym.owner.kind == MTH || sym.owner.kind == VAR); 1163 } 1164 1165 /** Check if the given tree is an explicit reference to the 'this' instance of the 1166 * class currently being compiled. This is true if tree is: 1167 * - An unqualified 'this' identifier 1168 * - A 'super' identifier qualified by a class name whose type is 'currentClass' or a supertype 1169 * - A 'this' identifier qualified by a class name whose type is 'currentClass' or a supertype 1170 * but also NOT an enclosing outer class of 'currentClass'. 1171 */ 1172 private boolean isExplicitThisReference(Types types, Type.ClassType currentClass, JCTree tree) { 1173 switch (tree.getTag()) { 1174 case PARENS: 1175 return isExplicitThisReference(types, currentClass, TreeInfo.skipParens(tree)); 1176 case IDENT: 1177 { 1178 JCIdent ident = (JCIdent)tree; 1179 Names names = ident.name.table.names; 1180 return ident.name == names._this; 1181 } 1182 case SELECT: 1183 { 1184 JCFieldAccess select = (JCFieldAccess)tree; 1185 Type selectedType = types.erasure(select.selected.type); 1186 if (!selectedType.hasTag(CLASS)) 1187 return false; 1188 ClassSymbol currentClassSym = (ClassSymbol)((Type.ClassType)types.erasure(currentClass)).tsym; 1189 ClassSymbol selectedClassSym = (ClassSymbol)((Type.ClassType)selectedType).tsym; 1190 Names names = select.name.table.names; 1191 return currentClassSym.isSubClass(selectedClassSym, types) && 1192 (select.name == names._super || 1193 (select.name == names._this && 1194 (currentClassSym == selectedClassSym || 1195 !currentClassSym.isEnclosedBy(selectedClassSym)))); 1196 } 1197 default: 1198 return false; 1199 } 1200 } 1201 1202 // When scanning nodes we can be in one of two modes: 1203 // (a) Looking for constructors - we do not recurse into any code blocks 1204 // (b) Analyzing a constructor - we are tracing its possible execution paths 1205 private boolean isAnalyzing() { 1206 return targetClass != null; 1207 } 1208 1209 // Debugging 1210 1211 // Invariant checks 1212 private boolean checkInvariants(boolean analyzing, boolean allowExpr) { 1213 Assert.check(analyzing == isAnalyzing()); 1214 if (isAnalyzing()) { 1215 Assert.check(methodClass != null); 1216 Assert.check(targetClass != null); 1217 Assert.check(refs != null); 1218 Assert.check(depth >= 0); 1219 Assert.check(refs.stream().noneMatch(ref -> ref.getDepth() > depth)); 1220 Assert.check(allowExpr || !refs.contains(ExprRef.direct(depth))); 1221 Assert.check(allowExpr || !refs.contains(ExprRef.indirect(depth))); 1222 } else { 1223 Assert.check(targetClass == null); 1224 Assert.check(refs == null); 1225 Assert.check(depth == -1); 1226 Assert.check(callStack.isEmpty()); 1227 Assert.check(pendingWarning == null); 1228 Assert.check(invocations.isEmpty()); 1229 } 1230 return true; 1231 } 1232 1233 // Ref's 1234 1235 /** Represents a location that could possibly hold a 'this' reference. 1236 * 1237 * <p> 1238 * If not "direct", the reference is found through at least one indirection. 1239 */ 1240 private abstract static class Ref { 1241 1242 private final int depth; 1243 private final boolean direct; 1244 1245 Ref(int depth, boolean direct) { 1246 this.depth = depth; 1247 this.direct = direct; 1248 } 1249 1250 public int getDepth() { 1251 return depth; 1252 } 1253 1254 public boolean isDirect() { 1255 return direct; 1256 } 1257 1258 @Override 1259 public int hashCode() { 1260 return getClass().hashCode() 1261 ^ Integer.hashCode(depth) 1262 ^ Boolean.hashCode(direct); 1263 } 1264 1265 @Override 1266 public boolean equals(Object obj) { 1267 if (obj == this) 1268 return true; 1269 if (obj == null || obj.getClass() != getClass()) 1270 return false; 1271 Ref that = (Ref)obj; 1272 return depth == that.depth 1273 && direct == that.direct; 1274 } 1275 1276 @Override 1277 public String toString() { 1278 ArrayList<String> properties = new ArrayList<>(); 1279 addProperties(properties); 1280 return getClass().getSimpleName() 1281 + "[" + properties.stream().collect(Collectors.joining(",")) + "]"; 1282 } 1283 1284 protected void addProperties(ArrayList<String> properties) { 1285 properties.add("depth=" + depth); 1286 properties.add(direct ? "direct" : "indirect"); 1287 } 1288 } 1289 1290 /** A reference from the current 'this' instance. 1291 */ 1292 private static class ThisRef extends Ref { 1293 1294 ThisRef(boolean direct) { 1295 super(0, direct); 1296 } 1297 1298 public static ThisRef direct() { 1299 return new ThisRef(true); 1300 } 1301 1302 public static ThisRef indirect() { 1303 return new ThisRef(false); 1304 } 1305 } 1306 1307 /** A reference from the current outer 'this' instance. 1308 */ 1309 private static class OuterRef extends Ref { 1310 1311 OuterRef(boolean direct) { 1312 super(0, direct); 1313 } 1314 } 1315 1316 /** A reference from the expression that was just evaluated. 1317 * In other words, a reference that's sitting on top of the stack. 1318 */ 1319 private static class ExprRef extends Ref { 1320 1321 ExprRef(int depth, boolean direct) { 1322 super(depth, direct); 1323 } 1324 1325 public static ExprRef direct(int depth) { 1326 return new ExprRef(depth, true); 1327 } 1328 1329 public static ExprRef indirect(int depth) { 1330 return new ExprRef(depth, false); 1331 } 1332 } 1333 1334 /** A reference from the return value of the current method being "invoked". 1335 */ 1336 private static class ReturnRef extends Ref { 1337 1338 ReturnRef(boolean direct) { 1339 super(0, direct); 1340 } 1341 } 1342 1343 /** A reference from the yield value of the current switch expression. 1344 */ 1345 private static class YieldRef extends Ref { 1346 1347 YieldRef(boolean direct) { 1348 super(0, direct); 1349 } 1350 } 1351 1352 /** A reference from a variable. 1353 */ 1354 private static class VarRef extends Ref { 1355 1356 private final VarSymbol sym; 1357 1358 VarRef(VarSymbol sym, boolean direct) { 1359 super(0, direct); 1360 this.sym = sym; 1361 } 1362 1363 public VarSymbol getSymbol() { 1364 return sym; 1365 } 1366 1367 public static VarRef direct(VarSymbol sym) { 1368 return new VarRef(sym, true); 1369 } 1370 1371 public static VarRef indirect(VarSymbol sym) { 1372 return new VarRef(sym, false); 1373 } 1374 1375 @Override 1376 public int hashCode() { 1377 return super.hashCode() 1378 ^ Objects.hashCode(sym); 1379 } 1380 1381 @Override 1382 public boolean equals(Object obj) { 1383 if (obj == this) 1384 return true; 1385 if (!super.equals(obj)) 1386 return false; 1387 VarRef that = (VarRef)obj; 1388 return Objects.equals(sym, that.sym); 1389 } 1390 1391 @Override 1392 protected void addProperties(ArrayList<String> properties) { 1393 super.addProperties(properties); 1394 properties.add("sym=" + sym); 1395 } 1396 } 1397 1398 // RefSet 1399 1400 /** Contains locations currently known to hold a possible 'this' reference. 1401 */ 1402 @SuppressWarnings("serial") 1403 private static class RefSet<T extends Ref> extends HashSet<T> { 1404 1405 public static <T extends Ref> RefSet<T> newEmpty() { 1406 return new RefSet<>(); 1407 } 1408 1409 /** 1410 * Discard any {@link ExprRef}'s at the specified depth. 1411 * Do this when discarding whatever is on top of the stack. 1412 */ 1413 public boolean discardExprs(int depth) { 1414 return remove(ExprRef.direct(depth)) | remove(ExprRef.indirect(depth)); 1415 } 1416 1417 /** 1418 * Extract any {@link ExprRef}'s at the specified depth. 1419 */ 1420 public RefSet<ExprRef> removeExprs(int depth) { 1421 return Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth)) 1422 .filter(this::remove) 1423 .collect(Collectors.toCollection(RefSet::new)); 1424 } 1425 1426 /** 1427 * Extract any {@link ExprRef}'s at the specified depth and do something with them. 1428 */ 1429 public void removeExprs(int depth, Consumer<? super Boolean> handler) { 1430 Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth)) 1431 .filter(this::remove) 1432 .map(ExprRef::isDirect) 1433 .forEach(handler); 1434 } 1435 1436 /** 1437 * Replace any references of the given type. 1438 */ 1439 public void replace(Class<? extends Ref> type, Function<Boolean, ? extends T> mapper) { 1440 final List<Ref> oldRefs = this.stream() 1441 .filter(type::isInstance) 1442 .collect(List.collector()); // avoid ConcurrentModificationException 1443 this.removeAll(oldRefs); 1444 oldRefs.stream() 1445 .map(Ref::isDirect) 1446 .map(mapper) 1447 .forEach(this::add); 1448 } 1449 1450 /** 1451 * Replace any {@link ExprRef}'s at the specified depth. 1452 */ 1453 public void replaceExprs(int depth, Function<Boolean, ? extends T> mapper) { 1454 removeExprs(depth, direct -> add(mapper.apply(direct))); 1455 } 1456 1457 /** 1458 * Find references of the given type, map them, and add them to {@code dest}. 1459 */ 1460 public <S extends Ref> void mapInto(RefSet<S> dest, Class<? extends Ref> type, 1461 Function<Boolean, ? extends S> mapper) { 1462 final List<S> newRefs = this.stream() 1463 .filter(type::isInstance) 1464 .map(Ref::isDirect) 1465 .map(mapper) 1466 .collect(List.collector()); // avoid ConcurrentModificationException 1467 dest.addAll(newRefs); 1468 } 1469 1470 @Override 1471 @SuppressWarnings("unchecked") 1472 public RefSet<T> clone() { 1473 return (RefSet<T>)super.clone(); 1474 } 1475 } 1476 1477 // MethodInfo 1478 1479 // Information about a constructor or method in the compilation unit 1480 private record MethodInfo( 1481 JCClassDecl declaringClass, // the class declaring "declaration" 1482 JCMethodDecl declaration, // the method or constructor itself 1483 boolean analyzable, // it's a constructor that we should analyze 1484 boolean invokable) { // it may be safely "invoked" during analysis 1485 } 1486 }