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         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     // When scanning nodes we can be in one of two modes:
1166     //  (a) Looking for constructors - we do not recurse into any code blocks
1167     //  (b) Analyzing a constructor - we are tracing its possible execution paths
1168     private boolean isAnalyzing() {
1169         return targetClass != null;
1170     }
1171 
1172 // Debugging
1173 
1174     // Invariant checks
1175     private boolean checkInvariants(boolean analyzing, boolean allowExpr) {
1176         Assert.check(analyzing == isAnalyzing());
1177         if (isAnalyzing()) {
1178             Assert.check(methodClass != null);
1179             Assert.check(targetClass != null);
1180             Assert.check(refs != null);
1181             Assert.check(depth >= 0);
1182             Assert.check(refs.stream().noneMatch(ref -> ref.getDepth() > depth));
1183             Assert.check(allowExpr || !refs.contains(ExprRef.direct(depth)));
1184             Assert.check(allowExpr || !refs.contains(ExprRef.indirect(depth)));
1185         } else {
1186             Assert.check(targetClass == null);
1187             Assert.check(refs == null);
1188             Assert.check(depth == -1);
1189             Assert.check(callStack.isEmpty());
1190             Assert.check(pendingWarning == null);
1191             Assert.check(invocations.isEmpty());
1192         }
1193         return true;
1194     }
1195 
1196 // Ref's
1197 
1198     /** Represents a location that could possibly hold a 'this' reference.
1199      *
1200      *  <p>
1201      *  If not "direct", the reference is found through at least one indirection.
1202      */
1203     private abstract static class Ref {
1204 
1205         private final int depth;
1206         private final boolean direct;
1207 
1208         Ref(int depth, boolean direct) {
1209             this.depth = depth;
1210             this.direct = direct;
1211         }
1212 
1213         public int getDepth() {
1214             return depth;
1215         }
1216 
1217         public boolean isDirect() {
1218             return direct;
1219         }
1220 
1221         @Override
1222         public int hashCode() {
1223             return getClass().hashCode()
1224                 ^ Integer.hashCode(depth)
1225                 ^ Boolean.hashCode(direct);
1226         }
1227 
1228         @Override
1229         public boolean equals(Object obj) {
1230             if (obj == this)
1231                 return true;
1232             if (obj == null || obj.getClass() != getClass())
1233                 return false;
1234             Ref that = (Ref)obj;
1235             return depth == that.depth
1236               && direct == that.direct;
1237         }
1238 
1239         @Override
1240         public String toString() {
1241             ArrayList<String> properties = new ArrayList<>();
1242             addProperties(properties);
1243             return getClass().getSimpleName()
1244               + "[" + properties.stream().collect(Collectors.joining(",")) + "]";
1245         }
1246 
1247         protected void addProperties(ArrayList<String> properties) {
1248             properties.add("depth=" + depth);
1249             properties.add(direct ? "direct" : "indirect");
1250         }
1251     }
1252 
1253     /** A reference from the current 'this' instance.
1254      */
1255     private static class ThisRef extends Ref {
1256 
1257         ThisRef(boolean direct) {
1258             super(0, direct);
1259         }
1260 
1261         public static ThisRef direct() {
1262             return new ThisRef(true);
1263         }
1264 
1265         public static ThisRef indirect() {
1266             return new ThisRef(false);
1267         }
1268     }
1269 
1270     /** A reference from the current outer 'this' instance.
1271      */
1272     private static class OuterRef extends Ref {
1273 
1274         OuterRef(boolean direct) {
1275             super(0, direct);
1276         }
1277     }
1278 
1279     /** A reference from the expression that was just evaluated.
1280      *  In other words, a reference that's sitting on top of the stack.
1281      */
1282     private static class ExprRef extends Ref {
1283 
1284         ExprRef(int depth, boolean direct) {
1285             super(depth, direct);
1286         }
1287 
1288         public static ExprRef direct(int depth) {
1289             return new ExprRef(depth, true);
1290         }
1291 
1292         public static ExprRef indirect(int depth) {
1293             return new ExprRef(depth, false);
1294         }
1295     }
1296 
1297     /** A reference from the return value of the current method being "invoked".
1298      */
1299     private static class ReturnRef extends Ref {
1300 
1301         ReturnRef(boolean direct) {
1302             super(0, direct);
1303         }
1304     }
1305 
1306     /** A reference from the yield value of the current switch expression.
1307      */
1308     private static class YieldRef extends Ref {
1309 
1310         YieldRef(boolean direct) {
1311             super(0, direct);
1312         }
1313     }
1314 
1315     /** A reference from a variable.
1316      */
1317     private static class VarRef extends Ref {
1318 
1319         private final VarSymbol sym;
1320 
1321         VarRef(VarSymbol sym, boolean direct) {
1322             super(0, direct);
1323             this.sym = sym;
1324         }
1325 
1326         public VarSymbol getSymbol() {
1327             return sym;
1328         }
1329 
1330         public static VarRef direct(VarSymbol sym) {
1331             return new VarRef(sym, true);
1332         }
1333 
1334         public static VarRef indirect(VarSymbol sym) {
1335             return new VarRef(sym, false);
1336         }
1337 
1338         @Override
1339         public int hashCode() {
1340             return super.hashCode()
1341                 ^ Objects.hashCode(sym);
1342         }
1343 
1344         @Override
1345         public boolean equals(Object obj) {
1346             if (obj == this)
1347                 return true;
1348             if (!super.equals(obj))
1349                 return false;
1350             VarRef that = (VarRef)obj;
1351             return Objects.equals(sym, that.sym);
1352         }
1353 
1354         @Override
1355         protected void addProperties(ArrayList<String> properties) {
1356             super.addProperties(properties);
1357             properties.add("sym=" + sym);
1358         }
1359     }
1360 
1361 // RefSet
1362 
1363     /** Contains locations currently known to hold a possible 'this' reference.
1364      */
1365     @SuppressWarnings("serial")
1366     private static class RefSet<T extends Ref> extends HashSet<T> {
1367 
1368         public static <T extends Ref> RefSet<T> newEmpty() {
1369             return new RefSet<>();
1370         }
1371 
1372         /**
1373          * Discard any {@link ExprRef}'s at the specified depth.
1374          * Do this when discarding whatever is on top of the stack.
1375          */
1376         public boolean discardExprs(int depth) {
1377             return remove(ExprRef.direct(depth)) | remove(ExprRef.indirect(depth));
1378         }
1379 
1380         /**
1381          * Extract any {@link ExprRef}'s at the specified depth.
1382          */
1383         public RefSet<ExprRef> removeExprs(int depth) {
1384             return Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth))
1385               .filter(this::remove)
1386               .collect(Collectors.toCollection(RefSet::new));
1387         }
1388 
1389         /**
1390          * Extract any {@link ExprRef}'s at the specified depth and do something with them.
1391          */
1392         public void removeExprs(int depth, Consumer<? super Boolean> handler) {
1393             Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth))
1394               .filter(this::remove)
1395               .map(ExprRef::isDirect)
1396               .forEach(handler);
1397         }
1398 
1399         /**
1400          * Replace any references of the given type.
1401          */
1402         public void replace(Class<? extends Ref> type, Function<Boolean, ? extends T> mapper) {
1403             final List<Ref> oldRefs = this.stream()
1404               .filter(type::isInstance)
1405               .collect(List.collector());             // avoid ConcurrentModificationException
1406             this.removeAll(oldRefs);
1407             oldRefs.stream()
1408               .map(Ref::isDirect)
1409               .map(mapper)
1410               .forEach(this::add);
1411         }
1412 
1413         /**
1414          * Replace any {@link ExprRef}'s at the specified depth.
1415          */
1416         public void replaceExprs(int depth, Function<Boolean, ? extends T> mapper) {
1417             removeExprs(depth, direct -> add(mapper.apply(direct)));
1418         }
1419 
1420         /**
1421          * Find references of the given type, map them, and add them to {@code dest}.
1422          */
1423         public <S extends Ref> void mapInto(RefSet<S> dest, Class<? extends Ref> type,
1424                 Function<Boolean, ? extends S> mapper) {
1425             final List<S> newRefs = this.stream()
1426               .filter(type::isInstance)
1427               .map(Ref::isDirect)
1428               .map(mapper)
1429               .collect(List.collector());             // avoid ConcurrentModificationException
1430             dest.addAll(newRefs);
1431         }
1432 
1433         @Override
1434         @SuppressWarnings("unchecked")
1435         public RefSet<T> clone() {
1436             return (RefSet<T>)super.clone();
1437         }
1438     }
1439 
1440 // MethodInfo
1441 
1442     // Information about a constructor or method in the compilation unit
1443     private record MethodInfo(
1444         JCClassDecl declaringClass,     // the class declaring "declaration"
1445         JCMethodDecl declaration,       // the method or constructor itself
1446         boolean analyzable,             // it's a constructor that we should analyze
1447         boolean invokable) {            // it may be safely "invoked" during analysis
1448     }
1449 }