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 (TreeInfo.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 
 814         // Scan target expression and extract 'this' references, if any
 815         scan(tree.expr);
 816         boolean direct = refs.remove(ExprRef.direct(depth));
 817         boolean indirect = refs.remove(ExprRef.indirect(depth));
 818 
 819         // Gather receiver references for deferred invocation
 820         RefSet<Ref> receiverRefs = RefSet.newEmpty();
 821         switch (tree.kind) {
 822         case UNBOUND:
 823         case STATIC:
 824         case TOPLEVEL:
 825         case ARRAY_CTOR:
 826             return;
 827         case SUPER:
 828             refs.mapInto(receiverRefs, ThisRef.class, ThisRef::new);
 829             break;
 830         case BOUND:
 831             if (direct)
 832                 receiverRefs.add(ThisRef.direct());
 833             if (indirect)
 834                 receiverRefs.add(ThisRef.indirect());
 835             break;
 836         case IMPLICIT_INNER:
 837             receiverRefs.addAll(outerThisRefs(null, tree.expr.type));
 838             break;
 839         default:
 840             throw new RuntimeException("non-exhaustive?");
 841         }
 842 
 843         // Treat method reference just like the equivalent lambda
 844         visitDeferred(() -> invoke(tree, (MethodSymbol)tree.sym, List.nil(), receiverRefs));
 845     }
 846 
 847     @Override
 848     public void visitIdent(JCIdent tree) {
 849 
 850         // Reference to this?
 851         if (tree.name == names._this || tree.name == names._super) {
 852             refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct));
 853             return;
 854         }
 855 
 856         // Parameter or local variable?
 857         if (isParamOrVar(tree.sym)) {
 858             VarSymbol sym = (VarSymbol)tree.sym;
 859             if (refs.contains(VarRef.direct(sym)))
 860                 refs.add(ExprRef.direct(depth));
 861             if (refs.contains(VarRef.indirect(sym)))
 862                 refs.add(ExprRef.indirect(depth));
 863             return;
 864         }
 865 
 866         // An unqualified, non-static method invocation must reference 'this' or outer 'this'.
 867         // The "value" of a non-static method is a reference to its instance.
 868         if (tree.sym.kind == MTH && (tree.sym.flags() & Flags.STATIC) == 0) {
 869             MethodSymbol sym = (MethodSymbol)tree.sym;
 870 
 871             // Check for implicit 'this' reference
 872             ClassSymbol methodClassSym = methodClass.sym;
 873             if (methodClassSym.isSubClass(sym.owner, types)) {
 874                 refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct));
 875                 return;
 876             }
 877 
 878             // Check for implicit outer 'this' reference
 879             if (methodClassSym.isEnclosedBy((ClassSymbol)sym.owner)) {
 880                 refs.mapInto(refs, OuterRef.class, direct -> new ExprRef(depth, direct));
 881                 return;
 882             }
 883 
 884             // What could it be?
 885             //Assert.check(false);
 886             return;
 887         }
 888 
 889         // Unknown
 890         return;
 891     }
 892 
 893     @Override
 894     public void visitSynchronized(JCSynchronized tree) {
 895         scan(tree.lock);
 896         refs.discardExprs(depth);
 897         scan(tree.body);
 898     }
 899 
 900     @Override
 901     public void visitConditional(JCConditional tree) {
 902         scan(tree.cond);
 903         refs.discardExprs(depth);
 904         RefSet<ExprRef> combinedRefs = new RefSet<>();
 905         scan(tree.truepart);
 906         combinedRefs.addAll(refs.removeExprs(depth));
 907         scan(tree.falsepart);
 908         combinedRefs.addAll(refs.removeExprs(depth));
 909         refs.addAll(combinedRefs);
 910     }
 911 
 912     @Override
 913     public void visitIf(JCIf tree) {
 914         scan(tree.cond);
 915         refs.discardExprs(depth);
 916         scan(tree.thenpart);
 917         scan(tree.elsepart);
 918     }
 919 
 920     @Override
 921     public void visitExec(JCExpressionStatement tree) {
 922         scan(tree.expr);
 923         refs.discardExprs(depth);
 924     }
 925 
 926     @Override
 927     public void visitThrow(JCThrow tree) {
 928         scan(tree.expr);
 929         if (refs.discardExprs(depth))     // we don't try to "catch" refs from thrown exceptions
 930             leakAt(tree);
 931     }
 932 
 933     @Override
 934     public void visitAssert(JCAssert tree) {
 935         scan(tree.cond);
 936         refs.discardExprs(depth);
 937         scan(tree.detail);
 938         refs.discardExprs(depth);
 939     }
 940 
 941     @Override
 942     public void visitNewArray(JCNewArray tree) {
 943         boolean ref = false;
 944         if (tree.elems != null) {
 945             for (List<JCExpression> elems = tree.elems; elems.nonEmpty(); elems = elems.tail) {
 946                 scan(elems.head);
 947                 ref |= refs.discardExprs(depth);
 948             }
 949         }
 950         if (ref)
 951             refs.add(ExprRef.indirect(depth));
 952     }
 953 
 954     @Override
 955     public void visitTypeCast(JCTypeCast tree) {
 956         scan(tree.expr);
 957     }
 958 
 959     @Override
 960     public void visitConstantCaseLabel(JCConstantCaseLabel tree) {
 961     }
 962 
 963     @Override
 964     public void visitPatternCaseLabel(JCPatternCaseLabel tree) {
 965     }
 966 
 967     @Override
 968     public void visitRecordPattern(JCRecordPattern that) {
 969     }
 970 
 971     @Override
 972     public void visitTypeTest(JCInstanceOf tree) {
 973         scan(tree.expr);
 974         refs.discardExprs(depth);
 975     }
 976 
 977     @Override
 978     public void visitTypeArray(JCArrayTypeTree tree) {
 979     }
 980 
 981     @Override
 982     public void visitTypeApply(JCTypeApply tree) {
 983     }
 984 
 985     @Override
 986     public void visitTypeUnion(JCTypeUnion tree) {
 987     }
 988 
 989     @Override
 990     public void visitTypeIntersection(JCTypeIntersection tree) {
 991     }
 992 
 993     @Override
 994     public void visitTypeParameter(JCTypeParameter tree) {
 995     }
 996 
 997     @Override
 998     public void visitWildcard(JCWildcard tree) {
 999     }
