On Mon, 16 Jan 2023 12:51:49 GMT, Maurizio Cimadamore <mcimadam...@openjdk.org> wrote:
>> The number of times around any single loop is bounded by the number of new >> references that can possibly be created during the analysis of that loop. >> >> That number is at most 2 * (1 + 1 + 1 + 1 + N) where N is the number of >> parameters and/or variables declared in that scope (the 2 is for direct or >> indirect, and the 1's are for each of the singleton reference types >> `ThisRef`, `OuterRef`, `ExprRef`, and `ReturnRef`). >> >> If you have nested scopes A, B, C each with Na, Nb, and Nc variables >> declared therein (respectively), then the bound would be something like 2 * >> (1 + 1 + 1 + 1 + Na + (Na * Nb) + (Na * Nb * Nc)) worst case (waving hands >> here). > > I have played a bit with the patch, trying to disable certain features. The > main reason to deal with loops and recursive calls the way the patch does is > to eliminate false positives. If we see a loop, and we can't perform the > analysis multiple times (as the PR does), then we'd have to conclude that the > loop can be potentially escaping (as we might have missed an local var > update). Same goes for recursive calls (e.g. a recursive call would be > treated as escaping). Same goes for analyzing invoked methods that fall in > the same compilation unit. If we don't do that, more methods might look as > "escaping". > > So, here's what I found: > > * compiling the JDK with the current patch produces 2376 warnings > * disabling support for recursive calls produces 2427 warnings > * treating all method calls inside a loop to be escaping produces 2464 > warnings > * disabling scanning of methods in same compilation unit produces 4317 > warnings > > (Note: the patches I used to do this analysis are a bit blunt, and perhaps > can be made a bit less conservative, which might result in less false > positives added - I just went for the simplest possible approach, just to > test the contribute of each analysis). > > This seems to suggest that even a blunt approach to deal with recursion and > loop does not result in a significant increase of false positives (~2% more). > That said, disabling scanning of methods in the same compilation unit results > in a big impact in terms of false positive (~100% increase). > > So, I'm pretty confident that, should performance problems arise, we could > probably dial back the analysis not to do loops (or to do them in a bounded > way, as Vicente suggest, which is much better of what I tried here) - and > that will probably give us the same results we have today (or a very very > minor increase of false positives). But scanning of dependent methods in same > compilation unit seems to be more or less a must-have. Thanks for doing that analysis - very interesting. It looks like you might be counting the "here via invocation" lines as separate warnings. These are really part of the previous `possible 'this' escape` line, e.g.: .../java/awt/Frame.java:429: Note: possible 'this' escape before subclass is fully initialized init(title, null); ^ .../java/awt/Frame.java:460: Note: previous possible 'this' escape happens here via invocation SunToolkit.checkAndSetPolicy(this); ^ Semi-related... I was also curious what would happen if we changed the semantics of the warning from "subclass must be in a separate compilation unit" to "subclass must be in a separate package". I'm not proposing we change this definition, and obviously there are trade-offs in where you draw this boundary, but was curious anywan (at one point I thought it might be worth having an option for this, e.g., with variants like `-Xlint:this-escape` vs. `-Xlint:this-escape:package`, or even `-Xlint:this-escape:module`, etc.). In any case, here are the results: * Warnings for subclass in separate compilation unit: 2093 * Warnings for subclass in separate package: 1334 So a 36% reduction - not too surprising since the JDK includes a bunch of non-public implementation classes. FWIW this is the patch I used: diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java index e81df22b017..f309a4742aa 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java @@ -216,24 +217,24 @@ class ThisEscapeAnalyzer extends TreeScanner { private Lint lint = ThisEscapeAnalyzer.this.lint; private JCClassDecl currentClass; - private boolean privateOuter; + private boolean nonPublicOuter; @Override public void visitClassDef(JCClassDecl tree) { JCClassDecl currentClassPrev = currentClass; - boolean privateOuterPrev = privateOuter; + boolean nonPublicOuterPrev = nonPublicOuter; Lint lintPrev = lint; lint = lint.augment(tree.sym); try { currentClass = tree; - privateOuter |= tree.sym.isAnonymous(); - privateOuter |= (tree.mods.flags & Flags.PRIVATE) != 0; + nonPublicOuter |= tree.sym.isAnonymous(); + nonPublicOuter |= (tree.mods.flags & Flags.PUBLIC) == 0; // Recurse super.visitClassDef(tree); } finally { currentClass = currentClassPrev; - privateOuter = privateOuterPrev; + nonPublicOuter = nonPublicOuterPrev; lint = lintPrev; } } @@ -268,10 +269,10 @@ class ThisEscapeAnalyzer extends TreeScanner { // Determine if this is a constructor we should analyze boolean analyzable = currentClassIsExternallyExtendable() && TreeInfo.isConstructor(tree) && - !tree.sym.isPrivate() && + (tree.sym.isPublic() || (tree.sym.flags() & Flags.PROTECTED) != 0) && !suppressed.contains(tree.sym); - // Determine if this method is "invokable" in an analysis (can't be overridden) + // Determine if this method is "invokable" in an analysis (can't be overridden outside package) boolean invokable = !currentClassIsExternallyExtendable() || TreeInfo.isConstructor(tree) || (tree.mods.flags & (Flags.STATIC | Flags.PRIVATE | Flags.FINAL)) != 0; @@ -286,12 +287,13 @@ class ThisEscapeAnalyzer extends TreeScanner { } } - // Determines if the current class could be extended in some external compilation unit + // Determines if the current class could be extended in some other package private boolean currentClassIsExternallyExtendable() { return !currentClass.sym.isFinal() && + currentClass.sym.isPublic() && !(currentClass.sym.isSealed() && currentClass.permitting.isEmpty()) && !(currentClass.sym.owner.kind == MTH) && - !privateOuter; + !nonPublicOuter; } }.scan(env.tree); ------------- PR: https://git.openjdk.org/jdk/pull/11874