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 }
--- EOF ---