1000 
1001     @Override
1002     public void visitTypeBoundKind(TypeBoundKind that) {
1003     }
1004 
1005     @Override
1006     public void visitModifiers(JCModifiers tree) {
1007     }
1008 
1009     @Override
1010     public void visitAnnotation(JCAnnotation tree) {
1011     }
1012 
1013     @Override
1014     public void visitAnnotatedType(JCAnnotatedType tree) {
1015     }
1016 
1017 //
1018 // Visitor methods - Non-Reference Stuff
1019 //
1020 
1021     @Override
1022     public void visitAssignop(JCAssignOp tree) {
1023         scan(tree.lhs);
1024         refs.discardExprs(depth);
1025         scan(tree.rhs);
1026         refs.discardExprs(depth);
1027     }
1028 
1029     @Override
1030     public void visitUnary(JCUnary tree) {
1031         scan(tree.arg);
1032         refs.discardExprs(depth);
1033     }
1034 
1035     @Override
1036     public void visitBinary(JCBinary tree) {
1037         scan(tree.lhs);
1038         refs.discardExprs(depth);
1039         scan(tree.rhs);
1040         refs.discardExprs(depth);
1041     }
1042 
1043 // Helper methods
1044 
1045     private void visitTopLevel(JCClassDecl klass, Runnable action) {
1046         Assert.check(targetClass == null);
1047         Assert.check(methodClass == null);
1048         Assert.check(depth == -1);
1049         Assert.check(refs == null);
1050         targetClass = klass;
1051         methodClass = klass;
1052         try {
1053 
1054             // Add the initial 'this' reference
1055             refs = RefSet.newEmpty();
1056             refs.add(ThisRef.direct());
1057 
1058             // Perform action
1059             this.visitScoped(false, action);
1060         } finally {
1061             Assert.check(depth == -1);
1062             methodClass = null;
1063             targetClass = null;
1064             refs = null;
1065         }
1066     }
1067 
1068     // Recurse through indirect code that might get executed later, e.g., a lambda.
1069     // We stash any pending warning and the current RefSet, then recurse into the deferred
1070     // code (still using the current RefSet) to see if it would leak. Then we restore the
1071     // pending warning and the current RefSet. Finally, if the deferred code would have
1072     // leaked, we create an indirect ExprRef because it must be holding a 'this' reference.
1073     // If the deferred code would not leak, then obviously no leak is possible, period.
1074     private <T extends JCTree> void visitDeferred(Runnable recurse) {
1075         DiagnosticPosition[] pendingWarningPrev = pendingWarning;
1076         pendingWarning = null;
1077         RefSet<Ref> refsPrev = refs.clone();
1078         boolean deferredCodeLeaks;
1079         try {
1080             recurse.run();
1081             deferredCodeLeaks = pendingWarning != null;
1082         } finally {
1083             refs = refsPrev;
1084             pendingWarning = pendingWarningPrev;
1085         }
1086         if (deferredCodeLeaks)
1087             refs.add(ExprRef.indirect(depth));
1088     }
1089 
1090     // Repeat loop as needed until the current set of references converges
1091     private <T extends JCTree> void visitLooped(T tree, Consumer<T> visitor) {
1092         visitScoped(false, () -> {
1093             while (true) {
1094                 RefSet<Ref> prevRefs = refs.clone();
1095                 visitor.accept(tree);
1096                 if (refs.equals(prevRefs))
1097                     break;
1098             }
1099         });
1100     }
1101 
1102     // Perform the given action within a new scope
1103     private void visitScoped(boolean promote, Runnable action) {
1104         pushScope();
1105         try {
1106 
1107             // Perform action
1108             Assert.check(checkInvariants(true, false));
1109             action.run();
1110             Assert.check(checkInvariants(true, promote));
1111 
1112             // "Promote" ExprRef's to the enclosing lexical scope, if requested
1113             if (promote) {
1114                 Assert.check(depth > 0);
1115                 refs.removeExprs(depth, direct -> refs.add(new ExprRef(depth - 1, direct)));
1116             }
1117         } finally {
1118             popScope();
1119         }
1120     }
1121 
1122     private void pushScope() {
1123         depth++;
1124     }
1125 
1126     private void popScope() {
1127         Assert.check(depth >= 0);
1128         depth--;
1129         refs.removeIf(ref -> ref.getDepth() > depth);
1130     }
1131 
1132     // Note a possible 'this' reference leak at the specified location
1133     private void leakAt(JCTree tree) {
1134 
1135         // Generate at most one warning per statement
1136         if (pendingWarning != null)
1137             return;
1138 
1139         // Snapshot the current stack trace
1140         callStack.push(tree.pos());
1141         pendingWarning = callStack.toArray(new DiagnosticPosition[0]);
1142         callStack.pop();
1143     }
1144 
1145     // Copy pending warning, if any, to the warning list and reset
1146     private boolean copyPendingWarning() {
1147         if (pendingWarning == null)
1148             return false;
1149         warningList.add(pendingWarning);
1150         pendingWarning = null;
1151         return true;
1152     }
1153 
1154     // Does the symbol correspond to a parameter or local variable (not a field)?
1155     private boolean isParamOrVar(Symbol sym) {
1156         return sym != null &&
1157             sym.kind == VAR &&
1158             (sym.owner.kind == MTH || sym.owner.kind == VAR);
1159     }
1160 
1161     // When scanning nodes we can be in one of two modes:
1162     //  (a) Looking for constructors - we do not recurse into any code blocks
1163     //  (b) Analyzing a constructor - we are tracing its possible execution paths
1164     private boolean isAnalyzing() {
1165         return targetClass != null;
1166     }
1167 
1168 // Debugging
1169 
1170     // Invariant checks
1171     private boolean checkInvariants(boolean analyzing, boolean allowExpr) {
1172         Assert.check(analyzing == isAnalyzing());
1173         if (isAnalyzing()) {
1174             Assert.check(methodClass != null);
1175             Assert.check(targetClass != null);
1176             Assert.check(refs != null);
1177             Assert.check(depth >= 0);
1178             Assert.check(refs.stream().noneMatch(ref -> ref.getDepth() > depth));
1179             Assert.check(allowExpr || !refs.contains(ExprRef.direct(depth)));
1180             Assert.check(allowExpr || !refs.contains(ExprRef.indirect(depth)));
1181         } else {
1182             Assert.check(targetClass == null);
1183             Assert.check(refs == null);
1184             Assert.check(depth == -1);
1185             Assert.check(callStack.isEmpty());
1186             Assert.check(pendingWarning == null);
1187             Assert.check(invocations.isEmpty());
1188         }
1189         return true;
1190     }
1191 
1192 // Ref's
1193 
1194     /** Represents a location that could possibly hold a 'this' reference.
1195      *
1196      *  <p>
1197      *  If not "direct", the reference is found through at least one indirection.
1198      */
1199     private abstract static class Ref {
1200 
1201         private final int depth;
1202         private final boolean direct;
1203 
1204         Ref(int depth, boolean direct) {
1205             this.depth = depth;
1206             this.direct = direct;
1207         }
1208 
1209         public int getDepth() {
1210             return depth;
1211         }
1212 
1213         public boolean isDirect() {
1214             return direct;
1215         }
1216 
1217         @Override
1218         public int hashCode() {
1219             return getClass().hashCode()
1220                 ^ Integer.hashCode(depth)
1221                 ^ Boolean.hashCode(direct);
1222         }
1223 
1224         @Override
1225         public boolean equals(Object obj) {
1226             if (obj == this)
1227                 return true;
1228             if (obj == null || obj.getClass() != getClass())
1229                 return false;
1230             Ref that = (Ref)obj;
1231             return depth == that.depth
1232               && direct == that.direct;
1233         }
1234 
1235         @Override
1236         public String toString() {
1237             ArrayList<String> properties = new ArrayList<>();
1238             addProperties(properties);
1239             return getClass().getSimpleName()
1240               + "[" + properties.stream().collect(Collectors.joining(",")) + "]";
1241         }
1242 
1243         protected void addProperties(ArrayList<String> properties) {
1244             properties.add("depth=" + depth);
1245             properties.add(direct ? "direct" : "indirect");
1246         }
1247     }
1248 
1249     /** A reference from the current 'this' instance.
1250      */
1251     private static class ThisRef extends Ref {
1252 
1253         ThisRef(boolean direct) {
1254             super(0, direct);
1255         }
1256 
1257         public static ThisRef direct() {
1258             return new ThisRef(true);
1259         }
1260 
1261         public static ThisRef indirect() {
1262             return new ThisRef(false);
1263         }
1264     }
1265 
1266     /** A reference from the current outer 'this' instance.
1267      */
1268     private static class OuterRef extends Ref {
1269 
1270         OuterRef(boolean direct) {
1271             super(0, direct);
1272         }
1273     }
1274 
1275     /** A reference from the expression that was just evaluated.
1276      *  In other words, a reference that's sitting on top of the stack.
1277      */
1278     private static class ExprRef extends Ref {
1279 
1280         ExprRef(int depth, boolean direct) {
1281             super(depth, direct);
1282         }
1283 
1284         public static ExprRef direct(int depth) {
1285             return new ExprRef(depth, true);
1286         }
1287 
1288         public static ExprRef indirect(int depth) {
1289             return new ExprRef(depth, false);
1290         }
1291     }
1292 
1293     /** A reference from the return value of the current method being "invoked".
1294      */
1295     private static class ReturnRef extends Ref {
1296 
1297         ReturnRef(boolean direct) {
1298             super(0, direct);
1299         }
1300     }
1301 
1302     /** A reference from the yield value of the current switch expression.
1303      */
1304     private static class YieldRef extends Ref {
1305 
1306         YieldRef(boolean direct) {
1307             super(0, direct);
1308         }
1309     }
1310 
1311     /** A reference from a variable.
1312      */
1313     private static class VarRef extends Ref {
1314 
1315         private final VarSymbol sym;
1316 
1317         VarRef(VarSymbol sym, boolean direct) {
1318             super(0, direct);
1319             this.sym = sym;
1320         }
1321 
1322         public VarSymbol getSymbol() {
1323             return sym;
1324         }
1325 
1326         public static VarRef direct(VarSymbol sym) {
1327             return new VarRef(sym, true);
1328         }
1329 
1330         public static VarRef indirect(VarSymbol sym) {
1331             return new VarRef(sym, false);
1332         }
1333 
1334         @Override
1335         public int hashCode() {
1336             return super.hashCode()
1337                 ^ Objects.hashCode(sym);
1338         }
1339 
1340         @Override
1341         public boolean equals(Object obj) {
1342             if (obj == this)
1343                 return true;
1344             if (!super.equals(obj))
1345                 return false;
1346             VarRef that = (VarRef)obj;
1347             return Objects.equals(sym, that.sym);
1348         }
1349 
1350         @Override
1351         protected void addProperties(ArrayList<String> properties) {
1352             super.addProperties(properties);
1353             properties.add("sym=" + sym);
1354         }
1355     }
1356 
1357 // RefSet
1358 
1359     /** Contains locations currently known to hold a possible 'this' reference.
1360      */
1361     @SuppressWarnings("serial")
1362     private static class RefSet<T extends Ref> extends HashSet<T> {
1363 
1364         public static <T extends Ref> RefSet<T> newEmpty() {
1365             return new RefSet<>();
1366         }
1367 
1368         /**
1369          * Discard any {@link ExprRef}'s at the specified depth.
1370          * Do this when discarding whatever is on top of the stack.
1371          */
1372         public boolean discardExprs(int depth) {
1373             return remove(ExprRef.direct(depth)) | remove(ExprRef.indirect(depth));
1374         }
1375 
1376         /**
1377          * Extract any {@link ExprRef}'s at the specified depth.
1378          */
1379         public RefSet<ExprRef> removeExprs(int depth) {
1380             return Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth))
1381               .filter(this::remove)
1382               .collect(Collectors.toCollection(RefSet::new));
1383         }
1384 
1385         /**
1386          * Extract any {@link ExprRef}'s at the specified depth and do something with them.
1387          */
1388         public void removeExprs(int depth, Consumer<? super Boolean> handler) {
1389             Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth))
1390               .filter(this::remove)
1391               .map(ExprRef::isDirect)
1392               .forEach(handler);
1393         }
1394 
1395         /**
1396          * Replace any references of the given type.
1397          */
1398         public void replace(Class<? extends Ref> type, Function<Boolean, ? extends T> mapper) {
1399             final List<Ref> oldRefs = this.stream()
1400               .filter(type::isInstance)
1401               .collect(List.collector());             // avoid ConcurrentModificationException
1402             this.removeAll(oldRefs);
1403             oldRefs.stream()
1404               .map(Ref::isDirect)
1405               .map(mapper)
1406               .forEach(this::add);
1407         }
1408 
1409         /**
1410          * Replace any {@link ExprRef}'s at the specified depth.
1411          */
1412         public void replaceExprs(int depth, Function<Boolean, ? extends T> mapper) {
1413             removeExprs(depth, direct -> add(mapper.apply(direct)));
1414         }
1415 
1416         /**
1417          * Find references of the given type, map them, and add them to {@code dest}.
1418          */
1419         public <S extends Ref> void mapInto(RefSet<S> dest, Class<? extends Ref> type,
1420                 Function<Boolean, ? extends S> mapper) {
1421             final List<S> newRefs = this.stream()
1422               .filter(type::isInstance)
1423               .map(Ref::isDirect)
1424               .map(mapper)
1425               .collect(List.collector());             // avoid ConcurrentModificationException
1426             dest.addAll(newRefs);
1427         }
1428 
1429         @Override
1430         @SuppressWarnings("unchecked")
1431         public RefSet<T> clone() {
1432             return (RefSet<T>)super.clone();
1433         }
1434     }
1435 
1436 // MethodInfo
1437 
1438     // Information about a constructor or method in the compilation unit
1439     private record MethodInfo(
1440         JCClassDecl declaringClass,     // the class declaring "declaration"
1441         JCMethodDecl declaration,       // the method or constructor itself
1442         boolean analyzable,             // it's a constructor that we should analyze
1443         boolean invokable) {            // it may be safely "invoked" during analysis
1444     }
1445 }